diff options
Diffstat (limited to 'python/vyos/configdict.py')
-rw-r--r-- | python/vyos/configdict.py | 237 |
1 files changed, 158 insertions, 79 deletions
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index bfc70b772..58ecd3f17 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -18,9 +18,12 @@ A library for retrieving value dicts from VyOS configs in a declarative fashion. """ import os -from enum import Enum from copy import deepcopy +from vyos.util import vyos_dict_search +from vyos.xml import defaults +from vyos.xml import is_tag +from vyos.xml import is_leaf from vyos import ConfigError def retrieve_config(path_hash, base_path, config): @@ -104,47 +107,6 @@ def list_diff(first, second): second = set(second) return [item for item in first if item not in second] -def T2665_default_dict_cleanup(dict): - """ Cleanup default keys for tag nodes https://phabricator.vyos.net/T2665. """ - # Cleanup - for vif in ['vif', 'vif_s']: - if vif in dict: - for key in ['ip', 'mtu', 'dhcpv6_options']: - if key in dict[vif]: - del dict[vif][key] - - # cleanup VIF-S defaults - if 'vif_c' in dict[vif]: - for key in ['ip', 'mtu', 'dhcpv6_options']: - if key in dict[vif]['vif_c']: - del dict[vif]['vif_c'][key] - # If there is no vif-c defined and we just cleaned the default - # keys - we can clean the entire vif-c dict as it's useless - if not dict[vif]['vif_c']: - del dict[vif]['vif_c'] - - # If there is no real vif/vif-s defined and we just cleaned the default - # keys - we can clean the entire vif dict as it's useless - if not dict[vif]: - del dict[vif] - - if 'dhcpv6_options' in dict and 'pd' in dict['dhcpv6_options']: - if 'length' in dict['dhcpv6_options']['pd']: - del dict['dhcpv6_options']['pd']['length'] - - # delete empty dicts - if 'dhcpv6_options' in dict: - if 'pd' in dict['dhcpv6_options']: - # test if 'pd' is an empty node so we can remove it - if not dict['dhcpv6_options']['pd']: - del dict['dhcpv6_options']['pd'] - - # test if 'dhcpv6_options' is an empty node so we can remove it - if not dict['dhcpv6_options']: - del dict['dhcpv6_options'] - - return dict - def leaf_node_changed(conf, path): """ Check if a leaf node was altered. If it has been altered - values has been @@ -207,16 +169,100 @@ def get_removed_vlans(conf, dict): return dict +def T2665_set_dhcpv6pd_defaults(config_dict): + """ Properly configure DHCPv6 default options in the dictionary. If there is + no DHCPv6 configured at all, it is safe to remove the entire configuration. + """ + # As this is the same for every interface type it is safe to assume this + # for ethernet + pd_defaults = defaults(['interfaces', 'ethernet', 'dhcpv6-options', 'pd']) -def dict_add_dhcpv6pd_defaults(defaults, config_dict): # Implant default dictionary for DHCPv6-PD instances - if 'dhcpv6_options' in config_dict and 'pd' in config_dict['dhcpv6_options']: - for pd, pd_config in config_dict['dhcpv6_options']['pd'].items(): - config_dict['dhcpv6_options']['pd'][pd] = dict_merge( - defaults, pd_config) + if vyos_dict_search('dhcpv6_options.pd.length', config_dict): + del config_dict['dhcpv6_options']['pd']['length'] + + for pd in (vyos_dict_search('dhcpv6_options.pd', config_dict) or []): + config_dict['dhcpv6_options']['pd'][pd] = dict_merge(pd_defaults, + config_dict['dhcpv6_options']['pd'][pd]) return config_dict +def is_member(conf, interface, intftype=None): + """ + Checks if passed interface is member of other interface of specified type. + intftype is optional, if not passed it will search all known types + (currently bridge and bonding) + + Returns: + None -> Interface is not a member + interface name -> Interface is a member of this interface + False -> interface type cannot have members + """ + ret_val = None + intftypes = ['bonding', 'bridge'] + if intftype not in intftypes + [None]: + raise ValueError(( + f'unknown interface type "{intftype}" or it cannot ' + f'have member interfaces')) + + intftype = intftypes if intftype == None else [intftype] + + # set config level to root + old_level = conf.get_level() + conf.set_level([]) + + for it in intftype: + base = ['interfaces', it] + for intf in conf.list_nodes(base): + memberintf = base + [intf, 'member', 'interface'] + if is_tag(memberintf): + if interface in conf.list_nodes(memberintf): + ret_val = intf + break + elif is_leaf(memberintf): + if ( conf.exists(memberintf) and + interface in conf.return_values(memberintf) ): + ret_val = intf + break + + old_level = conf.set_level(old_level) + return ret_val + +def is_source_interface(conf, interface, intftype=None): + """ + Checks if passed interface is configured as source-interface of other + interfaces of specified type. intftype is optional, if not passed it will + search all known types (currently pppoe, macsec, pseudo-ethernet, tunnel + and vxlan) + + Returns: + None -> Interface is not a member + interface name -> Interface is a member of this interface + False -> interface type cannot have members + """ + ret_val = None + intftypes = ['macsec', 'pppoe', 'pseudo-ethernet', 'tunnel', 'vxlan'] + if intftype not in intftypes + [None]: + raise ValueError(f'unknown interface type "{intftype}" or it can not ' + 'have a source-interface') + + intftype = intftypes if intftype == None else [intftype] + + # set config level to root + old_level = conf.get_level() + conf.set_level([]) + + for it in intftype: + base = ['interfaces', it] + for intf in conf.list_nodes(base): + lower_intf = base + [intf, 'source-interface'] + if conf.exists(lower_intf) and interface in conf.return_values(lower_intf): + ret_val = intf + break + + old_level = conf.set_level(old_level) + return ret_val + def get_interface_dict(config, base, ifname=''): """ Common utility function to retrieve and mandgle the interfaces available @@ -225,10 +271,6 @@ def get_interface_dict(config, base, ifname=''): Will return a dictionary with the necessary interface configuration """ - from vyos.util import vyos_dict_search - from vyos.validate import is_member - from vyos.xml import defaults - if not ifname: # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: @@ -238,6 +280,12 @@ def get_interface_dict(config, base, ifname=''): # retrieve interface default values default_values = defaults(base) + # We take care about VLAN (vif, vif-s, vif-c) default values later on when + # parsing vlans in default dict and merge the "proper" values in correctly, + # see T2665. + for vif in ['vif', 'vif_s']: + if vif in default_values: del default_values[vif] + # setup config level which is extracted in get_removed_vlans() config.set_level(base + [ifname]) dict = config.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) @@ -249,24 +297,42 @@ def get_interface_dict(config, base, ifname=''): # Add interface instance name into dictionary dict.update({'ifname': ifname}) + # XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely + # remove the default values from the dict. + if 'dhcpv6_options' not in dict: + if 'dhcpv6_options' in default_values: + del default_values['dhcpv6_options'] + # We have gathered the dict representation of the CLI, but there are # default options which we need to update into the dictionary # retrived. dict = dict_merge(default_values, dict) + # XXX: T2665: blend in proper DHCPv6-PD default values + dict = T2665_set_dhcpv6pd_defaults(dict) + # Check if we are a member of a bridge device bridge = is_member(config, ifname, 'bridge') - if bridge: - dict.update({'is_bridge_member' : bridge}) + if bridge: dict.update({'is_bridge_member' : bridge}) # Check if we are a member of a bond device bond = is_member(config, ifname, 'bonding') - if bond: - dict.update({'is_bond_member' : bond}) + if bond: dict.update({'is_bond_member' : bond}) + + # Some interfaces come with a source_interface which must also not be part + # of any other bond or bridge interface as it is exclusivly assigned as the + # Kernels "lower" interface to this new "virtual/upper" interface. + if 'source_interface' in dict: + # Check if source interface is member of another bridge + tmp = is_member(config, dict['source_interface'], 'bridge') + if tmp: dict.update({'source_interface_is_bridge_member' : tmp}) + + # Check if source interface is member of another bridge + tmp = is_member(config, dict['source_interface'], 'bonding') + if tmp: dict.update({'source_interface_is_bond_member' : tmp}) mac = leaf_node_changed(config, ['mac']) - if mac: - dict.update({'mac_old' : mac}) + if mac: dict.update({'mac_old' : mac}) eui64 = leaf_node_changed(config, ['ipv6', 'address', 'eui64']) if eui64: @@ -276,36 +342,49 @@ def get_interface_dict(config, base, ifname=''): else: dict['ipv6']['address'].update({'eui64_old': eui64}) - # remove wrongly inserted values - dict = T2665_default_dict_cleanup(dict) - - # Implant default dictionary for DHCPv6-PD instances - default_pd_values = defaults(base + ['dhcpv6-options', 'pd']) - dict = dict_add_dhcpv6pd_defaults(default_pd_values, dict) - # Implant default dictionary in vif/vif-s VLAN interfaces. Values are # identical for all types of VLAN interfaces as they all include the same # XML definitions which hold the defaults. - default_vif_values = defaults(base + ['vif']) for vif, vif_config in dict.get('vif', {}).items(): - dict['vif'][vif] = dict_add_dhcpv6pd_defaults( - default_pd_values, vif_config) - dict['vif'][vif] = T2665_default_dict_cleanup( - dict_merge(default_vif_values, vif_config)) + default_vif_values = defaults(base + ['vif']) + # XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely + # remove the default values from the dict. + if not 'dhcpv6_options' in vif_config: + del default_vif_values['dhcpv6_options'] + + dict['vif'][vif] = dict_merge(default_vif_values, vif_config) + # XXX: T2665: blend in proper DHCPv6-PD default values + dict['vif'][vif] = T2665_set_dhcpv6pd_defaults(dict['vif'][vif]) for vif_s, vif_s_config in dict.get('vif_s', {}).items(): - dict['vif_s'][vif_s] = dict_add_dhcpv6pd_defaults( - default_pd_values, vif_s_config) - dict['vif_s'][vif_s] = T2665_default_dict_cleanup( - dict_merge(default_vif_values, vif_s_config)) + default_vif_s_values = defaults(base + ['vif-s']) + # XXX: T2665: we only wan't the vif-s defaults - do not care about vif-c + if 'vif_c' in default_vif_s_values: del default_vif_s_values['vif_c'] + + # XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely + # remove the default values from the dict. + if not 'dhcpv6_options' in vif_s_config: + del default_vif_s_values['dhcpv6_options'] + + dict['vif_s'][vif_s] = dict_merge(default_vif_s_values, vif_s_config) + # XXX: T2665: blend in proper DHCPv6-PD default values + dict['vif_s'][vif_s] = T2665_set_dhcpv6pd_defaults( + dict['vif_s'][vif_s]) + for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): - dict['vif_s'][vif_s]['vif_c'][vif_c] = dict_add_dhcpv6pd_defaults( - default_pd_values, vif_c_config) - dict['vif_s'][vif_s]['vif_c'][vif_c] = T2665_default_dict_cleanup( - dict_merge(default_vif_values, vif_c_config)) + default_vif_c_values = defaults(base + ['vif-s', 'vif-c']) + + # XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely + # remove the default values from the dict. + if not 'dhcpv6_options' in vif_c_config: + del default_vif_c_values['dhcpv6_options'] + + dict['vif_s'][vif_s]['vif_c'][vif_c] = dict_merge( + default_vif_c_values, vif_c_config) + # XXX: T2665: blend in proper DHCPv6-PD default values + dict['vif_s'][vif_s]['vif_c'][vif_c] = T2665_set_dhcpv6pd_defaults( + dict['vif_s'][vif_s]['vif_c'][vif_c]) # Check vif, vif-s/vif-c VLAN interfaces for removal dict = get_removed_vlans(config, dict) - return dict - |