Coverage for nova/api/metadata/base.py: 91%

422 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-24 11:16 +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. 

16 

17"""Instance Metadata information.""" 

18 

19import itertools 

20import os 

21import posixpath 

22 

23from oslo_log import log as logging 

24from oslo_serialization import base64 

25from oslo_serialization import jsonutils 

26from oslo_utils import timeutils 

27 

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 

41 

42 

43CONF = nova.conf.CONF 

44 

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] 

56 

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' 

75 

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] 

88 

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" 

100 

101LOG = logging.getLogger(__name__) 

102 

103 

104class InvalidMetadataVersion(Exception): 

105 pass 

106 

107 

108class InvalidMetadataPath(Exception): 

109 pass 

110 

111 

112class InstanceMetadata(object): 

113 """Instance metadata.""" 

114 

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. 

120 

121 The user should then get a single instance and make multiple method 

122 calls on it. 

123 """ 

124 if not content: 

125 content = [] 

126 

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() 

131 

132 self.mappings = _format_instance_mapping(instance) 

133 

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()) 

144 

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 

149 

150 self.availability_zone = instance.get('availability_zone') 

151 

152 self.security_groups = security_group_api.get_instance_security_groups( 

153 ctxt, instance) 

154 

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 

159 

160 self.address = address 

161 

162 # expose instance metadata. 

163 self.launch_metadata = utils.instance_meta(instance) 

164 

165 self.password = password.extract_password(instance) 

166 

167 self.uuid = instance.uuid 

168 

169 self.content = {} 

170 self.files = [] 

171 

172 # get network info, and the rendered network template 

173 if network_info is None: 

174 network_info = instance.info_cache.network_info 

175 

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 

181 

182 self.ip_info = netutils.get_ec2_ip_info(network_info) 

183 

184 self.network_config = None 

185 cfg = netutils.get_injected_network_template(network_info) 

186 

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)} 

192 

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 

202 

203 self.route_configuration = None 

204 

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 } 

214 

215 def _route_configuration(self): 

216 if self.route_configuration: 

217 return self.route_configuration 

218 

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} 

227 

228 self.route_configuration = RouteConfiguration(path_handlers) 

229 return self.route_configuration 

230 

231 def set_mimetype(self, mime_type): 

232 self.md_mimetype = mime_type 

233 

234 def get_mimetype(self): 

235 return self.md_mimetype 

236 

237 def get_ec2_metadata(self, version): 

238 if version == "latest": 

239 version = VERSIONS[-1] 

240 

241 if version not in VERSIONS: 

242 raise InvalidMetadataVersion(version) 

243 

244 hostname = self._get_hostname() 

245 

246 floating_ips = self.ip_info['floating_ips'] 

247 floating_ip = floating_ips and floating_ips[0] or '' 

248 

249 fixed_ips = self.ip_info['fixed_ips'] 

250 fixed_ip = fixed_ips and fixed_ips[0] or '' 

251 

252 fmt_sgroups = [x['name'] for x in self.security_groups] 

253 

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} 

263 

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}} 

277 

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 

282 

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'] 

286 

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 

293 

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} 

297 

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' 

300 

301 data = {'meta-data': meta_data} 

302 if self.userdata_raw is not None: 

303 data['user-data'] = self.userdata_raw 

304 

305 return data 

306 

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:]) 

311 

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) 

316 

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 

327 

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 

335 

336 if keypair: 

337 metadata['public_keys'] = { 

338 keypair.name: keypair.public_key, 

339 } 

340 

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) 

350 

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 

355 

356 if self._check_os_version(GRIZZLY, version): 

357 metadata['random_seed'] = base64.encode_as_text(os.urandom(512)) 

358 

359 if self._check_os_version(LIBERTY, version): 

360 metadata['project_id'] = self.instance.project_id 

361 

362 if self._check_os_version(NEWTON_ONE, version): 

363 metadata['devices'] = self._get_device_metadata(version) 

364 

365 if self._check_os_version(VICTORIA, version): 

366 metadata['dedicated_cpus'] = self._get_instance_dedicated_cpus() 

367 

368 self.set_mimetype(MIME_TYPE_APPLICATION_JSON) 

369 return jsonutils.dump_as_bytes(metadata) 

370 

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' 

385 

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 

408 

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 

442 

443 device_metadata['bus'] = bus 

444 device_metadata['address'] = address 

445 if 'tags' in device: 

446 device_metadata['tags'] = device.tags 

447 

448 device_metadata_list.append(device_metadata) 

449 return device_metadata_list 

450 

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 ]))) 

457 

458 return dedicated_cpus 

459 

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]] 

466 

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) 

480 

481 return ret 

482 

483 def _user_data(self, version, path): 

484 if self.userdata_raw is None: 

485 raise KeyError(path) 

486 return self.userdata_raw 

487 

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) 

492 

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) 

497 

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) 

501 

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()) 

506 

507 raise KeyError(path) 

508 

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) 

512 

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) 

525 

526 return jsonutils.dump_as_bytes(j) 

527 

528 raise KeyError(path) 

529 

530 def _check_version(self, required, requested, versions=VERSIONS): 

531 return versions.index(requested) >= versions.index(required) 

532 

533 def _check_os_version(self, required, requested): 

534 return self._check_version(required, requested, OPENSTACK_VERSIONS) 

535 

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]) 

541 

542 return self.instance.hostname 

543 

544 def lookup(self, path): 

545 if path == "" or path[0] != "/": 

546 path = posixpath.normpath("/" + path) 

547 else: 

548 path = posixpath.normpath(path) 

549 

550 # Set default mimeType. It will be modified only if there is a change 

551 self.set_mimetype(MIME_TYPE_TEXT_PLAIN) 

552 

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) 

562 

563 # all values of 'path' input starts with '/' and have no trailing / 

564 

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 

579 

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) 

587 

588 return data 

589 

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 

596 

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'] 

602 

603 try: 

604 del data['public-keys']['0']['_name'] 

605 except KeyError: 

606 pass 

607 

608 filepath = os.path.join('ec2', version, 'meta-data.json') 

609 yield (filepath, jsonutils.dump_as_bytes(data['meta-data'])) 

610 

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)) 

615 

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)) 

619 

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)) 

623 

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)) 

627 

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)) 

632 

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) 

635 

636 

637class RouteConfiguration(object): 

638 """Routes metadata paths to request handlers.""" 

639 

640 def __init__(self, path_handler): 

641 self.path_handlers = path_handler 

642 

643 def _version(self, version): 

644 if version == "latest": 

645 version = OPENSTACK_VERSIONS[-1] 

646 

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) 

649 

650 return version 

651 

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:]) 

658 

659 path_handler = self.path_handlers[path] 

660 

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) 

663 

664 return path_handler(version, path) 

665 

666 

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']}) 

672 

673 return get_metadata_by_instance_id(fixed_ip['instance_uuid'], 

674 address, 

675 ctxt) 

676 

677 

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'] 

684 

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) 

689 

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) 

698 

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) 

703 

704 

705def _format_instance_mapping(instance): 

706 bdms = instance.get_bdms() 

707 return block_device.instance_block_mapping(instance, bdms) 

708 

709 

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 

723 

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) 

732 

733 

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