From e8787d2c4a449e24ec6df18d27abdd8a07cc85a0 Mon Sep 17 00:00:00 2001 From: Markos Chandras Date: Tue, 27 Sep 2016 20:35:47 +0100 Subject: [PATCH] Fix SUSE based network configuration The SUSE and openSUSE network configuration is similar to the RedHat one but it's not identical. There are quite a few differences. There is no network.service on SUSE. This is an alias to wicked.service and systemd refuses to issue an 'systemctl enable '. Moreover, the network configuration happens in ifcfg files in /etc/sysconfg/network instead of /etc/sysconfig/network-scripts. Furthermore, routes are being configured in /etc/sysconfig/network/ifroute-* files instead of /etc/sysconfig/network-scripts/route-*. Finally, the options within these files are not always the same, so we must use whatever options make sense for each case accordingly. Change-Id: I20bffabd333ea290d8712ec2a467f2b2d5678f3a --- glean/cmd.py | 232 +++++++++++++----- .../fixtures/test/hp.opensuse.network.out | 15 ++ .../test/liberty.opensuse.network.out | 106 ++++++++ .../fixtures/test/nokey.opensuse.network.out | 15 ++ .../test/rax-iad.opensuse.network.out | 22 ++ .../fixtures/test/rax.opensuse.network.out | 27 ++ glean/tests/test_glean.py | 9 +- 7 files changed, 368 insertions(+), 58 deletions(-) create mode 100644 glean/tests/fixtures/test/hp.opensuse.network.out create mode 100644 glean/tests/fixtures/test/liberty.opensuse.network.out create mode 100644 glean/tests/fixtures/test/nokey.opensuse.network.out create mode 100644 glean/tests/fixtures/test/rax-iad.opensuse.network.out create mode 100644 glean/tests/fixtures/test/rax.opensuse.network.out diff --git a/glean/cmd.py b/glean/cmd.py index c6d6c0c..82dd68c 100644 --- a/glean/cmd.py +++ b/glean/cmd.py @@ -66,30 +66,143 @@ def _exists_rh_interface(name): return os.path.exists(file_to_check) -def _write_rh_interface(name, interface): +def _is_suse(distro): + return distro in ('suse', 'opensuse') + + +def _network_files(distro): + network_files = {} + if _is_suse(distro): + # network.service is an alias to wicked.service on SUSE + # and openSUSE images so use that instead of network.service + # since systemd refuses to treat aliases as normal services + network_files = { + "systemd": "wicked.service", + "ifcfg": "/etc/sysconfig/network/ifcfg", + "route": "/etc/sysconfig/network/ifroute", + } + else: + network_files = { + "systemd": "network.service", + "ifcfg": "/etc/sysconfig/network-scripts/ifcfg", + "route": "/etc/sysconfig/network-scripts/route", + } + + return network_files + + +def _network_config(distro): + network_config = {} + if _is_suse(distro): + header = "\n".join(["# Automatically generated, do not edit", + "BOOTPROTO={bootproto}", + "LLADDR={hwaddr}"]) + footer = "STARTMODE=auto" + "\n" + + network_config = { + "static": "\n".join([header, + "IPADDR={ip_address}", + "NETMASK={netmask}", + footer]) + } + else: + header = "\n".join(["# Automatically generated, do not edit", + "DEVICE={name}", + "BOOTPROTO={bootproto}", + "HWADDR={hwaddr}"]) + footer = "\n".join(["ONBOOT=yes", "NM_CONTROLLED=no", + "TYPE=Ethernet"]) + "\n" + + network_config = { + # RedHat does not use TYPE=Ethernet in the static configurations + "static": "\n".join([header, + "IPADDR={ip_address}", + "NETMASK={netmask}", + footer.replace("TYPE=Ethernet\n", "")]) + } + + # RedHat does not use TYPE=Ethernet in the dhcp configurations + network_config["dhcp"] = "\n".join([header, footer]) + network_config["none"] = "\n".join([header, footer]) + + return network_config + + +def _set_rh_bonding(name, interface, distro, results): + if not any(bond in ['bond_slaves', 'bond_master'] for bond in interface): + return results + + # Careful, we are operating on the live 'results' variable + # so we need to always append our data + if _is_suse(distro): + # SUSE configures the slave interfaces on the master ifcfg file. + # The master interface contains a 'bond_slaves' key containing a list + # of the slave interfaces + if 'bond_slaves' in interface: + results += "BONDING_MASTER=yes\n" + slave_cnt = 0 + for slave in interface['bond_slaves']: + results += "BONDING_SLAVE_{id}={name}\n".format( + id=slave_cnt, name=slave) + slave_cnt += 1 + else: + # Slave interfaces do not know they are part of a bonded + # interface. All we need to do is to set the STARTMODE + # to hotplug + results = results.replace("=auto", "=hotplug") + + else: + # RedHat does not add any specific configuration to the master + # interface. All configuration is done in the slave ifcfg files. + if 'bond_slaves' in interface: + return results + + results += "SLAVE=yes\n" + results += "MASTER={0}\n".format(interface['bond_master']) + + return results + + +def _set_rh_vlan(name, interface, distro): + results = "" + + if 'vlan_id' not in interface: + return results + + if _is_suse(distro): + results += "VLAN_ID={vlan_id}\n".format(vlan_id=interface['vlan_id']) + results += "ETHERDEVICE={etherdevice}\n".format( + etherdevice=name.split('.')[0]) + else: + results += "VLAN=yes\n" + + return results + + +def _write_rh_interface(name, interface, distro): files_to_write = dict() - results = """# Automatically generated, do not edit -DEVICE={name} -BOOTPROTO=static -HWADDR={hwaddr} -IPADDR={ip_address} -NETMASK={netmask} -ONBOOT=yes -NM_CONTROLLED=no -""".format( + results = _network_config(distro)["static"].format( + bootproto="static", name=name, hwaddr=interface['mac_address'], ip_address=interface['ip_address'], netmask=interface['netmask'], ) - if 'vlan_id' in interface: - results += "VLAN=yes\n" + 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) routes = [] for route in interface['routes']: if route['network'] == '0.0.0.0' and route['netmask'] == '0.0.0.0': - results += "DEFROUTE=yes\n" - results += "GATEWAY={gw}\n".format(gw=route['gateway']) + if not _is_suse(distro): + results += "DEFROUTE=yes\n" + results += "GATEWAY={gw}\n".format(gw=route['gateway']) + else: + # Special notation for default route on SUSE/wicked + routes.append(dict( + net='default', mask='', gw=route['gateway'])) else: routes.append(dict( net=route['network'], mask=route['netmask'], @@ -98,50 +211,48 @@ NM_CONTROLLED=no if routes: route_content = "" for x in range(0, len(routes)): - route_content += "ADDRESS{x}={net}\n".format(x=x, **routes[x]) - route_content += "NETMASK{x}={mask}\n".format(x=x, **routes[x]) - route_content += "GATEWAY{x}={gw}\n".format(x=x, **routes[x]) - files_to_write['/etc/sysconfig/network-scripts/route-{name}'.format( - name=name)] = route_content - files_to_write['/etc/sysconfig/network-scripts/ifcfg-{name}'.format( + if not _is_suse(distro): + route_content += "ADDRESS{x}={net}\n".format(x=x, **routes[x]) + route_content += "NETMASK{x}={mask}\n".format(x=x, **routes[x]) + route_content += "GATEWAY{x}={gw}\n".format(x=x, **routes[x]) + else: + # Avoid the extra trailing whitespace for the default route + # because mask is empty in that case. + route_content += "{net} {gw} {mask}\n".format( + **routes[x]).replace(' \n', '\n') + files_to_write[_network_files(distro)["route"] + '-{name}' + .format(name=name)] = route_content + files_to_write[_network_files(distro)["ifcfg"] + '-{name}'.format( name=name)] = results + return files_to_write -def _write_rh_dhcp(name, interface): - filename = '/etc/sysconfig/network-scripts/ifcfg-{name}'.format(name=name) - results = """# Automatically generated, do not edit -DEVICE={name} -BOOTPROTO=dhcp -HWADDR={hwaddr} -ONBOOT=yes -NM_CONTROLLED=no -TYPE=Ethernet -""".format(name=name, hwaddr=interface['mac_address']) - if 'vlan_id' in interface: - results += "VLAN=yes\n" +def _write_rh_dhcp(name, interface, distro): + filename = _network_files(distro)["ifcfg"] + '-{name}'.format(name=name) + results = _network_config(distro)["dhcp"].format( + bootproto="dhcp", 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_manual(name, interface): - filename = '/etc/sysconfig/network-scripts/ifcfg-{name}'.format(name=name) - results = """# Automatically generated, do not edit -DEVICE={name} -BOOTPROTO=none -HWADDR={hwaddr} -ONBOOT=yes -NM_CONTROLLED=no -TYPE=Ethernet -""".format(name=name, hwaddr=interface['mac_address']) - if 'vlan_id' in interface: - results += "VLAN=yes\n" - if 'bond_master' in interface: - results += "SLAVE=yes\n" - results += "MASTER={0}\n".format(interface['bond_master']) +def _write_rh_manual(name, interface, distro): + filename = _network_files(distro)["ifcfg"] + '-{name}'.format(name=name) + results = _network_config(distro)["none"].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_redhat_interfaces(interfaces, sys_interfaces): +def write_redhat_interfaces(interfaces, sys_interfaces, distro): files_to_write = dict() # Sort the interfaces by id so that we'll have consistent output order for iname, interface in sorted( @@ -171,15 +282,25 @@ def write_redhat_interfaces(interfaces, sys_interfaces): else: interface_name = sys_interfaces[interface['mac_address']] + if 'bond_links' in interface: + # We need to keep track of the slave interfaces because + # SUSE configures the slaves on the master ifcfg file + bond_slaves = [] + for phy in interface['raw_macs']: + bond_slaves.append(sys_interfaces[phy]) + interface['bond_slaves'] = bond_slaves + # Remove the 'bond_links' key + interface.pop('bond_links') + if interface['type'] == 'ipv4': files_to_write.update( - _write_rh_interface(interface_name, interface)) + _write_rh_interface(interface_name, interface, distro)) if interface['type'] == 'ipv4_dhcp': files_to_write.update( - _write_rh_dhcp(interface_name, interface)) + _write_rh_dhcp(interface_name, interface, distro)) if interface['type'] == 'manual': files_to_write.update( - _write_rh_manual(interface_name, interface)) + _write_rh_manual(interface_name, interface, distro)) for mac, iname in sorted( sys_interfaces.items(), key=lambda x: x[1]): if _exists_rh_interface(iname): @@ -193,7 +314,8 @@ def write_redhat_interfaces(interfaces, sys_interfaces): # 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})) + files_to_write.update(_write_rh_dhcp(iname, {'mac_address': mac}, + distro)) return files_to_write @@ -612,7 +734,7 @@ def get_config_drive_interfaces(net): for link in bonds.values(): phy_macs = [] - for phy in link.pop('bond_links'): + for phy in link['bond_links']: phy_link = phys[phy] phy_link['bond_master'] = link['id'] if phy in phys: @@ -661,12 +783,12 @@ def write_static_network_info( write_debian_interfaces(interfaces, sys_interfaces)) elif args.distro in ('redhat', 'centos', 'fedora', 'suse', 'opensuse'): files_to_write.update( - write_redhat_interfaces(interfaces, sys_interfaces)) + write_redhat_interfaces(interfaces, sys_interfaces, args.distro)) # glean configures interfaces via # /etc/sysconfig/network-scripts, so we have to ensure that # the LSB init script /etc/init.d/network gets started! - systemd_enable('network.service', args) + systemd_enable(_network_files(args.distro)["systemd"], args) elif args.distro in 'gentoo': files_to_write.update( write_gentoo_interfaces(interfaces, sys_interfaces) diff --git a/glean/tests/fixtures/test/hp.opensuse.network.out b/glean/tests/fixtures/test/hp.opensuse.network.out new file mode 100644 index 0000000..2895d72 --- /dev/null +++ b/glean/tests/fixtures/test/hp.opensuse.network.out @@ -0,0 +1,15 @@ +### Write /etc/sysconfig/network/ifcfg-eth0 +# Automatically generated, do not edit +BOOTPROTO=dhcp +LLADDR=bc:76:4e:01:62:86 +STARTMODE=auto +### Write /etc/sysconfig/network/ifcfg-eth1 +# Automatically generated, do not edit +BOOTPROTO=dhcp +LLADDR=bc:76:4e:05:7b:06 +STARTMODE=auto +### Write /etc/sysconfig/network/ifcfg-eth3 +# Automatically generated, do not edit +BOOTPROTO=dhcp +LLADDR=bc:76:4e:12:a4:bb +STARTMODE=auto diff --git a/glean/tests/fixtures/test/liberty.opensuse.network.out b/glean/tests/fixtures/test/liberty.opensuse.network.out new file mode 100644 index 0000000..a1c3f6e --- /dev/null +++ b/glean/tests/fixtures/test/liberty.opensuse.network.out @@ -0,0 +1,106 @@ +### Write /etc/resolv.conf +nameserver 72.3.128.241 +nameserver 72.3.128.240 +### Write /etc/sysconfig/network/ifcfg-eth0 +# Automatically generated, do not edit +BOOTPROTO=static +LLADDR=bc:76:4e:01:62:86 +IPADDR=23.253.229.154 +NETMASK=255.255.255.0 +STARTMODE=auto +### Write /etc/sysconfig/network/ifroute-eth0 +default 23.253.229.1 +### Write /etc/sysconfig/network/ifcfg-eth1 +# Automatically generated, do not edit +BOOTPROTO=static +LLADDR=bc:76:4e:05:7b:06 +IPADDR=10.208.169.118 +NETMASK=255.255.224.0 +STARTMODE=auto +### Write /etc/sysconfig/network/ifroute-eth1 +10.176.0.0 10.208.160.1 255.240.0.0 +10.208.0.0 10.208.160.1 255.240.0.0 +### Write /etc/sysconfig/network/ifcfg-eth3 +# Automatically generated, do not edit +BOOTPROTO=dhcp +LLADDR=bc:76:4e:12:a4:bb +STARTMODE=auto +### Write /etc/sysconfig/network/ifcfg-eth4.25 +# Automatically generated, do not edit +BOOTPROTO=dhcp +LLADDR=bc:76:4e:12:a4:bc +STARTMODE=auto +VLAN_ID=25 +ETHERDEVICE=eth4 +### Write /etc/sysconfig/network/ifcfg-eth4.26 +# Automatically generated, do not edit +BOOTPROTO=dhcp +LLADDR=bc:76:4e:12:a4:bd +STARTMODE=auto +VLAN_ID=26 +ETHERDEVICE=eth4 +### Write /etc/sysconfig/network/ifcfg-eth5 +# Automatically generated, do not edit +BOOTPROTO=none +LLADDR=bc:76:4e:05:7b:13 +STARTMODE=hotplug +### Write /etc/sysconfig/network/ifcfg-eth6 +# Automatically generated, do not edit +BOOTPROTO=none +LLADDR=bc:76:4e:05:7b:14 +STARTMODE=hotplug +### Write /etc/sysconfig/network/ifcfg-bond0 +# Automatically generated, do not edit +BOOTPROTO=dhcp +LLADDR=bc:76:4e:05:7b:13 +STARTMODE=auto +BONDING_MASTER=yes +BONDING_SLAVE_0=eth5 +BONDING_SLAVE_1=eth6 +### Write /etc/sysconfig/network/ifcfg-eth7 +# Automatically generated, do not edit +BOOTPROTO=none +LLADDR=bc:76:4e:05:7b:15 +STARTMODE=hotplug +### Write /etc/sysconfig/network/ifcfg-eth8 +# Automatically generated, do not edit +BOOTPROTO=none +LLADDR=bc:76:4e:05:7b:16 +STARTMODE=hotplug +### Write /etc/sysconfig/network/ifcfg-bond1 +# Automatically generated, do not edit +BOOTPROTO=none +LLADDR=bc:76:4e:05:7b:15 +STARTMODE=auto +BONDING_MASTER=yes +BONDING_SLAVE_0=eth7 +BONDING_SLAVE_1=eth8 +### Write /etc/sysconfig/network/ifcfg-bond1.27 +# Automatically generated, do not edit +BOOTPROTO=dhcp +LLADDR=bc:76:4e:12:a4:be +STARTMODE=auto +VLAN_ID=27 +ETHERDEVICE=bond1 +### Write /etc/sysconfig/network/ifcfg-eth9 +# Automatically generated, do not edit +BOOTPROTO=none +LLADDR=bc:76:4e:05:7b:17 +STARTMODE=hotplug +### Write /etc/sysconfig/network/ifcfg-eth10 +# Automatically generated, do not edit +BOOTPROTO=none +LLADDR=bc:76:4e:05:7b:18 +STARTMODE=hotplug +### Write /etc/sysconfig/network/ifcfg-bond2 +# Automatically generated, do not edit +BOOTPROTO=static +LLADDR=bc:76:4e:05:7b:17 +IPADDR=192.0.2.2 +NETMASK=255.255.255.0 +STARTMODE=auto +BONDING_MASTER=yes +BONDING_SLAVE_0=eth9 +BONDING_SLAVE_1=eth10 +### Write /etc/sysconfig/network/ifroute-bond2 +default 192.0.2.1 diff --git a/glean/tests/fixtures/test/nokey.opensuse.network.out b/glean/tests/fixtures/test/nokey.opensuse.network.out new file mode 100644 index 0000000..2895d72 --- /dev/null +++ b/glean/tests/fixtures/test/nokey.opensuse.network.out @@ -0,0 +1,15 @@ +### Write /etc/sysconfig/network/ifcfg-eth0 +# Automatically generated, do not edit +BOOTPROTO=dhcp +LLADDR=bc:76:4e:01:62:86 +STARTMODE=auto +### Write /etc/sysconfig/network/ifcfg-eth1 +# Automatically generated, do not edit +BOOTPROTO=dhcp +LLADDR=bc:76:4e:05:7b:06 +STARTMODE=auto +### Write /etc/sysconfig/network/ifcfg-eth3 +# Automatically generated, do not edit +BOOTPROTO=dhcp +LLADDR=bc:76:4e:12:a4:bb +STARTMODE=auto diff --git a/glean/tests/fixtures/test/rax-iad.opensuse.network.out b/glean/tests/fixtures/test/rax-iad.opensuse.network.out new file mode 100644 index 0000000..05deeac --- /dev/null +++ b/glean/tests/fixtures/test/rax-iad.opensuse.network.out @@ -0,0 +1,22 @@ +### Write /etc/resolv.conf +nameserver 69.20.0.196 +nameserver 69.20.0.164 +### Write /etc/sysconfig/network/ifcfg-eth0 +# Automatically generated, do not edit +BOOTPROTO=static +LLADDR=bc:76:4e:20:d7:2f +IPADDR=146.20.110.113 +NETMASK=255.255.255.0 +STARTMODE=auto +### Write /etc/sysconfig/network/ifroute-eth0 +default 146.20.110.1 +### Write /etc/sysconfig/network/ifcfg-eth1 +# Automatically generated, do not edit +BOOTPROTO=static +LLADDR=bc:76:4e:20:d7:33 +IPADDR=10.210.32.174 +NETMASK=255.255.224.0 +STARTMODE=auto +### Write /etc/sysconfig/network/ifroute-eth1 +10.176.0.0 10.210.32.1 255.240.0.0 +10.208.0.0 10.210.32.1 255.240.0.0 diff --git a/glean/tests/fixtures/test/rax.opensuse.network.out b/glean/tests/fixtures/test/rax.opensuse.network.out new file mode 100644 index 0000000..6d4ada2 --- /dev/null +++ b/glean/tests/fixtures/test/rax.opensuse.network.out @@ -0,0 +1,27 @@ +### Write /etc/resolv.conf +nameserver 72.3.128.241 +nameserver 72.3.128.240 +### Write /etc/sysconfig/network/ifcfg-eth0 +# Automatically generated, do not edit +BOOTPROTO=static +LLADDR=bc:76:4e:01:62:86 +IPADDR=23.253.229.154 +NETMASK=255.255.255.0 +STARTMODE=auto +### Write /etc/sysconfig/network/ifcfg-eth1 +# Automatically generated, do not edit +BOOTPROTO=static +LLADDR=bc:76:4e:05:7b:06 +IPADDR=10.208.169.118 +NETMASK=255.255.224.0 +STARTMODE=auto +### Write /etc/sysconfig/network/ifroute-eth1 +10.176.0.0 10.208.160.1 255.240.0.0 +10.208.0.0 10.208.160.1 255.240.0.0 +### Write /etc/sysconfig/network/ifcfg-eth3 +# Automatically generated, do not edit +BOOTPROTO=dhcp +LLADDR=bc:76:4e:12:a4:bb +STARTMODE=auto +### Write /etc/sysconfig/network/ifroute-eth0 +default 23.253.229.1 diff --git a/glean/tests/test_glean.py b/glean/tests/test_glean.py index 6be0833..8ba0ee8 100644 --- a/glean/tests/test_glean.py +++ b/glean/tests/test_glean.py @@ -29,7 +29,8 @@ 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'] +distros = ['Ubuntu', 'Debian', 'Fedora', 'RedHat', 'CentOS', 'Gentoo', + 'openSUSE'] styles = ['hp', 'rax', 'rax-iad', 'liberty', 'nokey'] ips = {'hp': '127.0.1.1', 'rax': '23.253.229.154', @@ -80,7 +81,7 @@ 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/conf.d', '/etc/init.d', '/etc/sysconfig/network') mock_files = ('/etc/resolv.conf', '/etc/hostname', '/etc/hosts') if (path.startswith(mock_dirs) or path in mock_files): try: @@ -124,11 +125,13 @@ class TestGlean(base.BaseTestCase): path = os.path.join(sample_data_path, sample_prefix, path[1:]) if path in ('/etc/sysconfig/network-scripts/ifcfg-eth2', '/etc/network/interfaces.d/eth2.cfg', - '/etc/conf.d/net.eth2'): + '/etc/conf.d/net.eth2', + '/etc/sysconfig/network/ifcfg-eth2'): # 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/'))): # Don't check the host os's network config