Merge pull request #75 from tyll/interface_name

Use profile name for interface name by default
This commit is contained in:
Till Maas 2018-08-01 09:41:28 +02:00 committed by GitHub
commit 22539bdc18
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 151 additions and 42 deletions

View file

@ -12,31 +12,35 @@ The role can be used to configure:
- Bonded interfaces
- VLAN interfaces
- MacVLAN interfaces
- Infiniband interfaces
- IP configuration
General
-------
The role supports two providers: `nm` and `initscripts`. The provider can be configured per host
via the [`provider`](#provider) variable. In absence of explicit configuration, it is autodetected based on
the distribution. The `nm` provider is used by default on RHEL7 and
`initscripts` on RHEL6. However, note that the provider is not tied to a certain distribution,
given that the required API is available. For `nm` this means that at least version 1.2 of NetworkManager's
API is available. For `initscripts`, it requires the legacy network service as commonly available
on Fedora/RHEL.
The role supports two providers: `nm` and `initscripts`. The provider can be
configured per host via the [`network_provider`](#provider) variable. In
absence of explicit configuration, it is autodetected based on the
distribution. The `nm` provider is used by default on RHEL7 and `initscripts`
on RHEL6. However, note that the provider is not tied to a certain
distribution, given that the required API is available. For `nm` this means
that at least version 1.2 of NetworkManager's API is available. For
`initscripts`, it requires the legacy network service as commonly available on
Fedora/RHEL.
For each host a list of networking profiles can be configure via the `network` variable.
For each host a list of networking profiles can be configure via the
`network_connections` variable.
- For initscripts, profiles correspond to ifcfg files in `/etc/sysconfig/network-scripts/ifcfg-*`.
- For NetworkManager, profiles correspond to connection profiles as handled by NetworkManager. Fedora and RHEL use the `rh-plugin` for NetworkManager which also writes configuration files to `/etc/sysconfig/network-scripts/ifcfg-*` for compatibility.
Note that the role primarily operates on networking profiles (connections) and
not on devices. For example, in the role you would not configure the current IP address
of an interface. Instead, you create a profile with a certain IP configuration and
optionally activate the profile on a device. Which means, to apply the configuration
to the actual networking interface.
not on devices but it defaults to use the profile name as the interface name.
But it is also possible to create generic profiles, by creating for example a
profile with a certain IP configuration without activating the profile. To
apply the configuration to the actual networking interface, a command like
`nmcli` needs to be used on the target system.
Variables
@ -141,9 +145,10 @@ By default, profiles are created with autoconnect enabled.
### `mac`
The `mac` address is optional and restricts the profile to be usable only
on devices with the given MAC address. `mac` only makes sense for `type` `ethernet`
to match a non-virtual device with the profile.
The `mac` address is optional and restricts the profile to be usable only on
devices with the given MAC address. `mac` is only allowed for `type`
`ethernet` or `type` `infiniband` to match a non-virtual device with the
profile.
- For NetworkManager `mac` is the permanent MAC address `ethernet.mac-address`.
@ -151,18 +156,22 @@ to match a non-virtual device with the profile.
### `interface_name`
For type `ethernet`, this option restricts the profile to the
given interface by name. This argument is optional and by default
a profile is not restricted to any interface by name.
Note that with [persistent interface naming](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Networking_Guide/ch-Consistent_Network_Device_Naming.html),
For type `ethernet` and `infiniband`, this option restricts the profile to the
given interface by name. This argument is optional and by default the profile
name is used unless a mac address is specified using the `mac` key. Specifying
an empty string (`""`) allows to specify that the profile is not restricted to
a network interface.
**Note:** With [persistent interface naming](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Networking_Guide/ch-Consistent_Network_Device_Naming.html),
the interface is predictable based on the hardware configuration.
Otherwise, the `mac` address might be an option.
For virtual interface types like bridges, this argument is the name of the created
interface. In case of a missing `interface_name`, the profile name `name` is used.
Note the destinction between the profile name `name` and the device
name `interface_name`, which may or may not be the same.
**Note:** The profile name `name` and the device name `interface_name` may be
different or the profile may not be tied to an interface at all.
### `zone`

View file

@ -1080,7 +1080,7 @@ class ArgValidator_DictConnection(ArgValidatorDict):
enum_values=ArgValidator_DictConnection.VALID_SLAVE_TYPES,
),
ArgValidatorStr("master"),
ArgValidatorStr("interface_name"),
ArgValidatorStr("interface_name", allow_empty=True),
ArgValidatorMac("mac"),
ArgValidatorNum(
"mtu", val_min=0, val_max=0xFFFFFFFF, default_value=None
@ -1291,21 +1291,36 @@ class ArgValidator_DictConnection(ArgValidatorDict):
)
if "interface_name" in result:
if not Util.ifname_valid(result["interface_name"]):
# Ignore empty interface_name
if result["interface_name"] == "":
del result["interface_name"]
elif not Util.ifname_valid(result["interface_name"]):
raise ValidationError(
name + ".interface_name",
"invalid 'interface_name' '%s'" % (result["interface_name"]),
)
else:
if result["type"] in ["bridge", "bond", "team", "vlan", "macvlan"]:
if not result.get("mac"):
if not Util.ifname_valid(result["name"]):
raise ValidationError(
name + ".interface_name",
'requires \'interface_name\' as "name" "%s" is not valid'
'\'interface_name\' as "name" "%s" is not valid'
% (result["name"]),
)
result["interface_name"] = result["name"]
if "interface_name" not in result and result["type"] in [
"bond",
"bridge",
"macvlan",
"team",
"vlan",
]:
raise ValidationError(
name + ".interface_name",
"type '%s' requires 'interface_name'" % (result["type"]),
)
if result["type"] == "vlan":
if "vlan" not in result:
if "vlan_id" not in result:

View file

@ -2,11 +2,12 @@
""" Tests for network_connections Ansible module """
# SPDX-License-Identifier: BSD-3-Clause
import sys
import os
import unittest
import socket
import itertools
import os
import pprint as pprint_
import socket
import sys
import unittest
TESTS_BASEDIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(1, os.path.join(TESTS_BASEDIR, "..", "library"))
@ -43,9 +44,8 @@ if nmutil:
def pprint(msg, obj):
print("PRINT: %s\n" % (msg))
import pprint
p = pprint.PrettyPrinter(indent=4)
p = pprint_.PrettyPrinter(indent=4)
p.pprint(obj)
if nmutil is not None and isinstance(obj, NM.Connection):
obj.dump()
@ -281,7 +281,7 @@ class TestValidator(unittest.TestCase):
"zone": None,
"master": None,
"ignore_errors": None,
"interface_name": None,
"interface_name": "5",
"check_iface_exists": True,
"slave_type": None,
},
@ -324,7 +324,7 @@ class TestValidator(unittest.TestCase):
"zone": None,
"master": None,
"ignore_errors": None,
"interface_name": None,
"interface_name": "5",
"check_iface_exists": True,
"force_state_change": None,
"slave_type": None,
@ -362,7 +362,7 @@ class TestValidator(unittest.TestCase):
"zone": None,
"master": None,
"ignore_errors": None,
"interface_name": None,
"interface_name": "5",
"check_iface_exists": True,
"force_state_change": None,
"slave_type": None,
@ -379,6 +379,7 @@ class TestValidator(unittest.TestCase):
"NM_CONTROLLED": "no",
"ONBOOT": "no",
"TYPE": "Ethernet",
"DEVICE": "5",
},
"keys": None,
"route": None,
@ -488,7 +489,7 @@ class TestValidator(unittest.TestCase):
"master": None,
"mtu": None,
"ignore_errors": None,
"interface_name": None,
"interface_name": "prod1",
"type": "ethernet",
"slave_type": None,
"wait": None,
@ -1185,7 +1186,7 @@ class TestValidator(unittest.TestCase):
"zone": None,
"master": None,
"ignore_errors": None,
"interface_name": None,
"interface_name": "5",
"check_iface_exists": True,
"force_state_change": None,
"slave_type": None,
@ -1224,7 +1225,7 @@ class TestValidator(unittest.TestCase):
"zone": None,
"master": None,
"ignore_errors": None,
"interface_name": None,
"interface_name": "5",
"check_iface_exists": True,
"force_state_change": None,
"slave_type": None,
@ -1250,6 +1251,7 @@ class TestValidator(unittest.TestCase):
"NM_CONTROLLED": "no",
"ONBOOT": "yes",
"TYPE": "Ethernet",
"DEVICE": "5",
},
"keys": None,
"route": None,
@ -1301,7 +1303,7 @@ class TestValidator(unittest.TestCase):
"ethernet": {"autoneg": None, "duplex": None, "speed": 0},
"force_state_change": None,
"ignore_errors": None,
"interface_name": None,
"interface_name": "6643",
"ip": {
"address": [],
"auto6": True,
@ -1376,7 +1378,14 @@ class TestValidator(unittest.TestCase):
"zone": None,
}
],
[{"name": "infiniband.1", "state": "up", "type": "infiniband"}],
[
{
"name": "infiniband.1",
"interface_name": "",
"state": "up",
"type": "infiniband",
}
],
initscripts_dict_expected=[
{
"ifcfg": {
@ -1512,7 +1521,7 @@ class TestValidator(unittest.TestCase):
"zone": None,
"master": None,
"ignore_errors": None,
"interface_name": None,
"interface_name": "555",
"check_iface_exists": True,
"force_state_change": None,
"slave_type": None,
@ -1543,6 +1552,7 @@ class TestValidator(unittest.TestCase):
"NM_CONTROLLED": "no",
"ONBOOT": "yes",
"TYPE": "Ethernet",
"DEVICE": "555",
},
"keys": None,
"route": "192.168.45.0/24 metric 545\n192.168.46.0/30\n",
@ -1604,7 +1614,7 @@ class TestValidator(unittest.TestCase):
"zone": "external",
"master": None,
"ignore_errors": None,
"interface_name": None,
"interface_name": "e556",
"check_iface_exists": True,
"force_state_change": None,
"slave_type": None,
@ -1666,6 +1676,7 @@ class TestValidator(unittest.TestCase):
"ONBOOT": "yes",
"TYPE": "Ethernet",
"ZONE": "external",
"DEVICE": "e556",
},
"keys": None,
"route": "192.168.40.0/24 metric 545\n192.168.46.0/30\n"
@ -1681,6 +1692,80 @@ class TestValidator(unittest.TestCase):
[{"name": "b", "type": "ethernet", "mac": "aa:b"}]
)
def test_interface_name_ethernet_default(self):
""" Use the profile name as interface_name for ethernet profiles """
cons_without_interface_name = [{"name": "eth0", "type": "ethernet"}]
connections = ARGS_CONNECTIONS.validate(cons_without_interface_name)
self.assertTrue(connections[0]["interface_name"] == "eth0")
def test_interface_name_ethernet_mac(self):
""" Do not set interface_name when mac is specified """
cons_without_interface_name = [
{"name": "eth0", "type": "ethernet", "mac": "3b:0b:88:16:6d:1a"}
]
connections = ARGS_CONNECTIONS.validate(cons_without_interface_name)
self.assertTrue(connections[0]["interface_name"] is None)
def test_interface_name_ethernet_empty(self):
""" Allow not to restrict the profile to an interface """
network_connections = [
{"name": "internal_network", "type": "ethernet", "interface_name": ""}
]
connections = ARGS_CONNECTIONS.validate(network_connections)
self.assertTrue(connections[0]["interface_name"] is None)
def test_interface_name_ethernet_None(self):
""" Check that inerface_name cannot be None """
network_connections = [
{"name": "internal_network", "type": "ethernet", "interface_name": None}
]
self.assertRaises(
n.ValidationError, ARGS_CONNECTIONS.validate, network_connections
)
def test_interface_name_ethernet_explicit(self):
""" Use the explicitly provided interface name """
network_connections = [
{"name": "internal", "type": "ethernet", "interface_name": "eth0"}
]
connections = ARGS_CONNECTIONS.validate(network_connections)
self.assertEquals(connections[0]["interface_name"], "eth0")
def test_interface_name_ethernet_invalid_profile(self):
""" Require explicit interface_name when the profile name is not a
valid interface_name """
network_connections = [{"name": "internal:main", "type": "ethernet"}]
self.assertRaises(
n.ValidationError, ARGS_CONNECTIONS.validate, network_connections
)
network_connections = [
{"name": "internal:main", "type": "ethernet", "interface_name": "eth0"}
]
connections = ARGS_CONNECTIONS.validate(network_connections)
self.assertTrue(connections[0]["interface_name"] == "eth0")
def test_interface_name_ethernet_invalid_interface_name(self):
network_connections = [
{"name": "internal", "type": "ethernet", "interface_name": "invalid:name"}
]
self.assertRaises(
n.ValidationError, ARGS_CONNECTIONS.validate, network_connections
)
def test_interface_name_bond_empty_interface_name(self):
network_connections = [
{"name": "internal", "type": "bond", "interface_name": "invalid:name"}
]
self.assertRaises(
n.ValidationError, ARGS_CONNECTIONS.validate, network_connections
)
def test_interface_name_bond_profile_as_interface_name(self):
network_connections = [{"name": "internal", "type": "bond"}]
connections = ARGS_CONNECTIONS.validate(network_connections)
self.assertEquals(connections[0]["interface_name"], "internal")
@my_test_skipIf(nmutil is None, "no support for NM (libnm via pygobject)")
class TestNM(unittest.TestCase):