From 987cfd15db91a14df944a4c3174a094ff756cdd0 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Mon, 29 Jun 2020 21:48:24 +0100 Subject: tunnel: T2649: ConfigurationState, do not inherit from Config --- src/conf_mode/interfaces-tunnel.py | 118 +++++++++++++++++++++++-------------- 1 file changed, 74 insertions(+), 44 deletions(-) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index c13f77d91..ea15a7fb7 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -32,7 +32,8 @@ from vyos.dicts import FixedDict from vyos import airbag airbag.enable() -class ConfigurationState(Config): + +class ConfigurationState(object): """ The current API require a dict to be generated by get_config() which is then consumed by verify(), generate() and apply() @@ -40,7 +41,7 @@ class ConfigurationState(Config): ConfiguartionState is an helper class wrapping Config and providing an common API to this dictionary structure - Its to_dict() function return a dictionary containing three fields, + Its to_api() function return a dictionary containing three fields, each a dict, called options, changes, actions. options: @@ -84,16 +85,16 @@ class ConfigurationState(Config): which for each field represent how it was modified since the last commit """ - def __init__ (self, section, default): + def __init__(self, configuration, section, default): """ initialise the class for a given configuration path: - >>> conf = ConfigurationState('interfaces ethernet eth1') + >>> conf = ConfigurationState(conf, 'interfaces ethernet eth1') all further references to get_value(s) and get_effective(s) will be for this part of the configuration (eth1) """ - super().__init__() - self.section = section + self._conf = configuration + self.default = deepcopy(default) self.options = FixedDict(**default) self.actions = { @@ -104,13 +105,19 @@ class ConfigurationState(Config): 'delete': [], # the key was present and was deleted } self.changes = {} - if not self.exists(section): + if not self._conf.exists(section): self.changes['section'] = 'delete' - elif self.exists_effective(section): + elif self._conf.exists_effective(section): self.changes['section'] = 'modify' else: self.changes['section'] = 'create' + self.set_level(section) + + def set_level(self, lpath): + self.section = lpath + self._conf.set_level(lpath) + def _act(self, section): """ Returns for a given configuration field determine what happened to it @@ -121,18 +128,18 @@ class ConfigurationState(Config): 'delete': it was present but was removed from the configuration 'absent': it was not and is not present """ - if self.exists(section): - if self.exists_effective(section): - if self.return_value(section) != self.return_effective_value(section): + if self._conf.exists(section): + if self._conf.exists_effective(section): + if self._conf.return_value(section) != self._conf.return_effective_value(section): return 'modify' return 'static' return 'create' else: - if self.exists_effective(section): + if self._conf.exists_effective(section): return 'delete' return 'absent' - def _action (self, name, key): + def _action(self, name, key): action = self._act(key) self.changes[name] = action self.actions[action].append(name) @@ -157,18 +164,28 @@ class ConfigurationState(Config): """ if self._action(name, key) in ('delete', 'absent'): return - return self._get(name, key, default, self.return_value) + return self._get(name, key, default, self._conf.return_value) def get_values(self, name, key, default=None): """ - >>> conf.get_values('addresses-add', 'address') - will place a list made of the IP present in 'interface dummy dum1 address' - into the dictionnary entry 'addr' using Config.return_values - (the data in the configuration to apply) + >>> conf.get_values('addresses', 'address') + will place a list of the new IP present in 'interface dummy dum1 address' + into the dictionnary entry "-add" (here 'addresses-add') using + Config.return_values and will add the the one which were removed in into + the entry "-del" (here addresses-del') """ - if self._action(name, key) in ('delete', 'absent'): + add_name = f'{name}-add' + + if self._action(add_name, key) in ('delete', 'absent'): return - return self._get(name, key, default, self.return_values) + + self._get(add_name, key, default, self._conf.return_values) + + # get the effective values to determine which data is no longer valid + self.options['addresses-del'] = list_diff( + self._conf.return_effective_values('address'), + self.options['addresses-add'] + ) def get_effective(self, name, key, default=None): """ @@ -178,7 +195,7 @@ class ConfigurationState(Config): (the data in the configuration to apply) """ self._action(name, key) - return self._get(name, key, default, self.return_effective_value) + return self._get(name, key, default, self._conf.return_effective_value) def get_effectives(self, name, key, default=None): """ @@ -188,7 +205,7 @@ class ConfigurationState(Config): (the data in the un-modified configuration) """ self._action(name, key) - return self._get(name, key, default, self.return_effectives_value) + return self._get(name, key, default, self._conf.return_effectives_value) def load(self, mapping): """ @@ -220,16 +237,35 @@ class ConfigurationState(Config): else: self.get_value(local_name, config_name, default) - def remove_default (self,*options): + def remove_default(self,*options): """ remove all the values which were not changed from the default """ for option in options: - if self.exists(option) and self.self_return_value(option) != self.default[option]: + if not self._conf.exists(option): + del self.options[option] continue - del self.options[option] - def to_dict (self): + if self._conf.return_value(option) == self.default[option]: + del self.options[option] + continue + + if self._conf.return_values(option) == self.default[option]: + del self.options[option] + continue + + def as_dict(self, lpath): + l = self._conf.get_level() + self._conf.set_level([]) + d = self._conf.get_config_dict(lpath) + # XXX: that not what I would have expected from get_config_dict + if lpath: + d = d[lpath[-1]] + # XXX: it should have provided me the content and not the key + self._conf.set_level(l) + return d + + def to_api(self): """ provide a dictionary with the generated data for the configuration options: the configuration value for the key @@ -243,6 +279,7 @@ class ConfigurationState(Config): 'actions': self.actions, } + default_config_data = { # interface definition 'vrf': '', @@ -288,6 +325,7 @@ default_config_data = { '6rd-relay-prefix': '', } + # dict name -> config name, multiple values, default mapping = { 'type': ('encapsulation', False, None), @@ -310,7 +348,7 @@ mapping = { 'state': ('disable', False, 'down'), 'link_detect': ('disable-link-detect', False, 2), 'vrf': ('vrf', False, None), - 'addresses-add': ('address', True, None), + 'addresses': ('address', True, None), 'arp_filter': ('ip disable-arp-filter', False, 0), 'arp_accept': ('ip enable-arp-accept', False, 1), 'arp_announce': ('ip enable-arp-announce', False, 1), @@ -320,6 +358,7 @@ mapping = { 'ipv6_dad_transmits:': ('ipv6 dup-addr-detect-transmits', False, None) } + def get_class (options): dispatch = { 'gre': GREIf, @@ -363,19 +402,17 @@ def get_config(): if not ifname: raise ConfigError('Interface not specified') - conf = ConfigurationState('interfaces tunnel ' + ifname, default_config_data) + config = Config() + conf = ConfigurationState(config, ['interfaces', 'tunnel ', ifname], default_config_data) options = conf.options changes = conf.changes options['ifname'] = ifname - # set new configuration level - conf.set_level(conf.section) - if changes['section'] == 'delete': conf.get_effective('type', mapping['type'][0]) - conf.set_level('protocols nhrp tunnel') - options['nhrp'] = conf.list_nodes('') - return conf.to_dict() + config.set_level(['protocols', 'nhrp', 'tunnel']) + options['nhrp'] = config.list_nodes('') + return conf.to_api() # load all the configuration option according to the mapping conf.load(mapping) @@ -407,12 +444,6 @@ def get_config(): options['local'] = picked options['dhcp-interface'] = '' - # get interface addresses (currently effective) - to determine which - # address is no longer valid and needs to be removed - # could be done within ConfigurationState - eff_addr = conf.return_effective_values('address') - options['addresses-del'] = list_diff(eff_addr, options['addresses-add']) - # to make IPv6 SLAAC and DHCPv6 work with forwarding=1, # accept_ra must be 2 if options['ipv6_autoconf'] or 'dhcpv6' in options['addresses-add']: @@ -422,12 +453,11 @@ def get_config(): options['allmulticast'] = options['multicast'] # check that per encapsulation all local-remote pairs are unique - conf.set_level('interfaces tunnel') - ct = conf.get_config_dict()['tunnel'] + ct = conf.as_dict(['interfaces', 'tunnel']) options['tunnel'] = {} # check for bridges - options['bridge'] = is_member(conf, ifname, 'bridge') + options['bridge'] = is_member(config, ifname, 'bridge') options['interfaces'] = interfaces() for name in ct: @@ -440,7 +470,7 @@ def get_config(): pair = f'{local}-{remote}' options['tunnel'][encap][pair] = options['tunnel'].setdefault(encap, {}).get(pair, 0) + 1 - return conf.to_dict() + return conf.to_api() def verify(conf): -- cgit v1.2.3