Coverage for manila/share/drivers/inspur/as13000/as13000_nas.py: 96%
497 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright 2018 Inspur Corp.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
16"""
17Share driver for Inspur AS13000
18"""
20import functools
21import json
22import re
23import requests
24import time
26from oslo_config import cfg
27from oslo_log import log as logging
28from oslo_utils import units
30from manila import exception
31from manila.i18n import _
32from manila.share import driver
33from manila.share import utils as share_utils
36inspur_as13000_opts = [
37 cfg.HostAddressOpt(
38 'as13000_nas_ip',
39 required=True,
40 help='IP address for the AS13000 storage.'),
41 cfg.PortOpt(
42 'as13000_nas_port',
43 default=8088,
44 help='Port number for the AS13000 storage.'),
45 cfg.StrOpt(
46 'as13000_nas_login',
47 required=True,
48 help='Username for the AS13000 storage'),
49 cfg.StrOpt(
50 'as13000_nas_password',
51 required=True,
52 secret=True,
53 help='Password for the AS13000 storage'),
54 cfg.ListOpt(
55 'as13000_share_pools',
56 required=True,
57 help='The Storage Pools Manila should use, a comma separated list'),
58 cfg.IntOpt(
59 'as13000_token_available_time',
60 default=3600,
61 help='The effective time of token validity in seconds.')
62]
64CONF = cfg.CONF
65CONF.register_opts(inspur_as13000_opts)
66LOG = logging.getLogger(__name__)
69def inspur_driver_debug_trace(f):
70 """Log the method entrance and exit including active backend name.
72 This should only be used on Share_Driver class methods. It depends on
73 having a 'self' argument that is a AS13000_Driver.
74 """
75 @functools.wraps(f)
76 def wrapper(*args, **kwargs):
77 driver = args[0]
78 cls_name = driver.__class__.__name__
79 method_name = "%(cls_name)s.%(method)s" % {"cls_name": cls_name,
80 "method": f.__name__}
81 backend_name = driver.configuration.share_backend_name
82 LOG.debug("[%(backend_name)s] Enter %(method_name)s",
83 {"method_name": method_name, "backend_name": backend_name})
84 result = f(*args, **kwargs)
85 LOG.debug("[%(backend_name)s] Leave %(method_name)s",
86 {"method_name": method_name, "backend_name": backend_name})
87 return result
89 return wrapper
92class RestAPIExecutor(object):
93 def __init__(self, hostname, port, username, password):
94 self._hostname = hostname
95 self._port = port
96 self._username = username
97 self._password = password
98 self._token_pool = []
99 self._token_size = 1
101 def logins(self):
102 """login the AS13000 and store the token in token_pool"""
103 times = self._token_size
104 while times > 0:
105 token = self.login()
106 self._token_pool.append(token)
107 times = times - 1
108 LOG.debug('Logged into the AS13000.')
110 def login(self):
111 """login in the AS13000 and return the token"""
112 method = 'security/token'
113 params = {'name': self._username, 'password': self._password}
114 token = self.send_rest_api(method=method, params=params,
115 request_type='post').get('token')
116 return token
118 def logout(self):
119 method = 'security/token'
120 self.send_rest_api(method=method, request_type='delete')
122 def refresh_token(self, force=False):
123 if force is True:
124 for i in range(self._token_size):
125 self._token_pool = []
126 token = self.login()
127 self._token_pool.append(token)
128 else:
129 for i in range(self._token_size):
130 self.logout()
131 token = self.login()
132 self._token_pool.append(token)
133 LOG.debug('Tokens have been refreshed.')
135 def send_rest_api(self, method, params=None, request_type='post'):
136 attempts = 3
137 msge = ''
138 while attempts > 0:
139 attempts -= 1
140 try:
141 return self.send_api(method, params, request_type)
142 except exception.NetworkException as e:
143 msge = str(e)
144 LOG.error(msge)
146 self.refresh_token(force=True)
147 time.sleep(1)
148 except exception.ShareBackendException as e:
149 msge = str(e)
150 break
152 msg = (_('Access RestAPI /rest/%(method)s by %(type)s failed,'
153 ' error: %(msge)s') % {'method': method,
154 'msge': msge,
155 'type': request_type})
156 LOG.error(msg)
157 raise exception.ShareBackendException(msg)
159 @staticmethod
160 def do_request(cmd, url, header, data):
161 LOG.debug('CMD: %(cmd)s, URL: %(url)s, DATA: %(data)s',
162 {'cmd': cmd, 'url': url, 'data': data})
163 if cmd == 'post':
164 req = requests.post(url,
165 data=data,
166 headers=header)
167 elif cmd == 'get':
168 req = requests.get(url,
169 data=data,
170 headers=header)
171 elif cmd == 'put':
172 req = requests.put(url,
173 data=data,
174 headers=header)
175 elif cmd == 'delete': 175 ↛ 180line 175 didn't jump to line 180 because the condition on line 175 was always true
176 req = requests.delete(url,
177 data=data,
178 headers=header)
179 else:
180 msg = (_('Unsupported cmd: %s') % cmd)
181 raise exception.ShareBackendException(msg)
183 response = req.json()
184 code = req.status_code
185 LOG.debug('CODE: %(code)s, RESPONSE: %(response)s',
186 {'code': code, 'response': response})
188 if code != 200: 188 ↛ 189line 188 didn't jump to line 189 because the condition on line 188 was never true
189 msg = (_('Code: %(code)s, URL: %(url)s, Message: %(msg)s')
190 % {'code': req.status_code,
191 'url': req.url,
192 'msg': req.text})
193 LOG.error(msg)
194 raise exception.NetworkException(msg)
196 return response
198 def send_api(self, method, params=None, request_type='post'):
199 if params:
200 params = json.dumps(params)
202 url = ('http://%(hostname)s:%(port)s/%(rest)s/%(method)s'
203 % {'hostname': self._hostname,
204 'port': self._port,
205 'rest': 'rest',
206 'method': method})
208 # header is not needed when the driver login the backend
209 if method == 'security/token':
210 # token won't be return to the token_pool
211 if request_type == 'delete':
212 header = {'X-Auth-Token': self._token_pool.pop(0)}
213 else:
214 header = None
215 else:
216 if len(self._token_pool) == 0: 216 ↛ 217line 216 didn't jump to line 217 because the condition on line 216 was never true
217 self.logins()
218 token = self._token_pool.pop(0)
219 header = {'X-Auth-Token': token}
220 self._token_pool.append(token)
222 response = self.do_request(request_type, url, header, params)
224 try:
225 code = response.get('code')
226 if code == 0:
227 if request_type == 'get':
228 data = response.get('data')
229 else:
230 if method == 'security/token':
231 data = response.get('data')
232 else:
233 data = response.get('message')
234 data = str(data).lower()
235 if hasattr(data, 'success'): 235 ↛ 236line 235 didn't jump to line 236 because the condition on line 235 was never true
236 return
237 elif code == 301:
238 msg = _('Token is expired')
239 LOG.error(msg)
240 raise exception.NetworkException(msg)
241 else:
242 message = response.get('message')
243 msg = (_('Unexpected RestAPI response: %(code)d %(msg)s') % {
244 'code': code, 'msg': message})
245 LOG.error(msg)
246 raise exception.ShareBackendException(msg)
247 except ValueError:
248 msg = _("Deal with response failed")
249 raise exception.ShareBackendException(msg)
251 return data
254class AS13000ShareDriver(driver.ShareDriver):
256 """AS13000 Share Driver
258 Version history:
259 V1.0.0: Initial version
260 Driver support:
261 share create/delete,
262 snapshot create/delete,
263 extend size,
264 create_share_from_snapshot,
265 update_access.
266 protocol: NFS/CIFS
268 """
270 VENDOR = 'INSPUR'
271 VERSION = '1.0.0'
272 PROTOCOL = 'NFS_CIFS'
274 def __init__(self, *args, **kwargs):
275 super(AS13000ShareDriver, self).__init__(False, *args, **kwargs)
276 self.configuration.append_config_values(inspur_as13000_opts)
277 self.hostname = self.configuration.as13000_nas_ip
278 self.port = self.configuration.as13000_nas_port
279 self.username = self.configuration.as13000_nas_login
280 self.password = self.configuration.as13000_nas_password
281 self.token_available_time = (self.configuration.
282 as13000_token_available_time)
283 self.pools = self.configuration.as13000_share_pools
284 # base dir detail contain the information which we will use
285 # when we create subdirectorys
286 self.base_dir_detail = None
287 self._token_time = 0
288 self.ips = []
289 self._rest = RestAPIExecutor(self.hostname, self.port,
290 self.username, self.password)
292 @inspur_driver_debug_trace
293 def do_setup(self, context):
294 # get access tokens
295 self._rest.logins()
296 self._token_time = time.time()
298 # Check the pool in conf exist in the backend
299 self._validate_pools_exist()
301 # get the base directory detail
302 self.base_dir_detail = self._get_directory_detail(self.pools[0])
304 # get all backend node ip
305 self.ips = self._get_nodes_ips()
307 @inspur_driver_debug_trace
308 def check_for_setup_error(self):
309 if self.base_dir_detail is None:
310 msg = _('The pool status is not right')
311 raise exception.ShareBackendException(msg)
313 if len(self.ips) == 0: 313 ↛ exitline 313 didn't return from function 'check_for_setup_error' because the condition on line 313 was always true
314 msg = _('All backend nodes are down')
315 raise exception.ShareBackendException(msg)
317 @inspur_driver_debug_trace
318 def create_share(self, context, share, share_server=None):
319 """Create a share."""
320 pool, name, size, proto = self._get_share_instance_pnsp(share)
322 # create directory first
323 share_path = self._create_directory(share_name=name,
324 pool_name=pool)
326 # then create nfs or cifs share
327 if proto == 'nfs':
328 self._create_nfs_share(share_path=share_path)
329 else:
330 self._create_cifs_share(share_name=name,
331 share_path=share_path)
333 # finally we set the quota of directory
334 self._set_directory_quota(share_path, size)
336 locations = self._get_location_path(name, share_path, proto)
337 LOG.debug('Create share: name:%(name)s'
338 ' protocol:%(proto)s,location: %(loc)s',
339 {'name': name, 'proto': proto, 'loc': locations})
340 return locations
342 @inspur_driver_debug_trace
343 def create_share_from_snapshot(self, context, share, snapshot,
344 share_server=None, parent_share=None):
345 """Create a share from snapshot."""
346 pool, name, size, proto = self._get_share_instance_pnsp(share)
348 # create directory first
349 share_path = self._create_directory(share_name=name,
350 pool_name=pool)
352 # as quota must be set when directory is empty
353 # then we set the quota of directory
354 self._set_directory_quota(share_path, size)
356 # and next clone snapshot to dest_path
357 self._clone_directory_to_dest(snapshot=snapshot, dest_path=share_path)
359 # finally create share
360 if proto == 'nfs':
361 self._create_nfs_share(share_path=share_path)
362 else:
363 self._create_cifs_share(share_name=name,
364 share_path=share_path)
366 locations = self._get_location_path(name, share_path, proto)
367 LOG.debug('Create share from snapshot:'
368 ' name:%(name)s protocol:%(proto)s,location: %(loc)s',
369 {'name': name, 'proto': proto, 'loc': locations})
370 return locations
372 @inspur_driver_debug_trace
373 def delete_share(self, context, share, share_server=None):
374 """Delete share."""
375 pool, name, _, proto = self._get_share_instance_pnsp(share)
376 share_path = self._generate_share_path(pool, name)
377 if proto == 'nfs':
378 share_backend = self._get_nfs_share(share_path)
379 if len(share_backend) == 0:
380 return
381 else:
382 self._delete_nfs_share(share_path)
383 else:
384 share_backend = self._get_cifs_share(name)
385 if len(share_backend) == 0:
386 return
387 else:
388 self._delete_cifs_share(name)
389 self._delete_directory(share_path)
390 LOG.debug('Delete share: %s', name)
392 @inspur_driver_debug_trace
393 def extend_share(self, share, new_size, share_server=None):
394 """extend share to new size"""
395 pool, name, size, proto = self._get_share_instance_pnsp(share)
396 share_path = self._generate_share_path(pool, name)
397 self._set_directory_quota(share_path, new_size)
398 LOG.debug('extend share %(name)s to new size %(size)s GB',
399 {'name': name, 'size': new_size})
401 @inspur_driver_debug_trace
402 def ensure_share(self, context, share, share_server=None):
403 """Ensure that share is exported."""
404 pool, name, size, proto = self._get_share_instance_pnsp(share)
405 share_path = self._generate_share_path(pool, name)
407 if proto == 'nfs':
408 share_backend = self._get_nfs_share(share_path)
409 elif proto == 'cifs':
410 share_backend = self._get_cifs_share(name)
411 else:
412 msg = (_('Invalid NAS protocol supplied: %s.') % proto)
413 LOG.error(msg)
414 raise exception.InvalidInput(msg)
416 if len(share_backend) == 0:
417 raise exception.ShareResourceNotFound(share_id=share['share_id'])
419 return self._get_location_path(name, share_path, proto)
421 @inspur_driver_debug_trace
422 def create_snapshot(self, context, snapshot, share_server=None):
423 """create snapshot of share"""
424 # !!! Attention the share property is a ShareInstance
425 share = snapshot['share']
426 pool, share_name, _, _ = self._get_share_instance_pnsp(share)
427 share_path = self._generate_share_path(pool, share_name)
429 snap_name = self._generate_snapshot_name(snapshot)
431 method = 'snapshot/directory'
432 request_type = 'post'
433 params = {'path': share_path, 'snapName': snap_name}
434 self._rest.send_rest_api(method=method,
435 params=params,
436 request_type=request_type)
437 LOG.debug('Create snapshot %(snap)s for share %(share)s',
438 {'snap': snap_name, 'share': share_name})
440 @inspur_driver_debug_trace
441 def delete_snapshot(self, context, snapshot, share_server=None):
442 """delete snapshot of share"""
443 # !!! Attention the share property is a ShareInstance
444 share = snapshot['share']
445 pool, share_name, _, _ = self._get_share_instance_pnsp(share)
446 share_path = self._generate_share_path(pool, share_name)
448 # if there are no snapshot exist, driver will return directly
449 snaps_backend = self._get_snapshots_from_share(share_path)
450 if len(snaps_backend) == 0:
451 return
453 snap_name = self._generate_snapshot_name(snapshot)
455 method = ('snapshot/directory?path=%s&snapName=%s'
456 % (share_path, snap_name))
457 request_type = 'delete'
458 self._rest.send_rest_api(method=method, request_type=request_type)
459 LOG.debug('Delete snapshot %(snap)s of share %(share)s',
460 {'snap': snap_name, 'share': share_name})
462 @staticmethod
463 def transfer_rule_to_client(proto, rule):
464 """transfer manila access rule to backend client"""
465 access_level = rule['access_level']
466 if proto == 'cifs' and access_level == 'rw':
467 access_level = 'rwx'
468 return dict(name=rule['access_to'],
469 type=(0 if proto == 'nfs' else 1),
470 authority=access_level)
472 @inspur_driver_debug_trace
473 def update_access(self, context, share, access_rules, add_rules,
474 delete_rules, update_rules, share_server=None):
475 """update access of share"""
476 pool, share_name, _, proto = self._get_share_instance_pnsp(share)
477 share_path = self._generate_share_path(pool, share_name)
479 method = 'file/share/%s' % proto
480 request_type = 'put'
481 params = {
482 'path': share_path,
483 'addedClientList': [],
484 'deletedClientList': [],
485 'editedClientList': []
486 }
488 if proto == 'nfs':
489 share_backend = self._get_nfs_share(share_path)
490 params['pathAuthority'] = share_backend['pathAuthority']
491 else:
492 params['name'] = share_name
494 if add_rules or delete_rules:
495 to_add_clients = [self.transfer_rule_to_client(proto, rule)
496 for rule in add_rules]
497 params['addedClientList'] = to_add_clients
498 to_del_clients = [self.transfer_rule_to_client(proto, rule)
499 for rule in delete_rules]
500 params['deletedClientList'] = to_del_clients
501 else:
502 access_clients = [self.transfer_rule_to_client(proto, rule)
503 for rule in access_rules]
504 params['addedClientList'] = access_clients
505 self._clear_access(share)
507 self._rest.send_rest_api(method=method,
508 params=params,
509 request_type=request_type)
510 LOG.debug('complete the update access work for share %s', share_name)
512 @inspur_driver_debug_trace
513 def _update_share_stats(self, data=None):
514 """update the backend stats including driver info and pools info"""
515 # Do a check of the token validity each time we update share stats,
516 # do a refresh if token already expires
517 time_difference = time.time() - self._token_time
518 if time_difference > self.token_available_time:
519 self._rest.refresh_token()
520 self._token_time = time.time()
521 LOG.debug('Token of Driver has been refreshed')
523 data = {
524 'vendor_name': self.VENDOR,
525 'driver_version': self.VERSION,
526 'storage_protocol': self.PROTOCOL,
527 'share_backend_name':
528 self.configuration.safe_get('share_backend_name'),
529 'snapshot_support': True,
530 'create_share_from_snapshot_support': True,
531 'pools': [self._get_pool_stats(pool) for pool in self.pools]
532 }
534 super(AS13000ShareDriver, self)._update_share_stats(data)
536 @inspur_driver_debug_trace
537 def _clear_access(self, share):
538 """clear all access of share"""
539 pool, share_name, size, proto = self._get_share_instance_pnsp(share)
540 share_path = self._generate_share_path(pool, share_name)
542 method = 'file/share/%s' % proto
543 request_type = 'put'
544 params = {
545 'path': share_path,
546 'addedClientList': [],
547 'deletedClientList': [],
548 'editedClientList': []
549 }
551 if proto == 'nfs':
552 share_backend = self._get_nfs_share(share_path)
553 params['deletedClientList'] = share_backend['clientList']
554 params['pathAuthority'] = share_backend['pathAuthority']
555 else:
556 share_backend = self._get_cifs_share(share_name)
557 params['deletedClientList'] = share_backend['userList']
558 params['name'] = share_name
560 self._rest.send_rest_api(method=method,
561 params=params,
562 request_type=request_type)
563 LOG.debug('Clear all the access of share %s', share_name)
565 @inspur_driver_debug_trace
566 def _validate_pools_exist(self):
567 """Check the pool in conf exist in the backend"""
568 available_pools = self._get_directory_list('/')
569 for pool in self.pools:
570 if pool not in available_pools:
571 msg = (_('Pool %s is not exist in backend storage.') % pool)
572 LOG.error(msg)
573 raise exception.InvalidInput(reason=msg)
575 @inspur_driver_debug_trace
576 def _get_directory_quota(self, path):
577 """get the quota of directory"""
578 method = 'file/quota/directory?path=/%s' % path
579 request_type = 'get'
580 data = self._rest.send_rest_api(method=method,
581 request_type=request_type)
582 quota = data.get('hardthreshold')
583 if quota is None:
584 # the method of '_update_share_stats' will check quota of pools.
585 # To avoid return NONE for pool info, so raise this exception
586 msg = (_(r'Quota of pool: /%s is not set, '
587 r'please set it in GUI of AS13000') % path)
588 LOG.error(msg)
589 raise exception.ShareBackendException(msg=msg)
591 hardunit = data.get('hardunit')
592 used_capacity = data.get('capacity')
593 used_capacity = (str(used_capacity)).upper()
594 used_capacity = self._unit_convert(used_capacity)
596 if hardunit == 1:
597 quota = quota * 1024
598 total_capacity = int(quota)
599 used_capacity = int(used_capacity)
600 return total_capacity, used_capacity
602 def _get_pool_stats(self, path):
603 """Get the stats of pools, such as capacity and other information."""
605 total_capacity, used_capacity = self._get_directory_quota(path)
606 free_capacity = total_capacity - used_capacity
608 pool = {
609 'pool_name': path,
610 'reserved_percentage':
611 self.configuration.reserved_share_percentage,
612 'reserved_snapshot_percentage':
613 self.configuration.reserved_share_from_snapshot_percentage
614 or self.configuration.reserved_share_percentage,
615 'reserved_share_extend_percentage':
616 self.configuration.reserved_share_extend_percentage
617 or self.configuration.reserved_share_percentage,
618 'max_over_subscription_ratio':
619 self.configuration.max_over_subscription_ratio,
620 'dedupe': False,
621 'compression': False,
622 'qos': False,
623 'thin_provisioning': True,
624 'total_capacity_gb': total_capacity,
625 'free_capacity_gb': free_capacity,
626 'allocated_capacity_gb': used_capacity,
627 'snapshot_support': True,
628 'create_share_from_snapshot_support': True
629 }
631 return pool
633 @inspur_driver_debug_trace
634 def _get_directory_list(self, path):
635 """Get all the directory list of target path"""
636 method = 'file/directory?path=%s' % path
637 request_type = 'get'
638 directory_list = self._rest.send_rest_api(method=method,
639 request_type=request_type)
640 dir_list = []
641 for directory in directory_list:
642 dir_list.append(directory['name'])
643 return dir_list
645 @inspur_driver_debug_trace
646 def _create_directory(self, share_name, pool_name):
647 """create a directory for share"""
649 method = 'file/directory'
650 request_type = 'post'
651 params = {'name': share_name,
652 'parentPath': self.base_dir_detail['path'],
653 'authorityInfo': self.base_dir_detail['authorityInfo'],
654 'dataProtection': self.base_dir_detail['dataProtection'],
655 'poolName': self.base_dir_detail['poolName']}
656 self._rest.send_rest_api(method=method,
657 params=params,
658 request_type=request_type)
660 return self._generate_share_path(pool_name, share_name)
662 @inspur_driver_debug_trace
663 def _delete_directory(self, share_path):
664 """delete the directory when delete share"""
665 method = 'file/directory?path=%s' % share_path
666 request_type = 'delete'
667 self._rest.send_rest_api(method=method, request_type=request_type)
669 @inspur_driver_debug_trace
670 def _set_directory_quota(self, share_path, quota):
671 """set directory quota for share"""
672 method = 'file/quota/directory'
673 request_type = 'put'
674 params = {'path': share_path, 'hardthreshold': quota, 'hardunit': 2}
675 self._rest.send_rest_api(method=method,
676 params=params,
677 request_type=request_type)
679 @inspur_driver_debug_trace
680 def _create_nfs_share(self, share_path):
681 """create a NFS share"""
682 method = 'file/share/nfs'
683 request_type = 'post'
684 params = {'path': share_path, 'pathAuthority': 'rw', 'client': []}
685 self._rest.send_rest_api(method=method,
686 params=params,
687 request_type=request_type)
689 @inspur_driver_debug_trace
690 def _delete_nfs_share(self, share_path):
691 """Delete the NFS share"""
692 method = 'file/share/nfs?path=%s' % share_path
693 request_type = 'delete'
694 self._rest.send_rest_api(method=method, request_type=request_type)
696 @inspur_driver_debug_trace
697 def _get_nfs_share(self, share_path):
698 """Get the nfs share in backend"""
699 method = 'file/share/nfs?path=%s' % share_path
700 request_type = 'get'
701 share_backend = self._rest.send_rest_api(method=method,
702 request_type=request_type)
703 return share_backend
705 @inspur_driver_debug_trace
706 def _create_cifs_share(self, share_name, share_path):
707 """Create a CIFS share."""
708 method = 'file/share/cifs'
709 request_type = 'post'
710 params = {'path': share_path,
711 'name': share_name,
712 'userlist': []}
713 self._rest.send_rest_api(method=method,
714 params=params,
715 request_type=request_type)
717 @inspur_driver_debug_trace
718 def _delete_cifs_share(self, share_name):
719 """Delete the CIFS share."""
720 method = 'file/share/cifs?name=%s' % share_name
721 request_type = 'delete'
722 self._rest.send_rest_api(method=method, request_type=request_type)
724 @inspur_driver_debug_trace
725 def _get_cifs_share(self, share_name):
726 """Get the CIFS share in backend"""
727 method = 'file/share/cifs?name=%s' % share_name
728 request_type = 'get'
729 share_backend = self._rest.send_rest_api(method=method,
730 request_type=request_type)
731 return share_backend
733 @inspur_driver_debug_trace
734 def _clone_directory_to_dest(self, snapshot, dest_path):
735 """Clone the directory to the new directory"""
736 # get the origin share name of the snapshot
737 share_instance = snapshot['share_instance']
738 pool, name, _, _ = self._get_share_instance_pnsp(share_instance)
739 share_path = self._generate_share_path(pool, name)
741 # get the snapshot instance name
742 snap_name = self._generate_snapshot_name(snapshot)
744 method = 'snapshot/directory/clone'
745 request_type = 'post'
746 params = {'path': share_path,
747 'snapName': snap_name,
748 'destPath': dest_path}
749 self._rest.send_rest_api(method=method,
750 params=params,
751 request_type=request_type)
752 LOG.debug('Clone Path: %(path)s Snapshot: %(snap)s to Path %(dest)s',
753 {'path': share_path, 'snap': snap_name, 'dest': dest_path})
755 @inspur_driver_debug_trace
756 def _get_snapshots_from_share(self, path):
757 """get all the snapshot of share"""
758 method = 'snapshot/directory?path=%s' % path
759 request_type = 'get'
760 snaps = self._rest.send_rest_api(method=method,
761 request_type=request_type)
762 return snaps
764 @inspur_driver_debug_trace
765 def _get_location_path(self, share_name, share_path, share_proto):
766 """return all the location of all nodes"""
767 if share_proto == 'nfs':
768 location = [
769 {'path': r'%(ip)s:%(share_path)s'
770 % {'ip': ip, 'share_path': share_path}}
771 for ip in self.ips]
772 else:
773 location = [
774 {'path': r'\\%(ip)s\%(share_name)s'
775 % {'ip': ip, 'share_name': share_name}}
776 for ip in self.ips]
778 return location
780 def _get_nodes_virtual_ips(self):
781 """Get the virtual ip list of the node"""
782 method = 'ctdb/set'
783 request_type = 'get'
784 ctdb_set = self._rest.send_rest_api(method=method,
785 request_type=request_type)
786 virtual_ips = []
787 for vip in ctdb_set['virtualIpList']:
788 ip = vip['ip'].split('/')[0]
789 virtual_ips.append(ip)
790 return virtual_ips
792 def _get_nodes_physical_ips(self):
793 """Get the physical ip of all the backend nodes"""
794 method = 'cluster/node/cache'
795 request_type = 'get'
796 cached_nodes = self._rest.send_rest_api(method=method,
797 request_type=request_type)
798 node_ips = []
799 for node in cached_nodes:
800 if node['runningStatus'] == 1 and node['healthStatus'] == 1:
801 node_ips.append(node['nodeIp'])
803 return node_ips
805 def _get_nodes_ips(self):
806 """Return both the physical ip and virtual ip"""
807 virtual_ips = self._get_nodes_virtual_ips()
808 physical_ips = self._get_nodes_physical_ips()
810 return virtual_ips + physical_ips
812 def _get_share_instance_pnsp(self, share_instance):
813 """Get pool, name, size, proto information of a share instance.
815 AS13000 require all the names can only consist of letters,numbers,
816 and undercores,and must begin with a letter.
817 Also the length of name must less than 32 character.
818 The driver will use the ID as the name in backend,
819 add 'share_' to the beginning,and convert '-' to '_'
820 """
821 pool = share_utils.extract_host(share_instance['host'], level='pool')
822 name = self._generate_share_name(share_instance)
823 # a share instance may not contain size attr.
824 try:
825 size = share_instance['size']
826 except AttributeError:
827 size = None
829 # a share instance may not contain proto attr.
830 try:
831 proto = share_instance['share_proto'].lower()
832 except AttributeError:
833 proto = None
835 LOG.debug("Pool %s, Name: %s, Size: %s, Protocol: %s",
836 pool, name, size, proto)
838 return pool, name, size, proto
840 def _unit_convert(self, capacity):
841 """Convert all units to GB"""
842 capacity = str(capacity)
843 capacity = capacity.upper()
844 try:
845 unit_of_used = re.findall(r'[A-Z]', capacity)
846 unit_of_used = ''.join(unit_of_used)
847 except BaseException:
848 unit_of_used = ''
849 capacity = capacity.replace(unit_of_used, '')
850 capacity = float(capacity.replace(unit_of_used, ''))
851 if unit_of_used in ['B', '']:
852 capacity = capacity / units.Gi
853 elif unit_of_used in ['K', 'KB']:
854 capacity = capacity / units.Mi
855 elif unit_of_used in ['M', 'MB']:
856 capacity = capacity / units.Ki
857 elif unit_of_used in ['G', 'GB']:
858 capacity = capacity
859 elif unit_of_used in ['T', 'TB']: 859 ↛ 861line 859 didn't jump to line 861 because the condition on line 859 was always true
860 capacity = capacity * units.Ki
861 elif unit_of_used in ['E', 'EB']:
862 capacity = capacity * units.Mi
864 capacity = '%.0f' % capacity
865 return float(capacity)
867 def _format_name(self, name):
868 """format name to meet the backend requirements"""
869 name = name[0:32]
870 name = name.replace('-', '_')
871 return name
873 def _generate_share_name(self, share_instance):
874 share_name = 'share_%s' % share_instance['id']
875 return self._format_name(share_name)
877 def _generate_snapshot_name(self, snapshot_instance):
878 snap_name = 'snap_%s' % snapshot_instance['id']
879 return self._format_name(snap_name)
881 @staticmethod
882 def _generate_share_path(pool, share_name):
883 return r'/%s/%s' % (pool, share_name)
885 def _get_directory_detail(self, directory):
886 method = 'file/directory/detail?path=/%s' % directory
887 request_type = 'get'
888 details = self._rest.send_rest_api(method=method,
889 request_type=request_type)
890 return details[0]