Add networkd support

Initially I've only added this for Gentoo (will only be turned on within
Gentoo systemd installs).  Has full ipv6 support too.

Change-Id: I34eeb96eb90850d6a4407559e8a3f9aa74483a95
This commit is contained in:
Matthew Thode 2018-02-28 08:37:09 -06:00
parent 091633b380
commit fa417426ae
No known key found for this signature in database
GPG Key ID: 64A37BEAAE19A4E8
10 changed files with 702 additions and 17 deletions

View File

@ -314,6 +314,289 @@ def write_redhat_interfaces(interfaces, sys_interfaces, distro):
return files_to_write
def _write_networkd_interface(name, interfaces, files_struct=dict()):
vlans = []
for interface in interfaces:
iname = name
# if vlan set interface name to vlan format
if 'vlan_id' in interface:
iname = name + '-vlan' + str(interface['vlan_id'])
vlans.append(iname)
network_file = '/etc/systemd/network/{name}.network'.format(name=iname)
if network_file not in files_struct:
files_struct[network_file] = dict()
if '[Match]' not in files_struct[network_file]:
files_struct[network_file]['[Match]'] = list()
files_struct[network_file]['[Match]'].append(
'MACAddress={mac_address}'.format(
mac_address=interface['mac_address']
)
)
files_struct[network_file]['[Match]'].append(
'Name={name}'.format(name=iname)
)
# define network if needed (basically always)
if ((interface['type'] in ['ipv4_dhcp', 'ipv6_slaac',
'ipv6_dhcpv6_stateful', 'manual', 'ipv4', 'ipv6']) or
('vlan_id' in interface) or
('bond_mode' in interface)):
if '[Network]' not in files_struct[network_file]:
files_struct[network_file]['[Network]'] = list()
# dhcp network, set to yes if both dhcp6 and dhcp4 are set
if interface['type'] == 'ipv4_dhcp':
if 'DHCP=ipv6' in files_struct[network_file]['[Network]']:
files_struct[network_file]['[Network]'].append('DHCP=yes')
else:
files_struct[network_file]['[Network]'].append('DHCP=ipv4')
if interface['type'] == 'ipv6_dhcpv6_stateful':
if 'DHCP=ipv4' in files_struct[network_file]['[Network]']:
files_struct[network_file]['[Network]'].append('DHCP=yes')
else:
files_struct[network_file]['[Network]'].append('DHCP=ipv6')
# slaac can start dhcp6 if the associated RA option is sent to server
if interface['type'] == 'ipv6_slaac':
# we are accepting slaac now, remove the disabling of slaac
if 'IPv6AcceptRA=no' in files_struct[network_file]['[Network]']:
files_struct[network_file]['[Network]'].remove(
'IPv6AcceptRA=no'
)
files_struct[network_file]['[Network]'].append('IPv6AcceptRA=yes')
else:
# only disbale slaac if slac is not already enabled
if 'IPv6AcceptRA=yes' not in \
files_struct[network_file]['[Network]']:
files_struct[network_file]['[Network]'].append(
'IPv6AcceptRA=no'
)
# vlan network
# static network
if interface['type'] in ['ipv4', 'ipv6']:
if 'addresses' not in files_struct[network_file]:
files_struct[network_file]['addresses'] = list()
if interface['type'] == 'ipv4':
files_struct[network_file]['addresses'].append(
'Address={address}/{cidr}'.format(
address=interface['ip_address'],
cidr=utils.ipv4_netmask_length(interface['netmask'])
)
)
if interface['type'] == 'ipv6':
files_struct[network_file]['addresses'].append(
'Address={address}/{cidr}'.format(
address=interface['ip_address'],
cidr=utils.ipv6_netmask_length(interface['netmask'])
)
)
# routes
if 'routes' in interface:
if 'routes' not in files_struct[network_file]:
files_struct[network_file]['routes'] = list()
for route in interface['routes']:
route_destination = None
route_gateway = None
if 'network' in route:
if 'v6' in interface['type']:
cidr = utils.ipv6_netmask_length(route['netmask'])
else:
cidr = utils.ipv4_netmask_length(route['netmask'])
route_destination = 'Destination={network}/{cidr}'.format(
network=route['network'], cidr=cidr
)
if 'gateway' in route:
route_gateway = 'Gateway={gateway}'.format(
gateway=route['gateway']
)
# add route as a dictionary to the routes list
files_struct[network_file]['routes'].append({
'route': route_destination,
'gw': route_gateway
})
# create netdev files
if 'bond_mode' or 'vlan_id' in interface:
netdev_file = \
'/etc/systemd/network/{name}.netdev'.format(name=iname)
if netdev_file not in files_struct:
files_struct[netdev_file] = dict()
if '[NetDev]' not in files_struct[netdev_file]:
files_struct[netdev_file]['[NetDev]'] = list()
files_struct[netdev_file]['[NetDev]'].append(
'Name={name}'.format(name=iname)
)
if 'mac_address' in interface:
files_struct[netdev_file]['[NetDev]'].append(
'MACAddress={mac_address}'.format(
mac_address=interface['mac_address']
)
)
if 'vlan_id' in interface:
files_struct[netdev_file]['[NetDev]'].append('Kind=vlan')
files_struct[netdev_file]['[VLAN]'] = list()
files_struct[netdev_file]['[VLAN]'].append(
'Id={id}'.format(id=interface['vlan_id'])
)
if 'bond_mode' in interface:
files_struct[netdev_file]['[NetDev]'].append('Kind=bond')
files_struct[netdev_file]['[Bond]'] = list()
files_struct[netdev_file]['[Bond]'].append(
'Mode={bond_mode}'.format(bond_mode=interface['bond_mode'])
)
files_struct[netdev_file]['[Bond]'].append(
'LACPTransmitRate=fast'
)
if 'slaves' in interface:
for slave in interface['slaves']:
slave_net_file = \
'/etc/systemd/network/{name}.network'.format(
name=slave
)
if slave_net_file not in files_struct:
files_struct[slave_net_file] = dict()
if '[Network]' not in files_struct[slave_net_file]:
files_struct[slave_net_file]['[Network]'] = list()
files_struct[slave_net_file]['[Network]'].append(
'Bond={name}'.format(name=iname)
)
if 'bond_xmit_hash_policy' in interface:
files_struct[netdev_file]['[Bond]'].append(
'TransmitHashPolicy={bond_xmit_hash_policy}'.format(
bond_xmit_hash_policy=interface[
'bond_xmit_hash_policy'
]
)
)
if 'bond_miimon' in interface:
files_struct[netdev_file]['[Bond]'].append(
'MIIMonitorSec={milliseconds}'.format(
milliseconds=interface['bond_miimon']
)
)
# vlan mapping sucks (forward and reverse)
if vlans:
netdev = vlans[0].split('-')[0]
vlan_master_file = \
'/etc/systemd/network/{name}.network'.format(name=netdev)
if vlan_master_file not in files_struct:
files_struct[vlan_master_file] = dict()
if '[Network]' not in files_struct[vlan_master_file]:
files_struct[vlan_master_file]['[Network]'] = list()
for vlan in vlans:
files_struct[vlan_master_file]['[Network]'].append('VLAN=' + vlan)
vlan_file = '/etc/systemd/network/{name}.network'.format(name=vlan)
if vlan_file not in files_struct:
files_struct[vlan_file] = dict()
if '[Network]' not in files_struct[vlan_file]:
files_struct[vlan_file]['[Network]'] = list()
files_struct[vlan_file]['[Network]'].append('VLAN=' + vlan)
return files_struct
def write_networkd_interfaces(interfaces, sys_interfaces):
files_to_write = dict()
gen_intfs = {}
files_struct = dict()
# Sort the interfaces by id so that we'll have consistent output order
for iname, interface in sorted(
interfaces.items(), key=lambda x: x[1]['id']):
# sys_interfaces is pruned by --interface; if one of the
# raw_macs (or, *the* MAC for single interfaces) does not
# match as one of the interfaces we want configured, skip
raw_macs = interface.get('raw_macs', [interface['mac_address']])
if not set(sys_interfaces).intersection(set(raw_macs)):
continue
if 'bond_mode' in interface:
interface['slaves'] = [
sys_interfaces[mac] for mac in interface['raw_macs']]
if 'raw_macs' in interface:
key = tuple(interface['raw_macs'])
if key not in gen_intfs:
gen_intfs[key] = []
gen_intfs[key].append(interface)
else:
key = (interface['mac_address'],)
if key not in gen_intfs:
gen_intfs[key] = []
gen_intfs[key].append(interface)
for raw_macs, interfs in gen_intfs.items():
if len(raw_macs) == 1:
interface_name = sys_interfaces[raw_macs[0]]
else:
# It is possible our interface does not have a link, so
# fall back to interface id.
interface_name = next(
intf.get('link', intf['id']) for intf in interfs
if 'bond_mode' in intf)
files_struct = _write_networkd_interface(
interface_name, interfs, files_struct)
for mac, iname in sorted(
sys_interfaces.items(), key=lambda x: x[1]):
if _exists_gentoo_interface(iname):
# This interface already has a config file, move on
log.debug("%s already has config file, skipping" % iname)
continue
if (mac,) in gen_intfs:
# We have a config drive config, move on
log.debug("%s configured via config-drive" % mac)
continue
interface = {'type': 'ipv4_dhcp', 'mac_address': mac}
files_struct = _write_networkd_interface(
iname, [interface], files_struct)
for networkd_file in files_struct:
file_contents = '# Automatically generated, do not edit\n'
if '[Match]' in files_struct[networkd_file]:
file_contents += '[Match]\n'
for line in sorted(set(files_struct[networkd_file]['[Match]'])):
file_contents += line
file_contents += '\n'
file_contents += '\n'
if '[Network]' in files_struct[networkd_file]:
file_contents += '[Network]\n'
for line in sorted(set(files_struct[networkd_file]['[Network]'])):
file_contents += line
file_contents += '\n'
file_contents += '\n'
if 'addresses' in files_struct[networkd_file]:
for address in files_struct[networkd_file]['addresses']:
file_contents += '[Address]\n'
file_contents += address + '\n\n'
if 'routes' in files_struct[networkd_file]:
for route in files_struct[networkd_file]['routes']:
file_contents += '[Route]\n'
if route['route'] is not None:
file_contents += route['route'] + '\n'
if route['gw'] is not None:
file_contents += route['gw'] + '\n'
file_contents += '\n'
if '[NetDev]' in files_struct[networkd_file]:
file_contents += '[NetDev]\n'
for line in sorted(set(files_struct[networkd_file]['[NetDev]'])):
file_contents += line
file_contents += '\n'
file_contents += '\n'
if '[VLAN]' in files_struct[networkd_file]:
file_contents += '[VLAN]\n'
for line in sorted(set(files_struct[networkd_file]['[VLAN]'])):
file_contents += line
file_contents += '\n'
file_contents += '\n'
if '[Bond]' in files_struct[networkd_file]:
file_contents += '[Bond]\n'
for line in sorted(set(files_struct[networkd_file]['[Bond]'])):
file_contents += line
file_contents += '\n'
file_contents += '\n'
files_to_write['{path}'.format(path=networkd_file)] = file_contents
return files_to_write
def _exists_gentoo_interface(name):
file_to_check = '/etc/conf.d/net.{name}'.format(name=name)
return os.path.exists(file_to_check)
@ -773,6 +1056,10 @@ def write_static_network_info(
files_to_write.update(
write_gentoo_interfaces(interfaces, sys_interfaces)
)
elif args.distro in 'networkd':
files_to_write.update(
write_networkd_interfaces(interfaces, sys_interfaces)
)
else:
return False

View File

@ -0,0 +1,13 @@
[Unit]
Description=Glean system configuration
Before=systemd-networkd.service
Requires=systemd-networkd.service
[Service]
Type=oneshot
User=root
ExecStart=%%GLEANSH_PATH%%/glean.sh --distro networkd
RemainAfterExit=true
[Install]
WantedBy=multi-user.target

View File

@ -95,7 +95,6 @@ def main():
if os.path.exists('/etc/gentoo-release'):
log.info('installing openrc services')
install('glean.openrc', '/etc/init.d/glean')
subprocess.call(['rc-update', 'add', 'glean', 'boot'])
# Needs to check for the presence of systemd and systemctl
# as apparently some packages may stage systemd init files
# when systemd is not present.
@ -111,11 +110,19 @@ def main():
log.info("Installing systemd services")
log.info("glean.sh in %s" % p)
install(
'glean@.service',
'/usr/lib/systemd/system/glean@.service',
mode='0644',
replacements={'GLEANSH_PATH': p})
if os.path.exists('/etc/gentoo-release'):
install(
'glean-networkd.service',
'/lib/systemd/system/glean.service',
mode='0644',
replacements={'GLEANSH_PATH': p})
subprocess.call(['systemctl', 'enable', 'glean.service'])
else:
install(
'glean@.service',
'/usr/lib/systemd/system/glean@.service',
mode='0644',
replacements={'GLEANSH_PATH': p})
install(
'glean-udev.rules',
'/etc/udev/rules.d/99-glean.rules',
@ -123,12 +130,8 @@ def main():
elif os.path.exists('/etc/init'):
log.info("Installing upstart services")
install('glean.conf', '/etc/init/glean.conf')
elif os.path.exists('/etc/gentoo-release'):
# If installing on Gentoo and if systemd or upstart is not
# detected, then prevent the installation of sysv init scripts
# on Gentoo, which would overwrite the OpenRC init scripts as
# the sysv init script uses the same path.
pass
elif os.path.exists('/sbin/rc-update'):
subprocess.call(['rc-update', 'add', 'glean', 'boot'])
else:
log.info("Installing sysv services")
install('glean.init', '/etc/init.d/glean')

View File

@ -0,0 +1,30 @@
### Write /etc/systemd/network/eth0.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:01:62:86
Name=eth0
[Network]
DHCP=ipv4
IPv6AcceptRA=no
### Write /etc/systemd/network/eth1.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:05:7b:06
Name=eth1
[Network]
DHCP=ipv4
IPv6AcceptRA=no
### Write /etc/systemd/network/eth3.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:12:a4:bb
Name=eth3
[Network]
DHCP=ipv4
IPv6AcceptRA=no

View File

@ -0,0 +1,209 @@
### Write /etc/resolv.conf
nameserver 72.3.128.241
nameserver 72.3.128.240
### Write /etc/systemd/network/eth0.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:01:62:86
Name=eth0
[Network]
IPv6AcceptRA=no
[Address]
Address=23.253.229.154/24
[Route]
Destination=0.0.0.0/0
Gateway=23.253.229.1
### Write /etc/systemd/network/eth1.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:05:7b:06
Name=eth1
[Network]
IPv6AcceptRA=no
[Address]
Address=10.208.169.118/19
[Route]
Destination=10.176.0.0/12
Gateway=10.208.160.1
[Route]
Destination=10.208.0.0/12
Gateway=10.208.160.1
### Write /etc/systemd/network/eth3.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:12:a4:bb
Name=eth3
[Network]
DHCP=ipv4
IPv6AcceptRA=no
### Write /etc/systemd/network/eth4.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:12:a4:bc
Name=eth4
[Network]
IPv6AcceptRA=no
VLAN=eth4-vlan25
VLAN=eth4-vlan26
### Write /etc/systemd/network/eth4-vlan25.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:12:a4:bc
Name=eth4-vlan25
[Network]
DHCP=ipv4
IPv6AcceptRA=no
VLAN=eth4-vlan25
### Write /etc/systemd/network/eth4-vlan25.netdev
# Automatically generated, do not edit
[NetDev]
Kind=vlan
MACAddress=bc:76:4e:12:a4:bc
Name=eth4-vlan25
[VLAN]
Id=25
### Write /etc/systemd/network/eth4-vlan26.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:12:a4:bd
Name=eth4-vlan26
[Network]
DHCP=ipv4
IPv6AcceptRA=no
VLAN=eth4-vlan26
### Write /etc/systemd/network/eth4-vlan26.netdev
# Automatically generated, do not edit
[NetDev]
Kind=vlan
MACAddress=bc:76:4e:12:a4:bd
Name=eth4-vlan26
[VLAN]
Id=26
### Write /etc/systemd/network/eth5.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:05:7b:13
Name=eth5
[Network]
Bond=bond0
IPv6AcceptRA=no
### Write /etc/systemd/network/eth6.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:05:7b:14
Name=eth6
[Network]
Bond=bond0
IPv6AcceptRA=no
### Write /etc/systemd/network/bond0.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:05:7b:13
Name=bond0
[Network]
DHCP=ipv4
IPv6AcceptRA=no
### Write /etc/systemd/network/bond0.netdev
# Automatically generated, do not edit
[NetDev]
Kind=bond
MACAddress=bc:76:4e:05:7b:13
Name=bond0
[Bond]
LACPTransmitRate=fast
MIIMonitorSec=100
Mode=802.3ad
TransmitHashPolicy=layer3+4
### Write /etc/systemd/network/eth7.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:05:7b:15
Name=eth7
[Network]
Bond=bond1
IPv6AcceptRA=no
### Write /etc/systemd/network/eth8.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:05:7b:16
Name=eth8
[Network]
Bond=bond1
IPv6AcceptRA=no
### Write /etc/systemd/network/bond1.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:05:7b:15
Name=bond1
[Network]
IPv6AcceptRA=no
VLAN=bond1-vlan27
### Write /etc/systemd/network/bond1.netdev
# Automatically generated, do not edit
[NetDev]
Kind=bond
MACAddress=bc:76:4e:05:7b:15
Name=bond1
[Bond]
LACPTransmitRate=fast
MIIMonitorSec=100
Mode=802.3ad
TransmitHashPolicy=layer3+4
### Write /etc/systemd/network/bond1-vlan27.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:12:a4:be
Name=bond1-vlan27
[Network]
DHCP=ipv4
IPv6AcceptRA=no
VLAN=bond1-vlan27
### Write /etc/systemd/network/bond1-vlan27.netdev
# Automatically generated, do not edit
[NetDev]
Kind=vlan
MACAddress=bc:76:4e:12:a4:be
Name=bond1-vlan27
[VLAN]
Id=27

View File

@ -0,0 +1,30 @@
### Write /etc/systemd/network/eth0.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:01:62:86
Name=eth0
[Network]
DHCP=ipv4
IPv6AcceptRA=no
### Write /etc/systemd/network/eth1.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:05:7b:06
Name=eth1
[Network]
DHCP=ipv4
IPv6AcceptRA=no
### Write /etc/systemd/network/eth3.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:12:a4:bb
Name=eth3
[Network]
DHCP=ipv4
IPv6AcceptRA=no

View File

@ -0,0 +1,50 @@
### Write /etc/resolv.conf
nameserver 69.20.0.196
nameserver 69.20.0.164
### Write /etc/systemd/network/eth0.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:20:d7:2f
Name=eth0
[Network]
IPv6AcceptRA=no
[Address]
Address=146.20.110.113/24
[Address]
Address=2001:4802:7807:103:be76:4eff:fe20:d72f/64
[Route]
Destination=0.0.0.0/0
Gateway=146.20.110.1
[Route]
Destination=::/0
Gateway=fe80::def
[Route]
Destination=fd30::/48
Gateway=fe80::f001
### Write /etc/systemd/network/eth1.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:20:d7:33
Name=eth1
[Network]
IPv6AcceptRA=no
[Address]
Address=10.210.32.174/19
[Route]
Destination=10.176.0.0/12
Gateway=10.210.32.1
[Route]
Destination=10.208.0.0/12
Gateway=10.210.32.1

View File

@ -0,0 +1,49 @@
### Write /etc/resolv.conf
nameserver 72.3.128.241
nameserver 72.3.128.240
### Write /etc/systemd/network/eth0.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:01:62:86
Name=eth0
[Network]
IPv6AcceptRA=no
[Address]
Address=23.253.229.154/24
[Route]
Destination=0.0.0.0/0
Gateway=23.253.229.1
### Write /etc/systemd/network/eth1.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:05:7b:06
Name=eth1
[Network]
IPv6AcceptRA=no
[Address]
Address=10.208.169.118/19
[Route]
Destination=10.176.0.0/12
Gateway=10.208.160.1
[Route]
Destination=10.208.0.0/12
Gateway=10.208.160.1
### Write /etc/systemd/network/eth3.network
# Automatically generated, do not edit
[Match]
MACAddress=bc:76:4e:12:a4:bb
Name=eth3
[Network]
DHCP=ipv4
IPv6AcceptRA=no

View File

@ -30,7 +30,7 @@ sample_data_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)), 'fixtures')
distros = ['Ubuntu', 'Debian', 'Fedora', 'RedHat', 'CentOS', 'Gentoo',
'openSUSE']
'openSUSE', 'networkd']
styles = ['hp', 'rax', 'rax-iad', 'liberty', 'nokey']
ips = {'hp': '127.0.1.1',
'rax': '23.253.229.154',
@ -81,8 +81,10 @@ class TestGlean(base.BaseTestCase):
# them in file_handle_mocks{} so we can assert they were
# called later
mock_dirs = ('/etc/network', '/etc/sysconfig/network-scripts',
'/etc/conf.d', '/etc/init.d', '/etc/sysconfig/network')
mock_files = ('/etc/resolv.conf', '/etc/hostname', '/etc/hosts')
'/etc/conf.d', '/etc/init.d', '/etc/sysconfig/network',
'/etc/systemd/network')
mock_files = ('/etc/resolv.conf', '/etc/hostname', '/etc/hosts',
'/bin/systemctl')
if (path.startswith(mock_dirs) or path in mock_files):
try:
mock_handle = self.file_handle_mocks[path]
@ -126,14 +128,16 @@ class TestGlean(base.BaseTestCase):
if path in ('/etc/sysconfig/network-scripts/ifcfg-eth2',
'/etc/network/interfaces.d/eth2.cfg',
'/etc/conf.d/net.eth2',
'/etc/sysconfig/network/ifcfg-eth2'):
'/etc/sysconfig/network/ifcfg-eth2',
'/etc/systemd/network/eth2.network'):
# Pretend this file exists, we need to test skipping
# pre-existing config files
return True
elif (path.startswith(('/etc/sysconfig/network-scripts/',
'/etc/sysconfig/network/',
'/etc/network/interfaces.d/',
'/etc/conf.d/'))):
'/etc/conf.d/',
'/etc/systemd/network/'))):
# Don't check the host os's network config
return False
return real_path_exists(path)

View File

@ -29,3 +29,13 @@ def ipv6_netmask_length(netmask):
except:
raise SyntaxError('Bad Netmask')
return count
# code to convert netmask ip to cidr number
# https://stackoverflow.com/a/43885814
def ipv4_netmask_length(netmask):
'''
:param netmask: netmask ip addr (eg: 255.255.255.0)
:return: equivalent cidr number to given netmask ip (eg: 24)
'''
return sum([bin(int(x)).count('1') for x in netmask.split('.')])