diff --git a/SOURCES/0023-DatasourceEc2-add-warning-message-when-not-on-AWS.patch b/SOURCES/0023-DatasourceEc2-add-warning-message-when-not-on-AWS.patch index 89ae3c3..df75290 100644 --- a/SOURCES/0023-DatasourceEc2-add-warning-message-when-not-on-AWS.patch +++ b/SOURCES/0023-DatasourceEc2-add-warning-message-when-not-on-AWS.patch @@ -1,4 +1,4 @@ -From 75ee377f902082f23de0feea190444e19a942420 Mon Sep 17 00:00:00 2001 +From aeaaf3f3b3f426a7906a2e03d924b2e42528c600 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Thu, 23 Feb 2017 17:15:27 -0500 Subject: [PATCH 2/5] DatasourceEc2: add warning message when not on AWS. @@ -8,7 +8,7 @@ will now warn once per instance. (cherry picked from commit 9bb55c6c45bcc5e310cf7e4d42cad53759dcca15) -Resolves: rhbz#1496113 +Resolves: rhbz#1482547 Signed-off-by: Ryan McCabe --- diff --git a/SOURCES/0024-Identify-Brightbox-as-an-Ec2-datasource-user.patch b/SOURCES/0024-Identify-Brightbox-as-an-Ec2-datasource-user.patch index 148e7a1..4cdc8b2 100644 --- a/SOURCES/0024-Identify-Brightbox-as-an-Ec2-datasource-user.patch +++ b/SOURCES/0024-Identify-Brightbox-as-an-Ec2-datasource-user.patch @@ -1,4 +1,4 @@ -From 9044e39b1db9da242c244202ad649c5f8b05bc12 Mon Sep 17 00:00:00 2001 +From dcdb2961701ccb0025c8ab45fe76fbaa27ba8133 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 24 Feb 2017 14:19:20 -0500 Subject: [PATCH 3/5] Identify Brightbox as an Ec2 datasource user. @@ -9,7 +9,7 @@ product serial to a string that ends with 'brightbox.com'. LP: #1661693 (cherry picked from commit 5dd5b2cb539a84ed59f2b3181020d2bd18989718) -Resolves: rhbz#1496113 +Resolves: rhbz#1482547 Signed-off-by: Ryan McCabe --- diff --git a/SOURCES/0025-AliYun-Enable-platform-identification-and-enable-by-.patch b/SOURCES/0025-AliYun-Enable-platform-identification-and-enable-by-.patch index ac01a97..c7f475f 100644 --- a/SOURCES/0025-AliYun-Enable-platform-identification-and-enable-by-.patch +++ b/SOURCES/0025-AliYun-Enable-platform-identification-and-enable-by-.patch @@ -1,4 +1,4 @@ -From a7727ecf117a2bc02f68405823796afe1d76d3e3 Mon Sep 17 00:00:00 2001 +From 05561668e42d905cca7d72c2b80a939fbddb2c9d Mon Sep 17 00:00:00 2001 From: Junjie Wang Date: Fri, 21 Apr 2017 20:06:09 +0800 Subject: [PATCH 4/5] AliYun: Enable platform identification and enable by @@ -14,7 +14,7 @@ enable AliYun by default. LP: #1638931 (cherry picked from commit 4a60af54957634920e84a928aa22b4fc9a6dfd11) -Resolves: rhbz#1496113 +Resolves: rhbz#1482547 Signed-off-by: Ryan McCabe --- diff --git a/SOURCES/0026-Fix-alibaba-cloud-unit-tests-to-work-with-0.7.9.patch b/SOURCES/0026-Fix-alibaba-cloud-unit-tests-to-work-with-0.7.9.patch index 1755547..5e34868 100644 --- a/SOURCES/0026-Fix-alibaba-cloud-unit-tests-to-work-with-0.7.9.patch +++ b/SOURCES/0026-Fix-alibaba-cloud-unit-tests-to-work-with-0.7.9.patch @@ -1,9 +1,9 @@ -From b87c46fe008dc4df50b0103d598d218f8dd26735 Mon Sep 17 00:00:00 2001 +From 1a674be4e78d77ed40e3a07385c4cce617476129 Mon Sep 17 00:00:00 2001 From: Ryan McCabe Date: Tue, 5 Sep 2017 13:02:00 -0400 Subject: [PATCH 5/5] Fix alibaba cloud unit tests to work with 0.7.9 -Resolves: rhbz#1496113 +Resolves: rhbz#1482547 X-downstream-only: Yes Signed-off-by: Ryan McCabe diff --git a/SOURCES/0027-Fix-eni-rendering-of-multiple-IPs-per-interface.patch b/SOURCES/0027-Fix-eni-rendering-of-multiple-IPs-per-interface.patch new file mode 100644 index 0000000..7767342 --- /dev/null +++ b/SOURCES/0027-Fix-eni-rendering-of-multiple-IPs-per-interface.patch @@ -0,0 +1,185 @@ +From 335d2b7270c151fd981d9e500f239ab75a59a4b3 Mon Sep 17 00:00:00 2001 +From: Ryan Harper +Date: Wed, 25 Jan 2017 15:45:40 -0600 +Subject: [PATCH] Fix eni rendering of multiple IPs per interface + +The iface:alias syntax for eni rendering is brittle with ipv6. +Replace it with using multiple iface stanzas with the same iface +name which is supported. Side-effect is that one can no longer +do 'ifup $iface:$alias' but requires instead use of ip address +{add|delete} instead. + +LP: #1657940 +(cherry picked from commit 2de1c247e285cce0b25ab70abdc56ccd41019c27) + +Signed-off-by: Ryan McCabe +Resolves: rhbz#bz1497954 +--- + cloudinit/net/eni.py | 33 ++++++++++++++++++-------------- + tests/unittests/test_net.py | 46 +++++++++++++++++++++++++++++++++------------ + 2 files changed, 53 insertions(+), 26 deletions(-) + +diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py +index b06ffac9..5b249f1f 100644 +--- a/cloudinit/net/eni.py ++++ b/cloudinit/net/eni.py +@@ -90,8 +90,6 @@ def _iface_add_attrs(iface, index): + + def _iface_start_entry(iface, index, render_hwaddress=False): + fullname = iface['name'] +- if index != 0: +- fullname += ":%s" % index + + control = iface['control'] + if control == "auto": +@@ -113,6 +111,16 @@ def _iface_start_entry(iface, index, render_hwaddress=False): + return lines + + ++def _subnet_is_ipv6(subnet): ++ # 'static6' or 'dhcp6' ++ if subnet['type'].endswith('6'): ++ # This is a request for DHCPv6. ++ return True ++ elif subnet['type'] == 'static' and ":" in subnet['address']: ++ return True ++ return False ++ ++ + def _parse_deb_config_data(ifaces, contents, src_dir, src_path): + """Parses the file contents, placing result into ifaces. + +@@ -354,21 +362,23 @@ class Renderer(renderer.Renderer): + sections = [] + subnets = iface.get('subnets', {}) + if subnets: +- for index, subnet in zip(range(0, len(subnets)), subnets): ++ for index, subnet in enumerate(subnets): + iface['index'] = index + iface['mode'] = subnet['type'] + iface['control'] = subnet.get('control', 'auto') + subnet_inet = 'inet' +- if iface['mode'].endswith('6'): +- # This is a request for DHCPv6. +- subnet_inet += '6' +- elif iface['mode'] == 'static' and ":" in subnet['address']: +- # This is a static IPv6 address. ++ if _subnet_is_ipv6(subnet): + subnet_inet += '6' + iface['inet'] = subnet_inet +- if iface['mode'].startswith('dhcp'): ++ if subnet['type'].startswith('dhcp'): + iface['mode'] = 'dhcp' + ++ # do not emit multiple 'auto $IFACE' lines as older (precise) ++ # ifupdown complains ++ if True in ["auto %s" % (iface['name']) in line ++ for line in sections]: ++ iface['control'] = 'alias' ++ + lines = list( + _iface_start_entry( + iface, index, render_hwaddress=render_hwaddress) + +@@ -378,11 +388,6 @@ class Renderer(renderer.Renderer): + for route in subnet.get('routes', []): + lines.extend(self._render_route(route, indent=" ")) + +- if len(subnets) > 1 and index == 0: +- tmpl = " post-up ifup %s:%s\n" +- for i in range(1, len(subnets)): +- lines.append(tmpl % (iface['name'], i)) +- + sections.append(lines) + else: + # ifenslave docs say to auto the slave devices +diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py +index ffa911cc..4c0e3ad3 100644 +--- a/tests/unittests/test_net.py ++++ b/tests/unittests/test_net.py +@@ -376,11 +376,9 @@ NETWORK_CONFIGS = { + + auto eth99 + iface eth99 inet dhcp +- post-up ifup eth99:1 + +- +- auto eth99:1 +- iface eth99:1 inet static ++ # control-alias eth99 ++ iface eth99 inet static + address 192.168.21.3/24 + dns-nameservers 8.8.8.8 8.8.4.4 + dns-search barley.maas sach.maas +@@ -418,6 +416,27 @@ NETWORK_CONFIGS = { + - wark.maas + """), + }, ++ 'v4_and_v6': { ++ 'expected_eni': textwrap.dedent("""\ ++ auto lo ++ iface lo inet loopback ++ ++ auto iface0 ++ iface iface0 inet dhcp ++ ++ # control-alias iface0 ++ iface iface0 inet6 dhcp ++ """).rstrip(' '), ++ 'yaml': textwrap.dedent("""\ ++ version: 1 ++ config: ++ - type: 'physical' ++ name: 'iface0' ++ subnets: ++ - {'type': 'dhcp4'} ++ - {'type': 'dhcp6'} ++ """).rstrip(' '), ++ }, + 'all': { + 'expected_eni': ("""\ + auto lo +@@ -455,11 +474,9 @@ iface br0 inet static + address 192.168.14.2/24 + bridge_ports eth3 eth4 + bridge_stp off +- post-up ifup br0:1 +- + +-auto br0:1 +-iface br0:1 inet6 static ++# control-alias br0 ++iface br0 inet6 static + address 2001:1::1/64 + + auto bond0.200 +@@ -476,11 +493,9 @@ iface eth0.101 inet static + mtu 1500 + vlan-raw-device eth0 + vlan_id 101 +- post-up ifup eth0.101:1 +- + +-auto eth0.101:1 +-iface eth0.101:1 inet static ++# control-alias eth0.101 ++iface eth0.101 inet static + address 192.168.2.10/24 + + post-up route add -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true +@@ -1007,6 +1022,13 @@ class TestEniRoundTrip(TestCase): + entry['expected_eni'].splitlines(), + files['/etc/network/interfaces'].splitlines()) + ++ def testsimple_render_v4_and_v6(self): ++ entry = NETWORK_CONFIGS['v4_and_v6'] ++ files = self._render_and_read(network_config=yaml.load(entry['yaml'])) ++ self.assertEqual( ++ entry['expected_eni'].splitlines(), ++ files['/etc/network/interfaces'].splitlines()) ++ + def test_routes_rendered(self): + # as reported in bug 1649652 + conf = [ +-- +2.13.6 + diff --git a/SOURCES/0027-systemd-create-run-cloud-init-enabled.patch b/SOURCES/0027-systemd-create-run-cloud-init-enabled.patch deleted file mode 100644 index 7b822e6..0000000 --- a/SOURCES/0027-systemd-create-run-cloud-init-enabled.patch +++ /dev/null @@ -1,29 +0,0 @@ -From e210660ecaee3b44a6e8b4e0fe39e4055450696e Mon Sep 17 00:00:00 2001 -From: Ryan McCabe -Date: Fri, 10 Nov 2017 10:03:44 -0500 -Subject: [PATCH] Create an explicit enabled file in /run/cloud-init/ to - control whether the dhclient and NM hooks run on Azure. - -X-downstream-only: Yes -Resolves: rhbz#1474226 ---- - rhel/systemd/cloud-init-local.service | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/rhel/systemd/cloud-init-local.service b/rhel/systemd/cloud-init-local.service -index 8174937b..047907c4 100644 ---- a/rhel/systemd/cloud-init-local.service -+++ b/rhel/systemd/cloud-init-local.service -@@ -14,6 +14,9 @@ ConditionKernelCommandLine=!cloud-init=disabled - - [Service] - Type=oneshot -+ExecStartPre=/bin/mkdir -p /run/cloud-init -+ExecStartPre=/sbin/restorecon /run/cloud-init -+ExecStartPre=/usr/bin/touch /run/cloud-init/enabled - ExecStart=/usr/bin/cloud-init init --local - ExecStart=/bin/touch /run/cloud-init/network-config-ready - RemainAfterExit=yes --- -2.13.6 - diff --git a/SOURCES/0028-net-Allow-for-NetworkManager-configuration.patch b/SOURCES/0028-net-Allow-for-NetworkManager-configuration.patch deleted file mode 100644 index 72f6157..0000000 --- a/SOURCES/0028-net-Allow-for-NetworkManager-configuration.patch +++ /dev/null @@ -1,164 +0,0 @@ -From 5fc5da29e5187ff6f56c968e7c06fabd1fce62ad Mon Sep 17 00:00:00 2001 -From: Ryan McCabe -Date: Thu, 8 Jun 2017 13:24:23 -0400 -Subject: [PATCH] net: Allow for NetworkManager configuration - -In cases where the config json specifies nameserver entries, -if there are interfaces configured to use dhcp, NetworkManager, -if enabled, will clobber the /etc/resolv.conf that cloud-init -has produced, which can break dns. If there are no interfaces -configured to use dhcp, NetworkManager could clobber -/etc/resolv.conf with an empty file. - -This patch adds a mechanism for dropping additional configuration -into /etc/NetworkManager/conf.d/ and disables management of -/etc/resolv.conf by NetworkManager when nameserver information is -provided in the config. - -LP: #1693251 - -Resolves: rhbz#1454491 - -Signed-off-by: Ryan McCabe -(cherry picked from commit 67bab5bb804e2346673430868935f6bbcdb88f13) ---- - cloudinit/distros/parsers/networkmanager_conf.py | 23 +++++++++++++++++++++++ - cloudinit/net/sysconfig.py | 24 ++++++++++++++++++++++++ - tests/unittests/test_net.py | 21 +++++++++++++++++++++ - 3 files changed, 68 insertions(+) - create mode 100644 cloudinit/distros/parsers/networkmanager_conf.py - -diff --git a/cloudinit/distros/parsers/networkmanager_conf.py b/cloudinit/distros/parsers/networkmanager_conf.py -new file mode 100644 -index 00000000..ac51f122 ---- /dev/null -+++ b/cloudinit/distros/parsers/networkmanager_conf.py -@@ -0,0 +1,23 @@ -+# Copyright (C) 2017 Red Hat, Inc. -+# -+# Author: Ryan McCabe -+# -+# This file is part of cloud-init. See LICENSE file for license information. -+ -+import configobj -+ -+# This module is used to set additional NetworkManager configuration -+# in /etc/NetworkManager/conf.d -+# -+ -+ -+class NetworkManagerConf(configobj.ConfigObj): -+ def __init__(self, contents): -+ configobj.ConfigObj.__init__(self, contents, -+ interpolation=False, -+ write_empty_values=False) -+ -+ def set_section_keypair(self, section_name, key, value): -+ if section_name not in self.sections: -+ self.main[section_name] = {} -+ self.main[section_name] = {key: value} -diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py -index ef80d99b..d496d916 100644 ---- a/cloudinit/net/sysconfig.py -+++ b/cloudinit/net/sysconfig.py -@@ -5,6 +5,7 @@ import re - - import six - -+from cloudinit.distros.parsers import networkmanager_conf - from cloudinit.distros.parsers import resolv_conf - from cloudinit import util - -@@ -250,6 +251,9 @@ class Renderer(renderer.Renderer): - self.netrules_path = config.get( - 'netrules_path', 'etc/udev/rules.d/70-persistent-net.rules') - self.dns_path = config.get('dns_path', 'etc/resolv.conf') -+ nm_conf_path = 'etc/NetworkManager/conf.d/99-cloud-init.conf' -+ self.networkmanager_conf_path = config.get('networkmanager_conf_path', -+ nm_conf_path) - - @classmethod - def _render_iface_shared(cls, iface, iface_cfg): -@@ -443,6 +447,21 @@ class Renderer(renderer.Renderer): - content.add_search_domain(searchdomain) - return "\n".join([_make_header(';'), str(content)]) - -+ @staticmethod -+ def _render_networkmanager_conf(network_state): -+ content = networkmanager_conf.NetworkManagerConf("") -+ -+ # If DNS server information is provided, configure -+ # NetworkManager to not manage dns, so that /etc/resolv.conf -+ # does not get clobbered. -+ if network_state.dns_nameservers: -+ content.set_section_keypair('main', 'dns', 'none') -+ -+ if len(content) == 0: -+ return None -+ out = "".join([_make_header(), "\n", "\n".join(content.write()), "\n"]) -+ return out -+ - @classmethod - def _render_bridge_interfaces(cls, network_state, iface_contents): - bridge_filter = renderer.filter_by_type('bridge') -@@ -500,6 +519,11 @@ class Renderer(renderer.Renderer): - resolv_content = self._render_dns(network_state, - existing_dns_path=dns_path) - util.write_file(dns_path, resolv_content) -+ if self.networkmanager_conf_path: -+ nm_conf_path = os.path.join(target, self.networkmanager_conf_path) -+ nm_conf_content = self._render_networkmanager_conf(network_state) -+ if nm_conf_content: -+ util.write_file(nm_conf_path, nm_conf_content) - if self.netrules_path: - netrules_content = self._render_persistent_net(network_state) - netrules_path = os.path.join(target, self.netrules_path) -diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py -index 172d6046..379ac8bb 100755 ---- a/tests/unittests/test_net.py -+++ b/tests/unittests/test_net.py -@@ -162,6 +162,13 @@ NETMASK0=0.0.0.0 - ; - nameserver 172.19.0.12 - """.lstrip()), -+ ('etc/NetworkManager/conf.d/99-cloud-init.conf', -+ """ -+# Created by cloud-init on instance boot automatically, do not edit. -+# -+[main] -+dns = none -+""".lstrip()), - ('etc/udev/rules.d/70-persistent-net.rules', - "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ', - 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))] -@@ -222,6 +229,13 @@ USERCTL=no - ; - nameserver 172.19.0.12 - """.lstrip()), -+ ('etc/NetworkManager/conf.d/99-cloud-init.conf', -+ """ -+# Created by cloud-init on instance boot automatically, do not edit. -+# -+[main] -+dns = none -+""".lstrip()), - ('etc/udev/rules.d/70-persistent-net.rules', - "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ', - 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))] -@@ -304,6 +318,13 @@ USERCTL=no - ; - nameserver 172.19.0.12 - """.lstrip()), -+ ('etc/NetworkManager/conf.d/99-cloud-init.conf', -+ """ -+# Created by cloud-init on instance boot automatically, do not edit. -+# -+[main] -+dns = none -+""".lstrip()), - ('etc/udev/rules.d/70-persistent-net.rules', - "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ', - 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))] --- -2.13.6 - diff --git a/SOURCES/0028-systemd-create-run-cloud-init-enabled.patch b/SOURCES/0028-systemd-create-run-cloud-init-enabled.patch new file mode 100644 index 0000000..7b822e6 --- /dev/null +++ b/SOURCES/0028-systemd-create-run-cloud-init-enabled.patch @@ -0,0 +1,29 @@ +From e210660ecaee3b44a6e8b4e0fe39e4055450696e Mon Sep 17 00:00:00 2001 +From: Ryan McCabe +Date: Fri, 10 Nov 2017 10:03:44 -0500 +Subject: [PATCH] Create an explicit enabled file in /run/cloud-init/ to + control whether the dhclient and NM hooks run on Azure. + +X-downstream-only: Yes +Resolves: rhbz#1474226 +--- + rhel/systemd/cloud-init-local.service | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/rhel/systemd/cloud-init-local.service b/rhel/systemd/cloud-init-local.service +index 8174937b..047907c4 100644 +--- a/rhel/systemd/cloud-init-local.service ++++ b/rhel/systemd/cloud-init-local.service +@@ -14,6 +14,9 @@ ConditionKernelCommandLine=!cloud-init=disabled + + [Service] + Type=oneshot ++ExecStartPre=/bin/mkdir -p /run/cloud-init ++ExecStartPre=/sbin/restorecon /run/cloud-init ++ExecStartPre=/usr/bin/touch /run/cloud-init/enabled + ExecStart=/usr/bin/cloud-init init --local + ExecStart=/bin/touch /run/cloud-init/network-config-ready + RemainAfterExit=yes +-- +2.13.6 + diff --git a/SOURCES/0029-support-loopback-as-a-device-type.patch b/SOURCES/0029-support-loopback-as-a-device-type.patch index d421ad5..285efc4 100644 --- a/SOURCES/0029-support-loopback-as-a-device-type.patch +++ b/SOURCES/0029-support-loopback-as-a-device-type.patch @@ -1,7 +1,7 @@ -From 4aa2305022e5d42d858102941c51d7186ef8fef9 Mon Sep 17 00:00:00 2001 +From 8e8e1dffc528b738f92cd508d7a6faf58e40af00 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 15 Mar 2017 12:06:40 -0400 -Subject: [PATCH 1/3] support 'loopback' as a device type. +Subject: [PATCH 1/2] support 'loopback' as a device type. As reported in bug 1671927, sysconfig had an issue with rendering a loopback device. The problem was that some as yet unknown issue was @@ -21,7 +21,8 @@ Tests are added for eni and sysconfig renderer. (cherry picked from commit 1a2ca7530518d819cbab7287b12f942743427e38) -Related: rhbz#1540094 +Related: rhbz#1492726 +Signed-off-by: Ryan McCabe --- cloudinit/net/eni.py | 16 +++++++++------ cloudinit/net/network_state.py | 4 ++++ @@ -30,10 +31,10 @@ Related: rhbz#1540094 4 files changed, 60 insertions(+), 6 deletions(-) diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py -index b06ffac9..0d5e9712 100644 +index 5b249f1f..69ecbb5d 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py -@@ -265,8 +265,11 @@ def _ifaces_to_net_config_data(ifaces): +@@ -273,8 +273,11 @@ def _ifaces_to_net_config_data(ifaces): # devname is 'eth0' for name='eth0:1' devname = name.partition(":")[0] if devname not in devs: @@ -47,7 +48,7 @@ index b06ffac9..0d5e9712 100644 # this isnt strictly correct, but some might specify # hwaddress on a nic for matching / declaring name. if 'hwaddress' in data: -@@ -418,10 +421,11 @@ class Renderer(renderer.Renderer): +@@ -423,10 +426,11 @@ class Renderer(renderer.Renderer): bonding ''' order = { @@ -67,17 +68,17 @@ diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py index 11ef585b..90b2835a 100644 --- a/cloudinit/net/network_state.py +++ b/cloudinit/net/network_state.py -@@ -211,6 +211,10 @@ class NetworkStateInterpreter(object): - exc_info=True) +@@ -212,6 +212,10 @@ class NetworkStateInterpreter(object): LOG.debug(self.dump_network_state()) -+ @ensure_command_keys(['name']) + @ensure_command_keys(['name']) + def handle_loopback(self, command): + return self.handle_physical(command) + - @ensure_command_keys(['name']) ++ @ensure_command_keys(['name']) def handle_physical(self, command): ''' + command = { diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py index efd101ca..25c29104 100644 --- a/cloudinit/net/sysconfig.py @@ -92,10 +93,10 @@ index efd101ca..25c29104 100644 iface_cfg = NetInterface(iface_name, base_sysconf_dir) cls._render_iface_shared(iface, iface_cfg) diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py -index ffa911cc..d75742be 100644 +index 4c0e3ad3..7e389c10 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py -@@ -600,6 +600,14 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true +@@ -615,6 +615,14 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true } } @@ -110,7 +111,7 @@ index ffa911cc..d75742be 100644 def _setup_test(tmp_dir, mock_get_devicelist, mock_read_sys_net, mock_sys_dev_path): -@@ -770,6 +778,27 @@ USERCTL=no +@@ -785,6 +793,27 @@ USERCTL=no with open(os.path.join(render_dir, fn)) as fh: self.assertEqual(expected_content, fh.read()) @@ -138,7 +139,7 @@ index ffa911cc..d75742be 100644 class TestEniNetRendering(TestCase): -@@ -811,6 +840,21 @@ iface eth1000 inet dhcp +@@ -826,6 +855,21 @@ iface eth1000 inet dhcp """ self.assertEqual(expected.lstrip(), contents.lstrip()) @@ -161,5 +162,5 @@ index ffa911cc..d75742be 100644 class TestEniNetworkStateToEni(TestCase): mycfg = { -- -2.14.3 +2.13.6 diff --git a/SOURCES/0030-Render-the-GATEWAY-value-in-interface-files-which-ha.patch b/SOURCES/0030-Render-the-GATEWAY-value-in-interface-files-which-ha.patch deleted file mode 100644 index c05332b..0000000 --- a/SOURCES/0030-Render-the-GATEWAY-value-in-interface-files-which-ha.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 6f9c05464a8ed420fb3e2ed71b401ecd1d772bad Mon Sep 17 00:00:00 2001 -From: Ryan McCabe -Date: Tue, 30 Jan 2018 12:37:00 -0500 -Subject: [PATCH 2/3] Render the GATEWAY= value in interface files which have a - gateway in the subnet configuration. - -Resolves: rhbz#1540094 -Signed-off-by: Ryan McCabe ---- - cloudinit/net/sysconfig.py | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py -index 25c29104..ca031691 100644 ---- a/cloudinit/net/sysconfig.py -+++ b/cloudinit/net/sysconfig.py -@@ -347,6 +347,9 @@ class Renderer(renderer.Renderer): - iface_cfg['NETMASK' + str(ipv4_index)] = \ - subnet['netmask'] - -+ if 'gateway' in subnet: -+ 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)): --- -2.14.3 - diff --git a/SOURCES/0030-sysconfig-include-GATEWAY-value-if-set-in-subnet.patch b/SOURCES/0030-sysconfig-include-GATEWAY-value-if-set-in-subnet.patch new file mode 100644 index 0000000..8b911fd --- /dev/null +++ b/SOURCES/0030-sysconfig-include-GATEWAY-value-if-set-in-subnet.patch @@ -0,0 +1,142 @@ +From 8fd2f03c40606ad25135385f7badf42e48d713b6 Mon Sep 17 00:00:00 2001 +From: Ryan Harper +Date: Fri, 9 Jun 2017 12:35:11 -0500 +Subject: [PATCH 2/2] sysconfig: include GATEWAY value if set in subnet + +Render the GATEWAY= value in interface files which have a gateway in the +subnet configuration. + +LP: #1686856 +(cherry picked from commit d1e8eb73aca6a3f5cee415774dcf540e934ec250) + +Resolves: rhbz#1465730 +Signed-off-by: Ryan McCabe +(cherry picked from commit 158f53f223b763ddfbfa5967c58866424ae02689) + +Resolves: rhbz#1492726 +Signed-off-by: Ryan McCabe +--- + cloudinit/net/sysconfig.py | 3 ++ + tests/unittests/test_distros/test_netconfig.py | 2 ++ + tests/unittests/test_net.py | 50 +++++++++++++++++++++++++- + 3 files changed, 54 insertions(+), 1 deletion(-) + +diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py +index 25c29104..ca031691 100644 +--- a/cloudinit/net/sysconfig.py ++++ b/cloudinit/net/sysconfig.py +@@ -347,6 +347,9 @@ class Renderer(renderer.Renderer): + iface_cfg['NETMASK' + str(ipv4_index)] = \ + subnet['netmask'] + ++ if 'gateway' in subnet: ++ 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)): +diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py +index 861cf8ef..108d5741 100644 +--- a/tests/unittests/test_distros/test_netconfig.py ++++ b/tests/unittests/test_distros/test_netconfig.py +@@ -260,6 +260,7 @@ BOOTPROTO=none + DEVICE=eth0 + IPADDR=192.168.1.5 + NETMASK=255.255.255.0 ++GATEWAY=192.168.1.254 + ONBOOT=yes + TYPE=Ethernet + USERCTL=no +@@ -396,6 +397,7 @@ IPV6_AUTOCONF=no + BOOTPROTO=none + DEVICE=eth0 + IPV6ADDR=2607:f0d0:1002:0011::2/64 ++GATEWAY=2607:f0d0:1002:0011::1 + IPV6INIT=yes + ONBOOT=yes + TYPE=Ethernet +diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py +index 7e389c10..4a32eb88 100644 +--- a/tests/unittests/test_net.py ++++ b/tests/unittests/test_net.py +@@ -615,6 +615,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', +@@ -624,6 +625,30 @@ CONFIG_V1_EXPLICIT_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, ++ "carrier": False, ++ "dormant": False, ++ "operstate": "down", ++ "address": "07-1C-C6-75-A4-BE", ++ "device/driver": None, ++ "device/device": None, ++ } ++} ++ ++ + def _setup_test(tmp_dir, mock_get_devicelist, mock_read_sys_net, + mock_sys_dev_path): + mock_get_devicelist.return_value = ['eth1000'] +@@ -793,6 +818,30 @@ 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(render_dir, ns) ++ 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 ++DEVICE=interface0 ++GATEWAY=10.0.2.2 ++HWADDR=52:54:00:12:34:00 ++IPADDR=10.0.2.15 ++NETMASK=255.255.255.0 ++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") +@@ -807,7 +856,6 @@ USERCTL=no + # + BOOTPROTO=dhcp + DEVICE=eth0 +-NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no +-- +2.13.6 + diff --git a/SOURCES/0031-rh_subscription-Perform-null-checks-for-enabled-and-.patch b/SOURCES/0031-rh_subscription-Perform-null-checks-for-enabled-and-.patch new file mode 100644 index 0000000..4ded147 --- /dev/null +++ b/SOURCES/0031-rh_subscription-Perform-null-checks-for-enabled-and-.patch @@ -0,0 +1,190 @@ +From fc4ca923d02886669f2f0e0916732133e1969bb6 Mon Sep 17 00:00:00 2001 +From: Dave Mulford +Date: Mon, 9 Oct 2017 15:28:15 -0500 +Subject: [PATCH] rh_subscription: Perform null checks for enabled and disabled + repos. + +The rh_subscription module doesn't perform null checks when attempting to +iterate on the enabled and disable repos arrays. When only one is +specified, cloud-init fails to run. + +(cherry picked from commit 9bc4ce0596544ffa56d9d67245b00e07006a8662) + +Resolves: rhbz#1498974 +Signed-off-by: Ryan McCabe +--- + cloudinit/config/cc_rh_subscription.py | 46 ++++++++++++++++++++------------- + tests/unittests/test_rh_subscription.py | 15 +++++++++++ + 2 files changed, 43 insertions(+), 18 deletions(-) + +diff --git a/cloudinit/config/cc_rh_subscription.py b/cloudinit/config/cc_rh_subscription.py +index 7f36cf8f..a9d21e78 100644 +--- a/cloudinit/config/cc_rh_subscription.py ++++ b/cloudinit/config/cc_rh_subscription.py +@@ -38,14 +38,16 @@ Subscription`` example config. + server-hostname: + """ + ++from cloudinit import log as logging + from cloudinit import util + ++LOG = logging.getLogger(__name__) ++ + distros = ['fedora', 'rhel'] + + + def handle(name, cfg, _cloud, log, _args): +- sm = SubscriptionManager(cfg) +- sm.log = log ++ sm = SubscriptionManager(cfg, log=log) + if not sm.is_configured(): + log.debug("%s: module not configured.", name) + return None +@@ -86,10 +88,9 @@ def handle(name, cfg, _cloud, log, _args): + if not return_stat: + raise SubscriptionError("Unable to attach pools {0}" + .format(sm.pools)) +- if (sm.enable_repo is not None) or (sm.disable_repo is not None): +- return_stat = sm.update_repos(sm.enable_repo, sm.disable_repo) +- if not return_stat: +- raise SubscriptionError("Unable to add or remove repos") ++ return_stat = sm.update_repos() ++ if not return_stat: ++ raise SubscriptionError("Unable to add or remove repos") + sm.log_success("rh_subscription plugin completed successfully") + except SubscriptionError as e: + sm.log_warn(str(e)) +@@ -108,7 +109,10 @@ class SubscriptionManager(object): + 'rhsm-baseurl', 'server-hostname', + 'auto-attach', 'service-level'] + +- def __init__(self, cfg): ++ def __init__(self, cfg, log=None): ++ if log is None: ++ log = LOG ++ self.log = log + self.cfg = cfg + self.rhel_cfg = self.cfg.get('rh_subscription', {}) + self.rhsm_baseurl = self.rhel_cfg.get('rhsm-baseurl') +@@ -130,7 +134,7 @@ class SubscriptionManager(object): + + def log_warn(self, msg): + '''Simple wrapper for logging warning messages. Useful for unittests''' +- self.log.warn(msg) ++ self.log.warning(msg) + + def _verify_keys(self): + ''' +@@ -245,7 +249,7 @@ class SubscriptionManager(object): + return False + + reg_id = return_out.split("ID: ")[1].rstrip() +- self.log.debug("Registered successfully with ID {0}".format(reg_id)) ++ self.log.debug("Registered successfully with ID %s", reg_id) + return True + + def _set_service_level(self): +@@ -347,7 +351,7 @@ class SubscriptionManager(object): + try: + self._sub_man_cli(cmd) + self.log.debug("Attached the following pools to your " +- "system: %s" % (", ".join(pool_list)) ++ "system: %s", (", ".join(pool_list)) + .replace('--pool=', '')) + return True + except util.ProcessExecutionError as e: +@@ -355,18 +359,24 @@ class SubscriptionManager(object): + "due to {1}".format(pool, e)) + return False + +- def update_repos(self, erepos, drepos): ++ def update_repos(self): + ''' + Takes a list of yum repo ids that need to be disabled or enabled; then + it verifies if they are already enabled or disabled and finally + executes the action to disable or enable + ''' + +- if (erepos is not None) and (not isinstance(erepos, list)): ++ erepos = self.enable_repo ++ drepos = self.disable_repo ++ if erepos is None: ++ erepos = [] ++ if drepos is None: ++ drepos = [] ++ if not isinstance(erepos, list): + self.log_warn("Repo IDs must in the format of a list.") + return False + +- if (drepos is not None) and (not isinstance(drepos, list)): ++ if not isinstance(drepos, list): + self.log_warn("Repo IDs must in the format of a list.") + return False + +@@ -399,14 +409,14 @@ class SubscriptionManager(object): + for fail in enable_list_fail: + # Check if the repo exists or not + if fail in active_repos: +- self.log.debug("Repo {0} is already enabled".format(fail)) ++ self.log.debug("Repo %s is already enabled", fail) + else: + self.log_warn("Repo {0} does not appear to " + "exist".format(fail)) + if len(disable_list_fail) > 0: + for fail in disable_list_fail: +- self.log.debug("Repo {0} not disabled " +- "because it is not enabled".format(fail)) ++ self.log.debug("Repo %s not disabled " ++ "because it is not enabled", fail) + + cmd = ['repos'] + if len(disable_list) > 0: +@@ -422,10 +432,10 @@ class SubscriptionManager(object): + return False + + if len(enable_list) > 0: +- self.log.debug("Enabled the following repos: %s" % ++ self.log.debug("Enabled the following repos: %s", + (", ".join(enable_list)).replace('--enable=', '')) + if len(disable_list) > 0: +- self.log.debug("Disabled the following repos: %s" % ++ self.log.debug("Disabled the following repos: %s", + (", ".join(disable_list)).replace('--disable=', '')) + return True + +diff --git a/tests/unittests/test_rh_subscription.py b/tests/unittests/test_rh_subscription.py +index ca14cd46..7b35b9d0 100644 +--- a/tests/unittests/test_rh_subscription.py ++++ b/tests/unittests/test_rh_subscription.py +@@ -2,6 +2,7 @@ + + """Tests for registering RHEL subscription via rh_subscription.""" + ++import copy + import logging + + from cloudinit.config import cc_rh_subscription +@@ -68,6 +69,20 @@ class GoodTests(TestCase): + self.assertEqual(self.SM.log_success.call_count, 1) + self.assertEqual(self.SM._sub_man_cli.call_count, 2) + ++ @mock.patch.object(cc_rh_subscription.SubscriptionManager, "_getRepos") ++ @mock.patch.object(cc_rh_subscription.SubscriptionManager, "_sub_man_cli") ++ def test_update_repos_disable_with_none(self, m_sub_man_cli, m_get_repos): ++ cfg = copy.deepcopy(self.config) ++ m_get_repos.return_value = ([], ['repo1']) ++ m_sub_man_cli.return_value = (b'', b'') ++ cfg['rh_subscription'].update( ++ {'enable-repo': ['repo1'], 'disable-repo': None}) ++ mysm = cc_rh_subscription.SubscriptionManager(cfg) ++ self.assertEqual(True, mysm.update_repos()) ++ m_get_repos.assert_called_with() ++ self.assertEqual(m_sub_man_cli.call_args_list, ++ [mock.call(['repos', '--enable=repo1'])]) ++ + def test_full_registration(self): + ''' + Registration with auto-attach, service-level, adding pools, +-- +2.13.6 + diff --git a/SOURCES/0031-sysconfig-Don-t-write-BOOTPROTO-dhcp-for-ipv6-dhcp.patch b/SOURCES/0031-sysconfig-Don-t-write-BOOTPROTO-dhcp-for-ipv6-dhcp.patch deleted file mode 100644 index 57c49ba..0000000 --- a/SOURCES/0031-sysconfig-Don-t-write-BOOTPROTO-dhcp-for-ipv6-dhcp.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 92ab19b85a1cdab73280b679c4a3bd0e32f3d2e2 Mon Sep 17 00:00:00 2001 -From: Ryan McCabe -Date: Mon, 4 Dec 2017 13:43:30 -0500 -Subject: [PATCH 3/3] sysconfig: Don't write BOOTPROTO=dhcp for ipv6 dhcp - -Don't write BOOTPROTO=dhcp for ipv6 dhcp, as BOOTPROTO applies -only to ipv4. Explicitly write IPV6_AUTOCONF=no for dhcp on ipv6. - -X-downstream-only: yes - -Resolves: rhbz#1540093 -Signed-off-by: Ryan McCabe -(cherry picked from commit cbc09ba2f8e6fa967232039e6f6a3363d54ba592) ---- - cloudinit/net/sysconfig.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py -index ca031691..09df76e3 100644 ---- a/cloudinit/net/sysconfig.py -+++ b/cloudinit/net/sysconfig.py -@@ -287,7 +287,7 @@ class Renderer(renderer.Renderer): - if subnet_type == 'dhcp6': - iface_cfg['IPV6INIT'] = True - iface_cfg['DHCPV6C'] = True -- iface_cfg['BOOTPROTO'] = 'dhcp' -+ iface_cfg['IPV6_AUTOCONF'] = False - elif subnet_type in ['dhcp4', 'dhcp']: - iface_cfg['BOOTPROTO'] = 'dhcp' - elif subnet_type == 'static': --- -2.14.3 - diff --git a/SOURCES/0032-net-Allow-for-NetworkManager-configuration.patch b/SOURCES/0032-net-Allow-for-NetworkManager-configuration.patch new file mode 100644 index 0000000..72f6157 --- /dev/null +++ b/SOURCES/0032-net-Allow-for-NetworkManager-configuration.patch @@ -0,0 +1,164 @@ +From 5fc5da29e5187ff6f56c968e7c06fabd1fce62ad Mon Sep 17 00:00:00 2001 +From: Ryan McCabe +Date: Thu, 8 Jun 2017 13:24:23 -0400 +Subject: [PATCH] net: Allow for NetworkManager configuration + +In cases where the config json specifies nameserver entries, +if there are interfaces configured to use dhcp, NetworkManager, +if enabled, will clobber the /etc/resolv.conf that cloud-init +has produced, which can break dns. If there are no interfaces +configured to use dhcp, NetworkManager could clobber +/etc/resolv.conf with an empty file. + +This patch adds a mechanism for dropping additional configuration +into /etc/NetworkManager/conf.d/ and disables management of +/etc/resolv.conf by NetworkManager when nameserver information is +provided in the config. + +LP: #1693251 + +Resolves: rhbz#1454491 + +Signed-off-by: Ryan McCabe +(cherry picked from commit 67bab5bb804e2346673430868935f6bbcdb88f13) +--- + cloudinit/distros/parsers/networkmanager_conf.py | 23 +++++++++++++++++++++++ + cloudinit/net/sysconfig.py | 24 ++++++++++++++++++++++++ + tests/unittests/test_net.py | 21 +++++++++++++++++++++ + 3 files changed, 68 insertions(+) + create mode 100644 cloudinit/distros/parsers/networkmanager_conf.py + +diff --git a/cloudinit/distros/parsers/networkmanager_conf.py b/cloudinit/distros/parsers/networkmanager_conf.py +new file mode 100644 +index 00000000..ac51f122 +--- /dev/null ++++ b/cloudinit/distros/parsers/networkmanager_conf.py +@@ -0,0 +1,23 @@ ++# Copyright (C) 2017 Red Hat, Inc. ++# ++# Author: Ryan McCabe ++# ++# This file is part of cloud-init. See LICENSE file for license information. ++ ++import configobj ++ ++# This module is used to set additional NetworkManager configuration ++# in /etc/NetworkManager/conf.d ++# ++ ++ ++class NetworkManagerConf(configobj.ConfigObj): ++ def __init__(self, contents): ++ configobj.ConfigObj.__init__(self, contents, ++ interpolation=False, ++ write_empty_values=False) ++ ++ def set_section_keypair(self, section_name, key, value): ++ if section_name not in self.sections: ++ self.main[section_name] = {} ++ self.main[section_name] = {key: value} +diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py +index ef80d99b..d496d916 100644 +--- a/cloudinit/net/sysconfig.py ++++ b/cloudinit/net/sysconfig.py +@@ -5,6 +5,7 @@ import re + + import six + ++from cloudinit.distros.parsers import networkmanager_conf + from cloudinit.distros.parsers import resolv_conf + from cloudinit import util + +@@ -250,6 +251,9 @@ class Renderer(renderer.Renderer): + self.netrules_path = config.get( + 'netrules_path', 'etc/udev/rules.d/70-persistent-net.rules') + self.dns_path = config.get('dns_path', 'etc/resolv.conf') ++ nm_conf_path = 'etc/NetworkManager/conf.d/99-cloud-init.conf' ++ self.networkmanager_conf_path = config.get('networkmanager_conf_path', ++ nm_conf_path) + + @classmethod + def _render_iface_shared(cls, iface, iface_cfg): +@@ -443,6 +447,21 @@ class Renderer(renderer.Renderer): + content.add_search_domain(searchdomain) + return "\n".join([_make_header(';'), str(content)]) + ++ @staticmethod ++ def _render_networkmanager_conf(network_state): ++ content = networkmanager_conf.NetworkManagerConf("") ++ ++ # If DNS server information is provided, configure ++ # NetworkManager to not manage dns, so that /etc/resolv.conf ++ # does not get clobbered. ++ if network_state.dns_nameservers: ++ content.set_section_keypair('main', 'dns', 'none') ++ ++ if len(content) == 0: ++ return None ++ out = "".join([_make_header(), "\n", "\n".join(content.write()), "\n"]) ++ return out ++ + @classmethod + def _render_bridge_interfaces(cls, network_state, iface_contents): + bridge_filter = renderer.filter_by_type('bridge') +@@ -500,6 +519,11 @@ class Renderer(renderer.Renderer): + resolv_content = self._render_dns(network_state, + existing_dns_path=dns_path) + util.write_file(dns_path, resolv_content) ++ if self.networkmanager_conf_path: ++ nm_conf_path = os.path.join(target, self.networkmanager_conf_path) ++ nm_conf_content = self._render_networkmanager_conf(network_state) ++ if nm_conf_content: ++ util.write_file(nm_conf_path, nm_conf_content) + if self.netrules_path: + netrules_content = self._render_persistent_net(network_state) + netrules_path = os.path.join(target, self.netrules_path) +diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py +index 172d6046..379ac8bb 100755 +--- a/tests/unittests/test_net.py ++++ b/tests/unittests/test_net.py +@@ -162,6 +162,13 @@ NETMASK0=0.0.0.0 + ; + nameserver 172.19.0.12 + """.lstrip()), ++ ('etc/NetworkManager/conf.d/99-cloud-init.conf', ++ """ ++# Created by cloud-init on instance boot automatically, do not edit. ++# ++[main] ++dns = none ++""".lstrip()), + ('etc/udev/rules.d/70-persistent-net.rules', + "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ', + 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))] +@@ -222,6 +229,13 @@ USERCTL=no + ; + nameserver 172.19.0.12 + """.lstrip()), ++ ('etc/NetworkManager/conf.d/99-cloud-init.conf', ++ """ ++# Created by cloud-init on instance boot automatically, do not edit. ++# ++[main] ++dns = none ++""".lstrip()), + ('etc/udev/rules.d/70-persistent-net.rules', + "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ', + 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))] +@@ -304,6 +318,13 @@ USERCTL=no + ; + nameserver 172.19.0.12 + """.lstrip()), ++ ('etc/NetworkManager/conf.d/99-cloud-init.conf', ++ """ ++# Created by cloud-init on instance boot automatically, do not edit. ++# ++[main] ++dns = none ++""".lstrip()), + ('etc/udev/rules.d/70-persistent-net.rules', + "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ', + 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))] +-- +2.13.6 + diff --git a/SOURCES/0032-sysconfig-Render-IPV6_DEFAULTGW-correctly.patch b/SOURCES/0032-sysconfig-Render-IPV6_DEFAULTGW-correctly.patch deleted file mode 100644 index b4ab937..0000000 --- a/SOURCES/0032-sysconfig-Render-IPV6_DEFAULTGW-correctly.patch +++ /dev/null @@ -1,64 +0,0 @@ -From 75fbba4601d09112615c342f88ef7b43fead0508 Mon Sep 17 00:00:00 2001 -From: Ryan McCabe -Date: Fri, 2 Feb 2018 10:25:31 -0500 -Subject: [PATCH] sysconfig: Render IPV6_DEFAULTGW correctly - -Downstream backport of the fixes introduced in upstream commit -97abd83513bee191b58f095f4d683b18acce0b49 which will not apply to -the RHEL 0.7.9 tree. - -Signed-off-by: Ryan McCabe -Resolves: rhbz#1540094 ---- - cloudinit/net/sysconfig.py | 6 +++++- - tests/unittests/test_distros/test_netconfig.py | 4 ++++ - 2 files changed, 9 insertions(+), 1 deletion(-) - -diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py -index 09df76e3..9975fe2c 100644 ---- a/cloudinit/net/sysconfig.py -+++ b/cloudinit/net/sysconfig.py -@@ -348,7 +348,11 @@ class Renderer(renderer.Renderer): - subnet['netmask'] - - if 'gateway' in subnet: -- iface_cfg['GATEWAY'] = subnet['gateway'] -+ 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): -diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py -index 861cf8ef..10e25a72 100644 ---- a/tests/unittests/test_distros/test_netconfig.py -+++ b/tests/unittests/test_distros/test_netconfig.py -@@ -257,9 +257,11 @@ NETWORKING=yes - # Created by cloud-init on instance boot automatically, do not edit. - # - BOOTPROTO=none -+DEFROUTE=yes - DEVICE=eth0 - IPADDR=192.168.1.5 - NETMASK=255.255.255.0 -+GATEWAY=192.168.1.254 - ONBOOT=yes - TYPE=Ethernet - USERCTL=no -@@ -394,9 +396,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 - ONBOOT=yes - TYPE=Ethernet - USERCTL=no --- -2.14.3 - diff --git a/SOURCES/0033-Render-DNS-and-DOMAIN-lines-for-sysconfig.patch b/SOURCES/0033-Render-DNS-and-DOMAIN-lines-for-sysconfig.patch new file mode 100644 index 0000000..ee129d4 --- /dev/null +++ b/SOURCES/0033-Render-DNS-and-DOMAIN-lines-for-sysconfig.patch @@ -0,0 +1,89 @@ +From 3774ec0a9873b4dfbc647ee57f16fe461706c1b2 Mon Sep 17 00:00:00 2001 +From: Ryan McCabe +Date: Tue, 21 Nov 2017 11:50:18 -0500 +Subject: [PATCH] Render DNS and DOMAIN lines for sysconfig + +Currently when dns and dns search info is provided, it is not +rendered when outputting to sysconfig format. + +This patch causes the DNS and DOMAIN lines to be written out rendering +sysconfig. + +This is a backport of upstream commit +bbe91cdc6917adb503b455e6860c21ea7b3f567f which will not apply to the +0.7.9 tree. + +Signed-off-by: Ryan McCabe +Resolves: rhbz#1489270 +--- + cloudinit/net/sysconfig.py | 17 +++++++++++++++++ + tests/unittests/test_net.py | 11 ++++++++--- + 2 files changed, 25 insertions(+), 3 deletions(-) + +diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py +index ca031691..e34c8491 100644 +--- a/cloudinit/net/sysconfig.py ++++ b/cloudinit/net/sysconfig.py +@@ -350,6 +350,23 @@ class Renderer(renderer.Renderer): + if 'gateway' in subnet: + iface_cfg['GATEWAY'] = subnet['gateway'] + ++ if 'dns_search' in subnet: ++ if isinstance(subnet['dns_search'], (list, tuple)): ++ # Currently limited to 6 entries per resolv.conf(5) ++ search_list = subnet['dns_search'][:6] ++ iface_cfg['DOMAIN'] = ' '.join(search_list) ++ else: ++ iface_cfg['DOMAIN'] = subnet['dns_search'] ++ ++ if 'dns_nameservers' in subnet: ++ if isinstance(subnet['dns_search'], (list, tuple)): ++ # Currently limited to 3 entries per resolv.conf(5) ++ dns_list = subnet['dns_nameservers'][:3] ++ for i, k in enumerate(dns_list, 1): ++ iface_cfg['DNS' + str(i)] = k ++ else: ++ iface_cfg['DNS1'] = subnet['dns_nameservers'] ++ + @classmethod + def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets): + for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): +diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py +index 4a32eb88..551370d4 100644 +--- a/tests/unittests/test_net.py ++++ b/tests/unittests/test_net.py +@@ -820,7 +820,9 @@ USERCTL=no + + def test_network_config_v1_samples(self): + ns = network_state.parse_net_config_data(CONFIG_V1_SIMPLE_SUBNET) +- render_dir = self.tmp_path("render") ++ tmp_dir = tempfile.mkdtemp() ++ self.addCleanup(shutil.rmtree, tmp_dir) ++ render_dir = os.path.join(tmp_dir, "render") + os.makedirs(render_dir) + renderer = sysconfig.Renderer() + renderer.render_network_state(render_dir, ns) +@@ -844,7 +846,9 @@ USERCTL=no + + def test_config_with_explicit_loopback(self): + ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK) +- render_dir = self.tmp_path("render") ++ tmp_dir = tempfile.mkdtemp() ++ self.addCleanup(shutil.rmtree, tmp_dir) ++ render_dir = os.path.join(tmp_dir, "render") + os.makedirs(render_dir) + renderer = sysconfig.Renderer() + renderer.render_network_state(render_dir, ns) +@@ -904,7 +908,8 @@ iface eth1000 inet dhcp + self.assertEqual(expected.lstrip(), contents.lstrip()) + + def test_config_with_explicit_loopback(self): +- tmp_dir = self.tmp_dir() ++ tmp_dir = tempfile.mkdtemp() ++ self.addCleanup(shutil.rmtree, tmp_dir) + ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK) + renderer = eni.Renderer() + renderer.render_network_state(tmp_dir, ns) +-- +2.13.6 + diff --git a/SOURCES/0033-sysconfig-Render-DNS-and-DOMAIN.patch b/SOURCES/0033-sysconfig-Render-DNS-and-DOMAIN.patch deleted file mode 100644 index 3c2c3cc..0000000 --- a/SOURCES/0033-sysconfig-Render-DNS-and-DOMAIN.patch +++ /dev/null @@ -1,86 +0,0 @@ -From 6f54ccf28a327174df663ea2e07f32d7e632fddd Mon Sep 17 00:00:00 2001 -From: Ryan McCabe -Date: Thu, 15 Feb 2018 10:30:40 -0500 -Subject: [PATCH] sysconfig: Render DNS and DOMAIN - -Currently when dns and dns search info is provided, it is not -rendered when outputting to sysconfig format. - -This patch causes the DNS and DOMAIN lines to be written out rendering -sysconfig. - -This is a backport of upstream commit -bbe91cdc6917adb503b455e6860c21ea7b3f567f which will not apply to the -0.7.9 tree. - -Signed-off-by: Ryan McCabe -Resolves: rhbz#1545525 ---- - cloudinit/net/sysconfig.py | 17 +++++++++++++++++ - tests/unittests/test_net.py | 8 +++++--- - 2 files changed, 22 insertions(+), 3 deletions(-) - -diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py -index 9975fe2c..ec412512 100644 ---- a/cloudinit/net/sysconfig.py -+++ b/cloudinit/net/sysconfig.py -@@ -354,6 +354,23 @@ class Renderer(renderer.Renderer): - else: - iface_cfg['GATEWAY'] = subnet['gateway'] - -+ if 'dns_search' in subnet: -+ if isinstance(subnet['dns_search'], (list, tuple)): -+ # Currently limited to 6 entries per resolv.conf(5) -+ search_list = subnet['dns_search'][:6] -+ iface_cfg['DOMAIN'] = ' '.join(search_list) -+ else: -+ iface_cfg['DOMAIN'] = subnet['dns_search'] -+ -+ if 'dns_nameservers' in subnet: -+ if isinstance(subnet['dns_nameservers'], (list, tuple)): -+ # Currently limited to 3 entries per resolv.conf(5) -+ dns_list = subnet['dns_nameservers'][:3] -+ for i, k in enumerate(dns_list, 1): -+ iface_cfg['DNS' + str(i)] = k -+ else: -+ iface_cfg['DNS1'] = subnet['dns_nameservers'] -+ - @classmethod - def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets): - for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): -diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py -index d75742be..f2a1998a 100644 ---- a/tests/unittests/test_net.py -+++ b/tests/unittests/test_net.py -@@ -780,7 +780,9 @@ USERCTL=no - - def test_config_with_explicit_loopback(self): - ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK) -- render_dir = self.tmp_path("render") -+ tmp_dir = tempfile.mkdtemp() -+ self.addCleanup(shutil.rmtree, tmp_dir) -+ render_dir = os.path.join(tmp_dir, "render") - os.makedirs(render_dir) - renderer = sysconfig.Renderer() - renderer.render_network_state(render_dir, ns) -@@ -792,7 +794,6 @@ USERCTL=no - # - BOOTPROTO=dhcp - DEVICE=eth0 --NM_CONTROLLED=no - ONBOOT=yes - TYPE=Ethernet - USERCTL=no -@@ -841,7 +842,8 @@ iface eth1000 inet dhcp - self.assertEqual(expected.lstrip(), contents.lstrip()) - - def test_config_with_explicit_loopback(self): -- tmp_dir = self.tmp_dir() -+ tmp_dir = tempfile.mkdtemp() -+ self.addCleanup(shutil.rmtree, tmp_dir) - ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK) - renderer = eni.Renderer() - renderer.render_network_state(tmp_dir, ns) --- -2.14.3 - diff --git a/SOURCES/0034-Start_cloud_init_after_dbus.patch b/SOURCES/0034-Start_cloud_init_after_dbus.patch new file mode 100644 index 0000000..06419c0 --- /dev/null +++ b/SOURCES/0034-Start_cloud_init_after_dbus.patch @@ -0,0 +1,18 @@ +diff --git a/rhel/systemd/cloud-init-local.service b/rhel/systemd/cloud-init-local.service +index 047907c4..656eddb9 100644 +--- a/rhel/systemd/cloud-init-local.service ++++ b/rhel/systemd/cloud-init-local.service +@@ -3,10 +3,12 @@ Description=Initial cloud-init job (pre-networking) + DefaultDependencies=no + Wants=network-pre.target + After=systemd-remount-fs.service ++Requires=dbus.socket ++After=dbus.socket + Before=NetworkManager.service network.service + Before=network-pre.target + Before=shutdown.target +-Before=sysinit.target ++Before=firewalld.target + Conflicts=shutdown.target + RequiresMountsFor=/var/lib/cloud + ConditionPathExists=!/etc/cloud/cloud-init.disabled diff --git a/SOURCES/0035-sysconfig-Render-IPV6_DEFAULTGW-correctly.patch b/SOURCES/0035-sysconfig-Render-IPV6_DEFAULTGW-correctly.patch new file mode 100644 index 0000000..8aaa988 --- /dev/null +++ b/SOURCES/0035-sysconfig-Render-IPV6_DEFAULTGW-correctly.patch @@ -0,0 +1,74 @@ +From cee7eef674c4fe7e4a23e5c358df23064796e9e2 Mon Sep 17 00:00:00 2001 +From: Ryan McCabe +Date: Thu, 30 Nov 2017 08:59:03 -0500 +Subject: [PATCH] sysconfig: Render IPV6_DEFAULTGW correctly + +Downstream backport of the fixes introduced in upstream commit +97abd83513bee191b58f095f4d683b18acce0b49 which will not apply to +the RHEL 0.7.9 tree. + +Signed-off-by: Ryan McCabe +Resolves: rhbz#1492726 +--- + cloudinit/net/sysconfig.py | 6 +++++- + tests/unittests/test_distros/test_netconfig.py | 4 +++- + tests/unittests/test_net.py | 1 + + 3 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py +index e34c8491..380daedf 100644 +--- a/cloudinit/net/sysconfig.py ++++ b/cloudinit/net/sysconfig.py +@@ -348,7 +348,11 @@ class Renderer(renderer.Renderer): + subnet['netmask'] + + if 'gateway' in subnet: +- iface_cfg['GATEWAY'] = subnet['gateway'] ++ iface_cfg['DEFROUTE'] = True ++ if ":" in subnet['gateway']: ++ iface_cfg['IPV6_DEFAULTGW'] = subnet['gateway'] ++ else: ++ iface_cfg['GATEWAY'] = subnet['gateway'] + + if 'dns_search' in subnet: + if isinstance(subnet['dns_search'], (list, tuple)): +diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py +index 108d5741..10e25a72 100644 +--- a/tests/unittests/test_distros/test_netconfig.py ++++ b/tests/unittests/test_distros/test_netconfig.py +@@ -257,6 +257,7 @@ NETWORKING=yes + # Created by cloud-init on instance boot automatically, do not edit. + # + BOOTPROTO=none ++DEFROUTE=yes + DEVICE=eth0 + IPADDR=192.168.1.5 + NETMASK=255.255.255.0 +@@ -395,10 +396,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 +-GATEWAY=2607:f0d0:1002:0011::1 + IPV6INIT=yes ++IPV6_DEFAULTGW=2607:f0d0:1002:0011::1 + ONBOOT=yes + TYPE=Ethernet + USERCTL=no +diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py +index 551370d4..34af5daa 100644 +--- a/tests/unittests/test_net.py ++++ b/tests/unittests/test_net.py +@@ -833,6 +833,7 @@ USERCTL=no + # 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 +-- +2.14.3 + diff --git a/SOURCES/0036-sysconfig-Don-t-write-BOOTPROTO-dhcp-for-ipv6-dhcp.patch b/SOURCES/0036-sysconfig-Don-t-write-BOOTPROTO-dhcp-for-ipv6-dhcp.patch new file mode 100644 index 0000000..b34b8bc --- /dev/null +++ b/SOURCES/0036-sysconfig-Don-t-write-BOOTPROTO-dhcp-for-ipv6-dhcp.patch @@ -0,0 +1,32 @@ +From cbc09ba2f8e6fa967232039e6f6a3363d54ba592 Mon Sep 17 00:00:00 2001 +From: Ryan McCabe +Date: Mon, 4 Dec 2017 13:43:30 -0500 +Subject: [PATCH] sysconfig: Don't write BOOTPROTO=dhcp for ipv6 dhcp + +Don't write BOOTPROTO=dhcp for ipv6 dhcp, as BOOTPROTO applies +only to ipv4. Explicitly write IPV6_AUTOCONF=no for dhcp on ipv6. + +X-downstream-only: yes + +Resolves: rhbz#1519271 +Signed-off-by: Ryan McCabe +--- + cloudinit/net/sysconfig.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py +index e34c8491..bff844cd 100644 +--- a/cloudinit/net/sysconfig.py ++++ b/cloudinit/net/sysconfig.py +@@ -287,7 +287,7 @@ class Renderer(renderer.Renderer): + if subnet_type == 'dhcp6': + iface_cfg['IPV6INIT'] = True + iface_cfg['DHCPV6C'] = True +- iface_cfg['BOOTPROTO'] = 'dhcp' ++ iface_cfg['IPV6_AUTOCONF'] = False + elif subnet_type in ['dhcp4', 'dhcp']: + iface_cfg['BOOTPROTO'] = 'dhcp' + elif subnet_type == 'static': +-- +2.14.3 + diff --git a/SOURCES/0037-sysconfig-Fix-traceback.patch b/SOURCES/0037-sysconfig-Fix-traceback.patch new file mode 100644 index 0000000..433614e --- /dev/null +++ b/SOURCES/0037-sysconfig-Fix-traceback.patch @@ -0,0 +1,30 @@ +From a11ca2707b4b1330d3e4c83c6cf37295909bd0fc Mon Sep 17 00:00:00 2001 +From: Ryan McCabe +Date: Mon, 15 Jan 2018 09:23:09 -0500 +Subject: [PATCH] sysconfig: Fix traceback + +Fix a typo that caused a traceback in some situations that was introduced +as part of the fix for rhbz#1489270 + +Signed-off-by: Ryan McCabe +Resolves: rhbz#1489270 +--- + cloudinit/net/sysconfig.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py +index bff844cd..3c5d0615 100644 +--- a/cloudinit/net/sysconfig.py ++++ b/cloudinit/net/sysconfig.py +@@ -359,7 +359,7 @@ class Renderer(renderer.Renderer): + iface_cfg['DOMAIN'] = subnet['dns_search'] + + if 'dns_nameservers' in subnet: +- if isinstance(subnet['dns_search'], (list, tuple)): ++ if isinstance(subnet['dns_nameservers'], (list, tuple)): + # Currently limited to 3 entries per resolv.conf(5) + dns_list = subnet['dns_nameservers'][:3] + for i, k in enumerate(dns_list, 1): +-- +2.14.3 + diff --git a/SOURCES/0038-Fix-bug-that-resulted-in-an-attempt-to-rename-bonds.patch b/SOURCES/0038-Fix-bug-that-resulted-in-an-attempt-to-rename-bonds.patch new file mode 100644 index 0000000..d79bbc8 --- /dev/null +++ b/SOURCES/0038-Fix-bug-that-resulted-in-an-attempt-to-rename-bonds.patch @@ -0,0 +1,252 @@ +From 4ada8f2ec17d11835b44ab3d7786e5f3a732df41 Mon Sep 17 00:00:00 2001 +From: Scott Moser +Date: Fri, 31 Mar 2017 10:56:04 -0400 +Subject: [PATCH] Fix bug that resulted in an attempt to rename bonds or vlans. + +When cloud-init ran in the init stage (after networking had come up). +A bug could occur where cloud-init would attempt and fail to rename +network devices that had "inherited" mac addresses. + +The intent of apply_network_config_names was always to rename only +the devices that were "physical" per the network config. (This would +include veth devices in a container). The bug was in creating +the dictionary of interfaces by mac address. If there were multiple +interfaces with the same mac address then renames could fail. +This situation was guaranteed to occur with bonds or vlans or other +devices that inherit their mac. + +The solution is to change get_interfaces_by_mac to skip interfaces +that have an inherited mac. + +Also drop the 'devs' argument to get_interfaces_by_mac. It was +non-obvious what the result should be if a device in the input +list was filtered out. ie should the following have an entry for +bond0 or not. get_interfaces_by_mac(devs=['bond0']) + +LP: #1669860 +(cherry picked from commit bf7723e8092bb1f8a442aa2399dd870e130a27d9) + +Resolves: rhbz#1512247 +Signed-off-by: Ryan McCabe +(cherry picked from commit f9e8f13f916fe740e46c9a0e9dd2dbb3cdb39975) +--- + cloudinit/net/__init__.py | 78 ++++++++++++++++++++++++++++++++++----------- + tests/unittests/test_net.py | 76 +++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 135 insertions(+), 19 deletions(-) + mode change 100755 => 100644 cloudinit/net/__init__.py + +diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py +old mode 100755 +new mode 100644 +index ea649cc2..ab7e8996 +--- a/cloudinit/net/__init__.py ++++ b/cloudinit/net/__init__.py +@@ -82,6 +82,10 @@ def is_wireless(devname): + return os.path.exists(sys_dev_path(devname, "wireless")) + + ++def is_bridge(devname): ++ return os.path.exists(sys_dev_path(devname, "bridge")) ++ ++ + def is_connected(devname): + # is_connected isn't really as simple as that. 2 is + # 'physically connected'. 3 is 'not connected'. but a wlan interface will +@@ -132,7 +136,7 @@ def generate_fallback_config(): + for interface in potential_interfaces: + if interface.startswith("veth"): + continue +- if os.path.exists(sys_dev_path(interface, "bridge")): ++ if is_bridge(interface): + # skip any bridges + continue + carrier = read_sys_net_int(interface, 'carrier') +@@ -187,7 +191,11 @@ def apply_network_config_names(netcfg, strict_present=True, strict_busy=True): + """read the network config and rename devices accordingly. + if strict_present is false, then do not raise exception if no devices + match. if strict_busy is false, then do not raise exception if the +- device cannot be renamed because it is currently configured.""" ++ device cannot be renamed because it is currently configured. ++ ++ renames are only attempted for interfaces of type 'physical'. It is ++ expected that the network system will create other devices with the ++ correct name in place.""" + renames = [] + for ent in netcfg.get('config', {}): + if ent.get('type') != 'physical': +@@ -201,13 +209,35 @@ def apply_network_config_names(netcfg, strict_present=True, strict_busy=True): + return _rename_interfaces(renames) + + ++def interface_has_own_mac(ifname, strict=False): ++ """return True if the provided interface has its own address. ++ ++ Based on addr_assign_type in /sys. Return true for any interface ++ that does not have a 'stolen' address. Examples of such devices ++ are bonds or vlans that inherit their mac from another device. ++ Possible values are: ++ 0: permanent address 2: stolen from another device ++ 1: randomly generated 3: set using dev_set_mac_address""" ++ ++ assign_type = read_sys_net_int(ifname, "addr_assign_type") ++ if strict and assign_type is None: ++ raise ValueError("%s had no addr_assign_type.") ++ return assign_type in (0, 1, 3) ++ ++ + def _get_current_rename_info(check_downable=True): +- """Collect information necessary for rename_interfaces.""" +- names = get_devicelist() ++ """Collect information necessary for rename_interfaces. ++ ++ returns a dictionary by mac address like: ++ {mac: ++ {'name': name ++ 'up': boolean: is_up(name), ++ 'downable': None or boolean indicating that the ++ device has only automatically assigned ip addrs.}} ++ """ + bymac = {} +- for n in names: +- bymac[get_interface_mac(n)] = { +- 'name': n, 'up': is_up(n), 'downable': None} ++ for mac, name in get_interfaces_by_mac().items(): ++ bymac[mac] = {'name': name, 'up': is_up(name), 'downable': None} + + if check_downable: + nmatch = re.compile(r"[0-9]+:\s+(\w+)[@:]") +@@ -346,22 +376,32 @@ def get_interface_mac(ifname): + return read_sys_net_safe(ifname, path) + + +-def get_interfaces_by_mac(devs=None): +- """Build a dictionary of tuples {mac: name}""" +- if devs is None: +- try: +- devs = get_devicelist() +- except OSError as e: +- if e.errno == errno.ENOENT: +- devs = [] +- else: +- raise ++def get_interfaces_by_mac(): ++ """Build a dictionary of tuples {mac: name}. ++ ++ Bridges and any devices that have a 'stolen' mac are excluded.""" ++ try: ++ devs = get_devicelist() ++ except OSError as e: ++ if e.errno == errno.ENOENT: ++ devs = [] ++ else: ++ raise + ret = {} + for name in devs: ++ if not interface_has_own_mac(name): ++ continue ++ if is_bridge(name): ++ continue + mac = get_interface_mac(name) + # some devices may not have a mac (tun0) +- if mac: +- ret[mac] = name ++ if not mac: ++ continue ++ if mac in ret: ++ raise RuntimeError( ++ "duplicate mac found! both '%s' and '%s' have mac '%s'" % ++ (name, ret[mac], mac)) ++ ret[mac] = name + return ret + + # vi: ts=4 expandtab +diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py +index 551370d4..cadaf596 100644 +--- a/tests/unittests/test_net.py ++++ b/tests/unittests/test_net.py +@@ -1173,6 +1173,82 @@ class TestEniRoundTrip(TestCase): + expected, [line for line in found if line]) + + ++class TestGetInterfacesByMac(TestCase): ++ _data = {'devices': ['enp0s1', 'enp0s2', 'bond1', 'bridge1', ++ 'bridge1-nic', 'tun0'], ++ 'bonds': ['bond1'], ++ 'bridges': ['bridge1'], ++ 'own_macs': ['enp0s1', 'enp0s2', 'bridge1-nic', 'bridge1'], ++ 'macs': {'enp0s1': 'aa:aa:aa:aa:aa:01', ++ 'enp0s2': 'aa:aa:aa:aa:aa:02', ++ 'bond1': 'aa:aa:aa:aa:aa:01', ++ 'bridge1': 'aa:aa:aa:aa:aa:03', ++ 'bridge1-nic': 'aa:aa:aa:aa:aa:03', ++ 'tun0': None}} ++ data = {} ++ ++ def _se_get_devicelist(self): ++ return self.data['devices'] ++ ++ def _se_get_interface_mac(self, name): ++ return self.data['macs'][name] ++ ++ def _se_is_bridge(self, name): ++ return name in self.data['bridges'] ++ ++ def _se_interface_has_own_mac(self, name): ++ return name in self.data['own_macs'] ++ ++ def _mock_setup(self): ++ self.data = copy.deepcopy(self._data) ++ mocks = ('get_devicelist', 'get_interface_mac', 'is_bridge', ++ 'interface_has_own_mac') ++ self.mocks = {} ++ for n in mocks: ++ m = mock.patch('cloudinit.net.' + n, ++ side_effect=getattr(self, '_se_' + n)) ++ self.addCleanup(m.stop) ++ self.mocks[n] = m.start() ++ ++ def test_raise_exception_on_duplicate_macs(self): ++ self._mock_setup() ++ self.data['macs']['bridge1-nic'] = self.data['macs']['enp0s1'] ++ self.assertRaises(RuntimeError, net.get_interfaces_by_mac) ++ ++ def test_excludes_any_without_mac_address(self): ++ self._mock_setup() ++ ret = net.get_interfaces_by_mac() ++ self.assertIn('tun0', self._se_get_devicelist()) ++ self.assertNotIn('tun0', ret.values()) ++ ++ def test_excludes_stolen_macs(self): ++ self._mock_setup() ++ ret = net.get_interfaces_by_mac() ++ self.mocks['interface_has_own_mac'].assert_has_calls( ++ [mock.call('enp0s1'), mock.call('bond1')], any_order=True) ++ self.assertEqual( ++ {'aa:aa:aa:aa:aa:01': 'enp0s1', 'aa:aa:aa:aa:aa:02': 'enp0s2', ++ 'aa:aa:aa:aa:aa:03': 'bridge1-nic'}, ++ ret) ++ ++ def test_excludes_bridges(self): ++ self._mock_setup() ++ # add a device 'b1', make all return they have their "own mac", ++ # set everything other than 'b1' to be a bridge. ++ # then expect b1 is the only thing left. ++ self.data['macs']['b1'] = 'aa:aa:aa:aa:aa:b1' ++ self.data['devices'].append('b1') ++ self.data['bonds'] = [] ++ self.data['own_macs'] = self.data['devices'] ++ self.data['bridges'] = [f for f in self.data['devices'] if f != "b1"] ++ ret = net.get_interfaces_by_mac() ++ self.assertEqual({'aa:aa:aa:aa:aa:b1': 'b1'}, ret) ++ self.mocks['is_bridge'].assert_has_calls( ++ [mock.call('bridge1'), mock.call('enp0s1'), mock.call('bond1'), ++ mock.call('b1')], ++ any_order=True) ++ ++ + def _gzip_data(data): + with io.BytesIO() as iobuf: + gzfp = gzip.GzipFile(mode="wb", fileobj=iobuf) +-- +2.14.3 + diff --git a/SOURCES/0039-azure-Fix-publishing-of-hostname.patch b/SOURCES/0039-azure-Fix-publishing-of-hostname.patch new file mode 100644 index 0000000..f4cb744 --- /dev/null +++ b/SOURCES/0039-azure-Fix-publishing-of-hostname.patch @@ -0,0 +1,33 @@ +From d3a5f6f36ca673bc69d2e020c65fb85bf0dfc519 Mon Sep 17 00:00:00 2001 +From: Ryan McCabe +Date: Tue, 13 Feb 2018 22:42:17 -0500 +Subject: [PATCH] azure: Fix publishing of hostname + +Set the DHCP_HOSTNAME env var before bouncing the interfaces +to ensure that the hostname is published correctly. + +Resolves: rhbz#1434109 + +X-downstream-only: yes +Signed-off-by: Ryan McCabe +--- + cloudinit/sources/DataSourceAzure.py | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py +index 48a3e1df..a0ebd2ba 100644 +--- a/cloudinit/sources/DataSourceAzure.py ++++ b/cloudinit/sources/DataSourceAzure.py +@@ -27,7 +27,8 @@ AGENT_START = ['service', 'walinuxagent', 'start'] + AGENT_START_BUILTIN = "__builtin__" + BOUNCE_COMMAND = [ + 'sh', '-xc', +- "i=$interface; x=0; ifdown $i || x=$?; ifup $i || x=$?; exit $x" ++ "i=$interface; DHCP_HOSTNAME=$hostname; x=0; " ++ "ifdown $i || x=$?; ifup $i || x=$?; exit $x" + ] + # azure systems will always have a resource disk, and 66-azure-ephemeral.rules + # ensures that it gets linked to this path. +-- +2.14.3 + diff --git a/SPECS/cloud-init.spec b/SPECS/cloud-init.spec index 2eb667e..7fffb3c 100644 --- a/SPECS/cloud-init.spec +++ b/SPECS/cloud-init.spec @@ -7,7 +7,7 @@ Name: cloud-init Version: 0.7.9 -Release: 9%{?dist}.6 +Release: 24%{?dist} Summary: Cloud instance init scripts Group: System Environment/Base @@ -42,20 +42,26 @@ Patch0017: 0017-sysconfig-Raise-ValueError-when-multiple-default-gat.patch Patch0018: 0018-Fix-dual-stack-IPv4-IPv6-configuration-for-RHEL.patch Patch0019: 0019-Add-missing-sysconfig-unit-test-data.patch Patch0020: 0020-Fix-ipv6-subnet-detection.patch +# Not applied to work around additional issues related to rhbz#1474226 #Patch0021: 0021-azure-ensure-that-networkmanager-hook-script-runs.patch Patch0022: 0022-RHEL-CentOS-Fix-default-routes-for-IPv4-IPv6-configu.patch Patch0023: 0023-DatasourceEc2-add-warning-message-when-not-on-AWS.patch Patch0024: 0024-Identify-Brightbox-as-an-Ec2-datasource-user.patch Patch0025: 0025-AliYun-Enable-platform-identification-and-enable-by-.patch Patch0026: 0026-Fix-alibaba-cloud-unit-tests-to-work-with-0.7.9.patch -Patch0027: 0027-systemd-create-run-cloud-init-enabled.patch -Patch0028: 0028-net-Allow-for-NetworkManager-configuration.patch +Patch0027: 0027-Fix-eni-rendering-of-multiple-IPs-per-interface.patch +Patch0028: 0028-systemd-create-run-cloud-init-enabled.patch Patch0029: 0029-support-loopback-as-a-device-type.patch -Patch0030: 0030-Render-the-GATEWAY-value-in-interface-files-which-ha.patch -Patch0031: 0031-sysconfig-Don-t-write-BOOTPROTO-dhcp-for-ipv6-dhcp.patch -Patch0032: 0032-sysconfig-Render-IPV6_DEFAULTGW-correctly.patch -Patch0033: 0033-sysconfig-Render-DNS-and-DOMAIN.patch - +Patch0030: 0030-sysconfig-include-GATEWAY-value-if-set-in-subnet.patch +Patch0031: 0031-rh_subscription-Perform-null-checks-for-enabled-and-.patch +Patch0032: 0032-net-Allow-for-NetworkManager-configuration.patch +Patch0033: 0033-Render-DNS-and-DOMAIN-lines-for-sysconfig.patch +Patch0034: 0034-Start_cloud_init_after_dbus.patch +Patch0035: 0035-sysconfig-Render-IPV6_DEFAULTGW-correctly.patch +Patch0036: 0036-sysconfig-Don-t-write-BOOTPROTO-dhcp-for-ipv6-dhcp.patch +Patch0037: 0037-sysconfig-Fix-traceback.patch +Patch0038: 0038-Fix-bug-that-resulted-in-an-attempt-to-rename-bonds.patch +Patch0039: 0039-azure-Fix-publishing-of-hostname.patch # Deal with noarch -> arch # https://bugzilla.redhat.com/show_bug.cgi?id=1067089 @@ -146,6 +152,20 @@ if [ $1 -eq 1 ] ; then /bin/systemctl enable cloud-final.service >/dev/null 2>&1 || : /bin/systemctl enable cloud-init.service >/dev/null 2>&1 || : /bin/systemctl enable cloud-init-local.service >/dev/null 2>&1 || : +elif [ $1 -eq 2 ]; then + # Upgrade. If the upgrade is from a version older than 0.7.9-8, + # there will be stale systemd config + /bin/systemctl is-enabled cloud-config.service >/dev/null 2>&1 && + /bin/systemctl reenable cloud-config.service >/dev/null 2>&1 || : + + /bin/systemctl is-enabled cloud-final.service >/dev/null 2>&1 && + /bin/systemctl reenable cloud-final.service >/dev/null 2>&1 || : + + /bin/systemctl is-enabled cloud-init.service >/dev/null 2>&1 && + /bin/systemctl reenable cloud-init.service >/dev/null 2>&1 || : + + /bin/systemctl is-enabled cloud-init-local.service >/dev/null 2>&1 && + /bin/systemctl reenable cloud-init-local.service >/dev/null 2>&1 || : fi %preun @@ -191,31 +211,70 @@ fi %config(noreplace) %{_sysconfdir}/rsyslog.d/21-cloudinit.conf %changelog -* Thu Feb 15 2018 Ryan McCabe 0.7.9-9.6 -- Correctly render DNS and DOMAIN for sysconfig - Resolves: rhbz#1545525 +* Tue Feb 13 2018 Ryan McCabe 0.7.9-24 +- Set DHCP_HOSTNAME on Azure to allow for the hostname to be + published correctly when bouncing the network. + Resolves: rhbz#1434109 + +* Mon Jan 15 2018 Ryan McCabe 0.7.9-23 +- Fix a bug tha caused cloud-init to fail as a result of trying + to rename bonds. + Resolves: rhbz#1512247 + +* Mon Jan 15 2018 Ryan McCabe 0.7.9-22 +- Apply patch from -21 + Resolves: rhbz#1489270 + +* Mon Jan 15 2018 Ryan McCabe 0.7.9-21 +- sysconfig: Fix a potential traceback introduced in the + 0.7.9-17 build + Resolves: rhbz#1489270 + +* Sun Dec 17 2017 Ryan McCabe 0.7.9-20 +- sysconfig: Correct rendering for dhcp on ipv6 + Resolves: rhbz#1519271 -* Fri Feb 02 2018 Ryan McCabe 0.7.9-9.5 +* Thu Nov 30 2017 Ryan McCabe 0.7.9-19 - sysconfig: Fix rendering of default gateway for ipv6 - Resolves: rhbz#1540094 + Resolves: rhbz#1492726 -* Tue Jan 30 2018 Ryan McCabe 0.7.9-9.4 -- sysconfig: Fix rendering of default gateway for ipv4 - Resolves: rhbz#1540094 -- sysconfig: Correct rendering for dhcp on ipv6 - Resolves: rhbz#1540093 +* Fri Nov 24 2017 Ryan McCabe 0.7.9-18 +- Start the cloud-init init local service after the dbus socket is created + so that the hostnamectl command works. + Resolves: rhbz#1450521 -* Mon Jan 22 2018 Ryan McCabe 0.7.9-9.3 +* Tue Nov 21 2017 Ryan McCabe 0.7.9-17 +- Correctly render DNS and DOMAIN for sysconfig + Resolves: rhbz#1489270 + +* Mon Nov 20 2017 Ryan McCabe 0.7.9-16 - Disable NetworkManager management of resolv.conf if nameservers are specified by configuration. - Resolves: rhbz#1537439 + Resolves: rhbz#1454491 + +* Mon Nov 13 2017 Ryan McCabe 0.7.9-15 +- Fix a null reference error in the rh_subscription module + Resolves: rhbz#1498974 -* Thu Dec 21 2017 Ryan McCabe 0.7.9-9.2 +* Mon Nov 13 2017 Ryan McCabe 0-7.9-14 +- Include gateway if it's included in subnet configration + Resolves: rhbz#1492726 + +* Sun Nov 12 2017 Ryan McCabe 0-7.9-13 +- Do proper cleanup of systemd units when upgrading from versions + 0.7.9-3 through 0.7.9-8. + Resolves: rhbz#1465730 + +* Thu Nov 09 2017 Ryan McCabe 0.7.9-12 - Prevent Azure NM and dhclient hooks from running when cloud-init is - disabled (rhbz#1530127) + disabled (rhbz#1474226) + +* Tue Oct 31 2017 Ryan McCabe 0.7.9-11 +- Fix rendering of multiple static IPs per interface file + Resolves: rhbz#bz1497954 -* Tue Sep 26 2017 Ryan McCabe 0.7.9-9.1 -- Support AliCloud datasource (rhbz#1496113) +* Tue Sep 26 2017 Ryan McCabe 0.7.9-10 +- AliCloud: Add support for the Alibaba Cloud datasource (rhbz#1482547) * Thu Jun 22 2017 Lars Kellogg-Stedman 0.7.9-9 - RHEL/CentOS: Fix default routes for IPv4/IPv6 configuration. (rhbz#1438082)