Merge "Add initial support for systemd-networkd link configuration"
This commit is contained in:
commit
da4bcb59b6
@ -41,3 +41,9 @@
|
|||||||
systemd_networkd_cleanup: true
|
systemd_networkd_cleanup: true
|
||||||
systemd_networkd_cleanup_patterns:
|
systemd_networkd_cleanup_patterns:
|
||||||
- "{{ networkd_prefix }}*"
|
- "{{ networkd_prefix }}*"
|
||||||
|
|
||||||
|
- name: Ensure udev is triggered on links changes
|
||||||
|
become: true
|
||||||
|
command: "udevadm trigger --verbose --subsystem-match=net --action=add"
|
||||||
|
changed_when: false
|
||||||
|
when: network_interfaces | networkd_links | length
|
||||||
|
@ -429,6 +429,34 @@ To configure a network called ``example`` with an Ethernet interface on
|
|||||||
|
|
||||||
example_interface: eth0
|
example_interface: eth0
|
||||||
|
|
||||||
|
Advanced: Configuring (Renaming) Ethernet Interfaces System Name
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
The name of the Ethernet interface may be explicitly configured by binding
|
||||||
|
known MAC address of the specific interface to its name by setting the
|
||||||
|
``macaddress`` attribute for a network.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Supported only on Ubuntu/Debian operating systems.
|
||||||
|
|
||||||
|
To configure a network called ``example`` with known MAC address
|
||||||
|
``aa:bb:cc:dd:ee:ff`` and rename it from a system name (might be ``eth0``,
|
||||||
|
``ens3``, or any other name) to the ``lan0`` (new name):
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
:caption: ``inventory/group_vars/<group>/network-interfaces``
|
||||||
|
|
||||||
|
example_interface: lan0
|
||||||
|
example_macaddress: "aa:bb:cc:dd:ee:ff"
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
The network interface must be down before changing its name. See
|
||||||
|
`issue <https://github.com/systemd/systemd/issues/26601>`__ in the systemd
|
||||||
|
project. So the configured node reboot might be required right after the
|
||||||
|
``seed host configure`` or ``overcloud host configure`` Kayobe commands.
|
||||||
|
|
||||||
.. _configuring-bridge-interfaces:
|
.. _configuring-bridge-interfaces:
|
||||||
|
|
||||||
Configuring Bridge Interfaces
|
Configuring Bridge Interfaces
|
||||||
|
@ -479,6 +479,35 @@ def _veth_peer_network(context, veth, inventory_hostname):
|
|||||||
return _filter_options(config)
|
return _filter_options(config)
|
||||||
|
|
||||||
|
|
||||||
|
def _ether_link(context, name, inventory_hostname):
|
||||||
|
"""Return a networkd link configuration for a ether.
|
||||||
|
|
||||||
|
:param context: a Jinja2 Context object.
|
||||||
|
:param name: name of the network.
|
||||||
|
:param inventory_hostname: Ansible inventory hostname.
|
||||||
|
"""
|
||||||
|
config = []
|
||||||
|
|
||||||
|
device = networks.net_interface(context, name, inventory_hostname)
|
||||||
|
macaddress = networks.net_macaddress(context, name, inventory_hostname)
|
||||||
|
|
||||||
|
if macaddress is not None:
|
||||||
|
config = [
|
||||||
|
{
|
||||||
|
'Match': [
|
||||||
|
{'PermanentMACAddress': macaddress},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Link': [
|
||||||
|
{'Name': device},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return _filter_options(config)
|
||||||
|
|
||||||
|
|
||||||
def _add_to_result(result, prefix, device, config):
|
def _add_to_result(result, prefix, device, config):
|
||||||
"""Add configuration for an interface to a filter result.
|
"""Add configuration for an interface to a filter result.
|
||||||
|
|
||||||
@ -561,8 +590,20 @@ def networkd_links(context, names, inventory_hostname=None):
|
|||||||
:param inventory_hostname: Ansible inventory hostname.
|
:param inventory_hostname: Ansible inventory hostname.
|
||||||
:returns: a dict representation of networkd link configuration.
|
:returns: a dict representation of networkd link configuration.
|
||||||
"""
|
"""
|
||||||
# NOTE(mgoddard): We do not currently support link configuration.
|
# Prefix for configuration file names.
|
||||||
return {}
|
prefix = utils.get_hostvar(context, "networkd_prefix", inventory_hostname)
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
# only ethers
|
||||||
|
for name in networks.net_select_ethers(context, names, inventory_hostname):
|
||||||
|
device = networks.get_and_validate_interface(context, name,
|
||||||
|
inventory_hostname)
|
||||||
|
ether_link = _ether_link(context, name, inventory_hostname)
|
||||||
|
if ether_link:
|
||||||
|
_add_to_result(result, prefix, device, ether_link)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@jinja2.pass_context
|
@jinja2.pass_context
|
||||||
|
@ -274,6 +274,11 @@ def net_mtu(context, name, inventory_hostname=None):
|
|||||||
return mtu
|
return mtu
|
||||||
|
|
||||||
|
|
||||||
|
@jinja2.pass_context
|
||||||
|
def net_macaddress(context, name, inventory_hostname=None):
|
||||||
|
return net_attr(context, name, 'macaddress', inventory_hostname)
|
||||||
|
|
||||||
|
|
||||||
@jinja2.pass_context
|
@jinja2.pass_context
|
||||||
def net_bridge_stp(context, name, inventory_hostname=None):
|
def net_bridge_stp(context, name, inventory_hostname=None):
|
||||||
"""Return the Spanning Tree Protocol (STP) state for a bridge.
|
"""Return the Spanning Tree Protocol (STP) state for a bridge.
|
||||||
@ -394,6 +399,7 @@ def net_interface_obj(context, name, inventory_hostname=None, names=None):
|
|||||||
netmask = None
|
netmask = None
|
||||||
vlan = net_vlan(context, name, inventory_hostname)
|
vlan = net_vlan(context, name, inventory_hostname)
|
||||||
mtu = net_mtu(context, name, inventory_hostname)
|
mtu = net_mtu(context, name, inventory_hostname)
|
||||||
|
macaddress = net_macaddress(context, name, inventory_hostname)
|
||||||
|
|
||||||
# NOTE(priteau): do not pass MTU for VLAN interfaces on bridges when it is
|
# NOTE(priteau): do not pass MTU for VLAN interfaces on bridges when it is
|
||||||
# identical to the parent bridge, to work around a NetworkManager bug.
|
# identical to the parent bridge, to work around a NetworkManager bug.
|
||||||
@ -433,6 +439,7 @@ def net_interface_obj(context, name, inventory_hostname=None, names=None):
|
|||||||
'gateway': gateway,
|
'gateway': gateway,
|
||||||
'vlan': vlan,
|
'vlan': vlan,
|
||||||
'mtu': mtu,
|
'mtu': mtu,
|
||||||
|
'macaddress': macaddress,
|
||||||
'route': routes,
|
'route': routes,
|
||||||
'rules': rules,
|
'rules': rules,
|
||||||
'bootproto': bootproto or 'static',
|
'bootproto': bootproto or 'static',
|
||||||
@ -789,6 +796,7 @@ def get_filters():
|
|||||||
'net_neutron_gateway': net_neutron_gateway,
|
'net_neutron_gateway': net_neutron_gateway,
|
||||||
'net_vlan': net_vlan,
|
'net_vlan': net_vlan,
|
||||||
'net_mtu': net_mtu,
|
'net_mtu': net_mtu,
|
||||||
|
'net_macaddress': net_macaddress,
|
||||||
'net_routes': net_routes,
|
'net_routes': net_routes,
|
||||||
'net_rules': net_rules,
|
'net_rules': net_rules,
|
||||||
'net_physical_network': net_physical_network,
|
'net_physical_network': net_physical_network,
|
||||||
|
@ -33,6 +33,7 @@ class BaseNetworkdTest(unittest.TestCase):
|
|||||||
"net1_interface": "eth0",
|
"net1_interface": "eth0",
|
||||||
"net1_cidr": "1.2.3.0/24",
|
"net1_cidr": "1.2.3.0/24",
|
||||||
"net1_ips": {"test-host": "1.2.3.4"},
|
"net1_ips": {"test-host": "1.2.3.4"},
|
||||||
|
"net1_macaddress": "aa:bb:cc:dd:ee:ff",
|
||||||
# net2: VLAN on eth0.2 with VLAN 2 on interface eth0.
|
# net2: VLAN on eth0.2 with VLAN 2 on interface eth0.
|
||||||
"net2_interface": "eth0.2",
|
"net2_interface": "eth0.2",
|
||||||
"net2_vlan": 2,
|
"net2_vlan": 2,
|
||||||
@ -351,9 +352,27 @@ class TestNetworkdNetDevs(BaseNetworkdTest):
|
|||||||
class TestNetworkdLinks(BaseNetworkdTest):
|
class TestNetworkdLinks(BaseNetworkdTest):
|
||||||
|
|
||||||
def test_empty(self):
|
def test_empty(self):
|
||||||
links = networkd.networkd_links(self.context, ['net1'])
|
links = networkd.networkd_links(self.context, ['net2'])
|
||||||
self.assertEqual({}, links)
|
self.assertEqual({}, links)
|
||||||
|
|
||||||
|
def test_link_name(self):
|
||||||
|
links = networkd.networkd_links(self.context, ['net1'])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-eth0": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"PermanentMACAddress": "aa:bb:cc:dd:ee:ff"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Link": [
|
||||||
|
{"Name": "eth0"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, links)
|
||||||
|
|
||||||
|
|
||||||
class TestNetworkdNetworks(BaseNetworkdTest):
|
class TestNetworkdNetworks(BaseNetworkdTest):
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
|
|
||||||
import distro
|
import distro
|
||||||
import pytest
|
import pytest
|
||||||
@ -54,17 +53,18 @@ def test_network_bridge(host):
|
|||||||
interface = host.interface('br0')
|
interface = host.interface('br0')
|
||||||
assert interface.exists
|
assert interface.exists
|
||||||
assert '192.168.36.1' in interface.addresses
|
assert '192.168.36.1' in interface.addresses
|
||||||
stp_status = host.file('/sys/class/net/br0/bridge/stp_state').content_string.strip()
|
state_file = "/sys/class/net/br0/bridge/stp_state"
|
||||||
|
stp_status = host.file(state_file).content_string.strip()
|
||||||
assert '0' == stp_status
|
assert '0' == stp_status
|
||||||
ports = ['dummy3', 'dummy4']
|
ports = ['dummy3', 'dummy4']
|
||||||
sys_ports = host.check_output('ls -1 /sys/class/net/br0/brif')
|
sys_ports = host.check_output('ls -1 /sys/class/net/br0/brif')
|
||||||
assert sys_ports == "\n".join(ports)
|
assert sys_ports == "\n".join(ports)
|
||||||
for port in ports:
|
for port in ports:
|
||||||
interface = host.interface(port)
|
interface = host.interface(port)
|
||||||
assert interface.exists
|
assert interface.exists
|
||||||
v4_addresses = [a for a in interface.addresses
|
v4_addresses = [a for a in interface.addresses
|
||||||
if ipaddress.ip_address(a).version == '4']
|
if ipaddress.ip_address(a).version == '4']
|
||||||
assert not v4_addresses
|
assert not v4_addresses
|
||||||
|
|
||||||
|
|
||||||
def test_network_bridge_vlan(host):
|
def test_network_bridge_vlan(host):
|
||||||
@ -84,9 +84,9 @@ def test_network_bond(host):
|
|||||||
slaves = set(['dummy5', 'dummy6'])
|
slaves = set(['dummy5', 'dummy6'])
|
||||||
assert sys_slaves == slaves
|
assert sys_slaves == slaves
|
||||||
for slave in slaves:
|
for slave in slaves:
|
||||||
interface = host.interface(slave)
|
interface = host.interface(slave)
|
||||||
assert interface.exists
|
assert interface.exists
|
||||||
assert not interface.addresses
|
assert not interface.addresses
|
||||||
|
|
||||||
|
|
||||||
def test_network_bond_vlan(host):
|
def test_network_bond_vlan(host):
|
||||||
@ -99,8 +99,9 @@ def test_network_bond_vlan(host):
|
|||||||
def test_network_bridge_no_ip(host):
|
def test_network_bridge_no_ip(host):
|
||||||
interface = host.interface('br1')
|
interface = host.interface('br1')
|
||||||
assert interface.exists
|
assert interface.exists
|
||||||
assert not '192.168.40.1' in interface.addresses
|
assert '192.168.40.1' not in interface.addresses
|
||||||
stp_status = host.file('/sys/class/net/br1/bridge/stp_state').content_string.strip()
|
state_file = "/sys/class/net/br1/bridge/stp_state"
|
||||||
|
stp_status = host.file(state_file).content_string.strip()
|
||||||
assert '1' == stp_status
|
assert '1' == stp_status
|
||||||
|
|
||||||
|
|
||||||
@ -113,13 +114,13 @@ def test_network_systemd_vlan(host):
|
|||||||
|
|
||||||
|
|
||||||
def test_additional_user_account(host):
|
def test_additional_user_account(host):
|
||||||
user = host.user("kayobe-test-user")
|
user = host.user("kayobe-test-user")
|
||||||
assert user.name == "kayobe-test-user"
|
assert user.name == "kayobe-test-user"
|
||||||
assert user.group == "kayobe-test-user"
|
assert user.group == "kayobe-test-user"
|
||||||
assert set(user.groups) == {"kayobe-test-user", "stack"}
|
assert set(user.groups) == {"kayobe-test-user", "stack"}
|
||||||
assert user.gecos == "Kayobe test user"
|
assert user.gecos == "Kayobe test user"
|
||||||
with host.sudo():
|
with host.sudo():
|
||||||
assert user.password == 'kayobe-test-user-password'
|
assert user.password == 'kayobe-test-user-password'
|
||||||
|
|
||||||
|
|
||||||
def test_software_RAID(host):
|
def test_software_RAID(host):
|
||||||
@ -238,7 +239,8 @@ def test_apt_auth(host):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('repo', ["appstream", "baseos", "extras", "epel"])
|
@pytest.mark.parametrize('repo', ["appstream", "baseos", "extras", "epel"])
|
||||||
@pytest.mark.skipif(not _is_dnf_mirror(), reason="DNF OpenDev mirror only for CentOS 8")
|
@pytest.mark.skipif(not _is_dnf_mirror(),
|
||||||
|
reason="DNF OpenDev mirror only for CentOS 8")
|
||||||
def test_dnf_local_package_mirrors(host, repo):
|
def test_dnf_local_package_mirrors(host, repo):
|
||||||
# Depends on SITE_MIRROR_FQDN environment variable.
|
# Depends on SITE_MIRROR_FQDN environment variable.
|
||||||
assert os.getenv('SITE_MIRROR_FQDN')
|
assert os.getenv('SITE_MIRROR_FQDN')
|
||||||
@ -265,7 +267,7 @@ def test_dnf_automatic(host):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not _is_dnf(),
|
@pytest.mark.skipif(not _is_dnf(),
|
||||||
reason="tuned profile setting only supported on CentOS/Rocky")
|
reason="tuned profiles only supported on CentOS/Rocky")
|
||||||
def test_tuned_profile_is_active(host):
|
def test_tuned_profile_is_active(host):
|
||||||
tuned_output = host.check_output("tuned-adm active")
|
tuned_output = host.check_output("tuned-adm active")
|
||||||
assert "throughput-performance" in tuned_output
|
assert "throughput-performance" in tuned_output
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added initial support for systemd-networkd link configuration, now you can
|
||||||
|
configure and rename the name of a network interface if you know the MAC
|
||||||
|
address of the interface.
|
Loading…
Reference in New Issue
Block a user