Merge ~chad.smith/cloud-init:feature/azure-dhcp-coldplug into cloud-init:master
- Git
- lp:~chad.smith/cloud-init
- feature/azure-dhcp-coldplug
- Merge into 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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
cloud-init Commiters | Pending | ||
Review via email: mp+347991@code.launchpad.net |
Commit message
WIP for discussion
Description of the change
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
1 | diff --git a/cloudinit/hotplug.py b/cloudinit/hotplug.py |
2 | new file mode 100644 |
3 | index 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 | + |
24 | diff --git a/cloudinit/sources/DataSourceAltCloud.py b/cloudinit/sources/DataSourceAltCloud.py |
25 | index 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 |
37 | diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py |
38 | old mode 100755 |
39 | new mode 100644 |
40 | index 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 | |
308 | diff --git a/cloudinit/sources/DataSourceBigstep.py b/cloudinit/sources/DataSourceBigstep.py |
309 | index 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 |
321 | diff --git a/cloudinit/sources/DataSourceCloudSigma.py b/cloudinit/sources/DataSourceCloudSigma.py |
322 | index 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. |
334 | diff --git a/cloudinit/sources/DataSourceCloudStack.py b/cloudinit/sources/DataSourceCloudStack.py |
335 | index 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'] |
347 | diff --git a/cloudinit/sources/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py |
348 | index 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 = {} |
360 | diff --git a/cloudinit/sources/DataSourceDigitalOcean.py b/cloudinit/sources/DataSourceDigitalOcean.py |
361 | index 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 |
373 | diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py |
374 | index 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)) |
397 | diff --git a/cloudinit/sources/DataSourceGCE.py b/cloudinit/sources/DataSourceGCE.py |
398 | index 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}) |
410 | diff --git a/cloudinit/sources/DataSourceIBMCloud.py b/cloudinit/sources/DataSourceIBMCloud.py |
411 | index 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 |
423 | diff --git a/cloudinit/sources/DataSourceMAAS.py b/cloudinit/sources/DataSourceMAAS.py |
424 | index 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: |
436 | diff --git a/cloudinit/sources/DataSourceNoCloud.py b/cloudinit/sources/DataSourceNoCloud.py |
437 | index 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, |
458 | diff --git a/cloudinit/sources/DataSourceNone.py b/cloudinit/sources/DataSourceNone.py |
459 | index 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: |
471 | diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py |
472 | index 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 = "" |
484 | diff --git a/cloudinit/sources/DataSourceOpenNebula.py b/cloudinit/sources/DataSourceOpenNebula.py |
485 | index 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 |
497 | diff --git a/cloudinit/sources/DataSourceOpenStack.py b/cloudinit/sources/DataSourceOpenStack.py |
498 | index 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. |
510 | diff --git a/cloudinit/sources/DataSourceScaleway.py b/cloudinit/sources/DataSourceScaleway.py |
511 | index 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) |
529 | diff --git a/cloudinit/sources/DataSourceSmartOS.py b/cloudinit/sources/DataSourceSmartOS.py |
530 | index 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 = {} |
542 | diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py |
543 | index 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 |
673 | diff --git a/cloudinit/sources/tests/test_init.py b/cloudinit/sources/tests/test_init.py |
674 | index 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: |
698 | diff --git a/cloudinit/stages.py b/cloudinit/stages.py |
699 | index 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) |
722 | diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py |
723 | index 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 |