diff options
Diffstat (limited to 'netplan/configmanager.py')
-rw-r--r-- | netplan/configmanager.py | 244 |
1 files changed, 35 insertions, 209 deletions
diff --git a/netplan/configmanager.py b/netplan/configmanager.py index 186d561..7905f76 100644 --- a/netplan/configmanager.py +++ b/netplan/configmanager.py @@ -17,173 +17,85 @@ '''netplan configuration manager''' -import glob import logging import os import shutil import sys import tempfile -import yaml +from typing import Optional + +from netplan import libnetplan -class ConfigManager(object): +class ConfigManager(object): def __init__(self, prefix="/", extra_files={}): self.prefix = prefix self.tempdir = tempfile.mkdtemp(prefix='netplan_') self.temp_etc = os.path.join(self.tempdir, "etc") self.temp_run = os.path.join(self.tempdir, "run") self.extra_files = extra_files - self.config = {} self.new_interfaces = set() + self.np_state: Optional[libnetplan.State] = None - @property - def network(self): - return self.config['network'] - - @property - def interfaces(self): - interfaces = {} - interfaces.update(self.ovs_ports) - interfaces.update(self.ethernets) - interfaces.update(self.modems) - interfaces.update(self.wifis) - interfaces.update(self.bridges) - interfaces.update(self.bonds) - interfaces.update(self.tunnels) - interfaces.update(self.vlans) - return interfaces + def __getattr__(self, attr): + assert self.np_state is not None, "Must call parse() before accessing the config." + return getattr(self.np_state, attr) @property def physical_interfaces(self): + assert self.np_state is not None, "Must call parse() before accessing the config." interfaces = {} - interfaces.update(self.ethernets) - interfaces.update(self.modems) - interfaces.update(self.wifis) + interfaces.update(self.np_state.ethernets) + interfaces.update(self.np_state.modems) + interfaces.update(self.np_state.wifis) return interfaces @property def virtual_interfaces(self): + assert self.np_state is not None, "Must call parse() before accessing the config." interfaces = {} # what about ovs_ports? - interfaces.update(self.bridges) - interfaces.update(self.bonds) - interfaces.update(self.tunnels) - interfaces.update(self.vlans) + interfaces.update(self.np_state.bridges) + interfaces.update(self.np_state.bonds) + interfaces.update(self.np_state.tunnels) + interfaces.update(self.np_state.vlans) + interfaces.update(self.np_state.vrfs) return interfaces - @property - def ovs_ports(self): - return self.network['ovs_ports'] - - @property - def openvswitch(self): - return self.network['openvswitch'] - - @property - def ethernets(self): - return self.network['ethernets'] - - @property - def modems(self): - return self.network['modems'] - - @property - def wifis(self): - return self.network['wifis'] - - @property - def bridges(self): - return self.network['bridges'] - - @property - def bonds(self): - return self.network['bonds'] - - @property - def tunnels(self): - return self.network['tunnels'] - - @property - def vlans(self): - return self.network['vlans'] - - @property - def nm_devices(self): - return self.network['nm-devices'] - - @property - def version(self): - return self.network['version'] - - @property - def renderer(self): - return self.network['renderer'] - - @property - def tree(self): - return self.strip_tree(self.config) - - @staticmethod - def strip_tree(data): - '''clear empty branches''' - new_data = {} - for k, v in data.items(): - if isinstance(v, dict): - v = ConfigManager.strip_tree(v) - if v not in (u'', None, {}): - new_data[k] = v - return new_data - - def parse(self, extra_config=[]): + def parse(self, extra_config=None): """ Parse all our config files to return an object that describes the system's entire configuration, so that it can later be interrogated. - Returns a dict that contains the entire, collated and merged YAML. + Returns a libnetplan State wrapper """ - # TODO: Clean this up, there's no solid reason why we should parse YAML - # in two different spots; here and in parse.c. We'd do better by - # parsing things once, in C form, and having some small glue - # Cpython code to call on the right methods and return an object - # that is meaningful for the Python code; but minimal parsing in - # pure Python will do for now. ~cyphermox # /run/netplan shadows /etc/netplan/, which shadows /lib/netplan - names_to_paths = {} - for yaml_dir in ['lib', 'etc', 'run']: - for yaml_file in glob.glob(os.path.join(self.prefix, yaml_dir, 'netplan', '*.yaml')): - names_to_paths[os.path.basename(yaml_file)] = yaml_file - - files = [names_to_paths[name] for name in sorted(names_to_paths.keys())] + parser = libnetplan.Parser() + try: + parser.load_yaml_hierarchy(rootdir=self.prefix) - self.config['network'] = { - 'ovs_ports': {}, - 'openvswitch': {}, - 'ethernets': {}, - 'modems': {}, - 'wifis': {}, - 'bridges': {}, - 'bonds': {}, - 'tunnels': {}, - 'vlans': {}, - 'nm-devices': {}, - 'version': None, - 'renderer': None - } - for yaml_file in files: - self._merge_yaml_config(yaml_file) + if extra_config: + for f in extra_config: + parser.load_yaml(f) - for yaml_file in extra_config: - self.new_interfaces |= self._merge_yaml_config(yaml_file) + self.np_state = libnetplan.State() + self.np_state.import_parser_results(parser) + except libnetplan.LibNetplanException as e: + raise ConfigurationError(*e.args) - logging.debug("Merged config:\n{}".format(yaml.dump(self.tree, default_flow_style=False))) + self.np_state.dump_to_logs() + return self.np_state def add(self, config_dict): for config_file in config_dict: self._copy_file(config_file, config_dict[config_file]) self.extra_files.update(config_dict) + # Invalidate the current parsed state + self.np_state = None + def backup(self, backup_config_dir=True): if backup_config_dir: self._copy_tree(os.path.join(self.prefix, "etc/netplan"), @@ -243,92 +155,6 @@ class ConfigManager(object): else: raise - def _merge_ovs_ports_config(self, orig, new): - new_interfaces = set() - ports = dict() - if 'ports' in new: - for p1, p2 in new.get('ports'): - # Spoof an interface config for patch ports, which are usually - # just strings. Add 'peer' and mark it via 'openvswitch' key. - ports[p1] = {'peer': p2, 'openvswitch': {}} - ports[p2] = {'peer': p1, 'openvswitch': {}} - changed_ifaces = list(ports.keys()) - - for ifname in changed_ifaces: - iface = ports.pop(ifname) - if ifname in orig: - logging.debug("{} exists in {}".format(ifname, orig)) - orig[ifname].update(iface) - else: - logging.debug("{} not found in {}".format(ifname, orig)) - orig[ifname] = iface - new_interfaces.add(ifname) - - return new_interfaces - - def _merge_interface_config(self, orig, new): - new_interfaces = set() - changed_ifaces = list(new.keys()) - - for ifname in changed_ifaces: - iface = new.pop(ifname) - if ifname in orig: - logging.debug("{} exists in {}".format(ifname, orig)) - orig[ifname].update(iface) - else: - logging.debug("{} not found in {}".format(ifname, orig)) - orig[ifname] = iface - new_interfaces.add(ifname) - - return new_interfaces - - def _merge_yaml_config(self, yaml_file): - new_interfaces = set() - - try: - with open(yaml_file) as f: - yaml_data = yaml.load(f, Loader=yaml.CSafeLoader) - network = None - if yaml_data is not None: - network = yaml_data.get('network') - if network: - if 'openvswitch' in network: - new = self._merge_ovs_ports_config(self.ovs_ports, network.get('openvswitch')) - new_interfaces |= new - self.network['openvswitch'] = network.get('openvswitch') - if 'ethernets' in network: - new = self._merge_interface_config(self.ethernets, network.get('ethernets')) - new_interfaces |= new - if 'modems' in network: - new = self._merge_interface_config(self.modems, network.get('modems')) - new_interfaces |= new - if 'wifis' in network: - new = self._merge_interface_config(self.wifis, network.get('wifis')) - new_interfaces |= new - if 'bridges' in network: - new = self._merge_interface_config(self.bridges, network.get('bridges')) - new_interfaces |= new - if 'bonds' in network: - new = self._merge_interface_config(self.bonds, network.get('bonds')) - new_interfaces |= new - if 'tunnels' in network: - new = self._merge_interface_config(self.tunnels, network.get('tunnels')) - new_interfaces |= new - if 'vlans' in network: - new = self._merge_interface_config(self.vlans, network.get('vlans')) - new_interfaces |= new - if 'nm-devices' in network: - new = self._merge_interface_config(self.nm_devices, network.get('nm-devices')) - new_interfaces |= new - if 'version' in network: - self.network['version'] = network.get('version') - if 'renderer' in network: - self.network['renderer'] = network.get('renderer') - return new_interfaces - except (IOError, yaml.YAMLError): # pragma: nocover (filesystem failures/invalid YAML) - logging.error('Error while loading {}, aborting.'.format(yaml_file)) - sys.exit(1) - class ConfigurationError(Exception): """ |