Coverage for nova/api/metadata/base.py: 91%
422 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
1# Copyright 2010 United States Government as represented by the
2# Administrator of the National Aeronautics and Space Administration.
3# All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
17"""Instance Metadata information."""
19import itertools
20import os
21import posixpath
23from oslo_log import log as logging
24from oslo_serialization import base64
25from oslo_serialization import jsonutils
26from oslo_utils import timeutils
28from nova.api.metadata import password
29from nova.api.metadata import vendordata_dynamic
30from nova.api.metadata import vendordata_json
31from nova import block_device
32import nova.conf
33from nova import context
34from nova import exception
35from nova.network import neutron
36from nova.network import security_group_api
37from nova import objects
38from nova.objects import virt_device_metadata as metadata_obj
39from nova import utils
40from nova.virt import netutils
43CONF = nova.conf.CONF
45VERSIONS = [
46 '1.0',
47 '2007-01-19',
48 '2007-03-01',
49 '2007-08-29',
50 '2007-10-10',
51 '2007-12-15',
52 '2008-02-01',
53 '2008-09-01',
54 '2009-04-04',
55]
57# NOTE(mikal): think of these strings as version numbers. They traditionally
58# correlate with OpenStack release dates, with all the changes for a given
59# release bundled into a single version. Note that versions in the future are
60# hidden from the listing, but can still be requested explicitly, which is
61# required for testing purposes. We know this isn't great, but its inherited
62# from EC2, which this needs to be compatible with.
63# NOTE(jichen): please update doc/source/user/metadata.rst on the metadata
64# output when new version is created in order to make doc up-to-date.
65FOLSOM = '2012-08-10'
66GRIZZLY = '2013-04-04'
67HAVANA = '2013-10-17'
68LIBERTY = '2015-10-15'
69NEWTON_ONE = '2016-06-30'
70NEWTON_TWO = '2016-10-06'
71OCATA = '2017-02-22'
72ROCKY = '2018-08-27'
73VICTORIA = '2020-10-14'
74EPOXY = '2025-04-04'
76OPENSTACK_VERSIONS = [
77 FOLSOM,
78 GRIZZLY,
79 HAVANA,
80 LIBERTY,
81 NEWTON_ONE,
82 NEWTON_TWO,
83 OCATA,
84 ROCKY,
85 VICTORIA,
86 EPOXY,
87]
89VERSION = "version"
90CONTENT = "content"
91CONTENT_DIR = "content"
92MD_JSON_NAME = "meta_data.json"
93VD_JSON_NAME = "vendor_data.json"
94VD2_JSON_NAME = "vendor_data2.json"
95NW_JSON_NAME = "network_data.json"
96UD_NAME = "user_data"
97PASS_NAME = "password"
98MIME_TYPE_TEXT_PLAIN = "text/plain"
99MIME_TYPE_APPLICATION_JSON = "application/json"
101LOG = logging.getLogger(__name__)
104class InvalidMetadataVersion(Exception):
105 pass
108class InvalidMetadataPath(Exception):
109 pass
112class InstanceMetadata(object):
113 """Instance metadata."""
115 def __init__(self, instance, address=None, content=None, extra_md=None,
116 network_info=None, network_metadata=None):
117 """Creation of this object should basically cover all time consuming
118 collection. Methods after that should not cause time delays due to
119 network operations or lengthy cpu operations.
121 The user should then get a single instance and make multiple method
122 calls on it.
123 """
124 if not content:
125 content = []
127 # NOTE(gibi): this is not a cell targeted context even if we are called
128 # in a situation when the instance is in a different cell than the
129 # metadata service itself.
130 ctxt = context.get_admin_context()
132 self.mappings = _format_instance_mapping(instance)
134 # NOTE(danms): Sanitize the instance to limit the amount of stuff
135 # inside that may not pickle well (i.e. context). We also touch
136 # some of the things we'll lazy load later to make sure we keep their
137 # values in what we cache.
138 instance.ec2_ids
139 instance.keypairs
140 instance.device_metadata
141 instance.numa_topology
142 instance = objects.Instance.obj_from_primitive(
143 instance.obj_to_primitive())
145 # The default value of mimeType is set to MIME_TYPE_TEXT_PLAIN
146 self.set_mimetype(MIME_TYPE_TEXT_PLAIN)
147 self.instance = instance
148 self.extra_md = extra_md
150 self.availability_zone = instance.get('availability_zone')
152 self.security_groups = security_group_api.get_instance_security_groups(
153 ctxt, instance)
155 if instance.user_data is not None:
156 self.userdata_raw = base64.decode_as_bytes(instance.user_data)
157 else:
158 self.userdata_raw = None
160 self.address = address
162 # expose instance metadata.
163 self.launch_metadata = utils.instance_meta(instance)
165 self.password = password.extract_password(instance)
167 self.uuid = instance.uuid
169 self.content = {}
170 self.files = []
172 # get network info, and the rendered network template
173 if network_info is None:
174 network_info = instance.info_cache.network_info
176 # expose network metadata
177 if network_metadata is None:
178 self.network_metadata = netutils.get_network_metadata(network_info)
179 else:
180 self.network_metadata = network_metadata
182 self.ip_info = netutils.get_ec2_ip_info(network_info)
184 self.network_config = None
185 cfg = netutils.get_injected_network_template(network_info)
187 if cfg: 187 ↛ 188line 187 didn't jump to line 188 because the condition on line 187 was never true
188 key = "%04i" % len(self.content)
189 self.content[key] = cfg
190 self.network_config = {"name": "network_config",
191 'content_path': "/%s/%s" % (CONTENT_DIR, key)}
193 # 'content' is passed in from the configdrive code in
194 # nova/virt/libvirt/driver.py. That's how we get the injected files
195 # (personalities) in. AFAIK they're not stored in the db at all,
196 # so are not available later (web service metadata time).
197 for (path, contents) in content:
198 key = "%04i" % len(self.content)
199 self.files.append({'path': path,
200 'content_path': "/%s/%s" % (CONTENT_DIR, key)})
201 self.content[key] = contents
203 self.route_configuration = None
205 # NOTE(mikal): the decision to not pass extra_md here like we
206 # do to the StaticJSON driver is deliberate. extra_md will
207 # contain the admin password for the instance, and we shouldn't
208 # pass that to external services.
209 self.vendordata_providers = {
210 'StaticJSON': vendordata_json.JsonFileVendorData(),
211 'DynamicJSON': vendordata_dynamic.DynamicVendorData(
212 instance=instance)
213 }
215 def _route_configuration(self):
216 if self.route_configuration:
217 return self.route_configuration
219 path_handlers = {UD_NAME: self._user_data,
220 PASS_NAME: self._password,
221 VD_JSON_NAME: self._vendor_data,
222 VD2_JSON_NAME: self._vendor_data2,
223 MD_JSON_NAME: self._metadata_as_json,
224 NW_JSON_NAME: self._network_data,
225 VERSION: self._handle_version,
226 CONTENT: self._handle_content}
228 self.route_configuration = RouteConfiguration(path_handlers)
229 return self.route_configuration
231 def set_mimetype(self, mime_type):
232 self.md_mimetype = mime_type
234 def get_mimetype(self):
235 return self.md_mimetype
237 def get_ec2_metadata(self, version):
238 if version == "latest":
239 version = VERSIONS[-1]
241 if version not in VERSIONS:
242 raise InvalidMetadataVersion(version)
244 hostname = self._get_hostname()
246 floating_ips = self.ip_info['floating_ips']
247 floating_ip = floating_ips and floating_ips[0] or ''
249 fixed_ips = self.ip_info['fixed_ips']
250 fixed_ip = fixed_ips and fixed_ips[0] or ''
252 fmt_sgroups = [x['name'] for x in self.security_groups]
254 meta_data = {
255 'ami-id': self.instance.ec2_ids.ami_id,
256 'ami-launch-index': self.instance.launch_index,
257 'ami-manifest-path': 'FIXME',
258 'instance-id': self.instance.ec2_ids.instance_id,
259 'hostname': hostname,
260 'local-ipv4': fixed_ip or self.address,
261 'reservation-id': self.instance.reservation_id,
262 'security-groups': fmt_sgroups}
264 # public keys are strangely rendered in ec2 metadata service
265 # meta-data/public-keys/ returns '0=keyname' (with no trailing /)
266 # and only if there is a public key given.
267 # '0=keyname' means there is a normally rendered dict at
268 # meta-data/public-keys/0
269 #
270 # meta-data/public-keys/ : '0=%s' % keyname
271 # meta-data/public-keys/0/ : 'openssh-key'
272 # meta-data/public-keys/0/openssh-key : '%s' % publickey
273 if self.instance.key_name: 273 ↛ 278line 273 didn't jump to line 278 because the condition on line 273 was always true
274 meta_data['public-keys'] = {
275 '0': {'_name': "0=" + self.instance.key_name,
276 'openssh-key': self.instance.key_data}}
278 if self._check_version('2007-01-19', version): 278 ↛ 283line 278 didn't jump to line 283 because the condition on line 278 was always true
279 meta_data['local-hostname'] = hostname
280 meta_data['public-hostname'] = hostname
281 meta_data['public-ipv4'] = floating_ip
283 if self._check_version('2007-08-29', version): 283 ↛ 287line 283 didn't jump to line 287 because the condition on line 283 was always true
284 flavor = self.instance.get_flavor()
285 meta_data['instance-type'] = flavor['name']
287 if self._check_version('2007-12-15', version): 287 ↛ 294line 287 didn't jump to line 294 because the condition on line 287 was always true
288 meta_data['block-device-mapping'] = self.mappings
289 if self.instance.ec2_ids.kernel_id:
290 meta_data['kernel-id'] = self.instance.ec2_ids.kernel_id
291 if self.instance.ec2_ids.ramdisk_id:
292 meta_data['ramdisk-id'] = self.instance.ec2_ids.ramdisk_id
294 if self._check_version('2008-02-01', version): 294 ↛ 298line 294 didn't jump to line 298 because the condition on line 294 was always true
295 meta_data['placement'] = {'availability-zone':
296 self.availability_zone}
298 if self._check_version('2008-09-01', version): 298 ↛ 301line 298 didn't jump to line 301 because the condition on line 298 was always true
299 meta_data['instance-action'] = 'none'
301 data = {'meta-data': meta_data}
302 if self.userdata_raw is not None:
303 data['user-data'] = self.userdata_raw
305 return data
307 def get_ec2_item(self, path_tokens):
308 # get_ec2_metadata returns dict without top level version
309 data = self.get_ec2_metadata(path_tokens[0])
310 return find_path_in_tree(data, path_tokens[1:])
312 def get_openstack_item(self, path_tokens):
313 if path_tokens[0] == CONTENT_DIR:
314 return self._handle_content(path_tokens)
315 return self._route_configuration().handle_path(path_tokens)
317 def _metadata_as_json(self, version, path):
318 metadata = {'uuid': self.uuid}
319 if self.launch_metadata: 319 ↛ 320line 319 didn't jump to line 320 because the condition on line 319 was never true
320 metadata['meta'] = self.launch_metadata
321 if self.files:
322 metadata['files'] = self.files
323 if self.extra_md:
324 metadata.update(self.extra_md)
325 if self.network_config: 325 ↛ 326line 325 didn't jump to line 326 because the condition on line 325 was never true
326 metadata['network_config'] = self.network_config
328 if self.instance.key_name: 328 ↛ 351line 328 didn't jump to line 351 because the condition on line 328 was always true
329 keypairs = self.instance.keypairs
330 # NOTE(mriedem): It's possible for the keypair to be deleted
331 # before it was migrated to the instance_extra table, in which
332 # case lazy-loading instance.keypairs will handle the 404 and
333 # just set an empty KeyPairList object on the instance.
334 keypair = keypairs[0] if keypairs else None
336 if keypair:
337 metadata['public_keys'] = {
338 keypair.name: keypair.public_key,
339 }
341 metadata['keys'] = [
342 {'name': keypair.name,
343 'type': keypair.type,
344 'data': keypair.public_key}
345 ]
346 else:
347 LOG.debug("Unable to find keypair for instance with "
348 "key name '%s'.", self.instance.key_name,
349 instance=self.instance)
351 metadata['hostname'] = self._get_hostname()
352 metadata['name'] = self.instance.display_name
353 metadata['launch_index'] = self.instance.launch_index
354 metadata['availability_zone'] = self.availability_zone
356 if self._check_os_version(GRIZZLY, version):
357 metadata['random_seed'] = base64.encode_as_text(os.urandom(512))
359 if self._check_os_version(LIBERTY, version):
360 metadata['project_id'] = self.instance.project_id
362 if self._check_os_version(NEWTON_ONE, version):
363 metadata['devices'] = self._get_device_metadata(version)
365 if self._check_os_version(VICTORIA, version):
366 metadata['dedicated_cpus'] = self._get_instance_dedicated_cpus()
368 self.set_mimetype(MIME_TYPE_APPLICATION_JSON)
369 return jsonutils.dump_as_bytes(metadata)
371 def _get_device_metadata(self, version):
372 """Build a device metadata dict based on the metadata objects. This is
373 done here in the metadata API as opposed to in the objects themselves
374 because the metadata dict is part of the guest API and thus must be
375 controlled.
376 """
377 device_metadata_list = []
378 vif_vlans_supported = self._check_os_version(OCATA, version)
379 vif_vfs_trusted_supported = self._check_os_version(ROCKY, version)
380 if self.instance.device_metadata is not None:
381 for device in self.instance.device_metadata.devices:
382 device_metadata = {}
383 bus = 'none'
384 address = 'none'
386 if 'bus' in device:
387 # TODO(artom/mriedem) It would be nice if we had something
388 # more generic, like a type identifier or something, built
389 # into these types of objects, like a get_meta_type()
390 # abstract method on the base DeviceBus class.
391 if isinstance(device.bus, metadata_obj.PCIDeviceBus):
392 bus = 'pci'
393 elif isinstance(device.bus, metadata_obj.USBDeviceBus):
394 bus = 'usb'
395 elif isinstance(device.bus, metadata_obj.SCSIDeviceBus):
396 bus = 'scsi'
397 elif isinstance(device.bus, metadata_obj.IDEDeviceBus):
398 bus = 'ide'
399 elif isinstance(device.bus, metadata_obj.XenDeviceBus): 399 ↛ 400line 399 didn't jump to line 400 because the condition on line 399 was never true
400 bus = 'xen'
401 else:
402 LOG.debug('Metadata for device with unknown bus %s '
403 'has not been included in the '
404 'output', device.bus.__class__.__name__)
405 continue
406 if 'address' in device.bus: 406 ↛ 409line 406 didn't jump to line 409 because the condition on line 406 was always true
407 address = device.bus.address
409 if isinstance(device, metadata_obj.NetworkInterfaceMetadata):
410 vlan = device.vlan if 'vlan' in device else None
411 if vif_vlans_supported and vlan is not None:
412 device_metadata['vlan'] = vlan
413 if vif_vfs_trusted_supported:
414 vf_trusted = (device.vf_trusted if
415 'vf_trusted' in device else False)
416 device_metadata['vf_trusted'] = vf_trusted
417 device_metadata['type'] = 'nic'
418 device_metadata['mac'] = device.mac
419 # NOTE(artom) If a device has neither tags, vlan or
420 # vf_trusted, don't expose it
421 if not ('tags' in device or 'vlan' in device_metadata or
422 'vf_trusted' in device_metadata):
423 continue
424 elif isinstance(device, metadata_obj.DiskMetadata):
425 device_metadata['type'] = 'disk'
426 # serial and path are optional parameters
427 if 'serial' in device: 427 ↛ 429line 427 didn't jump to line 429 because the condition on line 427 was always true
428 device_metadata['serial'] = device.serial
429 if 'path' in device: 429 ↛ 443line 429 didn't jump to line 443 because the condition on line 429 was always true
430 device_metadata['path'] = device.path
431 elif self._check_os_version(EPOXY, version) and isinstance(
432 device, metadata_obj.ShareMetadata
433 ):
434 device_metadata['type'] = 'share'
435 device_metadata['share_id'] = device.share_id
436 device_metadata['tag'] = device.tag
437 else:
438 LOG.debug('Metadata for device of unknown type %s has not '
439 'been included in the '
440 'output', device.__class__.__name__)
441 continue
443 device_metadata['bus'] = bus
444 device_metadata['address'] = address
445 if 'tags' in device:
446 device_metadata['tags'] = device.tags
448 device_metadata_list.append(device_metadata)
449 return device_metadata_list
451 def _get_instance_dedicated_cpus(self):
452 dedicated_cpus = []
453 if self.instance.numa_topology:
454 dedicated_cpus = sorted(list(itertools.chain.from_iterable([
455 cell.pcpuset for cell in self.instance.numa_topology.cells
456 ])))
458 return dedicated_cpus
460 def _handle_content(self, path_tokens):
461 if len(path_tokens) == 1: 461 ↛ 462line 461 didn't jump to line 462 because the condition on line 461 was never true
462 raise KeyError("no listing for %s" % "/".join(path_tokens))
463 if len(path_tokens) != 2: 463 ↛ 464line 463 didn't jump to line 464 because the condition on line 463 was never true
464 raise KeyError("Too many tokens for /%s" % CONTENT_DIR)
465 return self.content[path_tokens[1]]
467 def _handle_version(self, version, path):
468 # request for /version, give a list of what is available
469 ret = [MD_JSON_NAME]
470 if self.userdata_raw is not None:
471 ret.append(UD_NAME)
472 if self._check_os_version(GRIZZLY, version):
473 ret.append(PASS_NAME)
474 if self._check_os_version(HAVANA, version):
475 ret.append(VD_JSON_NAME)
476 if self._check_os_version(LIBERTY, version):
477 ret.append(NW_JSON_NAME)
478 if self._check_os_version(NEWTON_TWO, version):
479 ret.append(VD2_JSON_NAME)
481 return ret
483 def _user_data(self, version, path):
484 if self.userdata_raw is None:
485 raise KeyError(path)
486 return self.userdata_raw
488 def _network_data(self, version, path):
489 if self.network_metadata is None:
490 return jsonutils.dump_as_bytes({})
491 return jsonutils.dump_as_bytes(self.network_metadata)
493 def _password(self, version, path):
494 if self._check_os_version(GRIZZLY, version): 494 ↛ 496line 494 didn't jump to line 496 because the condition on line 494 was always true
495 return password.handle_password
496 raise KeyError(path)
498 def _vendor_data(self, version, path):
499 if self._check_os_version(HAVANA, version): 499 ↛ 507line 499 didn't jump to line 507 because the condition on line 499 was always true
500 self.set_mimetype(MIME_TYPE_APPLICATION_JSON)
502 if (CONF.api.vendordata_providers and 502 ↛ 507line 502 didn't jump to line 507 because the condition on line 502 was always true
503 'StaticJSON' in CONF.api.vendordata_providers):
504 return jsonutils.dump_as_bytes(
505 self.vendordata_providers['StaticJSON'].get())
507 raise KeyError(path)
509 def _vendor_data2(self, version, path):
510 if self._check_os_version(NEWTON_TWO, version): 510 ↛ 528line 510 didn't jump to line 528 because the condition on line 510 was always true
511 self.set_mimetype(MIME_TYPE_APPLICATION_JSON)
513 j = {}
514 for provider in CONF.api.vendordata_providers:
515 if provider == 'StaticJSON':
516 j['static'] = self.vendordata_providers['StaticJSON'].get()
517 else:
518 values = self.vendordata_providers[provider].get()
519 for key in list(values):
520 if key in j: 520 ↛ 521line 520 didn't jump to line 521 because the condition on line 520 was never true
521 LOG.warning('Removing duplicate metadata key: %s',
522 key, instance=self.instance)
523 del values[key]
524 j.update(values)
526 return jsonutils.dump_as_bytes(j)
528 raise KeyError(path)
530 def _check_version(self, required, requested, versions=VERSIONS):
531 return versions.index(requested) >= versions.index(required)
533 def _check_os_version(self, required, requested):
534 return self._check_version(required, requested, OPENSTACK_VERSIONS)
536 def _get_hostname(self):
537 # TODO(stephenfin): At some point in the future, we may wish to
538 # retrieve this information from neutron.
539 if CONF.api.dhcp_domain:
540 return '.'.join([self.instance.hostname, CONF.api.dhcp_domain])
542 return self.instance.hostname
544 def lookup(self, path):
545 if path == "" or path[0] != "/":
546 path = posixpath.normpath("/" + path)
547 else:
548 path = posixpath.normpath(path)
550 # Set default mimeType. It will be modified only if there is a change
551 self.set_mimetype(MIME_TYPE_TEXT_PLAIN)
553 # fix up requests, prepending /ec2 to anything that does not match
554 path_tokens = path.split('/')[1:]
555 if path_tokens[0] not in ("ec2", "openstack"):
556 if path_tokens[0] == "": 556 ↛ 558line 556 didn't jump to line 558 because the condition on line 556 was never true
557 # request for /
558 path_tokens = ["ec2"]
559 else:
560 path_tokens = ["ec2"] + path_tokens
561 path = "/" + "/".join(path_tokens)
563 # all values of 'path' input starts with '/' and have no trailing /
565 # specifically handle the top level request
566 if len(path_tokens) == 1:
567 if path_tokens[0] == "openstack": 567 ↛ 577line 567 didn't jump to line 577 because the condition on line 567 was always true
568 # NOTE(vish): don't show versions that are in the future
569 today = timeutils.utcnow().strftime("%Y-%m-%d")
570 versions = [v for v in OPENSTACK_VERSIONS if v <= today]
571 if OPENSTACK_VERSIONS != versions: 571 ↛ 572line 571 didn't jump to line 572 because the condition on line 571 was never true
572 LOG.debug("future versions %s hidden in version list",
573 [v for v in OPENSTACK_VERSIONS
574 if v not in versions], instance=self.instance)
575 versions += ["latest"]
576 else:
577 versions = VERSIONS + ["latest"]
578 return versions
580 try:
581 if path_tokens[0] == "openstack":
582 data = self.get_openstack_item(path_tokens[1:])
583 else:
584 data = self.get_ec2_item(path_tokens[1:])
585 except (InvalidMetadataVersion, KeyError):
586 raise InvalidMetadataPath(path)
588 return data
590 def metadata_for_config_drive(self):
591 """Yields (path, value) tuples for metadata elements."""
592 # EC2 style metadata
593 for version in VERSIONS + ["latest"]:
594 if version in CONF.api.config_drive_skip_versions.split(' '):
595 continue
597 data = self.get_ec2_metadata(version)
598 if 'user-data' in data: 598 ↛ 603line 598 didn't jump to line 603 because the condition on line 598 was always true
599 filepath = os.path.join('ec2', version, 'user-data')
600 yield (filepath, data['user-data'])
601 del data['user-data']
603 try:
604 del data['public-keys']['0']['_name']
605 except KeyError:
606 pass
608 filepath = os.path.join('ec2', version, 'meta-data.json')
609 yield (filepath, jsonutils.dump_as_bytes(data['meta-data']))
611 ALL_OPENSTACK_VERSIONS = OPENSTACK_VERSIONS + ["latest"]
612 for version in ALL_OPENSTACK_VERSIONS:
613 path = 'openstack/%s/%s' % (version, MD_JSON_NAME)
614 yield (path, self.lookup(path))
616 path = 'openstack/%s/%s' % (version, UD_NAME)
617 if self.userdata_raw is not None: 617 ↛ 620line 617 didn't jump to line 620 because the condition on line 617 was always true
618 yield (path, self.lookup(path))
620 if self._check_version(HAVANA, version, ALL_OPENSTACK_VERSIONS):
621 path = 'openstack/%s/%s' % (version, VD_JSON_NAME)
622 yield (path, self.lookup(path))
624 if self._check_version(LIBERTY, version, ALL_OPENSTACK_VERSIONS):
625 path = 'openstack/%s/%s' % (version, NW_JSON_NAME)
626 yield (path, self.lookup(path))
628 if self._check_version(NEWTON_TWO, version,
629 ALL_OPENSTACK_VERSIONS):
630 path = 'openstack/%s/%s' % (version, VD2_JSON_NAME)
631 yield (path, self.lookup(path))
633 for (cid, content) in self.content.items(): 633 ↛ 634line 633 didn't jump to line 634 because the loop on line 633 never started
634 yield ('%s/%s/%s' % ("openstack", CONTENT_DIR, cid), content)
637class RouteConfiguration(object):
638 """Routes metadata paths to request handlers."""
640 def __init__(self, path_handler):
641 self.path_handlers = path_handler
643 def _version(self, version):
644 if version == "latest":
645 version = OPENSTACK_VERSIONS[-1]
647 if version not in OPENSTACK_VERSIONS: 647 ↛ 648line 647 didn't jump to line 648 because the condition on line 647 was never true
648 raise InvalidMetadataVersion(version)
650 return version
652 def handle_path(self, path_tokens):
653 version = self._version(path_tokens[0])
654 if len(path_tokens) == 1:
655 path = VERSION
656 else:
657 path = '/'.join(path_tokens[1:])
659 path_handler = self.path_handlers[path]
661 if path_handler is None: 661 ↛ 662line 661 didn't jump to line 662 because the condition on line 661 was never true
662 raise KeyError(path)
664 return path_handler(version, path)
667def get_metadata_by_address(address):
668 ctxt = context.get_admin_context()
669 fixed_ip = neutron.API().get_fixed_ip_by_address(ctxt, address)
670 LOG.info('Fixed IP %(ip)s translates to instance UUID %(uuid)s',
671 {'ip': address, 'uuid': fixed_ip['instance_uuid']})
673 return get_metadata_by_instance_id(fixed_ip['instance_uuid'],
674 address,
675 ctxt)
678def get_metadata_by_instance_id(instance_id, address, ctxt=None):
679 ctxt = ctxt or context.get_admin_context()
680 attrs = ['ec2_ids', 'flavor', 'info_cache',
681 'metadata', 'system_metadata',
682 'security_groups', 'keypairs',
683 'device_metadata', 'numa_topology']
685 if CONF.api.local_metadata_per_cell:
686 instance = objects.Instance.get_by_uuid(ctxt, instance_id,
687 expected_attrs=attrs)
688 return InstanceMetadata(instance, address)
690 try:
691 im = objects.InstanceMapping.get_by_instance_uuid(ctxt, instance_id)
692 except exception.InstanceMappingNotFound:
693 LOG.warning('Instance mapping for %(uuid)s not found; '
694 'cell setup is incomplete', {'uuid': instance_id})
695 instance = objects.Instance.get_by_uuid(ctxt, instance_id,
696 expected_attrs=attrs)
697 return InstanceMetadata(instance, address)
699 with context.target_cell(ctxt, im.cell_mapping) as cctxt:
700 instance = objects.Instance.get_by_uuid(cctxt, instance_id,
701 expected_attrs=attrs)
702 return InstanceMetadata(instance, address)
705def _format_instance_mapping(instance):
706 bdms = instance.get_bdms()
707 return block_device.instance_block_mapping(instance, bdms)
710def ec2_md_print(data):
711 if isinstance(data, dict):
712 output = ''
713 for key in sorted(data.keys()):
714 if key == '_name':
715 continue
716 if isinstance(data[key], dict):
717 if '_name' in data[key]:
718 output += str(data[key]['_name'])
719 else:
720 output += key + '/'
721 else:
722 output += key
724 output += '\n'
725 return output[:-1]
726 elif isinstance(data, list):
727 return '\n'.join(data)
728 elif isinstance(data, (bytes, str)):
729 return data
730 else:
731 return str(data)
734def find_path_in_tree(data, path_tokens):
735 # given a dict/list tree, and a path in that tree, return data found there.
736 for i in range(0, len(path_tokens)):
737 if isinstance(data, dict) or isinstance(data, list): 737 ↛ 743line 737 didn't jump to line 743 because the condition on line 737 was always true
738 if path_tokens[i] in data:
739 data = data[path_tokens[i]]
740 else:
741 raise KeyError("/".join(path_tokens[0:i]))
742 else:
743 if i != len(path_tokens) - 1:
744 raise KeyError("/".join(path_tokens[0:i]))
745 data = data[path_tokens[i]]
746 return data