From cd23205773943e58a762d710d7ff1f30aebd331db1afd6d95800d1e168468abd Mon Sep 17 00:00:00 2001 From: Robert Schweikert Date: Fri, 5 Jul 2019 18:59:55 +0000 Subject: [PATCH 1/3] - Follow the ever changing inconsistencies of version definitions and detection in the build service. + No more suse_version in SUSE internal instance for SLES 15 SP1 OBS-URL: https://build.opensuse.org/package/show/Cloud:Tools/cloud-init?expand=0&rev=142 --- cloud-init.changes | 7 +++++++ cloud-init.spec | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/cloud-init.changes b/cloud-init.changes index 6d510a6..2c1146d 100644 --- a/cloud-init.changes +++ b/cloud-init.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Fri Jul 5 18:58:22 UTC 2019 - Robert Schweikert + +- Follow the ever changing inconsistencies of version definitions and + detection in the build service. + + No more suse_version in SUSE internal instance for SLES 15 SP1 + ------------------------------------------------------------------- Tue Jun 11 12:37:16 UTC 2019 - Dominique Leuenberger diff --git a/cloud-init.spec b/cloud-init.spec index aeb9233..61505c2 100644 --- a/cloud-init.spec +++ b/cloud-init.spec @@ -75,11 +75,15 @@ BuildRequires: python-requests BuildRequires: python-setuptools BuildRequires: python-six %endif +%if 0%{?sle_version} +BuildRequires: sles-release +%else %if 0%{?is_opensuse} || 0%{?suse_version} == 1310 || 0%{?suse_version} == 1320 BuildRequires: openSUSE-release %else BuildRequires: sles-release %endif +%endif BuildRequires: util-linux Requires: bash Requires: dhcp-client From eceb75bdd48e37c2b2c12dc33fcdcc02cbbc21b0a6e1831fc5f6958603647c94 Mon Sep 17 00:00:00 2001 From: Robert Schweikert Date: Fri, 5 Jul 2019 19:11:25 +0000 Subject: [PATCH 2/3] - And another try at the version BS OBS-URL: https://build.opensuse.org/package/show/Cloud:Tools/cloud-init?expand=0&rev=143 --- cloud-init.spec | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cloud-init.spec b/cloud-init.spec index 61505c2..1149ddb 100644 --- a/cloud-init.spec +++ b/cloud-init.spec @@ -75,15 +75,11 @@ BuildRequires: python-requests BuildRequires: python-setuptools BuildRequires: python-six %endif -%if 0%{?sle_version} -BuildRequires: sles-release -%else -%if 0%{?is_opensuse} || 0%{?suse_version} == 1310 || 0%{?suse_version} == 1320 +%if 0%{?is_opensuse} BuildRequires: openSUSE-release %else BuildRequires: sles-release %endif -%endif BuildRequires: util-linux Requires: bash Requires: dhcp-client From ddfb957280490beef7dd41c8b297c2dd4a8a3995482c08a5438e21eeafda23e9 Mon Sep 17 00:00:00 2001 From: Robert Schweikert Date: Thu, 5 Sep 2019 13:32:10 +0000 Subject: [PATCH 3/3] - Add cloud-init-add-static-routes.diff (bsc#1141969) + Properly handle static routes. The EphemeralDHCP context manager did not parse or handle rfc3442 classless static routes which prevented reading datasource metadata in some clouds. - Update cloud-init-trigger-udev.patch (bsc#1144363) - The __str__ implementation no longer delivers the name of the interface, use the "name" attribute instead to form a proper path in the sysfs tree - Update cloud-init-write-routes.patch (bsc#1144881) + If no routes are set for a subnet but the subnet has a gateway specified, set the gateway as the default route for the interface OBS-URL: https://build.opensuse.org/package/show/Cloud:Tools/cloud-init?expand=0&rev=144 --- cloud-init-add-static-routes.diff | 426 ++++++++++++++++++++++++++++++ cloud-init-trigger-udev.patch | 2 +- cloud-init-write-routes.patch | 9 +- cloud-init.changes | 23 ++ cloud-init.spec | 3 + 5 files changed, 461 insertions(+), 2 deletions(-) create mode 100644 cloud-init-add-static-routes.diff diff --git a/cloud-init-add-static-routes.diff b/cloud-init-add-static-routes.diff new file mode 100644 index 0000000..7a043cb --- /dev/null +++ b/cloud-init-add-static-routes.diff @@ -0,0 +1,426 @@ +From 07b17236be5665bb552c7460102bcd07bf8f2be8 Mon Sep 17 00:00:00 2001 +From: Ryan Harper +Date: Tue, 16 Jul 2019 22:40:15 +0000 +Subject: net: add rfc3442 (classless static routes) to EphemeralDHCP + +The EphemeralDHCP context manager did not parse or handle +rfc3442 classless static routes which prevented reading +datasource metadata in some clouds. This branch adds support +for extracting the field from the leases output, parsing the +format and then adding the required iproute2 ip commands to +apply (and teardown) the static routes. + +LP: #1821102 + +diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py +index e758006..624c9b4 100644 +--- a/cloudinit/net/__init__.py ++++ b/cloudinit/net/__init__.py +@@ -679,7 +679,7 @@ class EphemeralIPv4Network(object): + """ + + def __init__(self, interface, ip, prefix_or_mask, broadcast, router=None, +- connectivity_url=None): ++ connectivity_url=None, static_routes=None): + """Setup context manager and validate call signature. + + @param interface: Name of the network interface to bring up. +@@ -690,6 +690,7 @@ class EphemeralIPv4Network(object): + @param router: Optionally the default gateway IP. + @param connectivity_url: Optionally, a URL to verify if a usable + connection already exists. ++ @param static_routes: Optionally a list of static routes from DHCP + """ + if not all([interface, ip, prefix_or_mask, broadcast]): + raise ValueError( +@@ -706,6 +707,7 @@ class EphemeralIPv4Network(object): + self.ip = ip + self.broadcast = broadcast + self.router = router ++ self.static_routes = static_routes + self.cleanup_cmds = [] # List of commands to run to cleanup state. + + def __enter__(self): +@@ -718,7 +720,21 @@ class EphemeralIPv4Network(object): + return + + self._bringup_device() +- if self.router: ++ ++ # rfc3442 requires us to ignore the router config *if* classless static ++ # routes are provided. ++ # ++ # https://tools.ietf.org/html/rfc3442 ++ # ++ # If the DHCP server returns both a Classless Static Routes option and ++ # a Router option, the DHCP client MUST ignore the Router option. ++ # ++ # Similarly, if the DHCP server returns both a Classless Static Routes ++ # option and a Static Routes option, the DHCP client MUST ignore the ++ # Static Routes option. ++ if self.static_routes: ++ self._bringup_static_routes() ++ elif self.router: + self._bringup_router() + + def __exit__(self, excp_type, excp_value, excp_traceback): +@@ -762,6 +778,20 @@ class EphemeralIPv4Network(object): + ['ip', '-family', 'inet', 'addr', 'del', cidr, 'dev', + self.interface]) + ++ def _bringup_static_routes(self): ++ # static_routes = [("169.254.169.254/32", "130.56.248.255"), ++ # ("0.0.0.0/0", "130.56.240.1")] ++ for net_address, gateway in self.static_routes: ++ via_arg = [] ++ if gateway != "0.0.0.0/0": ++ via_arg = ['via', gateway] ++ util.subp( ++ ['ip', '-4', 'route', 'add', net_address] + via_arg + ++ ['dev', self.interface], capture=True) ++ self.cleanup_cmds.insert( ++ 0, ['ip', '-4', 'route', 'del', net_address] + via_arg + ++ ['dev', self.interface]) ++ + def _bringup_router(self): + """Perform the ip commands to fully setup the router if needed.""" + # Check if a default route exists and exit if it does +diff --git a/cloudinit/net/dhcp.py b/cloudinit/net/dhcp.py +index c98a97c..1737991 100644 +--- a/cloudinit/net/dhcp.py ++++ b/cloudinit/net/dhcp.py +@@ -92,10 +92,14 @@ class EphemeralDHCPv4(object): + nmap = {'interface': 'interface', 'ip': 'fixed-address', + 'prefix_or_mask': 'subnet-mask', + 'broadcast': 'broadcast-address', ++ 'static_routes': 'rfc3442-classless-static-routes', + 'router': 'routers'} + kwargs = dict([(k, self.lease.get(v)) for k, v in nmap.items()]) + if not kwargs['broadcast']: + kwargs['broadcast'] = bcip(kwargs['prefix_or_mask'], kwargs['ip']) ++ if kwargs['static_routes']: ++ kwargs['static_routes'] = ( ++ parse_static_routes(kwargs['static_routes'])) + if self.connectivity_url: + kwargs['connectivity_url'] = self.connectivity_url + ephipv4 = EphemeralIPv4Network(**kwargs) +@@ -272,4 +276,90 @@ def networkd_get_option_from_leases(keyname, leases_d=None): + return data[keyname] + return None + ++ ++def parse_static_routes(rfc3442): ++ """ parse rfc3442 format and return a list containing tuple of strings. ++ ++ The tuple is composed of the network_address (including net length) and ++ gateway for a parsed static route. ++ ++ @param rfc3442: string in rfc3442 format ++ @returns: list of tuple(str, str) for all valid parsed routes until the ++ first parsing error. ++ ++ E.g. ++ sr = parse_state_routes("32,169,254,169,254,130,56,248,255,0,130,56,240,1") ++ sr = [ ++ ("169.254.169.254/32", "130.56.248.255"), ("0.0.0.0/0", "130.56.240.1") ++ ] ++ ++ Python version of isc-dhclient's hooks: ++ /etc/dhcp/dhclient-exit-hooks.d/rfc3442-classless-routes ++ """ ++ # raw strings from dhcp lease may end in semi-colon ++ rfc3442 = rfc3442.rstrip(";") ++ tokens = rfc3442.split(',') ++ static_routes = [] ++ ++ def _trunc_error(cidr, required, remain): ++ msg = ("RFC3442 string malformed. Current route has CIDR of %s " ++ "and requires %s significant octets, but only %s remain. " ++ "Verify DHCP rfc3442-classless-static-routes value: %s" ++ % (cidr, required, remain, rfc3442)) ++ LOG.error(msg) ++ ++ current_idx = 0 ++ for idx, tok in enumerate(tokens): ++ if idx < current_idx: ++ continue ++ net_length = int(tok) ++ if net_length in range(25, 33): ++ req_toks = 9 ++ if len(tokens[idx:]) < req_toks: ++ _trunc_error(net_length, req_toks, len(tokens[idx:])) ++ return static_routes ++ net_address = ".".join(tokens[idx+1:idx+5]) ++ gateway = ".".join(tokens[idx+5:idx+req_toks]) ++ current_idx = idx + req_toks ++ elif net_length in range(17, 25): ++ req_toks = 8 ++ if len(tokens[idx:]) < req_toks: ++ _trunc_error(net_length, req_toks, len(tokens[idx:])) ++ return static_routes ++ net_address = ".".join(tokens[idx+1:idx+4] + ["0"]) ++ gateway = ".".join(tokens[idx+4:idx+req_toks]) ++ current_idx = idx + req_toks ++ elif net_length in range(9, 17): ++ req_toks = 7 ++ if len(tokens[idx:]) < req_toks: ++ _trunc_error(net_length, req_toks, len(tokens[idx:])) ++ return static_routes ++ net_address = ".".join(tokens[idx+1:idx+3] + ["0", "0"]) ++ gateway = ".".join(tokens[idx+3:idx+req_toks]) ++ current_idx = idx + req_toks ++ elif net_length in range(1, 9): ++ req_toks = 6 ++ if len(tokens[idx:]) < req_toks: ++ _trunc_error(net_length, req_toks, len(tokens[idx:])) ++ return static_routes ++ net_address = ".".join(tokens[idx+1:idx+2] + ["0", "0", "0"]) ++ gateway = ".".join(tokens[idx+2:idx+req_toks]) ++ current_idx = idx + req_toks ++ elif net_length == 0: ++ req_toks = 5 ++ if len(tokens[idx:]) < req_toks: ++ _trunc_error(net_length, req_toks, len(tokens[idx:])) ++ return static_routes ++ net_address = "0.0.0.0" ++ gateway = ".".join(tokens[idx+1:idx+req_toks]) ++ current_idx = idx + req_toks ++ else: ++ LOG.error('Parsed invalid net length "%s". Verify DHCP ' ++ 'rfc3442-classless-static-routes value.', net_length) ++ return static_routes ++ ++ static_routes.append(("%s/%s" % (net_address, net_length), gateway)) ++ ++ return static_routes ++ + # vi: ts=4 expandtab +diff --git a/cloudinit/net/tests/test_dhcp.py b/cloudinit/net/tests/test_dhcp.py +index 5139024..91f503c 100644 +--- a/cloudinit/net/tests/test_dhcp.py ++++ b/cloudinit/net/tests/test_dhcp.py +@@ -8,7 +8,8 @@ from textwrap import dedent + import cloudinit.net as net + from cloudinit.net.dhcp import ( + InvalidDHCPLeaseFileError, maybe_perform_dhcp_discovery, +- parse_dhcp_lease_file, dhcp_discovery, networkd_load_leases) ++ parse_dhcp_lease_file, dhcp_discovery, networkd_load_leases, ++ parse_static_routes) + from cloudinit.util import ensure_file, write_file + from cloudinit.tests.helpers import ( + CiTestCase, HttprettyTestCase, mock, populate_dir, wrap_and_call) +@@ -64,6 +65,123 @@ class TestParseDHCPLeasesFile(CiTestCase): + self.assertItemsEqual(expected, parse_dhcp_lease_file(lease_file)) + + ++class TestDHCPRFC3442(CiTestCase): ++ ++ def test_parse_lease_finds_rfc3442_classless_static_routes(self): ++ """parse_dhcp_lease_file returns rfc3442-classless-static-routes.""" ++ lease_file = self.tmp_path('leases') ++ content = dedent(""" ++ lease { ++ interface "wlp3s0"; ++ fixed-address 192.168.2.74; ++ option subnet-mask 255.255.255.0; ++ option routers 192.168.2.1; ++ option rfc3442-classless-static-routes 0,130,56,240,1; ++ renew 4 2017/07/27 18:02:30; ++ expire 5 2017/07/28 07:08:15; ++ } ++ """) ++ expected = [ ++ {'interface': 'wlp3s0', 'fixed-address': '192.168.2.74', ++ 'subnet-mask': '255.255.255.0', 'routers': '192.168.2.1', ++ 'rfc3442-classless-static-routes': '0,130,56,240,1', ++ 'renew': '4 2017/07/27 18:02:30', ++ 'expire': '5 2017/07/28 07:08:15'}] ++ write_file(lease_file, content) ++ self.assertItemsEqual(expected, parse_dhcp_lease_file(lease_file)) ++ ++ @mock.patch('cloudinit.net.dhcp.EphemeralIPv4Network') ++ @mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery') ++ def test_obtain_lease_parses_static_routes(self, m_maybe, m_ipv4): ++ """EphemeralDHPCv4 parses rfc3442 routes for EphemeralIPv4Network""" ++ lease = [ ++ {'interface': 'wlp3s0', 'fixed-address': '192.168.2.74', ++ 'subnet-mask': '255.255.255.0', 'routers': '192.168.2.1', ++ 'rfc3442-classless-static-routes': '0,130,56,240,1', ++ 'renew': '4 2017/07/27 18:02:30', ++ 'expire': '5 2017/07/28 07:08:15'}] ++ m_maybe.return_value = lease ++ eph = net.dhcp.EphemeralDHCPv4() ++ eph.obtain_lease() ++ expected_kwargs = { ++ 'interface': 'wlp3s0', ++ 'ip': '192.168.2.74', ++ 'prefix_or_mask': '255.255.255.0', ++ 'broadcast': '192.168.2.255', ++ 'static_routes': [('0.0.0.0/0', '130.56.240.1')], ++ 'router': '192.168.2.1'} ++ m_ipv4.assert_called_with(**expected_kwargs) ++ ++ ++class TestDHCPParseStaticRoutes(CiTestCase): ++ ++ with_logs = True ++ ++ def parse_static_routes_empty_string(self): ++ self.assertEqual([], parse_static_routes("")) ++ ++ def test_parse_static_routes_invalid_input_returns_empty_list(self): ++ rfc3442 = "32,169,254,169,254,130,56,248" ++ self.assertEqual([], parse_static_routes(rfc3442)) ++ ++ def test_parse_static_routes_bogus_width_returns_empty_list(self): ++ rfc3442 = "33,169,254,169,254,130,56,248" ++ self.assertEqual([], parse_static_routes(rfc3442)) ++ ++ def test_parse_static_routes_single_ip(self): ++ rfc3442 = "32,169,254,169,254,130,56,248,255" ++ self.assertEqual([('169.254.169.254/32', '130.56.248.255')], ++ parse_static_routes(rfc3442)) ++ ++ def test_parse_static_routes_single_ip_handles_trailing_semicolon(self): ++ rfc3442 = "32,169,254,169,254,130,56,248,255;" ++ self.assertEqual([('169.254.169.254/32', '130.56.248.255')], ++ parse_static_routes(rfc3442)) ++ ++ def test_parse_static_routes_default_route(self): ++ rfc3442 = "0,130,56,240,1" ++ self.assertEqual([('0.0.0.0/0', '130.56.240.1')], ++ parse_static_routes(rfc3442)) ++ ++ def test_parse_static_routes_class_c_b_a(self): ++ class_c = "24,192,168,74,192,168,0,4" ++ class_b = "16,172,16,172,16,0,4" ++ class_a = "8,10,10,0,0,4" ++ rfc3442 = ",".join([class_c, class_b, class_a]) ++ self.assertEqual(sorted([ ++ ("192.168.74.0/24", "192.168.0.4"), ++ ("172.16.0.0/16", "172.16.0.4"), ++ ("10.0.0.0/8", "10.0.0.4") ++ ]), sorted(parse_static_routes(rfc3442))) ++ ++ def test_parse_static_routes_logs_error_truncated(self): ++ bad_rfc3442 = { ++ "class_c": "24,169,254,169,10", ++ "class_b": "16,172,16,10", ++ "class_a": "8,10,10", ++ "gateway": "0,0", ++ "netlen": "33,0", ++ } ++ for rfc3442 in bad_rfc3442.values(): ++ self.assertEqual([], parse_static_routes(rfc3442)) ++ ++ logs = self.logs.getvalue() ++ self.assertEqual(len(bad_rfc3442.keys()), len(logs.splitlines())) ++ ++ def test_parse_static_routes_returns_valid_routes_until_parse_err(self): ++ class_c = "24,192,168,74,192,168,0,4" ++ class_b = "16,172,16,172,16,0,4" ++ class_a_error = "8,10,10,0,0" ++ rfc3442 = ",".join([class_c, class_b, class_a_error]) ++ self.assertEqual(sorted([ ++ ("192.168.74.0/24", "192.168.0.4"), ++ ("172.16.0.0/16", "172.16.0.4"), ++ ]), sorted(parse_static_routes(rfc3442))) ++ ++ logs = self.logs.getvalue() ++ self.assertIn(rfc3442, logs.splitlines()[0]) ++ ++ + class TestDHCPDiscoveryClean(CiTestCase): + with_logs = True + +diff --git a/cloudinit/net/tests/test_init.py b/cloudinit/net/tests/test_init.py +index 6d2affe..d393e6a 100644 +--- a/cloudinit/net/tests/test_init.py ++++ b/cloudinit/net/tests/test_init.py +@@ -549,6 +549,45 @@ class TestEphemeralIPV4Network(CiTestCase): + self.assertEqual(expected_setup_calls, m_subp.call_args_list) + m_subp.assert_has_calls(expected_teardown_calls) + ++ def test_ephemeral_ipv4_network_with_rfc3442_static_routes(self, m_subp): ++ params = { ++ 'interface': 'eth0', 'ip': '192.168.2.2', ++ 'prefix_or_mask': '255.255.255.0', 'broadcast': '192.168.2.255', ++ 'static_routes': [('169.254.169.254/32', '192.168.2.1'), ++ ('0.0.0.0/0', '192.168.2.1')], ++ 'router': '192.168.2.1'} ++ expected_setup_calls = [ ++ mock.call( ++ ['ip', '-family', 'inet', 'addr', 'add', '192.168.2.2/24', ++ 'broadcast', '192.168.2.255', 'dev', 'eth0'], ++ capture=True, update_env={'LANG': 'C'}), ++ mock.call( ++ ['ip', '-family', 'inet', 'link', 'set', 'dev', 'eth0', 'up'], ++ capture=True), ++ mock.call( ++ ['ip', '-4', 'route', 'add', '169.254.169.254/32', ++ 'via', '192.168.2.1', 'dev', 'eth0'], capture=True), ++ mock.call( ++ ['ip', '-4', 'route', 'add', '0.0.0.0/0', ++ 'via', '192.168.2.1', 'dev', 'eth0'], capture=True)] ++ expected_teardown_calls = [ ++ mock.call( ++ ['ip', '-4', 'route', 'del', '0.0.0.0/0', ++ 'via', '192.168.2.1', 'dev', 'eth0'], capture=True), ++ mock.call( ++ ['ip', '-4', 'route', 'del', '169.254.169.254/32', ++ 'via', '192.168.2.1', 'dev', 'eth0'], capture=True), ++ mock.call( ++ ['ip', '-family', 'inet', 'link', 'set', 'dev', ++ 'eth0', 'down'], capture=True), ++ mock.call( ++ ['ip', '-family', 'inet', 'addr', 'del', ++ '192.168.2.2/24', 'dev', 'eth0'], capture=True) ++ ] ++ with net.EphemeralIPv4Network(**params): ++ self.assertEqual(expected_setup_calls, m_subp.call_args_list) ++ m_subp.assert_has_calls(expected_setup_calls + expected_teardown_calls) ++ + + class TestApplyNetworkCfgNames(CiTestCase): + V1_CONFIG = textwrap.dedent("""\ +diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py +index f27ef21..2de2aea 100644 +--- a/tests/unittests/test_datasource/test_azure.py ++++ b/tests/unittests/test_datasource/test_azure.py +@@ -1807,7 +1807,8 @@ class TestAzureDataSourcePreprovisioning(CiTestCase): + self.assertEqual(m_dhcp.call_count, 2) + m_net.assert_any_call( + broadcast='192.168.2.255', interface='eth9', ip='192.168.2.9', +- prefix_or_mask='255.255.255.0', router='192.168.2.1') ++ prefix_or_mask='255.255.255.0', router='192.168.2.1', ++ static_routes=None) + self.assertEqual(m_net.call_count, 2) + + def test__reprovision_calls__poll_imds(self, fake_resp, +@@ -1845,7 +1846,8 @@ class TestAzureDataSourcePreprovisioning(CiTestCase): + self.assertEqual(m_dhcp.call_count, 2) + m_net.assert_any_call( + broadcast='192.168.2.255', interface='eth9', ip='192.168.2.9', +- prefix_or_mask='255.255.255.0', router='192.168.2.1') ++ prefix_or_mask='255.255.255.0', router='192.168.2.1', ++ static_routes=None) + self.assertEqual(m_net.call_count, 2) + + +diff --git a/tests/unittests/test_datasource/test_ec2.py b/tests/unittests/test_datasource/test_ec2.py +index 20d59bf..1ec8e00 100644 +--- a/tests/unittests/test_datasource/test_ec2.py ++++ b/tests/unittests/test_datasource/test_ec2.py +@@ -538,7 +538,8 @@ class TestEc2(test_helpers.HttprettyTestCase): + m_dhcp.assert_called_once_with('eth9') + m_net.assert_called_once_with( + broadcast='192.168.2.255', interface='eth9', ip='192.168.2.9', +- prefix_or_mask='255.255.255.0', router='192.168.2.1') ++ prefix_or_mask='255.255.255.0', router='192.168.2.1', ++ static_routes=None) + self.assertIn('Crawl of metadata service took', self.logs.getvalue()) + + +-- +cgit v0.10.2 + diff --git a/cloud-init-trigger-udev.patch b/cloud-init-trigger-udev.patch index c4905bf..538e161 100644 --- a/cloud-init-trigger-udev.patch +++ b/cloud-init-trigger-udev.patch @@ -27,7 +27,7 @@ + # before they are all setup. Settle if that is the case. + for iface in network_state.iter_interfaces( + renderer.filter_by_physical): -+ path = net.sys_dev_path(str(iface)) ++ path = net.sys_dev_path(iface.name) + if not os.path.exists(path): + util.udevadm_settle(path, 5) + break diff --git a/cloud-init-write-routes.patch b/cloud-init-write-routes.patch index 3e005fe..06d3f8e 100644 --- a/cloud-init-write-routes.patch +++ b/cloud-init-write-routes.patch @@ -1,6 +1,6 @@ --- cloudinit/distros/opensuse.py.orig +++ cloudinit/distros/opensuse.py -@@ -172,7 +172,41 @@ class Distro(distros.Distro): +@@ -172,7 +172,48 @@ class Distro(distros.Distro): util.write_file(out_fn, str(conf), 0o644) def _write_network_config(self, netconfig): @@ -37,6 +37,13 @@ + config_routes += ' -' + config_routes += ' -' + config_routes += '\n' ++ if not config_routes: ++ dest = 'default' ++ gateway = subnet.get('gateway') ++ if gateway: ++ config_routes += ' '.join( ++ [dest, gateway, '-', '-', '\n'] ++ ) + if config_routes: + route_file = '/etc/sysconfig/network/ifroute-%s' % if_name + util.write_file(route_file, config_routes) diff --git a/cloud-init.changes b/cloud-init.changes index 2c1146d..64e902e 100644 --- a/cloud-init.changes +++ b/cloud-init.changes @@ -1,3 +1,26 @@ +------------------------------------------------------------------- +Mon Aug 19 16:46:35 UTC 2019 - Robert Schweikert + +- Add cloud-init-add-static-routes.diff (bsc#1141969) + + Properly handle static routes. The EphemeralDHCP context manager did + not parse or handle rfc3442 classless static routes which prevented + reading datasource metadata in some clouds. + +------------------------------------------------------------------- +Mon Aug 19 15:24:26 UTC 2019 - Robert Schweikert + +- Update cloud-init-trigger-udev.patch (bsc#1144363) + - The __str__ implementation no longer delivers the name of the interface, + use the "name" attribute instead to form a proper path in the + sysfs tree + +------------------------------------------------------------------- +Fri Aug 16 19:54:28 UTC 2019 - Robert Schweikert + +- Update cloud-init-write-routes.patch (bsc#1144881) + + If no routes are set for a subnet but the subnet has a gateway + specified, set the gateway as the default route for the interface + ------------------------------------------------------------------- Fri Jul 5 18:58:22 UTC 2019 - Robert Schweikert diff --git a/cloud-init.spec b/cloud-init.spec index 1149ddb..59a1115 100644 --- a/cloud-init.spec +++ b/cloud-init.spec @@ -46,6 +46,8 @@ Patch43: cloud-init-write-routes.patch Patch47: cloud-init-trigger-udev.patch # FIXME (lp#1669875) patch by mvoelker@launchpad Patch48: cloud-init-detect-nova.diff +# FIXME (lp##1821102) +Patch49: cloud-init-add-static-routes.diff BuildRequires: fdupes BuildRequires: filesystem @@ -190,6 +192,7 @@ Documentation and examples for cloud-init tools %patch43 %patch47 %patch48 -p1 +%patch49 -p1 %build %if 0%{?suse_version} && 0%{?suse_version} <= 1315