1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
|
commit 6e92c5f2fccaad24afb89f79f260cb496fb8d67f
Author: Scott Moser <smoser@brickies.net>
Date: Tue Nov 8 20:59:23 2016 -0500
net/cmdline: Consider ip= or ip6= on command line not only ip=
The previous behavior would miss ip6= on the command line and
would not pay attention to the written net-* or net6-* files if
only ip6= was found.
The fix here enables parsing the files if either ip= or ip6= is found,
and adds some tests as well.
LP: #1639930
--- a/cloudinit/net/cmdline.py
+++ b/cloudinit/net/cmdline.py
@@ -57,7 +57,7 @@ def _load_shell_content(content, add_emp
def _klibc_to_config_entry(content, mac_addrs=None):
- """Convert a klibc writtent shell content file to a 'config' entry
+ """Convert a klibc written shell content file to a 'config' entry
When ip= is seen on the kernel command line in debian initramfs
and networking is brought up, ipconfig will populate
/run/net-<name>.cfg.
@@ -140,7 +140,7 @@ def _klibc_to_config_entry(content, mac_
def config_from_klibc_net_cfg(files=None, mac_addrs=None):
if files is None:
- files = glob.glob('/run/net*.conf')
+ files = glob.glob('/run/net-*.conf') + glob.glob('/run/net6-*.conf')
entries = []
names = {}
@@ -148,12 +148,19 @@ def config_from_klibc_net_cfg(files=None
name, entry = _klibc_to_config_entry(util.load_file(cfg_file),
mac_addrs=mac_addrs)
if name in names:
- raise ValueError(
- "device '%s' defined multiple times: %s and %s" % (
- name, names[name], cfg_file))
+ prev = names[name]['entry']
+ if prev.get('mac_address') != entry.get('mac_address'):
+ raise ValueError(
+ "device '%s' was defined multiple times (%s)"
+ " but had differing mac addresses: %s -> %s.",
+ (name, ' '.join(names[name]['files']),
+ prev.get('mac_address'), entry.get('mac_address')))
+ prev['subnets'].extend(entry['subnets'])
+ names[name]['files'].append(cfg_file)
+ else:
+ names[name] = {'files': [cfg_file], 'entry': entry}
+ entries.append(entry)
- names[name] = cfg_file
- entries.append(entry)
return {'config': entries, 'version': 1}
@@ -199,7 +206,7 @@ def read_kernel_cmdline_config(files=Non
if data64:
return util.load_yaml(_b64dgz(data64))
- if 'ip=' not in cmdline:
+ if 'ip=' not in cmdline and 'ip6=' not in cmdline:
return None
if mac_addrs is None:
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -8,6 +8,8 @@ from cloudinit import util
from .helpers import dir2dict
from .helpers import mock
+from .helpers import populate_dir
+from .helpers import TempDirTestCase
from .helpers import TestCase
import base64
@@ -54,22 +56,9 @@ DHCP_EXPECTED_1 = {
}
DHCP6_CONTENT_1 = """
-DEVICE=eno1
+DEVICE6=eno1
HOSTNAME=
DNSDOMAIN=
-reason='PREINIT'
-interface='eno1'
-DEVICE=eno1
-HOSTNAME=
-DNSDOMAIN=
-reason='FAIL'
-interface='eno1'
-DEVICE=eno1
-HOSTNAME=
-DNSDOMAIN=
-reason='PREINIT6'
-interface='eno1'
-DEVICE=eno1
IPV6PROTO=dhcp6
IPV6ADDR=2001:67c:1562:8010:0:1::
IPV6NETMASK=64
@@ -77,11 +66,6 @@ IPV6DNS0=2001:67c:1562:8010::2:1
IPV6DOMAINSEARCH=
HOSTNAME=
DNSDOMAIN=
-reason='BOUND6'
-interface='eno1'
-new_ip6_address='2001:67c:1562:8010:0:1::'
-new_ip6_prefixlen='64'
-new_dhcp6_name_servers='2001:67c:1562:8010::2:1'
"""
DHCP6_EXPECTED_1 = {
@@ -677,6 +661,56 @@ class TestCmdlineConfigParsing(TestCase)
self.assertEqual(found, self.simple_cfg)
+class TestCmdlineReadKernelConfig(TempDirTestCase):
+ def test_ip_cmdline_read_kernel_cmdline_ip(self):
+ content = {'net-eth0.conf': DHCP_CONTENT_1}
+ populate_dir(self.tmp, content)
+ files = [os.path.join(self.tmp, k) for k in content.keys()]
+ found = cmdline.read_kernel_cmdline_config(
+ files=files, cmdline='foo ip=dhcp')
+ self.assertEqual(found['version'], 1)
+ self.assertEqual(found['config'], [DHCP_EXPECTED_1])
+
+ def test_ip_cmdline_read_kernel_cmdline_ip6(self):
+ content = {'net6-eno1.conf': DHCP6_CONTENT_1}
+ populate_dir(self.tmp, content)
+ files = [os.path.join(self.tmp, k) for k in content.keys()]
+ found = cmdline.read_kernel_cmdline_config(
+ files=files, cmdline='foo ip6=dhcp root=/dev/sda')
+ self.assertEqual(
+ found,
+ {'version': 1, 'config': [
+ {'type': 'physical', 'name': 'eno1',
+ 'subnets': [
+ {'dns_nameservers': ['2001:67c:1562:8010::2:1'],
+ 'control': 'manual', 'type': 'dhcp6', 'netmask': '64'}]}]})
+
+ def test_ip_cmdline_read_kernel_cmdline_none(self):
+ # if there is no ip= or ip6= on cmdline, return value should be None
+ content = {'net6-eno1.conf': DHCP6_CONTENT_1}
+ populate_dir(self.tmp, content)
+ files = [os.path.join(self.tmp, k) for k in content.keys()]
+ found = cmdline.read_kernel_cmdline_config(
+ files=files, cmdline='foo root=/dev/sda')
+ self.assertEqual(found, None)
+
+ def test_ip_cmdline_both_ip_ip6(self):
+ content = {'net-eth0.conf': DHCP_CONTENT_1,
+ 'net6-eth0.conf': DHCP6_CONTENT_1.replace('eno1', 'eth0')}
+ populate_dir(self.tmp, content)
+ files = [os.path.join(self.tmp, k) for k in sorted(content.keys())]
+ found = cmdline.read_kernel_cmdline_config(
+ files=files, cmdline='foo ip=dhcp ip6=dhcp')
+
+ eth0 = copy.deepcopy(DHCP_EXPECTED_1)
+ eth0['subnets'].append(
+ {'control': 'manual', 'type': 'dhcp6',
+ 'netmask': '64', 'dns_nameservers': ['2001:67c:1562:8010::2:1']})
+ expected = [eth0]
+ self.assertEqual(found['version'], 1)
+ self.assertEqual(found['config'], expected)
+
+
class TestEniRoundTrip(TestCase):
def setUp(self):
super(TestCase, self).setUp()
|