diff --git a/.github/run_test.sh b/.github/run_test.sh index a04c4a8..7a99a37 100755 --- a/.github/run_test.sh +++ b/.github/run_test.sh @@ -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 diff --git a/.yamllint.yml b/.yamllint.yml index 55bd703..c7b4845 100644 --- a/.yamllint.yml +++ b/.yamllint.yml @@ -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 diff --git a/README.md b/README.md index a0ec968..a9f29fb 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/examples/cloned_mac.yml b/examples/cloned_mac.yml new file mode 100644 index 0000000..11bce64 --- /dev/null +++ b/examples/cloned_mac.yml @@ -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 diff --git a/library/network_connections.py b/library/network_connections.py index 31e6e90..ee9b752 100644 --- a/library/network_connections.py +++ b/library/network_connections.py @@ -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) diff --git a/module_utils/network_lsr/argument_validator.py b/module_utils/network_lsr/argument_validator.py index df84cac..fb22f93 100644 --- a/module_utils/network_lsr/argument_validator.py +++ b/module_utils/network_lsr/argument_validator.py @@ -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) diff --git a/tests/playbooks/tests_bond_cloned_mac.yml b/tests/playbooks/tests_bond_cloned_mac.yml new file mode 100644 index 0000000..ff5dbd0 --- /dev/null +++ b/tests/playbooks/tests_bond_cloned_mac.yml @@ -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" diff --git a/tests/playbooks/tests_bridge_cloned_mac.yml b/tests/playbooks/tests_bridge_cloned_mac.yml new file mode 100644 index 0000000..806d2e5 --- /dev/null +++ b/tests/playbooks/tests_bridge_cloned_mac.yml @@ -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 }}" diff --git a/tests/tests_bond_cloned_mac_initscripts.yml b/tests/tests_bond_cloned_mac_initscripts.yml new file mode 100644 index 0000000..e26593f --- /dev/null +++ b/tests/tests_bond_cloned_mac_initscripts.yml @@ -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) diff --git a/tests/tests_bond_cloned_mac_nm.yml b/tests/tests_bond_cloned_mac_nm.yml new file mode 100644 index 0000000..8ce6d81 --- /dev/null +++ b/tests/tests_bond_cloned_mac_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_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' diff --git a/tests/tests_bridge_cloned_mac_initscripts.yml b/tests/tests_bridge_cloned_mac_initscripts.yml new file mode 100644 index 0000000..3f69cd3 --- /dev/null +++ b/tests/tests_bridge_cloned_mac_initscripts.yml @@ -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) diff --git a/tests/tests_bridge_cloned_mac_nm.yml b/tests/tests_bridge_cloned_mac_nm.yml new file mode 100644 index 0000000..b570414 --- /dev/null +++ b/tests/tests_bridge_cloned_mac_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_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' diff --git a/tests/unit/test_network_connections.py b/tests/unit/test_network_connections.py index bef7914..95c9437 100644 --- a/tests/unit/test_network_connections.py +++ b/tests/unit/test_network_connections.py @@ -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": {