summaryrefslogtreecommitdiff
path: root/python/vyos/configdict.py
diff options
context:
space:
mode:
authorMarcus Hoff <marcus.hoff@ring2.dk>2020-09-26 13:19:37 +0200
committerMarcus Hoff <marcus.hoff@ring2.dk>2020-09-26 13:19:37 +0200
commit1141bee72677b25d18436975625d2d298be503ff (patch)
tree4b6dc8fe1a8ced931e1ba08c58a348abfcd85a6b /python/vyos/configdict.py
parent45b30adfaaec7065f768d04085138a75a76ed376 (diff)
parent374724be64728101c262fcac1579beece63ee651 (diff)
downloadvyos-1x-1141bee72677b25d18436975625d2d298be503ff.tar.gz
vyos-1x-1141bee72677b25d18436975625d2d298be503ff.zip
Merge remote-tracking branch 'upstream/current' into current
Diffstat (limited to 'python/vyos/configdict.py')
-rw-r--r--python/vyos/configdict.py237
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
-