From 59be6188579cc61a30230f978f38f67878251abc Mon Sep 17 00:00:00 2001 From: Wen Liang Date: Wed, 2 Feb 2022 07:57:20 -0500 Subject: [PATCH] Support more bond options In order to enable user to flexibly control the network transmission over the bonded interface, support all the bond options which are currently supported by NetworkManager. Signed-off-by: Wen Liang --- README.md | 144 ++++++++++- examples/bond_options.yml | 51 ++++ library/network_connections.py | 10 +- .../network_lsr/argument_validator.py | 219 +++++++++++++++- tests/ensure_provider_tests.py | 1 + tests/playbooks/tests_bond_options.yml | 178 +++++++++++++ tests/tests_bond_options_nm.yml | 20 ++ tests/unit/test_network_connections.py | 240 +++++++++++++++++- 8 files changed, 851 insertions(+), 12 deletions(-) create mode 100644 examples/bond_options.yml create mode 100644 tests/playbooks/tests_bond_options.yml create mode 100644 tests/tests_bond_options_nm.yml diff --git a/README.md b/README.md index d4b4a4f..d14e677 100644 --- a/README.md +++ b/README.md @@ -692,19 +692,149 @@ SSL certificates and keys must be deployed on the host prior to running the role ### `bond` -The `bond` setting configures the options of bonded interfaces -(type `bond`). It supports the following options: +The `bond` setting configures the options of bonded interfaces (type `bond`). +See the [kernel documentation for +bonding](https://www.kernel.org/doc/Documentation/networking/bonding.txt) or +your distribution `nmcli` documentation for valid values. It supports the +following options: - `mode` - Bonding mode. See the - [kernel documentation](https://www.kernel.org/doc/Documentation/networking/bonding.txt) - or your distribution `nmcli` documentation for valid values. - NetworkManager defaults to `balance-rr`. + Bonding mode. The possible values are `balance-rr` (default), `active-backup`, + `balance-xor`, `broadcast`, `802.3ad`, `balance-tlb`, or `balance-alb`. + +- `ad_actor_sys_prio` + + In `802.3ad` bonding mode, this specifies the system priority. The valid range is + 1 - 65535. + +- `ad_actor_system` + + In `802.3ad` bonding mode, this specifies the system mac-address for the actor in + protocol packet exchanges (LACPDUs). + +- `ad_select` + + This option specifies the 802.3ad aggregation selection logic to use. The possible + values are: `stable`, `bandwidth`, `count`. + +- `ad_user_port_key` + + In `802.3ad` bonding mode, this defines the upper 10 bits of the port key. The + allowed range for the value is 0 - 1023. + +- `all_ports_active` + + `all_slaves_active` in kernel and NetworkManager. The boolean value `False` drops + the duplicate frames (received on inactive ports) and the boolean value `True` + delivers the duplicate frames. + +- `arp_all_targets` + + This option specifies the quantity of arp_ip_targets that must be reachable in + order for the ARP monitor to consider a port as being up. The possible values are + `any` or `all`. + +- `arp_interval` + + This option specifies the ARP link monitoring frequency in milliseconds. A value of + 0 disables ARP monitoring. + +- `arp_validate` + + In any mode that supports arp monitoring, this option specifies whether or not ARP + probes and replies should be validated. Or for link monitoring purposes, whether + non-ARP traffic should be filtered (disregarded). The possible values are: `none`, + `active`, `backup`, `all`, `filter`, `filter_active`, `filter_backup`. + +- `arp_ip_target` + + When `arp_interval` is enabled, this option specifies the IP addresses to use as + ARP monitoring peers. + +- `downdelay` + + The time to wait (in milliseconds) before disabling a port after a link failure + has been detected. + +- `fail_over_mac` + + This option specifies the policy to select the MAC address for the bond interface + in active-backup mode. The possible values are: `none` (default), `active`, + `follow`. + +- `lacp_rate` + + In `802.3ad` bonding mode, this option defines the rate in which we requst link + partner to transmit LACPDU packets. The possible values are: `slow`, `fast`. + +- `lp_interval` + + This option specifies the number of seconds between instances where the bonding + driver sends learning packets to each ports peer switch. - `miimon` - Sets the MII link monitoring interval (in milliseconds) + Sets the MII link monitoring interval (in milliseconds). + +- `min_links` + + This option specifies the minimum number of links that must be active before + asserting the carrier. + +- `num_grat_arp` + + This option specify the number of peer notifications (gratuitious ARPs) to be + issued after a failover event. The allowed range for the value is 0 - 255. + +- `packets_per_port` + + In `balance-rr` bonding mode, this option specifies the number of packets allowed + for a port in network transmission before moving to the next one. The allowed + range for the value is 0 - 65535. + +- `peer_notif_delay` + + This option specifies the delay (in milliseconds) between each peer notification + when they are issued after a failover event. + +- `primary` + + This option defines the primary device. + +- `primary_reselect` + + This option specifies the reselection policy for the primary port. The possible + values are: `always`, `better`, `failure`. + +- `resend_igmp` + + This option specifies the number of IGMP membership reports to be issued after a + failover event. The allowed range for the value is 0 - 255. + +- `tlb_dynamic_lb` + + This option specifies if dynamic shuffling of flows is enabled in tlb mode. The + boolean value `True` enables the flow shuffling while the boolean value `False` + disables it. + +- `updelay` + + This option specifies the time (in milliseconds) to wait before enabling a port + after a link recovery has been detected. + +- `use_carrier` + + This options specifies whether or not miimon should use MII or ETHTOOL ioctls + versus netif_carrier_ok() to determine the link sattus. The boolean value `True` + enables the use of netif_carrier_ok() while the boolean value `False` uses MII or + ETHTOOL ioctls instead. + +- `xmit_hash_policy` + + This option specifies the transmit hash policy to use for port selection, the + possible values are: `layer2`, `layer3+4`, `layer2+3`, `encap2+3`, `encap3+4`, + `vlan+srcmac`. Examples of Options ------------------- diff --git a/examples/bond_options.yml b/examples/bond_options.yml new file mode 100644 index 0000000..d236998 --- /dev/null +++ b/examples/bond_options.yml @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- hosts: network-test + vars: + network_connections: + # Specify the bond profile + - name: bond0 + state: up + type: bond + interface_name: bond0 + # ip configuration (optional) + ip: + address: + - "192.0.2.24/24" + - "2001:db8::23/64" + bond: + mode: 802.3ad + ad_actor_sys_prio: 65535 + ad_actor_system: 00:00:5e:00:53:5d + ad_select: stable + ad_user_port_key: 1023 + all_ports_active: True + arp_all_targets: all + downdelay: 0 + lacp_rate: slow + lp_interval: 128 + miimon: 110 + min_links: 0 + num_grat_arp: 64 + peer_notif_delay: 220 + primary_reselect: better + resend_igmp: 225 + updelay: 0 + use_carrier: True + xmit_hash_policy: encap2+3 + # add an ethernet profile to the bond + - name: member1 + state: up + type: ethernet + interface_name: eth1 + controller: bond0 + + # add a second ethernet profile to the bond + - name: member2 + state: up + type: ethernet + interface_name: eth2 + controller: bond0 + roles: + - linux-system-roles.network +... diff --git a/library/network_connections.py b/library/network_connections.py index 706b198..c661ad7 100644 --- a/library/network_connections.py +++ b/library/network_connections.py @@ -867,8 +867,14 @@ class NMUtil: s_con.set_property(NM.SETTING_CONNECTION_TYPE, NM.SETTING_BOND_SETTING_NAME) s_bond = self.connection_ensure_setting(con, NM.SettingBond) s_bond.add_option("mode", connection["bond"]["mode"]) - if connection["bond"]["miimon"] is not None: - s_bond.add_option("miimon", str(connection["bond"]["miimon"])) + for option, value in connection["bond"].items(): + if value is None: + continue + if option in ["all_ports_active", "use_carrier", "tlb_dynamic_lb"]: + value = int(value) + if option in ["all_ports_active", "packets_per_port"]: + option = option.replace("port", "slave") + s_bond.add_option(option, str(value)) elif connection["type"] == "team": s_con.set_property(NM.SETTING_CONNECTION_TYPE, NM.SETTING_TEAM_SETTING_NAME) elif connection["type"] == "dummy": diff --git a/module_utils/network_lsr/argument_validator.py b/module_utils/network_lsr/argument_validator.py index 279ac58..7064227 100644 --- a/module_utils/network_lsr/argument_validator.py +++ b/module_utils/network_lsr/argument_validator.py @@ -1158,15 +1158,204 @@ class ArgValidator_DictBond(ArgValidatorDict): name="bond", nested=[ ArgValidatorStr("mode", enum_values=ArgValidator_DictBond.VALID_MODES), + ArgValidatorNum( + "ad_actor_sys_prio", val_min=1, val_max=65535, default_value=None + ), + ArgValidatorMac("ad_actor_system"), + ArgValidatorStr( + "ad_select", enum_values=["stable", "bandwidth", "count"] + ), + ArgValidatorNum( + "ad_user_port_key", val_min=0, val_max=1023, default_value=None + ), + ArgValidatorBool("all_ports_active", default_value=None), + ArgValidatorStr("arp_all_targets", enum_values=["any", "all"]), + ArgValidatorNum( + "arp_interval", val_min=0, val_max=1000000, default_value=None + ), + ArgValidatorStr( + "arp_validate", + enum_values=[ + "none", + "active", + "backup", + "all", + "filter", + "filter_active", + "filter_backup", + ], + ), + ArgValidatorStr("arp_ip_target"), + ArgValidatorNum( + "downdelay", val_min=0, val_max=1000000, default_value=None + ), + ArgValidatorStr( + "fail_over_mac", enum_values=["none", "active", "follow"] + ), + ArgValidatorStr("lacp_rate", enum_values=["slow", "fast"]), + ArgValidatorNum( + "lp_interval", val_min=1, val_max=1000000, default_value=None + ), ArgValidatorNum( "miimon", val_min=0, val_max=1000000, default_value=None ), + ArgValidatorNum( + "min_links", val_min=0, val_max=1000000, default_value=None + ), + ArgValidatorNum( + "num_grat_arp", val_min=0, val_max=255, default_value=None + ), + ArgValidatorNum( + "packets_per_port", val_min=0, val_max=65535, default_value=None + ), + ArgValidatorNum( + "peer_notif_delay", val_min=0, val_max=1000000, default_value=None + ), + ArgValidatorStr("primary"), + ArgValidatorStr( + "primary_reselect", enum_values=["always", "better", "failure"] + ), + ArgValidatorNum( + "resend_igmp", val_min=0, val_max=255, default_value=None + ), + ArgValidatorBool("tlb_dynamic_lb", default_value=None), + ArgValidatorNum( + "updelay", val_min=0, val_max=1000000, default_value=None + ), + ArgValidatorBool("use_carrier", default_value=None), + ArgValidatorStr( + "xmit_hash_policy", + enum_values=[ + "layer2", + "layer3+4", + "layer2+3", + "encap2+3", + "encap3+4", + "vlan+srcmac", + ], + ), ], default_value=ArgValidator.MISSING, ) + def _validate_post(self, value, name, result): + AD_OPTIONS = [ + "ad_actor_sys_prio", + "ad_actor_system", + "ad_user_port_key", + "lacp_rate", + ] + ARP_OPTIONS = ["arp_interval", "arp_ip_target", "arp_validate"] + ARP_ONLY_MODE = ["balance-rr", "active-backup", "balance-xor", "broadcast"] + + if result["mode"] != "802.3ad": + for option in AD_OPTIONS: + if result[option] is not None: + raise ValidationError( + name, + "the bond option {0} is only valid with mode 802.3ad".format( + option + ), + ) + + if result["packets_per_port"] is not None and result["mode"] != "balance-rr": + raise ValidationError( + name, + "the bond option packets_per_port is only valid with mode balance-rr", + ) + + if result["mode"] not in ARP_ONLY_MODE: + for option in ARP_OPTIONS: + if result[option] is not None: + raise ValidationError( + name, + "the bond option {0} is only valid with mode balance-rr, active-backup, balance-xor or broadcast".format( + option + ), + ) + + if result["tlb_dynamic_lb"] is not None and result["mode"] not in [ + "balance-tlb", + "balance-alb", + ]: + raise ValidationError( + name, + "the bond option tlb_dynamic_lb is only valid with mode balance-tlb or balance-alb", + ) + + if result["primary"] is not None and result["mode"] not in [ + "active-backup", + "balance-tlb", + "balance-alb", + ]: + raise ValidationError( + name, + "the bond option primary is only valid with mode active-backup, balance-tlb, balance-alb", + ) + + if ( + result["updelay"] is not None or result["downdelay"] is not None + ) and not result["miimon"]: + raise ValidationError( + name, + "the bond option downdelay or updelay is only valid with miimon enabled", + ) + if result["peer_notif_delay"] is not None: + if not result["miimon"] or result["peer_notif_delay"] % result["miimon"]: + raise ValidationError( + name, + "the bond option peer_notif_delay needs miimon enabled and must be miimon multiple", + ) + if result["arp_interval"]: + raise ValidationError( + name, + "the bond option peer_notif_delay needs arp_interval disabled", + ) + if result["arp_ip_target"]: + if not result["arp_interval"]: + raise ValidationError( + name, + "the bond option arp_ip_target requires arp_interval to be set", + ) + + if result["arp_interval"]: + if not result["arp_ip_target"]: + raise ValidationError( + name, + "the bond option arp_interval requires arp_ip_target to be set", + ) + + return result + def get_default_bond(self): - return {"mode": ArgValidator_DictBond.VALID_MODES[0], "miimon": None} + return { + "mode": ArgValidator_DictBond.VALID_MODES[0], + "ad_actor_sys_prio": None, + "ad_actor_system": None, + "ad_select": None, + "ad_user_port_key": None, + "all_ports_active": None, + "arp_all_targets": None, + "arp_interval": None, + "arp_ip_target": None, + "arp_validate": None, + "downdelay": None, + "fail_over_mac": None, + "lacp_rate": None, + "lp_interval": None, + "miimon": None, + "min_links": None, + "num_grat_arp": None, + "packets_per_port": None, + "peer_notif_delay": None, + "primary": None, + "primary_reselect": None, + "resend_igmp": None, + "tlb_dynamic_lb": None, + "updelay": None, + "use_carrier": None, + "xmit_hash_policy": None, + } class ArgValidator_DictInfiniband(ArgValidatorDict): @@ -1889,6 +2078,12 @@ class ArgValidator_ListConnections(ArgValidatorList): "not a controller " "type by '%s'" % (connection["controller"], c["type"]), ) + if connection["type"] == "infiniband": + if c["type"] == "bond" and c["bond"]["mode"] != "active_backup": + raise ValidationError( + name + "[" + str(idx) + "].controller", + "bond only supports infiniband ports in active-backup mode", + ) if connection["port_type"] is None: connection["port_type"] = c["type"] elif connection["port_type"] != c["type"]: @@ -2083,6 +2278,28 @@ class ArgValidator_ListConnections(ArgValidatorList): "match.path is not supported by the running version of " "NetworkManger.", ) + + if "bond" in connection: + if mode == self.VALIDATE_ONE_MODE_INITSCRIPTS: + for option in connection["bond"]: + if connection["bond"][option] is not None and option not in [ + "mode", + "miimon", + ]: + raise ValidationError.from_connection( + idx, + "initscripts only supports the mode and miimon bond " + "options. All the other bond options are not supported by " + "initscripts.", + ) + # the `peer_notif_delay` bond option was supported in NM since NM 1.30 + if connection["bond"]["peer_notif_delay"]: + if not hasattr(Util.NM(), "SETTING_BOND_OPTION_PEER_NOTIF_DELAY"): + raise ValidationError.from_connection( + idx, + "the bond option peer_notif_delay is not supported in " + "NetworkManger until NM 1.30", + ) self.validate_route_tables(connection, idx) diff --git a/tests/ensure_provider_tests.py b/tests/ensure_provider_tests.py index 6f9b331..d53b3f5 100755 --- a/tests/ensure_provider_tests.py +++ b/tests/ensure_provider_tests.py @@ -67,6 +67,7 @@ ibution_major_version | int < 9", EXTRA_RUN_CONDITION: "ansible_distribution != 'RedHat' or\n ansible_distr\ ibution_major_version | int < 9", }, + "playbooks/tests_bond_options.yml": {}, "playbooks/tests_eth_dns_support.yml": {}, "playbooks/tests_dummy.yml": {}, "playbooks/tests_ipv6_disabled.yml": { diff --git a/tests/playbooks/tests_bond_options.yml b/tests/playbooks/tests_bond_options.yml new file mode 100644 index 0000000..36cbc97 --- /dev/null +++ b/tests/playbooks/tests_bond_options.yml @@ -0,0 +1,178 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- hosts: all + vars: + controller_profile: bond0 + controller_device: nm-bond + port1_profile: bond0.0 + dhcp_interface1: test1 + port2_profile: bond0.1 + dhcp_interface2: test2 + tasks: + - name: "INIT Prepare setup" + debug: + msg: "##################################################" + - import_tasks: tasks/create_test_interfaces_with_dhcp.yml + - import_tasks: tasks/assert_device_present.yml + vars: + interface: "{{ dhcp_interface1 }}" + - import_tasks: tasks/assert_device_present.yml + vars: + interface: "{{ dhcp_interface2 }}" + - block: + - name: "TEST Add Bond with 2 ports" + debug: + msg: "##################################################" + - name: Configure the bond options + import_role: + name: linux-system-roles.network + vars: + network_connections: + # Create a bond controller + - name: "{{ controller_profile }}" + state: up + type: bond + interface_name: "{{ controller_device }}" + bond: + mode: 802.3ad + ad_actor_sys_prio: 65535 + ad_actor_system: 00:00:5e:00:53:5d + ad_select: stable + ad_user_port_key: 1023 + all_ports_active: True + arp_all_targets: all + downdelay: 0 + lacp_rate: slow + lp_interval: 128 + miimon: 110 + min_links: 0 + num_grat_arp: 64 + primary_reselect: better + resend_igmp: 225 + updelay: 0 + use_carrier: True + xmit_hash_policy: encap2+3 + + # add an ethernet to the bond + - name: "{{ port1_profile }}" + state: up + type: ethernet + interface_name: "{{ dhcp_interface1 }}" + controller: "{{ controller_profile }}" + # add a second ethernet to the bond + - name: "{{ port2_profile }}" + state: up + type: ethernet + interface_name: "{{ dhcp_interface2 }}" + controller: "{{ controller_profile }}" + - import_tasks: tasks/assert_device_present.yml + vars: + interface: "{{ controller_device }}" + - include_tasks: tasks/assert_profile_present.yml + vars: + profile: "{{ item }}" + loop: + - "{{ controller_profile }}" + - "{{ port1_profile }}" + - "{{ port2_profile }}" + + - command: cat + /sys/class/net/{{ controller_device }}/bonding/'{{ item.key }}' + name: "** TEST check bond settings" + register: result + until: "'{{ item.value }}' in result.stdout" + loop: + - { key: 'mode', value: '802.3ad'} + - { key: 'ad_actor_sys_prio', value: '65535'} + - { key: 'ad_actor_system', value: '00:00:5e:00:53:5d'} + - { key: 'ad_select', value: 'stable'} + - { key: 'ad_user_port_key', value: '1023'} + - { key: 'all_slaves_active', value: '1'} + - { key: 'arp_all_targets', value: 'all'} + - { key: 'downdelay', value: '0'} + - { key: 'lacp_rate', value: 'slow'} + - { key: 'lp_interval', value: '128'} + - { key: 'miimon', value: '110'} + - { key: 'num_grat_arp', value: '64'} + - { key: 'resend_igmp', value: '225'} + - { key: 'updelay', value: '0'} + - { key: 'use_carrier', value: '1'} + - { key: 'xmit_hash_policy', value: 'encap2+3'} + + - command: ip -4 a s {{ controller_device }} + name: "** TEST check IPv4" + register: result + until: "'192.0.2' in result.stdout" + retries: 20 + delay: 2 + - command: ip -6 a s {{ controller_device }} + name: "** TEST check IPv6" + register: result + until: "'2001' in result.stdout" + retries: 20 + delay: 2 + + - name: Reconfigure the bond options + import_role: + name: linux-system-roles.network + vars: + network_connections: + # Create a bond controller + - name: "{{ controller_profile }}" + state: up + type: bond + interface_name: "{{ controller_device }}" + bond: + mode: active-backup + arp_interval: 60 + arp_ip_target: 192.0.2.128 + arp_validate: none + primary: "{{ dhcp_interface1 }}" + + - command: cat + /sys/class/net/{{ controller_device }}/bonding/'{{ item.key }}' + name: "** TEST check bond settings" + register: result + until: "'{{ item.value }}' in result.stdout" + loop: + - { key: 'mode', value: 'active-backup'} + - { key: 'arp_interval', value: '60'} + - { key: 'arp_ip_target', value: '192.0.2.128'} + - { key: 'arp_validate', value: 'none'} + - { key: 'primary', value: '{{ dhcp_interface1 }}'} + + - command: ip -4 a s {{ controller_device }} + name: "** TEST check IPv4" + register: result + until: "'192.0.2' in result.stdout" + retries: 20 + delay: 2 + - command: ip -6 a s {{ controller_device }} + name: "** TEST check IPv6" + register: result + until: "'2001' in result.stdout" + retries: 20 + delay: 2 + + always: + - block: + - name: Import network role + import_role: + name: linux-system-roles.network + vars: + network_connections: + - name: "{{ port2_profile }}" + persistent_state: absent + state: down + - name: "{{ port1_profile }}" + persistent_state: absent + state: down + - name: "{{ controller_profile }}" + persistent_state: absent + state: down + ignore_errors: true + - command: ip link del {{ controller_device }} + ignore_errors: true + - import_tasks: tasks/remove_test_interfaces_with_dhcp.yml + tags: + - "tests::cleanup" diff --git a/tests/tests_bond_options_nm.yml b/tests/tests_bond_options_nm.yml new file mode 100644 index 0000000..373d642 --- /dev/null +++ b/tests/tests_bond_options_nm.yml @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: BSD-3-Clause +# This file was generated by ensure_provider_tests.py +--- +# set network provider and gather facts +- hosts: all + name: Run playbook 'playbooks/tests_bond_options.yml' with nm as provider + tasks: + - include_tasks: tasks/el_repo_setup.yml + - name: Set network provider to 'nm' + set_fact: + network_provider: nm + tags: + - always + + +# The test requires or should run with NetworkManager, therefore it cannot run +# on RHEL/CentOS 6 +- import_playbook: playbooks/tests_bond_options.yml + when: + - ansible_distribution_major_version != '6' diff --git a/tests/unit/test_network_connections.py b/tests/unit/test_network_connections.py index 8d05f16..9bfa068 100644 --- a/tests/unit/test_network_connections.py +++ b/tests/unit/test_network_connections.py @@ -1737,7 +1737,34 @@ class TestValidator(Python26CompatTestCase): { "actions": ["present", "up"], "autoconnect": True, - "bond": {"mode": "balance-rr", "miimon": None}, + "bond": { + "mode": "balance-rr", + "ad_actor_sys_prio": None, + "ad_actor_system": None, + "ad_select": None, + "ad_user_port_key": None, + "all_ports_active": None, + "arp_all_targets": None, + "arp_interval": None, + "arp_ip_target": None, + "arp_validate": None, + "downdelay": None, + "fail_over_mac": None, + "lacp_rate": None, + "lp_interval": None, + "miimon": None, + "min_links": None, + "num_grat_arp": None, + "packets_per_port": None, + "peer_notif_delay": None, + "primary": None, + "primary_reselect": None, + "resend_igmp": None, + "tlb_dynamic_lb": None, + "updelay": None, + "use_carrier": None, + "xmit_hash_policy": None, + }, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, "ethtool": ETHTOOL_DEFAULTS, @@ -1788,7 +1815,34 @@ class TestValidator(Python26CompatTestCase): { "actions": ["present", "up"], "autoconnect": True, - "bond": {"mode": "active-backup", "miimon": None}, + "bond": { + "mode": "active-backup", + "ad_actor_sys_prio": None, + "ad_actor_system": None, + "ad_select": None, + "ad_user_port_key": None, + "all_ports_active": None, + "arp_all_targets": None, + "arp_interval": None, + "arp_ip_target": None, + "arp_validate": None, + "downdelay": None, + "fail_over_mac": None, + "lacp_rate": None, + "lp_interval": None, + "miimon": None, + "min_links": None, + "num_grat_arp": None, + "packets_per_port": None, + "peer_notif_delay": None, + "primary": None, + "primary_reselect": None, + "resend_igmp": None, + "tlb_dynamic_lb": None, + "updelay": None, + "use_carrier": None, + "xmit_hash_policy": None, + }, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, "ethtool": ETHTOOL_DEFAULTS, @@ -4312,6 +4366,188 @@ class TestValidatorRouteTable(Python26CompatTestCase): ) +class TestValidatorDictBond(Python26CompatTestCase): + def setUp(self): + self.validator = network_lsr.argument_validator.ArgValidator_ListConnections() + self.test_connections = [ + { + "name": "bond0", + "type": "bond", + "bond": { + "mode": "balance-rr", + }, + }, + ] + + def test_invalid_bond_option_ad(self): + + """ + Test the ad bond option restrictions + """ + self.test_connections[0]["bond"]["ad_actor_sys_prio"] = 65535 + self.assertRaisesRegex( + ValidationError, + "the bond option ad_actor_sys_prio is only valid with mode 802.3ad", + self.validator.validate, + self.test_connections, + ) + self.test_connections[0]["bond"]["mode"] = "802.3ad" + self.validator.validate(self.test_connections) + + def test_invalid_bond_option_packets_per_port(self): + """ + Test the packets_per_port bond option restrictions + """ + self.test_connections[0]["bond"]["mode"] = "802.3ad" + self.test_connections[0]["bond"]["packets_per_port"] = 2 + self.assertRaisesRegex( + ValidationError, + "the bond option packets_per_port is only valid with mode balance-rr", + self.validator.validate, + self.test_connections, + ) + self.test_connections[0]["bond"]["mode"] = "balance-rr" + self.validator.validate(self.test_connections) + + def test_invalid_bond_option_arp(self): + """ + Test the arp bond option restrictions + """ + self.test_connections[0]["bond"]["mode"] = "802.3ad" + self.test_connections[0]["bond"]["arp_interval"] = 2 + self.test_connections[0]["bond"]["arp_ip_target"] = "198.51.100.3" + + self.assertRaisesRegex( + ValidationError, + "the bond option arp_interval is only valid with mode balance-rr, active-backup, balance-xor or broadcast", + self.validator.validate, + self.test_connections, + ) + self.test_connections[0]["bond"]["mode"] = "balance-rr" + self.validator.validate(self.test_connections) + + def test_invalid_bond_option_tlb_dynamic_lb(self): + """ + Test the tlb_dynamic_lb bond option restrictions + """ + self.test_connections[0]["bond"]["tlb_dynamic_lb"] = True + self.assertRaisesRegex( + ValidationError, + "the bond option tlb_dynamic_lb is only valid with mode balance-tlb or balance-alb", + self.validator.validate, + self.test_connections, + ) + self.test_connections[0]["bond"]["mode"] = "balance-tlb" + self.validator.validate(self.test_connections) + + def test_invalid_bond_option_primary(self): + """ + Test the primary bond option restrictions + """ + self.test_connections[0]["bond"]["primary"] = "bond0.0" + self.assertRaisesRegex( + ValidationError, + "the bond option primary is only valid with mode active-backup, balance-tlb, balance-alb", + self.validator.validate, + self.test_connections, + ) + self.test_connections[0]["bond"]["mode"] = "balance-tlb" + self.validator.validate(self.test_connections) + + def test_invalid_bond_option_downdelay_updelay(self): + """ + Test the downdelay or updelay bond option restrictions + """ + self.test_connections[0]["bond"]["downdelay"] = 5 + self.assertRaisesRegex( + ValidationError, + "the bond option downdelay or updelay is only valid with miimon enabled", + self.validator.validate, + self.test_connections, + ) + self.test_connections[0]["bond"]["miimon"] = 110 + self.validator.validate(self.test_connections) + + def test_invalid_bond_option_peer_notif_delay(self): + """ + Test the peer_notif_delay bond option restrictions + """ + self.test_connections[0]["bond"]["miimon"] = 110 + self.test_connections[0]["bond"]["peer_notif_delay"] = 222 + self.assertRaisesRegex( + ValidationError, + "the bond option peer_notif_delay needs miimon enabled and must be miimon multiple", + self.validator.validate, + self.test_connections, + ) + self.test_connections[0]["bond"]["peer_notif_delay"] = 220 + self.test_connections[0]["bond"]["arp_interval"] = 110 + self.assertRaisesRegex( + ValidationError, + "the bond option peer_notif_delay needs arp_interval disabled", + self.validator.validate, + self.test_connections, + ) + self.test_connections[0]["bond"]["arp_interval"] = 0 + self.validator.validate(self.test_connections) + + def test_invalid_bond_option_peer_arp_ip_target_arp_interval(self): + """ + Test the arp_ip_target or arp_interval bond option restrictions + """ + self.test_connections[0]["bond"]["arp_interval"] = 4 + self.assertRaisesRegex( + ValidationError, + "the bond option arp_interval requires arp_ip_target to be set", + self.validator.validate, + self.test_connections, + ) + + self.test_connections[0]["bond"]["arp_ip_target"] = "198.51.100.3" + self.test_connections[0]["bond"]["arp_interval"] = 0 + self.assertRaisesRegex( + ValidationError, + "the bond option arp_ip_target requires arp_interval to be set", + self.validator.validate, + self.test_connections, + ) + self.test_connections[0]["bond"]["arp_interval"] = 4 + self.validator.validate(self.test_connections) + + def test_invalid_bond_option_infiniband_port(self): + """ + Test that bond only supports infiniband ports in active-backup mode + """ + test_connections_with_infiniband_port = [ + { + "name": "bond0", + "type": "bond", + "bond": { + "mode": "balance-rr", + }, + }, + { + "name": "bond0.0", + "type": "infiniband", + "controller": "bond0", + }, + { + "name": "bond0.1", + "type": "infiniband", + "controller": "bond0", + }, + ] + + self.assertRaisesRegex( + ValidationError, + "bond only supports infiniband ports in active-backup mode", + self.validator.validate, + test_connections_with_infiniband_port, + ) + self.test_connections[0]["bond"]["mode"] = "active-backup" + self.validator.validate(self.test_connections) + + class TestSysUtils(unittest.TestCase): def test_link_read_permaddress(self): self.assertEqual(SysUtil._link_read_permaddress("lo"), "00:00:00:00:00:00")