mirror of
https://github.com/linux-system-roles/network.git
synced 2026-01-23 02:15:17 +00:00
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:
parent
b569704c72
commit
9fd19afa25
9 changed files with 497 additions and 10 deletions
|
|
@ -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
|
||||
|
|
|
|||
75
README.md
75
README.md
|
|
@ -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
|
||||
|
|
|
|||
17
examples/match_path_support.yml
Normal file
17
examples/match_path_support.yml
Normal 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
|
||||
...
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
76
tests/playbooks/tests_eth_pci_address_match.yml
Normal file
76
tests/playbooks/tests_eth_pci_address_match.yml
Normal 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"
|
||||
38
tests/tests_eth_pci_address_match_nm.yml
Normal file
38
tests/tests_eth_pci_address_match_nm.yml
Normal 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', '>=')
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue