diff options
| author | Thomas Mangin <thomas.mangin@exa.net.uk> | 2020-05-07 00:50:44 +0100 | 
|---|---|---|
| committer | Thomas Mangin <thomas.mangin@exa.net.uk> | 2020-05-07 00:50:44 +0100 | 
| commit | 367094ff1764116089c9359e1e949db781a4a0da (patch) | |
| tree | cf3135162c8a57af652f027805d1d4a9d63cbca0 /python | |
| parent | e9516e73796bbbda1be76e3f8b5d83cd84070830 (diff) | |
| parent | ed22334321d3b6f27b5d695a4f984257b909f78b (diff) | |
| download | vyos-1x-367094ff1764116089c9359e1e949db781a4a0da.tar.gz vyos-1x-367094ff1764116089c9359e1e949db781a4a0da.zip  | |
debug: T1230: add time information to saved debug logs
Diffstat (limited to 'python')
| -rw-r--r-- | python/vyos/config.py | 15 | ||||
| -rw-r--r-- | python/vyos/configdict.py | 89 | ||||
| -rw-r--r-- | python/vyos/debug.py | 7 | ||||
| -rw-r--r-- | python/vyos/ifconfig/interface.py | 68 | ||||
| -rw-r--r-- | python/vyos/ifconfig/section.py | 21 | ||||
| -rw-r--r-- | python/vyos/ifconfig_vlan.py | 146 | ||||
| -rw-r--r-- | python/vyos/util.py | 71 | ||||
| -rw-r--r-- | python/vyos/validate.py | 66 | 
8 files changed, 361 insertions, 122 deletions
diff --git a/python/vyos/config.py b/python/vyos/config.py index 75055a603..0bc6be12a 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -155,7 +155,7 @@ class Config(object):          ``exists("system name-server"`` without ``set_level``.          Args: -            path (str): relative config path +            path (str|list): relative config path          """          # Make sure there's always a space between default path (level)          # and path supplied as method argument @@ -166,7 +166,7 @@ class Config(object):              else:                  self._level = []          elif isinstance(path, list): -            self._level = path +            self._level = path.copy()          else:              raise TypeError("Level path must be either a whitespace-separated string or a list") @@ -177,7 +177,7 @@ class Config(object):          Returns:              str: current edit level          """ -        return(self._level) +        return(self._level.copy())      def exists(self, path):          """ @@ -386,7 +386,7 @@ class Config(object):              values = []          if not values: -            return(default) +            return(default.copy())          else:              return(values) @@ -407,7 +407,7 @@ class Config(object):              nodes = []          if not nodes: -            return(default) +            return(default.copy())          else:              return(nodes) @@ -448,7 +448,6 @@ class Config(object):          else:              return(value) -      def return_effective_values(self, path, default=[]):          """          Retrieve all values of a multi-value node in a running (effective) config @@ -465,7 +464,7 @@ class Config(object):              values = []          if not values: -            return(default) +            return(default.copy())          else:              return(values) @@ -488,6 +487,6 @@ class Config(object):              nodes = []          if not nodes: -            return(default) +            return(default.copy())          else:              return(nodes) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index e1b704a31..5ca369f66 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -23,7 +23,8 @@ from copy import deepcopy  from vyos import ConfigError  from vyos.ifconfig import Interface - +from vyos.validate import is_member +from vyos.util import ifname_from_config  def retrieve_config(path_hash, base_path, config):      """ @@ -128,9 +129,10 @@ vlan_default = {      'ipv6_dup_addr_detect': 1,      'ingress_qos': '',      'ingress_qos_changed': False, +    'is_bridge_member': False,      'mac': '',      'mtu': 1500, -    'vif_c': [], +    'vif_c': {},      'vif_c_remove': [],      'vrf': ''  } @@ -197,10 +199,7 @@ def intf_to_dict(conf, default):      """      intf = deepcopy(default) - -    # retrieve configured interface addresses -    if conf.exists('address'): -        intf['address'] = conf.return_values('address') +    intf['intf'] = ifname_from_config(conf)      # retrieve interface description      if conf.exists('description'): @@ -255,23 +254,22 @@ def intf_to_dict(conf, default):      if conf.exists('ipv6 address autoconf'):          intf['ipv6_autoconf'] = 1 -    # Get prefixes for IPv6 addressing based on MAC address (EUI-64) -    if conf.exists('ipv6 address eui64'): -        intf['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') -      # Disable IPv6 forwarding on this interface      if conf.exists('ipv6 disable-forwarding'):          intf['ipv6_forwarding'] = 0 -    # Media Access Control (MAC) address -    if conf.exists('mac'): -        intf['mac'] = conf.return_value('mac') +    # check if interface is member of a bridge +    intf['is_bridge_member'] = is_member(conf, intf['intf'], 'bridge')      # IPv6 Duplicate Address Detection (DAD) tries      if conf.exists('ipv6 dup-addr-detect-transmits'):          intf['ipv6_dup_addr_detect'] = int(              conf.return_value('ipv6 dup-addr-detect-transmits')) +    # Media Access Control (MAC) address +    if conf.exists('mac'): +        intf['mac'] = conf.return_value('mac') +      # Maximum Transmission Unit (MTU)      if conf.exists('mtu'):          intf['mtu'] = int(conf.return_value('mtu')) @@ -298,60 +296,56 @@ def intf_to_dict(conf, default):          if intf['ingress_qos'] != conf.return_effective_value('ingress-qos'):              intf['ingress_qos_changed'] = True -    disabled = disable_state(conf) +    # Get the interface addresses +    intf['address'] = conf.return_values('address') -    # Get the interface IPs -    eff_addr = conf.return_effective_values('address') -    act_addr = conf.return_values('address') +    # addresses to remove - difference between effective and working config +    intf['address_remove'] = list_diff( +            conf.return_effective_values('address'), +            intf['address'] +            )      # Get prefixes for IPv6 addressing based on MAC address (EUI-64) -    eff_eui = conf.return_effective_values('ipv6 address eui64') -    act_eui = conf.return_values('ipv6 address eui64') +    intf['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') + +    # EUI64 to remove - difference between effective and working config +    intf['ipv6_eui64_prefix_remove'] = list_diff( +            conf.return_effective_values('ipv6 address eui64'), +            intf['ipv6_eui64_prefix'] +            ) -    # Determine what should stay or be removed +    # Determine if the interface should be disabled +    disabled = disable_state(conf)      if disabled == disable.both:          # was and is still disabled          intf['disable'] = True -        intf['address'] = [] -        intf['address_remove'] = [] -        intf['ipv6_eui64_prefix'] = [] -        intf['ipv6_eui64_prefix_remove'] = []      elif disabled == disable.now:          # it is now disable but was not before          intf['disable'] = True -        intf['address'] = [] -        intf['address_remove'] = eff_addr -        intf['ipv6_eui64_prefix'] = [] -        intf['ipv6_eui64_prefix_remove'] = eff_eui      elif disabled == disable.was:          # it was disable but not anymore          intf['disable'] = False -        intf['address'] = act_addr -        intf['address_remove'] = [] -        intf['ipv6_eui64_prefix'] = act_eui -        intf['ipv6_eui64_prefix_remove'] = []      else:          # normal change          intf['disable'] = False -        intf['address'] = act_addr -        intf['address_remove'] = list_diff(eff_addr, act_addr) -        intf['ipv6_eui64_prefix'] = act_eui -        intf['ipv6_eui64_prefix_remove'] = list_diff(eff_eui, act_eui) -    # Remove the default link-local address if set. -    if conf.exists('ipv6 address no-default-link-local'): +    # Remove the default link-local address if no-default-link-local is set, +    # if member of a bridge or if disabled (it may not have a MAC if it's down) +    if ( conf.exists('ipv6 address no-default-link-local') +            or intf.get('is_bridge_member') +            or intf['disable'] ):          intf['ipv6_eui64_prefix_remove'].append('fe80::/64')      else:          # add the link-local by default to make IPv6 work          intf['ipv6_eui64_prefix'].append('fe80::/64') -    # Find out if MAC has changed +    # If MAC has changed, remove and re-add all IPv6 EUI64 addresses      try:          interface = Interface(intf['intf'], create=False)          if intf['mac'] and intf['mac'] != interface.get_mac():              intf['ipv6_eui64_prefix_remove'] += intf['ipv6_eui64_prefix']      except Exception: -        # If the interface does not exists, it can not have changed +        # If the interface does not exist, it could not have changed          pass      return intf, disable @@ -381,8 +375,7 @@ def add_to_dict(conf, disabled, ifdict, section, key):      # the section to parse for vlan      sections = [] -    # Determine interface addresses (currently effective) - to determine which -    # address is no longer valid and needs to be removed from the bond +    # determine which interfaces to add or remove based on disable state      if disabled == disable.both:          # was and is still disabled          ifdict[f'{key}_remove'] = [] @@ -395,7 +388,7 @@ def add_to_dict(conf, disabled, ifdict, section, key):          sections = active      else:          # normal change -        # get vif-s interfaces (currently effective) - to determine which vif-s +        # get interfaces (currently effective) - to determine which          # interface is no longer present and needs to be removed          ifdict[f'{key}_remove'] = list_diff(effect, active)          sections = active @@ -406,7 +399,8 @@ def add_to_dict(conf, disabled, ifdict, section, key):      for s in sections:          # set config level to vif interface          conf.set_level(current_level + [section, s]) -        ifdict[f'{key}'].append(vlan_to_dict(conf)) +        # add the vlan config as a key (vlan id) - value (config) pair +        ifdict[key][s] = vlan_to_dict(conf)      # re-set configuration level to leave things as found      conf.set_level(current_level) @@ -416,13 +410,9 @@ def add_to_dict(conf, disabled, ifdict, section, key):  def vlan_to_dict(conf, default=vlan_default):      vlan, disabled = intf_to_dict(conf, default) -    # get the '100' in 'interfaces bonding bond0 vif-s 100 -    vlan['id'] = conf.get_level()[-1] - -    current_level = conf.get_level()      # if this is a not within vif-s node, we are done -    if current_level[-2] != 'vif-s': +    if conf.get_level()[-2] != 'vif-s':          return vlan      # ethertype is mandatory on vif-s nodes and only exists here! @@ -434,7 +424,6 @@ def vlan_to_dict(conf, default=vlan_default):      # check if there is a Q-in-Q vlan customer interface      # and call this function recursively -      add_to_dict(conf, disable, vlan, 'vif-c', 'vif_c')      return vlan diff --git a/python/vyos/debug.py b/python/vyos/debug.py index 60e5291c1..6ce42b173 100644 --- a/python/vyos/debug.py +++ b/python/vyos/debug.py @@ -86,10 +86,17 @@ def _timed(message):      return f'{now} {message}' +def _remove_invisible(string): +    for char in ('\0', '\a', '\b', '\f', '\v'): +        string = string.replace(char, '') +    return string + +  def _format(flag, message):      """      format a log message      """ +    message = _remove_invisible(message)      return f'DEBUG/{flag.upper():<7} {message}\n' diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index de5ca369f..7b42e3399 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -42,6 +42,7 @@ from vyos.ifconfig.control import Control  from vyos.ifconfig.dhcp import DHCP  from vyos.ifconfig.vrrp import VRRP  from vyos.ifconfig.operational import Operational +from vyos.ifconfig import Section  class Interface(Control): @@ -638,6 +639,10 @@ class Interface(Control):          # XXX: normalize/compress with ipaddress if calling functions don't?          # is subnet mask always passed, and in the same way? +        # do not add same address twice +        if addr in self._addr: +            return False +          # we can't have both DHCP and static IPv4 addresses assigned          for a in self._addr:              if ( ( addr == 'dhcp' and a != 'dhcpv6' and is_ipv4(a) ) or @@ -646,27 +651,20 @@ class Interface(Control):                      "Can't configure both static IPv4 and DHCP address "                      "on the same interface")) -        # do not add same address twice -        if addr in self._addr: -            return False -          # add to interface          if addr == 'dhcp': -            self._addr.append(addr)              self.dhcp.v4.set() -            return True - -        if addr == 'dhcpv6': -            self._addr.append(addr) +        elif addr == 'dhcpv6':              self.dhcp.v6.set() -            return True - -        if not is_intf_addr_assigned(self.ifname, addr): -            self._addr.append(addr) +        elif not is_intf_addr_assigned(self.ifname, addr):              self._cmd(f'ip addr add "{addr}" dev "{self.ifname}"') -            return True +        else: +            return False + +        # add to cache +        self._addr.append(addr) -        return False +        return True      def del_addr(self, addr):          """ @@ -693,24 +691,21 @@ class Interface(Control):          ['2001:db8::ffff/64']          """ -        # remove from cache (dhcp, and dhcpv6 can not be in it) -        if addr in self._addr: -            self._addr.remove(addr) -          # remove from interface          if addr == 'dhcp':              self.dhcp.v4.delete() -            return True - -        if addr == 'dhcpv6': +        elif addr == 'dhcpv6':              self.dhcp.v6.delete() -            return True - -        if is_intf_addr_assigned(self.ifname, addr): +        elif is_intf_addr_assigned(self.ifname, addr):              self._cmd(f'ip addr del "{addr}" dev "{self.ifname}"') -            return True +        else: +            return False + +        # remove from cache +        if addr in self._addr: +            self._addr.remove(addr) -        return False +        return True      def flush_addrs(self):          """ @@ -724,3 +719,22 @@ class Interface(Control):          # flush all addresses          self._cmd(f'ip addr flush dev "{self.ifname}"') + +    def add_to_bridge(self, br): +        """ +        Adds the interface to the bridge with the passed port config. + +        Returns False if bridge doesn't exist. +        """ + +        # check if the bridge exists (on boot it doesn't) +        if br not in Section.interfaces('bridge'): +            return False + +        self.flush_addrs() +        # add interface to bridge - use Section.klass to get BridgeIf class +        Section.klass(br)(br, create=False).add_port(self.ifname) + +        # TODO: port config (STP) + +        return True diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py index 092236fef..926c22e8a 100644 --- a/python/vyos/ifconfig/section.py +++ b/python/vyos/ifconfig/section.py @@ -54,7 +54,7 @@ class Section:          name = name.rstrip('0123456789')          name = name.rstrip('.')          if vlan: -            name = name.rstrip('0123456789') +            name = name.rstrip('0123456789.')          return name      @classmethod @@ -137,3 +137,22 @@ class Section:          eth, lo, vxlan, dum, ...          """          return list(cls._prefixes.keys()) + +    @classmethod +    def get_config_path(cls, name): +        """ +        get config path to interface with .vif or .vif-s.vif-c +        example: eth0.1.2 -> 'ethernet eth0 vif-s 1 vif-c 2' +        Returns False if interface name is invalid (not found in sections) +        """ +        sect = cls.section(name) +        if sect: +            splinterface = name.split('.') +            intfpath = f'{sect} {splinterface[0]}' +            if len(splinterface) == 2: +                intfpath += f' vif {splinterface[1]}' +            elif len(splinterface) == 3: +                intfpath += f' vif-s {splinterface[1]} vif-c {splinterface[2]}' +            return intfpath +        else: +            return False diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index ee009f7f9..09fb8c802 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -16,6 +16,53 @@  from netifaces import interfaces  from vyos import ConfigError +def apply_all_vlans(intf, intfconfig): +    """ +    Function applies all VLANs to the passed interface. + +    intf: object of Interface class +    intfconfig: dict with interface configuration +    """ +    # remove no longer required service VLAN interfaces (vif-s) +    for vif_s in intfconfig['vif_s_remove']: +        intf.del_vlan(vif_s) + +    # create service VLAN interfaces (vif-s) +    for vif_s_id, vif_s in intfconfig['vif_s'].items(): +        s_vlan = intf.add_vlan(vif_s_id, ethertype=vif_s['ethertype']) +        apply_vlan_config(s_vlan, vif_s) + +        # remove no longer required client VLAN interfaces (vif-c) +        # on lower service VLAN interface +        for vif_c in intfconfig['vif_c_remove']: +            s_vlan.del_vlan(vif_c) + +        # create client VLAN interfaces (vif-c) +        # on lower service VLAN interface +        for vif_c_id, vif_c in vif_s['vif_c'].items(): +            c_vlan = s_vlan.add_vlan(vif_c_id) +            apply_vlan_config(c_vlan, vif_c) + +    # remove no longer required VLAN interfaces (vif) +    for vif in intfconfig['vif_remove']: +        intf.del_vlan(vif) + +    # create VLAN interfaces (vif) +    for vif_id, vif in intfconfig['vif'].items(): +        # QoS priority mapping can only be set during interface creation +        # so we delete the interface first if required. +        if vif['egress_qos_changed'] or vif['ingress_qos_changed']: +            try: +                # on system bootup the above condition is true but the interface +                # does not exists, which throws an exception, but that's legal +                intf.del_vlan(vif_id) +            except: +                pass + +        vlan = intf.add_vlan(vif_id, ingress_qos=vif['ingress_qos'], egress_qos=vif['egress_qos']) +        apply_vlan_config(vlan, vif) + +  def apply_vlan_config(vlan, config):      """      Generic function to apply a VLAN configuration from a dictionary @@ -63,8 +110,10 @@ def apply_vlan_config(vlan, config):      # Maximum Transmission Unit (MTU)      vlan.set_mtu(config['mtu']) -    # assign/remove VRF -    vlan.set_vrf(config['vrf']) +    # assign/remove VRF (ONLY when not a member of a bridge, +    # otherwise 'nomaster' removes it from it) +    if not config['is_bridge_member']: +        vlan.set_vrf(config['vrf'])      # Delete old IPv6 EUI64 addresses before changing MAC      for addr in config['ipv6_eui64_prefix_remove']: @@ -92,46 +141,97 @@ def apply_vlan_config(vlan, config):      for addr in config['address']:          vlan.add_addr(addr) +    # re-add ourselves to any bridge we might have fallen out of +    if config['is_bridge_member']: +        vlan.add_to_bridge(config['is_bridge_member']) +  def verify_vlan_config(config):      """      Generic function to verify VLAN config consistency. Instead of re-      implementing this function in multiple places use single source \o/      """ -    for vif in config['vif']: +    # config['vif'] is a dict with ids as keys and config dicts as values +    for vif in config['vif'].values():          # DHCPv6 parameters-only and temporary address are mutually exclusive          if vif['dhcpv6_prm_only'] and vif['dhcpv6_temporary']:              raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') -        vrf_name = vif['vrf'] -        if vrf_name and vrf_name not in interfaces(): -            raise ConfigError(f'VRF "{vrf_name}" does not exist') +        if ( vif['is_bridge_member'] +                and ( vif['address'] +                    or vif['ipv6_eui64_prefix'] +                    or vif['ipv6_autoconf'] ) ): +            raise ConfigError(( +                    f'Cannot assign address to vif interface {vif["intf"]} ' +                    f'which is a member of bridge {vif["is_bridge_member"]}')) + +        if vif['vrf']: +            if vif['vrf'] not in interfaces(): +                raise ConfigError(f'VRF "{vif["vrf"]}" does not exist') + +            if vif['is_bridge_member']: +                raise ConfigError(( +                    f'vif {vif["intf"]} cannot be member of VRF {vif["vrf"]} ' +                    f'and bridge {vif["is_bridge_member"]} at the same time!'))      # e.g. wireless interface has no vif_s support      # thus we bail out eraly.      if 'vif_s' not in config.keys():          return -    for vif_s in config['vif_s']: -        for vif in config['vif']: -            if vif['id'] == vif_s['id']: -                raise ConfigError('Can not use identical ID on vif and vif-s interface') +    # config['vif_s'] is a dict with ids as keys and config dicts as values +    for vif_s_id, vif_s in config['vif_s'].items(): +        for vif_id, vif in config['vif'].items(): +            if vif_id == vif_s_id: +                raise ConfigError(( +                    f'Cannot use identical ID on vif "{vif["intf"]}" ' +                    f'and vif-s "{vif_s}"'))          # DHCPv6 parameters-only and temporary address are mutually exclusive          if vif_s['dhcpv6_prm_only'] and vif_s['dhcpv6_temporary']: -            raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - -            vrf_name = vif_s['vrf'] -            if vrf_name and vrf_name not in interfaces(): -                raise ConfigError(f'VRF "{vrf_name}" does not exist') - -        for vif_c in vif_s['vif_c']: +            raise ConfigError(( +                'DHCPv6 temporary and parameters-only options are mutually ' +                'exclusive!')) + +        if ( vif_s['is_bridge_member'] +                and ( vif_s['address'] +                    or vif_s['ipv6_eui64_prefix'] +                    or vif_s['ipv6_autoconf'] ) ): +            raise ConfigError(( +                    f'Cannot assign address to vif-s interface {vif_s["intf"]} ' +                    f'which is a member of bridge {vif_s["is_bridge_member"]}')) + +        if vif_s['vrf']: +            if vif_s['vrf'] not in interfaces(): +                raise ConfigError(f'VRF "{vif_s["vrf"]}" does not exist') + +            if vif_s['is_bridge_member']: +                raise ConfigError(( +                    f'vif-s {vif_s["intf"]} cannot be member of VRF {vif_s["vrf"]} ' +                    f'and bridge {vif_s["is_bridge_member"]} at the same time!')) + +        # vif_c is a dict with ids as keys and config dicts as values +        for vif_c in vif_s['vif_c'].values():              # DHCPv6 parameters-only and temporary address are mutually exclusive              if vif_c['dhcpv6_prm_only'] and vif_c['dhcpv6_temporary']: -                raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - -            vrf_name = vif_c['vrf'] -            if vrf_name and vrf_name not in interfaces(): -                raise ConfigError(f'VRF "{vrf_name}" does not exist') - +                raise ConfigError(( +                    'DHCPv6 temporary and parameters-only options are ' +                    'mutually exclusive!')) + +            if ( vif_c['is_bridge_member'] +                    and ( vif_c['address'] +                        or vif_c['ipv6_eui64_prefix'] +                        or vif_c['ipv6_autoconf'] ) ): +                raise ConfigError(( +                    f'Cannot assign address to vif-c interface {vif_c["intf"]} ' +                    f'which is a member of bridge {vif_c["is_bridge_member"]}')) + +            if vif_c['vrf']: +                if vif_c['vrf'] not in interfaces(): +                    raise ConfigError(f'VRF "{vif_c["vrf"]}" does not exist') + +                if vif_c['is_bridge_member']: +                    raise ConfigError(( +                    f'vif-c {vif_c["intf"]} cannot be member of VRF {vif_c["vrf"]} ' +                    f'and bridge {vif_c["is_bridge_member"]} at the same time!')) diff --git a/python/vyos/util.py b/python/vyos/util.py index 92b6f7992..e598e0ff3 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -498,3 +498,74 @@ def get_half_cpus():      if cpu > 1:          cpu /= 2      return int(cpu) + +def ifname_from_config(conf): +    """ +    Gets interface name with VLANs from current config level. +    Level must be at the interface whose name we want. + +    Example: +    >>> from vyos.util import ifname_from_config +    >>> from vyos.config import Config +    >>> conf = Config() +    >>> conf.set_level('interfaces ethernet eth0 vif-s 1 vif-c 2') +    >>> ifname_from_config(conf) +    'eth0.1.2' +    """ +    level = conf.get_level() + +    # vlans +    if level[-2] == 'vif' or level[-2] == 'vif-s': +        return level[-3] + '.' + level[-1] +    if level[-2] == 'vif-c': +        return level[-5] + '.' + level[-3] + '.' + level[-1] + +    # no vlans +    return level[-1] + +def get_bridge_member_config(conf, br, intf): +    """ +    Gets bridge port (member) configuration + +    Arguments: +    conf: Config +    br: bridge name +    intf: interface name + +    Returns: +    dict with the configuration +    False if bridge or bridge port doesn't exist +    """ +    old_level = conf.get_level() +    conf.set_level([]) + +    bridge = f'interfaces bridge {br}' +    member = f'{bridge} member interface {intf}' +    if not ( conf.exists(bridge) and conf.exists(member) ): +        return False + +    # default bridge port configuration +    # cost and priority initialized with linux defaults +    # by reading /sys/devices/virtual/net/br0/brif/eth2/{path_cost,priority} +    # after adding interface to bridge after reboot +    memberconf = { +        'cost': 100, +        'priority': 32, +        'arp_cache_tmo': 30, +        'disable_link_detect': 1, +    } + +    if conf.exists(f'{member} cost'): +        memberconf['cost'] = int(conf.return_value(f'{member} cost')) + +    if conf.exists(f'{member} priority'): +        memberconf['priority'] = int(conf.return_value(f'{member} priority')) + +    if conf.exists(f'{bridge} ip arp-cache-timeout'): +        memberconf['arp_cache_tmo'] = int(conf.return_value(f'{bridge} ip arp-cache-timeout')) + +    if conf.exists(f'{bridge} disable-link-detect'): +        memberconf['disable_link_detect'] = 2 + +    conf.set_level(old_level) +    return memberconf diff --git a/python/vyos/validate.py b/python/vyos/validate.py index 446f6e4ca..e083604c5 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -241,26 +241,66 @@ def assert_mac(m):      if octets[:5] == (0, 0, 94, 0, 1):          raise ValueError(f'{m} is a VRRP MAC address') -def is_bridge_member(conf, interface): +def is_member(conf, interface, intftype=None):      """ -    Checks if passed interfaces is part of a bridge device or not. - -    Returns a tuple: -    None -> Interface not a bridge member -    Bridge -> Interface is a member of this bridge +    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 -    old_level = conf.get_level() + +    if intftype not in ['bonding', 'bridge', None]: +        raise ValueError(( +            f'unknown interface type "{intftype}" or it cannot ' +            f'have member interfaces')) + +    intftype = ['bonding', 'bridge'] if intftype == None else [intftype]      # set config level to root +    old_level = conf.get_level()      conf.set_level([]) -    base = ['interfaces', 'bridge'] -    for bridge in conf.list_nodes(base): -        members = conf.list_nodes(base + [bridge, 'member', 'interface']) -        if interface in members: -            ret_val = bridge -            break + +    for it in intftype: +        base = 'interfaces ' + it +        for intf in conf.list_nodes(base): +            memberintf = f'{base} {intf} member interface' +            if conf.is_tag(memberintf): +                if interface in conf.list_nodes(memberintf): +                    ret_val = intf +                    break +            elif conf.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 has_address_configured(conf, intf): +    """ +    Checks if interface has an address configured. +    Checks the following config nodes: +    'address', 'ipv6 address eui64', 'ipv6 address autoconf' + +    Returns True if interface has address configured, False if it doesn't. +    """ +    from vyos.ifconfig import Section +    ret = False + +    old_level = conf.get_level() +    conf.set_level([]) + +    intfpath = 'interfaces ' + Section.get_config_path(intf) +    if ( conf.exists(f'{intfpath} address') or +            conf.exists(f'{intfpath} ipv6 address autoconf') or +            conf.exists(f'{intfpath} ipv6 address eui64') ): +        ret = True + +    conf.set_level(old_level) +    return ret  | 
