Coverage for manila/scheduler/utils.py: 97%

56 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2026-02-18 22:19 +0000

1# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. 

2# Copyright (c) 2016 EMC Corporation 

3# 

4# All Rights Reserved. 

5# 

6# Licensed under the Apache License, Version 2.0 (the "License"); you may 

7# not use this file except in compliance with the License. You may obtain 

8# a copy of the License at 

9# 

10# http://www.apache.org/licenses/LICENSE-2.0 

11# 

12# Unless required by applicable law or agreed to in writing, software 

13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

15# License for the specific language governing permissions and limitations 

16# under the License. 

17from oslo_log import log 

18from oslo_utils import strutils 

19 

20from manila.scheduler.filters import extra_specs_ops 

21 

22LOG = log.getLogger(__name__) 

23 

24 

25def generate_stats(host_state, properties): 

26 """Generates statistics from host and share data.""" 

27 

28 host_stats = { 

29 'host': host_state.host, 

30 'share_backend_name': host_state.share_backend_name, 

31 'vendor_name': host_state.vendor_name, 

32 'driver_version': host_state.driver_version, 

33 'storage_protocol': host_state.storage_protocol, 

34 'qos': host_state.qos, 

35 'total_capacity_gb': host_state.total_capacity_gb, 

36 'allocated_capacity_gb': host_state.allocated_capacity_gb, 

37 'free_capacity_gb': host_state.free_capacity_gb, 

38 'reserved_percentage': host_state.reserved_percentage, 

39 'reserved_snapshot_percentage': 

40 host_state.reserved_snapshot_percentage, 

41 'reserved_share_extend_percentage': 

42 host_state.reserved_share_extend_percentage, 

43 'driver_handles_share_servers': 

44 host_state.driver_handles_share_servers, 

45 'thin_provisioning': host_state.thin_provisioning, 

46 'updated': host_state.updated, 

47 'dedupe': host_state.dedupe, 

48 'compression': host_state.compression, 

49 'snapshot_support': host_state.snapshot_support, 

50 'create_share_from_snapshot_support': 

51 host_state.create_share_from_snapshot_support, 

52 'revert_to_snapshot_support': host_state.revert_to_snapshot_support, 

53 'mount_snapshot_support': host_state.mount_snapshot_support, 

54 'replication_domain': host_state.replication_domain, 

55 'replication_type': host_state.replication_type, 

56 'provisioned_capacity_gb': host_state.provisioned_capacity_gb, 

57 'pools': host_state.pools, 

58 'max_over_subscription_ratio': 

59 host_state.max_over_subscription_ratio, 

60 'sg_consistent_snapshot_support': ( 

61 host_state.sg_consistent_snapshot_support), 

62 'ipv4_support': host_state.ipv4_support, 

63 'ipv6_support': host_state.ipv6_support, 

64 'security_service_update_support': ( 

65 host_state.security_service_update_support), 

66 'network_allocation_update_support': ( 

67 host_state.network_allocation_update_support), 

68 'share_server_multiple_subnet_support': ( 

69 host_state.share_server_multiple_subnet_support), 

70 'mount_point_name_support': ( 

71 host_state.mount_point_name_support), 

72 'share_replicas_migration_support': ( 

73 host_state.share_replicas_migration_support), 

74 'encryption_support': host_state.encryption_support, 

75 } 

76 

77 host_caps = host_state.capabilities 

78 

79 share_type = properties.get('share_type', {}) 

80 extra_specs = share_type.get('extra_specs', {}) 

81 

82 share_group_type = properties.get('share_group_type', {}) 

83 group_specs = share_group_type.get('group_specs', {}) 

84 

85 request_spec = properties.get('request_spec', {}) 

86 share_stats = request_spec.get('resource_properties', {}) 

87 

88 stats = { 

89 'host_stats': host_stats, 

90 'host_caps': host_caps, 

91 'share_type': share_type, 

92 'extra_specs': extra_specs, 

93 'share_stats': share_stats, 

94 'share_group_type': share_group_type, 

95 'group_specs': group_specs, 

96 } 

97 

98 return stats 

99 

100 

101def use_thin_logic(share_type): 

102 # NOTE(xyang): To preserve the existing behavior, we use thin logic 

103 # to evaluate in two cases: 

104 # 1) 'thin_provisioning' is not set in extra specs (This is for 

105 # backward compatibility. If not set, the scheduler behaves 

106 # the same as before this bug fix). 

107 # 2) 'thin_provisioning' is set in extra specs and it is 

108 # '<is> True' or 'True'. 

109 # Otherwise we use the thick logic to evaluate. 

110 use_thin_logic = True 

111 thin_spec = None 

112 try: 

113 thin_spec = share_type.get('extra_specs', {}).get( 

114 'thin_provisioning') 

115 if thin_spec is None: 

116 thin_spec = share_type.get('extra_specs', {}).get( 

117 'capabilities:thin_provisioning') 

118 # NOTE(xyang) 'use_thin_logic' and 'thin_provisioning' are NOT 

119 # the same thing. The first purpose of "use_thin_logic" is to 

120 # preserve the existing scheduler behavior if 'thin_provisioning' 

121 # is NOT in extra_specs (if thin_spec is None, use_thin_logic 

122 # should be True). The second purpose of 'use_thin_logic' 

123 # is to honor 'thin_provisioning' if it is in extra specs (if 

124 # thin_spec is set to True, use_thin_logic should be True; if 

125 # thin_spec is set to False, use_thin_logic should be False). 

126 use_thin_logic = strutils.bool_from_string( 

127 thin_spec, strict=True) if thin_spec is not None else True 

128 except ValueError: 

129 # Check if the value of thin_spec is '<is> True'. 

130 if thin_spec is not None and not extra_specs_ops.match( 

131 True, thin_spec): 

132 use_thin_logic = False 

133 return use_thin_logic 

134 

135 

136def thin_provisioning(host_state_thin_provisioning): 

137 # NOTE(xyang): host_state_thin_provisioning is reported by driver. 

138 # It can be either bool (True or False) or 

139 # list ([True, False], [True], [False]). 

140 thin_capability = [host_state_thin_provisioning] if not isinstance( 

141 host_state_thin_provisioning, list) else host_state_thin_provisioning 

142 return True in thin_capability 

143 

144 

145def capabilities_satisfied(capabilities, extra_specs): 

146 

147 # These extra-specs are not capabilities for matching hosts 

148 ignored_extra_specs = ( 

149 'availability_zones', 'capabilities:availability_zones', 

150 ) 

151 

152 for key, req in extra_specs.items(): 

153 # Ignore some extra_specs if told to 

154 if key in ignored_extra_specs: 

155 continue 

156 

157 # Either not scoped format, or in capabilities scope 

158 scope = key.split(':') 

159 

160 # Ignore scoped (such as vendor-specific) capabilities 

161 if len(scope) > 1 and scope[0] != "capabilities": 

162 continue 

163 # Strip off prefix if spec started with 'capabilities:' 

164 elif scope[0] == "capabilities": 

165 del scope[0] 

166 

167 cap = capabilities 

168 for index in range(len(scope)): 

169 try: 

170 cap = cap.get(scope[index]) 

171 except AttributeError: 

172 cap = None 

173 if cap is None: 

174 LOG.debug("Host doesn't provide capability '%(cap)s' " 

175 "listed in the extra specs", 

176 {'cap': scope[index]}) 

177 return False 

178 

179 # Make all capability values a list so we can handle lists 

180 cap_list = [cap] if not isinstance(cap, list) else cap 

181 

182 # Loop through capability values looking for any match 

183 for cap_value in cap_list: 

184 if extra_specs_ops.match(cap_value, req): 

185 break 

186 else: 

187 # Nothing matched, so bail out 

188 LOG.debug('Share type extra spec requirement ' 

189 '"%(key)s=%(req)s" does not match reported ' 

190 'capability "%(cap)s"', 

191 {'key': key, 'req': req, 'cap': cap}) 

192 return False 

193 return True