Merge ~smoser/cloud-init:bond_name into cloud-init:master

Proposed by Scott Moser
Status: Merged
Merged at revision: 6a8aa46863f1a4a5f3c0d37d34fd02d57790be01
Proposed branch: ~smoser/cloud-init:bond_name
Merge into: cloud-init:master
Diff against target: 249 lines (+156/-9)
3 files modified
cloudinit/net/eni.py (+1/-1)
cloudinit/sources/helpers/openstack.py (+50/-8)
tests/unittests/test_datasource/test_configdrive.py (+105/-0)
Reviewer Review Type Date Requested Status
Mathieu Gagné (community) Approve
Matthew Thode (community) Approve
cloud-init Commiters Pending
Review via email: mp+303563@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Scott Moser (smoser) wrote :

this was originally submitted at https://code.launchpad.net/~mgagne/cloud-init/+git/cloud-init/+merge/301953 and then working with mgagne, we have some additional things here.

Revision history for this message
Scott Moser (smoser) wrote :

this isnt completely ready yet, but i hope close.
it definitely does solve some issues.

Revision history for this message
Matthew Thode (prometheanfire) wrote :

I just tested this and it solved my issue of the bonding_masters file causing an error to be thrown.

review: Approve
Revision history for this message
Mathieu Gagné (mgagne) wrote :

Same on my side. All issues identified in bug report were fixed. +1

There is still an issue I can't reproduce or explain where default gateway is not properly configured. I rebooted 10+ times, rebuilt the baremetal nodes 10+ and still can't reproduce. But I know it exists because so far, it happened 2 times. But I think it can be addressed in an other merge request.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py
2index eff5b92..cd533dd 100644
3--- a/cloudinit/net/eni.py
4+++ b/cloudinit/net/eni.py
5@@ -399,7 +399,7 @@ class Renderer(renderer.Renderer):
6 else:
7 # ifenslave docs say to auto the slave devices
8 lines = []
9- if 'bond-master' in iface:
10+ if 'bond-master' in iface or 'bond-slaves' in iface:
11 lines.append("auto {name}".format(**iface))
12 lines.append("iface {name} {inet} {mode}".format(**iface))
13 lines.extend(_iface_add_attrs(iface, index=0))
14diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py
15index 84322e0..a5a2a1d 100644
16--- a/cloudinit/sources/helpers/openstack.py
17+++ b/cloudinit/sources/helpers/openstack.py
18@@ -539,6 +539,10 @@ def convert_net_json(network_json=None, known_macs=None):
19 networks = network_json.get('networks', [])
20 services = network_json.get('services', [])
21
22+ link_updates = []
23+ link_id_info = {}
24+ bond_name_fmt = "bond%d"
25+ bond_number = 0
26 config = []
27 for link in links:
28 subnets = []
29@@ -551,6 +555,13 @@ def convert_net_json(network_json=None, known_macs=None):
30 if 'name' in link:
31 cfg['name'] = link['name']
32
33+ if link.get('ethernet_mac_address'):
34+ link_id_info[link['id']] = link.get('ethernet_mac_address')
35+
36+ curinfo = {'name': cfg.get('name'),
37+ 'mac': link.get('ethernet_mac_address'),
38+ 'id': link['id'], 'type': link['type']}
39+
40 for network in [n for n in networks
41 if n['link'] == link['id']]:
42 subnet = dict((k, v) for k, v in network.items()
43@@ -582,31 +593,56 @@ def convert_net_json(network_json=None, known_macs=None):
44 continue
45 elif k.startswith('bond'):
46 params.update({k: v})
47- cfg.update({
48- 'bond_interfaces': copy.deepcopy(link['bond_links']),
49- 'params': params,
50- })
51+
52+ # openstack does not provide a name for the bond.
53+ # they do provide an 'id', but that is possibly non-sensical.
54+ # so we just create our own name.
55+ link_name = bond_name_fmt % bond_number
56+ bond_number += 1
57+
58+ # bond_links reference links by their id, but we need to add
59+ # to the network config by their nic name.
60+ # store that in bond_links_needed, and update these later.
61+ link_updates.append(
62+ (cfg, 'bond_interfaces', '%s',
63+ copy.deepcopy(link['bond_links']))
64+ )
65+ cfg.update({'params': params, 'name': link_name})
66+
67+ curinfo['name'] = link_name
68 elif link['type'] in ['vlan']:
69+ name = "%s.%s" % (link['vlan_link'], link['vlan_id'])
70 cfg.update({
71- 'name': "%s.%s" % (link['vlan_link'],
72- link['vlan_id']),
73- 'vlan_link': link['vlan_link'],
74+ 'name': name,
75 'vlan_id': link['vlan_id'],
76 'mac_address': link['vlan_mac_address'],
77 })
78+ link_updates.append((cfg, 'vlan_link', '%s', link['vlan_link']))
79+ link_updates.append((cfg, 'name', "%%s.%s" % link['vlan_id'],
80+ link['vlan_link']))
81+ curinfo.update({'mac': link['vlan_mac_address'],
82+ 'name': name})
83 else:
84 raise ValueError(
85 'Unknown network_data link type: %s' % link['type'])
86
87 config.append(cfg)
88+ link_id_info[curinfo['id']] = curinfo
89
90 need_names = [d for d in config
91 if d.get('type') == 'physical' and 'name' not in d]
92
93- if need_names:
94+ if need_names or link_updates:
95 if known_macs is None:
96 known_macs = net.get_interfaces_by_mac()
97
98+ # go through and fill out the link_id_info with names
99+ for link_id, info in link_id_info.items():
100+ if info.get('name'):
101+ continue
102+ if info.get('mac') in known_macs:
103+ info['name'] = known_macs[info['mac']]
104+
105 for d in need_names:
106 mac = d.get('mac_address')
107 if not mac:
108@@ -615,6 +651,12 @@ def convert_net_json(network_json=None, known_macs=None):
109 raise ValueError("Unable to find a system nic for %s" % d)
110 d['name'] = known_macs[mac]
111
112+ for cfg, key, fmt, target in link_updates:
113+ if isinstance(target, (list, tuple)):
114+ cfg[key] = [fmt % link_id_info[l]['name'] for l in target]
115+ else:
116+ cfg[key] = fmt % link_id_info[target]['name']
117+
118 for service in services:
119 cfg = service
120 cfg.update({'type': 'nameserver'})
121diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py
122index d026994..98ff97a 100644
123--- a/tests/unittests/test_datasource/test_configdrive.py
124+++ b/tests/unittests/test_datasource/test_configdrive.py
125@@ -137,12 +137,71 @@ NETWORK_DATA_3 = {
126 ]
127 }
128
129+NETWORK_DATA_BOND = {
130+ "services": [
131+ {"type": "dns", "address": "1.1.1.191"},
132+ {"type": "dns", "address": "1.1.1.4"},
133+ ],
134+ "networks": [
135+ {"id": "network2-ipv4", "ip_address": "2.2.2.13",
136+ "link": "vlan2", "netmask": "255.255.255.248",
137+ "network_id": "4daf5ce8-38cf-4240-9f1a-04e86d7c6117",
138+ "type": "ipv4",
139+ "routes": [{"netmask": "0.0.0.0", "network": "0.0.0.0",
140+ "gateway": "2.2.2.9"}]},
141+ {"id": "network3-ipv4", "ip_address": "10.0.1.5",
142+ "link": "vlan3", "netmask": "255.255.255.248",
143+ "network_id": "a9e2f47c-3c43-4782-94d0-e1eeef1c8c9d",
144+ "type": "ipv4",
145+ "routes": [{"netmask": "255.255.255.255",
146+ "network": "192.168.1.0", "gateway": "10.0.1.1"}]}
147+ ],
148+ "links": [
149+ {"ethernet_mac_address": "0c:c4:7a:34:6e:3c",
150+ "id": "eth0", "mtu": 1500, "type": "phy"},
151+ {"ethernet_mac_address": "0c:c4:7a:34:6e:3d",
152+ "id": "eth1", "mtu": 1500, "type": "phy"},
153+ {"bond_links": ["eth0", "eth1"],
154+ "bond_miimon": 100, "bond_mode": "4",
155+ "bond_xmit_hash_policy": "layer3+4",
156+ "ethernet_mac_address": "0c:c4:7a:34:6e:3c",
157+ "id": "bond0", "type": "bond"},
158+ {"ethernet_mac_address": "fa:16:3e:b3:72:30",
159+ "id": "vlan2", "type": "vlan", "vlan_id": 602,
160+ "vlan_link": "bond0", "vlan_mac_address": "fa:16:3e:b3:72:30"},
161+ {"ethernet_mac_address": "fa:16:3e:66:ab:a6",
162+ "id": "vlan3", "type": "vlan", "vlan_id": 612, "vlan_link": "bond0",
163+ "vlan_mac_address": "fa:16:3e:66:ab:a6"}
164+ ]
165+}
166+
167+NETWORK_DATA_VLAN = {
168+ "services": [{"type": "dns", "address": "1.1.1.191"}],
169+ "networks": [
170+ {"id": "network1-ipv4", "ip_address": "10.0.1.5",
171+ "link": "vlan1", "netmask": "255.255.255.248",
172+ "network_id": "a9e2f47c-3c43-4782-94d0-e1eeef1c8c9d",
173+ "type": "ipv4",
174+ "routes": [{"netmask": "255.255.255.255",
175+ "network": "192.168.1.0", "gateway": "10.0.1.1"}]}
176+ ],
177+ "links": [
178+ {"ethernet_mac_address": "fa:16:3e:69:b0:58",
179+ "id": "eth0", "mtu": 1500, "type": "phy"},
180+ {"ethernet_mac_address": "fa:16:3e:b3:72:30",
181+ "id": "vlan1", "type": "vlan", "vlan_id": 602,
182+ "vlan_link": "eth0", "vlan_mac_address": "fa:16:3e:b3:72:30"},
183+ ]
184+}
185+
186 KNOWN_MACS = {
187 'fa:16:3e:69:b0:58': 'enp0s1',
188 'fa:16:3e:d4:57:ad': 'enp0s2',
189 'fa:16:3e:dd:50:9a': 'foo1',
190 'fa:16:3e:a8:14:69': 'foo2',
191 'fa:16:3e:ed:9a:59': 'foo3',
192+ '0c:c4:7a:34:6e:3d': 'oeth1',
193+ '0c:c4:7a:34:6e:3c': 'oeth0',
194 }
195
196 CFG_DRIVE_FILES_V2 = {
197@@ -599,6 +658,52 @@ class TestConvertNetworkData(TestCase):
198 physicals.add(i['name'])
199 self.assertEqual(physicals, set(('foo1', 'foo2')))
200
201+ def test_bond_conversion(self):
202+ # light testing of bond conversion and eni rendering of bond
203+ ncfg = openstack.convert_net_json(NETWORK_DATA_BOND,
204+ known_macs=KNOWN_MACS)
205+ eni_renderer = eni.Renderer()
206+ eni_renderer.render_network_state(
207+ self.tmp, network_state.parse_net_config_data(ncfg))
208+ with open(os.path.join(self.tmp, "etc",
209+ "network", "interfaces"), 'r') as f:
210+ eni_rendering = f.read()
211+
212+ # Verify there are expected interfaces in the net config.
213+ interfaces = sorted(
214+ [i['name'] for i in ncfg['config']
215+ if i['type'] in ('vlan', 'bond', 'physical')])
216+ self.assertEqual(
217+ sorted(["oeth0", "oeth1", "bond0", "bond0.602", "bond0.612"]),
218+ interfaces)
219+
220+ words = eni_rendering.split()
221+ # 'eth0' and 'eth1' are the ids. because their mac adresses
222+ # map to other names, we should not see them in the ENI
223+ self.assertNotIn('eth0', words)
224+ self.assertNotIn('eth1', words)
225+
226+ # oeth0 and oeth1 are the interface names for eni.
227+ # bond0 will be generated for the bond. Each should be auto.
228+ self.assertIn("auto oeth0", eni_rendering)
229+ self.assertIn("auto oeth1", eni_rendering)
230+ self.assertIn("auto bond0", eni_rendering)
231+
232+ def test_vlan(self):
233+ # light testing of vlan config conversion and eni rendering
234+ ncfg = openstack.convert_net_json(NETWORK_DATA_VLAN,
235+ known_macs=KNOWN_MACS)
236+ eni_renderer = eni.Renderer()
237+ eni_renderer.render_network_state(
238+ self.tmp, network_state.parse_net_config_data(ncfg))
239+ with open(os.path.join(self.tmp, "etc",
240+ "network", "interfaces"), 'r') as f:
241+ eni_rendering = f.read()
242+
243+ self.assertIn("iface enp0s1", eni_rendering)
244+ self.assertIn("address 10.0.1.5", eni_rendering)
245+ self.assertIn("auto enp0s1.602", eni_rendering)
246+
247
248 def cfg_ds_from_dir(seed_d):
249 cfg_ds = ds.DataSourceConfigDrive(settings.CFG_BUILTIN, None,

Subscribers

People subscribed via source and target branches