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
« 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.
17from oslo_log import log as logging
18from oslo_serialization import jsonutils
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
39LOG = logging.getLogger(__name__)
42AZ_NOT_IN_REQUEST_SPEC = object()
43SCHED_HINTS_NOT_IN_REQUEST_SPEC = object()
46class ViewBuilder(common.ViewBuilder):
47 """Model a server API response as a python dictionary."""
49 _collection_name = "servers"
51 _progress_statuses = (
52 "ACTIVE",
53 "BUILD",
54 "REBUILD",
55 "RESIZE",
56 "VERIFY_RESIZE",
57 "MIGRATING",
58 )
60 _fault_statuses = (
61 "ERROR", "DELETED"
62 )
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']
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()
77 def create(self, request, instance):
78 """View that should be returned when an instance is created."""
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)
96 return server
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 }
129 def get_show_expected_attrs(self, expected_attrs=None):
130 """Returns a list of lazy-loadable expected attributes used by show
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.
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)))
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
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
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
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
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})
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')
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
322 if server["server"]["status"] in self._progress_statuses:
323 server["server"]["progress"] = instance.get("progress", 0)
325 context = request.environ['nova.context']
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
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))
342 if show_config_drive:
343 server["server"]["config_drive"] = instance["config_drive"]
345 if show_keypair:
346 server["server"]["key_name"] = instance["key_name"]
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])
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})
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)
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]
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)
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
438 if api_version_request.is_supported(request, min_version="2.9"):
439 server["server"]["locked"] = (True if instance["locked_by"]
440 else False)
442 if api_version_request.is_supported(request, min_version="2.73"):
443 server["server"]["locked_reason"] = (instance.system_metadata.get(
444 "locked_reason"))
446 if api_version_request.is_supported(request, min_version="2.19"):
447 server["server"]["description"] = instance.get(
448 "display_description")
450 if api_version_request.is_supported(request, min_version="2.26"):
451 server["server"]["tags"] = [t.tag for t in instance.tags]
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
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
468 if show_server_groups:
469 server['server']['server_groups'] = self._get_server_groups(
470 context,
471 instance)
472 return server
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)
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']
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)
496 instance_uuids = [inst['uuid'] for inst in instances]
497 bdms = self._get_instance_bdms_in_multiple_cells(context,
498 instance_uuids)
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)
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)
521 self._add_security_grps(request, list(servers_dict["servers"]),
522 instances)
523 return servers_dict
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.
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})
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)
586 if servers_links:
587 servers_dict["servers_links"] = servers_links
589 return servers_dict
591 @staticmethod
592 def _get_metadata(instance):
593 return instance.metadata or {}
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"))
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)
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 {}
614 context = request.environ["nova.context"]
615 networks = common.get_networks_for_instance(context, instance)
617 return self._address_builder.index(
618 request, networks, extend_address,
619 )["addresses"]
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 }
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
645 image['properties'] = image_props
647 return image
648 else:
649 return ""
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
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 {}
671 if api_version_request.is_supported(request, min_version="2.47"):
672 return self._get_flavor_dict(request, flavor, show_extra_specs)
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 }
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
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
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)
706 if not fault:
707 return None
709 fault_dict = {
710 "code": fault["code"],
711 "created": utils.isotime(fault["created_at"]),
712 "message": fault["message"],
713 }
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)
721 if is_admin or fault['code'] != 500:
722 fault_dict['details'] = fault["details"]
724 return fault_dict
726 def _add_host_status(self, servers, instances, unknown_only=False):
727 """Adds the ``host_status`` field to the list of servers
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.
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
753 def _add_security_grps(self, req, servers, instances,
754 create_request=False):
755 if not len(servers):
756 return
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
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'}])
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)
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})
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
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
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 []