diff options
Diffstat (limited to 'src')
| -rwxr-xr-x | src/conf_mode/interfaces-bonding.py | 437 | 
1 files changed, 113 insertions, 324 deletions
| diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index a16c4e105..8e87a0059 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -16,41 +16,25 @@  import os -from copy import deepcopy  from sys import exit  from netifaces import interfaces -from vyos.ifconfig import BondIf -from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config -from vyos.configdict import list_diff, intf_to_dict, add_to_dict, interface_default_data  from vyos.config import Config -from vyos.util import call, cmd -from vyos.validate import is_member, has_address_configured +from vyos.configdict import get_interface_dict +from vyos.configdict import leaf_node_changed +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_vlan_config +from vyos.configverify import verify_vrf +from vyos.ifconfig import BondIf +from vyos.validate import is_member +from vyos.validate import has_address_configured  from vyos import ConfigError -  from vyos import airbag  airbag.enable() -default_config_data = { -    **interface_default_data, -    'arp_mon_intvl': 0, -    'arp_mon_tgt': [], -    'deleted': False, -    'hash_policy': 'layer2', -    'intf': '', -    'ip_arp_cache_tmo': 30, -    'ip_proxy_arp_pvlan': 0, -    'mode': '802.3ad', -    'member': [], -    'shutdown_required': False, -    'primary': '', -    'vif_s': {}, -    'vif_s_remove': [], -    'vif': {}, -    'vif_remove': [], -} - -  def get_bond_mode(mode):      if mode == 'round-robin':          return 'balance-rr' @@ -67,339 +51,144 @@ def get_bond_mode(mode):      elif mode == 'adaptive-load-balance':          return 'balance-alb'      else: -        raise ConfigError('invalid bond mode "{}"'.format(mode)) +        raise ConfigError(f'invalid bond mode "{mode}"')  def get_config(): +    """ +    Retrive CLI config as dictionary. Dictionary can never be empty, as at least the +    interface name will be added or a deleted flag +    """ +    conf = Config() +    base = ['interfaces', 'bonding'] +      # determine tagNode instance      if 'VYOS_TAGNODE_VALUE' not in os.environ:          raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified')      ifname = os.environ['VYOS_TAGNODE_VALUE'] -    conf = Config() +    bond = get_interface_dict(conf, base, ifname) + +    # To make our own life easier transfor the list of member interfaces +    # into a dictionary - we will use this to add additional information +    # later on for wach member +    if 'member' in bond and 'interface' in bond['member']: +        # first convert it to a list if only one member is given +        if isinstance(bond['member']['interface'], str): +            bond['member']['interface'] = [bond['member']['interface']] + +        tmp={} +        for interface in bond['member']['interface']: +            tmp.update({interface: {}}) + +        bond['member']['interface'] = tmp + +    if 'mode' in bond: +        bond['mode'] = get_bond_mode(bond['mode']) + +    tmp = leaf_node_changed(conf, ['mode']) +    if tmp: +        bond.update({'shutdown_required': ''}) + +    # determine which members have been removed +    tmp = leaf_node_changed(conf, ['member', 'interface']) +    if tmp: +        bond.update({'shutdown_required': ''}) +        if 'member' in bond: +            bond['member'].update({'interface_remove': tmp }) +        else: +            bond.update({'member': {'interface_remove': tmp }}) + +    if 'member' in bond and 'interface' in bond['member']: +        for interface, interface_config in bond['member']['interface'].items(): +            # Check if we are a member of another bond device +            tmp = is_member(conf, interface, 'bridge') +            if tmp: +                interface_config.update({'is_bridge_member' : tmp}) -    # initialize kernel module if not loaded -    if not os.path.isfile('/sys/class/net/bonding_masters'): -        import syslog -        syslog.syslog(syslog.LOG_NOTICE, "loading bonding kernel module") -        if call('modprobe bonding max_bonds=0 miimon=250') != 0: -            syslog.syslog(syslog.LOG_NOTICE, "failed loading bonding kernel module") -            raise ConfigError("failed loading bonding kernel module") - -    # check if bond has been removed -    cfg_base = 'interfaces bonding ' + ifname -    if not conf.exists(cfg_base): -        bond = deepcopy(default_config_data) -        bond['intf'] = ifname -        bond['deleted'] = True -        return bond - -    # set new configuration level -    conf.set_level(cfg_base) - -    bond, disabled = intf_to_dict(conf, default_config_data) - -    # ARP link monitoring frequency in milliseconds -    if conf.exists('arp-monitor interval'): -        bond['arp_mon_intvl'] = int(conf.return_value('arp-monitor interval')) - -    # IP address to use for ARP monitoring -    if conf.exists('arp-monitor target'): -        bond['arp_mon_tgt'] = conf.return_values('arp-monitor target') - -    # Bonding transmit hash policy -    if conf.exists('hash-policy'): -        bond['hash_policy'] = conf.return_value('hash-policy') - -    # ARP cache entry timeout in seconds -    if conf.exists('ip arp-cache-timeout'): -        bond['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) - -    # Enable private VLAN proxy ARP on this interface -    if conf.exists('ip proxy-arp-pvlan'): -        bond['ip_proxy_arp_pvlan'] = 1 - -    # Bonding mode -    if conf.exists('mode'): -        act_mode = conf.return_value('mode') -        eff_mode = conf.return_effective_value('mode') -        if not (act_mode == eff_mode): -            bond['shutdown_required'] = True - -        bond['mode'] = get_bond_mode(act_mode) - -    # determine bond member interfaces (currently configured) -    bond['member'] = conf.return_values('member interface') - -    # We can not call conf.return_effective_values() as it would not work -    # on reboots. Reboots/First boot will return that running config and -    # saved config is the same, thus on a reboot the bond members will -    # not be added all (https://phabricator.vyos.net/T2030) -    live_members = BondIf(bond['intf']).get_slaves() -    if not (bond['member'] == live_members): -        bond['shutdown_required'] = True - -    # Primary device interface -    if conf.exists('primary'): -        bond['primary'] = conf.return_value('primary') - -    add_to_dict(conf, disabled, bond, 'vif', 'vif') -    add_to_dict(conf, disabled, bond, 'vif-s', 'vif_s') +            # Check if we are a member of a bond device +            tmp = is_member(conf, interface, 'bonding') +            if tmp and tmp != ifname: +                interface_config.update({'is_bond_member' : tmp}) + +            # bond members must not have an assigned address +            tmp = has_address_configured(conf, interface) +            if tmp: +                interface_config.update({'has_address' : ''})      return bond  def verify(bond): -    if bond['deleted']: -        if bond['is_bridge_member']: -            raise ConfigError(( -                f'Cannot delete interface "{bond["intf"]}" as it is a ' -                f'member of bridge "{bond["is_bridge_member"]}"!')) - +    if 'deleted' in bond: +        verify_bridge_delete(bond)          return None -    if len(bond['arp_mon_tgt']) > 16: -        raise ConfigError('The maximum number of arp-monitor targets is 16') +    if 'arp_monitor' in bond: +        if 'target' in bond['arp_monitor'] and len(int(bond['arp_monitor']['target'])) > 16: +            raise ConfigError('The maximum number of arp-monitor targets is 16') + +        if 'interval' in bond['arp_monitor'] and len(int(bond['arp_monitor']['interval'])) > 0: +            if bond['mode'] in ['802.3ad', 'balance-tlb', 'balance-alb']: +                raise ConfigError('ARP link monitoring does not work for mode 802.3ad, ' \ +                                  'transmit-load-balance or adaptive-load-balance') -    if bond['primary']: +    if 'primary' in bond:          if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: -            raise ConfigError(( -                'Mode dependency failed, primary not supported in mode ' -                f'"{bond["mode"]}"!')) - -    if ( bond['is_bridge_member'] -            and ( bond['address'] -                or bond['ipv6_eui64_prefix'] -                or bond['ipv6_autoconf'] ) ): -        raise ConfigError(( -            f'Cannot assign address to interface "{bond["intf"]}" ' -            f'as it is a member of bridge "{bond["is_bridge_member"]}"!')) - -    if bond['vrf']: -        if bond['vrf'] not in interfaces(): -            raise ConfigError(f'VRF "{bond["vrf"]}" does not exist') - -        if bond['is_bridge_member']: -            raise ConfigError(( -                f'Interface "{bond["intf"]}" cannot be member of VRF ' -                f'"{bond["vrf"]}" and bridge {bond["is_bridge_member"]} ' -                f'at the same time!')) +            raise ConfigError('Option primary - mode dependency failed, not' +                              'supported in mode {mode}!'.format(**bond)) + +    verify_address(bond) +    verify_dhcpv6(bond) +    verify_vrf(bond)      # use common function to verify VLAN configuration      verify_vlan_config(bond) -    conf = Config() -    for intf in bond['member']: -        # check if member interface is "real" -        if intf not in interfaces(): -            raise ConfigError(f'Interface {intf} does not exist!') - -        # a bonding member interface is only allowed to be assigned to one bond! -        all_bonds = conf.list_nodes('interfaces bonding') -        # We do not need to check our own bond -        all_bonds.remove(bond['intf']) -        for tmp in all_bonds: -            if conf.exists('interfaces bonding {tmp} member interface {intf}'): -                raise ConfigError(( -                    f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' -                    f'it is already a member of bond "{tmp}"!')) - -        # can not add interfaces with an assigned address to a bond -        if has_address_configured(conf, intf): -            raise ConfigError(( -                f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' -                f'it has an address assigned!')) - -        # bond members are not allowed to be bridge members -        tmp = is_member(conf, intf, 'bridge') -        if tmp: -            raise ConfigError(( -                    f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' -                    f'it is already a member of bridge "{tmp}"!')) - -        # bond members are not allowed to be vrrp members -        for tmp in conf.list_nodes('high-availability vrrp group'): -            if conf.exists('high-availability vrrp group {tmp} interface {intf}'): -                raise ConfigError(( -                    f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' -                    f'it is already a member of VRRP group "{tmp}"!')) - -        # bond members are not allowed to be underlaying psuedo-ethernet devices -        for tmp in conf.list_nodes('interfaces pseudo-ethernet'): -            if conf.exists('interfaces pseudo-ethernet {tmp} link {intf}'): -                raise ConfigError(( -                    f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' -                    f'it is already the link of pseudo-ethernet "{tmp}"!')) - -        # bond members are not allowed to be underlaying vxlan devices -        for tmp in conf.list_nodes('interfaces vxlan'): -            if conf.exists('interfaces vxlan {tmp} link {intf}'): -                raise ConfigError(( -                    f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' -                    f'it is already the link of VXLAN "{tmp}"!')) - -    if bond['primary']: -        if bond['primary'] not in bond['member']: -            raise ConfigError(f'Bond "{bond["intf"]}" primary interface must be a member') +    bond_name = bond['ifname'] +    if 'member' in bond: +        member = bond.get('member') +        for interface, interface_config in member.get('interface', {}).items(): +            error_msg = f'Can not add interface "{interface}" to bond "{bond_name}", ' + +            if interface == 'lo': +                raise ConfigError('Loopback interface "lo" can not be added to a bond') + +            if interface not in interfaces(): +                raise ConfigError(error_msg + 'it does not exist!') + +            if 'is_bridge_member' in interface_config: +                tmp = interface_config['is_bridge_member'] +                raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') + +            if 'is_bond_member' in interface_config: +                tmp = interface_config['is_bond_member'] +                raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') + +            if 'has_address' in interface_config: +                raise ConfigError(error_msg + 'it has an address assigned!') + + +    if 'primary' in bond: +        if bond['primary'] not in bond['member']['interface']: +            raise ConfigError(f'Primary interface of bond "{bond_name}" must be a member interface')          if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']:              raise ConfigError('primary interface only works for mode active-backup, ' \                                'transmit-load-balance or adaptive-load-balance') -    if bond['arp_mon_intvl'] > 0: -        if bond['mode'] in ['802.3ad', 'balance-tlb', 'balance-alb']: -            raise ConfigError('ARP link monitoring does not work for mode 802.3ad, ' \ -                              'transmit-load-balance or adaptive-load-balance') -      return None  def generate(bond):      return None  def apply(bond): -    b = BondIf(bond['intf']) +    b = BondIf(bond['ifname']) -    if bond['deleted']: +    if 'deleted' in bond:          # delete interface          b.remove()      else: -        # ARP link monitoring frequency, reset miimon when arp-montior is inactive -        # this is done inside BondIf automatically -        b.set_arp_interval(bond['arp_mon_intvl']) - -        # ARP monitor targets need to be synchronized between sysfs and CLI. -        # Unfortunately an address can't be send twice to sysfs as this will -        # result in the following exception:  OSError: [Errno 22] Invalid argument. -        # -        # We remove ALL adresses prior adding new ones, this will remove addresses -        # added manually by the user too - but as we are limited to 16 adresses -        # from the kernel side this looks valid to me. We won't run into an error -        # when a user added manual adresses which would result in having more -        # then 16 adresses in total. -        arp_tgt_addr = list(map(str, b.get_arp_ip_target().split())) -        for addr in arp_tgt_addr: -            b.set_arp_ip_target('-' + addr) - -        # Add configured ARP target addresses -        for addr in bond['arp_mon_tgt']: -            b.set_arp_ip_target('+' + addr) - -        # update interface description used e.g. within SNMP -        b.set_alias(bond['description']) - -        if bond['dhcp_client_id']: -            b.dhcp.v4.options['client_id'] = bond['dhcp_client_id'] - -        if bond['dhcp_hostname']: -            b.dhcp.v4.options['hostname'] = bond['dhcp_hostname'] - -        if bond['dhcp_vendor_class_id']: -            b.dhcp.v4.options['vendor_class_id'] = bond['dhcp_vendor_class_id'] - -        if bond['dhcpv6_prm_only']: -            b.dhcp.v6.options['dhcpv6_prm_only'] = True - -        if bond['dhcpv6_temporary']: -            b.dhcp.v6.options['dhcpv6_temporary'] = True - -        if bond['dhcpv6_pd_length']: -            b.dhcp.v6.options['dhcpv6_pd_length'] = bond['dhcpv6_pd_length'] - -        if bond['dhcpv6_pd_interfaces']: -            b.dhcp.v6.options['dhcpv6_pd_interfaces'] = bond['dhcpv6_pd_interfaces'] - -        # ignore link state changes -        b.set_link_detect(bond['disable_link_detect']) -        # Bonding transmit hash policy -        b.set_hash_policy(bond['hash_policy']) -        # configure ARP cache timeout in milliseconds -        b.set_arp_cache_tmo(bond['ip_arp_cache_tmo']) -        # configure ARP filter configuration -        b.set_arp_filter(bond['ip_disable_arp_filter']) -        # configure ARP accept -        b.set_arp_accept(bond['ip_enable_arp_accept']) -        # configure ARP announce -        b.set_arp_announce(bond['ip_enable_arp_announce']) -        # configure ARP ignore -        b.set_arp_ignore(bond['ip_enable_arp_ignore']) -        # Enable proxy-arp on this interface -        b.set_proxy_arp(bond['ip_proxy_arp']) -        # Enable private VLAN proxy ARP on this interface -        b.set_proxy_arp_pvlan(bond['ip_proxy_arp_pvlan']) -        # IPv6 accept RA -        b.set_ipv6_accept_ra(bond['ipv6_accept_ra']) -        # IPv6 address autoconfiguration -        b.set_ipv6_autoconf(bond['ipv6_autoconf']) -        # IPv6 forwarding -        b.set_ipv6_forwarding(bond['ipv6_forwarding']) -        # IPv6 Duplicate Address Detection (DAD) tries -        b.set_ipv6_dad_messages(bond['ipv6_dup_addr_detect']) - -        # Delete old IPv6 EUI64 addresses before changing MAC -        for addr in bond['ipv6_eui64_prefix_remove']: -            b.del_ipv6_eui64_address(addr) - -        # Change interface MAC address -        if bond['mac']: -            b.set_mac(bond['mac']) - -        # Add IPv6 EUI-based addresses -        for addr in bond['ipv6_eui64_prefix']: -            b.add_ipv6_eui64_address(addr) - -        # Maximum Transmission Unit (MTU) -        b.set_mtu(bond['mtu']) - -        # Primary device interface -        if bond['primary']: -            b.set_primary(bond['primary']) - -        # Some parameters can not be changed when the bond is up. -        if bond['shutdown_required']: -            # Disable bond prior changing of certain properties -            b.set_admin_state('down') - -            # The bonding mode can not be changed when there are interfaces enslaved -            # to this bond, thus we will free all interfaces from the bond first! -            for intf in b.get_slaves(): -                b.del_port(intf) - -            # Bonding policy/mode -            b.set_mode(bond['mode']) - -            # Add (enslave) interfaces to bond -            for intf in bond['member']: -                # if we've come here we already verified the interface doesn't -                # have addresses configured so just flush any remaining ones -                cmd(f'ip addr flush dev "{intf}"') -                b.add_port(intf) - -        # As the bond interface is always disabled first when changing -        # parameters we will only re-enable the interface if it is not -        # administratively disabled -        if not bond['disable']: -            b.set_admin_state('up') -        else: -            b.set_admin_state('down') - -        # Configure interface address(es) -        # - not longer required addresses get removed first -        # - newly addresses will be added second -        for addr in bond['address_remove']: -            b.del_addr(addr) -        for addr in bond['address']: -            b.add_addr(addr) - -        # assign/remove VRF (ONLY when not a member of a bridge, -        # otherwise 'nomaster' removes it from it) -        if not bond['is_bridge_member']: -            b.set_vrf(bond['vrf']) - -        # re-add ourselves to any bridge we might have fallen out of -        if bond['is_bridge_member']: -            b.add_to_bridge(bond['is_bridge_member']) - -        # apply all vlans to interface -        apply_all_vlans(b, bond) +        b.update(bond)      return None | 
