Merge ~chad.smith/cloud-init:feature/azure-dhcp-coldplug into cloud-init:master

Proposed by Chad Smith
Status: Work in progress
Proposed branch: ~chad.smith/cloud-init:feature/azure-dhcp-coldplug
Merge into: cloud-init:master
Diff against target: 736 lines (+263/-17) (has conflicts)
23 files modified
cloudinit/hotplug.py (+17/-0)
cloudinit/sources/DataSourceAltCloud.py (+1/-1)
cloudinit/sources/DataSourceAzure.py (+135/-0)
cloudinit/sources/DataSourceBigstep.py (+1/-1)
cloudinit/sources/DataSourceCloudSigma.py (+1/-1)
cloudinit/sources/DataSourceCloudStack.py (+1/-1)
cloudinit/sources/DataSourceConfigDrive.py (+1/-1)
cloudinit/sources/DataSourceDigitalOcean.py (+1/-1)
cloudinit/sources/DataSourceEc2.py (+12/-0)
cloudinit/sources/DataSourceGCE.py (+1/-1)
cloudinit/sources/DataSourceIBMCloud.py (+1/-1)
cloudinit/sources/DataSourceMAAS.py (+1/-1)
cloudinit/sources/DataSourceNoCloud.py (+4/-0)
cloudinit/sources/DataSourceNone.py (+1/-1)
cloudinit/sources/DataSourceOVF.py (+1/-1)
cloudinit/sources/DataSourceOpenNebula.py (+1/-1)
cloudinit/sources/DataSourceOpenStack.py (+1/-1)
cloudinit/sources/DataSourceScaleway.py (+7/-0)
cloudinit/sources/DataSourceSmartOS.py (+1/-1)
cloudinit/sources/__init__.py (+57/-3)
cloudinit/sources/tests/test_init.py (+7/-0)
cloudinit/stages.py (+7/-0)
tests/unittests/test_datasource/test_azure.py (+3/-0)
Conflict in cloudinit/sources/DataSourceAzure.py
Conflict in cloudinit/sources/DataSourceEc2.py
Conflict in cloudinit/sources/DataSourceNoCloud.py
Conflict in cloudinit/sources/DataSourceScaleway.py
Conflict in cloudinit/sources/__init__.py
Conflict in cloudinit/sources/tests/test_init.py
Conflict in cloudinit/stages.py
Conflict in tests/unittests/test_datasource/test_azure.py
Reviewer Review Type Date Requested Status
cloud-init Commiters Pending
Review via email: mp+347991@code.launchpad.net

Commit message

WIP for discussion

To post a comment you must log in.
d8c9e65... by Chad Smith

revert local test changes

63e40e1... by Chad Smith

wip

857c1bc... by Chad Smith

revert remove azure unit tests

Unmerged commits

857c1bc... by Chad Smith

revert remove azure unit tests

63e40e1... by Chad Smith

wip

d8c9e65... by Chad Smith

revert local test changes

1bb8aeb... by Chad Smith

add maintenance event string lookup for logging

5c146bb... by Chad Smith

hook into datasource.maintain_metadata

9967383... by Chad Smith

WIP: Azure react to network config changes on each reboot

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/hotplug.py b/cloudinit/hotplug.py
2new file mode 100644
3index 0000000..7e4daeb
4--- /dev/null
5+++ b/cloudinit/hotplug.py
6@@ -0,0 +1,17 @@
7+# This file is part of cloud-init. See LICENSE file for license information.
8+"""Classes and functions related to hotplug and eventing."""
9+
10+# Maintenance events describing the source generating a maintenance request.
11+class MaintenanceEvent(object):
12+ NONE = 0x0 # React to no maintenance events
13+ BOOT = 0x1 # Any system boot or reboot event
14+ DEVICE_ADD = 0x2 # Any new device added
15+ DEVICE_REMOVE = 0x4 # Any device removed
16+ DEVICE_CHANGE = 0x8 # Any device metadata change
17+ ANY = 0xF # Match any defined MaintenanceEvents
18+
19+
20+MAINTENANCE_EVENT_STR = dict(
21+ (attr, getattr(MaintenanceEvent, attr))
22+ for attr in MaintenanceEvent.__dict__.keys() if attr.isupper())
23+
24diff --git a/cloudinit/sources/DataSourceAltCloud.py b/cloudinit/sources/DataSourceAltCloud.py
25index 5270fda..dbd79ba 100644
26--- a/cloudinit/sources/DataSourceAltCloud.py
27+++ b/cloudinit/sources/DataSourceAltCloud.py
28@@ -123,7 +123,7 @@ class DataSourceAltCloud(sources.DataSource):
29
30 return 'UNKNOWN'
31
32- def _get_data(self):
33+ def _get_data(self, clear_cache=False):
34 '''
35 Description:
36 User Data is passed to the launching instance which
37diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
38old mode 100755
39new mode 100644
40index d2fad9b..996285e
41--- a/cloudinit/sources/DataSourceAzure.py
42+++ b/cloudinit/sources/DataSourceAzure.py
43@@ -18,7 +18,11 @@ import xml.etree.ElementTree as ET
44
45 from cloudinit import log as logging
46 from cloudinit import net
47+<<<<<<< cloudinit/sources/DataSourceAzure.py
48 from cloudinit.event import EventType
49+=======
50+from cloudinit.hotplug import MaintenanceEvent
51+>>>>>>> cloudinit/sources/DataSourceAzure.py
52 from cloudinit.net.dhcp import EphemeralDHCPv4
53 from cloudinit import sources
54 from cloudinit.sources.helpers import netlink
55@@ -56,6 +60,7 @@ DEFAULT_FS = 'ext4'
56 AZURE_CHASSIS_ASSET_TAG = '7783-7084-3265-9085-8269-3286-77'
57 REPROVISION_MARKER_FILE = "/var/lib/cloud/data/poll_imds"
58 REPORTED_READY_MARKER_FILE = "/var/lib/cloud/data/reported_ready"
59+<<<<<<< cloudinit/sources/DataSourceAzure.py
60 AGENT_SEED_DIR = '/var/lib/waagent'
61
62 # In the event where the IMDS primary server is not
63@@ -73,6 +78,9 @@ UBUNTU_EXTENDED_NETWORK_SCRIPTS = [
64 '/etc/udev/rules.d/10-net-device-added.rules',
65 '/run/network/interfaces.ephemeral.d',
66 ]
67+=======
68+IMDS_URL = "http://169.254.169.254/metadata/"
69+>>>>>>> cloudinit/sources/DataSourceAzure.py
70
71
72 def find_storvscid_from_sysctl_pnpinfo(sysctl_out, deviceid):
73@@ -281,6 +289,12 @@ class DataSourceAzure(sources.DataSource):
74 dsname = 'Azure'
75 _negotiated = False
76 _metadata_imds = sources.UNSET
77+<<<<<<< cloudinit/sources/DataSourceAzure.py
78+=======
79+
80+ # Only regenerate network config on system boot for Azure
81+ network_maintenance_mask = MaintenanceEvent.BOOT
82+>>>>>>> cloudinit/sources/DataSourceAzure.py
83
84 def __init__(self, sys_cfg, distro, paths):
85 sources.DataSource.__init__(self, sys_cfg, distro, paths)
86@@ -373,6 +387,7 @@ class DataSourceAzure(sources.DataSource):
87 metadata['public-keys'] = key_value or pubkeys_from_crt_files(fp_files)
88 return metadata
89
90+<<<<<<< cloudinit/sources/DataSourceAzure.py
91 def _get_subplatform(self):
92 """Return the subplatform metadata source details."""
93 if self.seed.startswith('/dev'):
94@@ -390,6 +405,17 @@ class DataSourceAzure(sources.DataSource):
95 unavailable, broken or disabled.
96 """
97 crawled_data = {}
98+=======
99+
100+ @property
101+ def region(self):
102+ imds_compute_metadata = self.metadata.get('imds', {}).get('compute', {})
103+ if imds_compute_metadata:
104+ return imds_compute_metadata.get('location')
105+ return None
106+
107+ def _get_data(self, clear_cache=False):
108+>>>>>>> cloudinit/sources/DataSourceAzure.py
109 # azure removes/ejects the cdrom containing the ovf-env.xml
110 # file on reboot. So, in order to successfully reboot we
111 # need to look in the datadir and consider that valid
112@@ -434,6 +460,7 @@ class DataSourceAzure(sources.DataSource):
113 LOG.error(msg)
114 raise sources.InvalidMetaDataException(msg)
115 ret = self._reprovision()
116+<<<<<<< cloudinit/sources/DataSourceAzure.py
117 imds_md = get_metadata_from_imds(
118 self.fallback_interface, retries=10)
119 (md, userdata_raw, cfg, files) = ret
120@@ -444,6 +471,16 @@ class DataSourceAzure(sources.DataSource):
121 'metadata': util.mergemanydict(
122 [md, {'imds': imds_md}]),
123 'userdata_raw': userdata_raw})
124+=======
125+ if self._metadata_imds == sources.UNSET:
126+ self._metadata_imds = get_metadata_from_imds(
127+ self.fallback_interface, retries=3)
128+
129+ (md, self.userdata_raw, cfg, files) = ret
130+ self.seed = cdev
131+ self.metadata = util.mergemanydict([md, {'imds': self._metadata_imds}, DEFAULT_METADATA])
132+ self.cfg = util.mergemanydict([cfg, BUILTIN_CLOUD_CONFIG])
133+>>>>>>> cloudinit/sources/DataSourceAzure.py
134 found = cdev
135
136 LOG.debug("found datasource in %s", cdev)
137@@ -562,6 +599,7 @@ class DataSourceAzure(sources.DataSource):
138 LOG.debug("Wait for vnetswitch to happen")
139 while True:
140 try:
141+<<<<<<< cloudinit/sources/DataSourceAzure.py
142 # Save our EphemeralDHCPv4 context so we avoid repeated dhcp
143 self._ephemeral_dhcp_ctx = EphemeralDHCPv4()
144 lease = self._ephemeral_dhcp_ctx.obtain_lease()
145@@ -590,6 +628,19 @@ class DataSourceAzure(sources.DataSource):
146 return readurl(url, timeout=IMDS_TIMEOUT_IN_SECONDS,
147 headers=headers, exception_cb=exc_cb,
148 infinite=True, log_req_resp=False).contents
149+=======
150+ with EphemeralDHCPv4() as lease:
151+ if report_ready:
152+ path = REPORTED_READY_MARKER_FILE
153+ LOG.info(
154+ "Creating a marker file to report ready: %s", path)
155+ util.write_file(path, "{pid}: {time}\n".format(
156+ pid=os.getpid(), time=time()))
157+ self._report_ready(lease=lease)
158+ report_ready = False
159+ return str(readurl(url, timeout=1, headers=headers,
160+ exception_cb=exc_cb, infinite=True))
161+>>>>>>> cloudinit/sources/DataSourceAzure.py
162 except UrlError:
163 # Teardown our EphemeralDHCPv4 context on failure as we retry
164 self._ephemeral_dhcp_ctx.clean_network()
165@@ -698,12 +749,56 @@ class DataSourceAzure(sources.DataSource):
166 2. Generate a fallback network config that does not include any of
167 the blacklisted devices.
168 """
169+<<<<<<< cloudinit/sources/DataSourceAzure.py
170 if not self._network_config or self._network_config == sources.UNSET:
171 if self.ds_cfg.get('apply_network_config'):
172 nc_src = self._metadata_imds
173 else:
174 nc_src = None
175 self._network_config = parse_network_config(nc_src)
176+=======
177+ blacklist = ['mlx4_core']
178+ if not self._network_config:
179+ if self._metadata_imds != sources.UNSET:
180+ netconfig = {'version': 2, 'ethernets': {}}
181+ LOG.debug('Azure: generating network configuration from IMDS')
182+ network_metadata = self._metadata_imds['network']
183+ for idx, intf in enumerate(network_metadata['interface']):
184+ nicname = 'eth{idx}'.format(idx=idx)
185+ dev_config = {}
186+ for addr4 in intf['ipv4']['ipAddress']:
187+ privateIpv4 = addr4['privateIpAddress']
188+ if privateIpv4:
189+ if dev_config.get('dhcp4', False):
190+ # Append static address config for nic > 1
191+ netPrefix = intf['ipv4']['subnet'][0].get(
192+ 'prefix', '24')
193+ if not dev_config.get('addresses'):
194+ dev_config['addresses'] = []
195+ dev_config['addresses'].append(
196+ '{ip}/{prefix}'.format(
197+ ip=privateIpv4, prefix=netPrefix))
198+ else:
199+ dev_config['dhcp4'] = True
200+ for addr6 in intf['ipv6']['ipAddress']:
201+ privateIpv6 = addr6['privateIpAddress']
202+ if privateIpv6:
203+ dev_config['dhcp6'] = True
204+ break
205+ if dev_config:
206+ mac = ':'.join(re.findall(r'..', intf['macAddress']))
207+ dev_config.update(
208+ {'match': {'macaddress': mac.lower()},
209+ 'set-name': nicname})
210+ netconfig['ethernets'][nicname] = dev_config
211+ else:
212+ LOG.debug('Azure: generating fallback configuration')
213+ # generate a network config, blacklist picking mlx4_core devs
214+ netconfig = net.generate_fallback_config(
215+ blacklist_drivers=blacklist, config_driver=True)
216+
217+ self._network_config = netconfig
218+>>>>>>> cloudinit/sources/DataSourceAzure.py
219 return self._network_config
220
221 @property
222@@ -1217,6 +1312,7 @@ def load_azure_ds_dir(source_dir):
223 return (md, ud, cfg, {'ovf-env.xml': contents})
224
225
226+<<<<<<< cloudinit/sources/DataSourceAzure.py
227 def parse_network_config(imds_metadata):
228 """Convert imds_metadata dictionary to network v2 configuration.
229
230@@ -1274,6 +1370,8 @@ def parse_network_config(imds_metadata):
231
232
233 @azure_ds_telemetry_reporter
234+=======
235+>>>>>>> cloudinit/sources/DataSourceAzure.py
236 def get_metadata_from_imds(fallback_nic, retries):
237 """Query Azure's network metadata service, returning a dictionary.
238
239@@ -1282,12 +1380,17 @@ def get_metadata_from_imds(fallback_nic, retries):
240 https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service
241
242 @param fallback_nic: String. The name of the nic which requires active
243+<<<<<<< cloudinit/sources/DataSourceAzure.py
244 network in order to query IMDS.
245+=======
246+ networ in order to query IMDS.
247+>>>>>>> cloudinit/sources/DataSourceAzure.py
248 @param retries: The number of retries of the IMDS_URL.
249
250 @return: A dict of instance metadata containing compute and network
251 info.
252 """
253+<<<<<<< cloudinit/sources/DataSourceAzure.py
254 kwargs = {'logfunc': LOG.debug,
255 'msg': 'Crawl of Azure Instance Metadata Service (IMDS)',
256 'func': _get_metadata_from_imds, 'args': (retries,)}
257@@ -1313,11 +1416,41 @@ def _get_metadata_from_imds(retries):
258 try:
259 return util.load_json(str(response))
260 except json.decoder.JSONDecodeError:
261+=======
262+ if net.is_up(fallback_nic):
263+ return util.log_time(
264+ logfunc=LOG.debug,
265+ msg='Crawl of Azure Instance Metadata Service (IMDS)',
266+ func=_get_metadata_from_imds, args=(retries,))
267+ else:
268+ with EphemeralDHCPv4(fallback_nic):
269+ return util.log_time(
270+ logfunc=LOG.debug,
271+ msg='Crawl of Azure Instance Metadata Service (IMDS)',
272+ func=_get_metadata_from_imds, args=(retries,))
273+
274+
275+def _get_metadata_from_imds(retries):
276+ def retry_on_url_error(msg, exception):
277+ if isinstance(exception, UrlError):
278+ return True # Continue retries
279+ return False # Stop retries on all other exceptions, including 404s
280+
281+ url = IMDS_URL + "instance?api-version=2017-12-01"
282+ headers = {"Metadata": "true"}
283+ response = readurl(
284+ url, timeout=1, headers=headers, retries=3,
285+ exception_cb=retry_on_url_error)
286+ try:
287+ return util.load_json(str(response))
288+ except:
289+>>>>>>> cloudinit/sources/DataSourceAzure.py
290 LOG.warning(
291 'Ignoring non-json IMDS instance metadata: %s', str(response))
292 return {}
293
294
295+<<<<<<< cloudinit/sources/DataSourceAzure.py
296 @azure_ds_telemetry_reporter
297 def maybe_remove_ubuntu_network_config_scripts(paths=None):
298 """Remove Azure-specific ubuntu network config for non-primary nics.
299@@ -1372,6 +1505,8 @@ def _is_platform_viable(seed_dir):
300 return False
301
302
303+=======
304+>>>>>>> cloudinit/sources/DataSourceAzure.py
305 class BrokenAzureDataSource(Exception):
306 pass
307
308diff --git a/cloudinit/sources/DataSourceBigstep.py b/cloudinit/sources/DataSourceBigstep.py
309index 52fff20..c83cd31 100644
310--- a/cloudinit/sources/DataSourceBigstep.py
311+++ b/cloudinit/sources/DataSourceBigstep.py
312@@ -25,7 +25,7 @@ class DataSourceBigstep(sources.DataSource):
313 self.vendordata_raw = ""
314 self.userdata_raw = ""
315
316- def _get_data(self, apply_filter=False):
317+ def _get_data(self, clear_cache=False, apply_filter=False):
318 url = get_url_from_file()
319 if url is None:
320 return False
321diff --git a/cloudinit/sources/DataSourceCloudSigma.py b/cloudinit/sources/DataSourceCloudSigma.py
322index 2955d3f..9f71700 100644
323--- a/cloudinit/sources/DataSourceCloudSigma.py
324+++ b/cloudinit/sources/DataSourceCloudSigma.py
325@@ -49,7 +49,7 @@ class DataSourceCloudSigma(sources.DataSource):
326 LOG.warning("failed to query dmi data for system product name")
327 return False
328
329- def _get_data(self):
330+ def _get_data(self, clear_cache=False):
331 """
332 Metadata is the whole server context and /meta/cloud-config is used
333 as userdata.
334diff --git a/cloudinit/sources/DataSourceCloudStack.py b/cloudinit/sources/DataSourceCloudStack.py
335index f185dc7..f76c63e 100644
336--- a/cloudinit/sources/DataSourceCloudStack.py
337+++ b/cloudinit/sources/DataSourceCloudStack.py
338@@ -109,7 +109,7 @@ class DataSourceCloudStack(sources.DataSource):
339 def get_config_obj(self):
340 return self.cfg
341
342- def _get_data(self):
343+ def _get_data(self, clear_cache=False):
344 seed_ret = {}
345 if util.read_optional_seed(seed_ret, base=(self.seed_dir + "/")):
346 self.userdata_raw = seed_ret['user-data']
347diff --git a/cloudinit/sources/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py
348index 571d30d..be32e67 100644
349--- a/cloudinit/sources/DataSourceConfigDrive.py
350+++ b/cloudinit/sources/DataSourceConfigDrive.py
351@@ -54,7 +54,7 @@ class DataSourceConfigDrive(openstack.SourceMixin, sources.DataSource):
352 mstr += "[source=%s]" % (self.source)
353 return mstr
354
355- def _get_data(self):
356+ def _get_data(self, clear_cache=False):
357 found = None
358 md = {}
359 results = {}
360diff --git a/cloudinit/sources/DataSourceDigitalOcean.py b/cloudinit/sources/DataSourceDigitalOcean.py
361index e0ef665..25e8c87 100644
362--- a/cloudinit/sources/DataSourceDigitalOcean.py
363+++ b/cloudinit/sources/DataSourceDigitalOcean.py
364@@ -47,7 +47,7 @@ class DataSourceDigitalOcean(sources.DataSource):
365 def _get_sysinfo(self):
366 return do_helper.read_sysinfo()
367
368- def _get_data(self):
369+ def _get_data(self, clear_cache=False):
370 (is_do, droplet_id) = self._get_sysinfo()
371
372 # only proceed if we know we are on DigitalOcean
373diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
374index 5c017bf..d6984a5 100644
375--- a/cloudinit/sources/DataSourceEc2.py
376+++ b/cloudinit/sources/DataSourceEc2.py
377@@ -73,7 +73,19 @@ class DataSourceEc2(sources.DataSource):
378 """Return the cloud name as identified during _get_data."""
379 return identify_platform()
380
381+<<<<<<< cloudinit/sources/DataSourceEc2.py
382 def _get_data(self):
383+=======
384+ def _get_data(self, clear_cache=False):
385+ seed_ret = {}
386+ if util.read_optional_seed(seed_ret, base=(self.seed_dir + "/")):
387+ self.userdata_raw = seed_ret['user-data']
388+ self.metadata = seed_ret['meta-data']
389+ LOG.debug("Using seeded ec2 data from %s", self.seed_dir)
390+ self._cloud_platform = Platforms.SEEDED
391+ return True
392+
393+>>>>>>> cloudinit/sources/DataSourceEc2.py
394 strict_mode, _sleep = read_strict_mode(
395 util.get_cfg_by_path(self.sys_cfg, STRICT_ID_PATH,
396 STRICT_ID_DEFAULT), ("warn", None))
397diff --git a/cloudinit/sources/DataSourceGCE.py b/cloudinit/sources/DataSourceGCE.py
398index d816262..0fd9c77 100644
399--- a/cloudinit/sources/DataSourceGCE.py
400+++ b/cloudinit/sources/DataSourceGCE.py
401@@ -63,7 +63,7 @@ class DataSourceGCE(sources.DataSource):
402 BUILTIN_DS_CONFIG])
403 self.metadata_address = self.ds_cfg['metadata_url']
404
405- def _get_data(self):
406+ def _get_data(self, clear_cache=False):
407 ret = util.log_time(
408 LOG.debug, 'Crawl of GCE metadata service',
409 read_md, kwargs={'address': self.metadata_address})
410diff --git a/cloudinit/sources/DataSourceIBMCloud.py b/cloudinit/sources/DataSourceIBMCloud.py
411index 21e6ae6..de6db75 100644
412--- a/cloudinit/sources/DataSourceIBMCloud.py
413+++ b/cloudinit/sources/DataSourceIBMCloud.py
414@@ -136,7 +136,7 @@ class DataSourceIBMCloud(sources.DataSource):
415 mstr = "%s [%s %s]" % (root, self.platform, self.source)
416 return mstr
417
418- def _get_data(self):
419+ def _get_data(self, clear_cache=False):
420 results = read_md()
421 if results is None:
422 return False
423diff --git a/cloudinit/sources/DataSourceMAAS.py b/cloudinit/sources/DataSourceMAAS.py
424index 61aa6d7..d533d55 100644
425--- a/cloudinit/sources/DataSourceMAAS.py
426+++ b/cloudinit/sources/DataSourceMAAS.py
427@@ -61,7 +61,7 @@ class DataSourceMAAS(sources.DataSource):
428 root = sources.DataSource.__str__(self)
429 return "%s [%s]" % (root, self.base_url)
430
431- def _get_data(self):
432+ def _get_data(self, clear_cache=False):
433 mcfg = self.ds_cfg
434
435 try:
436diff --git a/cloudinit/sources/DataSourceNoCloud.py b/cloudinit/sources/DataSourceNoCloud.py
437index 8a9e5dd..6a3535e 100644
438--- a/cloudinit/sources/DataSourceNoCloud.py
439+++ b/cloudinit/sources/DataSourceNoCloud.py
440@@ -35,6 +35,7 @@ class DataSourceNoCloud(sources.DataSource):
441 root = sources.DataSource.__str__(self)
442 return "%s [seed=%s][dsmode=%s]" % (root, self.seed, self.dsmode)
443
444+<<<<<<< cloudinit/sources/DataSourceNoCloud.py
445 def _get_devices(self, label):
446 if util.is_FreeBSD():
447 devlist = [
448@@ -56,6 +57,9 @@ class DataSourceNoCloud(sources.DataSource):
449 return devlist
450
451 def _get_data(self):
452+=======
453+ def _get_data(self, clear_cache=False):
454+>>>>>>> cloudinit/sources/DataSourceNoCloud.py
455 defaults = {
456 "instance-id": "nocloud",
457 "dsmode": self.dsmode,
458diff --git a/cloudinit/sources/DataSourceNone.py b/cloudinit/sources/DataSourceNone.py
459index e625080..65af122 100644
460--- a/cloudinit/sources/DataSourceNone.py
461+++ b/cloudinit/sources/DataSourceNone.py
462@@ -19,7 +19,7 @@ class DataSourceNone(sources.DataSource):
463 self.metadata = {}
464 self.userdata_raw = ''
465
466- def _get_data(self):
467+ def _get_data(self, clear_cache=False):
468 # If the datasource config has any provided 'fallback'
469 # userdata or metadata, use it...
470 if 'userdata_raw' in self.ds_cfg:
471diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py
472index 70e7a5c..b3b7608 100644
473--- a/cloudinit/sources/DataSourceOVF.py
474+++ b/cloudinit/sources/DataSourceOVF.py
475@@ -67,7 +67,7 @@ class DataSourceOVF(sources.DataSource):
476 root = sources.DataSource.__str__(self)
477 return "%s [seed=%s]" % (root, self.seed)
478
479- def _get_data(self):
480+ def _get_data(self, clear_cache=False):
481 found = []
482 md = {}
483 ud = ""
484diff --git a/cloudinit/sources/DataSourceOpenNebula.py b/cloudinit/sources/DataSourceOpenNebula.py
485index 02c9a7b..c8bc4f3 100644
486--- a/cloudinit/sources/DataSourceOpenNebula.py
487+++ b/cloudinit/sources/DataSourceOpenNebula.py
488@@ -44,7 +44,7 @@ class DataSourceOpenNebula(sources.DataSource):
489 root = sources.DataSource.__str__(self)
490 return "%s [seed=%s][dsmode=%s]" % (root, self.seed, self.dsmode)
491
492- def _get_data(self):
493+ def _get_data(self, clear_cache=False):
494 defaults = {"instance-id": DEFAULT_IID}
495 results = None
496 seed = None
497diff --git a/cloudinit/sources/DataSourceOpenStack.py b/cloudinit/sources/DataSourceOpenStack.py
498index 4a01524..8afc094 100644
499--- a/cloudinit/sources/DataSourceOpenStack.py
500+++ b/cloudinit/sources/DataSourceOpenStack.py
501@@ -115,7 +115,7 @@ class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource):
502 self.network_json, known_macs=None)
503 return self._network_config
504
505- def _get_data(self):
506+ def _get_data(self, clear_cache=False):
507 """Crawl metadata, parse and persist that data for this instance.
508
509 @return: True when metadata discovered indicates OpenStack datasource.
510diff --git a/cloudinit/sources/DataSourceScaleway.py b/cloudinit/sources/DataSourceScaleway.py
511index b573b38..76a94db 100644
512--- a/cloudinit/sources/DataSourceScaleway.py
513+++ b/cloudinit/sources/DataSourceScaleway.py
514@@ -190,7 +190,14 @@ class DataSourceScaleway(sources.DataSource):
515 self._fallback_interface = None
516 self._network_config = None
517
518+<<<<<<< cloudinit/sources/DataSourceScaleway.py
519 def _crawl_metadata(self):
520+=======
521+ def _get_data(self, clear_cache=False):
522+ if not on_scaleway():
523+ return False
524+
525+>>>>>>> cloudinit/sources/DataSourceScaleway.py
526 resp = url_helper.readurl(self.metadata_address,
527 timeout=self.timeout,
528 retries=self.retries)
529diff --git a/cloudinit/sources/DataSourceSmartOS.py b/cloudinit/sources/DataSourceSmartOS.py
530index 32b57cd..58bd235 100644
531--- a/cloudinit/sources/DataSourceSmartOS.py
532+++ b/cloudinit/sources/DataSourceSmartOS.py
533@@ -216,7 +216,7 @@ class DataSourceSmartOS(sources.DataSource):
534 os.rename('/'.join([svc_path, 'provisioning']),
535 '/'.join([svc_path, 'provision_success']))
536
537- def _get_data(self):
538+ def _get_data(self, clear_cache=False):
539 self._init()
540
541 md = {}
542diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py
543index e6966b3..8d3fa64 100644
544--- a/cloudinit/sources/__init__.py
545+++ b/cloudinit/sources/__init__.py
546@@ -19,7 +19,11 @@ from cloudinit.atomic_helper import write_json
547 from cloudinit import importer
548 from cloudinit import log as logging
549 from cloudinit import net
550+<<<<<<< cloudinit/sources/__init__.py
551 from cloudinit.event import EventType
552+=======
553+from cloudinit.hotplug import MaintenanceEvent, MAINTENANCE_EVENT_STR
554+>>>>>>> cloudinit/sources/__init__.py
555 from cloudinit import type_utils
556 from cloudinit import user_data as ud
557 from cloudinit import util
558@@ -158,6 +162,7 @@ class DataSource(object):
559 url_timeout = 10 # timeout for each metadata url read attempt
560 url_retries = 5 # number of times to retry url upon 404
561
562+<<<<<<< cloudinit/sources/__init__.py
563 # The datasource defines a set of supported EventTypes during which
564 # the datasource can react to changes in metadata and regenerate
565 # network configuration on metadata changes.
566@@ -180,6 +185,14 @@ class DataSource(object):
567 # N-tuple of keypaths or keynames redact from instance-data.json for
568 # non-root users
569 sensitive_metadata_keys = ('security-credentials',)
570+=======
571+ # Subclasses can define a mask of supported MaintenanceEvents during
572+ # which the datasource will regenerate network_configuration. For example:
573+ # network_maintenance_mask = MEvent.BOOT|MEvent.DEVICE_ADD
574+
575+ # Default behavior, perform no network update for any maintenance event
576+ network_maintenance_mask = MaintenanceEvent.NONE
577+>>>>>>> cloudinit/sources/__init__.py
578
579 def __init__(self, sys_cfg, distro, paths, ud_proc=None):
580 self.sys_cfg = sys_cfg
581@@ -247,13 +260,31 @@ class DataSource(object):
582 if not attr_defaults:
583 self._dirty_cache = False
584
585- def get_data(self):
586+ def get_data(self, clear_cache=False):
587 """Datasources implement _get_data to setup metadata and userdata_raw.
588
589 Minimally, the datasource should return a boolean True on success.
590+ @param use_cache: Boolean set true to re-use data cache if present.
591+ Value of False, will clear any cached data, re-crawling all
592+ instance metadata.
593 """
594+<<<<<<< cloudinit/sources/__init__.py
595 self._dirty_cache = True
596 return_value = self._get_data()
597+=======
598+ if clear_cache:
599+ if hasattr(self, '_network_config'):
600+ # Clear network config property so it is regenerated from md.
601+ setattr(self, '_network_config', None)
602+ self.userdata = None
603+ self.metadata = {}
604+ self.userdata_raw = None
605+ self.vendordata = None
606+ self.vendordata_raw = None
607+
608+ return_value = self._get_data(clear_cache=clear_cache)
609+ json_file = os.path.join(self.paths.run_dir, INSTANCE_JSON_FILE)
610+>>>>>>> cloudinit/sources/__init__.py
611 if not return_value:
612 return return_value
613 self.persist_instance_data()
614@@ -309,11 +340,18 @@ class DataSource(object):
615 redact_sensitive_keys(processed_data), mode=0o600)
616 return True
617
618+<<<<<<< cloudinit/sources/__init__.py
619 def _get_data(self):
620 """Walk metadata sources, process crawled data and save attributes."""
621+=======
622+ def _get_data(self, clear_cache=False):
623+>>>>>>> cloudinit/sources/__init__.py
624 raise NotImplementedError(
625 'Subclasses of DataSource must implement _get_data which'
626- ' sets self.metadata, vendordata_raw and userdata_raw.')
627+ ' sets self.metadata, vendordata_raw and userdata_raw. _get_data'
628+ ' must accept an optional clear_cache boolean which will clear'
629+ ' any cached metadata, vendordata, userdata etc to ensure a fresh'
630+ ' crawl of new metadata')
631
632 def get_url_params(self):
633 """Return the Datasource's prefered url_read parameters.
634@@ -589,6 +627,7 @@ class DataSource(object):
635 def get_package_mirror_info(self):
636 return self.distro.get_package_mirror_info(data_source=self)
637
638+<<<<<<< cloudinit/sources/__init__.py
639 def update_metadata(self, source_event_types):
640 """Refresh cached metadata if the datasource supports this event.
641
642@@ -624,6 +663,21 @@ class DataSource(object):
643 return True
644 LOG.debug("Datasource %s not updated for events: %s", self,
645 ', '.join(source_event_types))
646+=======
647+ def maintain_metadata(self, maintenance_event):
648+ """Update datasource metadata if supported for a maintenance event.
649+
650+ @param maintenance_event: The source hotplug.MaintenanceEvent type
651+ observed to which the datasource reacts.
652+
653+ @return True if metadata was cleared.
654+ """
655+ if bool(maintenance_event & self.network_maintenance_mask):
656+ LOG.debug(
657+ "Re-reading datasource metadata due to maintenance event: '%s'",
658+ MAINTENANCE_EVENT_STR.get(maintenance_event, maintenance_event))
659+ return self.get_data(clear_cache=True)
660+>>>>>>> cloudinit/sources/__init__.py
661 return False
662
663 def check_instance_id(self, sys_cfg):
664@@ -652,7 +706,7 @@ class DataSource(object):
665 return default
666
667 @property
668- def network_config(self):
669+ def network_config(self, regenerate=False):
670 return None
671
672 @property
673diff --git a/cloudinit/sources/tests/test_init.py b/cloudinit/sources/tests/test_init.py
674index 6378e98..0fb5480 100644
675--- a/cloudinit/sources/tests/test_init.py
676+++ b/cloudinit/sources/tests/test_init.py
677@@ -34,6 +34,7 @@ class DataSourceTestSubclassNet(DataSource):
678 def _get_cloud_name(self):
679 return 'SubclassCloudName'
680
681+<<<<<<< cloudinit/sources/tests/test_init.py
682 def _get_data(self):
683 if self._custom_metadata:
684 self.metadata = self._custom_metadata
685@@ -41,6 +42,12 @@ class DataSourceTestSubclassNet(DataSource):
686 self.metadata = {'availability_zone': 'myaz',
687 'local-hostname': 'test-subclass-hostname',
688 'region': 'myregion'}
689+=======
690+ def _get_data(self, clear_cache=False):
691+ self.metadata = {'availability_zone': 'myaz',
692+ 'local-hostname': 'test-subclass-hostname',
693+ 'region': 'myregion'}
694+>>>>>>> cloudinit/sources/tests/test_init.py
695 if self._custom_userdata:
696 self.userdata_raw = self._custom_userdata
697 else:
698diff --git a/cloudinit/stages.py b/cloudinit/stages.py
699index da7d349..358e67f 100644
700--- a/cloudinit/stages.py
701+++ b/cloudinit/stages.py
702@@ -658,12 +658,19 @@ class Init(object):
703
704 if self.datasource is not NULL_DATA_SOURCE:
705 if not self.is_new_instance():
706+<<<<<<< cloudinit/stages.py
707 if not self.datasource.update_metadata([EventType.BOOT]):
708 LOG.debug(
709 "No network config applied. Neither a new instance"
710 " nor datasource network update on '%s' event",
711 EventType.BOOT)
712 return
713+=======
714+ if not self.datasource.maintain_metadata(MaintenanceEvent.boot):
715+ LOG.debug(
716+ "No network config applied. Neither a new instance nor datasource per-boot network config")
717+ return
718+>>>>>>> cloudinit/stages.py
719
720 LOG.info("Applying network configuration from %s bringup=%s: %s",
721 src, bring_up, netcfg)
722diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py
723index f27ef21..e5df86f 100644
724--- a/tests/unittests/test_datasource/test_azure.py
725+++ b/tests/unittests/test_datasource/test_azure.py
726@@ -1,3 +1,4 @@
727+<<<<<<< tests/unittests/test_datasource/test_azure.py
728 # This file is part of cloud-init. See LICENSE file for license information.
729
730 from cloudinit import distros
731@@ -1960,3 +1961,5 @@ class TestRandomSeed(CiTestCase):
732 self.assertEqual(deserialized['seed'], result)
733
734 # vi: ts=4 expandtab
735+=======
736+>>>>>>> tests/unittests/test_datasource/test_azure.py

Subscribers

People subscribed via source and target branches