From f7d6eaef7311ac2484d48e67ec69e915b31e16e2 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 15 Apr 2016 12:02:51 -0400 Subject: networking: no longer delete eth0.cfg on debian/ubuntu Ubuntu cloud images in created a file during build that would interfere with cloud-init's discovered or rendered networking. To avoid the issues, cloud-init was deleting /etc/network/interfaces.d/eth0.cfg . The build process no longer creates this file. However, to address any existing files cloud-init will still remove the file if it has known content and warn otherwise. LP: #1563487 --- cloudinit/distros/debian.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/cloudinit/distros/debian.py b/cloudinit/distros/debian.py index 5d7e6cfc..75ab340f 100644 --- a/cloudinit/distros/debian.py +++ b/cloudinit/distros/debian.py @@ -84,7 +84,8 @@ class Distro(distros.Distro): eni=self.network_conf_fn, links_prefix=self.links_prefix, netrules=None) - util.del_file("/etc/network/interfaces.d/eth0.cfg") + _maybe_remove_legacy_eth0() + return [] def _bring_up_interfaces(self, device_names): @@ -193,3 +194,34 @@ def _get_wrapper_prefix(cmd, mode): return cmd else: return [] + + +def _maybe_remove_legacy_eth0(path="/etc/network/interfaces.d/eth0.cfg"): + """Ubuntu cloud images previously included a 'eth0.cfg' that had + hard coded content. That file would interfere with the rendered + configuration if it was present. + + if the file does not exist do nothing. + If the file exists: + - with known content, remove it and warn + - with unknown content, leave it and warn + """ + + if not os.path.exists(path): + return + + bmsg = "Dynamic networking config may not apply." + try: + contents = util.load_file(path) + known_contents = ["auto eth0", "iface eth0 inet dhcp"] + lines = [f.strip() for f in contents.splitlines() + if not f.startswith("#")] + if lines == known_contents: + util.del_file(path) + msg = "removed %s with known contents" % path + else: + msg = (bmsg + " '%s' exists with user configured content." % path) + except: + msg = bmsg + " %s exists, but could not be read." % path + + LOG.warn(msg) -- cgit v1.2.3 From 79b59658dfdc0172818c54e3c7149e6ed914a93b Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 15 Apr 2016 12:16:14 -0400 Subject: only apply networking once per instance This attempts to only apply the networking once per instance by doing so only if the datasource was restored from disk. This will work by default for datasources with a functioning check_instance_id or if the user has set manual_cache_clean to true. --- bin/cloud-init | 4 +++- cloudinit/stages.py | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bin/cloud-init b/bin/cloud-init index 715be4b5..b449ac95 100755 --- a/bin/cloud-init +++ b/bin/cloud-init @@ -265,7 +265,9 @@ def main_init(name, args): else: return (None, ["No instance datasource found."]) - if args.local: + if args.local and not init.ds_restored: + # if local mode and the datasource was not restored from cache + # (this is not first boot) then apply networking. init.apply_network_config() # Stage 6 diff --git a/cloudinit/stages.py b/cloudinit/stages.py index 3fbb4443..ffb15165 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -66,6 +66,7 @@ class Init(object): self._distro = None # Changed only when a fetch occurs self.datasource = NULL_DATA_SOURCE + self.ds_restored = False if reporter is None: reporter = events.ReportEventStack( @@ -80,6 +81,7 @@ class Init(object): self._distro = None if reset_ds: self.datasource = NULL_DATA_SOURCE + self.ds_restored = False @property def distro(self): @@ -231,6 +233,8 @@ class Init(object): ds = None else: myrep.description = "no cache found" + + self.ds_restored = bool(ds) LOG.debug(myrep.description) if not ds: -- cgit v1.2.3 From 53931ee132f0d236e3ed1b7fd6e0e0b519f40bda Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 15 Apr 2016 13:54:05 -0400 Subject: log that you're not applying config --- bin/cloud-init | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bin/cloud-init b/bin/cloud-init index b449ac95..5857af32 100755 --- a/bin/cloud-init +++ b/bin/cloud-init @@ -265,10 +265,13 @@ def main_init(name, args): else: return (None, ["No instance datasource found."]) - if args.local and not init.ds_restored: - # if local mode and the datasource was not restored from cache - # (this is not first boot) then apply networking. - init.apply_network_config() + if args.local: + if not init.ds_restored: + # if local mode and the datasource was not restored from cache + # (this is not first boot) then apply networking. + init.apply_network_config() + else: + LOG.debug("skipping networking config from restored datasource.") # Stage 6 iid = init.instancify() -- cgit v1.2.3 From 14053fc79737f6ec6904021f3f6cdfd262c3b17e Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 15 Apr 2016 14:49:13 -0400 Subject: sync with curtin on render_interfaces This picks up newline cleanup and some bond fixes from curtin at rev 374. - Cleanup newline logic so we always have a clean '\n\n' between stanza - Add a unittest to validate bonding network config render, specifically when to emit auto $iface for dependent bond slaves. --- cloudinit/net/__init__.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py index a765b75a..7a6ec3a4 100644 --- a/cloudinit/net/__init__.py +++ b/cloudinit/net/__init__.py @@ -528,14 +528,16 @@ def render_interfaces(network_state): if len(value): content += " dns-{} {}\n".format(dnskey, " ".join(value)) - content += "\n" for iface in sorted(interfaces.values(), key=lambda k: (order[k['type']], k['name'])): - content += "auto {name}\n".format(**iface) + if content[-2:] != "\n\n": + content += "\n" subnets = iface.get('subnets', {}) if subnets: for index, subnet in zip(range(0, len(subnets)), subnets): + if content[-2:] != "\n\n": + content += "\n" iface['index'] = index iface['mode'] = subnet['type'] if iface['mode'].endswith('6'): @@ -546,27 +548,28 @@ def render_interfaces(network_state): iface['mode'] = 'dhcp' if index == 0: + if subnet['type'] != 'manual': + content += "auto {name}\n".format(**iface) content += "iface {name} {inet} {mode}\n".format(**iface) else: - content += "auto {name}:{index}\n".format(**iface) + if subnet['type'] != 'manual': + content += "auto {name}:{index}\n".format(**iface) content += \ "iface {name}:{index} {inet} {mode}\n".format(**iface) content += iface_add_subnet(iface, subnet) content += iface_add_attrs(iface) - for route in subnet.get('routes', []): - content += render_route(route, indent=" ") - content += "\n" else: + if 'bond-master' in iface: + content += "auto {name}\n".format(**iface) content += "iface {name} {inet} {mode}\n".format(**iface) content += iface_add_attrs(iface) - content += "\n" for route in network_state.get('routes'): content += render_route(route) # global replacements until v2 format - content = content.replace('mac_address', 'hwaddress ether') + content = content.replace('mac_address', 'hwaddress') return content -- cgit v1.2.3 From 5039a08032fc40c9c72afbef90184dbd1af10918 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 15 Apr 2016 15:13:07 -0400 Subject: support and render control=manual on initramfs network devices when reading the initramfs configurewd devices and turning them into network config, we change to not have 'auto' control (or allow=auto). The reason for this is that if the device was still up: a.) it would try to bring it up again (due to bug 1570142) b.) it would be brought down. 'b' is problematic if there is an iscsi or network root filesystem. Note, that ifupdown does now support 'no-auto-down' which means that the nic should not be brought down on 'ifdown -a'. LP: #1568637 --- cloudinit/net/__init__.py | 17 ++++++++++------- tests/unittests/test_net.py | 4 +++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py index 7a6ec3a4..647c595b 100644 --- a/cloudinit/net/__init__.py +++ b/cloudinit/net/__init__.py @@ -350,7 +350,7 @@ def _klibc_to_config_entry(content, mac_addrs=None): # if no IPV4ADDR or IPV6ADDR, then go on. if pre + "ADDR" not in data: continue - subnet = {'type': proto} + subnet = {'type': proto, 'control': 'manual'} # these fields go right on the subnet for key in ('NETMASK', 'BROADCAST', 'GATEWAY'): @@ -444,12 +444,13 @@ def iface_add_subnet(iface, subnet): def iface_add_attrs(iface): content = "" ignore_map = [ - 'type', - 'name', + 'control', + 'index', 'inet', 'mode', - 'index', + 'name', 'subnets', + 'type', ] if iface['type'] not in ['bond', 'bridge', 'vlan']: ignore_map.append('mac_address') @@ -540,6 +541,7 @@ def render_interfaces(network_state): content += "\n" iface['index'] = index iface['mode'] = subnet['type'] + iface['control'] = subnet.get('control', 'auto') if iface['mode'].endswith('6'): iface['inet'] += '6' elif iface['mode'] == 'static' and ":" in subnet['address']: @@ -548,11 +550,11 @@ def render_interfaces(network_state): iface['mode'] = 'dhcp' if index == 0: - if subnet['type'] != 'manual': - content += "auto {name}\n".format(**iface) + if iface['control'] != 'manual': + content += "allow-{control} {name}\n".format(**iface) content += "iface {name} {inet} {mode}\n".format(**iface) else: - if subnet['type'] != 'manual': + if iface['control'] != 'manual': content += "auto {name}:{index}\n".format(**iface) content += \ "iface {name}:{index} {inet} {mode}\n".format(**iface) @@ -560,6 +562,7 @@ def render_interfaces(network_state): content += iface_add_subnet(iface, subnet) content += iface_add_attrs(iface) else: + # ifenslave docs say to auto the slave devices if 'bond-master' in iface: content += "auto {name}\n".format(**iface) content += "iface {name} {inet} {mode}\n".format(**iface) diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index dfb31710..09235c4d 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -33,6 +33,7 @@ DHCP_EXPECTED_1 = { 'name': 'eth0', 'type': 'physical', 'subnets': [{'broadcast': '192.168.122.255', + 'control': 'manual', 'gateway': '192.168.122.1', 'dns_search': ['foo.com'], 'type': 'dhcp', @@ -59,7 +60,8 @@ DOMAINSEARCH='foo.com' STATIC_EXPECTED_1 = { 'name': 'eth1', 'type': 'physical', - 'subnets': [{'broadcast': '10.0.0.255', 'gateway': '10.0.0.1', + 'subnets': [{'broadcast': '10.0.0.255', 'control': 'manual', + 'gateway': '10.0.0.1', 'dns_search': ['foo.com'], 'type': 'static', 'netmask': '255.255.255.0', 'dns_nameservers': ['10.0.1.1']}], -- cgit v1.2.3 From 8384076e77d8fa5e0f6f3639f2dddd33c64236b9 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 15 Apr 2016 16:08:47 -0400 Subject: write 'allow-hotplug', but 'auto' for auto. --- cloudinit/net/__init__.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py index 647c595b..fa5a0033 100644 --- a/cloudinit/net/__init__.py +++ b/cloudinit/net/__init__.py @@ -509,6 +509,26 @@ def render_route(route, indent=""): return content +def iface_start_entry(iface, index): + fullname = iface['name'] + if index != 0: + fullname += ":%s" % index + + control = iface['control'] + if control == "auto": + cverb = "auto" + elif control in ("hotplug",): + cverb = "allow-" + control + else: + cverb = "# control-" + control + + subst = iface.copy() + subst.update({'fullname': fullname, 'cverb': cverb}) + + return ("{cverb} {fullname}\n" + "iface {fullname} {inet} {mode}\n").format(**subst) + + def render_interfaces(network_state): ''' Given state, emit etc/network/interfaces content ''' @@ -549,16 +569,7 @@ def render_interfaces(network_state): if iface['mode'].startswith('dhcp'): iface['mode'] = 'dhcp' - if index == 0: - if iface['control'] != 'manual': - content += "allow-{control} {name}\n".format(**iface) - content += "iface {name} {inet} {mode}\n".format(**iface) - else: - if iface['control'] != 'manual': - content += "auto {name}:{index}\n".format(**iface) - content += \ - "iface {name}:{index} {inet} {mode}\n".format(**iface) - + content += iface_start_entry(iface, index) content += iface_add_subnet(iface, subnet) content += iface_add_attrs(iface) else: -- cgit v1.2.3