diff --git a/SOURCES/ci-sysconfig-distro-specific-config-rendering-for-BOOTP.patch b/SOURCES/ci-sysconfig-distro-specific-config-rendering-for-BOOTP.patch new file mode 100644 index 0000000..6526b19 --- /dev/null +++ b/SOURCES/ci-sysconfig-distro-specific-config-rendering-for-BOOTP.patch @@ -0,0 +1,1900 @@ +From dc7315345b42b6f913191d1b52ed459d3ec5068b Mon Sep 17 00:00:00 2001 +From: Eduardo Otubo +Date: Tue, 23 Feb 2021 12:16:27 +0100 +Subject: [PATCH] sysconfig: distro-specific config rendering for BOOTPROTO + option (#162) + +RH-Author: Eduardo Terrell Ferrari Otubo (eterrell) +RH-MergeRequest: 1: sysconfig: distro-specific config rendering for BOOTPROTO option (#162) +RH-Commit: [1/1] d88e3e93e2d23c8b90191cf6c5d0e8b7733fcce2 (eterrell/cloud-init) +RH-Bugzilla: 1931835 + +commit 06e324ff8edb3126e5a8060757a48ceab2b1a121 +Author: Robert Schweikert +Date: Mon Feb 3 14:56:51 2020 -0500 + + sysconfig: distro-specific config rendering for BOOTPROTO option (#162) + + - Introduce the "flavor" configuration option for the sysconfig renderer + this is necessary to account for differences in the handling of the + BOOTPROTO setting between distributions (lp#1858808) + + Thanks to Petr Pavlu for the idea + - Network config clean up for sysconfig renderer + + The introduction of the "flavor" renderer configuration allows us + to only write values that are pertinent for the given distro + - Set the DHCPv6 client mode on SUSE (lp#1800854) + + Co-authored-by: Chad Smith + + LP: #1800854 + +Signed-off-by: Eduardo Otubo +--- + cloudinit/distros/opensuse.py | 1 + + cloudinit/net/sysconfig.py | 329 +++++++--- + .../unittests/test_distros/test_netconfig.py | 34 +- + tests/unittests/test_net.py | 596 +++++++++++------- + 4 files changed, 592 insertions(+), 368 deletions(-) + +diff --git a/cloudinit/distros/opensuse.py b/cloudinit/distros/opensuse.py +index e41e2f7b..dd56a3f4 100644 +--- a/cloudinit/distros/opensuse.py ++++ b/cloudinit/distros/opensuse.py +@@ -37,6 +37,7 @@ class Distro(distros.Distro): + renderer_configs = { + 'sysconfig': { + 'control': 'etc/sysconfig/network/config', ++ 'flavor': 'suse', + 'iface_templates': '%(base)s/network/ifcfg-%(name)s', + 'netrules_path': ( + 'etc/udev/rules.d/85-persistent-net-cloud-init.rules'), +diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py +index 1989d014..1e8a547e 100644 +--- a/cloudinit/net/sysconfig.py ++++ b/cloudinit/net/sysconfig.py +@@ -1,5 +1,6 @@ + # This file is part of cloud-init. See LICENSE file for license information. + ++import copy + import os + import re + +@@ -86,6 +87,9 @@ class ConfigMap(object): + def __getitem__(self, key): + return self._conf[key] + ++ def get(self, key): ++ return self._conf.get(key) ++ + def __contains__(self, key): + return key in self._conf + +@@ -115,6 +119,9 @@ class ConfigMap(object): + buf.write("%s=%s\n" % (key, _quote_value(value))) + return buf.getvalue() + ++ def update(self, updates): ++ self._conf.update(updates) ++ + + class Route(ConfigMap): + """Represents a route configuration.""" +@@ -281,12 +288,28 @@ class Renderer(renderer.Renderer): + # s1-networkscripts-interfaces.html (or other docs for + # details about this) + +- iface_defaults = tuple([ +- ('ONBOOT', True), +- ('USERCTL', False), +- ('BOOTPROTO', 'none'), +- ('STARTMODE', 'auto'), +- ]) ++ iface_defaults = { ++ 'rhel': {'ONBOOT': True, 'USERCTL': False, 'BOOTPROTO': 'none'}, ++ 'suse': {'BOOTPROTO': 'static', 'STARTMODE': 'auto'}, ++ } ++ ++ cfg_key_maps = { ++ 'rhel': { ++ 'accept-ra': 'IPV6_FORCE_ACCEPT_RA', ++ 'bridge_stp': 'STP', ++ 'bridge_ageing': 'AGEING', ++ 'bridge_bridgeprio': 'PRIO', ++ 'mac_address': 'HWADDR', ++ 'mtu': 'MTU', ++ }, ++ 'suse': { ++ 'bridge_stp': 'BRIDGE_STP', ++ 'bridge_ageing': 'BRIDGE_AGEINGTIME', ++ 'bridge_bridgeprio': 'BRIDGE_PRIORITY', ++ 'mac_address': 'LLADDR', ++ 'mtu': 'MTU', ++ }, ++ } + + # If these keys exist, then their values will be used to form + # a BONDING_OPTS grouping; otherwise no grouping will be set. +@@ -308,12 +331,6 @@ class Renderer(renderer.Renderer): + ('bond_primary_reselect', "primary_reselect=%s"), + ]) + +- bridge_opts_keys = tuple([ +- ('bridge_stp', 'STP'), +- ('bridge_ageing', 'AGEING'), +- ('bridge_bridgeprio', 'PRIO'), +- ]) +- + templates = {} + + def __init__(self, config=None): +@@ -331,65 +348,101 @@ class Renderer(renderer.Renderer): + 'iface_templates': config.get('iface_templates'), + 'route_templates': config.get('route_templates'), + } ++ self.flavor = config.get('flavor', 'rhel') + + @classmethod +- def _render_iface_shared(cls, iface, iface_cfg): +- for k, v in cls.iface_defaults: +- iface_cfg[k] = v ++ def _render_iface_shared(cls, iface, iface_cfg, flavor): ++ flavor_defaults = copy.deepcopy(cls.iface_defaults.get(flavor, {})) ++ iface_cfg.update(flavor_defaults) + +- for (old_key, new_key) in [('mac_address', 'HWADDR'), ('mtu', 'MTU')]: ++ for old_key in ('mac_address', 'mtu', 'accept-ra'): + old_value = iface.get(old_key) + if old_value is not None: + # only set HWADDR on physical interfaces + if (old_key == 'mac_address' and + iface['type'] not in ['physical', 'infiniband']): + continue +- iface_cfg[new_key] = old_value +- +- if iface['accept-ra'] is not None: +- iface_cfg['IPV6_FORCE_ACCEPT_RA'] = iface['accept-ra'] ++ new_key = cls.cfg_key_maps[flavor].get(old_key) ++ if new_key: ++ iface_cfg[new_key] = old_value + + @classmethod +- def _render_subnets(cls, iface_cfg, subnets, has_default_route): ++ def _render_subnets(cls, iface_cfg, subnets, has_default_route, flavor): + # setting base values +- iface_cfg['BOOTPROTO'] = 'none' ++ if flavor == 'suse': ++ iface_cfg['BOOTPROTO'] = 'static' ++ if 'BRIDGE' in iface_cfg: ++ iface_cfg['BOOTPROTO'] = 'dhcp' ++ iface_cfg.drop('BRIDGE') ++ else: ++ iface_cfg['BOOTPROTO'] = 'none' + + # modifying base values according to subnets + for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): + mtu_key = 'MTU' + subnet_type = subnet.get('type') + if subnet_type == 'dhcp6' or subnet_type == 'ipv6_dhcpv6-stateful': +- # TODO need to set BOOTPROTO to dhcp6 on SUSE +- iface_cfg['IPV6INIT'] = True +- # Configure network settings using DHCPv6 +- iface_cfg['DHCPV6C'] = True ++ if flavor == 'suse': ++ # User wants dhcp for both protocols ++ if iface_cfg['BOOTPROTO'] == 'dhcp4': ++ iface_cfg['BOOTPROTO'] = 'dhcp' ++ else: ++ # Only IPv6 is DHCP, IPv4 may be static ++ iface_cfg['BOOTPROTO'] = 'dhcp6' ++ iface_cfg['DHCLIENT6_MODE'] = 'managed' ++ else: ++ iface_cfg['IPV6INIT'] = True ++ # Configure network settings using DHCPv6 ++ iface_cfg['DHCPV6C'] = True + elif subnet_type == 'ipv6_dhcpv6-stateless': +- iface_cfg['IPV6INIT'] = True +- # Configure network settings using SLAAC from RAs and optional +- # info from dhcp server using DHCPv6 +- iface_cfg['IPV6_AUTOCONF'] = True +- iface_cfg['DHCPV6C'] = True +- # Use Information-request to get only stateless configuration +- # parameters (i.e., without address). +- iface_cfg['DHCPV6C_OPTIONS'] = '-S' ++ if flavor == 'suse': ++ # User wants dhcp for both protocols ++ if iface_cfg['BOOTPROTO'] == 'dhcp4': ++ iface_cfg['BOOTPROTO'] = 'dhcp' ++ else: ++ # Only IPv6 is DHCP, IPv4 may be static ++ iface_cfg['BOOTPROTO'] = 'dhcp6' ++ iface_cfg['DHCLIENT6_MODE'] = 'info' ++ else: ++ iface_cfg['IPV6INIT'] = True ++ # Configure network settings using SLAAC from RAs and ++ # optional info from dhcp server using DHCPv6 ++ iface_cfg['IPV6_AUTOCONF'] = True ++ iface_cfg['DHCPV6C'] = True ++ # Use Information-request to get only stateless ++ # configuration parameters (i.e., without address). ++ iface_cfg['DHCPV6C_OPTIONS'] = '-S' + elif subnet_type == 'ipv6_slaac': +- iface_cfg['IPV6INIT'] = True +- # Configure network settings using SLAAC from RAs +- iface_cfg['IPV6_AUTOCONF'] = True ++ if flavor == 'suse': ++ # User wants dhcp for both protocols ++ if iface_cfg['BOOTPROTO'] == 'dhcp4': ++ iface_cfg['BOOTPROTO'] = 'dhcp' ++ else: ++ # Only IPv6 is DHCP, IPv4 may be static ++ iface_cfg['BOOTPROTO'] = 'dhcp6' ++ iface_cfg['DHCLIENT6_MODE'] = 'info' ++ else: ++ iface_cfg['IPV6INIT'] = True ++ # Configure network settings using SLAAC from RAs ++ iface_cfg['IPV6_AUTOCONF'] = True + elif subnet_type in ['dhcp4', 'dhcp']: ++ bootproto_in = iface_cfg['BOOTPROTO'] + iface_cfg['BOOTPROTO'] = 'dhcp' ++ if flavor == 'suse' and subnet_type == 'dhcp4': ++ # If dhcp6 is already specified the user wants dhcp ++ # for both protocols ++ if bootproto_in != 'dhcp6': ++ # Only IPv4 is DHCP, IPv6 may be static ++ iface_cfg['BOOTPROTO'] = 'dhcp4' + elif subnet_type in ['static', 'static6']: ++ # RH info + # grep BOOTPROTO sysconfig.txt -A2 | head -3 + # BOOTPROTO=none|bootp|dhcp + # 'bootp' or 'dhcp' cause a DHCP client + # to run on the device. Any other + # value causes any static configuration + # in the file to be applied. +- # ==> the following should not be set to 'static' +- # but should remain 'none' +- # if iface_cfg['BOOTPROTO'] == 'none': +- # iface_cfg['BOOTPROTO'] = 'static' +- if subnet_is_ipv6(subnet): ++ if subnet_is_ipv6(subnet) and flavor != 'suse': + mtu_key = 'IPV6_MTU' + iface_cfg['IPV6INIT'] = True + if 'mtu' in subnet: +@@ -406,16 +459,21 @@ class Renderer(renderer.Renderer): + iface_cfg['IPV6_FORCE_ACCEPT_RA'] = False + iface_cfg['IPV6_AUTOCONF'] = False + elif subnet_type == 'manual': +- # If the subnet has an MTU setting, then ONBOOT=True +- # to apply the setting +- iface_cfg['ONBOOT'] = mtu_key in iface_cfg ++ if flavor == 'suse': ++ LOG.debug('Unknown subnet type setting "%s"', subnet_type) ++ else: ++ # If the subnet has an MTU setting, then ONBOOT=True ++ # to apply the setting ++ iface_cfg['ONBOOT'] = mtu_key in iface_cfg + else: + raise ValueError("Unknown subnet type '%s' found" + " for interface '%s'" % (subnet_type, + iface_cfg.name)) + if subnet.get('control') == 'manual': +- iface_cfg['ONBOOT'] = False +- iface_cfg['STARTMODE'] = 'manual' ++ if flavor == 'suse': ++ iface_cfg['STARTMODE'] = 'manual' ++ else: ++ iface_cfg['ONBOOT'] = False + + # set IPv4 and IPv6 static addresses + ipv4_index = -1 +@@ -424,13 +482,14 @@ class Renderer(renderer.Renderer): + subnet_type = subnet.get('type') + # metric may apply to both dhcp and static config + if 'metric' in subnet: +- iface_cfg['METRIC'] = subnet['metric'] +- # TODO(hjensas): Including dhcp6 here is likely incorrect. DHCPv6 +- # does not ever provide a default gateway, the default gateway +- # come from RA's. (https://github.com/openSUSE/wicked/issues/570) +- if subnet_type in ['dhcp', 'dhcp4', 'dhcp6']: +- if has_default_route and iface_cfg['BOOTPROTO'] != 'none': +- iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = False ++ if flavor != 'suse': ++ iface_cfg['METRIC'] = subnet['metric'] ++ if subnet_type in ['dhcp', 'dhcp4']: ++ # On SUSE distros 'DHCLIENT_SET_DEFAULT_ROUTE' is a global ++ # setting in /etc/sysconfig/network/dhcp ++ if flavor != 'suse': ++ if has_default_route and iface_cfg['BOOTPROTO'] != 'none': ++ iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = False + continue + elif subnet_type in IPV6_DYNAMIC_TYPES: + continue +@@ -439,14 +498,21 @@ class Renderer(renderer.Renderer): + ipv6_index = ipv6_index + 1 + ipv6_cidr = "%s/%s" % (subnet['address'], subnet['prefix']) + if ipv6_index == 0: +- iface_cfg['IPV6ADDR'] = ipv6_cidr +- iface_cfg['IPADDR6'] = ipv6_cidr ++ if flavor == 'suse': ++ iface_cfg['IPADDR6'] = ipv6_cidr ++ else: ++ iface_cfg['IPV6ADDR'] = ipv6_cidr + elif ipv6_index == 1: +- iface_cfg['IPV6ADDR_SECONDARIES'] = ipv6_cidr +- iface_cfg['IPADDR6_0'] = ipv6_cidr ++ if flavor == 'suse': ++ iface_cfg['IPADDR6_1'] = ipv6_cidr ++ else: ++ iface_cfg['IPV6ADDR_SECONDARIES'] = ipv6_cidr + else: +- iface_cfg['IPV6ADDR_SECONDARIES'] += " " + ipv6_cidr +- iface_cfg['IPADDR6_%d' % ipv6_index] = ipv6_cidr ++ if flavor == 'suse': ++ iface_cfg['IPADDR6_%d' % ipv6_index] = ipv6_cidr ++ else: ++ iface_cfg['IPV6ADDR_SECONDARIES'] += \ ++ " " + ipv6_cidr + else: + ipv4_index = ipv4_index + 1 + suff = "" if ipv4_index == 0 else str(ipv4_index) +@@ -454,17 +520,17 @@ class Renderer(renderer.Renderer): + iface_cfg['NETMASK' + suff] = \ + net_prefix_to_ipv4_mask(subnet['prefix']) + +- if 'gateway' in subnet: ++ if 'gateway' in subnet and flavor != 'suse': + iface_cfg['DEFROUTE'] = True + if is_ipv6_addr(subnet['gateway']): + iface_cfg['IPV6_DEFAULTGW'] = subnet['gateway'] + else: + iface_cfg['GATEWAY'] = subnet['gateway'] + +- if 'dns_search' in subnet: ++ if 'dns_search' in subnet and flavor != 'suse': + iface_cfg['DOMAIN'] = ' '.join(subnet['dns_search']) + +- if 'dns_nameservers' in subnet: ++ if 'dns_nameservers' in subnet and flavor != 'suse': + if len(subnet['dns_nameservers']) > 3: + # per resolv.conf(5) MAXNS sets this to 3. + LOG.debug("%s has %d entries in dns_nameservers. " +@@ -474,7 +540,12 @@ class Renderer(renderer.Renderer): + iface_cfg['DNS' + str(i)] = k + + @classmethod +- def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets): ++ def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets, flavor): ++ # TODO(rjschwei): route configuration on SUSE distro happens via ++ # ifroute-* files, see lp#1812117. SUSE currently carries a local ++ # patch in their package. ++ if flavor == 'suse': ++ return + for _, subnet in enumerate(subnets, start=len(iface_cfg.children)): + subnet_type = subnet.get('type') + for route in subnet.get('routes', []): +@@ -502,14 +573,7 @@ class Renderer(renderer.Renderer): + # TODO(harlowja): add validation that no other iface has + # also provided the default route? + iface_cfg['DEFROUTE'] = True +- # TODO(hjensas): Including dhcp6 here is likely incorrect. +- # DHCPv6 does not ever provide a default gateway, the +- # default gateway come from RA's. +- # (https://github.com/openSUSE/wicked/issues/570) +- if iface_cfg['BOOTPROTO'] in ('dhcp', 'dhcp4', 'dhcp6'): +- # NOTE(hjensas): DHCLIENT_SET_DEFAULT_ROUTE is SuSE +- # only. RHEL, CentOS, Fedora does not implement this +- # option. ++ if iface_cfg['BOOTPROTO'] in ('dhcp', 'dhcp4'): + iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = True + if 'gateway' in route: + if is_ipv6: +@@ -553,7 +617,9 @@ class Renderer(renderer.Renderer): + iface_cfg['BONDING_OPTS'] = " ".join(bond_opts) + + @classmethod +- def _render_physical_interfaces(cls, network_state, iface_contents): ++ def _render_physical_interfaces( ++ cls, network_state, iface_contents, flavor ++ ): + physical_filter = renderer.filter_by_physical + for iface in network_state.iter_interfaces(physical_filter): + iface_name = iface['name'] +@@ -562,12 +628,15 @@ class Renderer(renderer.Renderer): + route_cfg = iface_cfg.routes + + cls._render_subnets( +- iface_cfg, iface_subnets, network_state.has_default_route ++ iface_cfg, iface_subnets, network_state.has_default_route, ++ flavor ++ ) ++ cls._render_subnet_routes( ++ iface_cfg, route_cfg, iface_subnets, flavor + ) +- cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) + + @classmethod +- def _render_bond_interfaces(cls, network_state, iface_contents): ++ def _render_bond_interfaces(cls, network_state, iface_contents, flavor): + bond_filter = renderer.filter_by_type('bond') + slave_filter = renderer.filter_by_attr('bond-master') + for iface in network_state.iter_interfaces(bond_filter): +@@ -581,17 +650,24 @@ class Renderer(renderer.Renderer): + master_cfgs.extend(iface_cfg.children) + for master_cfg in master_cfgs: + master_cfg['BONDING_MASTER'] = True +- master_cfg.kind = 'bond' ++ if flavor != 'suse': ++ master_cfg.kind = 'bond' + + if iface.get('mac_address'): +- iface_cfg['MACADDR'] = iface.get('mac_address') ++ if flavor == 'suse': ++ iface_cfg['LLADDR'] = iface.get('mac_address') ++ else: ++ iface_cfg['MACADDR'] = iface.get('mac_address') + + iface_subnets = iface.get("subnets", []) + route_cfg = iface_cfg.routes + cls._render_subnets( +- iface_cfg, iface_subnets, network_state.has_default_route ++ iface_cfg, iface_subnets, network_state.has_default_route, ++ flavor ++ ) ++ cls._render_subnet_routes( ++ iface_cfg, route_cfg, iface_subnets, flavor + ) +- cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) + + # iter_interfaces on network-state is not sorted to produce + # consistent numbers we need to sort. +@@ -601,15 +677,22 @@ class Renderer(renderer.Renderer): + if slave_iface['bond-master'] == iface_name]) + + for index, bond_slave in enumerate(bond_slaves): +- slavestr = 'BONDING_SLAVE%s' % index ++ if flavor == 'suse': ++ slavestr = 'BONDING_SLAVE_%s' % index ++ else: ++ slavestr = 'BONDING_SLAVE%s' % index + iface_cfg[slavestr] = bond_slave + + slave_cfg = iface_contents[bond_slave] +- slave_cfg['MASTER'] = iface_name +- slave_cfg['SLAVE'] = True ++ if flavor == 'suse': ++ slave_cfg['BOOTPROTO'] = 'none' ++ slave_cfg['STARTMODE'] = 'hotplug' ++ else: ++ slave_cfg['MASTER'] = iface_name ++ slave_cfg['SLAVE'] = True + + @classmethod +- def _render_vlan_interfaces(cls, network_state, iface_contents): ++ def _render_vlan_interfaces(cls, network_state, iface_contents, flavor): + vlan_filter = renderer.filter_by_type('vlan') + for iface in network_state.iter_interfaces(vlan_filter): + iface_name = iface['name'] +@@ -629,9 +712,12 @@ class Renderer(renderer.Renderer): + iface_subnets = iface.get("subnets", []) + route_cfg = iface_cfg.routes + cls._render_subnets( +- iface_cfg, iface_subnets, network_state.has_default_route ++ iface_cfg, iface_subnets, network_state.has_default_route, ++ flavor ++ ) ++ cls._render_subnet_routes( ++ iface_cfg, route_cfg, iface_subnets, flavor + ) +- cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) + + @staticmethod + def _render_dns(network_state, existing_dns_path=None): +@@ -668,19 +754,39 @@ class Renderer(renderer.Renderer): + return out + + @classmethod +- def _render_bridge_interfaces(cls, network_state, iface_contents): ++ def _render_bridge_interfaces(cls, network_state, iface_contents, flavor): ++ bridge_key_map = { ++ old_k: new_k for old_k, new_k in cls.cfg_key_maps[flavor].items() ++ if old_k.startswith('bridge')} + bridge_filter = renderer.filter_by_type('bridge') ++ + for iface in network_state.iter_interfaces(bridge_filter): + iface_name = iface['name'] + iface_cfg = iface_contents[iface_name] +- iface_cfg.kind = 'bridge' +- for old_key, new_key in cls.bridge_opts_keys: ++ if flavor != 'suse': ++ iface_cfg.kind = 'bridge' ++ for old_key, new_key in bridge_key_map.items(): + if old_key in iface: + iface_cfg[new_key] = iface[old_key] + +- if iface.get('mac_address'): +- iface_cfg['MACADDR'] = iface.get('mac_address') ++ if flavor == 'suse': ++ if 'BRIDGE_STP' in iface_cfg: ++ if iface_cfg.get('BRIDGE_STP'): ++ iface_cfg['BRIDGE_STP'] = 'on' ++ else: ++ iface_cfg['BRIDGE_STP'] = 'off' + ++ if iface.get('mac_address'): ++ key = 'MACADDR' ++ if flavor == 'suse': ++ key = 'LLADDRESS' ++ iface_cfg[key] = iface.get('mac_address') ++ ++ if flavor == 'suse': ++ if iface.get('bridge_ports', []): ++ iface_cfg['BRIDGE_PORTS'] = '%s' % " ".join( ++ iface.get('bridge_ports') ++ ) + # Is this the right key to get all the connected interfaces? + for bridged_iface_name in iface.get('bridge_ports', []): + # Ensure all bridged interfaces are correctly tagged +@@ -689,17 +795,23 @@ class Renderer(renderer.Renderer): + bridged_cfgs = [bridged_cfg] + bridged_cfgs.extend(bridged_cfg.children) + for bridge_cfg in bridged_cfgs: +- bridge_cfg['BRIDGE'] = iface_name ++ bridge_value = iface_name ++ if flavor == 'suse': ++ bridge_value = 'yes' ++ bridge_cfg['BRIDGE'] = bridge_value + + iface_subnets = iface.get("subnets", []) + route_cfg = iface_cfg.routes + cls._render_subnets( +- iface_cfg, iface_subnets, network_state.has_default_route ++ iface_cfg, iface_subnets, network_state.has_default_route, ++ flavor ++ ) ++ cls._render_subnet_routes( ++ iface_cfg, route_cfg, iface_subnets, flavor + ) +- cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) + + @classmethod +- def _render_ib_interfaces(cls, network_state, iface_contents): ++ def _render_ib_interfaces(cls, network_state, iface_contents, flavor): + ib_filter = renderer.filter_by_type('infiniband') + for iface in network_state.iter_interfaces(ib_filter): + iface_name = iface['name'] +@@ -708,12 +820,15 @@ class Renderer(renderer.Renderer): + iface_subnets = iface.get("subnets", []) + route_cfg = iface_cfg.routes + cls._render_subnets( +- iface_cfg, iface_subnets, network_state.has_default_route ++ iface_cfg, iface_subnets, network_state.has_default_route, ++ flavor ++ ) ++ cls._render_subnet_routes( ++ iface_cfg, route_cfg, iface_subnets, flavor + ) +- cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) + + @classmethod +- def _render_sysconfig(cls, base_sysconf_dir, network_state, ++ def _render_sysconfig(cls, base_sysconf_dir, network_state, flavor, + templates=None): + '''Given state, return /etc/sysconfig files + contents''' + if not templates: +@@ -724,13 +839,17 @@ class Renderer(renderer.Renderer): + continue + iface_name = iface['name'] + iface_cfg = NetInterface(iface_name, base_sysconf_dir, templates) +- cls._render_iface_shared(iface, iface_cfg) ++ if flavor == 'suse': ++ iface_cfg.drop('DEVICE') ++ # If type detection fails it is considered a bug in SUSE ++ iface_cfg.drop('TYPE') ++ cls._render_iface_shared(iface, iface_cfg, flavor) + iface_contents[iface_name] = iface_cfg +- cls._render_physical_interfaces(network_state, iface_contents) +- cls._render_bond_interfaces(network_state, iface_contents) +- cls._render_vlan_interfaces(network_state, iface_contents) +- cls._render_bridge_interfaces(network_state, iface_contents) +- cls._render_ib_interfaces(network_state, iface_contents) ++ cls._render_physical_interfaces(network_state, iface_contents, flavor) ++ cls._render_bond_interfaces(network_state, iface_contents, flavor) ++ cls._render_vlan_interfaces(network_state, iface_contents, flavor) ++ cls._render_bridge_interfaces(network_state, iface_contents, flavor) ++ cls._render_ib_interfaces(network_state, iface_contents, flavor) + contents = {} + for iface_name, iface_cfg in iface_contents.items(): + if iface_cfg or iface_cfg.children: +@@ -752,7 +871,7 @@ class Renderer(renderer.Renderer): + file_mode = 0o644 + base_sysconf_dir = util.target_path(target, self.sysconf_dir) + for path, data in self._render_sysconfig(base_sysconf_dir, +- network_state, ++ network_state, self.flavor, + templates=templates).items(): + util.write_file(path, data, file_mode) + if self.dns_path: +diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py +index e277bca2..905e8281 100644 +--- a/tests/unittests/test_distros/test_netconfig.py ++++ b/tests/unittests/test_distros/test_netconfig.py +@@ -521,7 +521,6 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase): + NETMASK=255.255.255.0 + NM_CONTROLLED=no + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -530,7 +529,6 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase): + DEVICE=eth1 + NM_CONTROLLED=no + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -549,13 +547,11 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase): + BOOTPROTO=none + DEFROUTE=yes + DEVICE=eth0 +- IPADDR6=2607:f0d0:1002:0011::2/64 + IPV6ADDR=2607:f0d0:1002:0011::2/64 + IPV6INIT=yes + IPV6_DEFAULTGW=2607:f0d0:1002:0011::1 + NM_CONTROLLED=no + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -564,7 +560,6 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase): + DEVICE=eth1 + NM_CONTROLLED=no + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -690,26 +685,14 @@ class TestNetCfgDistroOpensuse(TestNetCfgDistroBase): + """Opensuse uses apply_network_config and renders sysconfig""" + expected_cfgs = { + self.ifcfg_path('eth0'): dedent("""\ +- BOOTPROTO=none +- DEFROUTE=yes +- DEVICE=eth0 +- GATEWAY=192.168.1.254 ++ BOOTPROTO=static + IPADDR=192.168.1.5 + NETMASK=255.255.255.0 +- NM_CONTROLLED=no +- ONBOOT=yes + STARTMODE=auto +- TYPE=Ethernet +- USERCTL=no + """), + self.ifcfg_path('eth1'): dedent("""\ +- BOOTPROTO=dhcp +- DEVICE=eth1 +- NM_CONTROLLED=no +- ONBOOT=yes ++ BOOTPROTO=dhcp4 + STARTMODE=auto +- TYPE=Ethernet +- USERCTL=no + """), + } + self._apply_and_verify(self.distro.apply_network_config, +@@ -720,9 +703,7 @@ class TestNetCfgDistroOpensuse(TestNetCfgDistroBase): + """Opensuse uses apply_network_config and renders sysconfig w/ipv6""" + expected_cfgs = { + self.ifcfg_path('eth0'): dedent("""\ +- BOOTPROTO=none +- DEFROUTE=yes +- DEVICE=eth0 ++ BOOTPROTO=static + IPADDR6=2607:f0d0:1002:0011::2/64 + IPV6ADDR=2607:f0d0:1002:0011::2/64 + IPV6INIT=yes +@@ -732,17 +713,10 @@ class TestNetCfgDistroOpensuse(TestNetCfgDistroBase): + NM_CONTROLLED=no + ONBOOT=yes + STARTMODE=auto +- TYPE=Ethernet +- USERCTL=no + """), + self.ifcfg_path('eth1'): dedent("""\ +- BOOTPROTO=dhcp +- DEVICE=eth1 +- NM_CONTROLLED=no +- ONBOOT=yes ++ BOOTPROTO=dhcp4 + STARTMODE=auto +- TYPE=Ethernet +- USERCTL=no + """), + } + self._apply_and_verify(self.distro.apply_network_config, +diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py +index b2b7c4b2..7e598411 100644 +--- a/tests/unittests/test_net.py ++++ b/tests/unittests/test_net.py +@@ -489,18 +489,11 @@ OS_SAMPLES = [ + """ + # Created by cloud-init on instance boot automatically, do not edit. + # +-BOOTPROTO=none +-DEFROUTE=yes +-DEVICE=eth0 +-GATEWAY=172.19.3.254 +-HWADDR=fa:16:3e:ed:9a:59 ++BOOTPROTO=static + IPADDR=172.19.1.34 ++LLADDR=fa:16:3e:ed:9a:59 + NETMASK=255.255.252.0 +-NM_CONTROLLED=no +-ONBOOT=yes + STARTMODE=auto +-TYPE=Ethernet +-USERCTL=no + """.lstrip()), + ('etc/resolv.conf', + """ +@@ -531,7 +524,6 @@ HWADDR=fa:16:3e:ed:9a:59 + IPADDR=172.19.1.34 + NETMASK=255.255.252.0 + ONBOOT=yes +-STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """.lstrip()), +@@ -590,20 +582,13 @@ dns = none + """ + # Created by cloud-init on instance boot automatically, do not edit. + # +-BOOTPROTO=none +-DEFROUTE=yes +-DEVICE=eth0 +-GATEWAY=172.19.3.254 +-HWADDR=fa:16:3e:ed:9a:59 ++BOOTPROTO=static + IPADDR=172.19.1.34 + IPADDR1=10.0.0.10 ++LLADDR=fa:16:3e:ed:9a:59 + NETMASK=255.255.252.0 + NETMASK1=255.255.255.0 +-NM_CONTROLLED=no +-ONBOOT=yes + STARTMODE=auto +-TYPE=Ethernet +-USERCTL=no + """.lstrip()), + ('etc/resolv.conf', + """ +@@ -636,7 +621,6 @@ IPADDR1=10.0.0.10 + NETMASK=255.255.252.0 + NETMASK1=255.255.255.0 + ONBOOT=yes +-STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """.lstrip()), +@@ -715,25 +699,14 @@ dns = none + """ + # Created by cloud-init on instance boot automatically, do not edit. + # +-BOOTPROTO=none +-DEFROUTE=yes +-DEVICE=eth0 +-GATEWAY=172.19.3.254 +-HWADDR=fa:16:3e:ed:9a:59 ++BOOTPROTO=static + IPADDR=172.19.1.34 + IPADDR6=2001:DB8::10/64 +-IPADDR6_0=2001:DB9::10/64 ++IPADDR6_1=2001:DB9::10/64 + IPADDR6_2=2001:DB10::10/64 +-IPV6ADDR=2001:DB8::10/64 +-IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64" +-IPV6INIT=yes +-IPV6_DEFAULTGW=2001:DB8::1 ++LLADDR=fa:16:3e:ed:9a:59 + NETMASK=255.255.252.0 +-NM_CONTROLLED=no +-ONBOOT=yes + STARTMODE=auto +-TYPE=Ethernet +-USERCTL=no + """.lstrip()), + ('etc/resolv.conf', + """ +@@ -762,9 +735,6 @@ DEVICE=eth0 + GATEWAY=172.19.3.254 + HWADDR=fa:16:3e:ed:9a:59 + IPADDR=172.19.1.34 +-IPADDR6=2001:DB8::10/64 +-IPADDR6_0=2001:DB9::10/64 +-IPADDR6_2=2001:DB10::10/64 + IPV6ADDR=2001:DB8::10/64 + IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64" + IPV6INIT=yes +@@ -773,7 +743,6 @@ IPV6_DEFAULTGW=2001:DB8::1 + IPV6_FORCE_ACCEPT_RA=no + NETMASK=255.255.252.0 + ONBOOT=yes +-STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """.lstrip()), +@@ -883,13 +852,24 @@ NETWORK_CONFIGS = { + via: 65.61.151.37 + set-name: eth99 + """).rstrip(' '), +- 'expected_sysconfig': { ++ 'expected_sysconfig_opensuse': { ++ 'ifcfg-eth1': textwrap.dedent("""\ ++ BOOTPROTO=static ++ LLADDR=cf:d6:af:48:e8:80 ++ STARTMODE=auto"""), ++ 'ifcfg-eth99': textwrap.dedent("""\ ++ BOOTPROTO=dhcp4 ++ LLADDR=c0:d6:9f:2c:e8:80 ++ IPADDR=192.168.21.3 ++ NETMASK=255.255.255.0 ++ STARTMODE=auto"""), ++ }, ++ 'expected_sysconfig_rhel': { + 'ifcfg-eth1': textwrap.dedent("""\ + BOOTPROTO=none + DEVICE=eth1 + HWADDR=cf:d6:af:48:e8:80 + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no"""), + 'ifcfg-eth99': textwrap.dedent("""\ +@@ -906,7 +886,6 @@ NETWORK_CONFIGS = { + NETMASK=255.255.255.0 + METRIC=10000 + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no"""), + }, +@@ -960,6 +939,12 @@ NETWORK_CONFIGS = { + dhcp4: true + dhcp6: true + """).rstrip(' '), ++ 'expected_sysconfig_opensuse': { ++ 'ifcfg-iface0': textwrap.dedent("""\ ++ BOOTPROTO=dhcp ++ DHCLIENT6_MODE=managed ++ STARTMODE=auto""") ++ }, + 'yaml': textwrap.dedent("""\ + version: 1 + config: +@@ -1010,19 +995,27 @@ NETWORK_CONFIGS = { + address: 2001:1::1/64 + mtu: 1500 + """).rstrip(' '), +- 'expected_sysconfig': { ++ 'expected_sysconfig_opensuse': { ++ 'ifcfg-iface0': textwrap.dedent("""\ ++ BOOTPROTO=static ++ IPADDR=192.168.14.2 ++ IPADDR6=2001:1::1/64 ++ NETMASK=255.255.255.0 ++ STARTMODE=auto ++ MTU=9000 ++ """), ++ }, ++ 'expected_sysconfig_rhel': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=none + DEVICE=iface0 + IPADDR=192.168.14.2 +- IPADDR6=2001:1::1/64 + IPV6ADDR=2001:1::1/64 + IPV6INIT=yes + IPV6_AUTOCONF=no + IPV6_FORCE_ACCEPT_RA=no + NETMASK=255.255.255.0 + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + MTU=9000 +@@ -1030,6 +1023,23 @@ NETWORK_CONFIGS = { + """), + }, + }, ++ 'v6_and_v4': { ++ 'expected_sysconfig_opensuse': { ++ 'ifcfg-iface0': textwrap.dedent("""\ ++ BOOTPROTO=dhcp ++ DHCLIENT6_MODE=managed ++ STARTMODE=auto""") ++ }, ++ 'yaml': textwrap.dedent("""\ ++ version: 1 ++ config: ++ - type: 'physical' ++ name: 'iface0' ++ subnets: ++ - type: dhcp6 ++ - type: dhcp4 ++ """).rstrip(' '), ++ }, + 'dhcpv6_only': { + 'expected_eni': textwrap.dedent("""\ + auto lo +@@ -1053,7 +1063,14 @@ NETWORK_CONFIGS = { + subnets: + - {'type': 'dhcp6'} + """).rstrip(' '), +- 'expected_sysconfig': { ++ 'expected_sysconfig_opensuse': { ++ 'ifcfg-iface0': textwrap.dedent("""\ ++ BOOTPROTO=dhcp6 ++ DHCLIENT6_MODE=managed ++ STARTMODE=auto ++ """), ++ }, ++ 'expected_sysconfig_rhel': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=none + DEVICE=iface0 +@@ -1062,7 +1079,6 @@ NETWORK_CONFIGS = { + DEVICE=iface0 + NM_CONTROLLED=no + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -1101,7 +1117,14 @@ NETWORK_CONFIGS = { + dhcp6: true + accept-ra: true + """).rstrip(' '), +- 'expected_sysconfig': { ++ 'expected_sysconfig_opensuse': { ++ 'ifcfg-iface0': textwrap.dedent("""\ ++ BOOTPROTO=dhcp6 ++ DHCLIENT6_MODE=managed ++ STARTMODE=auto ++ """), ++ }, ++ 'expected_sysconfig_rhel': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=none + DEVICE=iface0 +@@ -1111,7 +1134,6 @@ NETWORK_CONFIGS = { + DEVICE=iface0 + NM_CONTROLLED=no + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -1150,7 +1172,14 @@ NETWORK_CONFIGS = { + dhcp6: true + accept-ra: false + """).rstrip(' '), +- 'expected_sysconfig': { ++ 'expected_sysconfig_opensuse': { ++ 'ifcfg-iface0': textwrap.dedent("""\ ++ BOOTPROTO=dhcp6 ++ DHCLIENT6_MODE=managed ++ STARTMODE=auto ++ """), ++ }, ++ 'expected_sysconfig_rhel': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=none + DEVICE=iface0 +@@ -1160,7 +1189,6 @@ NETWORK_CONFIGS = { + DEVICE=iface0 + NM_CONTROLLED=no + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -1190,7 +1218,14 @@ NETWORK_CONFIGS = { + subnets: + - {'type': 'ipv6_slaac'} + """).rstrip(' '), +- 'expected_sysconfig': { ++ 'expected_sysconfig_opensuse': { ++ 'ifcfg-iface0': textwrap.dedent("""\ ++ BOOTPROTO=dhcp6 ++ DHCLIENT6_MODE=info ++ STARTMODE=auto ++ """), ++ }, ++ 'expected_sysconfig_rhel': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=none + DEVICE=iface0 +@@ -1199,7 +1234,6 @@ NETWORK_CONFIGS = { + DEVICE=iface0 + NM_CONTROLLED=no + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -1256,7 +1290,14 @@ NETWORK_CONFIGS = { + subnets: + - {'type': 'ipv6_dhcpv6-stateless'} + """).rstrip(' '), +- 'expected_sysconfig': { ++ 'expected_sysconfig_opensuse': { ++ 'ifcfg-iface0': textwrap.dedent("""\ ++ BOOTPROTO=dhcp6 ++ DHCLIENT6_MODE=info ++ STARTMODE=auto ++ """), ++ }, ++ 'expected_sysconfig_rhel': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=none + DEVICE=iface0 +@@ -1267,7 +1308,6 @@ NETWORK_CONFIGS = { + DEVICE=iface0 + NM_CONTROLLED=no + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -1298,7 +1338,14 @@ NETWORK_CONFIGS = { + - {'type': 'ipv6_dhcpv6-stateful'} + accept-ra: true + """).rstrip(' '), +- 'expected_sysconfig': { ++ 'expected_sysconfig_opensuse': { ++ 'ifcfg-iface0': textwrap.dedent("""\ ++ BOOTPROTO=dhcp6 ++ DHCLIENT6_MODE=managed ++ STARTMODE=auto ++ """), ++ }, ++ 'expected_sysconfig_rhel': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=none + DEVICE=iface0 +@@ -1308,7 +1355,6 @@ NETWORK_CONFIGS = { + DEVICE=iface0 + NM_CONTROLLED=no + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -1503,7 +1549,80 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + - sacchromyces.maas + - brettanomyces.maas + """).rstrip(' '), +- 'expected_sysconfig': { ++ 'expected_sysconfig_opensuse': { ++ 'ifcfg-bond0': textwrap.dedent("""\ ++ BONDING_MASTER=yes ++ BONDING_OPTS="mode=active-backup """ ++ """xmit_hash_policy=layer3+4 """ ++ """miimon=100" ++ BONDING_SLAVE_0=eth1 ++ BONDING_SLAVE_1=eth2 ++ BOOTPROTO=dhcp6 ++ DHCLIENT6_MODE=managed ++ LLADDR=aa:bb:cc:dd:ee:ff ++ STARTMODE=auto"""), ++ 'ifcfg-bond0.200': textwrap.dedent("""\ ++ BOOTPROTO=dhcp4 ++ ETHERDEVICE=bond0 ++ STARTMODE=auto ++ VLAN_ID=200"""), ++ 'ifcfg-br0': textwrap.dedent("""\ ++ BRIDGE_AGEINGTIME=250 ++ BOOTPROTO=static ++ IPADDR=192.168.14.2 ++ IPADDR6=2001:1::1/64 ++ LLADDRESS=bb:bb:bb:bb:bb:aa ++ NETMASK=255.255.255.0 ++ BRIDGE_PRIORITY=22 ++ BRIDGE_PORTS='eth3 eth4' ++ STARTMODE=auto ++ BRIDGE_STP=off"""), ++ 'ifcfg-eth0': textwrap.dedent("""\ ++ BOOTPROTO=static ++ LLADDR=c0:d6:9f:2c:e8:80 ++ STARTMODE=auto"""), ++ 'ifcfg-eth0.101': textwrap.dedent("""\ ++ BOOTPROTO=static ++ IPADDR=192.168.0.2 ++ IPADDR1=192.168.2.10 ++ MTU=1500 ++ NETMASK=255.255.255.0 ++ NETMASK1=255.255.255.0 ++ ETHERDEVICE=eth0 ++ STARTMODE=auto ++ VLAN_ID=101"""), ++ 'ifcfg-eth1': textwrap.dedent("""\ ++ BOOTPROTO=none ++ LLADDR=aa:d6:9f:2c:e8:80 ++ STARTMODE=hotplug"""), ++ 'ifcfg-eth2': textwrap.dedent("""\ ++ BOOTPROTO=none ++ LLADDR=c0:bb:9f:2c:e8:80 ++ STARTMODE=hotplug"""), ++ 'ifcfg-eth3': textwrap.dedent("""\ ++ BOOTPROTO=static ++ BRIDGE=yes ++ LLADDR=66:bb:9f:2c:e8:80 ++ STARTMODE=auto"""), ++ 'ifcfg-eth4': textwrap.dedent("""\ ++ BOOTPROTO=static ++ BRIDGE=yes ++ LLADDR=98:bb:9f:2c:e8:80 ++ STARTMODE=auto"""), ++ 'ifcfg-eth5': textwrap.dedent("""\ ++ BOOTPROTO=dhcp ++ LLADDR=98:bb:9f:2c:e8:8a ++ STARTMODE=manual"""), ++ 'ifcfg-ib0': textwrap.dedent("""\ ++ BOOTPROTO=static ++ LLADDR=a0:00:02:20:fe:80:00:00:00:00:00:00:ec:0d:9a:03:00:15:e2:c1 ++ IPADDR=192.168.200.7 ++ MTU=9000 ++ NETMASK=255.255.255.0 ++ STARTMODE=auto ++ TYPE=InfiniBand"""), ++ }, ++ 'expected_sysconfig_rhel': { + 'ifcfg-bond0': textwrap.dedent("""\ + BONDING_MASTER=yes + BONDING_OPTS="mode=active-backup """ +@@ -1517,7 +1636,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + IPV6INIT=yes + MACADDR=aa:bb:cc:dd:ee:ff + ONBOOT=yes +- STARTMODE=auto + TYPE=Bond + USERCTL=no"""), + 'ifcfg-bond0.200': textwrap.dedent("""\ +@@ -1527,6 +1645,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + ONBOOT=yes + PHYSDEV=bond0 + STARTMODE=auto ++ TYPE=Ethernet + USERCTL=no + VLAN=yes"""), + 'ifcfg-br0': textwrap.dedent("""\ +@@ -1535,7 +1654,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + DEFROUTE=yes + DEVICE=br0 + IPADDR=192.168.14.2 +- IPADDR6=2001:1::1/64 + IPV6ADDR=2001:1::1/64 + IPV6INIT=yes + IPV6_AUTOCONF=no +@@ -1545,7 +1663,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + NETMASK=255.255.255.0 + ONBOOT=yes + PRIO=22 +- STARTMODE=auto + STP=no + TYPE=Bridge + USERCTL=no"""), +@@ -1554,7 +1671,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + DEVICE=eth0 + HWADDR=c0:d6:9f:2c:e8:80 + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no"""), + 'ifcfg-eth0.101': textwrap.dedent("""\ +@@ -1573,6 +1689,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + ONBOOT=yes + PHYSDEV=eth0 + STARTMODE=auto ++ TYPE=Ethernet + USERCTL=no + VLAN=yes"""), + 'ifcfg-eth1': textwrap.dedent("""\ +@@ -1581,7 +1698,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + HWADDR=aa:d6:9f:2c:e8:80 + MASTER=bond0 + ONBOOT=yes +- STARTMODE=auto + SLAVE=yes + TYPE=Ethernet + USERCTL=no"""), +@@ -1591,7 +1707,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + HWADDR=c0:bb:9f:2c:e8:80 + MASTER=bond0 + ONBOOT=yes +- STARTMODE=auto + SLAVE=yes + TYPE=Ethernet + USERCTL=no"""), +@@ -1601,7 +1716,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + DEVICE=eth3 + HWADDR=66:bb:9f:2c:e8:80 + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no"""), + 'ifcfg-eth4': textwrap.dedent("""\ +@@ -1610,7 +1724,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + DEVICE=eth4 + HWADDR=98:bb:9f:2c:e8:80 + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no"""), + 'ifcfg-eth5': textwrap.dedent("""\ +@@ -1619,7 +1732,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + DHCLIENT_SET_DEFAULT_ROUTE=no + HWADDR=98:bb:9f:2c:e8:8a + ONBOOT=no +- STARTMODE=manual + TYPE=Ethernet + USERCTL=no"""), + 'ifcfg-ib0': textwrap.dedent("""\ +@@ -1631,7 +1743,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + NETMASK=255.255.255.0 + NM_CONTROLLED=no + ONBOOT=yes +- STARTMODE=auto + TYPE=InfiniBand + USERCTL=no"""), + }, +@@ -2027,58 +2138,29 @@ iface bond0 inet6 static + """fail_over_mac=active """ + """primary=bond0s0 """ + """primary_reselect=always" +- BONDING_SLAVE0=bond0s0 +- BONDING_SLAVE1=bond0s1 +- BOOTPROTO=none +- DEFROUTE=yes +- DEVICE=bond0 +- GATEWAY=192.168.0.1 +- MACADDR=aa:bb:cc:dd:e8:ff ++ BONDING_SLAVE_0=bond0s0 ++ BONDING_SLAVE_1=bond0s1 ++ BOOTPROTO=static ++ LLADDR=aa:bb:cc:dd:e8:ff + IPADDR=192.168.0.2 + IPADDR1=192.168.1.2 + IPADDR6=2001:1::1/92 +- IPV6ADDR=2001:1::1/92 +- IPV6INIT=yes + MTU=9000 + NETMASK=255.255.255.0 + NETMASK1=255.255.255.0 +- NM_CONTROLLED=no +- ONBOOT=yes + STARTMODE=auto +- TYPE=Bond +- USERCTL=no + """), + 'ifcfg-bond0s0': textwrap.dedent("""\ + BOOTPROTO=none +- DEVICE=bond0s0 +- HWADDR=aa:bb:cc:dd:e8:00 +- MASTER=bond0 +- NM_CONTROLLED=no +- ONBOOT=yes +- SLAVE=yes +- STARTMODE=auto +- TYPE=Ethernet +- USERCTL=no +- """), +- 'ifroute-bond0': textwrap.dedent("""\ +- ADDRESS0=10.1.3.0 +- GATEWAY0=192.168.0.3 +- NETMASK0=255.255.255.0 ++ LLADDR=aa:bb:cc:dd:e8:00 ++ STARTMODE=hotplug + """), + 'ifcfg-bond0s1': textwrap.dedent("""\ + BOOTPROTO=none +- DEVICE=bond0s1 +- HWADDR=aa:bb:cc:dd:e8:01 +- MASTER=bond0 +- NM_CONTROLLED=no +- ONBOOT=yes +- SLAVE=yes +- STARTMODE=auto +- TYPE=Ethernet +- USERCTL=no ++ LLADDR=aa:bb:cc:dd:e8:01 ++ STARTMODE=hotplug + """), + }, +- + 'expected_sysconfig_rhel': { + 'ifcfg-bond0': textwrap.dedent("""\ + BONDING_MASTER=yes +@@ -2097,7 +2179,6 @@ iface bond0 inet6 static + MACADDR=aa:bb:cc:dd:e8:ff + IPADDR=192.168.0.2 + IPADDR1=192.168.1.2 +- IPADDR6=2001:1::1/92 + IPV6ADDR=2001:1::1/92 + IPV6INIT=yes + IPV6_AUTOCONF=no +@@ -2106,7 +2187,6 @@ iface bond0 inet6 static + NETMASK=255.255.255.0 + NETMASK1=255.255.255.0 + ONBOOT=yes +- STARTMODE=auto + TYPE=Bond + USERCTL=no + """), +@@ -2117,7 +2197,6 @@ iface bond0 inet6 static + MASTER=bond0 + ONBOOT=yes + SLAVE=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -2139,7 +2218,6 @@ iface bond0 inet6 static + MASTER=bond0 + ONBOOT=yes + SLAVE=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -2170,13 +2248,31 @@ iface bond0 inet6 static + netmask: '::' + network: '::' + """), +- 'expected_sysconfig': { ++ 'expected_sysconfig_opensuse': { ++ # TODO RJS: unknown proper BOOTPROTO setting ask Marius ++ 'ifcfg-en0': textwrap.dedent("""\ ++ BOOTPROTO=static ++ LLADDR=aa:bb:cc:dd:e8:00 ++ STARTMODE=auto"""), ++ 'ifcfg-en0.99': textwrap.dedent("""\ ++ BOOTPROTO=static ++ IPADDR=192.168.2.2 ++ IPADDR1=192.168.1.2 ++ IPADDR6=2001:1::bbbb/96 ++ MTU=2222 ++ NETMASK=255.255.255.0 ++ NETMASK1=255.255.255.0 ++ STARTMODE=auto ++ ETHERDEVICE=en0 ++ VLAN_ID=99 ++ """), ++ }, ++ 'expected_sysconfig_rhel': { + 'ifcfg-en0': textwrap.dedent("""\ + BOOTPROTO=none + DEVICE=en0 + HWADDR=aa:bb:cc:dd:e8:00 + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no"""), + 'ifcfg-en0.99': textwrap.dedent("""\ +@@ -2186,7 +2282,6 @@ iface bond0 inet6 static + GATEWAY=192.168.1.1 + IPADDR=192.168.2.2 + IPADDR1=192.168.1.2 +- IPADDR6=2001:1::bbbb/96 + IPV6ADDR=2001:1::bbbb/96 + IPV6INIT=yes + IPV6_AUTOCONF=no +@@ -2198,6 +2293,7 @@ iface bond0 inet6 static + ONBOOT=yes + PHYSDEV=en0 + STARTMODE=auto ++ TYPE=Ethernet + USERCTL=no + VLAN=yes"""), + }, +@@ -2229,7 +2325,32 @@ iface bond0 inet6 static + subnets: + - type: static + address: 192.168.2.2/24"""), +- 'expected_sysconfig': { ++ 'expected_sysconfig_opensuse': { ++ 'ifcfg-br0': textwrap.dedent("""\ ++ BOOTPROTO=static ++ IPADDR=192.168.2.2 ++ NETMASK=255.255.255.0 ++ STARTMODE=auto ++ BRIDGE_STP=off ++ BRIDGE_PRIORITY=22 ++ BRIDGE_PORTS='eth0 eth1' ++ """), ++ 'ifcfg-eth0': textwrap.dedent("""\ ++ BOOTPROTO=static ++ BRIDGE=yes ++ LLADDR=52:54:00:12:34:00 ++ IPADDR6=2001:1::100/96 ++ STARTMODE=auto ++ """), ++ 'ifcfg-eth1': textwrap.dedent("""\ ++ BOOTPROTO=static ++ BRIDGE=yes ++ LLADDR=52:54:00:12:34:01 ++ IPADDR6=2001:1::101/96 ++ STARTMODE=auto ++ """), ++ }, ++ 'expected_sysconfig_rhel': { + 'ifcfg-br0': textwrap.dedent("""\ + BOOTPROTO=none + DEVICE=br0 +@@ -2237,7 +2358,6 @@ iface bond0 inet6 static + NETMASK=255.255.255.0 + ONBOOT=yes + PRIO=22 +- STARTMODE=auto + STP=no + TYPE=Bridge + USERCTL=no +@@ -2247,14 +2367,12 @@ iface bond0 inet6 static + BRIDGE=br0 + DEVICE=eth0 + HWADDR=52:54:00:12:34:00 +- IPADDR6=2001:1::100/96 + IPV6ADDR=2001:1::100/96 + IPV6INIT=yes + IPV6_AUTOCONF=no + IPV6_FORCE_ACCEPT_RA=no + NM_CONTROLLED=no + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -2263,14 +2381,12 @@ iface bond0 inet6 static + BRIDGE=br0 + DEVICE=eth1 + HWADDR=52:54:00:12:34:01 +- IPADDR6=2001:1::101/96 + IPV6ADDR=2001:1::101/96 + IPV6INIT=yes + IPV6_AUTOCONF=no + IPV6_FORCE_ACCEPT_RA=no + NM_CONTROLLED=no + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -2336,7 +2452,27 @@ iface bond0 inet6 static + macaddress: 52:54:00:12:34:ff + set-name: eth2 + """), +- 'expected_sysconfig': { ++ 'expected_sysconfig_opensuse': { ++ 'ifcfg-eth0': textwrap.dedent("""\ ++ BOOTPROTO=static ++ LLADDR=52:54:00:12:34:00 ++ IPADDR=192.168.1.2 ++ NETMASK=255.255.255.0 ++ STARTMODE=manual ++ """), ++ 'ifcfg-eth1': textwrap.dedent("""\ ++ BOOTPROTO=static ++ LLADDR=52:54:00:12:34:aa ++ MTU=1480 ++ STARTMODE=auto ++ """), ++ 'ifcfg-eth2': textwrap.dedent("""\ ++ BOOTPROTO=static ++ LLADDR=52:54:00:12:34:ff ++ STARTMODE=manual ++ """), ++ }, ++ 'expected_sysconfig_rhel': { + 'ifcfg-eth0': textwrap.dedent("""\ + BOOTPROTO=none + DEVICE=eth0 +@@ -2344,7 +2480,6 @@ iface bond0 inet6 static + IPADDR=192.168.1.2 + NETMASK=255.255.255.0 + ONBOOT=no +- STARTMODE=manual + TYPE=Ethernet + USERCTL=no + """), +@@ -2354,7 +2489,6 @@ iface bond0 inet6 static + HWADDR=52:54:00:12:34:aa + MTU=1480 + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -2363,7 +2497,6 @@ iface bond0 inet6 static + DEVICE=eth2 + HWADDR=52:54:00:12:34:ff + ONBOOT=no +- STARTMODE=manual + TYPE=Ethernet + USERCTL=no + """), +@@ -2694,7 +2827,7 @@ class TestRhelSysConfigRendering(CiTestCase): + header = ('# Created by cloud-init on instance boot automatically, ' + 'do not edit.\n#\n') + +- expected_name = 'expected_sysconfig' ++ expected_name = 'expected_sysconfig_rhel' + + def _get_renderer(self): + distro_cls = distros.fetch('rhel') +@@ -2780,7 +2913,6 @@ BOOTPROTO=dhcp + DEVICE=eth1000 + HWADDR=07-1c-c6-75-a4-be + ONBOOT=yes +-STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """.lstrip() +@@ -2901,7 +3033,6 @@ HWADDR=52:54:00:12:34:00 + IPADDR=10.0.2.15 + NETMASK=255.255.255.0 + ONBOOT=yes +-STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """ +@@ -2933,7 +3064,6 @@ MTU=1500 + NETMASK=255.255.240.0 + NM_CONTROLLED=no + ONBOOT=yes +-STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """ +@@ -2948,7 +3078,6 @@ HWADDR=fa:16:3e:b1:ca:29 + MTU=9000 + NM_CONTROLLED=no + ONBOOT=yes +-STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """ +@@ -2973,7 +3102,6 @@ USERCTL=no + BOOTPROTO=dhcp + DEVICE=eth0 + ONBOOT=yes +-STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """ +@@ -2982,10 +3110,9 @@ USERCTL=no + self.assertEqual(resolvconf_content, found['/etc/resolv.conf']) + + def test_bond_config(self): +- expected_name = 'expected_sysconfig_rhel' + entry = NETWORK_CONFIGS['bond'] + found = self._render_and_read(network_config=yaml.load(entry['yaml'])) +- self._compare_files_to_expected(entry[expected_name], found) ++ self._compare_files_to_expected(entry[self.expected_name], found) + self._assert_headers(found) + + def test_vlan_config(self): +@@ -3228,7 +3355,6 @@ USERCTL=no + GATEWAY=192.168.42.1 + HWADDR=52:54:00:ab:cd:ef + IPADDR=192.168.42.100 +- IPADDR6=2001:db8::100/32 + IPV6ADDR=2001:db8::100/32 + IPV6INIT=yes + IPV6_AUTOCONF=no +@@ -3237,7 +3363,6 @@ USERCTL=no + NETMASK=255.255.255.0 + NM_CONTROLLED=no + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -3263,7 +3388,6 @@ USERCTL=no + DEVICE=eno1 + NM_CONTROLLED=no + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -3277,6 +3401,7 @@ USERCTL=no + ONBOOT=yes + PHYSDEV=eno1 + STARTMODE=auto ++ TYPE=Ethernet + USERCTL=no + VLAN=yes + """) +@@ -3306,7 +3431,6 @@ USERCTL=no + NETMASK=255.255.255.192 + NM_CONTROLLED=no + ONBOOT=yes +- STARTMODE=auto + TYPE=Bond + USERCTL=no + """), +@@ -3318,7 +3442,6 @@ USERCTL=no + NM_CONTROLLED=no + ONBOOT=yes + SLAVE=yes +- STARTMODE=auto + TYPE=Bond + USERCTL=no + """), +@@ -3330,7 +3453,6 @@ USERCTL=no + NM_CONTROLLED=no + ONBOOT=yes + SLAVE=yes +- STARTMODE=auto + TYPE=Bond + USERCTL=no + """) +@@ -3354,7 +3476,6 @@ USERCTL=no + METRIC=100 + NM_CONTROLLED=no + ONBOOT=yes +- STARTMODE=auto + TYPE=Ethernet + USERCTL=no + """), +@@ -3377,7 +3498,7 @@ class TestOpenSuseSysConfigRendering(CiTestCase): + header = ('# Created by cloud-init on instance boot automatically, ' + 'do not edit.\n#\n') + +- expected_name = 'expected_sysconfig' ++ expected_name = 'expected_sysconfig_opensuse' + + def _get_renderer(self): + distro_cls = distros.fetch('opensuse') +@@ -3449,92 +3570,89 @@ class TestOpenSuseSysConfigRendering(CiTestCase): + expected_content = """ + # Created by cloud-init on instance boot automatically, do not edit. + # +-BOOTPROTO=dhcp +-DEVICE=eth1000 +-HWADDR=07-1c-c6-75-a4-be +-NM_CONTROLLED=no +-ONBOOT=yes ++BOOTPROTO=dhcp4 ++LLADDR=07-1c-c6-75-a4-be + STARTMODE=auto +-TYPE=Ethernet +-USERCTL=no + """.lstrip() + self.assertEqual(expected_content, content) + +- def test_multiple_ipv4_default_gateways(self): +- """ValueError is raised when duplicate ipv4 gateways exist.""" +- net_json = { +- "services": [{"type": "dns", "address": "172.19.0.12"}], +- "networks": [{ +- "network_id": "dacd568d-5be6-4786-91fe-750c374b78b4", +- "type": "ipv4", "netmask": "255.255.252.0", +- "link": "tap1a81968a-79", +- "routes": [{ +- "netmask": "0.0.0.0", +- "network": "0.0.0.0", +- "gateway": "172.19.3.254", +- }, { +- "netmask": "0.0.0.0", # A second default gateway +- "network": "0.0.0.0", +- "gateway": "172.20.3.254", +- }], +- "ip_address": "172.19.1.34", "id": "network0" +- }], +- "links": [ +- { +- "ethernet_mac_address": "fa:16:3e:ed:9a:59", +- "mtu": None, "type": "bridge", "id": +- "tap1a81968a-79", +- "vif_id": "1a81968a-797a-400f-8a80-567f997eb93f" +- }, +- ], +- } +- macs = {'fa:16:3e:ed:9a:59': 'eth0'} +- render_dir = self.tmp_dir() +- network_cfg = openstack.convert_net_json(net_json, known_macs=macs) +- ns = network_state.parse_net_config_data(network_cfg, +- skip_broken=False) +- renderer = self._get_renderer() +- with self.assertRaises(ValueError): +- renderer.render_network_state(ns, target=render_dir) +- self.assertEqual([], os.listdir(render_dir)) +- +- def test_multiple_ipv6_default_gateways(self): +- """ValueError is raised when duplicate ipv6 gateways exist.""" +- net_json = { +- "services": [{"type": "dns", "address": "172.19.0.12"}], +- "networks": [{ +- "network_id": "public-ipv6", +- "type": "ipv6", "netmask": "", +- "link": "tap1a81968a-79", +- "routes": [{ +- "gateway": "2001:DB8::1", +- "netmask": "::", +- "network": "::" +- }, { +- "gateway": "2001:DB9::1", +- "netmask": "::", +- "network": "::" +- }], +- "ip_address": "2001:DB8::10", "id": "network1" +- }], +- "links": [ +- { +- "ethernet_mac_address": "fa:16:3e:ed:9a:59", +- "mtu": None, "type": "bridge", "id": +- "tap1a81968a-79", +- "vif_id": "1a81968a-797a-400f-8a80-567f997eb93f" +- }, +- ], +- } +- macs = {'fa:16:3e:ed:9a:59': 'eth0'} +- render_dir = self.tmp_dir() +- network_cfg = openstack.convert_net_json(net_json, known_macs=macs) +- ns = network_state.parse_net_config_data(network_cfg, +- skip_broken=False) +- renderer = self._get_renderer() +- with self.assertRaises(ValueError): +- renderer.render_network_state(ns, target=render_dir) +- self.assertEqual([], os.listdir(render_dir)) ++ # TODO(rjschwei): re-enable test once route writing is implemented ++ # for SUSE distros ++# def test_multiple_ipv4_default_gateways(self): ++# """ValueError is raised when duplicate ipv4 gateways exist.""" ++# net_json = { ++# "services": [{"type": "dns", "address": "172.19.0.12"}], ++# "networks": [{ ++# "network_id": "dacd568d-5be6-4786-91fe-750c374b78b4", ++# "type": "ipv4", "netmask": "255.255.252.0", ++# "link": "tap1a81968a-79", ++# "routes": [{ ++# "netmask": "0.0.0.0", ++# "network": "0.0.0.0", ++# "gateway": "172.19.3.254", ++# }, { ++# "netmask": "0.0.0.0", # A second default gateway ++# "network": "0.0.0.0", ++# "gateway": "172.20.3.254", ++# }], ++# "ip_address": "172.19.1.34", "id": "network0" ++# }], ++# "links": [ ++# { ++# "ethernet_mac_address": "fa:16:3e:ed:9a:59", ++# "mtu": None, "type": "bridge", "id": ++# "tap1a81968a-79", ++# "vif_id": "1a81968a-797a-400f-8a80-567f997eb93f" ++# }, ++# ], ++# } ++# macs = {'fa:16:3e:ed:9a:59': 'eth0'} ++# render_dir = self.tmp_dir() ++# network_cfg = openstack.convert_net_json(net_json, known_macs=macs) ++# ns = network_state.parse_net_config_data(network_cfg, ++# skip_broken=False) ++# renderer = self._get_renderer() ++# with self.assertRaises(ValueError): ++# renderer.render_network_state(ns, target=render_dir) ++# self.assertEqual([], os.listdir(render_dir)) ++# ++# def test_multiple_ipv6_default_gateways(self): ++# """ValueError is raised when duplicate ipv6 gateways exist.""" ++# net_json = { ++# "services": [{"type": "dns", "address": "172.19.0.12"}], ++# "networks": [{ ++# "network_id": "public-ipv6", ++# "type": "ipv6", "netmask": "", ++# "link": "tap1a81968a-79", ++# "routes": [{ ++# "gateway": "2001:DB8::1", ++# "netmask": "::", ++# "network": "::" ++# }, { ++# "gateway": "2001:DB9::1", ++# "netmask": "::", ++# "network": "::" ++# }], ++# "ip_address": "2001:DB8::10", "id": "network1" ++# }], ++# "links": [ ++# { ++# "ethernet_mac_address": "fa:16:3e:ed:9a:59", ++# "mtu": None, "type": "bridge", "id": ++# "tap1a81968a-79", ++# "vif_id": "1a81968a-797a-400f-8a80-567f997eb93f" ++# }, ++# ], ++# } ++# macs = {'fa:16:3e:ed:9a:59': 'eth0'} ++# render_dir = self.tmp_dir() ++# network_cfg = openstack.convert_net_json(net_json, known_macs=macs) ++# ns = network_state.parse_net_config_data(network_cfg, ++# skip_broken=False) ++# renderer = self._get_renderer() ++# with self.assertRaises(ValueError): ++# renderer.render_network_state(ns, target=render_dir) ++# self.assertEqual([], os.listdir(render_dir)) + + def test_openstack_rendering_samples(self): + for os_sample in OS_SAMPLES: +@@ -3567,18 +3685,11 @@ USERCTL=no + expected = """\ + # Created by cloud-init on instance boot automatically, do not edit. + # +-BOOTPROTO=none +-DEFROUTE=yes +-DEVICE=interface0 +-GATEWAY=10.0.2.2 +-HWADDR=52:54:00:12:34:00 ++BOOTPROTO=static + IPADDR=10.0.2.15 ++LLADDR=52:54:00:12:34:00 + NETMASK=255.255.255.0 +-NM_CONTROLLED=no +-ONBOOT=yes + STARTMODE=auto +-TYPE=Ethernet +-USERCTL=no + """ + self.assertEqual(expected, found[nspath + 'ifcfg-interface0']) + # The configuration has no nameserver information make sure we +@@ -3603,12 +3714,7 @@ USERCTL=no + # Created by cloud-init on instance boot automatically, do not edit. + # + BOOTPROTO=dhcp +-DEVICE=eth0 +-NM_CONTROLLED=no +-ONBOOT=yes + STARTMODE=auto +-TYPE=Ethernet +-USERCTL=no + """ + self.assertEqual(expected, found[nspath + 'ifcfg-eth0']) + # a dhcp only config should not modify resolv.conf +@@ -3679,6 +3785,30 @@ USERCTL=no + self._compare_files_to_expected(entry[self.expected_name], found) + self._assert_headers(found) + ++ def test_simple_render_ipv6_slaac(self): ++ entry = NETWORK_CONFIGS['ipv6_slaac'] ++ found = self._render_and_read(network_config=yaml.load(entry['yaml'])) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ self._assert_headers(found) ++ ++ def test_dhcpv6_stateless_config(self): ++ entry = NETWORK_CONFIGS['dhcpv6_stateless'] ++ found = self._render_and_read(network_config=yaml.load(entry['yaml'])) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ self._assert_headers(found) ++ ++ def test_render_v4_and_v6(self): ++ entry = NETWORK_CONFIGS['v4_and_v6'] ++ found = self._render_and_read(network_config=yaml.load(entry['yaml'])) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ self._assert_headers(found) ++ ++ def test_render_v6_and_v4(self): ++ entry = NETWORK_CONFIGS['v6_and_v4'] ++ found = self._render_and_read(network_config=yaml.load(entry['yaml'])) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ self._assert_headers(found) ++ + + class TestEniNetRendering(CiTestCase): + +-- +2.27.0 + diff --git a/SPECS/cloud-init.spec b/SPECS/cloud-init.spec index 00a198a..c42e1fc 100644 --- a/SPECS/cloud-init.spec +++ b/SPECS/cloud-init.spec @@ -6,7 +6,7 @@ Name: cloud-init Version: 19.4 -Release: 11%{?dist}.2 +Release: 11%{?dist}.3 Summary: Cloud instance init scripts Group: System Environment/Base @@ -66,6 +66,8 @@ Patch27: ci-Explicit-set-IPV6_AUTOCONF-and-IPV6_FORCE_ACCEPT_RA-.patch Patch28: ci-net-fix-rendering-of-static6-in-network-config-77.patch # For bz#1916839 - [Azure] Update existing user password RHEL8x [rhel-8.3.0.z] Patch29: ci-DataSourceAzure-update-password-for-defuser-if-exist.patch +# For bz#1931835 - SUSE specific option, STARTMODE, should not exist in ifcfg-XXX file. [rhel-8.3.0.z] +Patch30: ci-sysconfig-distro-specific-config-rendering-for-BOOTP.patch BuildArch: noarch @@ -249,6 +251,11 @@ fi %config(noreplace) %{_sysconfdir}/rsyslog.d/21-cloudinit.conf %changelog +* Wed Mar 10 2021 Miroslav Rezanina - 19.4-11.el8_3.3 +- ci-sysconfig-distro-specific-config-rendering-for-BOOTP.patch [bz#1931835] +- Resolves: bz#1931835 + (SUSE specific option, STARTMODE, should not exist in ifcfg-XXX file. [rhel-8.3.0.z]) + * Tue Jan 26 2021 Miroslav Rezanina - 19.4-11.el8_3.2 - ci-DataSourceAzure-update-password-for-defuser-if-exist.patch [bz#1916839] - Resolves: bz#1916839