Coverage for nova/objects/selection.py: 100%
36 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 (c) 2017 IBM Corp.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
15from oslo_serialization import jsonutils
16from oslo_utils import versionutils
17from oslo_versionedobjects import base as ovo_base
18from oslo_versionedobjects import fields
20from nova import conf
21from nova import objects
22from nova.objects import base
23from nova.scheduler.filters import utils as filter_utils
25CONF = conf.CONF
28@base.NovaObjectRegistry.register
29class Selection(base.NovaObject, ovo_base.ComparableVersionedObject):
30 """Represents a destination that has been selected by the Scheduler. Note
31 that these objects are not persisted to the database.
32 """
34 # Version 1.0: Initial version
35 # Version 1.1: Added availability_zone field.
36 VERSION = "1.1"
38 fields = {
39 "compute_node_uuid": fields.UUIDField(),
40 "service_host": fields.StringField(),
41 "nodename": fields.StringField(),
42 "cell_uuid": fields.UUIDField(),
43 "limits": fields.ObjectField("SchedulerLimits", nullable=True),
44 # An allocation_request is a non-trivial dict, and so it will be stored
45 # as an encoded string.
46 "allocation_request": fields.StringField(nullable=True),
47 "allocation_request_version": fields.StringField(nullable=True),
48 # The availability_zone represents the AZ the service_host is in at
49 # the time of scheduling. This is nullable for two reasons:
50 # 1. The Instance.availability_zone field is nullable - though that's
51 # not a great reason, the bigger reason is:
52 # 2. The host may not be in an AZ, and CONF.default_availability_zone
53 # is a StrOpt which technically could be set to None, so we have to
54 # account for it being a None value (rather than just not setting
55 # the field).
56 'availability_zone': fields.StringField(nullable=True),
57 }
59 def obj_make_compatible(self, primitive, target_version):
60 super(Selection, self).obj_make_compatible(primitive, target_version)
61 target_version = versionutils.convert_version_to_tuple(target_version)
62 if target_version < (1, 1):
63 primitive.pop('availability_zone', None)
65 @classmethod
66 def from_host_state(cls, host_state, allocation_request=None,
67 allocation_request_version=None):
68 """A convenience method for converting a HostState, an
69 allocation_request, and an allocation_request_version into a Selection
70 object. Note that allocation_request and allocation_request_version
71 must be passed separately, as they are not part of the HostState.
72 """
73 allocation_request_json = jsonutils.dumps(allocation_request)
74 limits = objects.SchedulerLimits.from_dict(host_state.limits)
75 metadata = filter_utils.aggregate_metadata_get_by_host(
76 host_state, key='availability_zone')
77 availability_zone = metadata.get('availability_zone')
78 if availability_zone:
79 # aggregate_metadata_get_by_host returns a set for the value but
80 # a host can only be in one AZ.
81 availability_zone = list(availability_zone)[0]
82 else:
83 availability_zone = CONF.default_availability_zone
84 return cls(compute_node_uuid=host_state.uuid,
85 service_host=host_state.host,
86 nodename=host_state.nodename,
87 cell_uuid=host_state.cell_uuid,
88 limits=limits,
89 allocation_request=allocation_request_json,
90 allocation_request_version=allocation_request_version,
91 availability_zone=availability_zone)
93 def to_dict(self):
94 if self.limits is not None:
95 limits = self.limits.to_dict()
96 else:
97 limits = {}
98 # The NUMATopologyFilter can set 'numa_topology' in the limits dict to
99 # a NUMATopologyLimits object which we need to convert to a primitive
100 # before this hits jsonutils.to_primitive(). We only check for that
101 # known case specifically as we don't care about handling out of tree
102 # filters or drivers injecting non-serializable things in the limits
103 # dict.
104 numa_limit = limits.get("numa_topology")
105 if numa_limit is not None:
106 limits['numa_topology'] = numa_limit.obj_to_primitive()
107 return {
108 'host': self.service_host,
109 'nodename': self.nodename,
110 'limits': limits,
111 }