Merge ~chad.smith/cloud-init:ubuntu/disco into cloud-init:ubuntu/disco

Proposed by Chad Smith
Status: Merged
Merged at revision: f0141fe08a30d606035109975696bb1bd6c1c54d
Proposed branch: ~chad.smith/cloud-init:ubuntu/disco
Merge into: cloud-init:ubuntu/disco
Diff against target: 557 lines (+279/-81)
7 files modified
cloudinit/net/cmdline.py (+95/-32)
cloudinit/sources/DataSourceExoscale.py (+17/-9)
cloudinit/sources/DataSourceOracle.py (+20/-16)
cloudinit/sources/tests/test_oracle.py (+36/-3)
debian/changelog (+9/-0)
tests/unittests/test_datasource/test_exoscale.py (+16/-8)
tests/unittests/test_net.py (+86/-13)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Ryan Harper Approve
Review via email: mp+371959@code.launchpad.net

Commit message

new-upstream snapshot to get exoscale fixes before we complete current -proposed SRU

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:f0141fe08a30d606035109975696bb1bd6c1c54d
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1088/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    FAILED: Ubuntu LTS: Build

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1088//rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Ryan Harper (raharper) wrote :

This matches my output.

review: Approve
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:f0141fe08a30d606035109975696bb1bd6c1c54d
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1091/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    FAILED: Ubuntu LTS: Build

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1091//rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:f0141fe08a30d606035109975696bb1bd6c1c54d
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1092/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    FAILED: Ubuntu LTS: Integration

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1092//rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:f0141fe08a30d606035109975696bb1bd6c1c54d
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1093/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1093//rebuild

review: Approve (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:f0141fe08a30d606035109975696bb1bd6c1c54d
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1095/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1095//rebuild

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/cloudinit/net/cmdline.py b/cloudinit/net/cmdline.py
index 556a10f..55166ea 100755
--- a/cloudinit/net/cmdline.py
+++ b/cloudinit/net/cmdline.py
@@ -5,20 +5,95 @@
5#5#
6# This file is part of cloud-init. See LICENSE file for license information.6# This file is part of cloud-init. See LICENSE file for license information.
77
8import abc
8import base649import base64
9import glob10import glob
10import gzip11import gzip
11import io12import io
12import os13import os
1314
14from . import get_devicelist15import six
15from . import read_sys_net_safe
1616
17from cloudinit import util17from cloudinit import util
1818
19from . import get_devicelist
20from . import read_sys_net_safe
21
19_OPEN_ISCSI_INTERFACE_FILE = "/run/initramfs/open-iscsi.interface"22_OPEN_ISCSI_INTERFACE_FILE = "/run/initramfs/open-iscsi.interface"
2023
2124
25@six.add_metaclass(abc.ABCMeta)
26class InitramfsNetworkConfigSource(object):
27 """ABC for net config sources that read config written by initramfses"""
28
29 @abc.abstractmethod
30 def is_applicable(self):
31 # type: () -> bool
32 """Is this initramfs config source applicable to the current system?"""
33 pass
34
35 @abc.abstractmethod
36 def render_config(self):
37 # type: () -> dict
38 """Render a v1 network config from the initramfs configuration"""
39 pass
40
41
42class KlibcNetworkConfigSource(InitramfsNetworkConfigSource):
43 """InitramfsNetworkConfigSource for klibc initramfs (i.e. Debian/Ubuntu)
44
45 Has three parameters, but they are intended to make testing simpler, _not_
46 for use in production code. (This is indicated by the prepended
47 underscores.)
48 """
49
50 def __init__(self, _files=None, _mac_addrs=None, _cmdline=None):
51 self._files = _files
52 self._mac_addrs = _mac_addrs
53 self._cmdline = _cmdline
54
55 # Set defaults here, as they require computation that we don't want to
56 # do at method definition time
57 if self._files is None:
58 self._files = _get_klibc_net_cfg_files()
59 if self._cmdline is None:
60 self._cmdline = util.get_cmdline()
61 if self._mac_addrs is None:
62 self._mac_addrs = {}
63 for k in get_devicelist():
64 mac_addr = read_sys_net_safe(k, 'address')
65 if mac_addr:
66 self._mac_addrs[k] = mac_addr
67
68 def is_applicable(self):
69 # type: () -> bool
70 """
71 Return whether this system has klibc initramfs network config or not
72
73 Will return True if:
74 (a) klibc files exist in /run, AND
75 (b) either:
76 (i) ip= or ip6= are on the kernel cmdline, OR
77 (ii) an open-iscsi interface file is present in the system
78 """
79 if self._files:
80 if 'ip=' in self._cmdline or 'ip6=' in self._cmdline:
81 return True
82 if os.path.exists(_OPEN_ISCSI_INTERFACE_FILE):
83 # iBft can configure networking without ip=
84 return True
85 return False
86
87 def render_config(self):
88 # type: () -> dict
89 return config_from_klibc_net_cfg(
90 files=self._files, mac_addrs=self._mac_addrs,
91 )
92
93
94_INITRAMFS_CONFIG_SOURCES = [KlibcNetworkConfigSource]
95
96
22def _klibc_to_config_entry(content, mac_addrs=None):97def _klibc_to_config_entry(content, mac_addrs=None):
23 """Convert a klibc written shell content file to a 'config' entry98 """Convert a klibc written shell content file to a 'config' entry
24 When ip= is seen on the kernel command line in debian initramfs99 When ip= is seen on the kernel command line in debian initramfs
@@ -137,6 +212,24 @@ def config_from_klibc_net_cfg(files=None, mac_addrs=None):
137 return {'config': entries, 'version': 1}212 return {'config': entries, 'version': 1}
138213
139214
215def read_initramfs_config():
216 """
217 Return v1 network config for initramfs-configured networking (or None)
218
219 This will consider each _INITRAMFS_CONFIG_SOURCES entry in turn, and return
220 v1 network configuration for the first one that is applicable. If none are
221 applicable, return None.
222 """
223 for src_cls in _INITRAMFS_CONFIG_SOURCES:
224 cfg_source = src_cls()
225
226 if not cfg_source.is_applicable():
227 continue
228
229 return cfg_source.render_config()
230 return None
231
232
140def _decomp_gzip(blob, strict=True):233def _decomp_gzip(blob, strict=True):
141 # decompress blob. raise exception if not compressed unless strict=False.234 # decompress blob. raise exception if not compressed unless strict=False.
142 with io.BytesIO(blob) as iobuf:235 with io.BytesIO(blob) as iobuf:
@@ -167,36 +260,6 @@ def _b64dgz(b64str, gzipped="try"):
167 return _decomp_gzip(blob, strict=gzipped != "try")260 return _decomp_gzip(blob, strict=gzipped != "try")
168261
169262
170def _is_initramfs_netconfig(files, cmdline):
171 if files:
172 if 'ip=' in cmdline or 'ip6=' in cmdline:
173 return True
174 if os.path.exists(_OPEN_ISCSI_INTERFACE_FILE):
175 # iBft can configure networking without ip=
176 return True
177 return False
178
179
180def read_initramfs_config(files=None, mac_addrs=None, cmdline=None):
181 if cmdline is None:
182 cmdline = util.get_cmdline()
183
184 if files is None:
185 files = _get_klibc_net_cfg_files()
186
187 if not _is_initramfs_netconfig(files, cmdline):
188 return None
189
190 if mac_addrs is None:
191 mac_addrs = {}
192 for k in get_devicelist():
193 mac_addr = read_sys_net_safe(k, 'address')
194 if mac_addr:
195 mac_addrs[k] = mac_addr
196
197 return config_from_klibc_net_cfg(files=files, mac_addrs=mac_addrs)
198
199
200def read_kernel_cmdline_config(cmdline=None):263def read_kernel_cmdline_config(cmdline=None):
201 if cmdline is None:264 if cmdline is None:
202 cmdline = util.get_cmdline()265 cmdline = util.get_cmdline()
diff --git a/cloudinit/sources/DataSourceExoscale.py b/cloudinit/sources/DataSourceExoscale.py
index 52e7f6f..fdfb4ed 100644
--- a/cloudinit/sources/DataSourceExoscale.py
+++ b/cloudinit/sources/DataSourceExoscale.py
@@ -6,6 +6,7 @@
6from cloudinit import ec2_utils as ec26from cloudinit import ec2_utils as ec2
7from cloudinit import log as logging7from cloudinit import log as logging
8from cloudinit import sources8from cloudinit import sources
9from cloudinit import helpers
9from cloudinit import url_helper10from cloudinit import url_helper
10from cloudinit import util11from cloudinit import util
1112
@@ -20,13 +21,6 @@ URL_RETRIES = 6
2021
21EXOSCALE_DMI_NAME = "Exoscale"22EXOSCALE_DMI_NAME = "Exoscale"
2223
23BUILTIN_DS_CONFIG = {
24 # We run the set password config module on every boot in order to enable
25 # resetting the instance's password via the exoscale console (and a
26 # subsequent instance reboot).
27 'cloud_config_modules': [["set-passwords", "always"]]
28}
29
3024
31class DataSourceExoscale(sources.DataSource):25class DataSourceExoscale(sources.DataSource):
3226
@@ -42,8 +36,22 @@ class DataSourceExoscale(sources.DataSource):
42 self.ds_cfg.get('password_server_port', PASSWORD_SERVER_PORT))36 self.ds_cfg.get('password_server_port', PASSWORD_SERVER_PORT))
43 self.url_timeout = self.ds_cfg.get('timeout', URL_TIMEOUT)37 self.url_timeout = self.ds_cfg.get('timeout', URL_TIMEOUT)
44 self.url_retries = self.ds_cfg.get('retries', URL_RETRIES)38 self.url_retries = self.ds_cfg.get('retries', URL_RETRIES)
4539 self.extra_config = {}
46 self.extra_config = BUILTIN_DS_CONFIG40
41 def activate(self, cfg, is_new_instance):
42 """Adjust set-passwords module to run 'always' during each boot"""
43 # We run the set password config module on every boot in order to
44 # enable resetting the instance's password via the exoscale console
45 # (and a subsequent instance reboot).
46 # Exoscale password server only provides set-passwords user-data if
47 # a user has triggered a password reset. So calling that password
48 # service generally results in no additional cloud-config.
49 # TODO(Create util functions for overriding merged sys_cfg module freq)
50 mod = 'set_passwords'
51 sem_path = self.paths.get_ipath_cur('sem')
52 sem_helper = helpers.FileSemaphores(sem_path)
53 if sem_helper.clear('config_' + mod, None):
54 LOG.debug('Overriding module set-passwords with frequency always')
4755
48 def wait_for_metadata_service(self):56 def wait_for_metadata_service(self):
49 """Wait for the metadata service to be reachable."""57 """Wait for the metadata service to be reachable."""
diff --git a/cloudinit/sources/DataSourceOracle.py b/cloudinit/sources/DataSourceOracle.py
index 6e73f56..1cb0636 100644
--- a/cloudinit/sources/DataSourceOracle.py
+++ b/cloudinit/sources/DataSourceOracle.py
@@ -51,8 +51,8 @@ def _add_network_config_from_opc_imds(network_config):
51 include the secondary VNICs.51 include the secondary VNICs.
5252
53 :param network_config:53 :param network_config:
54 A v1 network config dict with the primary NIC already configured. This54 A v1 or v2 network config dict with the primary NIC already configured.
55 dict will be mutated.55 This dict will be mutated.
5656
57 :raises:57 :raises:
58 Exceptions are not handled within this function. Likely exceptions are58 Exceptions are not handled within this function. Likely exceptions are
@@ -88,20 +88,24 @@ def _add_network_config_from_opc_imds(network_config):
88 LOG.debug('Interface with MAC %s not found; skipping', mac_address)88 LOG.debug('Interface with MAC %s not found; skipping', mac_address)
89 continue89 continue
90 name = interfaces_by_mac[mac_address]90 name = interfaces_by_mac[mac_address]
91 subnet = {91
92 'type': 'static',92 if network_config['version'] == 1:
93 'address': vnic_dict['privateIp'],93 subnet = {
94 'netmask': vnic_dict['subnetCidrBlock'].split('/')[1],94 'type': 'static',
95 'gateway': vnic_dict['virtualRouterIp'],95 'address': vnic_dict['privateIp'],
96 'control': 'manual',96 }
97 }97 network_config['config'].append({
98 network_config['config'].append({98 'name': name,
99 'name': name,99 'type': 'physical',
100 'type': 'physical',100 'mac_address': mac_address,
101 'mac_address': mac_address,101 'mtu': MTU,
102 'mtu': MTU,102 'subnets': [subnet],
103 'subnets': [subnet],103 })
104 })104 elif network_config['version'] == 2:
105 network_config['ethernets'][name] = {
106 'addresses': [vnic_dict['privateIp']],
107 'mtu': MTU, 'dhcp4': False, 'dhcp6': False,
108 'match': {'macaddress': mac_address}}
105109
106110
107class DataSourceOracle(sources.DataSource):111class DataSourceOracle(sources.DataSource):
diff --git a/cloudinit/sources/tests/test_oracle.py b/cloudinit/sources/tests/test_oracle.py
index 3ddf7df..2a70bbc 100644
--- a/cloudinit/sources/tests/test_oracle.py
+++ b/cloudinit/sources/tests/test_oracle.py
@@ -526,6 +526,18 @@ class TestNetworkConfigFromOpcImds(test_helpers.CiTestCase):
526 'Interface with MAC 00:00:17:02:2b:b1 not found; skipping',526 'Interface with MAC 00:00:17:02:2b:b1 not found; skipping',
527 self.logs.getvalue())527 self.logs.getvalue())
528528
529 def test_missing_mac_skipped_v2(self):
530 self.m_readurl.return_value = OPC_VM_SECONDARY_VNIC_RESPONSE
531 self.m_get_interfaces_by_mac.return_value = {}
532
533 network_config = {'version': 2, 'ethernets': {'primary': {'nic': {}}}}
534 oracle._add_network_config_from_opc_imds(network_config)
535
536 self.assertEqual(1, len(network_config['ethernets']))
537 self.assertIn(
538 'Interface with MAC 00:00:17:02:2b:b1 not found; skipping',
539 self.logs.getvalue())
540
529 def test_secondary_nic(self):541 def test_secondary_nic(self):
530 self.m_readurl.return_value = OPC_VM_SECONDARY_VNIC_RESPONSE542 self.m_readurl.return_value = OPC_VM_SECONDARY_VNIC_RESPONSE
531 mac_addr, nic_name = '00:00:17:02:2b:b1', 'ens3'543 mac_addr, nic_name = '00:00:17:02:2b:b1', 'ens3'
@@ -549,8 +561,29 @@ class TestNetworkConfigFromOpcImds(test_helpers.CiTestCase):
549 subnet_cfg = secondary_nic_cfg['subnets'][0]561 subnet_cfg = secondary_nic_cfg['subnets'][0]
550 # These values are hard-coded in OPC_VM_SECONDARY_VNIC_RESPONSE562 # These values are hard-coded in OPC_VM_SECONDARY_VNIC_RESPONSE
551 self.assertEqual('10.0.0.231', subnet_cfg['address'])563 self.assertEqual('10.0.0.231', subnet_cfg['address'])
552 self.assertEqual('24', subnet_cfg['netmask'])564
553 self.assertEqual('10.0.0.1', subnet_cfg['gateway'])565 def test_secondary_nic_v2(self):
554 self.assertEqual('manual', subnet_cfg['control'])566 self.m_readurl.return_value = OPC_VM_SECONDARY_VNIC_RESPONSE
567 mac_addr, nic_name = '00:00:17:02:2b:b1', 'ens3'
568 self.m_get_interfaces_by_mac.return_value = {
569 mac_addr: nic_name,
570 }
571
572 network_config = {'version': 2, 'ethernets': {'primary': {'nic': {}}}}
573 oracle._add_network_config_from_opc_imds(network_config)
574
575 # The input is mutated
576 self.assertEqual(2, len(network_config['ethernets']))
577
578 secondary_nic_cfg = network_config['ethernets']['ens3']
579 self.assertFalse(secondary_nic_cfg['dhcp4'])
580 self.assertFalse(secondary_nic_cfg['dhcp6'])
581 self.assertEqual(mac_addr, secondary_nic_cfg['match']['macaddress'])
582 self.assertEqual(9000, secondary_nic_cfg['mtu'])
583
584 self.assertEqual(1, len(secondary_nic_cfg['addresses']))
585 # These values are hard-coded in OPC_VM_SECONDARY_VNIC_RESPONSE
586 self.assertEqual('10.0.0.231', secondary_nic_cfg['addresses'][0])
587
555588
556# vi: ts=4 expandtab589# vi: ts=4 expandtab
diff --git a/debian/changelog b/debian/changelog
index 0ac84a4..ba9606f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,12 @@
1cloud-init (19.2-24-ge7881d5c-0ubuntu1~19.04.1) disco; urgency=medium
2
3 * New upstream snapshot. (LP: #1841099)
4 - Oracle: Render secondary vnic IP and MTU values only
5 - exoscale: fix sysconfig cloud_config_modules overrides
6 - net/cmdline: refactor to allow multiple initramfs network config sources
7
8 -- Chad Smith <chad.smith@canonical.com> Wed, 28 Aug 2019 15:02:21 -0600
9
1cloud-init (19.2-21-ge6383719-0ubuntu1~19.04.1) disco; urgency=medium10cloud-init (19.2-21-ge6383719-0ubuntu1~19.04.1) disco; urgency=medium
211
3 * debian/cloud-init.templates: enable Exoscale cloud.12 * debian/cloud-init.templates: enable Exoscale cloud.
diff --git a/tests/unittests/test_datasource/test_exoscale.py b/tests/unittests/test_datasource/test_exoscale.py
index 350c330..f006119 100644
--- a/tests/unittests/test_datasource/test_exoscale.py
+++ b/tests/unittests/test_datasource/test_exoscale.py
@@ -11,8 +11,10 @@ from cloudinit.sources.DataSourceExoscale import (
11 PASSWORD_SERVER_PORT,11 PASSWORD_SERVER_PORT,
12 read_metadata)12 read_metadata)
13from cloudinit.tests.helpers import HttprettyTestCase, mock13from cloudinit.tests.helpers import HttprettyTestCase, mock
14from cloudinit import util
1415
15import httpretty16import httpretty
17import os
16import requests18import requests
1719
1820
@@ -63,6 +65,18 @@ class TestDatasourceExoscale(HttprettyTestCase):
63 password = get_password()65 password = get_password()
64 self.assertEqual(expected_password, password)66 self.assertEqual(expected_password, password)
6567
68 def test_activate_removes_set_passwords_semaphore(self):
69 """Allow set_passwords to run every boot by removing the semaphore."""
70 path = helpers.Paths({'cloud_dir': self.tmp})
71 sem_dir = self.tmp_path('instance/sem', dir=self.tmp)
72 util.ensure_dir(sem_dir)
73 sem_file = os.path.join(sem_dir, 'config_set_passwords')
74 with open(sem_file, 'w') as stream:
75 stream.write('')
76 ds = DataSourceExoscale({}, None, path)
77 ds.activate(None, None)
78 self.assertFalse(os.path.exists(sem_file))
79
66 def test_get_data(self):80 def test_get_data(self):
67 """The datasource conforms to expected behavior when supplied81 """The datasource conforms to expected behavior when supplied
68 full test data."""82 full test data."""
@@ -95,8 +109,6 @@ class TestDatasourceExoscale(HttprettyTestCase):
95 self.assertEqual(ds.get_config_obj(),109 self.assertEqual(ds.get_config_obj(),
96 {'ssh_pwauth': True,110 {'ssh_pwauth': True,
97 'password': expected_password,111 'password': expected_password,
98 'cloud_config_modules': [
99 ["set-passwords", "always"]],
100 'chpasswd': {112 'chpasswd': {
101 'expire': False,113 'expire': False,
102 }})114 }})
@@ -130,9 +142,7 @@ class TestDatasourceExoscale(HttprettyTestCase):
130 self.assertEqual(ds.userdata_raw.decode("utf-8"), "#cloud-config")142 self.assertEqual(ds.userdata_raw.decode("utf-8"), "#cloud-config")
131 self.assertEqual(ds.metadata, {"instance-id": expected_id,143 self.assertEqual(ds.metadata, {"instance-id": expected_id,
132 "local-hostname": expected_hostname})144 "local-hostname": expected_hostname})
133 self.assertEqual(ds.get_config_obj(),145 self.assertEqual(ds.get_config_obj(), {})
134 {'cloud_config_modules': [
135 ["set-passwords", "always"]]})
136146
137 def test_get_data_no_password(self):147 def test_get_data_no_password(self):
138 """The datasource conforms to expected behavior when no password is148 """The datasource conforms to expected behavior when no password is
@@ -163,9 +173,7 @@ class TestDatasourceExoscale(HttprettyTestCase):
163 self.assertEqual(ds.userdata_raw.decode("utf-8"), "#cloud-config")173 self.assertEqual(ds.userdata_raw.decode("utf-8"), "#cloud-config")
164 self.assertEqual(ds.metadata, {"instance-id": expected_id,174 self.assertEqual(ds.metadata, {"instance-id": expected_id,
165 "local-hostname": expected_hostname})175 "local-hostname": expected_hostname})
166 self.assertEqual(ds.get_config_obj(),176 self.assertEqual(ds.get_config_obj(), {})
167 {'cloud_config_modules': [
168 ["set-passwords", "always"]]})
169177
170 @mock.patch('cloudinit.sources.DataSourceExoscale.get_password')178 @mock.patch('cloudinit.sources.DataSourceExoscale.get_password')
171 def test_read_metadata_when_password_server_unreachable(self, m_password):179 def test_read_metadata_when_password_server_unreachable(self, m_password):
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 4f7e420..e578992 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -3591,7 +3591,7 @@ class TestCmdlineConfigParsing(CiTestCase):
3591 self.assertEqual(found, self.simple_cfg)3591 self.assertEqual(found, self.simple_cfg)
35923592
35933593
3594class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase):3594class TestCmdlineKlibcNetworkConfigSource(FilesystemMockingTestCase):
3595 macs = {3595 macs = {
3596 'eth0': '14:02:ec:42:48:00',3596 'eth0': '14:02:ec:42:48:00',
3597 'eno1': '14:02:ec:42:48:01',3597 'eno1': '14:02:ec:42:48:01',
@@ -3607,8 +3607,11 @@ class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase):
3607 populate_dir(root, content)3607 populate_dir(root, content)
3608 self.reRoot(root)3608 self.reRoot(root)
36093609
3610 found = cmdline.read_initramfs_config(3610 src = cmdline.KlibcNetworkConfigSource(
3611 cmdline='foo root=/root/bar', mac_addrs=self.macs)3611 _cmdline='foo root=/root/bar', _mac_addrs=self.macs,
3612 )
3613 self.assertTrue(src.is_applicable())
3614 found = src.render_config()
3612 self.assertEqual(found['version'], 1)3615 self.assertEqual(found['version'], 1)
3613 self.assertEqual(found['config'], [exp1])3616 self.assertEqual(found['config'], [exp1])
36143617
@@ -3621,8 +3624,11 @@ class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase):
3621 populate_dir(root, content)3624 populate_dir(root, content)
3622 self.reRoot(root)3625 self.reRoot(root)
36233626
3624 found = cmdline.read_initramfs_config(3627 src = cmdline.KlibcNetworkConfigSource(
3625 cmdline='foo ip=dhcp', mac_addrs=self.macs)3628 _cmdline='foo ip=dhcp', _mac_addrs=self.macs,
3629 )
3630 self.assertTrue(src.is_applicable())
3631 found = src.render_config()
3626 self.assertEqual(found['version'], 1)3632 self.assertEqual(found['version'], 1)
3627 self.assertEqual(found['config'], [exp1])3633 self.assertEqual(found['config'], [exp1])
36283634
@@ -3632,9 +3638,11 @@ class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase):
3632 populate_dir(root, content)3638 populate_dir(root, content)
3633 self.reRoot(root)3639 self.reRoot(root)
36343640
3635 found = cmdline.read_initramfs_config(3641 src = cmdline.KlibcNetworkConfigSource(
3636 cmdline='foo ip6=dhcp root=/dev/sda',3642 _cmdline='foo ip6=dhcp root=/dev/sda', _mac_addrs=self.macs,
3637 mac_addrs=self.macs)3643 )
3644 self.assertTrue(src.is_applicable())
3645 found = src.render_config()
3638 self.assertEqual(3646 self.assertEqual(
3639 found,3647 found,
3640 {'version': 1, 'config': [3648 {'version': 1, 'config': [
@@ -3648,9 +3656,10 @@ class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase):
3648 # if there is no ip= or ip6= on cmdline, return value should be None3656 # if there is no ip= or ip6= on cmdline, return value should be None
3649 content = {'net6-eno1.conf': DHCP6_CONTENT_1}3657 content = {'net6-eno1.conf': DHCP6_CONTENT_1}
3650 files = sorted(populate_dir(self.tmp_dir(), content))3658 files = sorted(populate_dir(self.tmp_dir(), content))
3651 found = cmdline.read_initramfs_config(3659 src = cmdline.KlibcNetworkConfigSource(
3652 files=files, cmdline='foo root=/dev/sda', mac_addrs=self.macs)3660 _files=files, _cmdline='foo root=/dev/sda', _mac_addrs=self.macs,
3653 self.assertIsNone(found)3661 )
3662 self.assertFalse(src.is_applicable())
36543663
3655 def test_with_both_ip_ip6(self):3664 def test_with_both_ip_ip6(self):
3656 content = {3665 content = {
@@ -3667,13 +3676,77 @@ class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase):
3667 populate_dir(root, content)3676 populate_dir(root, content)
3668 self.reRoot(root)3677 self.reRoot(root)
36693678
3670 found = cmdline.read_initramfs_config(3679 src = cmdline.KlibcNetworkConfigSource(
3671 cmdline='foo ip=dhcp ip6=dhcp', mac_addrs=self.macs)3680 _cmdline='foo ip=dhcp ip6=dhcp', _mac_addrs=self.macs,
3681 )
36723682
3683 self.assertTrue(src.is_applicable())
3684 found = src.render_config()
3673 self.assertEqual(found['version'], 1)3685 self.assertEqual(found['version'], 1)
3674 self.assertEqual(found['config'], expected)3686 self.assertEqual(found['config'], expected)
36753687
36763688
3689class TestReadInitramfsConfig(CiTestCase):
3690
3691 def _config_source_cls_mock(self, is_applicable, render_config=None):
3692 return lambda: mock.Mock(
3693 is_applicable=lambda: is_applicable,
3694 render_config=lambda: render_config,
3695 )
3696
3697 def test_no_sources(self):
3698 with mock.patch('cloudinit.net.cmdline._INITRAMFS_CONFIG_SOURCES', []):
3699 self.assertIsNone(cmdline.read_initramfs_config())
3700
3701 def test_no_applicable_sources(self):
3702 sources = [
3703 self._config_source_cls_mock(is_applicable=False),
3704 self._config_source_cls_mock(is_applicable=False),
3705 self._config_source_cls_mock(is_applicable=False),
3706 ]
3707 with mock.patch('cloudinit.net.cmdline._INITRAMFS_CONFIG_SOURCES',
3708 sources):
3709 self.assertIsNone(cmdline.read_initramfs_config())
3710
3711 def test_one_applicable_source(self):
3712 expected_config = object()
3713 sources = [
3714 self._config_source_cls_mock(
3715 is_applicable=True, render_config=expected_config,
3716 ),
3717 ]
3718 with mock.patch('cloudinit.net.cmdline._INITRAMFS_CONFIG_SOURCES',
3719 sources):
3720 self.assertEqual(expected_config, cmdline.read_initramfs_config())
3721
3722 def test_one_applicable_source_after_inapplicable_sources(self):
3723 expected_config = object()
3724 sources = [
3725 self._config_source_cls_mock(is_applicable=False),
3726 self._config_source_cls_mock(is_applicable=False),
3727 self._config_source_cls_mock(
3728 is_applicable=True, render_config=expected_config,
3729 ),
3730 ]
3731 with mock.patch('cloudinit.net.cmdline._INITRAMFS_CONFIG_SOURCES',
3732 sources):
3733 self.assertEqual(expected_config, cmdline.read_initramfs_config())
3734
3735 def test_first_applicable_source_is_used(self):
3736 first_config, second_config = object(), object()
3737 sources = [
3738 self._config_source_cls_mock(
3739 is_applicable=True, render_config=first_config,
3740 ),
3741 self._config_source_cls_mock(
3742 is_applicable=True, render_config=second_config,
3743 ),
3744 ]
3745 with mock.patch('cloudinit.net.cmdline._INITRAMFS_CONFIG_SOURCES',
3746 sources):
3747 self.assertEqual(first_config, cmdline.read_initramfs_config())
3748
3749
3677class TestNetplanRoundTrip(CiTestCase):3750class TestNetplanRoundTrip(CiTestCase):
3678 def _render_and_read(self, network_config=None, state=None,3751 def _render_and_read(self, network_config=None, state=None,
3679 netplan_path=None, target=None):3752 netplan_path=None, target=None):

Subscribers

People subscribed via source and target branches