Support cloned MAC address

The users want to create the bonding interface with the MAC address
specified by themselves or specify the strategy (e.g. random, preserve)
to get the default MAC for the bonding interface. Thus, add support for
the cloned MAC address.

Signed-off-by: Sylvain <35niavlys@gmail.com>
This commit is contained in:
Sylvain 2022-08-23 15:32:27 +02:00 committed by Richard Megginson
parent 385c27420e
commit 79d1fa2739
13 changed files with 395 additions and 3 deletions

2
.github/run_test.sh vendored
View file

@ -16,6 +16,7 @@ EXCLUDE_TESTS_C7='
-e tests/tests_auto_gateway_initscripts.yml
-e tests/tests_bond_deprecated_initscripts.yml
-e tests/tests_bond_initscripts.yml
-e tests/tests_bond_cloned_mac_initscripts.yml
-e tests/tests_bond_removal_initscripts.yml
-e tests/tests_infiniband_nm.yml
-e tests/tests_team_nm.yml
@ -31,6 +32,7 @@ EXCLUDE_TESTS_C8S='
-e tests/tests_auto_gateway_initscripts.yml
-e tests/tests_bond_deprecated_initscripts.yml
-e tests/tests_bond_initscripts.yml
-e tests/tests_bond_cloned_mac_initscripts.yml
-e tests/tests_bond_removal_initscripts.yml
-e tests/tests_infiniband_nm.yml
-e tests/tests_integration_pytest.yml

View file

@ -38,3 +38,6 @@ rules:
/tests/playbooks/tests_eth_pci_address_match.yml
/tests/tasks/setup_802_1x_server.yml
/tests/tests_bond_removal_initscripts.yml
/tests/tests_bond_cloned_mac_initscripts.yml
/tests/tests_bridge_cloned_mac_initscripts.yml
/tests/tests_bridge_cloned_mac_nm.yml

View file

@ -364,6 +364,19 @@ with double quotes and sometimes it is necessary.
- For `initscripts`, `mac` is the currently configured MAC address of the device (`HWADDR`).
### `cloned_mac`
The `cloned_mac` address is optional and allow to specify the strategy to get the default
mac or to set your own mac. The value of the `cloned_mac` address needs to be specified in
hexadecimal notation like `mac` property. Besides explicitly specifying the value as a MAC
address with hexadecimal notation, the following special values are also supported:
- `default`: honor the default behavior in NetworkManager
- `permanent`: use the permanent MAC address of the device
- `preserve`: don't change the MAC address of the device upon activation
- `random`: generate a randomized value upon each connect
- `stable`: generate a stable, hashed MAC address
### `mtu`
The `mtu` option denotes the maximum transmission unit for the profile's

30
examples/cloned_mac.yml Normal file
View file

@ -0,0 +1,30 @@
# 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
# define cloned_mac for the bond interface
cloned_mac: "12:23:34:45:56:60"
# add an ethernet profile to the bond
- name: member1
state: up
type: ethernet
interface_name: eth1
controller: bond0
# define cloned_mac for eth1 interface
cloned_mac: "21:23:34:45:56:60"
# add a second ethernet profile to the bond without a cloned mac
- name: member2
state: up
type: ethernet
interface_name: eth2
controller: bond0
roles:
- linux-system-roles.network

View file

@ -334,6 +334,8 @@ class IfcfgUtil:
ifcfg["ONBOOT"] = "no"
ifcfg["DEVICE"] = connection["interface_name"]
if connection["cloned_mac"] != "default":
ifcfg["MACADDR"] = connection["cloned_mac"]
if connection["type"] == "ethernet":
ifcfg["TYPE"] = "Ethernet"
@ -960,6 +962,18 @@ class NMUtil:
else:
raise MyError("unsupported type %s" % (connection["type"]))
if connection["cloned_mac"] != "default":
if connection["type"] == "wireless":
s_wireless = self.connection_ensure_setting(con, NM.SettingWireless)
s_wireless.set_property(
NM.SETTING_WIRELESS_CLONED_MAC_ADDRESS, connection["cloned_mac"]
)
else:
s_wired = self.connection_ensure_setting(con, NM.SettingWired)
s_wired.set_property(
NM.SETTING_WIRED_CLONED_MAC_ADDRESS, connection["cloned_mac"]
)
if "ethernet" in connection:
if connection["ethernet"]["autoneg"] is not None:
s_wired = self.connection_ensure_setting(con, NM.SettingWired)

View file

@ -578,21 +578,34 @@ class ArgValidatorIP(ArgValidatorStr):
class ArgValidatorMac(ArgValidatorStr):
def __init__(self, name, force_len=None, required=False, default_value=None):
def __init__(
self, name, force_len=None, required=False, default_value=None, enum_values=None
):
ArgValidatorStr.__init__(self, name, required, default_value, None)
self.force_len = force_len
self.enum_values_mac = enum_values
def _validate_impl(self, value, name):
v = ArgValidatorStr._validate_impl(self, value, name)
if self.enum_values_mac is not None and value in self.enum_values_mac:
return v
try:
addr = Util.mac_aton(v, self.force_len)
except MyError:
enum_ex = ""
if self.enum_values_mac is not None:
enum_ex = " nor one of %s" % (self.enum_values_mac)
raise ValidationError(
name, "value '%s' is not a valid MAC address" % (value)
name, "value '%s' is not a valid MAC address%s" % (value, enum_ex)
)
if not addr:
enum_ex = ""
if self.enum_values_mac is not None:
enum_ex = " nor one of %s" % (self.enum_values_mac)
raise ValidationError(
name, "value '%s' is not a valid MAC address" % (value)
name, "value '%s' is not a valid MAC address%s" % (value, enum_ex)
)
return Util.mac_ntoa(addr)
@ -1822,6 +1835,17 @@ class ArgValidator_DictConnection(ArgValidatorDict):
ArgValidatorDeprecated("master", deprecated_by="controller"),
ArgValidatorStr("interface_name", allow_empty=True),
ArgValidatorMac("mac"),
ArgValidatorMac(
"cloned_mac",
enum_values=[
"default",
"preserve",
"permanent",
"random",
"stable",
],
default_value="default",
),
ArgValidatorNum(
"mtu", val_min=0, val_max=0xFFFFFFFF, default_value=None
),
@ -2577,6 +2601,17 @@ class ArgValidator_ListConnections(ArgValidatorList):
"NetworkManger until NM 1.34",
)
if mode == self.VALIDATE_ONE_MODE_INITSCRIPTS and connection["cloned_mac"] in [
"preserve",
"permanent",
"random",
"stable",
]:
raise ValidationError.from_connection(
idx,
"Non-MAC argument is not supported by initscripts.",
)
self.validate_route_tables(connection, idx)

View file

@ -0,0 +1,128 @@
# 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
- name: Backup the /etc/resolv.conf for initscript
command: cp -vf /etc/resolv.conf /etc/resolv.conf.bak
when:
- network_provider == "initscripts"
- block:
- name: TEST Add Bond with 2 ports
debug:
msg: "##################################################"
- name: Import network role
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
miimon: 110
ip:
route_metric4: 65535
cloned_mac: "12:23:34:45:56:60"
# add an ethernet to the bond
- name: "{{ port1_profile }}"
state: up
type: ethernet
interface_name: "{{ dhcp_interface1 }}"
controller: "{{ controller_profile }}"
cloned_mac: "12:23:34:45:56:61"
# add a second ethernet to the bond
- name: "{{ port2_profile }}"
state: up
type: ethernet
interface_name: "{{ dhcp_interface2 }}"
controller: "{{ controller_profile }}"
- name: Verify nmcli cloned-mac-address entry
command: >-
nmcli -f 802-3-ethernet.cloned-mac-address con show {{ item.name }}
register: cloned_mac_address
ignore_errors: yes
changed_when: false
loop:
- { name: "{{ controller_profile }}", mac: "12:23:34:45:56:60"}
- { name: "{{ port1_profile }}", mac: "12:23:34:45:56:61"}
- { name: "{{ port2_profile }}", mac: "--"}
when: network_provider == 'nm'
- name: >
Assert that cloned-mac-address addresses are configured correctly
assert:
that:
- item.stdout.endswith(item.item.mac)
msg: cloned-mac-address is configured incorrectly
loop: "{{ cloned_mac_address.results }}"
when: network_provider == 'nm'
- name: Verify the MAC address in {{ controller_profile }}
command: >-
grep 'MACADDR'
/etc/sysconfig/network-scripts/ifcfg-{{ controller_profile }}
register: mac_address_controller
ignore_errors: yes
changed_when: false
when: network_provider == 'initscripts'
- name: Verify the MAC address in {{ port1_profile }}
command: >-
grep 'MACADDR'
/etc/sysconfig/network-scripts/ifcfg-{{ port1_profile }}
register: mac_address_port1
ignore_errors: yes
changed_when: false
when: network_provider == 'initscripts'
- name: Assert that MAC addresses are configured correctly for bonding
interface
assert:
that:
- mac_address_controller.stdout is search("12:23:34:45:56:60")
- mac_address_port1.stdout is search("12:23:34:45:56:61")
msg: the MAC addresses are configured incorrectly for bonding
interface
when: network_provider == 'initscripts'
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
- name: Restore the /etc/resolv.conf for initscript
command: mv -vf /etc/resolv.conf.bak /etc/resolv.conf
when:
- network_provider == "initscripts"
tags:
- "tests::cleanup"

View file

@ -0,0 +1,60 @@
# SPDX-License-Identifier: BSD-3-Clause
---
- name: Test configuring bridges
hosts: all
tasks:
- name: define vars
set_fact:
interface: "LSR-TST-br31"
cloned_mac: "12:23:34:45:56:70"
- name: Add test bridge
hosts: all
vars:
network_connections:
- name: "{{ interface }}"
interface_name: "{{ interface }}"
state: up
type: bridge
ip:
dhcp4: no
auto6: yes
cloned_mac: "{{ cloned_mac }}"
roles:
- linux-system-roles.network
tasks:
- name: Verify ifcfg MACADDR entry
command: >-
grep ^MACADDR= /etc/sysconfig/network-scripts/ifcfg-{{ interface }}
register: cloned_mac_address_init
changed_when: false
when:
- network_provider == "initscripts"
- name: Verify nmcli cloned-mac-address entry
command: >-
nmcli -f 802-3-ethernet.cloned-mac-address con show {{ interface }}
register: cloned_mac_address_nm
ignore_errors: yes
changed_when: false
when:
- network_provider == "nm"
- name: Assert that cloned-mac-address addresses are configured correctly
assert:
that:
- >-
cloned_mac_address_init.stdout is not defined or
cloned_mac_address_init.stdout.find(cloned_mac) != -1
- >-
cloned_mac_address_nm.stdout is not defined or
cloned_mac_address_nm.stdout.find(cloned_mac) != -1
msg: "cloned-mac-address is configured incorrectly"
- import_playbook: down_profile+delete_interface.yml
vars:
profile: "{{ interface }}"
- import_playbook: remove_profile.yml
vars:
profile: "{{ interface }}"

View file

@ -0,0 +1,16 @@
# SPDX-License-Identifier: BSD-3-Clause
# This file was generated by ensure_provider_tests.py
---
- hosts: all
name: Run playbook 'playbooks/tests_bond_cloned_mac.yml' with initscripts as provider
tasks:
- include_tasks: tasks/el_repo_setup.yml
- name: Set network provider to 'initscripts'
set_fact:
network_provider: initscripts
tags:
- always
- import_playbook: playbooks/tests_bond_cloned_mac.yml
when: (ansible_distribution in ['CentOS','RedHat'] and
ansible_distribution_major_version | int < 9)

View file

@ -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_cloned_mac.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_cloned_mac.yml
when:
- ansible_distribution_major_version != '6'

View file

@ -0,0 +1,16 @@
# SPDX-License-Identifier: BSD-3-Clause
# This file was generated by ensure_provider_tests.py
---
- hosts: all
name: Run playbook 'playbooks/tests_bridge_cloned_mac.yml' with initscripts as provider
tasks:
- include_tasks: tasks/el_repo_setup.yml
- name: Set network provider to 'initscripts'
set_fact:
network_provider: initscripts
tags:
- always
- import_playbook: playbooks/tests_bridge_cloned_mac.yml
when: (ansible_distribution in ['CentOS','RedHat'] and
ansible_distribution_major_version | int < 9)

View file

@ -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_bridge_cloned_mac.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_bridge_cloned_mac.yml
when:
- ansible_distribution_major_version != '6'

View file

@ -202,6 +202,7 @@ class TestValidator(Python26CompatTestCase):
"dns_search": [],
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -592,6 +593,7 @@ class TestValidator(Python26CompatTestCase):
"dns_search": [],
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -650,6 +652,7 @@ class TestValidator(Python26CompatTestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -702,6 +705,7 @@ class TestValidator(Python26CompatTestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -798,6 +802,7 @@ class TestValidator(Python26CompatTestCase):
"routing_rule": [],
},
"mac": "52:54:00:44:9f:ba",
"cloned_mac": "default",
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -866,6 +871,7 @@ class TestValidator(Python26CompatTestCase):
"routing_rule": [],
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -943,6 +949,7 @@ class TestValidator(Python26CompatTestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"cloned_mac": "default",
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -1043,6 +1050,7 @@ class TestValidator(Python26CompatTestCase):
"dns": [],
},
"mac": "52:54:00:44:9f:ba",
"cloned_mac": "default",
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -1107,6 +1115,7 @@ class TestValidator(Python26CompatTestCase):
"routing_rule": [],
},
"mac": None,
"cloned_mac": "default",
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -1185,6 +1194,7 @@ class TestValidator(Python26CompatTestCase):
"routing_rule": [],
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -1263,6 +1273,7 @@ class TestValidator(Python26CompatTestCase):
"routing_rule": [],
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -1371,6 +1382,7 @@ class TestValidator(Python26CompatTestCase):
"dns": [],
},
"mac": "52:54:00:44:9f:ba",
"cloned_mac": "default",
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -1435,6 +1447,7 @@ class TestValidator(Python26CompatTestCase):
"routing_rule": [],
},
"mac": None,
"cloned_mac": "default",
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -1520,6 +1533,7 @@ class TestValidator(Python26CompatTestCase):
"dns": [],
},
"mac": "33:24:10:24:2f:b9",
"cloned_mac": "default",
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -1578,6 +1592,7 @@ class TestValidator(Python26CompatTestCase):
"routing_rule": [],
},
"mac": None,
"cloned_mac": "default",
"macvlan": {"mode": "bridge", "promiscuous": True, "tap": False},
"controller": None,
"ieee802_1x": None,
@ -1637,6 +1652,7 @@ class TestValidator(Python26CompatTestCase):
"routing_rule": [],
},
"mac": None,
"cloned_mac": "default",
"macvlan": {"mode": "passthru", "promiscuous": False, "tap": True},
"controller": None,
"ieee802_1x": None,
@ -1730,6 +1746,7 @@ class TestValidator(Python26CompatTestCase):
"rule_append_only": False,
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -1774,6 +1791,7 @@ class TestValidator(Python26CompatTestCase):
"rule_append_only": False,
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": "prod2",
"ieee802_1x": None,
@ -1869,6 +1887,7 @@ class TestValidator(Python26CompatTestCase):
"routing_rule": [],
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -1949,6 +1968,7 @@ class TestValidator(Python26CompatTestCase):
"routing_rule": [],
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -2012,6 +2032,7 @@ class TestValidator(Python26CompatTestCase):
"dns_search": [],
},
"mac": "aa:bb:cc:dd:ee:ff",
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -2063,6 +2084,7 @@ class TestValidator(Python26CompatTestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -2142,6 +2164,7 @@ class TestValidator(Python26CompatTestCase):
"rule_append_only": False,
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -2186,6 +2209,7 @@ class TestValidator(Python26CompatTestCase):
"rule_append_only": False,
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": "6643-controller",
"ieee802_1x": None,
@ -2246,6 +2270,7 @@ class TestValidator(Python26CompatTestCase):
"rule_append_only": False,
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -2324,6 +2349,7 @@ class TestValidator(Python26CompatTestCase):
},
"mac": "11:22:33:44:55:66:77:88:99:00:"
"11:22:33:44:55:66:77:88:99:00",
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -2406,6 +2432,7 @@ class TestValidator(Python26CompatTestCase):
"rule_append_only": False,
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -2450,6 +2477,7 @@ class TestValidator(Python26CompatTestCase):
},
"mac": "11:22:33:44:55:66:77:88:99:00:"
"11:22:33:44:55:66:77:88:99:00",
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -2574,6 +2602,7 @@ class TestValidator(Python26CompatTestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -2683,6 +2712,7 @@ class TestValidator(Python26CompatTestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -2802,6 +2832,7 @@ class TestValidator(Python26CompatTestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": {
@ -2883,6 +2914,7 @@ class TestValidator(Python26CompatTestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": {
@ -2964,6 +2996,7 @@ class TestValidator(Python26CompatTestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": {
@ -3043,6 +3076,7 @@ class TestValidator(Python26CompatTestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": None,
@ -3112,6 +3146,7 @@ class TestValidator(Python26CompatTestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"cloned_mac": "default",
"match": {},
"controller": None,
"ieee802_1x": {