Coverage for manila/share/drivers/netapp/utils.py: 93%

227 statements  

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

1# Copyright (c) 2015 Bob Callaway. All rights reserved. 

2# Copyright (c) 2015 Tom Barron. All rights reserved. 

3# Copyright (c) 2015 Clinton Knight. 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. 

16"""Utilities for NetApp drivers.""" 

17 

18from collections import abc 

19import decimal 

20import platform 

21import re 

22 

23from oslo_concurrency import processutils as putils 

24from oslo_log import log 

25from oslo_utils import timeutils 

26from oslo_utils import units 

27 

28from manila import exception 

29from manila.i18n import _ 

30from manila import version 

31 

32 

33LOG = log.getLogger(__name__) 

34 

35VALID_TRACE_FLAGS = ['method', 'api'] 

36TRACE_METHOD = False 

37TRACE_API = False 

38API_TRACE_PATTERN = '(.*)' 

39SVM_MIGRATE_POLICY_TYPE_NAME = 'migrate' 

40MIGRATION_OPERATION_ID_KEY = 'migration_operation_id' 

41MIGRATION_STATE_READY_FOR_CUTOVER = 'ready_for_cutover' 

42MIGRATION_STATE_READY_FOR_SOURCE_CLEANUP = 'ready_for_source_cleanup' 

43MIGRATION_STATE_MIGRATE_COMPLETE = 'migrate_complete' 

44MIGRATION_STATE_MIGRATE_PAUSED = 'migrate_paused' 

45 

46EXTENDED_DATA_PROTECTION_TYPE = 'extended_data_protection' 

47MIRROR_ALL_SNAP_POLICY = 'MirrorAllSnapshots' 

48DATA_PROTECTION_TYPE = 'data_protection' 

49 

50FLEXGROUP_STYLE_EXTENDED = 'flexgroup' 

51FLEXVOL_STYLE_EXTENDED = 'flexvol' 

52 

53FLEXGROUP_DEFAULT_POOL_NAME = 'flexgroup_auto' 

54 

55ABSOLUTE_MAX_INODES = 2_040_109_45 

56 

57CLONE_SPLIT_STATUS_ONGOING = 'ongoing' 

58CLONE_SPLIT_STATUS_UNKNOWN = 'unknown' 

59CLONE_SPLIT_STATUS_FINISHED = 'finished' 

60 

61 

62class NetAppDriverException(exception.ShareBackendException): 

63 message = _("NetApp Manila Driver exception.") 

64 

65 

66def validate_driver_instantiation(**kwargs): 

67 """Checks if a driver is instantiated other than by the unified driver. 

68 

69 Helps check direct instantiation of netapp drivers. 

70 Call this function in every netapp block driver constructor. 

71 """ 

72 if kwargs and kwargs.get('netapp_mode') == 'proxy': 

73 return 

74 LOG.warning('Please use NetAppDriver in the configuration file ' 

75 'to load the driver instead of directly specifying ' 

76 'the driver module name.') 

77 

78 

79def check_flags(required_flags, configuration): 

80 """Ensure that the flags we care about are set.""" 

81 for flag in required_flags: 

82 if getattr(configuration, flag, None) is None: 

83 msg = _('Configuration value %s is not set.') % flag 

84 raise exception.InvalidInput(reason=msg) 

85 

86 

87def round_down(value, precision='0.00'): 

88 """Round a number downward using a specified level of precision. 

89 

90 Example: round_down(float(total_space_in_bytes) / units.Gi, '0.01') 

91 """ 

92 return float(decimal.Decimal(str(value)).quantize( 

93 decimal.Decimal(precision), rounding=decimal.ROUND_DOWN)) 

94 

95 

96def setup_tracing(trace_flags_string, api_trace_pattern=API_TRACE_PATTERN): 

97 global TRACE_METHOD 

98 global TRACE_API 

99 global API_TRACE_PATTERN 

100 TRACE_METHOD = False 

101 TRACE_API = False 

102 API_TRACE_PATTERN = api_trace_pattern 

103 if trace_flags_string: 

104 flags = trace_flags_string.split(',') 

105 flags = [flag.strip() for flag in flags] 

106 for invalid_flag in list(set(flags) - set(VALID_TRACE_FLAGS)): 

107 LOG.warning('Invalid trace flag: %s', invalid_flag) 

108 try: 

109 re.compile(api_trace_pattern) 

110 except re.error: 

111 msg = _('Cannot parse the API trace pattern. %s is not a ' 

112 'valid python regular expression.') % api_trace_pattern 

113 raise exception.BadConfigurationException(reason=msg) 

114 TRACE_METHOD = 'method' in flags 

115 TRACE_API = 'api' in flags 

116 

117 

118def trace(f): 

119 def trace_wrapper(self, *args, **kwargs): 

120 if TRACE_METHOD: 

121 LOG.debug('Entering method %s', f.__name__) 

122 result = f(self, *args, **kwargs) 

123 if TRACE_METHOD: 

124 LOG.debug('Leaving method %s', f.__name__) 

125 return result 

126 return trace_wrapper 

127 

128 

129def convert_to_list(value): 

130 

131 if value is None: 

132 return [] 

133 elif isinstance(value, str): 

134 return [value] 

135 elif isinstance(value, abc.Iterable): 

136 return list(value) 

137 else: 

138 return [value] 

139 

140 

141def convert_string_to_list(string, separator=','): 

142 return [elem.strip() for elem in string.split(separator)] 

143 

144 

145def get_relationship_type(is_flexgroup): 

146 """Returns the snapmirror relationship type.""" 

147 return (EXTENDED_DATA_PROTECTION_TYPE if is_flexgroup 

148 else DATA_PROTECTION_TYPE) 

149 

150 

151def is_style_extended_flexgroup(style_extended): 

152 """Returns whether the style is extended type or not.""" 

153 return style_extended == FLEXGROUP_STYLE_EXTENDED 

154 

155 

156def parse_flexgroup_pool_config(config, cluster_aggr_set={}, check=False): 

157 """Returns the dict with the FlexGroup pools and if it is auto provisioned. 

158 

159 :param config: the configuration flexgroup list of dict. 

160 :param cluster_aggr_set: the set of aggregates in the cluster. 

161 :param check: should check the config is correct. 

162 """ 

163 

164 flexgroup_pools_map = {} 

165 aggr_list_used = [] 

166 for pool_dic in config: 

167 for pool_name, aggr_str in pool_dic.items(): 

168 aggr_name_list = aggr_str.split() 

169 

170 if not check: 

171 aggr_name_list.sort() 

172 flexgroup_pools_map[pool_name] = aggr_name_list 

173 continue 

174 

175 if pool_name in cluster_aggr_set: 

176 msg = _('The %s FlexGroup pool name is not valid, because ' 

177 'it is a cluster aggregate name. Ensure that the ' 

178 'configuration option netapp_flexgroup_pools is ' 

179 'set correctly.') 

180 raise exception.NetAppException(msg % pool_name) 

181 

182 aggr_name_set = set(aggr_name_list) 

183 if len(aggr_name_set) != len(aggr_name_list): 

184 msg = _('There is a repeated aggregate name in the ' 

185 'FlexGroup pool %s definition. Ensure that the ' 

186 'configuration option netapp_flexgroup_pools is ' 

187 'set correctly.') 

188 raise exception.NetAppException(msg % pool_name) 

189 

190 not_found_aggr = aggr_name_set - cluster_aggr_set 

191 if not_found_aggr: 

192 not_found_list = [str(s) for s in not_found_aggr] 

193 not_found_str = ", ".join(not_found_list) 

194 msg = _('There is an aggregate name in the FlexGroup pool ' 

195 '%(pool)s that is not in the cluster: %(aggr)s. ' 

196 'Ensure that the configuration option ' 

197 'netapp_flexgroup_pools is set correctly.') 

198 msg_args = {'pool': pool_name, 'aggr': not_found_str} 

199 raise exception.NetAppException(msg % msg_args) 

200 

201 aggr_name_list.sort() 

202 aggr_name_list_str = "".join(aggr_name_list) 

203 if aggr_name_list_str in aggr_list_used: 

204 msg = _('The FlexGroup pool %s is duplicated. Ensure that ' 

205 'the configuration option netapp_flexgroup_pools ' 

206 'is set correctly.') 

207 raise exception.NetAppException(msg % pool_name) 

208 

209 aggr_list_used.append(aggr_name_list_str) 

210 flexgroup_pools_map[pool_name] = aggr_name_list 

211 

212 return flexgroup_pools_map 

213 

214 

215def calculate_max_files(size, max_files_multiplier, max_files=None): 

216 """Returns the max_files as integer or None. 

217 

218 :param size: volume size in gb 

219 :param max_files_multiplier: config out of string extra spec 

220 :param max_files: pass max_files option 

221 """ 

222 if size is None or max_files_multiplier is None: 

223 return None 

224 

225 if max_files is not None: 

226 msg = _('Something went wrong: ' 

227 'validate_provisioning_options_for_share should have made ' 

228 'sure that max_files and max_files_multiplier cannot be set ' 

229 'at same time.') 

230 raise exception.NetAppException(msg) 

231 

232 # size_gb * units.Mi = size_kib 

233 # calculation based upon TR-4617 

234 max_files = int(size * units.Mi * float(max_files_multiplier) / 33.6925) 

235 

236 return min(max_files, ABSOLUTE_MAX_INODES) 

237 

238 

239class OpenStackInfo(object): 

240 """OS/distribution, release, and version. 

241 

242 NetApp uses these fields as content for EMS log entry. 

243 """ 

244 

245 PACKAGE_NAME = 'python3-manila' 

246 

247 def __init__(self): 

248 self._version = 'unknown version' 

249 self._release = 'unknown release' 

250 self._vendor = 'unknown vendor' 

251 self._platform = 'unknown platform' 

252 

253 def _update_version_from_version_string(self): 

254 try: 

255 self._version = version.version_info.version_string() 

256 except Exception: 

257 pass 

258 

259 def _update_release_from_release_string(self): 

260 try: 

261 self._release = version.version_info.release_string() 

262 except Exception: 

263 pass 

264 

265 def _update_platform(self): 

266 try: 

267 self._platform = platform.platform() 

268 except Exception: 

269 pass 

270 

271 @staticmethod 

272 def _get_version_info_version(): 

273 return version.version_info.version 

274 

275 @staticmethod 

276 def _get_version_info_release(): 

277 return version.version_info.release_string() 

278 

279 def _update_info_from_version_info(self): 

280 try: 

281 ver = self._get_version_info_version() 

282 if ver: 

283 self._version = ver 

284 except Exception: 

285 pass 

286 try: 

287 rel = self._get_version_info_release() 

288 if rel: 

289 self._release = rel 

290 except Exception: 

291 pass 

292 

293 # RDO, RHEL-OSP, Mirantis on Redhat, SUSE. 

294 def _update_info_from_rpm(self): 

295 LOG.debug('Trying rpm command.') 

296 try: 

297 out, err = putils.execute("rpm", "-q", "--queryformat", 

298 "'%{version}\t%{release}\t%{vendor}'", 

299 self.PACKAGE_NAME) 

300 if not out: 

301 LOG.info('No rpm info found for %(pkg)s package.', { 

302 'pkg': self.PACKAGE_NAME}) 

303 return False 

304 parts = out.split() 

305 self._version = parts[0] 

306 self._release = parts[1] 

307 self._vendor = ' '.join(parts[2::]) 

308 return True 

309 except Exception as e: 

310 LOG.info('Could not run rpm command: %(msg)s.', { 

311 'msg': e}) 

312 return False 

313 

314 # Ubuntu, Mirantis on Ubuntu. 

315 def _update_info_from_dpkg(self): 

316 LOG.debug('Trying dpkg-query command.') 

317 try: 

318 _vendor = None 

319 out, err = putils.execute("dpkg-query", "-W", "-f='${Version}'", 

320 self.PACKAGE_NAME) 

321 if not out: 

322 LOG.info( 

323 'No dpkg-query info found for %(pkg)s package.', { 

324 'pkg': self.PACKAGE_NAME}) 

325 return False 

326 # Debian format: [epoch:]upstream_version[-debian_revision] 

327 deb_version = out 

328 # In case epoch or revision is missing, copy entire string. 

329 _release = deb_version 

330 if ':' in deb_version: 330 ↛ 333line 330 didn't jump to line 333 because the condition on line 330 was always true

331 deb_epoch, upstream_version = deb_version.split(':') 

332 _release = upstream_version 

333 if '-' in deb_version: 333 ↛ 336line 333 didn't jump to line 336 because the condition on line 333 was always true

334 deb_revision = deb_version.split('-')[1] 

335 _vendor = deb_revision 

336 self._release = _release 

337 if _vendor: 337 ↛ 339line 337 didn't jump to line 339 because the condition on line 337 was always true

338 self._vendor = _vendor 

339 return True 

340 except Exception as e: 

341 LOG.info('Could not run dpkg-query command: %(msg)s.', { 

342 'msg': e}) 

343 return False 

344 

345 def _update_openstack_info(self): 

346 self._update_version_from_version_string() 

347 self._update_release_from_release_string() 

348 self._update_platform() 

349 # Some distributions override with more meaningful information. 

350 self._update_info_from_version_info() 

351 # See if we have still more targeted info from rpm or apt. 

352 found_package = self._update_info_from_rpm() 

353 if not found_package: 

354 self._update_info_from_dpkg() 

355 

356 def info(self): 

357 self._update_openstack_info() 

358 return '%(version)s|%(release)s|%(vendor)s|%(platform)s' % { 

359 'version': self._version, 'release': self._release, 

360 'vendor': self._vendor, 'platform': self._platform} 

361 

362 

363class DataCache(object): 

364 """DataCache class for caching NetApp information. 

365 

366 The cache validity is measured by a stop watch that is 

367 not thread-safe. 

368 """ 

369 

370 def __init__(self, duration): 

371 self._stop_watch = timeutils.StopWatch(duration) 

372 self._cached_data = None 

373 

374 def is_expired(self): 

375 return not self._stop_watch.has_started() or self._stop_watch.expired() 

376 

377 def get_data(self): 

378 return self._cached_data 

379 

380 def update_data(self, cached_data): 

381 if not self._stop_watch.has_started(): 

382 self._stop_watch.start() 

383 else: 

384 self._stop_watch.restart() 

385 

386 self._cached_data = cached_data