From 53b9e599295be2f84ff74be49f11792967d714df Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Thu, 10 Sep 2015 13:25:54 -0500 Subject: [PATCH] Generate network configuration Update network model to generate a network configuration for each active network device. Note, some of the examples won't quite work since we now want updated probert data which includes new information about the source of any discovered ip. Signed-off-by: Ryan Harper --- subiquity/controllers/network.py | 6 +++ subiquity/models/actions.py | 72 ++++++++++++++++++++++++++++ subiquity/models/network.py | 81 ++++++++++++++++++++++++++++++-- subiquity/ui/views/network.py | 11 ++++- 4 files changed, 163 insertions(+), 7 deletions(-) diff --git a/subiquity/controllers/network.py b/subiquity/controllers/network.py index e98796e9..7657fd53 100644 --- a/subiquity/controllers/network.py +++ b/subiquity/controllers/network.py @@ -18,6 +18,8 @@ from subiquity.models import NetworkModel from subiquity.ui.views import NetworkView from subiquity.ui.dummy import DummyView +from subiquity.curtin import curtin_write_network_actions + class NetworkController(ControllerPolicy): def __init__(self, common): @@ -34,6 +36,10 @@ class NetworkController(ControllerPolicy): self.ui.set_footer(footer, 20) self.ui.set_body(NetworkView(self.model, self.signal)) + def network_finish(self, actions): + curtin_write_network_actions(actions) + self.signal.emit_signal('filesystem:show') + def set_default_route(self): self.ui.set_body(DummyView(self.signal)) diff --git a/subiquity/models/actions.py b/subiquity/models/actions.py index 92460c25..9b067934 100644 --- a/subiquity/models/actions.py +++ b/subiquity/models/actions.py @@ -16,6 +16,78 @@ import yaml +class NetAction(): + def __init__(self, **options): + for k, v in options.items(): + setattr(self, k, v) + self._action_keys = ['type', 'name', 'params', 'subnets'] + + def get(self): + action = {k: v for k, v in self.__dict__.items() + if k in self._action_keys} + return action + + +class PhysicalAction(NetAction): + def __init__(self, **options): + if 'type' not in options or options['type'] != 'physical': + raise Exception('Invalid type for {}'.format( + self.__class__.__name__)) + if 'name' not in options or len(options['name']) == 0: + raise Exception('Invalid name for {}'.format( + self.__class__.__name__)) + if 'mac_address' not in options: + raise Exception('{} requires a valid mac_address attr'.format( + self.__class__.__name__)) + super().__init__(**options) + self._action_keys.extend(['mac_address']) + + +class BridgeAction(NetAction): + def __init__(self, **options): + if 'type' not in options or options['type'] != 'bridge': + raise Exception('Invalid type for {}'.format( + self.__class__.__name__)) + if 'name' not in options or len(options['name']) == 0: + raise Exception('Invalid name for {}'.format( + self.__class__.__name__)) + if 'bridge_interfaces' not in options: + raise Exception('{} requires bridge_interfaces attr'.format( + self.__class__.__name__)) + super().__init__(**options) + self._action_keys.extend(['bridge_interfaces']) + + +class VlanAction(NetAction): + def __init__(self, **options): + if 'type' not in options or options['type'] != 'vlan': + raise Exception('Invalid type for {}'.format( + self.__class__.__name__)) + if 'name' not in options or len(options['name']) == 0: + raise Exception('Invalid name for {}'.format( + self.__class__.__name__)) + if 'vlan_id' not in options or 'vlan_link' not in options: + raise Exception('{} requires vlan_id and vlan_link attr'.format( + self.__class__.__name__)) + super().__init__(**options) + self._action_keys.extend(['vlan_id', 'vlan_link']) + + +class BondAction(NetAction): + def __init__(self, **options): + if 'type' not in options or options['type'] != 'bond': + raise Exception('Invalid type for {}'.format( + self.__class__.__name__)) + if 'name' not in options or len(options['name']) == 0: + raise Exception('Invalid name for {}'.format( + self.__class__.__name__)) + if 'bond_interfaces' not in options: + raise Exception('{} requires bond_interfaces attr'.format( + self.__class__.__name__)) + super().__init__(**options) + self._action_keys.extend(['bond_interfaces']) + + class DiskAction(): def __init__(self, action_id, model, serial, ptable='gpt', wipe=None): self._action_id = action_id diff --git a/subiquity/models/network.py b/subiquity/models/network.py index 385c1a5b..5fda1b3b 100644 --- a/subiquity/models/network.py +++ b/subiquity/models/network.py @@ -14,6 +14,7 @@ # along with this program. If not, see . import errno +import ipaddress import logging import json import os @@ -21,6 +22,11 @@ from subiquity.model import ModelPolicy from subiquity.utils import (read_sys_net, sys_dev_path) +from .actions import ( + BondAction, + PhysicalAction, +) + log = logging.getLogger('subiquity.models.network') @@ -49,7 +55,10 @@ class NetworkModel(ModelPolicy): signals = [ ('Network main view', 'network:show', - 'network') + 'network'), + ('Network finish', + 'network:finish', + 'network_finish') ] additional_options = [ @@ -68,6 +77,12 @@ class NetworkModel(ModelPolicy): self.opts = opts self.prober = prober self.network = {} + self.configured_interfaces = {} + + def reset(self): + log.debug('resetting network model') + self.configured_interfaces = {} + self.network = {} def get_signal_by_name(self, selection): for x, y, z in self.get_signals(): @@ -181,11 +196,67 @@ class NetworkModel(ModelPolicy): return False raise + def configure_iface(self, ifname): + iface_info = self.network[ifname] + log.debug('iface_info of {}:\n{}'.format(ifname, iface_info)) + mac_address = iface_info['hardware']['attrs'].get('address') + + action = { + 'type': 'physical', + 'name': ifname, + 'mac_address': mac_address, + 'subnets': [], + } + ip_info = self.network[ifname]['ip'] + log.debug('iface {} ip info:\n{}'.format(ifname, ip_info)) + source = ip_info.get('source', None) + if source and source['method'] is 'dhcp': + action['subnets'].extend([{'type': 'dhcp'}]) + elif ip_info['addr'] is not None: + # FIXME: + # - ipv6 + # - read/fine default dns and route info + ip_network = \ + ipaddress.IPv4Interface("{addr}/{netmask}".format(**ip_info)) + action['subnets'].extend([{ + 'type': 'static', + 'address': ip_network.with_prefixlen}]) + log.debug("Configuring iface: {} as PhysicalAction({})".format( + ifname, action)) + self.configured_interfaces.update({ifname: PhysicalAction(**action)}) + def get_interfaces(self): - ignored = ['lo', 'bridge', 'tun', 'tap'] - return [iface for iface in self.network.keys() - if self.network[iface]['type'] not in ignored and - self.iface_is_up(iface)] + if len(self.configured_interfaces) == 0: + ignored = ['lo', 'bridge', 'tun', 'tap'] + for ifname in self.network.keys(): + if self.network[ifname]['type'] not in ignored and \ + self.iface_is_up(ifname): + self.configure_iface(ifname) + + return self.configured_interfaces + + def add_bond(self, ifname, interfaces, params=[], subnets=[]): + ''' take bondname and then a set of options ''' + action = { + 'type': 'bond', + 'name': ifname, + 'bond_interfaces': interfaces, + 'params': params, + 'subnets': subnets, + } + self.configured_interfaces.update({ifname: BondAction(**action)}) + log.debug("add_bond: {} as BondAction({})".format( + ifname, action)) + + def add_subnet(self, ifname, subnet): + if ifname not in self.configured_interfaces: + raise Exception('No such configured interface: {}'.format(ifname)) + + action = self.configured_interfaces[ifname] + if 'subnets' in action: + action['subnets'].extend([subnet]) + else: + action['subnets'] = [subnet] def get_bridges(self): return [iface for iface in self.network.keys() diff --git a/subiquity/ui/views/network.py b/subiquity/ui/views/network.py index 51f2bc06..a2efea57 100644 --- a/subiquity/ui/views/network.py +++ b/subiquity/ui/views/network.py @@ -22,6 +22,7 @@ Provides network device listings and extended network information import logging from urwid import (ListBox, Pile, BoxAdapter, Text, Columns) +import yaml from subiquity.ui.lists import SimpleList from subiquity.ui.buttons import cancel_btn, menu_btn from subiquity.ui.utils import Padding, Color @@ -115,7 +116,7 @@ class NetworkView(ViewPolicy): if ipv4_status['provider']: ipv4_template += 'from {provider} '.format(**ipv4_status) col_2.append(Text(ipv4_template)) - col_2.append(Text("No IPv6 connection")) # vertical holder for ipv6 + col_2.append(Text("No IPv6 connection")) # vert. holder for ipv6 if len(col_2): col_2 = BoxAdapter(SimpleList(col_2, is_selectable=False), height=len(col_2)) @@ -143,7 +144,13 @@ class NetworkView(ViewPolicy): def on_net_dev_press(self, result): log.debug("Selected network dev: {}".format(result.label)) - self.signal.emit_signal('filesystem:show') + actions = [action.get() for _, action in + self.model.configured_interfaces.items()] + log.debug('Configured Network Actions:\n{}'.format( + yaml.dump(actions, default_flow_style=False))) + + self.signal.emit_signal('network:finish', actions) def cancel(self, button): + self.model.reset() self.signal.emit_signal(self.model.get_previous_signal)