Allow configuring network connection via matching path

Users can easily configure or update network connection via matching
physical device path of the interface, which add certain flexibilty of
user experience.

Update connection profile via matching `path` setting:

```yaml
  - name: eth0
    type: ethernet
    autoconnect: yes
    # For PCI devices, the path has the form "pci-$domain:$bus:$device.$function"
    # It will only update the interface with the path "pci-0000:00:03.0"
    match:
      path:
        - pci-0000:00:03.0
```

Signed-off-by: Wen Liang <liangwen12year@gmail.com>
This commit is contained in:
Wen Liang 2021-03-07 11:43:17 -05:00 committed by Gris Ge
parent b569704c72
commit 9fd19afa25
9 changed files with 497 additions and 10 deletions

View file

@ -31,3 +31,5 @@ rules:
/tests/tests_bond_deprecated_initscripts.yml
/tests/tests_ethtool_features_initscripts.yml
/tests/tests_wireless_wpa3_sae_nm.yml
/tests/tests_eth_pci_address_match_nm.yml
/tests/playbooks/tests_eth_pci_address_match.yml

View file

@ -328,6 +328,51 @@ interface. In case of a missing `interface_name`, the `name` of the profile name
**Note:** The `name` (the profile name) and the `interface_name` (the device name) may be
different or the profile may not be tied to an interface at all.
### `match`
Settings to specify devices or systems matching a profile. Currently, only the `path`
setting is implemented.
The settings support a list of patterns which support the following modifiers and
wildcards:
**Special modifiers for `match` settings:**
- `|`, the element is an alternative, the match evaluates to be true if at least one of
the alternatives matches (logical OR). By default, an element is an alternative.
This means that an element `foo` behaves the same as `|foo`
- `&`, the element is mandatory, the match evaluates to be true if all the element
matches (logical AND)
- `!`, an element can also be inverted with exclamation mark (`!`) between the pipe
symbol (or the ampersand) and before the pattern. Note that `!foo` is a shortcut for
the mandatory match `&!foo`
- `\`, a backslash can be used at the beginning of the element (after the optional
special characters) to escape the start of the pattern. For example, `&\!a` is an
mandatory match for literally `!a`
**Wildcard patterns for `match` Settings:**
In general these work like shell globs.
- `*`, matches zero or more of any character
- `?`, matches any single character
- `[fo]` - matches any single `f` or `o` character - also supports ranges - `[0-9]`
will match any single digit character
#### `path`
The `path` setting is a list of patterns to match against the `ID_PATH` udev property
of devices. The `ID_PATH` udev property represents the persistent path of a device. It
consists of a subsystem string (pci, usb, platform, etc.) and a subsystem-specific
identifier. The `ID_PATH` of a device can be obtained with the command
`udevadm info /sys/class/net/$dev | grep ID_PATH=` or by looking at the `path` property
exported by NetworkManager (`nmcli -f general.path device show $dev`). The `path`
setting is optional and restricts the profile to be activated only on devices with a
matching `ID_PATH`. The `path` setting is only supported for ethernet or infiniband
profiles. It supports modifiers and wildcards as described for match settings.
### `zone`
The `zone` option sets the firewalld zone for the interface.
@ -686,6 +731,36 @@ network_connections:
dhcp4: yes
```
Specifying a connecting profile for an ethernet device with the `ID_PATH`:
```yaml
network_connections:
- name: eth0
type: ethernet
# For PCI devices, the path has the form "pci-$domain:$bus:$device.$function"
# The profile will only match the interface at the PCI address pci-0000:00:03.0
match:
path:
- pci-0000:00:03.0
ip:
address:
- 192.0.2.3/24
```
```yaml
- name: eth0
type: ethernet
# Specifying a connecting profile for an ethernet device only with the PCI address
# pci-0000:00:01.0 or pci-0000:00:03.0
match:
path:
- pci-0000:00:0[1-3].0
- &!pci-0000:00:02.0
ip:
address:
- 192.0.2.3/24
```
Deleting a connection profile named `eth0` (if it exists):
```yaml

View file

@ -0,0 +1,17 @@
# SPDX-License-Identifier: BSD-3-Clause
---
- hosts: all
vars:
network_connections:
- name: eth0
type: ethernet
match:
path:
- pci-0000:00:03.0
ip:
dhcp4: no
address:
- 192.0.2.3/24
roles:
- linux-system-roles.network
...

View file

@ -1184,7 +1184,9 @@ class NMUtil:
NM.SETTING_802_1X_DOMAIN_SUFFIX_MATCH,
connection["ieee802_1x"]["domain_suffix_match"],
)
if connection["match"]:
s_match = self.connection_ensure_setting(con, NM.SettingMatch)
s_match.set_property(NM.SETTING_MATCH_PATH, connection["match"]["path"])
try:
con.normalize()
except Exception as e:

View file

@ -400,25 +400,34 @@ class ArgValidatorDict(ArgValidator):
class ArgValidatorList(ArgValidator):
def __init__(self, name, nested, default_value=None):
def __init__(
self,
name,
nested,
default_value=None,
remove_none_or_empty=False,
):
ArgValidator.__init__(self, name, required=False, default_value=default_value)
self.nested = nested
self.remove_none_or_empty = remove_none_or_empty
def _validate_impl(self, value, name):
if isinstance(value, Util.STRING_TYPE):
# we expect a list. However, for convenience allow to
# specify a string, separated by space. Escaping is
# not supported. If you need that, define a proper list.
value = [s for s in value.split(" ") if s]
if value is None:
# Users might want to use jinja2 templates to set properties. As such,
# it's convenient to accept None as an alias for an empty list
# e.g. setting like `"match": {"path": None}` will be allowed by the role
return []
value = []
elif isinstance(value, Util.STRING_TYPE):
# we expect a list. However, for convenience allow to
# specify a string, separated by space. Escaping is
# not supported. If you need that, define a proper list.
value = [s for s in value.split(" ") if s]
result = []
for (idx, v) in enumerate(value):
if (v is None or v == "") and self.remove_none_or_empty:
continue
try:
vv = self.nested._validate(v, name + "[" + str(idx) + "]")
except ValidationError as e:
@ -1270,6 +1279,45 @@ class ArgValidator_DictWireless(ArgValidatorDict):
return result
class ArgValidatorListMatchPath(ArgValidatorList):
def __init__(self, name, nested, default_value, remove_none_or_empty):
ArgValidatorList.__init__(
self,
name,
nested,
default_value,
remove_none_or_empty,
)
def _validate_impl(self, value, name):
result = ArgValidatorList._validate_impl(self, value, name)
if result == ["|"] or result == ["&"]:
raise ValidationError(
name,
"value '%s' is not a valid 'match.path' setting, after "
"normalization, '%s' will only match the devices that have no PCI "
"path" % (value, result),
)
return result
class ArgValidator_DictMatch(ArgValidatorDict):
def __init__(self):
ArgValidatorDict.__init__(
self,
name="match",
nested=[
ArgValidatorListMatchPath(
"path",
nested=ArgValidatorStr("path[?]", allow_empty=True),
default_value=None,
remove_none_or_empty=True,
),
],
default_value={},
)
class ArgValidator_DictConnection(ArgValidatorDict):
VALID_PERSISTENT_STATES = ["absent", "present"]
@ -1340,6 +1388,7 @@ class ArgValidator_DictConnection(ArgValidatorDict):
ArgValidator_DictMacvlan(),
ArgValidator_Dict802_1X(),
ArgValidator_DictWireless(),
ArgValidator_DictMatch(),
# deprecated options:
ArgValidatorStr(
"infiniband_transport_mode",
@ -1563,6 +1612,15 @@ class ArgValidator_DictConnection(ArgValidatorDict):
"but is '%s'" % result["mac"],
)
if result.get("match"):
if "path" in result["match"]:
if result["type"] not in ["ethernet", "infiniband"]:
raise ValidationError(
name + ".match.path",
"'match.path' settings are only supported for type "
"'ethernet' or 'infiniband'",
)
if result["type"] == "infiniband":
if "infiniband" not in result:
result["infiniband"] = self.nested[
@ -1627,7 +1685,9 @@ class ArgValidator_DictConnection(ArgValidatorDict):
"invalid 'interface_name' '%s'" % (result["interface_name"]),
)
else:
if not result.get("mac"):
if not result.get("mac") and (
not result.get("match") or not result["match"].get("path")
):
if not Util.ifname_valid(result["name"]):
raise ValidationError(
name + ".interface_name",
@ -1921,3 +1981,18 @@ class ArgValidator_ListConnections(ArgValidatorList):
"Setting DNS options 'inet6', 'ip6-bytestring', 'ip6-dotint', "
"'no-ip6-dotint' is not allowed when IPv6 is disabled.",
)
if connection["match"]:
if connection["match"]["path"]:
if mode == self.VALIDATE_ONE_MODE_INITSCRIPTS:
raise ValidationError.from_connection(
idx,
"match.path is not supported by initscripts.",
)
else:
if not hasattr(Util.NM(), "SETTING_MATCH_PATH"):
raise ValidationError.from_connection(
idx,
"match.path is not supported by the running version of "
"NetworkManger.",
)

View file

@ -76,6 +76,10 @@ ibution_major_version | int < 9",
EXTRA_RUN_CONDITION: "ansible_distribution != 'RedHat' or\n ansible_distr\
ibution_major_version | int < 9",
},
"playbooks/tests_eth_pci_address_match.yml": {
MINIMUM_VERSION: "'1.26.0'",
"comment": "# NetworkManager 1.26.0 added support for match.path setting",
},
"playbooks/tests_reapply.yml": {},
# team interface is not supported on Fedora
"playbooks/tests_team.yml": {

View file

@ -0,0 +1,76 @@
# SPDX-License-Identifier: BSD-3-Clause
---
- hosts: all
tasks:
- import_role:
name: linux-system-roles.network
vars:
network_connections:
- name: pseudo-pci-test-only
persistent_state: present
type: ethernet
# `pci-0001:00:00.0` is a pseudo PCI address,
# which used only for testing purpose
match:
path:
- pci-0001:00:00.0
ip:
dhcp4: no
address:
- 192.0.2.3/24
- block:
- name: Get match.path setting for connection
command: nmcli -f match.path connection show pseudo-pci-test-only
register: match_path
ignore_errors: yes
changed_when: false
- name: Assert that the connection profile contains the specified PCI
address
assert:
that:
- "'pci-0001:00:00.0' in match_path.stdout"
msg: The connection profile does not contain the specified PCI
address
- name: "Remove the PCI address from match path"
import_role:
name: linux-system-roles.network
vars:
network_connections:
- name: pseudo-pci-test-only
type: ethernet
# When clearing the `match.path` setting through the role, the
# `interface_name` has to be defined, otherwise the `interface_name`
# will be the profile name. Since it is unclear if a specific
# interface exists for the test profile, by setting the
# `interface_name` to empty string, the role will accept any interface
# for the test profile.
interface_name: ""
state: down
match:
path:
- name: Get match.path setting for connection
command: nmcli -f match.path connection show pseudo-pci-test-only
register: clear_path
ignore_errors: yes
changed_when: false
- name: "Assert that the PCI address is removed"
assert:
that:
- "'pci-0001:00:00.0' not in clear_path.stdout"
msg: "The PCI address is not removed"
always:
- block:
- import_role:
name: linux-system-roles.network
vars:
network_connections:
- name: pseudo-pci-test-only
persistent_state: absent
state: down
ignore_errors: true
tags:
- "tests::cleanup"

View file

@ -0,0 +1,38 @@
# 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_eth_pci_address_match.yml' with nm as provider
tasks:
- name: Set network provider to 'nm'
set_fact:
network_provider: nm
tags:
- always
- block:
- name: Install NetworkManager
package:
name: NetworkManager
state: present
- name: Get NetworkManager version
command: rpm -q --qf "%{version}" NetworkManager
args:
warn: false
register: NetworkManager_version
when: true
when:
- ansible_distribution_major_version != '6'
tags:
- always
# The test requires or should run with NetworkManager, therefore it cannot run
# on RHEL/CentOS 6
# NetworkManager 1.26.0 added support for match.path setting
- import_playbook: playbooks/tests_eth_pci_address_match.yml
when:
- ansible_distribution_major_version != '6'
- NetworkManager_version.stdout is version('1.26.0', '>=')

View file

@ -185,6 +185,7 @@ class TestValidator(unittest.TestCase):
"dns_search": [],
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -448,6 +449,51 @@ class TestValidator(unittest.TestCase):
self.assertValidationError(v, [1, "s"])
self.assertEqual(v.validate(None), [])
def test_validate_allow_empty_string_in_list(self):
"""
Test that when ArgValidatorStr.allow_empty is True, empty string is allowed in
in ArgValidatorList
"""
v = network_lsr.argument_validator.ArgValidatorList(
"list",
nested=network_lsr.argument_validator.ArgValidatorStr(
"list[?]", allow_empty=True
),
)
self.assertEqual(v.validate(["pci-0001:00:00.0", ""]), ["pci-0001:00:00.0", ""])
def test_validate_disallow_none_in_list(self):
"""
Test that None is not allowed in ArgValidatorList
"""
v = network_lsr.argument_validator.ArgValidatorList(
"list",
nested=network_lsr.argument_validator.ArgValidatorStr(
"list[?]", allow_empty=True
),
)
self.assertRaisesRegexp(
ValidationError,
"must be a string but is 'None'",
v.validate,
["pci-0001:00:00.0", None],
)
def test_validate_list_remove_none_or_empty(self):
"""
Test that when ArgValidatorStr.remove_none_or_empty is True, None or empty
string will be removed from ArgValidatorList
"""
v = network_lsr.argument_validator.ArgValidatorList(
"list",
nested=network_lsr.argument_validator.ArgValidatorStr(
"list[?]", allow_empty=True
),
remove_none_or_empty=True,
)
self.assertEqual(v.validate(["pci-0001:00:00.0", ""]), ["pci-0001:00:00.0"])
self.assertEqual(v.validate(["pci-0001:00:00.0", None]), ["pci-0001:00:00.0"])
def test_empty(self):
self.maxDiff = None
self.do_connections_validate([], [])
@ -483,6 +529,7 @@ class TestValidator(unittest.TestCase):
"dns_search": [],
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -538,6 +585,7 @@ class TestValidator(unittest.TestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -587,6 +635,7 @@ class TestValidator(unittest.TestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -654,6 +703,7 @@ class TestValidator(unittest.TestCase):
"force_state_change": None,
"ignore_errors": None,
"interface_name": None,
"match": {},
"ip": {
"dhcp4": False,
"route_metric6": None,
@ -745,6 +795,7 @@ class TestValidator(unittest.TestCase):
"route": [],
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -783,6 +834,7 @@ class TestValidator(unittest.TestCase):
"force_state_change": None,
"ignore_errors": None,
"interface_name": "prod1",
"match": {},
"ip": {
"gateway6": "2001:db8::1",
"gateway4": None,
@ -885,6 +937,7 @@ class TestValidator(unittest.TestCase):
"force_state_change": None,
"ignore_errors": None,
"interface_name": None,
"match": {},
"ip": {
"dhcp4": False,
"auto6": True,
@ -937,6 +990,7 @@ class TestValidator(unittest.TestCase):
"force_state_change": None,
"ignore_errors": None,
"interface_name": "prod.100",
"match": {},
"ip": {
"dhcp4": False,
"route_metric6": None,
@ -1051,6 +1105,7 @@ class TestValidator(unittest.TestCase):
"route": [],
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -1126,6 +1181,7 @@ class TestValidator(unittest.TestCase):
"route": [],
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -1200,6 +1256,7 @@ class TestValidator(unittest.TestCase):
"force_state_change": None,
"ignore_errors": None,
"interface_name": None,
"match": {},
"ip": {
"dhcp4": False,
"auto6": True,
@ -1252,6 +1309,7 @@ class TestValidator(unittest.TestCase):
"force_state_change": None,
"ignore_errors": None,
"interface_name": "prod.100",
"match": {},
"ip": {
"dhcp4": False,
"route_metric6": None,
@ -1347,6 +1405,7 @@ class TestValidator(unittest.TestCase):
"force_state_change": None,
"ignore_errors": None,
"interface_name": "eth0",
"match": {},
"ip": {
"dhcp4": False,
"auto6": False,
@ -1393,6 +1452,7 @@ class TestValidator(unittest.TestCase):
"force_state_change": None,
"ignore_errors": None,
"interface_name": "veth0",
"match": {},
"ip": {
"dhcp4": False,
"route_metric6": None,
@ -1448,6 +1508,7 @@ class TestValidator(unittest.TestCase):
"force_state_change": None,
"ignore_errors": None,
"interface_name": "veth1",
"match": {},
"ip": {
"dhcp4": False,
"route_metric6": None,
@ -1572,6 +1633,7 @@ class TestValidator(unittest.TestCase):
"rule_append_only": False,
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -1613,6 +1675,7 @@ class TestValidator(unittest.TestCase):
"rule_append_only": False,
},
"mac": None,
"match": {},
"controller": "prod2",
"ieee802_1x": None,
"wireless": None,
@ -1678,6 +1741,7 @@ class TestValidator(unittest.TestCase):
"route": [],
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -1728,6 +1792,7 @@ class TestValidator(unittest.TestCase):
"route": [],
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -1788,6 +1853,7 @@ class TestValidator(unittest.TestCase):
"dns_search": [],
},
"mac": "aa:bb:cc:dd:ee:ff",
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -1836,6 +1902,7 @@ class TestValidator(unittest.TestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -1912,6 +1979,7 @@ class TestValidator(unittest.TestCase):
"rule_append_only": False,
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -1953,6 +2021,7 @@ class TestValidator(unittest.TestCase):
"rule_append_only": False,
},
"mac": None,
"match": {},
"controller": "6643-controller",
"ieee802_1x": None,
"wireless": None,
@ -2010,6 +2079,7 @@ class TestValidator(unittest.TestCase):
"rule_append_only": False,
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -2085,6 +2155,7 @@ class TestValidator(unittest.TestCase):
},
"mac": "11:22:33:44:55:66:77:88:99:00:"
"11:22:33:44:55:66:77:88:99:00",
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -2180,6 +2251,7 @@ class TestValidator(unittest.TestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -2283,6 +2355,7 @@ class TestValidator(unittest.TestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": None,
@ -2399,6 +2472,7 @@ class TestValidator(unittest.TestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": {
"identity": "myhost",
@ -2477,6 +2551,7 @@ class TestValidator(unittest.TestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": {
"identity": "myhost",
@ -2555,6 +2630,7 @@ class TestValidator(unittest.TestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": {
"identity": "myhost",
@ -2631,6 +2707,7 @@ class TestValidator(unittest.TestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": None,
"wireless": {
@ -2697,6 +2774,7 @@ class TestValidator(unittest.TestCase):
"dhcp4_send_hostname": None,
},
"mac": None,
"match": {},
"controller": None,
"ieee802_1x": {
"identity": "myhost",
@ -3761,6 +3839,126 @@ class TestNM(unittest.TestCase):
self.assertEqual(result.get_data(), b"file:///my/test/path\x00")
class TestValidatorMatch(unittest.TestCase):
def setUp(self):
self.test_profile = {
"name": "test",
"type": "ethernet",
"ip": {
"dhcp4": False,
"address": "192.168.245.7/24",
},
"match": None,
}
self.validator = network_lsr.argument_validator.ArgValidator_DictConnection()
def test_match_path_empty_list(self):
"""
Test that 'match.path' setting can be defined as None, [], [""], [None] or ""
and such a setting will be normalized into []
"""
for disabled_path in [None, [], [""], [None], ""]:
self.test_profile["match"] = {"path": disabled_path}
result = self.validator.validate(self.test_profile)
self.assertEqual(result["match"], {"path": []})
def test_match_path_setting_normalization(self):
"""
Test that 'match.path' setting ["", "usb123", None] will be normalized into
["usb123"]
"""
self.test_profile["match"] = {"path": ["", "usb123", None]}
result = self.validator.validate(self.test_profile)
self.assertEqual(result["match"], {"path": ["usb123"]})
def test_match_path_valid_setting(self):
"""
Test that values like ["pci-0000:00:03.0"] and ["&!pci-0000:00:0[1-3].0"] are
valid values for 'match.path' setting
"""
self.test_profile["match"] = {"path": ["pci-0000:00:03.0"]}
result1 = self.validator.validate(self.test_profile)
self.assertEqual(result1["match"], {"path": ["pci-0000:00:03.0"]})
self.test_profile["match"] = {"path": ["&!pci-0000:00:0[1-3].0"]}
result2 = self.validator.validate(self.test_profile)
self.assertEqual(result2["match"], {"path": ["&!pci-0000:00:0[1-3].0"]})
def test_match_path_invalid_setting(self):
"""
Test that values like ["&"] and ["|"] are invalid values for 'match.path'
setting
"""
self.test_profile["match"] = {"path": ["&", ""]}
self.assertRaisesRegexp(
ValidationError,
"['&'] will only match the devices that have no PCI path",
self.validator.validate,
self.test_profile,
)
self.test_profile["match"] = {"path": ["|", None]}
self.assertRaisesRegexp(
ValidationError,
"['|'] will only match the devices that have no PCI path",
self.validator.validate,
self.test_profile,
)
def test_match_path_invalid_connection_type(self):
"""
Test that when 'match.path' setting is correctly defined but the connection
type is neither ethernet nor infiniband, a ValidationError raises.
"""
self.test_profile["match"] = {"path": ["pci-0000:00:03.0"]}
result = self.validator.validate(self.test_profile)
self.assertEqual(result["match"], {"path": ["pci-0000:00:03.0"]})
self.test_profile["type"] = "dummy"
self.assertRaisesRegexp(
ValidationError,
"'match.path' settings are only supported for type 'ethernet' or "
"'infiniband'",
self.validator.validate,
self.test_profile,
)
def test_interface_name_when_match_not_specified(self):
"""
Test that when 'match' setting is not specified, interface name should be
profile name.
"""
result = self.validator.validate(self.test_profile)
self.assertEqual(result["interface_name"], "test")
def test_interface_name_and_match_when_match_is_None(self):
"""
Test that when 'match' setting is None, interface name should be profile name
and 'match' setting will be normalized into {}.
"""
self.test_profile["match"] = None
result = self.validator.validate(self.test_profile)
self.assertEqual(result["match"], {})
self.assertEqual(result["interface_name"], "test")
def test_interface_name_when_match_path_is_empty_list(self):
"""
Test that when 'match.path' setting is empty list, interface name should be
profile name.
"""
self.test_profile["match"] = {"path": []}
result = self.validator.validate(self.test_profile)
self.assertEqual(result["interface_name"], "test")
def test_interface_name_when_match_path_is_valid(self):
"""
Test that when 'match.path' setting contains interface path, interface name
should be unset.
"""
self.test_profile["match"] = {"path": ["pci-0000:00:03.0"]}
result = self.validator.validate(self.test_profile)
self.assertEqual(result["interface_name"], None)
class TestUtils(unittest.TestCase):
def test_mac_ntoa(self):
mac_bytes = b"\xaa\xbb\xcc\xdd\xee\xff"