Merge ~smoser/cloud-init:bug/1752391-fix-iscsi-root-without-ip into cloud-init:master

Proposed by Scott Moser
Status: Merged
Approved by: Chad Smith
Approved revision: 7428655537de85f2c0732862402391736c765dbe
Merge reported by: Chad Smith
Merged at revision: de34dc7c467b318b2d04d065f8d752c7a530e155
Proposed branch: ~smoser/cloud-init:bug/1752391-fix-iscsi-root-without-ip
Merge into: cloud-init:master
Diff against target: 194 lines (+72/-22)
3 files modified
cloudinit/net/cmdline.py (+22/-2)
cloudinit/tests/helpers.py (+7/-2)
tests/unittests/test_net.py (+43/-18)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Chad Smith Approve
Review via email: mp+340140@code.launchpad.net

Commit message

net: recognize iscsi root cases without ip= on kernel command line.

When 'ip=' or 'ip6=' is found on the kernel command line,
cloud-init will consider read network config from /run/net-*.conf files.

There are some iscsi-root scenarios where initramfs configures networking
but the ip= parameter is not present. 2 such cases are:
 a.) static config in /etc/iscsi/iscsi.initramfs (copied into the
initramfs)
 b.) iBft

This changes cloud-init to consider initramfs provided networking
information if:
 * there are /run/net-* files and
 * (ip= or ip6 is on the command line) or open-iscsi.interface file
exists.

LP: #1752391

Description of the change

see commit message

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:11f8aa59eea1fd06098c9e2e11e95b5578774b53
https://jenkins.ubuntu.com/server/job/cloud-init-ci/797/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

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

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

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

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

review: Approve (continuous-integration)
Revision history for this message
Ryan Harper (raharper) :
Revision history for this message
Scott Moser (smoser) :
Revision history for this message
Chad Smith (chad.smith) wrote :

Minor nit inline about the commandline ip6= match. but looks good.

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

FAILED: Continuous integration, rev:7428655537de85f2c0732862402391736c765dbe
https://jenkins.ubuntu.com/server/job/cloud-init-ci/875/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

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

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

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

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

review: Approve (continuous-integration)
Revision history for this message
Chad Smith (chad.smith) wrote :

An upstream commit landed for this bug.

To view that commit see the following URL:
https://git.launchpad.net/cloud-init/commit/?id=de34dc7c

There was an error fetching revisions from git servers. Please try again in a few minutes. If the problem persists, contact Launchpad support.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/net/cmdline.py b/cloudinit/net/cmdline.py
2index 7b2cc9d..9e9fe0f 100755
3--- a/cloudinit/net/cmdline.py
4+++ b/cloudinit/net/cmdline.py
5@@ -9,12 +9,15 @@ import base64
6 import glob
7 import gzip
8 import io
9+import os
10
11 from . import get_devicelist
12 from . import read_sys_net_safe
13
14 from cloudinit import util
15
16+_OPEN_ISCSI_INTERFACE_FILE = "/run/initramfs/open-iscsi.interface"
17+
18
19 def _klibc_to_config_entry(content, mac_addrs=None):
20 """Convert a klibc written shell content file to a 'config' entry
21@@ -103,9 +106,13 @@ def _klibc_to_config_entry(content, mac_addrs=None):
22 return name, iface
23
24
25+def _get_klibc_net_cfg_files():
26+ return glob.glob('/run/net-*.conf') + glob.glob('/run/net6-*.conf')
27+
28+
29 def config_from_klibc_net_cfg(files=None, mac_addrs=None):
30 if files is None:
31- files = glob.glob('/run/net-*.conf') + glob.glob('/run/net6-*.conf')
32+ files = _get_klibc_net_cfg_files()
33
34 entries = []
35 names = {}
36@@ -160,10 +167,23 @@ def _b64dgz(b64str, gzipped="try"):
37 return _decomp_gzip(blob, strict=gzipped != "try")
38
39
40+def _is_initramfs_netconfig(files, cmdline):
41+ if files:
42+ if 'ip=' in cmdline or 'ip6=' in cmdline:
43+ return True
44+ if os.path.exists(_OPEN_ISCSI_INTERFACE_FILE):
45+ # iBft can configure networking without ip=
46+ return True
47+ return False
48+
49+
50 def read_kernel_cmdline_config(files=None, mac_addrs=None, cmdline=None):
51 if cmdline is None:
52 cmdline = util.get_cmdline()
53
54+ if files is None:
55+ files = _get_klibc_net_cfg_files()
56+
57 if 'network-config=' in cmdline:
58 data64 = None
59 for tok in cmdline.split():
60@@ -172,7 +192,7 @@ def read_kernel_cmdline_config(files=None, mac_addrs=None, cmdline=None):
61 if data64:
62 return util.load_yaml(_b64dgz(data64))
63
64- if 'ip=' not in cmdline and 'ip6=' not in cmdline:
65+ if not _is_initramfs_netconfig(files, cmdline):
66 return None
67
68 if mac_addrs is None:
69diff --git a/cloudinit/tests/helpers.py b/cloudinit/tests/helpers.py
70index a2e1053..999b1d7 100644
71--- a/cloudinit/tests/helpers.py
72+++ b/cloudinit/tests/helpers.py
73@@ -283,10 +283,15 @@ class FilesystemMockingTestCase(ResourceUsingTestCase):
74 def patchOS(self, new_root):
75 patch_funcs = {
76 os.path: [('isfile', 1), ('exists', 1),
77- ('islink', 1), ('isdir', 1)],
78+ ('islink', 1), ('isdir', 1), ('lexists', 1)],
79 os: [('listdir', 1), ('mkdir', 1),
80- ('lstat', 1), ('symlink', 2)],
81+ ('lstat', 1), ('symlink', 2)]
82 }
83+
84+ if hasattr(os, 'scandir'):
85+ # py27 does not have scandir
86+ patch_funcs[os].append(('scandir', 1))
87+
88 for (mod, funcs) in patch_funcs.items():
89 for f, nargs in funcs:
90 func = getattr(mod, f)
91diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
92index 9cf11f2..84a0eab 100644
93--- a/tests/unittests/test_net.py
94+++ b/tests/unittests/test_net.py
95@@ -12,10 +12,8 @@ from cloudinit.sources.helpers import openstack
96 from cloudinit import temp_utils
97 from cloudinit import util
98
99-from cloudinit.tests.helpers import CiTestCase
100-from cloudinit.tests.helpers import dir2dict
101-from cloudinit.tests.helpers import mock
102-from cloudinit.tests.helpers import populate_dir
103+from cloudinit.tests.helpers import (
104+ CiTestCase, FilesystemMockingTestCase, dir2dict, mock, populate_dir)
105
106 import base64
107 import copy
108@@ -2186,27 +2184,49 @@ class TestCmdlineConfigParsing(CiTestCase):
109 self.assertEqual(found, self.simple_cfg)
110
111
112-class TestCmdlineReadKernelConfig(CiTestCase):
113+class TestCmdlineReadKernelConfig(FilesystemMockingTestCase):
114 macs = {
115 'eth0': '14:02:ec:42:48:00',
116 'eno1': '14:02:ec:42:48:01',
117 }
118
119- def test_ip_cmdline_read_kernel_cmdline_ip(self):
120- content = {'net-eth0.conf': DHCP_CONTENT_1}
121- files = sorted(populate_dir(self.tmp_dir(), content))
122+ def test_ip_cmdline_without_ip(self):
123+ content = {'/run/net-eth0.conf': DHCP_CONTENT_1,
124+ cmdline._OPEN_ISCSI_INTERFACE_FILE: "eth0\n"}
125+ exp1 = copy.deepcopy(DHCP_EXPECTED_1)
126+ exp1['mac_address'] = self.macs['eth0']
127+
128+ root = self.tmp_dir()
129+ populate_dir(root, content)
130+ self.reRoot(root)
131+
132 found = cmdline.read_kernel_cmdline_config(
133- files=files, cmdline='foo ip=dhcp', mac_addrs=self.macs)
134+ cmdline='foo root=/root/bar', mac_addrs=self.macs)
135+ self.assertEqual(found['version'], 1)
136+ self.assertEqual(found['config'], [exp1])
137+
138+ def test_ip_cmdline_read_kernel_cmdline_ip(self):
139+ content = {'/run/net-eth0.conf': DHCP_CONTENT_1}
140 exp1 = copy.deepcopy(DHCP_EXPECTED_1)
141 exp1['mac_address'] = self.macs['eth0']
142+
143+ root = self.tmp_dir()
144+ populate_dir(root, content)
145+ self.reRoot(root)
146+
147+ found = cmdline.read_kernel_cmdline_config(
148+ cmdline='foo ip=dhcp', mac_addrs=self.macs)
149 self.assertEqual(found['version'], 1)
150 self.assertEqual(found['config'], [exp1])
151
152 def test_ip_cmdline_read_kernel_cmdline_ip6(self):
153- content = {'net6-eno1.conf': DHCP6_CONTENT_1}
154- files = sorted(populate_dir(self.tmp_dir(), content))
155+ content = {'/run/net6-eno1.conf': DHCP6_CONTENT_1}
156+ root = self.tmp_dir()
157+ populate_dir(root, content)
158+ self.reRoot(root)
159+
160 found = cmdline.read_kernel_cmdline_config(
161- files=files, cmdline='foo ip6=dhcp root=/dev/sda',
162+ cmdline='foo ip6=dhcp root=/dev/sda',
163 mac_addrs=self.macs)
164 self.assertEqual(
165 found,
166@@ -2226,18 +2246,23 @@ class TestCmdlineReadKernelConfig(CiTestCase):
167 self.assertIsNone(found)
168
169 def test_ip_cmdline_both_ip_ip6(self):
170- content = {'net-eth0.conf': DHCP_CONTENT_1,
171- 'net6-eth0.conf': DHCP6_CONTENT_1.replace('eno1', 'eth0')}
172- files = sorted(populate_dir(self.tmp_dir(), content))
173- found = cmdline.read_kernel_cmdline_config(
174- files=files, cmdline='foo ip=dhcp ip6=dhcp', mac_addrs=self.macs)
175-
176+ content = {
177+ '/run/net-eth0.conf': DHCP_CONTENT_1,
178+ '/run/net6-eth0.conf': DHCP6_CONTENT_1.replace('eno1', 'eth0')}
179 eth0 = copy.deepcopy(DHCP_EXPECTED_1)
180 eth0['mac_address'] = self.macs['eth0']
181 eth0['subnets'].append(
182 {'control': 'manual', 'type': 'dhcp6',
183 'netmask': '64', 'dns_nameservers': ['2001:67c:1562:8010::2:1']})
184 expected = [eth0]
185+
186+ root = self.tmp_dir()
187+ populate_dir(root, content)
188+ self.reRoot(root)
189+
190+ found = cmdline.read_kernel_cmdline_config(
191+ cmdline='foo ip=dhcp ip6=dhcp', mac_addrs=self.macs)
192+
193 self.assertEqual(found['version'], 1)
194 self.assertEqual(found['config'], expected)
195

Subscribers

People subscribed via source and target branches