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

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. 

14 

15from oslo_serialization import jsonutils 

16from oslo_utils import versionutils 

17from oslo_versionedobjects import base as ovo_base 

18from oslo_versionedobjects import fields 

19 

20from nova import conf 

21from nova import objects 

22from nova.objects import base 

23from nova.scheduler.filters import utils as filter_utils 

24 

25CONF = conf.CONF 

26 

27 

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 """ 

33 

34 # Version 1.0: Initial version 

35 # Version 1.1: Added availability_zone field. 

36 VERSION = "1.1" 

37 

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 } 

58 

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) 

64 

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) 

92 

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 }