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
« 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.
17import collections
19from oslo_log import log as logging
20from oslo_serialization import jsonutils
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
41LOG = logging.getLogger(__name__)
44SCHED_HINTS_NOT_IN_REQUEST_SPEC = object()
47class ViewBuilder(common.ViewBuilder):
48 """Model a server API response as a python dictionary."""
50 _collection_name = "servers"
52 _progress_statuses = (
53 "ACTIVE",
54 "BUILD",
55 "REBUILD",
56 "RESIZE",
57 "VERIFY_RESIZE",
58 "MIGRATING",
59 )
61 _fault_statuses = (
62 "ERROR", "DELETED"
63 )
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']
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()
78 def create(self, request, instance):
79 """View that should be returned when an instance is created."""
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)
97 return server
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 }
130 def get_show_expected_attrs(self, expected_attrs=None):
131 """Returns a list of lazy-loadable expected attributes used by show
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.
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)))
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
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
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
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
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})
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')
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
319 if server["server"]["status"] in self._progress_statuses:
320 server["server"]["progress"] = instance.get("progress", 0)
322 context = request.environ['nova.context']
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
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))
339 if show_config_drive:
340 server["server"]["config_drive"] = instance["config_drive"]
342 if show_keypair:
343 server["server"]["key_name"] = instance["key_name"]
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])
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})
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)
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]
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)
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
435 if api_version_request.is_supported(request, min_version="2.9"):
436 server["server"]["locked"] = (True if instance["locked_by"]
437 else False)
439 if api_version_request.is_supported(request, min_version="2.73"):
440 server["server"]["locked_reason"] = (instance.system_metadata.get(
441 "locked_reason"))
443 if api_version_request.is_supported(request, min_version="2.19"):
444 server["server"]["description"] = instance.get(
445 "display_description")
447 if api_version_request.is_supported(request, min_version="2.26"):
448 server["server"]["tags"] = [t.tag for t in instance.tags]
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
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
465 if show_server_groups:
466 server['server']['server_groups'] = self._get_server_groups(
467 context,
468 instance)
469 return server
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)
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']
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)
493 instance_uuids = [inst['uuid'] for inst in instances]
494 bdms = self._get_instance_bdms_in_multiple_cells(context,
495 instance_uuids)
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)
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)
518 self._add_security_grps(request, list(servers_dict["servers"]),
519 instances)
520 return servers_dict
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.
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})
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)
581 if servers_links:
582 servers_dict["servers_links"] = servers_links
584 return servers_dict
586 @staticmethod
587 def _get_metadata(instance):
588 return instance.metadata or {}
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"))
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)
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 {}
609 context = request.environ["nova.context"]
610 networks = common.get_networks_for_instance(context, instance)
612 return self._address_builder.index(
613 request, networks, extend_address,
614 )["addresses"]
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 }
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
640 image['properties'] = image_props
642 return image
643 else:
644 return ""
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
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 {}
666 if api_version_request.is_supported(request, min_version="2.47"):
667 return self._get_flavor_dict(request, flavor, show_extra_specs)
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 }
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
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
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)
701 if not fault:
702 return None
704 fault_dict = {
705 "code": fault["code"],
706 "created": utils.isotime(fault["created_at"]),
707 "message": fault["message"],
708 }
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)
716 if is_admin or fault['code'] != 500:
717 fault_dict['details'] = fault["details"]
719 return fault_dict
721 def _add_host_status(self, servers, instances, unknown_only=False):
722 """Adds the ``host_status`` field to the list of servers
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.
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
748 def _add_security_grps(self, req, servers, instances,
749 create_request=False):
750 if not len(servers):
751 return
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
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'}])
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)
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})
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
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
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 []