Merge pull request #853 from mwhudson/network-prep
prepare network code for upcoming client server split
This commit is contained in:
commit
68f23ffb5a
|
@ -312,7 +312,7 @@ class FilesystemController(SubiquityTuiController):
|
|||
def _action_clean_level(self, level):
|
||||
return raidlevels_by_value[level]
|
||||
|
||||
def _answers_action(self, action):
|
||||
async def _answers_action(self, action):
|
||||
from subiquitycore.ui.stretchy import StretchyOverlay
|
||||
from subiquity.ui.views.filesystem.delete import ConfirmDeleteStretchy
|
||||
log.debug("_answers_action %r", action)
|
||||
|
@ -333,28 +333,31 @@ class FilesystemController(SubiquityTuiController):
|
|||
if action.get("submit", True):
|
||||
body.stretchy.done()
|
||||
else:
|
||||
yield from self._enter_form_data(
|
||||
body.stretchy.form,
|
||||
action['data'],
|
||||
action.get("submit", True))
|
||||
async for _ in self._enter_form_data(
|
||||
body.stretchy.form,
|
||||
action['data'],
|
||||
action.get("submit", True)):
|
||||
pass
|
||||
elif action['action'] == 'create-raid':
|
||||
self.ui.body.create_raid()
|
||||
yield
|
||||
body = self.ui.body._w
|
||||
yield from self._enter_form_data(
|
||||
body.stretchy.form,
|
||||
action['data'],
|
||||
action.get("submit", True),
|
||||
clean_suffix='raid')
|
||||
async for _ in self._enter_form_data(
|
||||
body.stretchy.form,
|
||||
action['data'],
|
||||
action.get("submit", True),
|
||||
clean_suffix='raid'):
|
||||
pass
|
||||
elif action['action'] == 'create-vg':
|
||||
self.ui.body.create_vg()
|
||||
yield
|
||||
body = self.ui.body._w
|
||||
yield from self._enter_form_data(
|
||||
body.stretchy.form,
|
||||
action['data'],
|
||||
action.get("submit", True),
|
||||
clean_suffix='vg')
|
||||
async for _ in self._enter_form_data(
|
||||
body.stretchy.form,
|
||||
action['data'],
|
||||
action.get("submit", True),
|
||||
clean_suffix='vg'):
|
||||
pass
|
||||
elif action['action'] == 'done':
|
||||
if not self.ui.body.done.enabled:
|
||||
raise Exception("answers did not provide complete fs config")
|
||||
|
@ -367,7 +370,8 @@ class FilesystemController(SubiquityTuiController):
|
|||
if self.answers['guided']:
|
||||
self.finish(self.app.confirm_install())
|
||||
if self.answers['manual']:
|
||||
self._run_iterator(self._run_actions(self.answers['manual']))
|
||||
self.app.aio_loop.create_task(
|
||||
self._run_actions(self.answers['manual']))
|
||||
self.answers['manual'] = []
|
||||
|
||||
def guided(self):
|
||||
|
|
|
@ -171,10 +171,6 @@ class NetworkController(NetworkController, SubiquityTuiController):
|
|||
if not self.interactive():
|
||||
raise
|
||||
|
||||
def run_answers(self):
|
||||
# handled elsewhere
|
||||
pass
|
||||
|
||||
def done(self):
|
||||
self.configured()
|
||||
super().done()
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import abc
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
|
@ -34,6 +35,7 @@ from subiquitycore.models.network import (
|
|||
WLANConfig,
|
||||
)
|
||||
from subiquitycore import netplan
|
||||
from subiquitycore.controller import BaseController
|
||||
from subiquitycore.tuicontroller import TuiController
|
||||
from subiquitycore.ui.stretchy import StretchyOverlay
|
||||
from subiquitycore.ui.views.network import (
|
||||
|
@ -122,15 +124,13 @@ network:
|
|||
'''
|
||||
|
||||
|
||||
class NetworkController(TuiController):
|
||||
class BaseNetworkController(BaseController):
|
||||
|
||||
model_name = "network"
|
||||
root = "/"
|
||||
|
||||
def __init__(self, app):
|
||||
super().__init__(app)
|
||||
self.view = None
|
||||
self.view_shown = False
|
||||
self.apply_config_task = SingleInstanceTask(self._apply_config)
|
||||
if self.opts.dry_run:
|
||||
self.root = os.path.abspath(".subiquity")
|
||||
|
@ -150,28 +150,6 @@ class NetworkController(TuiController):
|
|||
def parse_netplan_configs(self):
|
||||
self.model.parse_netplan_configs(self.root)
|
||||
|
||||
def update_default_routes(self, routes):
|
||||
if routes:
|
||||
self.signal.emit_signal('network-change')
|
||||
if self.view:
|
||||
self.view.update_default_routes(routes)
|
||||
|
||||
def new_link(self, netdev):
|
||||
if self.view is not None:
|
||||
self.view.new_link(netdev.netdev_info())
|
||||
|
||||
def update_link(self, netdev):
|
||||
for v, e in netdev.dhcp_events.items():
|
||||
if netdev.dhcp_addresses()[v]:
|
||||
netdev.set_dhcp_state(v, DHCPState.CONFIGURED)
|
||||
e.set()
|
||||
if self.view is not None:
|
||||
self.view.update_link(netdev.netdev_info())
|
||||
|
||||
def del_link(self, netdev):
|
||||
if self.view is not None:
|
||||
self.view.del_link(netdev.netdev_info())
|
||||
|
||||
def start(self):
|
||||
self._observer_handles = []
|
||||
self.observer, self._observer_fds = (
|
||||
|
@ -204,72 +182,6 @@ class NetworkController(TuiController):
|
|||
return
|
||||
self.observer.data_ready(fd)
|
||||
|
||||
def _action_get(self, id):
|
||||
dev_spec = id[0].split()
|
||||
dev = None
|
||||
if dev_spec[0] == "interface":
|
||||
if dev_spec[1] == "index":
|
||||
dev = self.model.get_all_netdevs()[int(dev_spec[2])]
|
||||
elif dev_spec[1] == "name":
|
||||
dev = self.model.get_netdev_by_name(dev_spec[2])
|
||||
if dev is None:
|
||||
raise Exception("could not resolve {}".format(id))
|
||||
if len(id) > 1:
|
||||
part, index = id[1].split()
|
||||
if part == "part":
|
||||
return dev.partitions()[int(index)]
|
||||
else:
|
||||
return dev
|
||||
raise Exception("could not resolve {}".format(id))
|
||||
|
||||
def _action_clean_interfaces(self, devices):
|
||||
r = [self._action_get(device).name for device in devices]
|
||||
log.debug("%s", r)
|
||||
return r
|
||||
|
||||
def _answers_action(self, action):
|
||||
log.debug("_answers_action %r", action)
|
||||
if 'obj' in action:
|
||||
obj = self._action_get(action['obj']).netdev_info()
|
||||
meth = getattr(
|
||||
self.ui.body,
|
||||
"_action_{}".format(action['action']))
|
||||
action_obj = getattr(NetDevAction, action['action'])
|
||||
table = self.ui.body.dev_name_to_table[obj.name]
|
||||
self.ui.body._action(None, (action_obj, meth), table)
|
||||
yield
|
||||
body = self.ui.body._w
|
||||
if not isinstance(body, StretchyOverlay):
|
||||
return
|
||||
for k, v in action.items():
|
||||
if not k.endswith('data'):
|
||||
continue
|
||||
form_name = "form"
|
||||
submit_key = "submit"
|
||||
if '-' in k:
|
||||
prefix = k.split('-')[0]
|
||||
form_name = prefix + "_form"
|
||||
submit_key = prefix + "-submit"
|
||||
yield from self._enter_form_data(
|
||||
getattr(body.stretchy, form_name),
|
||||
v,
|
||||
action.get(submit_key, True))
|
||||
elif action['action'] == 'create-bond':
|
||||
self.ui.body._create_bond()
|
||||
yield
|
||||
body = self.ui.body._w
|
||||
data = action['data'].copy()
|
||||
if 'devices' in data:
|
||||
data['interfaces'] = data.pop('devices')
|
||||
yield from self._enter_form_data(
|
||||
body.stretchy.form,
|
||||
data,
|
||||
action.get("submit", True))
|
||||
elif action['action'] == 'done':
|
||||
self.ui.body.done()
|
||||
else:
|
||||
raise Exception("could not process action {}".format(action))
|
||||
|
||||
def update_initial_configs(self):
|
||||
# Any device that does not have a (global) address by the time
|
||||
# we get to the network screen is marked as disabled, with an
|
||||
|
@ -368,13 +280,13 @@ class NetworkController(TuiController):
|
|||
|
||||
self._write_config()
|
||||
|
||||
if not silent and self.view:
|
||||
self.view.show_apply_spinner()
|
||||
if not silent:
|
||||
self.apply_starting()
|
||||
|
||||
try:
|
||||
def error(stage):
|
||||
if not silent and self.view:
|
||||
self.view.show_network_error(stage)
|
||||
if not silent:
|
||||
self.apply_error(stage)
|
||||
|
||||
if self.opts.dry_run:
|
||||
delay = 1/self.app.scale_factor
|
||||
|
@ -423,15 +335,8 @@ class NetworkController(TuiController):
|
|||
['systemctl', 'start', 'systemd-networkd.socket'],
|
||||
check=False)
|
||||
finally:
|
||||
if not silent and self.view:
|
||||
self.view.hide_apply_spinner()
|
||||
|
||||
if self.answers.get('accept-default', False):
|
||||
self.done()
|
||||
elif self.answers.get('actions', False):
|
||||
actions = self.answers['actions']
|
||||
self.answers.clear()
|
||||
self._run_iterator(self._run_actions(actions))
|
||||
if not silent:
|
||||
self.apply_stopping()
|
||||
|
||||
if not dhcp_events:
|
||||
return
|
||||
|
@ -449,32 +354,6 @@ class NetworkController(TuiController):
|
|||
dev.set_dhcp_state(v, DHCPState.TIMED_OUT)
|
||||
self.network_event_receiver.update_link(dev.ifindex)
|
||||
|
||||
def make_ui(self):
|
||||
if not self.view_shown:
|
||||
self.update_initial_configs()
|
||||
netdev_infos = [
|
||||
dev.netdev_info() for dev in self.model.get_all_netdevs()
|
||||
]
|
||||
self.view = NetworkView(self, netdev_infos)
|
||||
if not self.view_shown:
|
||||
self.apply_config(silent=True)
|
||||
self.view_shown = True
|
||||
self.view.update_default_routes(
|
||||
self.network_event_receiver.default_routes)
|
||||
return self.view
|
||||
|
||||
def end_ui(self):
|
||||
self.view = None
|
||||
|
||||
def done(self):
|
||||
log.debug("NetworkController.done next_screen")
|
||||
self.model.has_network = bool(
|
||||
self.network_event_receiver.default_routes)
|
||||
self.app.next_screen()
|
||||
|
||||
def cancel(self):
|
||||
self.app.prev_screen()
|
||||
|
||||
def set_static_config(self, dev_name: str, ip_version: int,
|
||||
static_config: StaticConfig) -> None:
|
||||
dev = self.model.get_netdev_by_name(dev_name)
|
||||
|
@ -575,3 +454,195 @@ class NetworkController(TuiController):
|
|||
device = self.model.get_netdev_by_name(dev_name)
|
||||
self.observer.trigger_scan(device.ifindex)
|
||||
self.update_link(device)
|
||||
|
||||
@abc.abstractmethod
|
||||
def apply_starting(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def apply_stopping(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def apply_error(self, stage):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_default_routes(self, routes):
|
||||
if routes:
|
||||
self.signal.emit_signal('network-change')
|
||||
|
||||
@abc.abstractmethod
|
||||
def new_link(self, netdev):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_link(self, netdev):
|
||||
for v, e in netdev.dhcp_events.items():
|
||||
if netdev.dhcp_addresses()[v]:
|
||||
netdev.set_dhcp_state(v, DHCPState.CONFIGURED)
|
||||
e.set()
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def del_link(self, netdev):
|
||||
pass
|
||||
|
||||
|
||||
class NetworkAnswersMixin:
|
||||
|
||||
def run_answers(self):
|
||||
if self.answers.get('accept-default', False):
|
||||
self.done()
|
||||
elif self.answers.get('actions', False):
|
||||
actions = self.answers['actions']
|
||||
self.answers.clear()
|
||||
self.app.aio_loop.create_task(
|
||||
self._run_actions(actions))
|
||||
|
||||
def _action_get(self, id):
|
||||
dev_spec = id[0].split()
|
||||
if dev_spec[0] == "interface":
|
||||
if dev_spec[1] == "index":
|
||||
name = self.view.cur_netdev_names[int(dev_spec[2])]
|
||||
elif dev_spec[1] == "name":
|
||||
name = dev_spec[2]
|
||||
return self.view.dev_name_to_table[name]
|
||||
raise Exception("could not resolve {}".format(id))
|
||||
|
||||
def _action_clean_interfaces(self, devices):
|
||||
r = [self._action_get(device).dev_info.name for device in devices]
|
||||
log.debug("%s", r)
|
||||
return r
|
||||
|
||||
async def _answers_action(self, action):
|
||||
log.debug("_answers_action %r", action)
|
||||
if 'obj' in action:
|
||||
table = self._action_get(action['obj'])
|
||||
meth = getattr(
|
||||
self.ui.body,
|
||||
"_action_{}".format(action['action']))
|
||||
action_obj = getattr(NetDevAction, action['action'])
|
||||
self.ui.body._action(None, (action_obj, meth), table)
|
||||
yield
|
||||
body = self.ui.body._w
|
||||
if action['action'] == "DELETE":
|
||||
t = 0.0
|
||||
while table.dev_info.name in self.view.cur_netdev_names:
|
||||
await asyncio.sleep(0.1)
|
||||
t += 0.1
|
||||
if t > 5.0:
|
||||
raise Exception(
|
||||
"interface did not disappear in 5 secs")
|
||||
log.debug("waited %s for interface to disappear", t)
|
||||
if not isinstance(body, StretchyOverlay):
|
||||
return
|
||||
for k, v in action.items():
|
||||
if not k.endswith('data'):
|
||||
continue
|
||||
form_name = "form"
|
||||
submit_key = "submit"
|
||||
if '-' in k:
|
||||
prefix = k.split('-')[0]
|
||||
form_name = prefix + "_form"
|
||||
submit_key = prefix + "-submit"
|
||||
async for _ in self._enter_form_data(
|
||||
getattr(body.stretchy, form_name),
|
||||
v,
|
||||
action.get(submit_key, True)):
|
||||
pass
|
||||
elif action['action'] == 'create-bond':
|
||||
self.ui.body._create_bond()
|
||||
yield
|
||||
body = self.ui.body._w
|
||||
data = action['data'].copy()
|
||||
if 'devices' in data:
|
||||
data['interfaces'] = data.pop('devices')
|
||||
async for _ in self._enter_form_data(
|
||||
body.stretchy.form,
|
||||
data,
|
||||
action.get("submit", True)):
|
||||
pass
|
||||
t = 0.0
|
||||
while data['name'] not in self.view.cur_netdev_names:
|
||||
await asyncio.sleep(0.1)
|
||||
t += 0.1
|
||||
if t > 5.0:
|
||||
raise Exception("bond did not appear in 5 secs")
|
||||
if t > 0:
|
||||
log.debug("waited %s for bond to appear", t)
|
||||
yield
|
||||
elif action['action'] == 'done':
|
||||
self.ui.body.done()
|
||||
else:
|
||||
raise Exception("could not process action {}".format(action))
|
||||
|
||||
|
||||
class NetworkController(BaseNetworkController, TuiController,
|
||||
NetworkAnswersMixin):
|
||||
|
||||
def __init__(self, app):
|
||||
super().__init__(app)
|
||||
self.view = None
|
||||
self.view_shown = False
|
||||
|
||||
def make_ui(self):
|
||||
if not self.view_shown:
|
||||
self.update_initial_configs()
|
||||
netdev_infos = [
|
||||
dev.netdev_info() for dev in self.model.get_all_netdevs()
|
||||
]
|
||||
self.view = NetworkView(self, netdev_infos)
|
||||
if not self.view_shown:
|
||||
self.apply_config(silent=True)
|
||||
self.view_shown = True
|
||||
self.view.update_default_routes(
|
||||
self.network_event_receiver.default_routes)
|
||||
return self.view
|
||||
|
||||
def end_ui(self):
|
||||
self.view = None
|
||||
|
||||
def done(self):
|
||||
log.debug("NetworkController.done next_screen")
|
||||
self.model.has_network = bool(
|
||||
self.network_event_receiver.default_routes)
|
||||
self.app.next_screen()
|
||||
|
||||
def cancel(self):
|
||||
self.app.prev_screen()
|
||||
|
||||
def apply_starting(self):
|
||||
super().apply_starting()
|
||||
if self.view is not None:
|
||||
self.view.show_apply_spinner()
|
||||
|
||||
def apply_stopping(self):
|
||||
super().apply_stopping()
|
||||
if self.view is not None:
|
||||
self.view.hide_apply_spinner()
|
||||
|
||||
def apply_error(self, stage):
|
||||
super().apply_error(stage)
|
||||
if self.view is not None:
|
||||
self.view.show_network_error(stage)
|
||||
|
||||
def update_default_routes(self, routes):
|
||||
super().update_default_routes(routes)
|
||||
if self.view:
|
||||
self.view.update_default_routes(routes)
|
||||
|
||||
def new_link(self, netdev):
|
||||
super().new_link(netdev)
|
||||
if self.view is not None:
|
||||
self.view.new_link(netdev.netdev_info())
|
||||
|
||||
def update_link(self, netdev):
|
||||
super().update_link(netdev)
|
||||
if self.view is not None:
|
||||
self.view.update_link(netdev.netdev_info())
|
||||
|
||||
def del_link(self, netdev):
|
||||
super().del_link(netdev)
|
||||
if self.view is not None:
|
||||
self.view.del_link(netdev.netdev_info())
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from abc import abstractmethod
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from subiquitycore.controller import BaseController
|
||||
|
@ -60,7 +61,7 @@ class TuiController(BaseController):
|
|||
# Stuff for fine grained actions, used by filesystem and network
|
||||
# controller at time of writing this comment.
|
||||
|
||||
def _enter_form_data(self, form, data, submit, clean_suffix=''):
|
||||
async def _enter_form_data(self, form, data, submit, clean_suffix=''):
|
||||
for k, v in data.items():
|
||||
c = getattr(
|
||||
self, '_action_clean_{}_{}'.format(k, clean_suffix), None)
|
||||
|
@ -81,18 +82,12 @@ class TuiController(BaseController):
|
|||
raise Exception("answers left form invalid!")
|
||||
form._click_done(None)
|
||||
|
||||
def _run_actions(self, actions):
|
||||
async def _run_actions(self, actions):
|
||||
delay = 0.2/self.app.scale_factor
|
||||
for action in actions:
|
||||
yield from self._answers_action(action)
|
||||
|
||||
def _run_iterator(self, it, delay=None):
|
||||
if delay is None:
|
||||
delay = 0.2/self.app.scale_factor
|
||||
try:
|
||||
next(it)
|
||||
except StopIteration:
|
||||
return
|
||||
self.app.aio_loop.call_later(delay, self._run_iterator, it, delay/1.1)
|
||||
async for _ in self._answers_action(action):
|
||||
await asyncio.sleep(delay)
|
||||
delay /= 1.1
|
||||
|
||||
|
||||
class RepeatedController(BaseController):
|
||||
|
|
Loading…
Reference in New Issue