Merge ~chad.smith/cloud-init:feature/maintain-network-on-boot into cloud-init:master
- Git
- lp:~chad.smith/cloud-init
- feature/maintain-network-on-boot
- Merge into master
Status: | Merged |
---|---|
Approved by: | Chad Smith |
Approved revision: | 61355a62829261b251f9741bced741483a4ce53c |
Merge reported by: | Chad Smith |
Merged at revision: | be9ecc12823607b4709b64408aee137bfdfc7d01 |
Proposed branch: | ~chad.smith/cloud-init:feature/maintain-network-on-boot |
Merge into: | cloud-init:master |
Diff against target: |
522 lines (+417/-6) 5 files modified
cloudinit/event.py (+17/-0) cloudinit/sources/__init__.py (+77/-1) cloudinit/sources/tests/test_init.py (+82/-1) cloudinit/stages.py (+10/-4) cloudinit/tests/test_stages.py (+231/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
Scott Moser | Approve | ||
Review via email: mp+348000@code.launchpad.net |
Commit message
update_metadata: a datasource can support network re-config every boot
Very basic type definitions are now defined to distinguish 'boot'
events from 'new instance (first boot)'. Event types will now be handed
to a datasource.
to refresh its metadata and re-render configuration based on that
source event.
A datasource can 'subscribe' to an event by setting up the update_events
attribute on the datasource class which describe what config scope is
updated by a list of matching events. By default datasources will have
the following update_events: {'network': [EventType.
This setting says the datasource will re-write network configuration only
on first boot of a new instance or when the instance id changes.
New methods are now present on the datasource:
- clear_cached_attrs: Resets cached datasource attributes to values
listed in datasource.
processing a fresh metadata process to avoid keeping old/invalid
cached data around.
- update_metadata: accepts source_event_types to determine if the
metadata should be crawled again and processed
Description of the change
Server Team CI bot (server-team-bot) wrote : | # |
Scott Moser (smoser) wrote : | # |
so where would the logic of "apply if changed" be placed?
Ryan Harper (raharper) wrote : | # |
I'm not super happy with the Maintenance name. Maybe SystemEvent? Also, I think we need other classes of Event types; DatasourceEvents (METADATA_
Chad Smith (chad.smith) : | # |
Mike Gerdts (mgerdts) wrote : | # |
This seems to meet my needs for lp#1765801
Scott Moser (smoser) : | # |
Chad Smith (chad.smith) : | # |
Mike Gerdts (mgerdts) : | # |
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:8d553aed1dd
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild:
https:/
- 82ae6c7... by Chad Smith
-
pylint
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:82ae6c7576f
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild:
https:/
- 45772ff... by Chad Smith
-
increment count in unit tests
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:45772ffa3f3
https:/
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:/
Chad Smith (chad.smith) : | # |
- 4efecd0... by Chad Smith
-
scope update_events to specific config keys, such as network
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:4efecd0a29f
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild:
https:/
- 959cb0c... by Chad Smith
-
rework logic of clear cache add unit test for clearing _network_config
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:959cb0c875b
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild:
https:/
Chad Smith (chad.smith) wrote : | # |
> so where would the logic of "apply if changed" be placed?
In the latest iteration of this branch our "apply if change logic" will require some followup work.
I think we need a branch that will decompose get_data into the following separate methods:
1. ds.detect() - speedy/low-cost python logic performs ds-identify determine if the environment is not a match for this datasource so it can get out fast
2. ds.crawl_
3. ds.get_data() renamed to ds.process_
a. will take crawled_metadata dictionary which will be processed and persisted as instance attributes metadata, vendordata, userdata.
b. write instance-data.json
c. cache crawled_data as ds._crawled_data for future comparison by metadata_changed method
4. ds.metadata_
update_metadata will then look like this:
process_data
if supported_events:
if hasattr(self, 'detect'):
if not ds.detect():
if self.metadata_
- 8b5ce65... by Chad Smith
-
renamed maintenance_events -> update_events.
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:8b5ce65544d
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) wrote : | # |
i'm a +1 with this generally.
c-i says it needs fixing. but I think i'm ok with the basic framework at this point.
- 61355a6... by Chad Smith
-
flakes: rename local events variable to avoid name collision
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:61355a62829
https:/
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:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:61355a62829
https:/
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:/
Chad Smith (chad.smith) wrote : | # |
This merge has landed in commit be9ecc12 to cloud-init branch master.
To view that commit see the following URL:
https:/
Preview Diff
1 | diff --git a/cloudinit/event.py b/cloudinit/event.py |
2 | new file mode 100644 |
3 | index 0000000..f7b311f |
4 | --- /dev/null |
5 | +++ b/cloudinit/event.py |
6 | @@ -0,0 +1,17 @@ |
7 | +# This file is part of cloud-init. See LICENSE file for license information. |
8 | + |
9 | +"""Classes and functions related to event handling.""" |
10 | + |
11 | + |
12 | +# Event types which can generate maintenance requests for cloud-init. |
13 | +class EventType(object): |
14 | + BOOT = "System boot" |
15 | + BOOT_NEW_INSTANCE = "New instance first boot" |
16 | + |
17 | + # TODO: Cloud-init will grow support for the follow event types: |
18 | + # UDEV |
19 | + # METADATA_CHANGE |
20 | + # USER_REQUEST |
21 | + |
22 | + |
23 | +# vi: ts=4 expandtab |
24 | diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py |
25 | index 90d7457..f424316 100644 |
26 | --- a/cloudinit/sources/__init__.py |
27 | +++ b/cloudinit/sources/__init__.py |
28 | @@ -19,6 +19,7 @@ from cloudinit.atomic_helper import write_json |
29 | from cloudinit import importer |
30 | from cloudinit import log as logging |
31 | from cloudinit import net |
32 | +from cloudinit.event import EventType |
33 | from cloudinit import type_utils |
34 | from cloudinit import user_data as ud |
35 | from cloudinit import util |
36 | @@ -102,6 +103,25 @@ class DataSource(object): |
37 | url_timeout = 10 # timeout for each metadata url read attempt |
38 | url_retries = 5 # number of times to retry url upon 404 |
39 | |
40 | + # The datasource defines a list of supported EventTypes during which |
41 | + # the datasource can react to changes in metadata and regenerate |
42 | + # network configuration on metadata changes. |
43 | + # A datasource which supports writing network config on each system boot |
44 | + # would set update_events = {'network': [EventType.BOOT]} |
45 | + |
46 | + # Default: generate network config on new instance id (first boot). |
47 | + update_events = {'network': [EventType.BOOT_NEW_INSTANCE]} |
48 | + |
49 | + # N-tuple listing default values for any metadata-related class |
50 | + # attributes cached on an instance by a process_data runs. These attribute |
51 | + # values are reset via clear_cached_attrs during any update_metadata call. |
52 | + cached_attr_defaults = ( |
53 | + ('ec2_metadata', UNSET), ('network_json', UNSET), |
54 | + ('metadata', {}), ('userdata', None), ('userdata_raw', None), |
55 | + ('vendordata', None), ('vendordata_raw', None)) |
56 | + |
57 | + _dirty_cache = False |
58 | + |
59 | def __init__(self, sys_cfg, distro, paths, ud_proc=None): |
60 | self.sys_cfg = sys_cfg |
61 | self.distro = distro |
62 | @@ -134,11 +154,31 @@ class DataSource(object): |
63 | 'region': self.region, |
64 | 'availability-zone': self.availability_zone}} |
65 | |
66 | + def clear_cached_attrs(self, attr_defaults=()): |
67 | + """Reset any cached metadata attributes to datasource defaults. |
68 | + |
69 | + @param attr_defaults: Optional tuple of (attr, value) pairs to |
70 | + set instead of cached_attr_defaults. |
71 | + """ |
72 | + if not self._dirty_cache: |
73 | + return |
74 | + if attr_defaults: |
75 | + attr_values = attr_defaults |
76 | + else: |
77 | + attr_values = self.cached_attr_defaults |
78 | + |
79 | + for attribute, value in attr_values: |
80 | + if hasattr(self, attribute): |
81 | + setattr(self, attribute, value) |
82 | + if not attr_defaults: |
83 | + self._dirty_cache = False |
84 | + |
85 | def get_data(self): |
86 | """Datasources implement _get_data to setup metadata and userdata_raw. |
87 | |
88 | Minimally, the datasource should return a boolean True on success. |
89 | """ |
90 | + self._dirty_cache = True |
91 | return_value = self._get_data() |
92 | json_file = os.path.join(self.paths.run_dir, INSTANCE_JSON_FILE) |
93 | if not return_value: |
94 | @@ -174,6 +214,7 @@ class DataSource(object): |
95 | return return_value |
96 | |
97 | def _get_data(self): |
98 | + """Walk metadata sources, process crawled data and save attributes.""" |
99 | raise NotImplementedError( |
100 | 'Subclasses of DataSource must implement _get_data which' |
101 | ' sets self.metadata, vendordata_raw and userdata_raw.') |
102 | @@ -416,6 +457,41 @@ class DataSource(object): |
103 | def get_package_mirror_info(self): |
104 | return self.distro.get_package_mirror_info(data_source=self) |
105 | |
106 | + def update_metadata(self, source_event_types): |
107 | + """Refresh cached metadata if the datasource supports this event. |
108 | + |
109 | + The datasource has a list of update_events which |
110 | + trigger refreshing all cached metadata as well as refreshing the |
111 | + network configuration. |
112 | + |
113 | + @param source_event_types: List of EventTypes which may trigger a |
114 | + metadata update. |
115 | + |
116 | + @return True if the datasource did successfully update cached metadata |
117 | + due to source_event_type. |
118 | + """ |
119 | + supported_events = {} |
120 | + for event in source_event_types: |
121 | + for update_scope, update_events in self.update_events.items(): |
122 | + if event in update_events: |
123 | + if not supported_events.get(update_scope): |
124 | + supported_events[update_scope] = [] |
125 | + supported_events[update_scope].append(event) |
126 | + for scope, matched_events in supported_events.items(): |
127 | + LOG.debug( |
128 | + "Update datasource metadata and %s config due to events: %s", |
129 | + scope, ', '.join(matched_events)) |
130 | + # Each datasource has a cached config property which needs clearing |
131 | + # Once cleared that config property will be regenerated from |
132 | + # current metadata. |
133 | + self.clear_cached_attrs((('_%s_config' % scope, UNSET),)) |
134 | + if supported_events: |
135 | + self.clear_cached_attrs() |
136 | + result = self.get_data() |
137 | + if result: |
138 | + return True |
139 | + return False |
140 | + |
141 | def check_instance_id(self, sys_cfg): |
142 | # quickly (local check only) if self.instance_id is still |
143 | return False |
144 | @@ -520,7 +596,7 @@ def find_source(sys_cfg, distro, paths, ds_deps, cfg_list, pkg_list, reporter): |
145 | with myrep: |
146 | LOG.debug("Seeing if we can get any data from %s", cls) |
147 | s = cls(sys_cfg, distro, paths) |
148 | - if s.get_data(): |
149 | + if s.update_metadata([EventType.BOOT_NEW_INSTANCE]): |
150 | myrep.message = "found %s data from %s" % (mode, name) |
151 | return (s, type_utils.obj_name(cls)) |
152 | except Exception: |
153 | diff --git a/cloudinit/sources/tests/test_init.py b/cloudinit/sources/tests/test_init.py |
154 | index d5bc98a..dcd221b 100644 |
155 | --- a/cloudinit/sources/tests/test_init.py |
156 | +++ b/cloudinit/sources/tests/test_init.py |
157 | @@ -5,10 +5,11 @@ import os |
158 | import six |
159 | import stat |
160 | |
161 | +from cloudinit.event import EventType |
162 | from cloudinit.helpers import Paths |
163 | from cloudinit import importer |
164 | from cloudinit.sources import ( |
165 | - INSTANCE_JSON_FILE, DataSource) |
166 | + INSTANCE_JSON_FILE, DataSource, UNSET) |
167 | from cloudinit.tests.helpers import CiTestCase, skipIf, mock |
168 | from cloudinit.user_data import UserDataProcessor |
169 | from cloudinit import util |
170 | @@ -381,3 +382,83 @@ class TestDataSource(CiTestCase): |
171 | get_args(grandchild.get_hostname), # pylint: disable=W1505 |
172 | '%s does not implement DataSource.get_hostname params' |
173 | % grandchild) |
174 | + |
175 | + def test_clear_cached_attrs_resets_cached_attr_class_attributes(self): |
176 | + """Class attributes listed in cached_attr_defaults are reset.""" |
177 | + count = 0 |
178 | + # Setup values for all cached class attributes |
179 | + for attr, value in self.datasource.cached_attr_defaults: |
180 | + setattr(self.datasource, attr, count) |
181 | + count += 1 |
182 | + self.datasource._dirty_cache = True |
183 | + self.datasource.clear_cached_attrs() |
184 | + for attr, value in self.datasource.cached_attr_defaults: |
185 | + self.assertEqual(value, getattr(self.datasource, attr)) |
186 | + |
187 | + def test_clear_cached_attrs_noops_on_clean_cache(self): |
188 | + """Class attributes listed in cached_attr_defaults are reset.""" |
189 | + count = 0 |
190 | + # Setup values for all cached class attributes |
191 | + for attr, _ in self.datasource.cached_attr_defaults: |
192 | + setattr(self.datasource, attr, count) |
193 | + count += 1 |
194 | + self.datasource._dirty_cache = False # Fake clean cache |
195 | + self.datasource.clear_cached_attrs() |
196 | + count = 0 |
197 | + for attr, _ in self.datasource.cached_attr_defaults: |
198 | + self.assertEqual(count, getattr(self.datasource, attr)) |
199 | + count += 1 |
200 | + |
201 | + def test_clear_cached_attrs_skips_non_attr_class_attributes(self): |
202 | + """Skip any cached_attr_defaults which aren't class attributes.""" |
203 | + self.datasource._dirty_cache = True |
204 | + self.datasource.clear_cached_attrs() |
205 | + for attr in ('ec2_metadata', 'network_json'): |
206 | + self.assertFalse(hasattr(self.datasource, attr)) |
207 | + |
208 | + def test_clear_cached_attrs_of_custom_attrs(self): |
209 | + """Custom attr_values can be passed to clear_cached_attrs.""" |
210 | + self.datasource._dirty_cache = True |
211 | + cached_attr_name = self.datasource.cached_attr_defaults[0][0] |
212 | + setattr(self.datasource, cached_attr_name, 'himom') |
213 | + self.datasource.myattr = 'orig' |
214 | + self.datasource.clear_cached_attrs( |
215 | + attr_defaults=(('myattr', 'updated'),)) |
216 | + self.assertEqual('himom', getattr(self.datasource, cached_attr_name)) |
217 | + self.assertEqual('updated', self.datasource.myattr) |
218 | + |
219 | + def test_update_metadata_only_acts_on_supported_update_events(self): |
220 | + """update_metadata won't get_data on unsupported update events.""" |
221 | + self.assertEqual( |
222 | + {'network': [EventType.BOOT_NEW_INSTANCE]}, |
223 | + self.datasource.update_events) |
224 | + |
225 | + def fake_get_data(): |
226 | + raise Exception('get_data should not be called') |
227 | + |
228 | + self.datasource.get_data = fake_get_data |
229 | + self.assertFalse( |
230 | + self.datasource.update_metadata( |
231 | + source_event_types=[EventType.BOOT])) |
232 | + |
233 | + def test_update_metadata_returns_true_on_supported_update_event(self): |
234 | + """update_metadata returns get_data response on supported events.""" |
235 | + |
236 | + def fake_get_data(): |
237 | + return True |
238 | + |
239 | + self.datasource.get_data = fake_get_data |
240 | + self.datasource._network_config = 'something' |
241 | + self.datasource._dirty_cache = True |
242 | + self.assertTrue( |
243 | + self.datasource.update_metadata( |
244 | + source_event_types=[ |
245 | + EventType.BOOT, EventType.BOOT_NEW_INSTANCE])) |
246 | + self.assertEqual(UNSET, self.datasource._network_config) |
247 | + self.assertIn( |
248 | + "DEBUG: Update datasource metadata and network config due to" |
249 | + " events: New instance first boot", |
250 | + self.logs.getvalue()) |
251 | + |
252 | + |
253 | +# vi: ts=4 expandtab |
254 | diff --git a/cloudinit/stages.py b/cloudinit/stages.py |
255 | index 286607b..c132b57 100644 |
256 | --- a/cloudinit/stages.py |
257 | +++ b/cloudinit/stages.py |
258 | @@ -22,6 +22,8 @@ from cloudinit.handlers import cloud_config as cc_part |
259 | from cloudinit.handlers import shell_script as ss_part |
260 | from cloudinit.handlers import upstart_job as up_part |
261 | |
262 | +from cloudinit.event import EventType |
263 | + |
264 | from cloudinit import cloud |
265 | from cloudinit import config |
266 | from cloudinit import distros |
267 | @@ -648,10 +650,14 @@ class Init(object): |
268 | except Exception as e: |
269 | LOG.warning("Failed to rename devices: %s", e) |
270 | |
271 | - if (self.datasource is not NULL_DATA_SOURCE and |
272 | - not self.is_new_instance()): |
273 | - LOG.debug("not a new instance. network config is not applied.") |
274 | - return |
275 | + if self.datasource is not NULL_DATA_SOURCE: |
276 | + if not self.is_new_instance(): |
277 | + if not self.datasource.update_metadata([EventType.BOOT]): |
278 | + LOG.debug( |
279 | + "No network config applied. Neither a new instance" |
280 | + " nor datasource network update on '%s' event", |
281 | + EventType.BOOT) |
282 | + return |
283 | |
284 | LOG.info("Applying network configuration from %s bringup=%s: %s", |
285 | src, bring_up, netcfg) |
286 | diff --git a/cloudinit/tests/test_stages.py b/cloudinit/tests/test_stages.py |
287 | new file mode 100644 |
288 | index 0000000..94b6b25 |
289 | --- /dev/null |
290 | +++ b/cloudinit/tests/test_stages.py |
291 | @@ -0,0 +1,231 @@ |
292 | +# This file is part of cloud-init. See LICENSE file for license information. |
293 | + |
294 | +"""Tests related to cloudinit.stages module.""" |
295 | + |
296 | +import os |
297 | + |
298 | +from cloudinit import stages |
299 | +from cloudinit import sources |
300 | + |
301 | +from cloudinit.event import EventType |
302 | +from cloudinit.util import write_file |
303 | + |
304 | +from cloudinit.tests.helpers import CiTestCase, mock |
305 | + |
306 | +TEST_INSTANCE_ID = 'i-testing' |
307 | + |
308 | + |
309 | +class FakeDataSource(sources.DataSource): |
310 | + |
311 | + def __init__(self, paths=None, userdata=None, vendordata=None, |
312 | + network_config=''): |
313 | + super(FakeDataSource, self).__init__({}, None, paths=paths) |
314 | + self.metadata = {'instance-id': TEST_INSTANCE_ID} |
315 | + self.userdata_raw = userdata |
316 | + self.vendordata_raw = vendordata |
317 | + self._network_config = None |
318 | + if network_config: # Permit for None value to setup attribute |
319 | + self._network_config = network_config |
320 | + |
321 | + @property |
322 | + def network_config(self): |
323 | + return self._network_config |
324 | + |
325 | + def _get_data(self): |
326 | + return True |
327 | + |
328 | + |
329 | +class TestInit(CiTestCase): |
330 | + with_logs = True |
331 | + |
332 | + def setUp(self): |
333 | + super(TestInit, self).setUp() |
334 | + self.tmpdir = self.tmp_dir() |
335 | + self.init = stages.Init() |
336 | + # Setup fake Paths for Init to reference |
337 | + self.init._cfg = {'system_info': { |
338 | + 'distro': 'ubuntu', 'paths': {'cloud_dir': self.tmpdir, |
339 | + 'run_dir': self.tmpdir}}} |
340 | + self.init.datasource = FakeDataSource(paths=self.init.paths) |
341 | + |
342 | + def test_wb__find_networking_config_disabled(self): |
343 | + """find_networking_config returns no config when disabled.""" |
344 | + disable_file = os.path.join( |
345 | + self.init.paths.get_cpath('data'), 'upgraded-network') |
346 | + write_file(disable_file, '') |
347 | + self.assertEqual( |
348 | + (None, disable_file), |
349 | + self.init._find_networking_config()) |
350 | + |
351 | + @mock.patch('cloudinit.stages.cmdline.read_kernel_cmdline_config') |
352 | + def test_wb__find_networking_config_disabled_by_kernel(self, m_cmdline): |
353 | + """find_networking_config returns when disabled by kernel cmdline.""" |
354 | + m_cmdline.return_value = {'config': 'disabled'} |
355 | + self.assertEqual( |
356 | + (None, 'cmdline'), |
357 | + self.init._find_networking_config()) |
358 | + self.assertEqual('DEBUG: network config disabled by cmdline\n', |
359 | + self.logs.getvalue()) |
360 | + |
361 | + @mock.patch('cloudinit.stages.cmdline.read_kernel_cmdline_config') |
362 | + def test_wb__find_networking_config_disabled_by_datasrc(self, m_cmdline): |
363 | + """find_networking_config returns when disabled by datasource cfg.""" |
364 | + m_cmdline.return_value = {} # Kernel doesn't disable networking |
365 | + self.init._cfg = {'system_info': {'paths': {'cloud_dir': self.tmpdir}}, |
366 | + 'network': {}} # system config doesn't disable |
367 | + |
368 | + self.init.datasource = FakeDataSource( |
369 | + network_config={'config': 'disabled'}) |
370 | + self.assertEqual( |
371 | + (None, 'ds'), |
372 | + self.init._find_networking_config()) |
373 | + self.assertEqual('DEBUG: network config disabled by ds\n', |
374 | + self.logs.getvalue()) |
375 | + |
376 | + @mock.patch('cloudinit.stages.cmdline.read_kernel_cmdline_config') |
377 | + def test_wb__find_networking_config_disabled_by_sysconfig(self, m_cmdline): |
378 | + """find_networking_config returns when disabled by system config.""" |
379 | + m_cmdline.return_value = {} # Kernel doesn't disable networking |
380 | + self.init._cfg = {'system_info': {'paths': {'cloud_dir': self.tmpdir}}, |
381 | + 'network': {'config': 'disabled'}} |
382 | + self.assertEqual( |
383 | + (None, 'system_cfg'), |
384 | + self.init._find_networking_config()) |
385 | + self.assertEqual('DEBUG: network config disabled by system_cfg\n', |
386 | + self.logs.getvalue()) |
387 | + |
388 | + @mock.patch('cloudinit.stages.cmdline.read_kernel_cmdline_config') |
389 | + def test_wb__find_networking_config_returns_kernel(self, m_cmdline): |
390 | + """find_networking_config returns kernel cmdline config if present.""" |
391 | + expected_cfg = {'config': ['fakekernel']} |
392 | + m_cmdline.return_value = expected_cfg |
393 | + self.init._cfg = {'system_info': {'paths': {'cloud_dir': self.tmpdir}}, |
394 | + 'network': {'config': ['fakesys_config']}} |
395 | + self.init.datasource = FakeDataSource( |
396 | + network_config={'config': ['fakedatasource']}) |
397 | + self.assertEqual( |
398 | + (expected_cfg, 'cmdline'), |
399 | + self.init._find_networking_config()) |
400 | + |
401 | + @mock.patch('cloudinit.stages.cmdline.read_kernel_cmdline_config') |
402 | + def test_wb__find_networking_config_returns_system_cfg(self, m_cmdline): |
403 | + """find_networking_config returns system config when present.""" |
404 | + m_cmdline.return_value = {} # No kernel network config |
405 | + expected_cfg = {'config': ['fakesys_config']} |
406 | + self.init._cfg = {'system_info': {'paths': {'cloud_dir': self.tmpdir}}, |
407 | + 'network': expected_cfg} |
408 | + self.init.datasource = FakeDataSource( |
409 | + network_config={'config': ['fakedatasource']}) |
410 | + self.assertEqual( |
411 | + (expected_cfg, 'system_cfg'), |
412 | + self.init._find_networking_config()) |
413 | + |
414 | + @mock.patch('cloudinit.stages.cmdline.read_kernel_cmdline_config') |
415 | + def test_wb__find_networking_config_returns_datasrc_cfg(self, m_cmdline): |
416 | + """find_networking_config returns datasource net config if present.""" |
417 | + m_cmdline.return_value = {} # No kernel network config |
418 | + # No system config for network in setUp |
419 | + expected_cfg = {'config': ['fakedatasource']} |
420 | + self.init.datasource = FakeDataSource(network_config=expected_cfg) |
421 | + self.assertEqual( |
422 | + (expected_cfg, 'ds'), |
423 | + self.init._find_networking_config()) |
424 | + |
425 | + @mock.patch('cloudinit.stages.cmdline.read_kernel_cmdline_config') |
426 | + def test_wb__find_networking_config_returns_fallback(self, m_cmdline): |
427 | + """find_networking_config returns fallback config if not defined.""" |
428 | + m_cmdline.return_value = {} # Kernel doesn't disable networking |
429 | + # Neither datasource nor system_info disable or provide network |
430 | + |
431 | + fake_cfg = {'config': [{'type': 'physical', 'name': 'eth9'}], |
432 | + 'version': 1} |
433 | + |
434 | + def fake_generate_fallback(): |
435 | + return fake_cfg |
436 | + |
437 | + # Monkey patch distro which gets cached on self.init |
438 | + distro = self.init.distro |
439 | + distro.generate_fallback_config = fake_generate_fallback |
440 | + self.assertEqual( |
441 | + (fake_cfg, 'fallback'), |
442 | + self.init._find_networking_config()) |
443 | + self.assertNotIn('network config disabled', self.logs.getvalue()) |
444 | + |
445 | + def test_apply_network_config_disabled(self): |
446 | + """Log when network is disabled by upgraded-network.""" |
447 | + disable_file = os.path.join( |
448 | + self.init.paths.get_cpath('data'), 'upgraded-network') |
449 | + |
450 | + def fake_network_config(): |
451 | + return (None, disable_file) |
452 | + |
453 | + self.init._find_networking_config = fake_network_config |
454 | + |
455 | + self.init.apply_network_config(True) |
456 | + self.assertIn( |
457 | + 'INFO: network config is disabled by %s' % disable_file, |
458 | + self.logs.getvalue()) |
459 | + |
460 | + @mock.patch('cloudinit.distros.ubuntu.Distro') |
461 | + def test_apply_network_on_new_instance(self, m_ubuntu): |
462 | + """Call distro apply_network_config methods on is_new_instance.""" |
463 | + net_cfg = { |
464 | + 'version': 1, 'config': [ |
465 | + {'subnets': [{'type': 'dhcp'}], 'type': 'physical', |
466 | + 'name': 'eth9', 'mac_address': '42:42:42:42:42:42'}]} |
467 | + |
468 | + def fake_network_config(): |
469 | + return net_cfg, 'fallback' |
470 | + |
471 | + self.init._find_networking_config = fake_network_config |
472 | + self.init.apply_network_config(True) |
473 | + self.init.distro.apply_network_config_names.assert_called_with(net_cfg) |
474 | + self.init.distro.apply_network_config.assert_called_with( |
475 | + net_cfg, bring_up=True) |
476 | + |
477 | + @mock.patch('cloudinit.distros.ubuntu.Distro') |
478 | + def test_apply_network_on_same_instance_id(self, m_ubuntu): |
479 | + """Only call distro.apply_network_config_names on same instance id.""" |
480 | + old_instance_id = os.path.join( |
481 | + self.init.paths.get_cpath('data'), 'instance-id') |
482 | + write_file(old_instance_id, TEST_INSTANCE_ID) |
483 | + net_cfg = { |
484 | + 'version': 1, 'config': [ |
485 | + {'subnets': [{'type': 'dhcp'}], 'type': 'physical', |
486 | + 'name': 'eth9', 'mac_address': '42:42:42:42:42:42'}]} |
487 | + |
488 | + def fake_network_config(): |
489 | + return net_cfg, 'fallback' |
490 | + |
491 | + self.init._find_networking_config = fake_network_config |
492 | + self.init.apply_network_config(True) |
493 | + self.init.distro.apply_network_config_names.assert_called_with(net_cfg) |
494 | + self.init.distro.apply_network_config.assert_not_called() |
495 | + self.assertIn( |
496 | + 'No network config applied. Neither a new instance' |
497 | + " nor datasource network update on '%s' event" % EventType.BOOT, |
498 | + self.logs.getvalue()) |
499 | + |
500 | + @mock.patch('cloudinit.distros.ubuntu.Distro') |
501 | + def test_apply_network_on_datasource_allowed_event(self, m_ubuntu): |
502 | + """Apply network if datasource.update_metadata permits BOOT event.""" |
503 | + old_instance_id = os.path.join( |
504 | + self.init.paths.get_cpath('data'), 'instance-id') |
505 | + write_file(old_instance_id, TEST_INSTANCE_ID) |
506 | + net_cfg = { |
507 | + 'version': 1, 'config': [ |
508 | + {'subnets': [{'type': 'dhcp'}], 'type': 'physical', |
509 | + 'name': 'eth9', 'mac_address': '42:42:42:42:42:42'}]} |
510 | + |
511 | + def fake_network_config(): |
512 | + return net_cfg, 'fallback' |
513 | + |
514 | + self.init._find_networking_config = fake_network_config |
515 | + self.init.datasource = FakeDataSource(paths=self.init.paths) |
516 | + self.init.datasource.update_events = {'network': [EventType.BOOT]} |
517 | + self.init.apply_network_config(True) |
518 | + self.init.distro.apply_network_config_names.assert_called_with(net_cfg) |
519 | + self.init.distro.apply_network_config.assert_called_with( |
520 | + net_cfg, bring_up=True) |
521 | + |
522 | +# vi: ts=4 expandtab |
FAILED: Continuous integration, rev:ca3b0d7c1a6 fc23bbea2945885 5c6c9550ea08c4 /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 84/
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 84/rebuild
https:/