Merge ~raharper/cloud-init:fix/net-wait-for-interfaces into cloud-init:master

Proposed by Ryan Harper
Status: Merged
Approved by: Ryan Harper
Approved revision: 68d4cb807e34bfe03bbff1daae4bf95ff8e30cb3
Merge reported by: Server Team CI bot
Merged at revision: not available
Proposed branch: ~raharper/cloud-init:fix/net-wait-for-interfaces
Merge into: cloud-init:master
Diff against target: 496 lines (+315/-32)
6 files modified
cloudinit/distros/opensuse.py (+2/-0)
cloudinit/net/__init__.py (+67/-21)
cloudinit/net/tests/test_init.py (+213/-0)
cloudinit/stages.py (+21/-6)
cloudinit/tests/test_stages.py (+9/-2)
tests/unittests/test_net.py (+3/-3)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Dan Watkins Approve
Review via email: mp+366667@code.launchpad.net

Commit message

net: update net sequence, include wait on netdevs, opensuse netrules path

On systems with many interfaces, processing udev events may take a while.
Cloud-init expects devices included in a provided network-configuration
to be present when attempting to configure them. This patch adds a step
in net configuration where it will check for devices provided in the
configuration and if not found, issue udevadm settle commands to wait
for them to appear.

Additionally, the default path for udev persistent network rules
70-persistent-net.rules may also be written to systems which include
the 75-net-generator.rules. During boot, cloud-init and the
generator may race and interleave values causing issues. OpenSUSE
will now use a newer file, 85-persistent-net-cloud-init.rules which
will take precedence over values created by 75-net-generator and
avoid collisions on the same file.

LP: #1817368

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

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Robert Schweikert (rjschwei) wrote :

Fine with me, should address the problem.

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

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

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Dan Watkins (oddbloke) wrote :

Overall, the logic here looks good, I just have a few minor inline comments. I think we do need some testing for the new functionality, though.

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

Thanks for the review. I've rebased to master and applied your suggestions. I also dropped some unrelated changes to the mounts unittest.

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

PASSED: Continuous integration, rev:9123dc08eff19909df9ea61da7000312c3e343f3
https://jenkins.ubuntu.com/server/job/cloud-init-ci/788/
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/788/rebuild

review: Approve (continuous-integration)
Revision history for this message
Dan Watkins (oddbloke) wrote :

Code LGTM now, but I still think we need some testing of wait_for_physdevs (and maybe a higher-level test that captures the issue we're actually addressing here?).

Revision history for this message
Ryan Harper (raharper) wrote :

On Tue, Jul 16, 2019 at 12:00 PM Dan Watkins <email address hidden>
wrote:

> Code LGTM now, but I still think we need some testing of wait_for_physdevs
> (and maybe a higher-level test that captures the issue we're actually
> addressing here?).
>

Yes, let me add some unittests here.

> --
>
> https://code.launchpad.net/~raharper/cloud-init/+git/cloud-init/+merge/366667
> You are the owner of ~raharper/cloud-init:fix/net-wait-for-interfaces.
>

Revision history for this message
Dan Watkins (oddbloke) wrote :

Tests look good, thanks!

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

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

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

review: Needs Fixing (continuous-integration)
68d4cb8... by Ryan Harper

Fix leaked calls to net.device_driver, net.device_devid, add testing

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

PASSED: Continuous integration, rev:68d4cb807e34bfe03bbff1daae4bf95ff8e30cb3
https://jenkins.ubuntu.com/server/job/cloud-init-ci/795/
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/795/rebuild

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/distros/opensuse.py b/cloudinit/distros/opensuse.py
2index 1bfe047..e41e2f7 100644
3--- a/cloudinit/distros/opensuse.py
4+++ b/cloudinit/distros/opensuse.py
5@@ -38,6 +38,8 @@ class Distro(distros.Distro):
6 'sysconfig': {
7 'control': 'etc/sysconfig/network/config',
8 'iface_templates': '%(base)s/network/ifcfg-%(name)s',
9+ 'netrules_path': (
10+ 'etc/udev/rules.d/85-persistent-net-cloud-init.rules'),
11 'route_templates': {
12 'ipv4': '%(base)s/network/ifroute-%(name)s',
13 'ipv6': '%(base)s/network/ifroute-%(name)s',
14diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py
15index 624c9b4..f3cec79 100644
16--- a/cloudinit/net/__init__.py
17+++ b/cloudinit/net/__init__.py
18@@ -9,6 +9,7 @@ import errno
19 import logging
20 import os
21 import re
22+from functools import partial
23
24 from cloudinit.net.network_state import mask_to_net_prefix
25 from cloudinit import util
26@@ -292,18 +293,10 @@ def generate_fallback_config(blacklist_drivers=None, config_driver=None):
27 return None
28
29
30-def apply_network_config_names(netcfg, strict_present=True, strict_busy=True):
31- """read the network config and rename devices accordingly.
32- if strict_present is false, then do not raise exception if no devices
33- match. if strict_busy is false, then do not raise exception if the
34- device cannot be renamed because it is currently configured.
35-
36- renames are only attempted for interfaces of type 'physical'. It is
37- expected that the network system will create other devices with the
38- correct name in place."""
39+def extract_physdevs(netcfg):
40
41 def _version_1(netcfg):
42- renames = []
43+ physdevs = []
44 for ent in netcfg.get('config', {}):
45 if ent.get('type') != 'physical':
46 continue
47@@ -317,11 +310,11 @@ def apply_network_config_names(netcfg, strict_present=True, strict_busy=True):
48 driver = device_driver(name)
49 if not device_id:
50 device_id = device_devid(name)
51- renames.append([mac, name, driver, device_id])
52- return renames
53+ physdevs.append([mac, name, driver, device_id])
54+ return physdevs
55
56 def _version_2(netcfg):
57- renames = []
58+ physdevs = []
59 for ent in netcfg.get('ethernets', {}).values():
60 # only rename if configured to do so
61 name = ent.get('set-name')
62@@ -337,16 +330,69 @@ def apply_network_config_names(netcfg, strict_present=True, strict_busy=True):
63 driver = device_driver(name)
64 if not device_id:
65 device_id = device_devid(name)
66- renames.append([mac, name, driver, device_id])
67- return renames
68+ physdevs.append([mac, name, driver, device_id])
69+ return physdevs
70+
71+ version = netcfg.get('version')
72+ if version == 1:
73+ return _version_1(netcfg)
74+ elif version == 2:
75+ return _version_2(netcfg)
76+
77+ raise RuntimeError('Unknown network config version: %s' % version)
78+
79
80- if netcfg.get('version') == 1:
81- return _rename_interfaces(_version_1(netcfg))
82- elif netcfg.get('version') == 2:
83- return _rename_interfaces(_version_2(netcfg))
84+def wait_for_physdevs(netcfg, strict=True):
85+ physdevs = extract_physdevs(netcfg)
86+
87+ # set of expected iface names and mac addrs
88+ expected_ifaces = dict([(iface[0], iface[1]) for iface in physdevs])
89+ expected_macs = set(expected_ifaces.keys())
90+
91+ # set of current macs
92+ present_macs = get_interfaces_by_mac().keys()
93+
94+ # compare the set of expected mac address values to
95+ # the current macs present; we only check MAC as cloud-init
96+ # has not yet renamed interfaces and the netcfg may include
97+ # such renames.
98+ for _ in range(0, 5):
99+ if expected_macs.issubset(present_macs):
100+ LOG.debug('net: all expected physical devices present')
101+ return
102
103- raise RuntimeError('Failed to apply network config names. Found bad'
104- ' network config version: %s' % netcfg.get('version'))
105+ missing = expected_macs.difference(present_macs)
106+ LOG.debug('net: waiting for expected net devices: %s', missing)
107+ for mac in missing:
108+ # trigger a settle, unless this interface exists
109+ syspath = sys_dev_path(expected_ifaces[mac])
110+ settle = partial(util.udevadm_settle, exists=syspath)
111+ msg = 'Waiting for udev events to settle or %s exists' % syspath
112+ util.log_time(LOG.debug, msg, func=settle)
113+
114+ # update present_macs after settles
115+ present_macs = get_interfaces_by_mac().keys()
116+
117+ msg = 'Not all expected physical devices present: %s' % missing
118+ LOG.warning(msg)
119+ if strict:
120+ raise RuntimeError(msg)
121+
122+
123+def apply_network_config_names(netcfg, strict_present=True, strict_busy=True):
124+ """read the network config and rename devices accordingly.
125+ if strict_present is false, then do not raise exception if no devices
126+ match. if strict_busy is false, then do not raise exception if the
127+ device cannot be renamed because it is currently configured.
128+
129+ renames are only attempted for interfaces of type 'physical'. It is
130+ expected that the network system will create other devices with the
131+ correct name in place."""
132+
133+ try:
134+ _rename_interfaces(extract_physdevs(netcfg))
135+ except RuntimeError as e:
136+ raise RuntimeError('Failed to apply network config names: %s' % e)
137
138
139 def interface_has_own_mac(ifname, strict=False):
140diff --git a/cloudinit/net/tests/test_init.py b/cloudinit/net/tests/test_init.py
141index d393e6a..e6e77d7 100644
142--- a/cloudinit/net/tests/test_init.py
143+++ b/cloudinit/net/tests/test_init.py
144@@ -708,3 +708,216 @@ class TestHasURLConnectivity(HttprettyTestCase):
145 httpretty.register_uri(httpretty.GET, self.url, body={}, status=404)
146 self.assertFalse(
147 net.has_url_connectivity(self.url), 'Expected False on url fail')
148+
149+
150+def _mk_v1_phys(mac, name, driver, device_id):
151+ v1_cfg = {'type': 'physical', 'name': name, 'mac_address': mac}
152+ params = {}
153+ if driver:
154+ params.update({'driver': driver})
155+ if device_id:
156+ params.update({'device_id': device_id})
157+
158+ if params:
159+ v1_cfg.update({'params': params})
160+
161+ return v1_cfg
162+
163+
164+def _mk_v2_phys(mac, name, driver=None, device_id=None):
165+ v2_cfg = {'set-name': name, 'match': {'macaddress': mac}}
166+ if driver:
167+ v2_cfg['match'].update({'driver': driver})
168+ if device_id:
169+ v2_cfg['match'].update({'device_id': device_id})
170+
171+ return v2_cfg
172+
173+
174+class TestExtractPhysdevs(CiTestCase):
175+
176+ def setUp(self):
177+ super(TestExtractPhysdevs, self).setUp()
178+ self.add_patch('cloudinit.net.device_driver', 'm_driver')
179+ self.add_patch('cloudinit.net.device_devid', 'm_devid')
180+
181+ def test_extract_physdevs_looks_up_driver_v1(self):
182+ driver = 'virtio'
183+ self.m_driver.return_value = driver
184+ physdevs = [
185+ ['aa:bb:cc:dd:ee:ff', 'eth0', None, '0x1000'],
186+ ]
187+ netcfg = {
188+ 'version': 1,
189+ 'config': [_mk_v1_phys(*args) for args in physdevs],
190+ }
191+ # insert the driver value for verification
192+ physdevs[0][2] = driver
193+ self.assertEqual(sorted(physdevs),
194+ sorted(net.extract_physdevs(netcfg)))
195+ self.m_driver.assert_called_with('eth0')
196+
197+ def test_extract_physdevs_looks_up_driver_v2(self):
198+ driver = 'virtio'
199+ self.m_driver.return_value = driver
200+ physdevs = [
201+ ['aa:bb:cc:dd:ee:ff', 'eth0', None, '0x1000'],
202+ ]
203+ netcfg = {
204+ 'version': 2,
205+ 'ethernets': {args[1]: _mk_v2_phys(*args) for args in physdevs},
206+ }
207+ # insert the driver value for verification
208+ physdevs[0][2] = driver
209+ self.assertEqual(sorted(physdevs),
210+ sorted(net.extract_physdevs(netcfg)))
211+ self.m_driver.assert_called_with('eth0')
212+
213+ def test_extract_physdevs_looks_up_devid_v1(self):
214+ devid = '0x1000'
215+ self.m_devid.return_value = devid
216+ physdevs = [
217+ ['aa:bb:cc:dd:ee:ff', 'eth0', 'virtio', None],
218+ ]
219+ netcfg = {
220+ 'version': 1,
221+ 'config': [_mk_v1_phys(*args) for args in physdevs],
222+ }
223+ # insert the driver value for verification
224+ physdevs[0][3] = devid
225+ self.assertEqual(sorted(physdevs),
226+ sorted(net.extract_physdevs(netcfg)))
227+ self.m_devid.assert_called_with('eth0')
228+
229+ def test_extract_physdevs_looks_up_devid_v2(self):
230+ devid = '0x1000'
231+ self.m_devid.return_value = devid
232+ physdevs = [
233+ ['aa:bb:cc:dd:ee:ff', 'eth0', 'virtio', None],
234+ ]
235+ netcfg = {
236+ 'version': 2,
237+ 'ethernets': {args[1]: _mk_v2_phys(*args) for args in physdevs},
238+ }
239+ # insert the driver value for verification
240+ physdevs[0][3] = devid
241+ self.assertEqual(sorted(physdevs),
242+ sorted(net.extract_physdevs(netcfg)))
243+ self.m_devid.assert_called_with('eth0')
244+
245+ def test_get_v1_type_physical(self):
246+ physdevs = [
247+ ['aa:bb:cc:dd:ee:ff', 'eth0', 'virtio', '0x1000'],
248+ ['00:11:22:33:44:55', 'ens3', 'e1000', '0x1643'],
249+ ['09:87:65:43:21:10', 'ens0p1', 'mlx4_core', '0:0:1000'],
250+ ]
251+ netcfg = {
252+ 'version': 1,
253+ 'config': [_mk_v1_phys(*args) for args in physdevs],
254+ }
255+ self.assertEqual(sorted(physdevs),
256+ sorted(net.extract_physdevs(netcfg)))
257+
258+ def test_get_v2_type_physical(self):
259+ physdevs = [
260+ ['aa:bb:cc:dd:ee:ff', 'eth0', 'virtio', '0x1000'],
261+ ['00:11:22:33:44:55', 'ens3', 'e1000', '0x1643'],
262+ ['09:87:65:43:21:10', 'ens0p1', 'mlx4_core', '0:0:1000'],
263+ ]
264+ netcfg = {
265+ 'version': 2,
266+ 'ethernets': {args[1]: _mk_v2_phys(*args) for args in physdevs},
267+ }
268+ self.assertEqual(sorted(physdevs),
269+ sorted(net.extract_physdevs(netcfg)))
270+
271+ def test_get_v2_type_physical_skips_if_no_set_name(self):
272+ netcfg = {
273+ 'version': 2,
274+ 'ethernets': {
275+ 'ens3': {
276+ 'match': {'macaddress': '00:11:22:33:44:55'},
277+ }
278+ }
279+ }
280+ self.assertEqual([], net.extract_physdevs(netcfg))
281+
282+ def test_runtime_error_on_unknown_netcfg_version(self):
283+ with self.assertRaises(RuntimeError):
284+ net.extract_physdevs({'version': 3, 'awesome_config': []})
285+
286+
287+class TestWaitForPhysdevs(CiTestCase):
288+
289+ with_logs = True
290+
291+ def setUp(self):
292+ super(TestWaitForPhysdevs, self).setUp()
293+ self.add_patch('cloudinit.net.get_interfaces_by_mac',
294+ 'm_get_iface_mac')
295+ self.add_patch('cloudinit.util.udevadm_settle', 'm_udev_settle')
296+
297+ def test_wait_for_physdevs_skips_settle_if_all_present(self):
298+ physdevs = [
299+ ['aa:bb:cc:dd:ee:ff', 'eth0', 'virtio', '0x1000'],
300+ ['00:11:22:33:44:55', 'ens3', 'e1000', '0x1643'],
301+ ]
302+ netcfg = {
303+ 'version': 2,
304+ 'ethernets': {args[1]: _mk_v2_phys(*args)
305+ for args in physdevs},
306+ }
307+ self.m_get_iface_mac.side_effect = iter([
308+ {'aa:bb:cc:dd:ee:ff': 'eth0',
309+ '00:11:22:33:44:55': 'ens3'},
310+ ])
311+ net.wait_for_physdevs(netcfg)
312+ self.assertEqual(0, self.m_udev_settle.call_count)
313+
314+ def test_wait_for_physdevs_calls_udev_settle_on_missing(self):
315+ physdevs = [
316+ ['aa:bb:cc:dd:ee:ff', 'eth0', 'virtio', '0x1000'],
317+ ['00:11:22:33:44:55', 'ens3', 'e1000', '0x1643'],
318+ ]
319+ netcfg = {
320+ 'version': 2,
321+ 'ethernets': {args[1]: _mk_v2_phys(*args)
322+ for args in physdevs},
323+ }
324+ self.m_get_iface_mac.side_effect = iter([
325+ {'aa:bb:cc:dd:ee:ff': 'eth0'}, # first call ens3 is missing
326+ {'aa:bb:cc:dd:ee:ff': 'eth0',
327+ '00:11:22:33:44:55': 'ens3'}, # second call has both
328+ ])
329+ net.wait_for_physdevs(netcfg)
330+ self.m_udev_settle.assert_called_with(exists=net.sys_dev_path('ens3'))
331+
332+ def test_wait_for_physdevs_raise_runtime_error_if_missing_and_strict(self):
333+ physdevs = [
334+ ['aa:bb:cc:dd:ee:ff', 'eth0', 'virtio', '0x1000'],
335+ ['00:11:22:33:44:55', 'ens3', 'e1000', '0x1643'],
336+ ]
337+ netcfg = {
338+ 'version': 2,
339+ 'ethernets': {args[1]: _mk_v2_phys(*args)
340+ for args in physdevs},
341+ }
342+ self.m_get_iface_mac.return_value = {}
343+ with self.assertRaises(RuntimeError):
344+ net.wait_for_physdevs(netcfg)
345+
346+ self.assertEqual(5 * len(physdevs), self.m_udev_settle.call_count)
347+
348+ def test_wait_for_physdevs_no_raise_if_not_strict(self):
349+ physdevs = [
350+ ['aa:bb:cc:dd:ee:ff', 'eth0', 'virtio', '0x1000'],
351+ ['00:11:22:33:44:55', 'ens3', 'e1000', '0x1643'],
352+ ]
353+ netcfg = {
354+ 'version': 2,
355+ 'ethernets': {args[1]: _mk_v2_phys(*args)
356+ for args in physdevs},
357+ }
358+ self.m_get_iface_mac.return_value = {}
359+ net.wait_for_physdevs(netcfg, strict=False)
360+ self.assertEqual(5 * len(physdevs), self.m_udev_settle.call_count)
361diff --git a/cloudinit/stages.py b/cloudinit/stages.py
362index da7d349..5f9d47b 100644
363--- a/cloudinit/stages.py
364+++ b/cloudinit/stages.py
365@@ -644,18 +644,21 @@ class Init(object):
366 return (ncfg, loc)
367 return (self.distro.generate_fallback_config(), "fallback")
368
369- def apply_network_config(self, bring_up):
370- netcfg, src = self._find_networking_config()
371- if netcfg is None:
372- LOG.info("network config is disabled by %s", src)
373- return
374-
375+ def _apply_netcfg_names(self, netcfg):
376 try:
377 LOG.debug("applying net config names for %s", netcfg)
378 self.distro.apply_network_config_names(netcfg)
379 except Exception as e:
380 LOG.warning("Failed to rename devices: %s", e)
381
382+ def apply_network_config(self, bring_up):
383+ # get a network config
384+ netcfg, src = self._find_networking_config()
385+ if netcfg is None:
386+ LOG.info("network config is disabled by %s", src)
387+ return
388+
389+ # request an update if needed/available
390 if self.datasource is not NULL_DATA_SOURCE:
391 if not self.is_new_instance():
392 if not self.datasource.update_metadata([EventType.BOOT]):
393@@ -663,8 +666,20 @@ class Init(object):
394 "No network config applied. Neither a new instance"
395 " nor datasource network update on '%s' event",
396 EventType.BOOT)
397+ # nothing new, but ensure proper names
398+ self._apply_netcfg_names(netcfg)
399 return
400+ else:
401+ # refresh netcfg after update
402+ netcfg, src = self._find_networking_config()
403+
404+ # ensure all physical devices in config are present
405+ net.wait_for_physdevs(netcfg)
406+
407+ # apply renames from config
408+ self._apply_netcfg_names(netcfg)
409
410+ # rendering config
411 LOG.info("Applying network configuration from %s bringup=%s: %s",
412 src, bring_up, netcfg)
413 try:
414diff --git a/cloudinit/tests/test_stages.py b/cloudinit/tests/test_stages.py
415index 94b6b25..9b48312 100644
416--- a/cloudinit/tests/test_stages.py
417+++ b/cloudinit/tests/test_stages.py
418@@ -37,6 +37,7 @@ class FakeDataSource(sources.DataSource):
419
420 class TestInit(CiTestCase):
421 with_logs = True
422+ allowed_subp = False
423
424 def setUp(self):
425 super(TestInit, self).setUp()
426@@ -166,8 +167,9 @@ class TestInit(CiTestCase):
427 'INFO: network config is disabled by %s' % disable_file,
428 self.logs.getvalue())
429
430+ @mock.patch('cloudinit.net.get_interfaces_by_mac')
431 @mock.patch('cloudinit.distros.ubuntu.Distro')
432- def test_apply_network_on_new_instance(self, m_ubuntu):
433+ def test_apply_network_on_new_instance(self, m_ubuntu, m_macs):
434 """Call distro apply_network_config methods on is_new_instance."""
435 net_cfg = {
436 'version': 1, 'config': [
437@@ -177,6 +179,8 @@ class TestInit(CiTestCase):
438 def fake_network_config():
439 return net_cfg, 'fallback'
440
441+ m_macs.return_value = {'42:42:42:42:42:42': 'eth9'}
442+
443 self.init._find_networking_config = fake_network_config
444 self.init.apply_network_config(True)
445 self.init.distro.apply_network_config_names.assert_called_with(net_cfg)
446@@ -206,8 +210,9 @@ class TestInit(CiTestCase):
447 " nor datasource network update on '%s' event" % EventType.BOOT,
448 self.logs.getvalue())
449
450+ @mock.patch('cloudinit.net.get_interfaces_by_mac')
451 @mock.patch('cloudinit.distros.ubuntu.Distro')
452- def test_apply_network_on_datasource_allowed_event(self, m_ubuntu):
453+ def test_apply_network_on_datasource_allowed_event(self, m_ubuntu, m_macs):
454 """Apply network if datasource.update_metadata permits BOOT event."""
455 old_instance_id = os.path.join(
456 self.init.paths.get_cpath('data'), 'instance-id')
457@@ -220,6 +225,8 @@ class TestInit(CiTestCase):
458 def fake_network_config():
459 return net_cfg, 'fallback'
460
461+ m_macs.return_value = {'42:42:42:42:42:42': 'eth9'}
462+
463 self.init._find_networking_config = fake_network_config
464 self.init.datasource = FakeDataSource(paths=self.init.paths)
465 self.init.datasource.update_events = {'network': [EventType.BOOT]}
466diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
467index 18efce9..de4e7f4 100644
468--- a/tests/unittests/test_net.py
469+++ b/tests/unittests/test_net.py
470@@ -515,7 +515,7 @@ nameserver 172.19.0.12
471 [main]
472 dns = none
473 """.lstrip()),
474- ('etc/udev/rules.d/70-persistent-net.rules',
475+ ('etc/udev/rules.d/85-persistent-net-cloud-init.rules',
476 "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ',
477 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))],
478 'out_sysconfig_rhel': [
479@@ -619,7 +619,7 @@ nameserver 172.19.0.12
480 [main]
481 dns = none
482 """.lstrip()),
483- ('etc/udev/rules.d/70-persistent-net.rules',
484+ ('etc/udev/rules.d/85-persistent-net-cloud-init.rules',
485 "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ',
486 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))],
487 'out_sysconfig_rhel': [
488@@ -750,7 +750,7 @@ nameserver 172.19.0.12
489 [main]
490 dns = none
491 """.lstrip()),
492- ('etc/udev/rules.d/70-persistent-net.rules',
493+ ('etc/udev/rules.d/85-persistent-net-cloud-init.rules',
494 "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ',
495 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))],
496 'out_sysconfig_rhel': [

Subscribers

People subscribed via source and target branches