Add support for CentOS 10 keyfiles

Co-Authored-By: Michal Nasiadka <mnasiadka@gmail.com>
Change-Id: Id7af6127593f411dbc30e010188e07576181a127
This commit is contained in:
karolinku
2025-02-25 11:56:02 +01:00
committed by Michal Nasiadka
parent 2ba5c62d79
commit 9bc9428772
16 changed files with 590 additions and 29 deletions

View File

@@ -22,6 +22,7 @@ import errno
import json
import logging
import os
import re
import subprocess
import sys
import textwrap
@@ -75,6 +76,22 @@ def _exists_rh_interface(name, distro):
return os.path.exists(file_to_check)
def _exists_rh_keyfile_interface(name):
file_to_check = \
'/etc/NetworkManager/system-connections/{name}.nmconnection'.format(
name=name)
return os.path.exists(file_to_check)
# Since RHEL10/CentOS 10 keyfiles format configs are madatory
def is_keyfile_format():
if "el10" in distro.os_release_info().get("platform_id", ""):
keyfiles = True
else:
keyfiles = False
return keyfiles
def _is_suse(distro):
# 'distro could be any of suse, opensuse,
# opensuse-leap, opensuse-tumbleweed, sles
@@ -109,6 +126,20 @@ def _network_config(args):
LLADDR={hwaddr}
STARTMODE=auto
""")
elif is_keyfile_format():
preamble = textwrap.dedent("""\
# Automatically generated, do not edit
[connection]
id={id}
uuid=
type=802-3-ethernet
[ipv4]
method={method}
[802-3-ethernet]
mac_address={mac_address}
""")
else:
preamble = textwrap.dedent("""\
# Automatically generated, do not edit
@@ -146,6 +177,14 @@ def _set_rh_bonding(name, interface, distro, results):
# to hotplug
results = results.replace("=auto", "=hotplug")
elif is_keyfile_format():
if 'bond_slaves' in interface:
results += "type=bond\n"
else:
results += "master={0}\n".format(interface['bond_master'])
results += "slave-type=bond\n"
else:
# RedHat does not add any specific configuration to the master
# interface. All configuration is done in the slave ifcfg files.
@@ -168,6 +207,10 @@ def _set_rh_vlan(name, interface, distro):
results += "VLAN_ID={vlan_id}\n".format(vlan_id=interface['vlan_id'])
results += "ETHERDEVICE={etherdevice}\n".format(
etherdevice=name.split('.')[0])
elif is_keyfile_format():
results += "\n[vlan]\n"
results += "id={vlan_id}\n".format(vlan_id=interface['vlan_id'])
results += "parent={parent}\n".format(parent=interface['vlan_link'])
else:
results += "VLAN=yes\n"
@@ -178,7 +221,7 @@ def _write_rh_v6_interface(name, interface, args, files):
config_file = _network_files(args.distro)["ifcfg"] + '-%s' % name
# We should already have this config file key key, we need to
# append to it
assert(config_file in files)
assert (config_file in files)
config_data = files[config_file]
config_data += 'IPV6INIT=yes\n'
config_data += 'IPV6_PRIVACY=no\n'
@@ -220,6 +263,53 @@ def _write_rh_v6_interface(name, interface, args, files):
return files
def _write_rh_v6_keyfile_interface(name, interface, args, files):
filename = \
'/etc/NetworkManager/system-connections/{name}.nmconnection'.format(
name=name)
if interface['type'] == 'ipv6_slaac' and not interface['ip_address']:
results = ""
results += "[ipv6]\n"
results += "method=auto\n"
else:
results = ""
results += "[ipv6]\n"
results += "method=manual\n"
try:
netmask = utils.ipv6_netmask_length(interface['netmask'])
except Exception:
logging.error("Invalid netmask format %s." % interface['netmask'])
results += "address1={address}/{netmask}\n".format(
address=interface['ip_address'], netmask=netmask)
if interface.get('routes', ()):
routes = []
route_counter = 0
for route in interface.get('routes', ()):
route_counter += 1
ipv6_netmask = utils.ipv6_netmask_length(route['netmask'])
full_route = "route" + str(route_counter) + "=" \
+ route['network'] + "/" + str(ipv6_netmask) \
+ ',' + route['gateway']
routes.append(full_route)
if routes:
route_match = re.search(r"address.*/[0-9][0-9]\n", results)
if not route_match:
route_match = re.search(r"method=*\n", results)
str_routes = ""
for i in range(len(routes)):
str_routes += routes[i] + "\n"
results = results[:route_match.end()] + str_routes + \
results[route_match.end():]
# append ipv6 config at the end
if results:
files[filename] = files[filename] + "\n" + results
return files
def _write_rh_interface(name, interface, args):
distro = args.distro
files_to_write = dict()
@@ -284,11 +374,13 @@ def _write_rh_dhcp(name, interface, args):
return {filename: results}
def _write_rh_manual(name, interface, args):
def _write_rh_keyfile_dhcp(name, interface, args):
distro = args.distro
filename = _network_files(distro)["ifcfg"] + '-{name}'.format(name=name)
filename = \
'/etc/NetworkManager/system-connections/{name}.nmconnection'.format(
name=name)
results = _network_config(args).format(
bootproto="none", name=name, hwaddr=interface['mac_address'])
method="auto", id=name, mac_address=interface['mac_address'])
results += _set_rh_vlan(name, interface, distro)
# set_rh_bonding takes results as argument so we need to assign
# the return value, not append it
@@ -297,9 +389,76 @@ def _write_rh_manual(name, interface, args):
return {filename: results}
def write_redhat_interfaces(interfaces, sys_interfaces, args):
files_to_write = dict()
def _write_rh_manual(name, interface, args):
distro = args.distro
if is_keyfile_format():
filename = \
'/etc/NetworkManager/system-connections/{name}.nmconnection' \
.format(name=name)
results = _network_config(args).format(
method="manual", id=name, mac_address=interface['mac_address'])
else:
filename = _network_files(distro)["ifcfg"] + '-{name}'.format(
name=name)
results = _network_config(args).format(
bootproto="none", name=name, hwaddr=interface['mac_address'])
results += _set_rh_vlan(name, interface, distro)
# set_rh_bonding takes results as argument so we need to assign
# the return value, not append it
results = _set_rh_bonding(name, interface, distro, results)
return {filename: results}
def _write_rh_keyfile_interface(_name, interface, args):
# NOTE(mnasiadka): Importing here to maintain py2.7 compatibility
import ipaddress
distro = args.distro
files_to_write = dict()
filename = \
'/etc/NetworkManager/system-connections/{name}.nmconnection'.format(
name=_name)
results = _network_config(args).format(
method="manual", id=_name, mac_address=interface['mac_address'])
# insert value after specific option using slicing
address = interface['ip_address'] + "/" + interface['netmask']
cidr_address = 'address1=%s' % ipaddress.ip_interface(
address).with_prefixlen
match = re.search(r'manual\n', results)
results = results[:match.end()] + cidr_address + \
"\n" + results[match.end():]
results += _set_rh_vlan(_name, interface, distro)
results = _set_rh_bonding(_name, interface, distro, results)
# format:comma separated list of routes in [ipv4]
routes = []
route_counter = 0
for route in interface.get('routes', ()):
route_counter += 1
route_cidr_address = ipaddress.ip_interface(
route['network'] + "/" + route['netmask']).with_prefixlen
full_route = "route" + str(route_counter) + "=" + \
route_cidr_address + ',' + route['gateway']
routes.append(full_route)
if routes:
route_match = re.search(r"address.*/[0-9][0-9]\n", results)
str_routes = ""
for i in range(len(routes)):
str_routes += routes[i] + "\n"
results = results[:route_match.end()] + \
str_routes + results[route_match.end():]
files_to_write[filename] = results
return files_to_write
def write_redhat_interfaces(interfaces, sys_interfaces, args):
files_to_write = dict()
# Strip out ignored interfaces
_interfaces = {}
for iname, interface in interfaces.items():
@@ -322,7 +481,6 @@ def write_redhat_interfaces(interfaces, sys_interfaces, args):
_interfaces[iname] = interface
interfaces = _interfaces
# Sort the interfaces by id so that we'll have consistent output order
_interfaces_by_sys_name = defaultdict(list)
for iname, interface in sorted(
@@ -372,18 +530,33 @@ def write_redhat_interfaces(interfaces, sys_interfaces, args):
logging.debug("Processing interface %s/%s" %
(_name, interface['type']))
if interface['type'] == 'ipv4':
files_to_write.update(
_write_rh_interface(_name, interface, args))
if is_keyfile_format():
files_to_write.update(
_write_rh_keyfile_interface(_name, interface, args))
else:
files_to_write.update(
_write_rh_interface(_name, interface, args))
elif interface['type'] == 'ipv4_dhcp':
files_to_write.update(
_write_rh_dhcp(_name, interface, args))
if is_keyfile_format():
files_to_write.update(
_write_rh_keyfile_dhcp(_name, interface, args))
else:
files_to_write.update(
_write_rh_dhcp(_name, interface, args))
elif interface['type'] == 'manual':
files_to_write.update(
_write_rh_manual(_name, interface, args))
elif interface['type'] in ('ipv6', 'ipv6_slaac'):
files_to_write.update(
_write_rh_v6_interface(_name,
interface, args, files_to_write))
if is_keyfile_format():
files_to_write.update(
_write_rh_v6_keyfile_interface(_name,
interface, args,
files_to_write))
else:
files_to_write.update(
_write_rh_v6_interface(_name,
interface, args,
files_to_write))
else:
logging.error(
"Unhandled interface %s/%s" % (_name, interface['type']))
@@ -396,10 +569,15 @@ def write_redhat_interfaces(interfaces, sys_interfaces, args):
# back to simple DHCP
for mac, iname in sorted(
sys_interfaces.items(), key=lambda x: x[1]):
if _exists_rh_interface(iname, args.distro):
# This interface already has a config file, move on
log.debug("%s already has config file, skipping" % iname)
continue
if is_keyfile_format:
if _exists_rh_keyfile_interface(iname):
log.debug("%s already has config file, skipping" % iname)
continue
else:
if _exists_rh_interface(iname, args.distro):
# This interface already has a config file, move on
log.debug("%s already has config file, skipping" % iname)
continue
inter_macs = [intf['mac_address'] for intf in interfaces.values()]
link_macs = [intf.get('link_mac') for intf in interfaces.values()
if 'vlan_id' in intf]
@@ -407,8 +585,13 @@ def write_redhat_interfaces(interfaces, sys_interfaces, args):
# We have a config drive config, move on
log.debug("%s configured via config-drive" % mac)
continue
files_to_write.update(_write_rh_dhcp(iname, {'mac_address': mac},
args))
if is_keyfile_format():
files_to_write.update(
_write_rh_keyfile_dhcp(iname, {'mac_address': mac}, args))
else:
files_to_write.update(
_write_rh_dhcp(iname, {'mac_address': mac}, args))
return files_to_write
@@ -1427,7 +1610,6 @@ def get_network_info(args):
network_info_file = '%s/openstack/latest/network_info.json' % config_drive
network_data_file = '%s/openstack/latest/network_data.json' % config_drive
vendor_data_file = '%s/openstack/latest/vendor_data.json' % config_drive
network_info = {}
if os.path.exists(network_info_file):
log.debug("Found network_info file %s" % network_info_file)
@@ -1628,7 +1810,7 @@ def main(argv=None):
log.debug("Starting glean")
log.debug("Detected distro : %s" % args.distro)
log.debug("Configuring %s NetworkManager" %
"with" if args.use_nm else "without")
("with" if args.use_nm else "without"))
with systemlock.Lock('/tmp/glean.lock'):
config_drive = os.path.join(args.root, 'mnt/config')

View File

@@ -0,0 +1 @@
hp.redhat-10.network.out

View File

@@ -0,0 +1,36 @@
### Write /etc/NetworkManager/system-connections/eth0.nmconnection
# Automatically generated, do not edit
[connection]
id=eth0
uuid=
type=802-3-ethernet
[ipv4]
method=auto
[802-3-ethernet]
mac_address=bc:76:4e:01:62:86
### Write /etc/NetworkManager/system-connections/eth1.nmconnection
# Automatically generated, do not edit
[connection]
id=eth1
uuid=
type=802-3-ethernet
[ipv4]
method=auto
[802-3-ethernet]
mac_address=bc:76:4e:05:7b:06
### Write /etc/NetworkManager/system-connections/eth3.nmconnection
# Automatically generated, do not edit
[connection]
id=eth3
uuid=
type=802-3-ethernet
[ipv4]
method=auto
[802-3-ethernet]
mac_address=bc:76:4e:12:a4:bb

View File

@@ -0,0 +1 @@
liberty.redhat-10.network.out

View File

@@ -0,0 +1,174 @@
### Write /etc/resolv.conf
nameserver 72.3.128.241
nameserver 72.3.128.240
### Write /etc/NetworkManager/system-connections/eth0.nmconnection
# Automatically generated, do not edit
[connection]
id=eth0
uuid=
type=802-3-ethernet
[ipv4]
method=manual
address1=23.253.229.154/24
route1=0.0.0.0/0,23.253.229.1
[802-3-ethernet]
mac_address=bc:76:4e:01:62:86
### Write /etc/NetworkManager/system-connections/eth1.nmconnection
# Automatically generated, do not edit
[connection]
id=eth1
uuid=
type=802-3-ethernet
[ipv4]
method=manual
address1=10.208.169.118/19
route1=10.176.0.0/12,10.208.160.1
route2=10.208.0.0/12,10.208.160.1
[802-3-ethernet]
mac_address=bc:76:4e:05:7b:06
### Write /etc/NetworkManager/system-connections/eth3.nmconnection
# Automatically generated, do not edit
[connection]
id=eth3
uuid=
type=802-3-ethernet
[ipv4]
method=auto
[802-3-ethernet]
mac_address=bc:76:4e:12:a4:bb
### Write /etc/NetworkManager/system-connections/eth4.25.nmconnection
# Automatically generated, do not edit
[connection]
id=eth4.25
uuid=
type=802-3-ethernet
[ipv4]
method=auto
[802-3-ethernet]
mac_address=bc:76:4e:12:a4:bc
[vlan]
id=25
parent=ethwithvlan
### Write /etc/NetworkManager/system-connections/eth4.26.nmconnection
# Automatically generated, do not edit
[connection]
id=eth4.26
uuid=
type=802-3-ethernet
[ipv4]
method=auto
[802-3-ethernet]
mac_address=bc:76:4e:12:a4:bd
[vlan]
id=26
parent=ethwithvlan
### Write /etc/NetworkManager/system-connections/eth5.nmconnection
# Automatically generated, do not edit
[connection]
id=eth5
uuid=
type=802-3-ethernet
[ipv4]
method=manual
[802-3-ethernet]
mac_address=bc:76:4e:05:7b:13
master=bond0
slave-type=bond
### Write /etc/NetworkManager/system-connections/eth6.nmconnection
# Automatically generated, do not edit
[connection]
id=eth6
uuid=
type=802-3-ethernet
[ipv4]
method=manual
[802-3-ethernet]
mac_address=bc:76:4e:05:7b:14
master=bond0
slave-type=bond
### Write /etc/NetworkManager/system-connections/bond0.nmconnection
# Automatically generated, do not edit
[connection]
id=bond0
uuid=
type=802-3-ethernet
[ipv4]
method=auto
[802-3-ethernet]
mac_address=bc:76:4e:05:7b:13
type=bond
### Write /etc/NetworkManager/system-connections/eth7.nmconnection
# Automatically generated, do not edit
[connection]
id=eth7
uuid=
type=802-3-ethernet
[ipv4]
method=manual
[802-3-ethernet]
mac_address=bc:76:4e:05:7b:15
master=bond1
slave-type=bond
### Write /etc/NetworkManager/system-connections/eth8.nmconnection
# Automatically generated, do not edit
[connection]
id=eth8
uuid=
type=802-3-ethernet
[ipv4]
method=manual
[802-3-ethernet]
mac_address=bc:76:4e:05:7b:16
master=bond1
slave-type=bond
### Write /etc/NetworkManager/system-connections/bond1.nmconnection
# Automatically generated, do not edit
[connection]
id=bond1
uuid=
type=802-3-ethernet
[ipv4]
method=manual
[802-3-ethernet]
mac_address=bc:76:4e:05:7b:15
type=bond
### Write /etc/NetworkManager/system-connections/bond1.27.nmconnection
# Automatically generated, do not edit
[connection]
id=bond1.27
uuid=
type=802-3-ethernet
[ipv4]
method=auto
[802-3-ethernet]
mac_address=bc:76:4e:12:a4:be
[vlan]
id=27
parent=bond1

View File

@@ -0,0 +1 @@
nokey.redhat-10.network.out

View File

@@ -0,0 +1,36 @@
### Write /etc/NetworkManager/system-connections/eth0.nmconnection
# Automatically generated, do not edit
[connection]
id=eth0
uuid=
type=802-3-ethernet
[ipv4]
method=auto
[802-3-ethernet]
mac_address=bc:76:4e:01:62:86
### Write /etc/NetworkManager/system-connections/eth1.nmconnection
# Automatically generated, do not edit
[connection]
id=eth1
uuid=
type=802-3-ethernet
[ipv4]
method=auto
[802-3-ethernet]
mac_address=bc:76:4e:05:7b:06
### Write /etc/NetworkManager/system-connections/eth3.nmconnection
# Automatically generated, do not edit
[connection]
id=eth3
uuid=
type=802-3-ethernet
[ipv4]
method=auto
[802-3-ethernet]
mac_address=bc:76:4e:12:a4:bb

View File

@@ -0,0 +1 @@
rax-iad.redhat-10.network.out

View File

@@ -0,0 +1,38 @@
### Write /etc/resolv.conf
nameserver 69.20.0.196
nameserver 69.20.0.164
### Write /etc/NetworkManager/system-connections/eth0.nmconnection
# Automatically generated, do not edit
[connection]
id=eth0
uuid=
type=802-3-ethernet
[ipv4]
method=manual
address1=146.20.110.113/24
route1=0.0.0.0/0,146.20.110.1
[802-3-ethernet]
mac_address=bc:76:4e:20:d7:2f
[ipv6]
method=manual
address1=2001:4802:7807:103:be76:4eff:fe20:d72f/64
route1=::/0,fe80::def
route2=fd30::/48,fe80::f001
### Write /etc/NetworkManager/system-connections/eth1.nmconnection
# Automatically generated, do not edit
[connection]
id=eth1
uuid=
type=802-3-ethernet
[ipv4]
method=manual
address1=10.210.32.174/19
route1=10.176.0.0/12,10.210.32.1
route2=10.208.0.0/12,10.210.32.1
[802-3-ethernet]
mac_address=bc:76:4e:20:d7:33

View File

@@ -0,0 +1 @@
rax.redhat-10.network.out

View File

@@ -0,0 +1,44 @@
### Write /etc/resolv.conf
nameserver 72.3.128.241
nameserver 72.3.128.240
### Write /etc/NetworkManager/system-connections/eth0.nmconnection
# Automatically generated, do not edit
[connection]
id=eth0
uuid=
type=802-3-ethernet
[ipv4]
method=manual
address1=23.253.229.154/24
route1=0.0.0.0/0,23.253.229.1
[802-3-ethernet]
mac_address=bc:76:4e:01:62:86
### Write /etc/NetworkManager/system-connections/eth1.nmconnection
# Automatically generated, do not edit
[connection]
id=eth1
uuid=
type=802-3-ethernet
[ipv4]
method=manual
address1=10.208.169.118/19
route1=10.176.0.0/12,10.208.160.1
route2=10.208.0.0/12,10.208.160.1
[802-3-ethernet]
mac_address=bc:76:4e:05:7b:06
### Write /etc/NetworkManager/system-connections/eth3.nmconnection
# Automatically generated, do not edit
[connection]
id=eth3
uuid=
type=802-3-ethernet
[ipv4]
method=auto
[802-3-ethernet]
mac_address=bc:76:4e:12:a4:bb

View File

@@ -0,0 +1 @@
vexxhost.redhat-10.network.out

View File

@@ -0,0 +1,19 @@
### Write /etc/resolv.conf
nameserver 199.204.44.24
nameserver 199.204.47.54
### Write /etc/NetworkManager/system-connections/ens3.nmconnection
# Automatically generated, do not edit
[connection]
id=ens3
uuid=
type=802-3-ethernet
[ipv4]
method=auto
[802-3-ethernet]
mac_address=fa:16:3e:d1:1f:b2
[ipv6]
method=manual
address1=2604:e100:1:0:f816:3eff:fed1:1fb2/64

View File

@@ -20,19 +20,20 @@ import os
try:
from unittest import mock
except ImportError:
import mock
import mock # noqa H216
from oslotest import base
from testscenarios import load_tests_apply_scenarios as load_tests # noqa
from glean._vendor import distro as vendor_distro # noqa
from glean import cmd
sample_data_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)), 'fixtures')
distros = ['Ubuntu', 'Debian', 'Fedora', 'RedHat', 'CentOS', 'Gentoo',
'openSUSE', 'networkd', 'Rocky']
distros = ['Ubuntu', 'Debian', 'Fedora', 'RedHat', 'CentOS', 'CentOS-10',
'Gentoo', 'openSUSE', 'networkd', 'Rocky']
styles = ['hp', 'rax', 'rax-iad', 'liberty', 'nokey', 'vexxhost']
ips = {'hp': '127.0.1.1',
@@ -95,7 +96,8 @@ class TestGlean(base.BaseTestCase):
# called later
mock_dirs = ('/etc/network', '/etc/sysconfig/network-scripts',
'/etc/conf.d', '/etc/init.d', '/etc/sysconfig/network',
'/etc/systemd/network')
'/etc/systemd/network',
'/etc/NetworkManager/system-connections')
mock_files = ('/etc/resolv.conf', '/etc/hostname', '/etc/hosts',
'/etc/systemd/resolved.conf', '/bin/systemctl')
if (path.startswith(mock_dirs) or path in mock_files):
@@ -170,7 +172,13 @@ class TestGlean(base.BaseTestCase):
@mock.patch('os.listdir', new_callable=mock.Mock)
@mock.patch('os.system', return_value=0, new_callable=mock.Mock)
@mock.patch('glean.cmd.open', new_callable=mock.Mock)
@mock.patch('glean._vendor.distro.linux_distribution',
new_callable=mock.Mock)
@mock.patch('glean.cmd.is_keyfile_format', return_value=False,
new_callable=mock.Mock)
def _assert_distro_provider(self, distro, provider, interface,
mock_is_keyfile,
mock_vendor_linux_distribution,
mock_open,
mock_os_system,
mock_os_listdir,
@@ -208,6 +216,18 @@ class TestGlean(base.BaseTestCase):
mock_os_system.side_effect = functools.partial(
self.os_system_side_effect, distro)
# distro versions handling
if distro.__contains__("-"):
version = distro.rsplit("-", 1)[1]
distro = distro.rsplit("-", 1)[0]
mock_vendor_linux_distribution.return_value = [distro, version]
if distro in ['CentOS', 'Rocky', 'RHEL'] and version == "10":
mock_is_keyfile.return_value = True
use_nm = True
else:
version = None
mock_vendor_linux_distribution.return_value = [distro]
# default args
argv = ['--hostname']
@@ -222,7 +242,13 @@ class TestGlean(base.BaseTestCase):
cmd.main(argv)
output_filename = '%s.%s.network.out' % (provider, distro.lower())
if version:
output_filename = '%s.%s-%s.network.out' % (provider,
distro.lower(),
version)
else:
output_filename = '%s.%s.network.out' % (provider, distro.lower())
output_path = os.path.join(sample_data_path, 'test', output_filename)
if not skip_dns:
if os.path.exists(output_path + '.dns'):

View File

@@ -2,7 +2,7 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
hacking>=2.0.0,<3.0.0
hacking>=2.0.0,<8.0.0
coverage>=3.6
mock>=1.0;python_version=='2.7'

View File

@@ -1,6 +1,6 @@
[tox]
minversion = 1.6
envlist = py36,py35,py27,pypy,pep8
envlist = py312,py39,py36,py35,py27,pypy,pep8
skipsdist = True
[testenv]