Ryan Harper has proposed merging ~raharper/cloud-init:curtin-centos into cloud-init:master.
Requested reviews: cloud-init commiters (cloud-init-dev) Related bugs: Bug #1687725 in cloud-init: "sysconfig render does not support type manual subnets" https://bugs.launchpad.net/cloud-init/+bug/1687725 Bug #1694801 in cloud-init: "sysconfig needs fix for ipv6 gateway routes" https://bugs.launchpad.net/cloud-init/+bug/1694801 Bug #1695092 in cloud-init: "sysconfig only applies subnet/route config to physical interfaces" https://bugs.launchpad.net/cloud-init/+bug/1695092 Bug #1701097 in cloud-init: "eni rendering of ipv6 gateways fails" https://bugs.launchpad.net/cloud-init/+bug/1701097 Bug #1701417 in cloud-init: "cloud-init fails to configure bonding on CentOS 7" https://bugs.launchpad.net/cloud-init/+bug/1701417 Bug #1702513 in cloud-init: "sysconfig should render MTU values from subnets/routes including ipv6" https://bugs.launchpad.net/cloud-init/+bug/1702513 For more details, see: https://code.launchpad.net/~raharper/cloud-init/+git/cloud-init/+merge/327648 WIP branch with changes for el6/el7 networking rendering -- Your team cloud-init commiters is requested to review the proposed merge of ~raharper/cloud-init:curtin-centos into cloud-init:master.
diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index b707146..bb80ec0 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -355,7 +355,7 @@ class Renderer(renderer.Renderer): default_gw = " default gw %s" % route['gateway'] content.append(up + default_gw + or_true) content.append(down + default_gw + or_true) - elif route['network'] == '::' and route['netmask'] == 0: + elif route['network'] == '::' and route['prefix'] == 0: # ipv6! default_gw = " -A inet6 default gw %s" % route['gateway'] content.append(up + default_gw + or_true) diff --git a/cloudinit/net/renderer.py b/cloudinit/net/renderer.py index bba139e..57652e2 100644 --- a/cloudinit/net/renderer.py +++ b/cloudinit/net/renderer.py @@ -20,6 +20,10 @@ def filter_by_name(match_name): return lambda iface: match_name == iface['name'] +def filter_by_attr(match_name): + return lambda iface: (match_name in iface and iface[match_name]) + + filter_by_physical = filter_by_type('physical') diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py index 7ed11d1..95324ea 100644 --- a/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py @@ -151,9 +151,10 @@ class Route(ConfigMap): elif proto == "ipv6" and self.is_ipv6_route(address_value): netmask_value = str(self._conf['NETMASK' + index]) gateway_value = str(self._conf['GATEWAY' + index]) - buf.write("%s/%s via %s\n" % (address_value, - netmask_value, - gateway_value)) + buf.write("%s/%s via %s dev %s\n" % (address_value, + netmask_value, + gateway_value, + self._route_name)) return buf.getvalue() @@ -262,6 +263,9 @@ class Renderer(renderer.Renderer): for (old_key, new_key) in [('mac_address', 'HWADDR'), ('mtu', 'MTU')]: 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'] != 'physical': + continue iface_cfg[new_key] = old_value @classmethod @@ -271,6 +275,7 @@ class Renderer(renderer.Renderer): # 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': iface_cfg['IPV6INIT'] = True @@ -290,7 +295,13 @@ class Renderer(renderer.Renderer): # if iface_cfg['BOOTPROTO'] == 'none': # iface_cfg['BOOTPROTO'] = 'static' if subnet_is_ipv6(subnet): + mtu_key = 'IPV6_MTU' iface_cfg['IPV6INIT'] = True + if 'mtu' in subnet: + iface_cfg[mtu_key] = subnet['mtu'] + + elif subnet_type == 'manual': + iface_cfg['ONBOOT'] = False else: raise ValueError("Unknown subnet type '%s' found" " for interface '%s'" % (subnet_type, @@ -311,7 +322,7 @@ class Renderer(renderer.Renderer): if 'netmask' in subnet and str(subnet['netmask']) != "": ipv6_cidr = (subnet['address'] + '/' + - str(subnet['netmask'])) + str(subnet['prefix'])) else: ipv6_cidr = subnet['address'] if ipv6_index == 0: @@ -329,11 +340,18 @@ class Renderer(renderer.Renderer): iface_cfg['NETMASK' + suff] = \ net_prefix_to_ipv4_mask(subnet['prefix']) + if 'gateway' in subnet: + iface_cfg['DEFROUTE'] = True + if ':' in subnet['gateway']: + iface_cfg['IPV6_DEFAULTGW'] = subnet['gateway'] + else: + iface_cfg['GATEWAY'] = subnet['gateway'] + @classmethod def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets): for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): for route in subnet.get('routes', []): - is_ipv6 = subnet.get('ipv6') + is_ipv6 = subnet.get('ipv6') or ':' in route['gateway'] if _is_default_route(route): if ( @@ -355,7 +373,7 @@ class Renderer(renderer.Renderer): # also provided the default route? iface_cfg['DEFROUTE'] = True if 'gateway' in route: - if is_ipv6: + if is_ipv6 or ':' in route['gateway']: iface_cfg['IPV6_DEFAULTGW'] = route['gateway'] route_cfg.has_set_default_ipv6 = True else: @@ -406,24 +424,42 @@ class Renderer(renderer.Renderer): @classmethod def _render_bond_interfaces(cls, network_state, iface_contents): bond_filter = renderer.filter_by_type('bond') + slave_filter = renderer.filter_by_attr('bond-master') for iface in network_state.iter_interfaces(bond_filter): iface_name = iface['name'] iface_cfg = iface_contents[iface_name] cls._render_bonding_opts(iface_cfg, iface) - iface_master_name = iface['bond-master'] - iface_cfg['MASTER'] = iface_master_name - iface_cfg['SLAVE'] = True + # Ensure that the master interface (and any of its children) # are actually marked as being bond types... - master_cfg = iface_contents[iface_master_name] - master_cfgs = [master_cfg] - master_cfgs.extend(master_cfg.children) + master_cfgs = [iface_cfg] + master_cfgs.extend(iface_cfg.children) for master_cfg in master_cfgs: master_cfg['BONDING_MASTER'] = True master_cfg.kind = 'bond' - @staticmethod - def _render_vlan_interfaces(network_state, iface_contents): + if 'mac_address' in iface and iface.get('mac_address'): + iface_cfg['MACADDR'] = iface.get('mac_address') + + iface_subnets = iface.get("subnets", []) + route_cfg = iface_cfg.routes + cls._render_subnets(iface_cfg, iface_subnets) + cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) + + bond_slaves = sorted([slave_iface['name'] + for slave_iface in + network_state.iter_interfaces(slave_filter) + if slave_iface['bond-master'] == iface_name]) + for index, bond_slave in enumerate(bond_slaves): + 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 + + @classmethod + def _render_vlan_interfaces(cls, network_state, iface_contents): vlan_filter = renderer.filter_by_type('vlan') for iface in network_state.iter_interfaces(vlan_filter): iface_name = iface['name'] @@ -431,6 +467,11 @@ class Renderer(renderer.Renderer): iface_cfg['VLAN'] = True iface_cfg['PHYSDEV'] = iface_name[:iface_name.rfind('.')] + iface_subnets = iface.get("subnets", []) + route_cfg = iface_cfg.routes + cls._render_subnets(iface_cfg, iface_subnets) + cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) + @staticmethod def _render_dns(network_state, existing_dns_path=None): content = resolv_conf.ResolvConf("") @@ -467,6 +508,10 @@ class Renderer(renderer.Renderer): for old_key, new_key in cls.bridge_opts_keys: if old_key in iface: iface_cfg[new_key] = iface[old_key] + + if 'mac_address' in iface and iface.get('mac_address'): + iface_cfg['MACADDR'] = iface.get('mac_address') + # 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 @@ -477,6 +522,11 @@ class Renderer(renderer.Renderer): for bridge_cfg in bridged_cfgs: bridge_cfg['BRIDGE'] = iface_name + iface_subnets = iface.get("subnets", []) + route_cfg = iface_cfg.routes + cls._render_subnets(iface_cfg, iface_subnets) + cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) + @classmethod def _render_sysconfig(cls, base_sysconf_dir, network_state): '''Given state, return /etc/sysconfig files + contents''' diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py index b5a95a1..e02d7a7 100644 --- a/cloudinit/sources/DataSourceAzure.py +++ b/cloudinit/sources/DataSourceAzure.py @@ -442,7 +442,7 @@ class DataSourceAzure(sources.DataSource): self.ds_cfg['agent_command']) try: fabric_data = metadata_func() - except Exception as exc: + except Exception: LOG.warning( "Error communicating with Azure fabric; You may experience." "connectivity issues.", exc_info=True) diff --git a/packages/redhat/cloud-init.spec.in b/packages/redhat/cloud-init.spec.in index 9f75c4b..ab22e0f 100644 --- a/packages/redhat/cloud-init.spec.in +++ b/packages/redhat/cloud-init.spec.in @@ -117,7 +117,8 @@ mkdir -p $RPM_BUILD_ROOT/%{_libexecdir}/%{name} %if "%{init_system}" == "systemd" mkdir -p $RPM_BUILD_ROOT/%{_unitdir} -cp -p systemd/* $RPM_BUILD_ROOT/%{_unitdir} +# only copy targets, .service files are installed by setup.py +cp -p systemd/*.target $RPM_BUILD_ROOT/%{_unitdir} %endif %clean diff --git a/setup.py b/setup.py index bce06ad..fbcad6f 100755 --- a/setup.py +++ b/setup.py @@ -110,14 +110,45 @@ def render_cloud_cfg(): relpath = os.path.join(os.path.basename(tmpd), 'cloud.cfg') return relpath +def render_systemd_unit(template): + """render systemd unit into a tmpdir under same dir as setup.py + + This is rendered to a temporary directory under the top level + directory with the name 'systemd.cfg'. The reason for not just + rendering to systemd/{unit}.service is for a.) don't want to write + over contents in that file if user had something there. b.) debuild + will complain that files are different outside of the debian directory.""" + + # older versions of tox use bdist (xenial), and then install from there. + # newer versions just use install. + if not (sys.argv[1] == 'install' or sys.argv[1].startswith('bdist*')): + return template + + # we may get passed a non-template file, just pass it back + if not template.endswith('.tmpl'): + return template + + topdir = os.path.dirname(sys.argv[0]) + tmpd = tempfile.mkdtemp(dir=topdir) + atexit.register(shutil.rmtree, tmpd) + unit_fname = os.path.basename(template.rstrip('.tmpl')) + fpath = os.path.join(tmpd, unit_fname) + tiny_p([sys.executable, './tools/render-cloudcfg', + template, fpath]) + # relpath is relative to setup.py + relpath = os.path.join(os.path.basename(tmpd), unit_fname) + return relpath + INITSYS_FILES = { 'sysvinit': [f for f in glob('sysvinit/redhat/*') if is_f(f)], 'sysvinit_freebsd': [f for f in glob('sysvinit/freebsd/*') if is_f(f)], 'sysvinit_deb': [f for f in glob('sysvinit/debian/*') if is_f(f)], 'sysvinit_openrc': [f for f in glob('sysvinit/gentoo/*') if is_f(f)], - 'systemd': [f for f in (glob('systemd/*.service') + - glob('systemd/*.target')) if is_f(f)], + 'systemd': [render_systemd_unit(f) + for f in (glob('systemd/*.tmpl') + + glob('systemd/*.service') + + glob('systemd/*.target')) if is_f(f)], 'systemd.generators': [f for f in glob('systemd/*-generator') if is_f(f)], 'upstart': [f for f in glob('upstart/*') if is_f(f)], } diff --git a/systemd/cloud-config.service b/systemd/cloud-config.service deleted file mode 100644 index 3309e08..0000000 --- a/systemd/cloud-config.service +++ /dev/null @@ -1,16 +0,0 @@ -[Unit] -Description=Apply the settings specified in cloud-config -After=network-online.target cloud-config.target -Wants=network-online.target cloud-config.target - -[Service] -Type=oneshot -ExecStart=/usr/bin/cloud-init modules --mode=config -RemainAfterExit=yes -TimeoutSec=0 - -# Output needs to appear in instance console output -StandardOutput=journal+console - -[Install] -WantedBy=cloud-init.target diff --git a/systemd/cloud-config.service.tmpl b/systemd/cloud-config.service.tmpl new file mode 100644 index 0000000..bdee3ce --- /dev/null +++ b/systemd/cloud-config.service.tmpl @@ -0,0 +1,17 @@ +## template:jinja +[Unit] +Description=Apply the settings specified in cloud-config +After=network-online.target cloud-config.target +Wants=network-online.target cloud-config.target + +[Service] +Type=oneshot +ExecStart=/usr/bin/cloud-init modules --mode=config +RemainAfterExit=yes +TimeoutSec=0 + +# Output needs to appear in instance console output +StandardOutput=journal+console + +[Install] +WantedBy=cloud-init.target diff --git a/systemd/cloud-final.service b/systemd/cloud-final.service deleted file mode 100644 index 66f5b8f..0000000 --- a/systemd/cloud-final.service +++ /dev/null @@ -1,18 +0,0 @@ -[Unit] -Description=Execute cloud user/final scripts -After=network-online.target cloud-config.service rc-local.service multi-user.target -Wants=network-online.target cloud-config.service -Before=apt-daily.service - -[Service] -Type=oneshot -ExecStart=/usr/bin/cloud-init modules --mode=final -RemainAfterExit=yes -TimeoutSec=0 -KillMode=process - -# Output needs to appear in instance console output -StandardOutput=journal+console - -[Install] -WantedBy=cloud-init.target diff --git a/systemd/cloud-final.service.tmpl b/systemd/cloud-final.service.tmpl new file mode 100644 index 0000000..fc01b89 --- /dev/null +++ b/systemd/cloud-final.service.tmpl @@ -0,0 +1,22 @@ +## template:jinja +[Unit] +Description=Execute cloud user/final scripts +After=network-online.target cloud-config.service rc-local.service +{% if variant in ["ubuntu", "unknown", "debian"] %} +After=multi-user.target +{% endif %} +Wants=network-online.target cloud-config.service +Before=apt-daily.service + +[Service] +Type=oneshot +ExecStart=/usr/bin/cloud-init modules --mode=final +RemainAfterExit=yes +TimeoutSec=0 +KillMode=process + +# Output needs to appear in instance console output +StandardOutput=journal+console + +[Install] +WantedBy=cloud-init.target diff --git a/systemd/cloud-init-local.service b/systemd/cloud-init-local.service deleted file mode 100644 index 7ee43ed..0000000 --- a/systemd/cloud-init-local.service +++ /dev/null @@ -1,24 +0,0 @@ -[Unit] -Description=Initial cloud-init job (pre-networking) -DefaultDependencies=no -Wants=network-pre.target -After=systemd-remount-fs.service -Before=NetworkManager.service -Before=network-pre.target -Before=shutdown.target -Before=sysinit.target -Conflicts=shutdown.target -RequiresMountsFor=/var/lib/cloud - -[Service] -Type=oneshot -ExecStart=/usr/bin/cloud-init init --local -ExecStart=/bin/touch /run/cloud-init/network-config-ready -RemainAfterExit=yes -TimeoutSec=0 - -# Output needs to appear in instance console output -StandardOutput=journal+console - -[Install] -WantedBy=cloud-init.target diff --git a/systemd/cloud-init-local.service.tmpl b/systemd/cloud-init-local.service.tmpl new file mode 100644 index 0000000..ff9c644 --- /dev/null +++ b/systemd/cloud-init-local.service.tmpl @@ -0,0 +1,29 @@ +## template:jinja +[Unit] +Description=Initial cloud-init job (pre-networking) +{% if variant in ["ubuntu", "unknown", "debian"] %} +DefaultDependencies=no +{% endif %} +Wants=network-pre.target +After=systemd-remount-fs.service +Before=NetworkManager.service +Before=network-pre.target +Before=shutdown.target +{% if variant in ["ubuntu", "unknown", "debian"] %} +Before=sysinit.target +Conflicts=shutdown.target +{% endif %} +RequiresMountsFor=/var/lib/cloud + +[Service] +Type=oneshot +ExecStart=/usr/bin/cloud-init init --local +ExecStart=/bin/touch /run/cloud-init/network-config-ready +RemainAfterExit=yes +TimeoutSec=0 + +# Output needs to appear in instance console output +StandardOutput=journal+console + +[Install] +WantedBy=cloud-init.target diff --git a/systemd/cloud-init.service b/systemd/cloud-init.service deleted file mode 100644 index 39acc20..0000000 --- a/systemd/cloud-init.service +++ /dev/null @@ -1,27 +0,0 @@ -[Unit] -Description=Initial cloud-init job (metadata service crawler) -DefaultDependencies=no -Wants=cloud-init-local.service -Wants=sshd-keygen.service -Wants=sshd.service -After=cloud-init-local.service -After=systemd-networkd-wait-online.service -After=networking.service -Before=network-online.target -Before=sshd-keygen.service -Before=sshd.service -Before=sysinit.target -Before=systemd-user-sessions.service -Conflicts=shutdown.target - -[Service] -Type=oneshot -ExecStart=/usr/bin/cloud-init init -RemainAfterExit=yes -TimeoutSec=0 - -# Output needs to appear in instance console output -StandardOutput=journal+console - -[Install] -WantedBy=cloud-init.target diff --git a/systemd/cloud-init.service.tmpl b/systemd/cloud-init.service.tmpl new file mode 100644 index 0000000..2c71889 --- /dev/null +++ b/systemd/cloud-init.service.tmpl @@ -0,0 +1,35 @@ +## template:jinja +[Unit] +Description=Initial cloud-init job (metadata service crawler) +DefaultDependencies=no +Wants=cloud-init-local.service +Wants=sshd-keygen.service +Wants=sshd.service +After=cloud-init-local.service +After=systemd-networkd-wait-online.service +{% if variant in ["ubuntu", "unknown", "debian"] %} +After=networking.service +{% endif %} +{% if variant in ["centos", "fedora", "redhat"] %} +After=network.service +{% endif %} +Before=network-online.target +Before=sshd-keygen.service +Before=sshd.service +{% if variant in ["ubuntu", "unknown", "debian"] %} +Before=sysinit.target +Conflicts=shutdown.target +{% endif %} +Before=systemd-user-sessions.service + +[Service] +Type=oneshot +ExecStart=/usr/bin/cloud-init init +RemainAfterExit=yes +TimeoutSec=0 + +# Output needs to appear in instance console output +StandardOutput=journal+console + +[Install] +WantedBy=cloud-init.target diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py index 83580cc..2f505d9 100644 --- a/tests/unittests/test_distros/test_netconfig.py +++ b/tests/unittests/test_distros/test_netconfig.py @@ -476,7 +476,9 @@ NETWORKING=yes # Created by cloud-init on instance boot automatically, do not edit. # BOOTPROTO=none +DEFROUTE=yes DEVICE=eth0 +GATEWAY=192.168.1.254 IPADDR=192.168.1.5 NETMASK=255.255.255.0 NM_CONTROLLED=no @@ -625,9 +627,11 @@ IPV6_AUTOCONF=no # Created by cloud-init on instance boot automatically, do not edit. # BOOTPROTO=none +DEFROUTE=yes DEVICE=eth0 IPV6ADDR=2607:f0d0:1002:0011::2/64 IPV6INIT=yes +IPV6_DEFAULTGW=2607:f0d0:1002:0011::1 NM_CONTROLLED=no ONBOOT=yes TYPE=Ethernet diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 06e8f09..1c4f4de 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -422,6 +422,31 @@ NETWORK_CONFIGS = { via: 65.61.151.37 set-name: eth99 """).rstrip(' '), + 'expected_sysconfig': { + 'ifcfg-eth1': textwrap.dedent("""\ + # Created by cloud-init on instance boot automatically, do not edit. + # + BOOTPROTO=none + DEVICE=eth1 + HWADDR=cf:d6:af:48:e8:80 + NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no""").rstrip(' '), + 'ifcfg-eth99': textwrap.dedent("""\ + # Created by cloud-init on instance boot automatically, do not edit. + # + BOOTPROTO=dhcp + DEFROUTE=yes + DEVICE=eth99 + GATEWAY=65.61.151.37 + HWADDR=c0:d6:9f:2c:e8:80 + IPADDR=192.168.21.3 + NETMASK=255.255.255.0 + NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no """).rstrip(' ')}, 'yaml': textwrap.dedent(""" version: 1 config: @@ -541,6 +566,8 @@ iface br0 inet static # control-alias br0 iface br0 inet6 static address 2001:1::1/64 + post-up route add -A inet6 default gw 2001:4800:78ff:1b::1 || true + pre-down route del -A inet6 default gw 2001:4800:78ff:1b::1 || true auto bond0.200 iface bond0.200 inet dhcp @@ -675,6 +702,9 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true eth3: 50 eth4: 75 priority: 22 + routes: + - to: ::/0 + via: 2001:4800:78ff:1b::1 vlans: bond0.200: dhcp4: true @@ -697,6 +727,146 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true - sacchromyces.maas - brettanomyces.maas """).rstrip(' '), + 'expected_sysconfig': { + 'ifcfg-bond0': textwrap.dedent("""\ + # Created by cloud-init on instance boot automatically, do not edit. + # + BONDING_MASTER=yes + BONDING_OPTS="mode=active-backup xmit_hash_policy=layer3+4 miimon=100" + BONDING_SLAVE0=eth1 + BONDING_SLAVE1=eth2 + BOOTPROTO=dhcp + DEVICE=bond0 + DHCPV6C=yes + IPV6INIT=yes + MACADDR=aa:bb:cc:dd:ee:ff + NM_CONTROLLED=no + ONBOOT=yes + TYPE=Bond + USERCTL=no + """).rstrip(' '), + 'ifcfg-bond0.200': textwrap.dedent("""\ + # Created by cloud-init on instance boot automatically, do not edit. + # + BOOTPROTO=dhcp + DEVICE=bond0.200 + NM_CONTROLLED=no + ONBOOT=yes + PHYSDEV=bond0 + TYPE=Ethernet + USERCTL=no + VLAN=yes + """).rstrip(' '), + 'ifcfg-br0': textwrap.dedent("""\ + # Created by cloud-init on instance boot automatically, do not edit. + # + AGEING=250 + BOOTPROTO=none + DEFROUTE=yes + DEVICE=br0 + IPADDR=192.168.14.2 + IPV6ADDR=2001:1::1 + IPV6INIT=yes + IPV6_DEFAULTGW=2001:4800:78ff:1b::1 + NETMASK=255.255.255.0 + NM_CONTROLLED=no + ONBOOT=yes + PRIO=22 + STP=off + TYPE=Bridge + USERCTL=no + """).rstrip(' '), + 'ifcfg-eth0': textwrap.dedent("""\ + # Created by cloud-init on instance boot automatically, do not edit. + # + BOOTPROTO=none + DEVICE=eth0 + HWADDR=c0:d6:9f:2c:e8:80 + NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no + """).rstrip(' '), + 'ifcfg-eth0.101': textwrap.dedent("""\ + # Created by cloud-init on instance boot automatically, do not edit. + # + BOOTPROTO=none + DEFROUTE=yes + DEVICE=eth0.101 + GATEWAY=192.168.0.1 + IPADDR=192.168.0.2 + IPADDR1=192.168.2.10 + MTU=1500 + NETMASK=255.255.255.0 + NETMASK1=255.255.255.0 + NM_CONTROLLED=no + ONBOOT=yes + PHYSDEV=eth0 + TYPE=Ethernet + USERCTL=no + VLAN=yes + """).rstrip(' '), + 'ifcfg-eth1': textwrap.dedent("""\ + # Created by cloud-init on instance boot automatically, do not edit. + # + BOOTPROTO=none + DEVICE=eth1 + HWADDR=aa:d6:9f:2c:e8:80 + MASTER=bond0 + NM_CONTROLLED=no + ONBOOT=yes + SLAVE=yes + TYPE=Ethernet + USERCTL=no + """).rstrip(' '), + 'ifcfg-eth2': textwrap.dedent("""\ + # Created by cloud-init on instance boot automatically, do not edit. + # + BOOTPROTO=none + DEVICE=eth2 + HWADDR=c0:bb:9f:2c:e8:80 + MASTER=bond0 + NM_CONTROLLED=no + ONBOOT=yes + SLAVE=yes + TYPE=Ethernet + USERCTL=no + """).rstrip(' '), + 'ifcfg-eth3': textwrap.dedent("""\ + # Created by cloud-init on instance boot automatically, do not edit. + # + BOOTPROTO=none + BRIDGE=br0 + DEVICE=eth3 + HWADDR=66:bb:9f:2c:e8:80 + NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no + """).rstrip(' '), + 'ifcfg-eth4': textwrap.dedent("""\ + # Created by cloud-init on instance boot automatically, do not edit. + # + BOOTPROTO=none + BRIDGE=br0 + DEVICE=eth4 + HWADDR=98:bb:9f:2c:e8:80 + NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no + """).rstrip(' '), + 'ifcfg-eth5': textwrap.dedent("""\ + # Created by cloud-init on instance boot automatically, do not edit. + # + BOOTPROTO=dhcp + DEVICE=eth5 + HWADDR=98:bb:9f:2c:e8:8a + NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no + """).rstrip(' ')}, 'yaml': textwrap.dedent(""" version: 1 config: @@ -807,6 +977,10 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true address: 192.168.14.2/24 - type: static address: 2001:1::1/64 # default to /64 + routes: + - gateway: 2001:4800:78ff:1b::1 + netmask: '::' + network: '::' # A global nameserver. - type: nameserver address: 8.8.8.8 @@ -828,6 +1002,7 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true } } + CONFIG_V1_EXPLICIT_LOOPBACK = { 'version': 1, 'config': [{'name': 'eth0', 'type': 'physical', @@ -836,6 +1011,18 @@ CONFIG_V1_EXPLICIT_LOOPBACK = { 'subnets': [{'control': 'auto', 'type': 'loopback'}]}, ]} + +CONFIG_V1_SIMPLE_SUBNET = { + 'version': 1, + 'config': [{'mac_address': '52:54:00:12:34:00', + 'name': 'interface0', + 'subnets': [{'address': '10.0.2.15', + 'gateway': '10.0.2.2', + 'netmask': '255.255.255.0', + 'type': 'static'}], + 'type': 'physical'}]} + + DEFAULT_DEV_ATTRS = { 'eth1000': { "bridge": False, @@ -1135,6 +1322,32 @@ USERCTL=no with open(os.path.join(render_dir, fn)) as fh: self.assertEqual(expected_content, fh.read()) + def test_network_config_v1_samples(self): + ns = network_state.parse_net_config_data(CONFIG_V1_SIMPLE_SUBNET) + render_dir = self.tmp_path("render") + os.makedirs(render_dir) + renderer = sysconfig.Renderer() + renderer.render_network_state(ns, render_dir) + found = dir2dict(render_dir) + nspath = '/etc/sysconfig/network-scripts/' + self.assertNotIn(nspath + 'ifcfg-lo', found.keys()) + 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 +IPADDR=10.0.2.15 +NETMASK=255.255.255.0 +NM_CONTROLLED=no +ONBOOT=yes +TYPE=Ethernet +USERCTL=no +""" + self.assertEqual(expected, found[nspath + 'ifcfg-interface0']) + def test_config_with_explicit_loopback(self): ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK) render_dir = self.tmp_path("render") @@ -1654,6 +1867,43 @@ class TestEniRoundTrip(CiTestCase): expected, [line for line in found if line]) +class TestSysconfigRoundTrip(CiTestCase): + def _render_and_read(self, network_config=None, state=None, dir=None): + if dir is None: + dir = self.tmp_dir() + + if network_config: + ns = network_state.parse_net_config_data(network_config) + elif state: + ns = state + else: + raise ValueError("Expected data or state, got neither") + + renderer = sysconfig.Renderer() + renderer.render_network_state(ns, dir) + return dir2dict(dir) + + def testsimple_render_small(self): + entry = NETWORK_CONFIGS['small'] + files = self._render_and_read(network_config=yaml.load(entry['yaml'])) + expected_sysconfig = entry.get('expected_sysconfig') + for ifcfg_name in expected_sysconfig: + expected_ifcfg = expected_sysconfig.get(ifcfg_name) + ifcfg_path = '/etc/sysconfig/network-scripts/' + ifcfg_name + self.assertEqual(expected_ifcfg.splitlines(), + files[ifcfg_path].splitlines()) + + def testsimple_render_all(self): + entry = NETWORK_CONFIGS['all'] + files = self._render_and_read(network_config=yaml.load(entry['yaml'])) + expected_sysconfig = entry.get('expected_sysconfig') + for ifcfg_name in expected_sysconfig: + expected_ifcfg = expected_sysconfig.get(ifcfg_name) + ifcfg_path = '/etc/sysconfig/network-scripts/' + ifcfg_name + self.assertEqual(expected_ifcfg.splitlines(), + files[ifcfg_path].splitlines()) + + class TestNetRenderers(CiTestCase): @mock.patch("cloudinit.net.renderers.sysconfig.available") @mock.patch("cloudinit.net.renderers.eni.available")
_______________________________________________ Mailing list: https://launchpad.net/~cloud-init-dev Post to : cloud-init-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~cloud-init-dev More help : https://help.launchpad.net/ListHelp