Coverage for nova/api/openstack/compute/views/servers.py: 93%

340 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-17 15:08 +0000

1# Copyright 2010-2011 OpenStack Foundation 

2# Copyright 2011 Piston Cloud Computing, Inc. 

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 

17import collections 

18 

19from oslo_log import log as logging 

20from oslo_serialization import jsonutils 

21 

22from nova.api.openstack import api_version_request 

23from nova.api.openstack import common 

24from nova.api.openstack.compute.views import addresses as views_addresses 

25from nova.api.openstack.compute.views import flavors as views_flavors 

26from nova.api.openstack.compute.views import images as views_images 

27from nova import availability_zones as avail_zone 

28from nova.compute import api as compute 

29from nova.compute import vm_states 

30from nova import context as nova_context 

31from nova import exception 

32from nova.network import security_group_api 

33from nova import objects 

34from nova.objects import fields 

35from nova.objects import virtual_interface 

36from nova.policies import extended_server_attributes as esa_policies 

37from nova.policies import servers as servers_policies 

38from nova import utils 

39 

40 

41LOG = logging.getLogger(__name__) 

42 

43 

44SCHED_HINTS_NOT_IN_REQUEST_SPEC = object() 

45 

46 

47class ViewBuilder(common.ViewBuilder): 

48 """Model a server API response as a python dictionary.""" 

49 

50 _collection_name = "servers" 

51 

52 _progress_statuses = ( 

53 "ACTIVE", 

54 "BUILD", 

55 "REBUILD", 

56 "RESIZE", 

57 "VERIFY_RESIZE", 

58 "MIGRATING", 

59 ) 

60 

61 _fault_statuses = ( 

62 "ERROR", "DELETED" 

63 ) 

64 

65 # These are the lazy-loadable instance attributes required for showing 

66 # details about an instance. Add to this list as new things need to be 

67 # shown. 

68 _show_expected_attrs = ['flavor', 'info_cache', 'metadata'] 

69 

70 def __init__(self): 

71 """Initialize view builder.""" 

72 super(ViewBuilder, self).__init__() 

73 self._address_builder = views_addresses.ViewBuilder() 

74 self._image_builder = views_images.ViewBuilder() 

75 self._flavor_builder = views_flavors.ViewBuilder() 

76 self.compute_api = compute.API() 

77 

78 def create(self, request, instance): 

79 """View that should be returned when an instance is created.""" 

80 

81 server = { 

82 "server": { 

83 "id": instance["uuid"], 

84 "links": self._get_links(request, 

85 instance["uuid"], 

86 self._collection_name), 

87 # NOTE(sdague): historically this was the 

88 # os-disk-config extension, but now that extensions 

89 # are gone, we merge these attributes here. 

90 "OS-DCF:diskConfig": ( 

91 'AUTO' if instance.get('auto_disk_config') else 'MANUAL'), 

92 }, 

93 } 

94 self._add_security_grps(request, [server["server"]], [instance], 

95 create_request=True) 

96 

97 return server 

98 

99 def basic(self, request, instance, show_extra_specs=False, 

100 show_extended_attr=None, show_host_status=None, 

101 show_sec_grp=None, bdms=None, cell_down_support=False, 

102 show_user_data=False, provided_az=None, 

103 provided_sched_hints=None): 

104 """Generic, non-detailed view of an instance.""" 

105 if cell_down_support and 'display_name' not in instance: 

106 # NOTE(tssurya): If the microversion is >= 2.69, this boolean will 

107 # be true in which case we check if there are instances from down 

108 # cells (by checking if their objects have missing keys like 

109 # `display_name`) and return partial constructs based on the 

110 # information available from the nova_api database. 

111 return { 

112 "server": { 

113 "id": instance.uuid, 

114 "status": "UNKNOWN", 

115 "links": self._get_links(request, 

116 instance.uuid, 

117 self._collection_name), 

118 }, 

119 } 

120 return { 

121 "server": { 

122 "id": instance["uuid"], 

123 "name": instance["display_name"], 

124 "links": self._get_links(request, 

125 instance["uuid"], 

126 self._collection_name), 

127 }, 

128 } 

129 

130 def get_show_expected_attrs(self, expected_attrs=None): 

131 """Returns a list of lazy-loadable expected attributes used by show 

132 

133 This should be used when getting the instances from the database so 

134 that the necessary attributes are pre-loaded before needing to build 

135 the show response where lazy-loading can fail if an instance was 

136 deleted. 

137 

138 :param list expected_attrs: The list of expected attributes that will 

139 be requested in addition to what this view builder requires. This 

140 method will merge the two lists and return what should be 

141 ultimately used when getting an instance from the database. 

142 :returns: merged and sorted list of expected attributes 

143 """ 

144 if expected_attrs is None: 144 ↛ 145line 144 didn't jump to line 145 because the condition on line 144 was never true

145 expected_attrs = [] 

146 # NOTE(mriedem): We sort the list so we can have predictable test 

147 # results. 

148 return sorted(list(set(self._show_expected_attrs + expected_attrs))) 

149 

150 def _show_from_down_cell(self, request, instance, show_extra_specs, 

151 show_server_groups): 

152 """Function that constructs the partial response for the instance.""" 

153 ret = { 

154 "server": { 

155 "id": instance.uuid, 

156 "status": "UNKNOWN", 

157 "tenant_id": instance.project_id, 

158 "created": utils.isotime(instance.created_at), 

159 "links": self._get_links( 

160 request, instance.uuid, self._collection_name), 

161 }, 

162 } 

163 if 'flavor' in instance: 

164 # If the key 'flavor' is present for an instance from a down cell 

165 # it means that the request is ``GET /servers/{server_id}`` and 

166 # thus we include the information from the request_spec of the 

167 # instance like its flavor, image, avz, and user_id in addition to 

168 # the basic information from its instance_mapping. 

169 # If 'flavor' key is not present for an instance from a down cell 

170 # down cell it means the request is ``GET /servers/detail`` and we 

171 # do not expose the flavor in the response when listing servers 

172 # with details for performance reasons of fetching it from the 

173 # request specs table for the whole list of instances. 

174 ret["server"]["image"] = self._get_image(request, instance) 

175 ret["server"]["flavor"] = self._get_flavor(request, instance, 

176 show_extra_specs) 

177 # in case availability zone was not requested by the user during 

178 # boot time, return UNKNOWN. 

179 avz = instance.availability_zone or "UNKNOWN" 

180 ret["server"]["OS-EXT-AZ:availability_zone"] = avz 

181 ret["server"]["OS-EXT-STS:power_state"] = instance.power_state 

182 # in case its an old request spec which doesn't have the user_id 

183 # data migrated, return UNKNOWN. 

184 ret["server"]["user_id"] = instance.user_id or "UNKNOWN" 

185 if show_server_groups: 185 ↛ 186line 185 didn't jump to line 186 because the condition on line 185 was never true

186 context = request.environ['nova.context'] 

187 ret['server']['server_groups'] = self._get_server_groups( 

188 context, instance) 

189 return ret 

190 

191 @staticmethod 

192 def _get_host_status_unknown_only(context, instance=None): 

193 """We will use the unknown_only variable to tell us what host status we 

194 can show, if any: 

195 * unknown_only = False means we can show any host status. 

196 * unknown_only = True means that we can only show host 

197 status: UNKNOWN. If the host status is anything other than 

198 UNKNOWN, we will not include the host_status field in the 

199 response. 

200 * unknown_only = None means we cannot show host status at all and 

201 we will not include the host_status field in the response. 

202 """ 

203 unknown_only = None 

204 # Check show:host_status policy first because if it passes, we know we 

205 # can show any host status and need not check the more restrictive 

206 # show:host_status:unknown-only policy. 

207 # Keeping target as None (which means policy will default these target 

208 # to context.project_id) for now which is case of 'detail' API which 

209 # policy is default to system and project reader. 

210 target = None 

211 if instance is not None: 

212 target = {'project_id': instance.project_id} 

213 if context.can( 

214 servers_policies.SERVERS % 'show:host_status', 

215 fatal=False, target=target): 

216 unknown_only = False 

217 # If we are not allowed to show any/all host status, check if we can at 

218 # least show only the host status: UNKNOWN. 

219 elif context.can( 

220 servers_policies.SERVERS % 

221 'show:host_status:unknown-only', 

222 fatal=False, 

223 target=target): 

224 unknown_only = True 

225 return unknown_only 

226 

227 def _get_pinned_az(self, context, instance, provided_az): 

228 pinned_az = '' 

229 if provided_az is not None: 229 ↛ 230line 229 didn't jump to line 230 because the condition on line 229 was never true

230 pinned_az = provided_az 

231 else: 

232 try: 

233 req_spec = objects.RequestSpec.get_by_instance_uuid( 

234 context, instance.uuid) 

235 pinned_az = req_spec.availability_zone 

236 except exception.RequestSpecNotFound: 

237 pinned_az = '' 

238 return pinned_az 

239 

240 def _get_scheduler_hints(self, context, instance, provided_sched_hints): 

241 if provided_sched_hints is SCHED_HINTS_NOT_IN_REQUEST_SPEC: 241 ↛ 243line 241 didn't jump to line 243 because the condition on line 241 was never true

242 # Case where it was pre fetched, but not specified 

243 sched_hints = None 

244 elif provided_sched_hints is not None: 244 ↛ 245line 244 didn't jump to line 245 because the condition on line 244 was never true

245 sched_hints = provided_sched_hints 

246 else: 

247 # Case the provided_az is not pre fethed. 

248 try: 

249 req_spec = objects.RequestSpec.get_by_instance_uuid( 

250 context, instance.uuid) 

251 sched_hints = req_spec.scheduler_hints 

252 except exception.RequestSpecNotFound: 

253 sched_hints = {} 

254 return sched_hints 

255 

256 def show(self, request, instance, extend_address=True, 

257 show_extra_specs=None, show_AZ=True, show_config_drive=True, 

258 show_extended_attr=None, show_host_status=None, 

259 show_keypair=True, show_srv_usg=True, show_sec_grp=True, 

260 show_extended_status=True, show_extended_volumes=True, 

261 bdms=None, cell_down_support=False, show_server_groups=False, 

262 show_user_data=True, provided_az=None, provided_sched_hints=None): 

263 """Detailed view of a single instance.""" 

264 if show_extra_specs is None: 

265 # detail will pre-calculate this for us. If we're doing show, 

266 # then figure it out here. 

267 show_extra_specs = False 

268 if api_version_request.is_supported(request, min_version='2.47'): 

269 context = request.environ['nova.context'] 

270 show_extra_specs = context.can( 

271 servers_policies.SERVERS % 'show:flavor-extra-specs', 

272 fatal=False, 

273 target={'project_id': instance.project_id}) 

274 

275 if cell_down_support and 'display_name' not in instance: 

276 # NOTE(tssurya): If the microversion is >= 2.69, this boolean will 

277 # be true in which case we check if there are instances from down 

278 # cells (by checking if their objects have missing keys like 

279 # `display_name`) and return partial constructs based on the 

280 # information available from the nova_api database. 

281 return self._show_from_down_cell( 

282 request, instance, show_extra_specs, show_server_groups) 

283 ip_v4 = instance.get('access_ip_v4') 

284 ip_v6 = instance.get('access_ip_v6') 

285 

286 server = { 

287 "server": { 

288 "id": instance["uuid"], 

289 "name": instance["display_name"], 

290 "status": self._get_vm_status(instance), 

291 "tenant_id": instance.get("project_id") or "", 

292 "user_id": instance.get("user_id") or "", 

293 "metadata": self._get_metadata(instance), 

294 "hostId": self._get_host_id(instance), 

295 "image": self._get_image(request, instance), 

296 "flavor": self._get_flavor(request, instance, 

297 show_extra_specs), 

298 "created": utils.isotime(instance["created_at"]), 

299 "updated": utils.isotime(instance["updated_at"]), 

300 "addresses": self._get_addresses(request, instance, 

301 extend_address), 

302 "accessIPv4": str(ip_v4) if ip_v4 is not None else '', 

303 "accessIPv6": str(ip_v6) if ip_v6 is not None else '', 

304 "links": self._get_links(request, 

305 instance["uuid"], 

306 self._collection_name), 

307 # NOTE(sdague): historically this was the 

308 # os-disk-config extension, but now that extensions 

309 # are gone, we merge these attributes here. 

310 "OS-DCF:diskConfig": ( 

311 'AUTO' if instance.get('auto_disk_config') else 'MANUAL'), 

312 }, 

313 } 

314 if server["server"]["status"] in self._fault_statuses: 

315 _inst_fault = self._get_fault(request, instance) 

316 if _inst_fault: 

317 server['server']['fault'] = _inst_fault 

318 

319 if server["server"]["status"] in self._progress_statuses: 

320 server["server"]["progress"] = instance.get("progress", 0) 

321 

322 context = request.environ['nova.context'] 

323 

324 if show_AZ: 

325 az = avail_zone.get_instance_availability_zone(context, instance) 

326 # NOTE(mriedem): The OS-EXT-AZ prefix should not be used for new 

327 # attributes after v2.1. They are only in v2.1 for backward compat 

328 # with v2.0. 

329 server["server"]["OS-EXT-AZ:availability_zone"] = az or '' 

330 if api_version_request.is_supported(request, min_version='2.96'): 

331 pinned_az = self._get_pinned_az(context, instance, provided_az) 

332 server['server']['pinned_availability_zone'] = pinned_az 

333 

334 if api_version_request.is_supported(request, min_version='2.100'): 

335 server['server']['scheduler_hints'] = ( 

336 self._get_scheduler_hints( 

337 context, instance, provided_sched_hints)) 

338 

339 if show_config_drive: 

340 server["server"]["config_drive"] = instance["config_drive"] 

341 

342 if show_keypair: 

343 server["server"]["key_name"] = instance["key_name"] 

344 

345 if show_srv_usg: 

346 for k in ['launched_at', 'terminated_at']: 

347 key = "OS-SRV-USG:" + k 

348 # NOTE(danms): Historically, this timestamp has been generated 

349 # merely by grabbing str(datetime) of a TZ-naive object. The 

350 # only way we can keep that with instance objects is to strip 

351 # the tzinfo from the stamp and str() it. 

352 server["server"][key] = (instance[k].replace(tzinfo=None) 

353 if instance[k] else None) 

354 if show_sec_grp: 

355 self._add_security_grps(request, [server["server"]], [instance]) 

356 

357 if show_extended_attr is None: 

358 show_extended_attr = context.can( 

359 esa_policies.BASE_POLICY_NAME, fatal=False, 

360 target={'project_id': instance.project_id}) 

361 

362 if show_extended_attr: 

363 properties = ['host', 'name', 'node'] 

364 if api_version_request.is_supported(request, min_version='2.3'): 

365 # NOTE(mriedem): These will use the OS-EXT-SRV-ATTR prefix 

366 # below and that's OK for microversion 2.3 which is being 

367 # compatible with v2.0 for the ec2 API split out from Nova. 

368 # After this, however, new microversions should not be using 

369 # the OS-EXT-SRV-ATTR prefix. 

370 properties += ['reservation_id', 'launch_index', 

371 'hostname', 'kernel_id', 'ramdisk_id', 

372 'root_device_name'] 

373 # NOTE(gmann): Since microversion 2.75, PUT and Rebuild 

374 # response include all the server attributes including these 

375 # extended attributes also. But microversion 2.57 already 

376 # adding the 'user_data' in Rebuild response in API method. 

377 # so we will skip adding the user data attribute for rebuild 

378 # case. 'show_user_data' is false only in case of rebuild. 

379 if show_user_data: 

380 properties += ['user_data'] 

381 for attr in properties: 

382 if attr == 'name': 

383 key = "OS-EXT-SRV-ATTR:instance_%s" % attr 

384 elif attr == 'node': 

385 key = "OS-EXT-SRV-ATTR:hypervisor_hostname" 

386 else: 

387 # NOTE(mriedem): Nothing after microversion 2.3 should use 

388 # the OS-EXT-SRV-ATTR prefix for the attribute key name. 

389 key = "OS-EXT-SRV-ATTR:%s" % attr 

390 server["server"][key] = getattr(instance, attr) 

391 

392 if show_extended_status: 

393 # NOTE(gmann): Removed 'locked_by' from extended status 

394 # to make it same as V2. If needed it can be added with 

395 # microversion. 

396 for state in ['task_state', 'vm_state', 'power_state']: 

397 # NOTE(mriedem): The OS-EXT-STS prefix should not be used for 

398 # new attributes after v2.1. They are only in v2.1 for backward 

399 # compat with v2.0. 

400 key = "%s:%s" % ('OS-EXT-STS', state) 

401 server["server"][key] = instance[state] 

402 

403 if show_extended_volumes: 

404 # NOTE(mriedem): The os-extended-volumes prefix should not be used 

405 # for new attributes after v2.1. They are only in v2.1 for backward 

406 # compat with v2.0. 

407 add_delete_on_termination = api_version_request.is_supported( 

408 request, min_version='2.3') 

409 if bdms is None: 

410 bdms = objects.BlockDeviceMappingList.bdms_by_instance_uuid( 

411 context, [instance["uuid"]]) 

412 self._add_volumes_attachments(server["server"], 

413 bdms, 

414 add_delete_on_termination) 

415 

416 if api_version_request.is_supported(request, min_version='2.16'): 

417 if show_host_status is None: 

418 unknown_only = self._get_host_status_unknown_only( 

419 context, instance) 

420 # If we're not allowed by policy to show host status at all, 

421 # don't bother requesting instance host status from the compute 

422 # API. 

423 if unknown_only is not None: 

424 host_status = self.compute_api.get_instance_host_status( 

425 instance) 

426 # If we are allowed to show host status of some kind, set 

427 # the host status field only if: 

428 # * unknown_only = False, meaning we can show any status 

429 # OR 

430 # * if unknown_only = True and host_status == UNKNOWN 

431 if (not unknown_only or 431 ↛ 435line 431 didn't jump to line 435 because the condition on line 431 was always true

432 host_status == fields.HostStatus.UNKNOWN): 

433 server["server"]['host_status'] = host_status 

434 

435 if api_version_request.is_supported(request, min_version="2.9"): 

436 server["server"]["locked"] = (True if instance["locked_by"] 

437 else False) 

438 

439 if api_version_request.is_supported(request, min_version="2.73"): 

440 server["server"]["locked_reason"] = (instance.system_metadata.get( 

441 "locked_reason")) 

442 

443 if api_version_request.is_supported(request, min_version="2.19"): 

444 server["server"]["description"] = instance.get( 

445 "display_description") 

446 

447 if api_version_request.is_supported(request, min_version="2.26"): 

448 server["server"]["tags"] = [t.tag for t in instance.tags] 

449 

450 if api_version_request.is_supported(request, min_version="2.63"): 

451 trusted_certs = None 

452 if instance.trusted_certs: 

453 trusted_certs = instance.trusted_certs.ids 

454 server["server"]["trusted_image_certificates"] = trusted_certs 

455 

456 # TODO(stephenfin): Remove this check once we remove the 

457 # OS-EXT-SRV-ATTR:hostname policy checks from the policy is Y or later 

458 if api_version_request.is_supported(request, min_version='2.90'): 

459 # API 2.90 made this field visible to non-admins, but we only show 

460 # it if it's not already added 

461 if not show_extended_attr: 461 ↛ 462line 461 didn't jump to line 462 because the condition on line 461 was never true

462 server["server"]["OS-EXT-SRV-ATTR:hostname"] = \ 

463 instance.hostname 

464 

465 if show_server_groups: 

466 server['server']['server_groups'] = self._get_server_groups( 

467 context, 

468 instance) 

469 return server 

470 

471 def index(self, request, instances, cell_down_support=False): 

472 """Show a list of servers without many details.""" 

473 coll_name = self._collection_name 

474 return self._list_view(self.basic, request, instances, coll_name, 

475 False, cell_down_support=cell_down_support) 

476 

477 def detail(self, request, instances, cell_down_support=False): 

478 """Detailed view of a list of instance.""" 

479 coll_name = self._collection_name + '/detail' 

480 context = request.environ['nova.context'] 

481 

482 if api_version_request.is_supported(request, min_version='2.47'): 

483 # Determine if we should show extra_specs in the inlined flavor 

484 # once before we iterate the list of instances 

485 show_extra_specs = context.can( 

486 servers_policies.SERVERS % 'show:flavor-extra-specs', 

487 fatal=False) 

488 else: 

489 show_extra_specs = False 

490 show_extended_attr = context.can( 

491 esa_policies.BASE_POLICY_NAME, fatal=False) 

492 

493 instance_uuids = [inst['uuid'] for inst in instances] 

494 bdms = self._get_instance_bdms_in_multiple_cells(context, 

495 instance_uuids) 

496 

497 # NOTE(gmann): pass show_sec_grp=False in _list_view() because 

498 # security groups for detail method will be added by separate 

499 # call to self._add_security_grps by passing the all servers 

500 # together. That help to avoid multiple neutron call for each server. 

501 servers_dict = self._list_view(self.show, request, instances, 

502 coll_name, show_extra_specs, 

503 show_extended_attr=show_extended_attr, 

504 # We process host_status in aggregate. 

505 show_host_status=False, 

506 show_sec_grp=False, 

507 bdms=bdms, 

508 cell_down_support=cell_down_support) 

509 

510 if api_version_request.is_supported(request, min_version='2.16'): 

511 unknown_only = self._get_host_status_unknown_only(context) 

512 # If we're not allowed by policy to show host status at all, don't 

513 # bother requesting instance host status from the compute API. 

514 if unknown_only is not None: 

515 self._add_host_status(list(servers_dict["servers"]), instances, 

516 unknown_only=unknown_only) 

517 

518 self._add_security_grps(request, list(servers_dict["servers"]), 

519 instances) 

520 return servers_dict 

521 

522 def _list_view(self, func, request, servers, coll_name, show_extra_specs, 

523 show_extended_attr=None, show_host_status=None, 

524 show_sec_grp=False, bdms=None, cell_down_support=False): 

525 """Provide a view for a list of servers. 

526 

527 :param func: Function used to format the server data 

528 :param request: API request 

529 :param servers: List of servers in dictionary format 

530 :param coll_name: Name of collection, used to generate the next link 

531 for a pagination query 

532 :param show_extended_attr: If the server extended attributes should be 

533 included in the response dict. 

534 :param show_host_status: If the host status should be included in 

535 the response dict. 

536 :param show_sec_grp: If the security group should be included in 

537 the response dict. 

538 :param bdms: Instances bdms info from multiple cells. 

539 :param cell_down_support: True if the API (and caller) support 

540 returning a minimal instance 

541 construct if the relevant cell is 

542 down. 

543 :returns: Server data in dictionary format 

544 """ 

545 req_specs = None 

546 req_specs_dict = collections.defaultdict(str) 

547 sched_hints_dict = {} 

548 if api_version_request.is_supported(request, min_version='2.96'): 

549 context = request.environ['nova.context'] 

550 instance_uuids = [s.uuid for s in servers] 

551 req_specs = objects.RequestSpec.get_by_instance_uuids( 

552 context, instance_uuids) 

553 req_specs_dict = {req.instance_uuid: req.availability_zone 

554 for req in req_specs} 

555 if api_version_request.is_supported(request, min_version='2.100'): 555 ↛ 556line 555 didn't jump to line 556 because the condition on line 555 was never true

556 sched_hints_dict.update({ 

557 req.instance_uuid: req.scheduler_hints 

558 for req in req_specs 

559 if req.scheduler_hints is not None}) 

560 

561 server_list = [ 

562 func(request, server, 

563 show_extra_specs=show_extra_specs, 

564 show_extended_attr=show_extended_attr, 

565 show_host_status=show_host_status, 

566 show_sec_grp=show_sec_grp, bdms=bdms, 

567 cell_down_support=cell_down_support, 

568 provided_az=req_specs_dict[server.uuid], 

569 provided_sched_hints=sched_hints_dict.get( 

570 server.uuid, SCHED_HINTS_NOT_IN_REQUEST_SPEC) 

571 )["server"] 

572 for server in servers 

573 # Filter out the fake marker instance created by the 

574 # fill_virtual_interface_list online data migration. 

575 if server.uuid != virtual_interface.FAKE_UUID] 

576 servers_links = self._get_collection_links(request, 

577 servers, 

578 coll_name) 

579 servers_dict = dict(servers=server_list) 

580 

581 if servers_links: 

582 servers_dict["servers_links"] = servers_links 

583 

584 return servers_dict 

585 

586 @staticmethod 

587 def _get_metadata(instance): 

588 return instance.metadata or {} 

589 

590 @staticmethod 

591 def _get_vm_status(instance): 

592 # If the instance is deleted the vm and task states don't really matter 

593 if instance.get("deleted"): 

594 return "DELETED" 

595 return common.status_from_state(instance.get("vm_state"), 

596 instance.get("task_state")) 

597 

598 @staticmethod 

599 def _get_host_id(instance): 

600 host = instance.get("host") 

601 project = str(instance.get("project_id")) 

602 return utils.generate_hostid(host, project) 

603 

604 def _get_addresses(self, request, instance, extend_address=False): 

605 # Hide server addresses while the server is building. 

606 if instance.vm_state == vm_states.BUILDING: 

607 return {} 

608 

609 context = request.environ["nova.context"] 

610 networks = common.get_networks_for_instance(context, instance) 

611 

612 return self._address_builder.index( 

613 request, networks, extend_address, 

614 )["addresses"] 

615 

616 def _get_image(self, request, instance): 

617 image_ref = instance["image_ref"] 

618 if image_ref: 

619 image_id = str(common.get_id_from_href(image_ref)) 

620 bookmark = self._image_builder._get_bookmark_link(request, 

621 image_id, 

622 "images") 

623 image = { 

624 "id": image_id, 

625 "links": [{ 

626 "rel": "bookmark", 

627 "href": bookmark, 

628 }], 

629 } 

630 

631 if api_version_request.is_supported(request, min_version='2.98'): 

632 image_props = {} 

633 for key, value in instance.system_metadata.items(): 

634 if key.startswith(utils.SM_IMAGE_PROP_PREFIX): 634 ↛ 637line 634 didn't jump to line 637 because the condition on line 634 was never true

635 # remove prefix 'image_' at start of key, so that 

636 # key 'image_<key-name>' becomes '<key-name>' 

637 k = key.partition('_')[2] 

638 image_props[k] = value 

639 

640 image['properties'] = image_props 

641 

642 return image 

643 else: 

644 return "" 

645 

646 def _get_flavor_dict(self, request, flavor, show_extra_specs): 

647 flavordict = { 

648 "vcpus": flavor.vcpus, 

649 "ram": flavor.memory_mb, 

650 "disk": flavor.root_gb, 

651 "ephemeral": flavor.ephemeral_gb, 

652 "swap": flavor.swap, 

653 "original_name": flavor.name 

654 } 

655 if show_extra_specs: 

656 flavordict['extra_specs'] = flavor.extra_specs 

657 return flavordict 

658 

659 def _get_flavor(self, request, instance, show_extra_specs): 

660 flavor = instance.get_flavor() 

661 if not flavor: 661 ↛ 662line 661 didn't jump to line 662 because the condition on line 661 was never true

662 LOG.warning("Instance has had its flavor removed " 

663 "from the DB", instance=instance) 

664 return {} 

665 

666 if api_version_request.is_supported(request, min_version="2.47"): 

667 return self._get_flavor_dict(request, flavor, show_extra_specs) 

668 

669 flavor_id = flavor["flavorid"] 

670 flavor_bookmark = self._flavor_builder._get_bookmark_link( 

671 request, flavor_id, "flavors") 

672 return { 

673 "id": str(flavor_id), 

674 "links": [{ 

675 "rel": "bookmark", 

676 "href": flavor_bookmark, 

677 }], 

678 } 

679 

680 def _load_fault(self, request, instance): 

681 try: 

682 mapping = objects.InstanceMapping.get_by_instance_uuid( 

683 request.environ['nova.context'], instance.uuid) 

684 if mapping.cell_mapping is not None: 

685 with nova_context.target_cell(instance._context, 

686 mapping.cell_mapping): 

687 return instance.fault 

688 except exception.InstanceMappingNotFound: 

689 pass 

690 

691 # NOTE(danms): No instance mapping at all, or a mapping with no cell, 

692 # which means a legacy environment or instance. 

693 return instance.fault 

694 

695 def _get_fault(self, request, instance): 

696 if 'fault' in instance: 

697 fault = instance.fault 

698 else: 

699 fault = self._load_fault(request, instance) 

700 

701 if not fault: 

702 return None 

703 

704 fault_dict = { 

705 "code": fault["code"], 

706 "created": utils.isotime(fault["created_at"]), 

707 "message": fault["message"], 

708 } 

709 

710 if fault.get('details', None): 

711 is_admin = False 

712 context = request.environ["nova.context"] 

713 if context: 713 ↛ 716line 713 didn't jump to line 716 because the condition on line 713 was always true

714 is_admin = getattr(context, 'is_admin', False) 

715 

716 if is_admin or fault['code'] != 500: 

717 fault_dict['details'] = fault["details"] 

718 

719 return fault_dict 

720 

721 def _add_host_status(self, servers, instances, unknown_only=False): 

722 """Adds the ``host_status`` field to the list of servers 

723 

724 This method takes care to filter instances from down cells since they 

725 do not have a host set and as such we cannot determine the host status. 

726 

727 :param servers: list of detailed server dicts for the API response 

728 body; this list is modified by reference by updating the server 

729 dicts within the list 

730 :param instances: list of Instance objects 

731 :param unknown_only: whether to show only UNKNOWN host status 

732 """ 

733 # Filter out instances from down cells which do not have a host field. 

734 instances = [instance for instance in instances if 'host' in instance] 

735 # Get the dict, keyed by instance.uuid, of host status values. 

736 host_statuses = self.compute_api.get_instances_host_statuses(instances) 

737 for server in servers: 

738 # Filter out anything that is not in the resulting dict because 

739 # we had to filter the list of instances above for down cells. 

740 if server['id'] in host_statuses: 

741 host_status = host_statuses[server['id']] 

742 if unknown_only and host_status != fields.HostStatus.UNKNOWN: 742 ↛ 745line 742 didn't jump to line 745 because the condition on line 742 was never true

743 # Filter servers that are not allowed by policy to see 

744 # host_status values other than UNKNOWN. 

745 continue 

746 server['host_status'] = host_status 

747 

748 def _add_security_grps(self, req, servers, instances, 

749 create_request=False): 

750 if not len(servers): 

751 return 

752 

753 # If request is a POST create server we get the security groups 

754 # intended for an instance from the request. This is necessary because 

755 # the requested security groups for the instance have not yet been sent 

756 # to neutron. 

757 # Starting from microversion 2.75, security groups is returned in 

758 # PUT and POST Rebuild response also. 

759 if not create_request: 

760 context = req.environ['nova.context'] 

761 sg_instance_bindings = ( 

762 security_group_api.get_instances_security_groups_bindings( 

763 context, servers)) 

764 for server in servers: 

765 groups = sg_instance_bindings.get(server['id']) 

766 if groups: 

767 server['security_groups'] = groups 

768 

769 # This section is for POST create server request. There can be 

770 # only one security group for POST create server request. 

771 else: 

772 # try converting to json 

773 req_obj = jsonutils.loads(req.body) 

774 # Add security group to server, if no security group was in 

775 # request add default since that is the group it is part of 

776 servers[0]['security_groups'] = req_obj['server'].get( 

777 'security_groups', [{'name': 'default'}]) 

778 

779 @staticmethod 

780 def _get_instance_bdms_in_multiple_cells(ctxt, instance_uuids): 

781 inst_maps = objects.InstanceMappingList.get_by_instance_uuids( 

782 ctxt, instance_uuids) 

783 

784 cell_mappings = {} 

785 for inst_map in inst_maps: 

786 if (inst_map.cell_mapping is not None and 786 ↛ 785line 786 didn't jump to line 785 because the condition on line 786 was always true

787 inst_map.cell_mapping.uuid not in cell_mappings): 

788 cell_mappings.update( 

789 {inst_map.cell_mapping.uuid: inst_map.cell_mapping}) 

790 

791 bdms = {} 

792 results = nova_context.scatter_gather_cells( 

793 ctxt, cell_mappings.values(), 

794 nova_context.CELL_TIMEOUT, 

795 objects.BlockDeviceMappingList.bdms_by_instance_uuid, 

796 instance_uuids) 

797 for cell_uuid, result in results.items(): 

798 if isinstance(result, Exception): 

799 LOG.warning('Failed to get block device mappings for cell %s', 

800 cell_uuid) 

801 elif result is nova_context.did_not_respond_sentinel: 801 ↛ 802line 801 didn't jump to line 802 because the condition on line 801 was never true

802 LOG.warning('Timeout getting block device mappings for cell ' 

803 '%s', cell_uuid) 

804 else: 

805 bdms.update(result) 

806 return bdms 

807 

808 def _add_volumes_attachments(self, server, bdms, 

809 add_delete_on_termination): 

810 # server['id'] is guaranteed to be in the cache due to 

811 # the core API adding it in the 'detail' or 'show' method. 

812 # If that instance has since been deleted, it won't be in the 

813 # 'bdms' dictionary though, so use 'get' to avoid KeyErrors. 

814 instance_bdms = bdms.get(server['id'], []) 

815 volumes_attached = [] 

816 for bdm in instance_bdms: 

817 if bdm.get('volume_id'): 817 ↛ 816line 817 didn't jump to line 816 because the condition on line 817 was always true

818 volume_attached = {'id': bdm['volume_id']} 

819 if add_delete_on_termination: 

820 volume_attached['delete_on_termination'] = ( 

821 bdm['delete_on_termination']) 

822 volumes_attached.append(volume_attached) 

823 # NOTE(mriedem): The os-extended-volumes prefix should not be used for 

824 # new attributes after v2.1. They are only in v2.1 for backward compat 

825 # with v2.0. 

826 key = "os-extended-volumes:volumes_attached" 

827 server[key] = volumes_attached 

828 

829 @staticmethod 

830 def _get_server_groups(context, instance): 

831 try: 

832 sg = objects.InstanceGroup.get_by_instance_uuid(context, 

833 instance.uuid) 

834 return [sg.uuid] 

835 except exception.InstanceGroupNotFound: 

836 return []