summaryrefslogtreecommitdiff
path: root/netplan/configmanager.py
diff options
context:
space:
mode:
Diffstat (limited to 'netplan/configmanager.py')
-rw-r--r--netplan/configmanager.py244
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):
"""