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

341 statements  

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

17from oslo_log import log as logging 

18from oslo_serialization import jsonutils 

19 

20from nova.api.openstack import api_version_request 

21from nova.api.openstack import common 

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

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

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

25from nova import availability_zones as avail_zone 

26from nova.compute import api as compute 

27from nova.compute import vm_states 

28from nova import context as nova_context 

29from nova import exception 

30from nova.network import security_group_api 

31from nova import objects 

32from nova.objects import fields 

33from nova.objects import virtual_interface 

34from nova.policies import extended_server_attributes as esa_policies 

35from nova.policies import servers as servers_policies 

36from nova import utils 

37 

38 

39LOG = logging.getLogger(__name__) 

40 

41 

42AZ_NOT_IN_REQUEST_SPEC = object() 

43SCHED_HINTS_NOT_IN_REQUEST_SPEC = object() 

44 

45 

46class ViewBuilder(common.ViewBuilder): 

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

48 

49 _collection_name = "servers" 

50 

51 _progress_statuses = ( 

52 "ACTIVE", 

53 "BUILD", 

54 "REBUILD", 

55 "RESIZE", 

56 "VERIFY_RESIZE", 

57 "MIGRATING", 

58 ) 

59 

60 _fault_statuses = ( 

61 "ERROR", "DELETED" 

62 ) 

63 

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

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

66 # shown. 

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

68 

69 def __init__(self): 

70 """Initialize view builder.""" 

71 super(ViewBuilder, self).__init__() 

72 self._address_builder = views_addresses.ViewBuilder() 

73 self._image_builder = views_images.ViewBuilder() 

74 self._flavor_builder = views_flavors.ViewBuilder() 

75 self.compute_api = compute.API() 

76 

77 def create(self, request, instance): 

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

79 

80 server = { 

81 "server": { 

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

83 "links": self._get_links(request, 

84 instance["uuid"], 

85 self._collection_name), 

86 # NOTE(sdague): historically this was the 

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

88 # are gone, we merge these attributes here. 

89 "OS-DCF:diskConfig": ( 

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

91 }, 

92 } 

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

94 create_request=True) 

95 

96 return server 

97 

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

99 show_extended_attr=None, show_host_status=None, 

100 show_sec_grp=None, bdms=None, cell_down_support=False, 

101 show_user_data=False, provided_az=None, 

102 provided_sched_hints=None): 

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

104 if cell_down_support and 'display_name' not in instance: 

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

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

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

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

109 # information available from the nova_api database. 

110 return { 

111 "server": { 

112 "id": instance.uuid, 

113 "status": "UNKNOWN", 

114 "links": self._get_links(request, 

115 instance.uuid, 

116 self._collection_name), 

117 }, 

118 } 

119 return { 

120 "server": { 

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

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

123 "links": self._get_links(request, 

124 instance["uuid"], 

125 self._collection_name), 

126 }, 

127 } 

128 

129 def get_show_expected_attrs(self, expected_attrs=None): 

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

131 

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

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

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

135 deleted. 

136 

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

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

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

140 ultimately used when getting an instance from the database. 

141 :returns: merged and sorted list of expected attributes 

142 """ 

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

144 expected_attrs = [] 

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

146 # results. 

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

148 

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

150 show_server_groups): 

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

152 ret = { 

153 "server": { 

154 "id": instance.uuid, 

155 "status": "UNKNOWN", 

156 "tenant_id": instance.project_id, 

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

158 "links": self._get_links( 

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

160 }, 

161 } 

162 if 'flavor' in instance: 

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

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

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

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

167 # the basic information from its instance_mapping. 

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

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

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

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

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

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

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

175 show_extra_specs) 

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

177 # boot time, return UNKNOWN. 

178 avz = instance.availability_zone or "UNKNOWN" 

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

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

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

182 # data migrated, return UNKNOWN. 

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

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

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

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

187 context, instance) 

188 return ret 

189 

190 @staticmethod 

191 def _get_host_status_unknown_only(context, instance=None): 

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

193 can show, if any: 

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

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

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

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

198 response. 

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

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

201 """ 

202 unknown_only = None 

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

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

205 # show:host_status:unknown-only policy. 

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

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

208 # policy is default to system and project reader. 

209 target = None 

210 if instance is not None: 

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

212 if context.can( 

213 servers_policies.SERVERS % 'show:host_status', 

214 fatal=False, target=target): 

215 unknown_only = False 

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

217 # least show only the host status: UNKNOWN. 

218 elif context.can( 

219 servers_policies.SERVERS % 

220 'show:host_status:unknown-only', 

221 fatal=False, 

222 target=target): 

223 unknown_only = True 

224 return unknown_only 

225 

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

227 if provided_az is AZ_NOT_IN_REQUEST_SPEC: 

228 # Case the provided_az is pre fetched, but not specified 

229 pinned_az = None 

230 elif provided_az is not None: 

231 # Case the provided_az is pre fetched, and specified 

232 pinned_az = provided_az 

233 else: 

234 # Case the provided_az is not pre fethed. 

235 try: 

236 req_spec = objects.RequestSpec.get_by_instance_uuid( 

237 context, instance.uuid) 

238 pinned_az = req_spec.availability_zone 

239 except exception.RequestSpecNotFound: 

240 pinned_az = None 

241 return pinned_az 

242 

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

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

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

246 sched_hints = None 

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

248 sched_hints = provided_sched_hints 

249 else: 

250 # Case the provided_az is not pre fethed. 

251 try: 

252 req_spec = objects.RequestSpec.get_by_instance_uuid( 

253 context, instance.uuid) 

254 sched_hints = req_spec.scheduler_hints 

255 except exception.RequestSpecNotFound: 

256 sched_hints = {} 

257 return sched_hints 

258 

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

260 show_extra_specs=None, show_AZ=True, show_config_drive=True, 

261 show_extended_attr=None, show_host_status=None, 

262 show_keypair=True, show_srv_usg=True, show_sec_grp=True, 

263 show_extended_status=True, show_extended_volumes=True, 

264 bdms=None, cell_down_support=False, show_server_groups=False, 

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

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

267 if show_extra_specs is None: 

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

269 # then figure it out here. 

270 show_extra_specs = False 

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

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

273 show_extra_specs = context.can( 

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

275 fatal=False, 

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

277 

278 if cell_down_support and 'display_name' not in instance: 

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

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

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

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

283 # information available from the nova_api database. 

284 return self._show_from_down_cell( 

285 request, instance, show_extra_specs, show_server_groups) 

286 ip_v4 = instance.get('access_ip_v4') 

287 ip_v6 = instance.get('access_ip_v6') 

288 

289 server = { 

290 "server": { 

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

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

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

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

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

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

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

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

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

300 show_extra_specs), 

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

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

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

304 extend_address), 

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

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

307 "links": self._get_links(request, 

308 instance["uuid"], 

309 self._collection_name), 

310 # NOTE(sdague): historically this was the 

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

312 # are gone, we merge these attributes here. 

313 "OS-DCF:diskConfig": ( 

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

315 }, 

316 } 

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

318 _inst_fault = self._get_fault(request, instance) 

319 if _inst_fault: 

320 server['server']['fault'] = _inst_fault 

321 

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

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

324 

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

326 

327 if show_AZ: 

328 az = avail_zone.get_instance_availability_zone(context, instance) 

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

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

331 # with v2.0. 

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

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

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

335 server['server']['pinned_availability_zone'] = pinned_az 

336 

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

338 server['server']['scheduler_hints'] = ( 

339 self._get_scheduler_hints( 

340 context, instance, provided_sched_hints)) 

341 

342 if show_config_drive: 

343 server["server"]["config_drive"] = instance["config_drive"] 

344 

345 if show_keypair: 

346 server["server"]["key_name"] = instance["key_name"] 

347 

348 if show_srv_usg: 

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

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

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

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

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

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

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

356 if instance[k] else None) 

357 if show_sec_grp: 

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

359 

360 if show_extended_attr is None: 

361 show_extended_attr = context.can( 

362 esa_policies.BASE_POLICY_NAME, fatal=False, 

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

364 

365 if show_extended_attr: 

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

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

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

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

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

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

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

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

374 'hostname', 'kernel_id', 'ramdisk_id', 

375 'root_device_name'] 

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

377 # response include all the server attributes including these 

378 # extended attributes also. But microversion 2.57 already 

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

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

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

382 if show_user_data: 

383 properties += ['user_data'] 

384 for attr in properties: 

385 if attr == 'name': 

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

387 elif attr == 'node': 

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

389 else: 

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

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

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

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

394 

395 if show_extended_status: 

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

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

398 # microversion. 

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

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

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

402 # compat with v2.0. 

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

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

405 

406 if show_extended_volumes: 

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

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

409 # compat with v2.0. 

410 add_delete_on_termination = api_version_request.is_supported( 

411 request, min_version='2.3') 

412 if bdms is None: 

413 bdms = objects.BlockDeviceMappingList.bdms_by_instance_uuid( 

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

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

416 bdms, 

417 add_delete_on_termination) 

418 

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

420 if show_host_status is None: 

421 unknown_only = self._get_host_status_unknown_only( 

422 context, instance) 

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

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

425 # API. 

426 if unknown_only is not None: 

427 host_status = self.compute_api.get_instance_host_status( 

428 instance) 

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

430 # the host status field only if: 

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

432 # OR 

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

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

435 host_status == fields.HostStatus.UNKNOWN): 

436 server["server"]['host_status'] = host_status 

437 

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

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

440 else False) 

441 

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

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

444 "locked_reason")) 

445 

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

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

448 "display_description") 

449 

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

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

452 

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

454 trusted_certs = None 

455 if instance.trusted_certs: 

456 trusted_certs = instance.trusted_certs.ids 

457 server["server"]["trusted_image_certificates"] = trusted_certs 

458 

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

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

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

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

463 # it if it's not already added 

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

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

466 instance.hostname 

467 

468 if show_server_groups: 

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

470 context, 

471 instance) 

472 return server 

473 

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

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

476 coll_name = self._collection_name 

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

478 False, cell_down_support=cell_down_support) 

479 

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

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

482 coll_name = self._collection_name + '/detail' 

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

484 

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

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

487 # once before we iterate the list of instances 

488 show_extra_specs = context.can( 

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

490 fatal=False) 

491 else: 

492 show_extra_specs = False 

493 show_extended_attr = context.can( 

494 esa_policies.BASE_POLICY_NAME, fatal=False) 

495 

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

497 bdms = self._get_instance_bdms_in_multiple_cells(context, 

498 instance_uuids) 

499 

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

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

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

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

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

505 coll_name, show_extra_specs, 

506 show_extended_attr=show_extended_attr, 

507 # We process host_status in aggregate. 

508 show_host_status=False, 

509 show_sec_grp=False, 

510 bdms=bdms, 

511 cell_down_support=cell_down_support) 

512 

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

514 unknown_only = self._get_host_status_unknown_only(context) 

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

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

517 if unknown_only is not None: 

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

519 unknown_only=unknown_only) 

520 

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

522 instances) 

523 return servers_dict 

524 

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

526 show_extended_attr=None, show_host_status=None, 

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

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

529 

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

531 :param request: API request 

532 :param servers: List of servers in dictionary format 

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

534 for a pagination query 

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

536 included in the response dict. 

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

538 the response dict. 

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

540 the response dict. 

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

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

543 returning a minimal instance 

544 construct if the relevant cell is 

545 down. 

546 :returns: Server data in dictionary format 

547 """ 

548 req_specs = None 

549 req_specs_dict = {} 

550 sched_hints_dict = {} 

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

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

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

554 req_specs = objects.RequestSpec.get_by_instance_uuids( 

555 context, instance_uuids) 

556 req_specs_dict.update({req.instance_uuid: req.availability_zone 

557 for req in req_specs 

558 if req.availability_zone is not None}) 

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

560 sched_hints_dict.update({ 

561 req.instance_uuid: req.scheduler_hints 

562 for req in req_specs 

563 if req.scheduler_hints is not None}) 

564 

565 server_list = [ 

566 func(request, server, 

567 show_extra_specs=show_extra_specs, 

568 show_extended_attr=show_extended_attr, 

569 show_host_status=show_host_status, 

570 show_sec_grp=show_sec_grp, bdms=bdms, 

571 cell_down_support=cell_down_support, 

572 provided_az=req_specs_dict.get( 

573 server.uuid, AZ_NOT_IN_REQUEST_SPEC), 

574 provided_sched_hints=sched_hints_dict.get( 

575 server.uuid, SCHED_HINTS_NOT_IN_REQUEST_SPEC) 

576 )["server"] 

577 for server in servers 

578 # Filter out the fake marker instance created by the 

579 # fill_virtual_interface_list online data migration. 

580 if server.uuid != virtual_interface.FAKE_UUID] 

581 servers_links = self._get_collection_links(request, 

582 servers, 

583 coll_name) 

584 servers_dict = dict(servers=server_list) 

585 

586 if servers_links: 

587 servers_dict["servers_links"] = servers_links 

588 

589 return servers_dict 

590 

591 @staticmethod 

592 def _get_metadata(instance): 

593 return instance.metadata or {} 

594 

595 @staticmethod 

596 def _get_vm_status(instance): 

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

598 if instance.get("deleted"): 

599 return "DELETED" 

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

601 instance.get("task_state")) 

602 

603 @staticmethod 

604 def _get_host_id(instance): 

605 host = instance.get("host") 

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

607 return utils.generate_hostid(host, project) 

608 

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

610 # Hide server addresses while the server is building. 

611 if instance.vm_state == vm_states.BUILDING: 

612 return {} 

613 

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

615 networks = common.get_networks_for_instance(context, instance) 

616 

617 return self._address_builder.index( 

618 request, networks, extend_address, 

619 )["addresses"] 

620 

621 def _get_image(self, request, instance): 

622 image_ref = instance["image_ref"] 

623 if image_ref: 

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

625 bookmark = self._image_builder._get_bookmark_link(request, 

626 image_id, 

627 "images") 

628 image = { 

629 "id": image_id, 

630 "links": [{ 

631 "rel": "bookmark", 

632 "href": bookmark, 

633 }], 

634 } 

635 

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

637 image_props = {} 

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

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

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

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

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

643 image_props[k] = value 

644 

645 image['properties'] = image_props 

646 

647 return image 

648 else: 

649 return "" 

650 

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

652 flavordict = { 

653 "vcpus": flavor.vcpus, 

654 "ram": flavor.memory_mb, 

655 "disk": flavor.root_gb, 

656 "ephemeral": flavor.ephemeral_gb, 

657 "swap": flavor.swap, 

658 "original_name": flavor.name 

659 } 

660 if show_extra_specs: 

661 flavordict['extra_specs'] = flavor.extra_specs 

662 return flavordict 

663 

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

665 flavor = instance.get_flavor() 

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

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

668 "from the DB", instance=instance) 

669 return {} 

670 

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

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

673 

674 flavor_id = flavor["flavorid"] 

675 flavor_bookmark = self._flavor_builder._get_bookmark_link( 

676 request, flavor_id, "flavors") 

677 return { 

678 "id": str(flavor_id), 

679 "links": [{ 

680 "rel": "bookmark", 

681 "href": flavor_bookmark, 

682 }], 

683 } 

684 

685 def _load_fault(self, request, instance): 

686 try: 

687 mapping = objects.InstanceMapping.get_by_instance_uuid( 

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

689 if mapping.cell_mapping is not None: 

690 with nova_context.target_cell(instance._context, 

691 mapping.cell_mapping): 

692 return instance.fault 

693 except exception.InstanceMappingNotFound: 

694 pass 

695 

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

697 # which means a legacy environment or instance. 

698 return instance.fault 

699 

700 def _get_fault(self, request, instance): 

701 if 'fault' in instance: 

702 fault = instance.fault 

703 else: 

704 fault = self._load_fault(request, instance) 

705 

706 if not fault: 

707 return None 

708 

709 fault_dict = { 

710 "code": fault["code"], 

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

712 "message": fault["message"], 

713 } 

714 

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

716 is_admin = False 

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

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

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

720 

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

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

723 

724 return fault_dict 

725 

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

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

728 

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

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

731 

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

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

734 dicts within the list 

735 :param instances: list of Instance objects 

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

737 """ 

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

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

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

741 host_statuses = self.compute_api.get_instances_host_statuses(instances) 

742 for server in servers: 

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

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

745 if server['id'] in host_statuses: 

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

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

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

749 # host_status values other than UNKNOWN. 

750 continue 

751 server['host_status'] = host_status 

752 

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

754 create_request=False): 

755 if not len(servers): 

756 return 

757 

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

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

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

761 # to neutron. 

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

763 # PUT and POST Rebuild response also. 

764 if not create_request: 

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

766 sg_instance_bindings = ( 

767 security_group_api.get_instances_security_groups_bindings( 

768 context, servers)) 

769 for server in servers: 

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

771 if groups: 

772 server['security_groups'] = groups 

773 

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

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

776 else: 

777 # try converting to json 

778 req_obj = jsonutils.loads(req.body) 

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

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

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

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

783 

784 @staticmethod 

785 def _get_instance_bdms_in_multiple_cells(ctxt, instance_uuids): 

786 inst_maps = objects.InstanceMappingList.get_by_instance_uuids( 

787 ctxt, instance_uuids) 

788 

789 cell_mappings = {} 

790 for inst_map in inst_maps: 

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

792 inst_map.cell_mapping.uuid not in cell_mappings): 

793 cell_mappings.update( 

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

795 

796 bdms = {} 

797 results = nova_context.scatter_gather_cells( 

798 ctxt, cell_mappings.values(), 

799 nova_context.CELL_TIMEOUT, 

800 objects.BlockDeviceMappingList.bdms_by_instance_uuid, 

801 instance_uuids) 

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

803 if isinstance(result, Exception): 

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

805 cell_uuid) 

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

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

808 '%s', cell_uuid) 

809 else: 

810 bdms.update(result) 

811 return bdms 

812 

813 def _add_volumes_attachments(self, server, bdms, 

814 add_delete_on_termination): 

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

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

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

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

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

820 volumes_attached = [] 

821 for bdm in instance_bdms: 

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

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

824 if add_delete_on_termination: 

825 volume_attached['delete_on_termination'] = ( 

826 bdm['delete_on_termination']) 

827 volumes_attached.append(volume_attached) 

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

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

830 # with v2.0. 

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

832 server[key] = volumes_attached 

833 

834 @staticmethod 

835 def _get_server_groups(context, instance): 

836 try: 

837 sg = objects.InstanceGroup.get_by_instance_uuid(context, 

838 instance.uuid) 

839 return [sg.uuid] 

840 except exception.InstanceGroupNotFound: 

841 return []