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
This commit is contained in:
parent
623f4e197b
commit
d40987cf7d
@ -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::
|
||||
|
||||
|
@ -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
Normal file
0
manila/share/drivers/inspur/__init__.py
Normal file
0
manila/share/drivers/inspur/as13000/__init__.py
Normal file
0
manila/share/drivers/inspur/as13000/__init__.py
Normal file
883
manila/share/drivers/inspur/as13000/as13000_nas.py
Normal file
883
manila/share/drivers/inspur/as13000/as13000_nas.py
Normal file
@ -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]
|
@ -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
Normal file
0
manila/tests/share/drivers/inspur/__init__.py
Normal file
1196
manila/tests/share/drivers/inspur/as13000/test_as13000_nas.py
Normal file
1196
manila/tests/share/drivers/inspur/as13000/test_as13000_nas.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -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…
Reference in New Issue
Block a user