Browse Source

Manila share driver for Inspur AS13000 series.

Features that Inspur AS13000 Driver support:
share create/delete,
snapshot create/delete,
extend size,
create_share_from_snapshot,
update_access.
protocol: nfs/cifs
ThirdPartySystems: INSPUR CI

Change-Id: If0e1134e80f799186bb7cd057ff0f2d713f39a06
Implements: Blueprint inspur-as13000-manila-driver
changes/54/550454/9
inspur-storage 3 years ago
committed by wang yong
parent
commit
d40987cf7d
  1. 8
      doc/source/admin/share_back_ends_feature_support_mapping.rst
  2. 2
      manila/opts.py
  3. 0
      manila/share/drivers/inspur/__init__.py
  4. 0
      manila/share/drivers/inspur/as13000/__init__.py
  5. 883
      manila/share/drivers/inspur/as13000/as13000_nas.py
  6. 5
      manila/tests/conf_fixture.py
  7. 0
      manila/tests/share/drivers/inspur/__init__.py
  8. 0
      manila/tests/share/drivers/inspur/as13000/__init__.py
  9. 1196
      manila/tests/share/drivers/inspur/as13000/test_as13000_nas.py
  10. 6
      releasenotes/notes/inspur-as13000-driver-41f6b7caea82e46e.yaml

8
doc/source/admin/share_back_ends_feature_support_mapping.rst

@ -67,6 +67,8 @@ Mapping of share drivers and share features support
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| INFINIDAT | Q | \- | Q | \- | Q | Q | \- | Q | Q |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| INSPUR | R | \- | R | \- | R | R | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| LVM | M | \- | M | \- | M | M | \- | O | O |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| Quobyte | K | \- | M | M | \- | \- | \- | \- | \- |
@ -136,6 +138,8 @@ Mapping of share drivers and share access rules support
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
| INFINIDAT | NFS (Q) | \- | \- | \- | \- | NFS (Q) | \- | \- | \- | \- |
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
| INSPUR | NFS (R) | \- | CIFS (R) | \- | \- | NFS (R) | \- | CIFS (R) | \- | \- |
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
| Oracle ZFSSA | NFS,CIFS(K) | \- | \- | \- | \- | \- | \- | \- | \- | \- |
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
| CephFS | NFS (P) | \- | \- | \- | CEPHFS (M) | NFS (P) | \- | \- | \- | CEPHFS (N) |
@ -197,6 +201,8 @@ Mapping of share drivers and security services support
+----------------------------------------+------------------+-----------------+------------------+
| INFINIDAT | \- | \- | \- |
+----------------------------------------+------------------+-----------------+------------------+
| INSPUR | \- | \- | \- |
+----------------------------------------+------------------+-----------------+------------------+
| Oracle ZFSSA | \- | \- | \- |
+----------------------------------------+------------------+-----------------+------------------+
| CephFS | \- | \- | \- |
@ -274,6 +280,8 @@ More information: :ref:`capabilities_and_extra_specs`
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+
| QNAP | \- | O | Q | Q | O | Q | \- | O | \- | \- | P | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+
| INSPUR | \- | R | \- | \- | R | \- | \- | R | \- | \- | R | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+
.. note::

2
manila/opts.py

@ -72,6 +72,7 @@ import manila.share.drivers.hpe.hpe_3par_driver
import manila.share.drivers.huawei.huawei_nas
import manila.share.drivers.ibm.gpfs
import manila.share.drivers.infinidat.infinibox
import manila.share.drivers.inspur.as13000.as13000_nas
import manila.share.drivers.lvm
import manila.share.drivers.maprfs.maprfs_native
import manila.share.drivers.netapp.options
@ -157,6 +158,7 @@ _global_opt_lists = [
manila.share.drivers.infinidat.infinibox.infinidat_auth_opts,
manila.share.drivers.infinidat.infinibox.infinidat_connection_opts,
manila.share.drivers.infinidat.infinibox.infinidat_general_opts,
manila.share.drivers.inspur.as13000.as13000_nas.inspur_as13000_opts,
manila.share.drivers.maprfs.maprfs_native.maprfs_native_share_opts,
manila.share.drivers.lvm.share_opts,
manila.share.drivers.netapp.options.netapp_proxy_opts,

0
manila/share/drivers/inspur/__init__.py

0
manila/share/drivers/inspur/as13000/__init__.py

883
manila/share/drivers/inspur/as13000/as13000_nas.py

@ -0,0 +1,883 @@
# Copyright 2018 Inspur Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Share driver for Inspur AS13000
"""
import eventlet
import functools
import json
import re
import requests
import six
import time
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import units
from manila import exception
from manila.i18n import _
from manila.share import driver
from manila.share import utils as share_utils
inspur_as13000_opts = [
cfg.HostAddressOpt(
'as13000_nas_ip',
required=True,
help='IP address for the AS13000 storage.'),
cfg.PortOpt(
'as13000_nas_port',
default=8088,
help='Port number for the AS13000 storage.'),
cfg.StrOpt(
'as13000_nas_login',
required=True,
help='Username for the AS13000 storage'),
cfg.StrOpt(
'as13000_nas_password',
required=True,
secret=True,
help='Password for the AS13000 storage'),
cfg.ListOpt(
'as13000_share_pools',
required=True,
help='The Storage Pools Manila should use, a comma separated list'),
cfg.IntOpt(
'as13000_token_available_time',
default=3600,
help='The effective time of token validity in seconds.')
]
CONF = cfg.CONF
CONF.register_opts(inspur_as13000_opts)
LOG = logging.getLogger(__name__)
def inspur_driver_debug_trace(f):
"""Log the method entrance and exit including active backend name.
This should only be used on Share_Driver class methods. It depends on
having a 'self' argument that is a AS13000_Driver.
"""
@functools.wraps(f)
def wrapper(*args, **kwargs):
driver = args[0]
cls_name = driver.__class__.__name__
method_name = "%(cls_name)s.%(method)s" % {"cls_name": cls_name,
"method": f.__name__}
backend_name = driver.configuration.share_backend_name
LOG.debug("[%(backend_name)s] Enter %(method_name)s",
{"method_name": method_name, "backend_name": backend_name})
result = f(*args, **kwargs)
LOG.debug("[%(backend_name)s] Leave %(method_name)s",
{"method_name": method_name, "backend_name": backend_name})
return result
return wrapper
class RestAPIExecutor(object):
def __init__(self, hostname, port, username, password):
self._hostname = hostname
self._port = port
self._username = username
self._password = password
self._token_pool = []
self._token_size = 1
def logins(self):
"""login the AS13000 and store the token in token_pool"""
times = self._token_size
while times > 0:
token = self.login()
self._token_pool.append(token)
times = times - 1
LOG.debug('Logged into the AS13000.')
def login(self):
"""login in the AS13000 and return the token"""
method = 'security/token'
params = {'name': self._username, 'password': self._password}
token = self.send_rest_api(method=method, params=params,
request_type='post').get('token')
return token
def logout(self):
method = 'security/token'
self.send_rest_api(method=method, request_type='delete')
def refresh_token(self, force=False):
if force is True:
for i in range(self._token_size):
self._token_pool = []
token = self.login()
self._token_pool.append(token)
else:
for i in range(self._token_size):
self.logout()
token = self.login()
self._token_pool.append(token)
LOG.debug('Tokens have been refreshed.')
def send_rest_api(self, method, params=None, request_type='post'):
attempts = 3
msge = ''
while attempts > 0:
attempts -= 1
try:
return self.send_api(method, params, request_type)
except exception.NetworkException as e:
msge = six.text_type(e)
LOG.error(msge)
self.refresh_token(force=True)
eventlet.sleep(1)
except exception.ShareBackendException as e:
msge = six.text_type(e)
break
msg = (_('Access RestAPI /rest/%(method)s by %(type)s failed,'
' error: %(msge)s') % {'method': method,
'msge': msge,
'type': request_type})
LOG.error(msg)
raise exception.ShareBackendException(msg)
@staticmethod
def do_request(cmd, url, header, data):
LOG.debug('CMD: %(cmd)s, URL: %(url)s, DATA: %(data)s',
{'cmd': cmd, 'url': url, 'data': data})
if cmd == 'post':
req = requests.post(url,
data=data,
headers=header)
elif cmd == 'get':
req = requests.get(url,
data=data,
headers=header)
elif cmd == 'put':
req = requests.put(url,
data=data,
headers=header)
elif cmd == 'delete':
req = requests.delete(url,
data=data,
headers=header)
else:
msg = (_('Unsupported cmd: %s') % cmd)
raise exception.ShareBackendException(msg)
response = req.json()
code = req.status_code
LOG.debug('CODE: %(code)s, RESPONSE: %(response)s',
{'code': code, 'response': response})
if code != 200:
msg = (_('Code: %(code)s, URL: %(url)s, Message: %(msg)s')
% {'code': req.status_code,
'url': req.url,
'msg': req.text})
LOG.error(msg)
raise exception.NetworkException(msg)
return response
def send_api(self, method, params=None, request_type='post'):
if params:
params = json.dumps(params)
url = ('http://%(hostname)s:%(port)s/%(rest)s/%(method)s'
% {'hostname': self._hostname,
'port': self._port,
'rest': 'rest',
'method': method})
# header is not needed when the driver login the backend
if method == 'security/token':
# token won't be return to the token_pool
if request_type == 'delete':
header = {'X-Auth-Token': self._token_pool.pop(0)}
else:
header = None
else:
if len(self._token_pool) == 0:
self.logins()
token = self._token_pool.pop(0)
header = {'X-Auth-Token': token}
self._token_pool.append(token)
response = self.do_request(request_type, url, header, params)
try:
code = response.get('code')
if code == 0:
if request_type == 'get':
data = response.get('data')
else:
if method == 'security/token':
data = response.get('data')
else:
data = response.get('message')
data = str(data).lower()
if hasattr(data, 'success'):
return
elif code == 301:
msg = _('Token is expired')
LOG.error(msg)
raise exception.NetworkException(msg)
else:
message = response.get('message')
msg = (_('Unexpected RestAPI response: %(code)d %(msg)s') % {
'code': code, 'msg': message})
LOG.error(msg)
raise exception.ShareBackendException(msg)
except ValueError:
msg = _("Deal with response failed")
raise exception.ShareBackendException(msg)
return data
class AS13000ShareDriver(driver.ShareDriver):
"""AS13000 Share Driver
Version history:
V1.0.0: Initial version
Driver support:
share create/delete,
snapshot create/delete,
extend size,
create_share_from_snapshot,
update_access.
protocol: NFS/CIFS
"""
VENDOR = 'INSPUR'
VERSION = '1.0.0'
PROTOCOL = 'NFS_CIFS'
def __init__(self, *args, **kwargs):
super(AS13000ShareDriver, self).__init__(False, *args, **kwargs)
self.configuration.append_config_values(inspur_as13000_opts)
self.hostname = self.configuration.as13000_nas_ip
self.port = self.configuration.as13000_nas_port
self.username = self.configuration.as13000_nas_login
self.password = self.configuration.as13000_nas_password
self.token_available_time = (self.configuration.
as13000_token_available_time)
self.pools = self.configuration.as13000_share_pools
# base dir detail contain the information which we will use
# when we create subdirectorys
self.base_dir_detail = None
self._token_time = 0
self.ips = []
self._rest = RestAPIExecutor(self.hostname, self.port,
self.username, self.password)
@inspur_driver_debug_trace
def do_setup(self, context):
# get access tokens
self._rest.logins()
self._token_time = time.time()
# Check the pool in conf exist in the backend
self._validate_pools_exist()
# get the base directory detail
self.base_dir_detail = self._get_directory_detail(self.pools[0])
# get all backend node ip
self.ips = self._get_nodes_ips()
@inspur_driver_debug_trace
def check_for_setup_error(self):
if self.base_dir_detail is None:
msg = _('The pool status is not right')
raise exception.ShareBackendException(msg)
if len(self.ips) == 0:
msg = _('All backend nodes are down')
raise exception.ShareBackendException(msg)
@inspur_driver_debug_trace
def create_share(self, context, share, share_server=None):
"""Create a share."""
pool, name, size, proto = self._get_share_instance_pnsp(share)
# create directory first
share_path = self._create_directory(share_name=name,
pool_name=pool)
# then create nfs or cifs share
if proto == 'nfs':
self._create_nfs_share(share_path=share_path)
else:
self._create_cifs_share(share_name=name,
share_path=share_path)
# finally we set the quota of directory
self._set_directory_quota(share_path, size)
locations = self._get_location_path(name, share_path, proto)
LOG.debug('Create share: name:%(name)s'
' protocol:%(proto)s,location: %(loc)s',
{'name': name, 'proto': proto, 'loc': locations})
return locations
@inspur_driver_debug_trace
def create_share_from_snapshot(self, context, share, snapshot,
share_server=None):
"""Create a share from snapshot."""
pool, name, size, proto = self._get_share_instance_pnsp(share)
# create directory first
share_path = self._create_directory(share_name=name,
pool_name=pool)
# as quota must be set when directory is empty
# then we set the quota of directory
self._set_directory_quota(share_path, size)
# and next clone snapshot to dest_path
self._clone_directory_to_dest(snapshot=snapshot, dest_path=share_path)
# finally create share
if proto == 'nfs':
self._create_nfs_share(share_path=share_path)
else:
self._create_cifs_share(share_name=name,
share_path=share_path)
locations = self._get_location_path(name, share_path, proto)
LOG.debug('Create share from snapshot:'
' name:%(name)s protocol:%(proto)s,location: %(loc)s',
{'name': name, 'proto': proto, 'loc': locations})
return locations
@inspur_driver_debug_trace
def delete_share(self, context, share, share_server=None):
"""Delete share."""
pool, name, _, proto = self._get_share_instance_pnsp(share)
share_path = self._generate_share_path(pool, name)
if proto == 'nfs':
share_backend = self._get_nfs_share(share_path)
if len(share_backend) == 0:
return
else:
self._delete_nfs_share(share_path)
else:
share_backend = self._get_cifs_share(name)
if len(share_backend) == 0:
return
else:
self._delete_cifs_share(name)
self._delete_directory(share_path)
LOG.debug('Delete share: %s', name)
@inspur_driver_debug_trace
def extend_share(self, share, new_size, share_server=None):
"""extend share to new size"""
pool, name, size, proto = self._get_share_instance_pnsp(share)
share_path = self._generate_share_path(pool, name)
self._set_directory_quota(share_path, new_size)
LOG.debug('extend share %(name)s to new size %(size)s GB',
{'name': name, 'size': new_size})
@inspur_driver_debug_trace
def ensure_share(self, context, share, share_server=None):
"""Ensure that share is exported."""
pool, name, size, proto = self._get_share_instance_pnsp(share)
share_path = self._generate_share_path(pool, name)
if proto == 'nfs':
share_backend = self._get_nfs_share(share_path)
elif proto == 'cifs':
share_backend = self._get_cifs_share(name)
else:
msg = (_('Invalid NAS protocol supplied: %s.') % proto)
LOG.error(msg)
raise exception.InvalidInput(msg)
if len(share_backend) == 0:
raise exception.ShareResourceNotFound(share_id=share['share_id'])
return self._get_location_path(name, share_path, proto)
@inspur_driver_debug_trace
def create_snapshot(self, context, snapshot, share_server=None):
"""create snapshot of share"""
# !!! Attention the share property is a ShareInstance
share = snapshot['share']
pool, share_name, _, _ = self._get_share_instance_pnsp(share)
share_path = self._generate_share_path(pool, share_name)
snap_name = self._generate_snapshot_name(snapshot)
method = 'snapshot/directory'
request_type = 'post'
params = {'path': share_path, 'snapName': snap_name}
self._rest.send_rest_api(method=method,
params=params,
request_type=request_type)
LOG.debug('Create snapshot %(snap)s for share %(share)s',
{'snap': snap_name, 'share': share_name})
@inspur_driver_debug_trace
def delete_snapshot(self, context, snapshot, share_server=None):
"""delete snapshot of share"""
# !!! Attention the share property is a ShareInstance
share = snapshot['share']
pool, share_name, _, _ = self._get_share_instance_pnsp(share)
share_path = self._generate_share_path(pool, share_name)
# if there are no snapshot exist, driver will return directly
snaps_backend = self._get_snapshots_from_share(share_path)
if len(snaps_backend) == 0:
return
snap_name = self._generate_snapshot_name(snapshot)
method = ('snapshot/directory?path=%s&snapName=%s'
% (share_path, snap_name))
request_type = 'delete'
self._rest.send_rest_api(method=method, request_type=request_type)
LOG.debug('Delete snapshot %(snap)s of share %(share)s',
{'snap': snap_name, 'share': share_name})
@staticmethod
def transfer_rule_to_client(proto, rule):
"""transfer manila access rule to backend client"""
return dict(name=rule['access_to'],
type=(0 if proto == 'nfs' else 1),
authority=rule['access_level'])
@inspur_driver_debug_trace
def update_access(self, context, share, access_rules, add_rules,
delete_rules, share_server=None):
"""update access of share"""
pool, share_name, _, proto = self._get_share_instance_pnsp(share)
share_path = self._generate_share_path(pool, share_name)
method = 'file/share/%s' % proto
request_type = 'put'
params = {
'path': share_path,
'addedClientList': [],
'deletedClientList': [],
'editedClientList': []
}
if proto == 'nfs':
share_backend = self._get_nfs_share(share_path)
params['pathAuthority'] = share_backend['pathAuthority']
else:
params['name'] = share_name
if add_rules or delete_rules:
to_add_clients = [self.transfer_rule_to_client(proto, rule)
for rule in add_rules]
params['addedClientList'] = to_add_clients
to_del_clients = [self.transfer_rule_to_client(proto, rule)
for rule in delete_rules]
params['deletedClientList'] = to_del_clients
else:
access_clients = [self.transfer_rule_to_client(proto, rule)
for rule in access_rules]
params['addedClientList'] = access_clients
self._clear_access(share)
self._rest.send_rest_api(method=method,
params=params,
request_type=request_type)
LOG.debug('complete the update access work for share %s', share_name)
@inspur_driver_debug_trace
def _update_share_stats(self, data=None):
"""update the backend stats including driver info and pools info"""
# Do a check of the token validity each time we update share stats,
# do a refresh if token already expires
time_difference = time.time() - self._token_time
if time_difference > self.token_available_time:
self._rest.refresh_token()
self._token_time = time.time()
LOG.debug('Token of Driver has been refreshed')
data = {
'vendor_name': self.VENDOR,
'driver_version': self.VERSION,
'storage_protocol': self.PROTOCOL,
'share_backend_name':
self.configuration.safe_get('share_backend_name'),
'snapshot_support': True,
'create_share_from_snapshot_support': True,
'pools': [self._get_pool_stats(pool) for pool in self.pools]
}
super(AS13000ShareDriver, self)._update_share_stats(data)
@inspur_driver_debug_trace
def _clear_access(self, share):
"""clear all access of share"""
pool, share_name, size, proto = self._get_share_instance_pnsp(share)
share_path = self._generate_share_path(pool, share_name)
method = 'file/share/%s' % proto
request_type = 'put'
params = {
'path': share_path,
'addedClientList': [],
'deletedClientList': [],
'editedClientList': []
}
if proto == 'nfs':
share_backend = self._get_nfs_share(share_path)
params['deletedClientList'] = share_backend['clientList']
params['pathAuthority'] = share_backend['pathAuthority']
else:
share_backend = self._get_cifs_share(share_name)
params['deletedClientList'] = share_backend['userList']
params['name'] = share_name
self._rest.send_rest_api(method=method,
params=params,
request_type=request_type)
LOG.debug('Clear all the access of share %s', share_name)
@inspur_driver_debug_trace
def _validate_pools_exist(self):
"""Check the pool in conf exist in the backend"""
available_pools = self._get_directory_list('/')
for pool in self.pools:
if pool not in available_pools:
msg = (_('Pool %s is not exist in backend storage.') % pool)
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
@inspur_driver_debug_trace
def _get_directory_quota(self, path):
"""get the quota of directory"""
method = 'file/quota/directory?path=/%s' % path
request_type = 'get'
data = self._rest.send_rest_api(method=method,
request_type=request_type)
quota = data.get('hardthreshold')
if quota is None:
# the method of '_update_share_stats' will check quota of pools.
# To avoid return NONE for pool info, so raise this exception
msg = (_(r'Quota of pool: /%s is not set, '
r'please set it in GUI of AS13000') % path)
LOG.error(msg)
raise exception.ShareBackendException(msg=msg)
hardunit = data.get('hardunit')
used_capacity = data.get('capacity')
used_capacity = (str(used_capacity)).upper()
used_capacity = self._unit_convert(used_capacity)
if hardunit == 1:
quota = quota * 1024
total_capacity = int(quota)
used_capacity = int(used_capacity)
return total_capacity, used_capacity
def _get_pool_stats(self, path):
"""Get the stats of pools, such as capacity and other information."""
total_capacity, used_capacity = self._get_directory_quota(path)
free_capacity = total_capacity - used_capacity
pool = {
'pool_name': path,
'reserved_percentage':
self.configuration.reserved_share_percentage,
'max_over_subscription_ratio':
self.configuration.max_over_subscription_ratio,
'dedupe': False,
'compression': False,
'qos': False,
'thin_provisioning': True,
'total_capacity_gb': total_capacity,
'free_capacity_gb': free_capacity,
'allocated_capacity_gb': used_capacity,
'snapshot_support': True,
'create_share_from_snapshot_support': True
}
return pool
@inspur_driver_debug_trace
def _get_directory_list(self, path):
"""Get all the directory list of target path"""
method = 'file/directory?path=%s' % path
request_type = 'get'
directory_list = self._rest.send_rest_api(method=method,
request_type=request_type)
dir_list = []
for directory in directory_list:
dir_list.append(directory['name'])
return dir_list
@inspur_driver_debug_trace
def _create_directory(self, share_name, pool_name):
"""create a directory for share"""
method = 'file/directory'
request_type = 'post'
params = {'name': share_name,
'parentPath': self.base_dir_detail['path'],
'authorityInfo': self.base_dir_detail['authorityInfo'],
'dataProtection': self.base_dir_detail['dataProtection'],
'poolName': self.base_dir_detail['poolName']}
self._rest.send_rest_api(method=method,
params=params,
request_type=request_type)
return self._generate_share_path(pool_name, share_name)
@inspur_driver_debug_trace
def _delete_directory(self, share_path):
"""delete the directory when delete share"""
method = 'file/directory?path=%s' % share_path
request_type = 'delete'
self._rest.send_rest_api(method=method, request_type=request_type)
@inspur_driver_debug_trace
def _set_directory_quota(self, share_path, quota):
"""set directory quota for share"""
method = 'file/quota/directory'
request_type = 'put'
params = {'path': share_path, 'hardthreshold': quota, 'hardunit': 2}
self._rest.send_rest_api(method=method,
params=params,
request_type=request_type)
@inspur_driver_debug_trace
def _create_nfs_share(self, share_path):
"""create a NFS share"""
method = 'file/share/nfs'
request_type = 'post'
params = {'path': share_path, 'pathAuthority': 'rw', 'client': []}
self._rest.send_rest_api(method=method,
params=params,
request_type=request_type)
@inspur_driver_debug_trace
def _delete_nfs_share(self, share_path):
"""Delete the NFS share"""
method = 'file/share/nfs?path=%s' % share_path
request_type = 'delete'
self._rest.send_rest_api(method=method, request_type=request_type)
@inspur_driver_debug_trace
def _get_nfs_share(self, share_path):
"""Get the nfs share in backend"""
method = 'file/share/nfs?path=%s' % share_path
request_type = 'get'
share_backend = self._rest.send_rest_api(method=method,
request_type=request_type)
return share_backend
@inspur_driver_debug_trace
def _create_cifs_share(self, share_name, share_path):
"""Create a CIFS share."""
method = 'file/share/cifs'
request_type = 'post'
params = {'path': share_path,
'name': share_name,
'userlist': []}
self._rest.send_rest_api(method=method,
params=params,
request_type=request_type)
@inspur_driver_debug_trace
def _delete_cifs_share(self, share_name):
"""Delete the CIFS share."""
method = 'file/share/cifs?name=%s' % share_name
request_type = 'delete'
self._rest.send_rest_api(method=method, request_type=request_type)
@inspur_driver_debug_trace
def _get_cifs_share(self, share_name):
"""Get the CIFS share in backend"""
method = 'file/share/cifs?name=%s' % share_name
request_type = 'get'
share_backend = self._rest.send_rest_api(method=method,
request_type=request_type)
return share_backend
@inspur_driver_debug_trace
def _clone_directory_to_dest(self, snapshot, dest_path):
"""Clone the directory to the new directory"""
# get the origin share name of the snapshot
share_instance = snapshot['share_instance']
pool, name, _, _ = self._get_share_instance_pnsp(share_instance)
share_path = self._generate_share_path(pool, name)
# get the snapshot instance name
snap_name = self._generate_snapshot_name(snapshot)
method = 'snapshot/directory/clone'
request_type = 'post'
params = {'path': share_path,
'snapName': snap_name,
'destPath': dest_path}
self._rest.send_rest_api(method=method,
params=params,
request_type=request_type)
LOG.debug('Clone Path: %(path)s Snapshot: %(snap)s to Path %(dest)s',
{'path': share_path, 'snap': snap_name, 'dest': dest_path})
@inspur_driver_debug_trace
def _get_snapshots_from_share(self, path):
"""get all the snapshot of share"""
method = 'snapshot/directory?path=%s' % path
request_type = 'get'
snaps = self._rest.send_rest_api(method=method,
request_type=request_type)
return snaps
@inspur_driver_debug_trace
def _get_location_path(self, share_name, share_path, share_proto):
"""return all the location of all nodes"""
if share_proto == 'nfs':
location = [
{'path': r'%(ip)s:%(share_path)s'
% {'ip': ip, 'share_path': share_path}}
for ip in self.ips]
else:
location = [
{'path': r'\\%(ip)s\%(share_name)s'
% {'ip': ip, 'share_name': share_name}}
for ip in self.ips]
return location
def _get_nodes_virtual_ips(self):
"""Get the virtual ip list of the node"""
method = 'ctdb/set'
request_type = 'get'
ctdb_set = self._rest.send_rest_api(method=method,
request_type=request_type)
virtual_ips = []
for vip in ctdb_set['virtualIpList']:
ip = vip['ip'].split('/')[0]
virtual_ips.append(ip)
return virtual_ips
def _get_nodes_physical_ips(self):
"""Get the physical ip of all the backend nodes"""
method = 'cluster/node/cache'
request_type = 'get'
cached_nodes = self._rest.send_rest_api(method=method,
request_type=request_type)
node_ips = []
for node in cached_nodes:
if node['runningStatus'] == 1 and node['healthStatus'] == 1:
node_ips.append(node['nodeIp'])
return node_ips
def _get_nodes_ips(self):
"""Return both the physical ip and virtual ip"""
virtual_ips = self._get_nodes_virtual_ips()
physical_ips = self._get_nodes_physical_ips()
return virtual_ips + physical_ips
def _get_share_instance_pnsp(self, share_instance):
"""Get pool, name, size, proto information of a share instance.
AS13000 require all the names can only consist of letters,numbers,
and undercores,and must begin with a letter.
Also the length of name must less than 32 character.
The driver will use the ID as the name in backend,
add 'share_' to the beginning,and convert '-' to '_'
"""
pool = share_utils.extract_host(share_instance['host'], level='pool')
name = self._generate_share_name(share_instance)
# a share instance may not contain size attr.
try:
size = share_instance['size']
except AttributeError:
size = None
# a share instance may not contain proto attr.
try:
proto = share_instance['share_proto'].lower()
except AttributeError:
proto = None
LOG.debug("Pool %s, Name: %s, Size: %s, Protocol: %s",
pool, name, size, proto)
return pool, name, size, proto
def _unit_convert(self, capacity):
"""Convert all units to GB"""
capacity = str(capacity)
capacity = capacity.upper()
try:
unit_of_used = re.findall(r'[A-Z]', capacity)
unit_of_used = ''.join(unit_of_used)
except BaseException:
unit_of_used = ''
capacity = capacity.replace(unit_of_used, '')
capacity = float(capacity.replace(unit_of_used, ''))
if unit_of_used in ['B', '']:
capacity = capacity / units.Gi
elif unit_of_used in ['K', 'KB']:
capacity = capacity / units.Mi
elif unit_of_used in ['M', 'MB']:
capacity = capacity / units.Ki
elif unit_of_used in ['G', 'GB']:
capacity = capacity
elif unit_of_used in ['T', 'TB']:
capacity = capacity * units.Ki
elif unit_of_used in ['E', 'EB']:
capacity = capacity * units.Mi
capacity = '%.0f' % capacity
return float(capacity)
def _format_name(self, name):
"""format name to meet the backend requirements"""
name = name[0:32]
name = name.replace('-', '_')
return name
def _generate_share_name(self, share_instance):
share_name = 'share_%s' % share_instance['id']
return self._format_name(share_name)
def _generate_snapshot_name(self, snapshot_instance):
snap_name = 'snap_%s' % snapshot_instance['id']
return self._format_name(snap_name)
@staticmethod
def _generate_share_path(pool, share_name):
return r'/%s/%s' % (pool, share_name)
def _get_directory_detail(self, directory):
method = 'file/directory/detail?path=/%s' % directory
request_type = 'get'
details = self._rest.send_rest_api(method=method,
request_type=request_type)
return details[0]

5
manila/tests/conf_fixture.py

@ -55,6 +55,11 @@ def set_defaults(conf):
_safe_set_of_opts(conf, 'hitachi_hsp_username', 'hsp_user')
_safe_set_of_opts(conf, 'hitachi_hsp_password', 'hsp_password')
_safe_set_of_opts(conf, 'as13000_nas_ip', '1.1.1.1')
_safe_set_of_opts(conf, 'as13000_nas_login', 'admin')
_safe_set_of_opts(conf, 'as13000_nas_password', 'password')
_safe_set_of_opts(conf, 'as13000_share_pools', 'pool0')
_safe_set_of_opts(conf, 'qnap_management_url', 'http://1.2.3.4:8080')
_safe_set_of_opts(conf, 'qnap_share_ip', '1.2.3.4')
_safe_set_of_opts(conf, 'qnap_nas_login', 'admin')

0
manila/tests/share/drivers/inspur/__init__.py

0
manila/tests/share/drivers/inspur/as13000/__init__.py

1196
manila/tests/share/drivers/inspur/as13000/test_as13000_nas.py
File diff suppressed because it is too large
View File

6
releasenotes/notes/inspur-as13000-driver-41f6b7caea82e46e.yaml

@ -0,0 +1,6 @@
---
prelude: >
Add Inspur AS13000 driver.
features:
- Added new Inspur AS13000 driver, which supports snapshots operation along with
all the minimum driver features.
Loading…
Cancel
Save