Merge "Add Macrosan Manila Driver"

This commit is contained in:
Zuul 2022-08-31 20:14:22 +00:00 committed by Gerrit Code Review
commit 30cc734f0f
14 changed files with 3849 additions and 0 deletions

View File

@ -94,6 +94,7 @@ each back end.
hitachi_hnas_driver
hpe_3par_driver
infortrend_driver
macrosan_driver
purestorage_flashblade_driver
tegile_driver
nexentastor5_driver

View File

@ -0,0 +1,103 @@
..
Copyright (c) 2022 Macrosan Technologies Co., Ltd.
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.
====================================
Macrosan Driver for OpenStack Manila
====================================
The `Macrosan <http://www.macrosan.com>`__ driver
provides NFS and CIFS shared file systems to Openstack.
Requirements
------------
- The following service should be enabled on NAS system:
* CIFS
* NFS
Supported Operations
--------------------
The following operations are supported:
- Create CIFS/NFS Share
- Delete CIFS/NFS Share
- Allow CIFS/NFS Share access
* Only IP access type is supported for NFS (ro/rw).
* Only USER access type is supported for CIFS (ro/rw).
- Deny CIFS/NFS Share access
- Extend a share.
- Shrink a share.
Backend Configuration
---------------------
The following parameters need to be configured in the [DEFAULT] section of
manila configuration (/etc/manila/manila.conf):
- `enabled_share_backends` - Name of the section on manila.conf used to specify
a backend i.e. *enabled_share_backends = macrosan*
- `enabled_share_protocols` - Specify a list of protocols to be allowed for
share creation. The VPSA driver support the following options: *NFS* or
*CIFS* or *NFS, CIFS*
The following parameters need to be configured in the [backend] section of
manila configuration (/etc/manila/manila.conf):
- `share_backend_name` = <backend name to enable>
- `share_driver` = manila.share.drivers.macrosan.macrosan_nas.MacrosanNasDriver
- `driver_handles_share_servers` = False
- `macrosan_nas_ip` = <IP address for access to the NAS controller>
- `macrosan_nas_port` = <Port number for access to the NAS controller>
- `macrosan_nas_user` = <username for access>
- `macrosan_nas_password` = <password for the user specified in macrosan_nas_user>
- `macrosan_share_pools` = <Poolname of the NAS controller>
Share Types
-----------
When creating a share, a share type can be specified to determine where and
how the share will be created. If a share type is not specified, the
`default_share_type` set in the manila configuration file is used.
Manila requires that the share type includes the
`driver_handles_share_servers` extra-spec. This ensures that the share
will be created on a backend that supports the requested
driver_handles_share_servers (share networks) capability.
For the Macrosan driver, this must be set to False.
Back-end configuration example
------------------------------
.. code-block:: ini
[DEFAULT]
enabled_share_backends = macrosan
enabled_share_protocols = NFS, CIFS
[macrosan]
share_backend_name = MACROSAN
share_driver = manila.share.drivers.macrosan.macrosan_nas.MacrosanNasDriver
driver_handles_share_servers = False
macrosan_nas_ip = FAKE_IP
macrosan_nas_port = 8443
macrosan_nas_user = FAKE_USER
macrosan_nas_password = FAKE_PASSWORD
macrosan_share_pools = fake_pool1, fake_pool2

View File

@ -75,6 +75,8 @@ Mapping of share drivers and share features support
+----------------------------------------+-----------------------+-----------------------+--------------------------+--------------------------+------------------------+-----------------------------------+--------------------------+--------------------+--------------------+
| Infortrend | T | T | T | T | \- | \- | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------------------+--------------------------+------------------------+-----------------------------------+--------------------------+--------------------+--------------------+
| Macrosan | Z | \- | Z | Z | \- | \- | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------------------+--------------------------+------------------------+-----------------------------------+--------------------------+--------------------+--------------------+
| LVM | M | \- | M | \- | M | M | \- | O | O |
+----------------------------------------+-----------------------+-----------------------+--------------------------+--------------------------+------------------------+-----------------------------------+--------------------------+--------------------+--------------------+
| Quobyte | K | \- | M | M | \- | \- | \- | \- | \- |
@ -152,6 +154,8 @@ Mapping of share drivers and share access rules support
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
| Infortrend | NFS (T) | \- | CIFS (T) | \- | \- | NFS (T) | \- | CIFS (T) | \- | \- |
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
| Macrosan | NFS (Z) | \- | CIFS (Z) | \- | \- | NFS (Z) | \- | CIFS (Z) | \- | \- |
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
| Oracle ZFSSA | NFS,CIFS(K) | \- | \- | \- | \- | \- | \- | \- | \- | \- |
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
| CephFS | NFS (P) | NFS (T) | \- | \- | CEPHFS (M) | NFS (P) | NFS (T) | \- | \- | CEPHFS (N) |
@ -221,6 +225,8 @@ Mapping of share drivers and security services support
+----------------------------------------+------------------+-----------------+------------------+
| Infortrend | \- | \- | \- |
+----------------------------------------+------------------+-----------------+------------------+
| Macrosan | \- | \- | \- |
+----------------------------------------+------------------+-----------------+------------------+
| Oracle ZFSSA | \- | \- | \- |
+----------------------------------------+------------------+-----------------+------------------+
| CephFS | \- | \- | \- |
@ -282,6 +288,8 @@ More information: :ref:`capabilities_and_extra_specs`
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+
| LVM | \- | M | \- | \- | \- | M | \- | K | O | O | P | P | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+
| Macrosan | \- | Z | \- | \- | \- | Z | \- | \- | \- | \- | Z | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+
| Quobyte | \- | K | \- | \- | \- | L | \- | M | \- | \- | P | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+
| Windows SMB | L | L | \- | \- | \- | L | \- | \- | \- | \- | P | \- | \- |

View File

@ -1122,3 +1122,8 @@ class ZadaraVPSASnapshotManageFailed(ShareBackendException):
class ZadaraServerNotFound(NotFound):
message = _("Unable to find server object for initiator %(name)s")
# Macrosan Storage driver
class MacrosanBackendExeption(ShareBackendException):
message = _("Macrosan backend exception: %(reason)s")

View File

@ -72,6 +72,7 @@ import manila.share.drivers.infortrend.driver
import manila.share.drivers.inspur.as13000.as13000_nas
import manila.share.drivers.inspur.instorage.instorage
import manila.share.drivers.lvm
import manila.share.drivers.macrosan.macrosan_nas
import manila.share.drivers.maprfs.maprfs_native
import manila.share.drivers.netapp.options
import manila.share.drivers.nexenta.options
@ -160,6 +161,7 @@ _global_opt_lists = [
manila.share.drivers.infortrend.driver.infortrend_nas_opts,
manila.share.drivers.inspur.as13000.as13000_nas.inspur_as13000_opts,
manila.share.drivers.inspur.instorage.instorage.instorage_opts,
manila.share.drivers.macrosan.macrosan_nas.macrosan_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,

View File

@ -0,0 +1,44 @@
# Copyright (c) 2022 MacroSAN Technologies Co., Ltd.
# 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.
NFS_NON_CONFIG = 0
NFS_ENABLED = 1
NFS_DISABLED = 2
NFS_EXCEPTION = 3
NFS_NON_SUPPORTED = 0
NFS_SUPPORTED = 1
CIFS_SHARE_MODE = '2'
CIFS_ENABLED = '1'
CIFS_NON_CONFIG = '-1'
CIFS_DISABLED = '-2'
CIFS_EXCEPTION = '-3'
USER_NOT_EXIST = '0'
USER_EXIST = '1'
USER_FORMAT_ERROR = '2'
GROUP_NOT_EXIST = '0'
GROUP_EXIST = '1'
GROUP_FORMAT_ERROR = '2'
CODE_SUCCESS = 0
CODE_NOT_FOUND = 4
CODE_SOURCE_NOT_EXIST = 403
TOKEN_EXPIRED = 301
TOKEN_VERIFY_FAILED = 302
TOKEN_FORMAT_ERROR = 303
TOKEN_REQUIRED = 304

View File

@ -0,0 +1,550 @@
# Copyright (c) 2022 MacroSAN Technologies Co., Ltd.
# 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.
import re
from oslo_config import cfg
from oslo_log import log
from oslo_utils import units
from manila import exception
from manila.i18n import _
from manila.share.drivers.macrosan import macrosan_constants as constants
from manila.share.drivers.macrosan import rest_helper
from manila.share import utils as share_utils
CONF = cfg.CONF
LOG = log.getLogger(__name__)
class MacrosanHelper(object):
def __init__(self, configuration):
self.configuration = configuration
self.rest = rest_helper.RestHelper(self.configuration)
self.snapshot_support = False
self.replication_support = False
self.pools = self.configuration.macrosan_share_pools
def check_share_service(self):
nfs_service = self.rest._get_nfs_service_status()
if nfs_service['serviceStatus'] not in [constants.NFS_NON_CONFIG,
constants.NFS_ENABLED,
constants.NFS_DISABLED]:
raise exception.MacrosanBackendExeption(
reason=_("nfs service exception. Please check backend"))
elif nfs_service['serviceStatus'] == constants.NFS_NON_CONFIG:
self.rest._config_nfs_service()
self.rest._start_nfs_service()
elif nfs_service['serviceStatus'] == constants.NFS_DISABLED:
if (nfs_service['nfs3Status'] == constants.NFS_NON_SUPPORTED and
nfs_service['nfs4Status'] == constants.NFS_NON_SUPPORTED):
self.rest._config_nfs_service()
self.rest._start_nfs_service()
else:
if (nfs_service['nfs3Status'] == constants.NFS_NON_SUPPORTED and
nfs_service['nfs4Status'] == constants.NFS_NON_SUPPORTED):
self.rest._config_nfs_service()
cifs_status = self.rest._get_cifs_service_status()
if cifs_status == constants.CIFS_EXCEPTION:
raise exception.MacrosanBackendExeption(
reason=_("cifs service exception.Please check backend"))
elif cifs_status == constants.CIFS_NON_CONFIG:
"""need config first, then start service"""
self.rest._config_cifs_service()
self.rest._start_cifs_service()
elif cifs_status == constants.CIFS_DISABLED:
self.rest._start_cifs_service()
status = self.rest._get_cifs_service_status()
if status == constants.CIFS_SHARE_MODE:
self.rest._config_cifs_service()
elif cifs_status == constants.CIFS_SHARE_MODE:
self.rest._config_cifs_service()
def do_setup(self):
"""get token"""
self.rest.login()
def create_share(self, share, share_server=None):
"""Create a share"""
pool_name, share_name, proto = self._get_share_instance_pnp(share)
share_size = ''.join((str(share['size']), 'GB'))
# check pool available
storage_pools = self.rest._get_all_pool()
pool_info = self._find_pool_info(pool_name, storage_pools)
if not pool_info:
msg = f'Failed to find information regarding pool {pool_name}'
msg = _(msg)
raise exception.InvalidHost(reason=msg)
# first create filesystem
self.rest._create_filesystem(fs_name=share_name,
pool_name=pool_name,
filesystem_quota=share_size)
share_path = self._generate_share_path(share_name)
# second create filesystem dir
self.rest._create_filesystem_dir(share_path)
# third create nfs or cifs share
if proto == 'NFS':
self.rest._create_nfs_share(share_path=share_path)
else:
user_name = 'manilanobody'
user_passwd = 'manilanobody'
group_name = 'manilanobody'
ret = self._ensure_user(user_name, user_passwd, group_name)
if not ret:
self.rest._delete_filesystem(share_name)
raise exception.MacrosanBackendExeption(
reason=(_(
'Failed to create share %(share)s.Reason:'
'username %(user_name)s error.')
% {'share': share_name, 'user_name': user_name}))
rw_list = [user_name]
rw_list_type = ['0']
self.rest._create_cifs_share(share_name=share_name,
share_path=share_path,
rw_list=rw_list,
rw_list_type=rw_list_type)
location = self._get_location_path(share_path, share_name, proto)
return location
def delete_share(self, share, share_server=None):
"""Delete a share."""
pool, share_name, proto = self._get_share_instance_pnp(share)
share_path = self._generate_share_path(share_name)
backend_share = self._get_share(share_path, proto)
if not backend_share:
LOG.error(f'Share {share_name} not found.')
filesystem = self.rest._get_filesystem(share_name)
if filesystem:
self.rest._delete_filesystem(share_name)
else:
if proto == 'NFS':
self.rest._delete_nfs_share(share_path)
else:
self.rest._delete_cifs_share(share_name, share_path)
self.rest._delete_filesystem(share_name)
def extend_share(self, share, new_size, share_server=None):
"""Extend share"""
pool, share_name, proto = self._get_share_instance_pnp(share)
share_path = self._generate_share_path(share_name)
backend_share = self._get_share(share_path, proto)
if not backend_share:
msg = f"Can't find the share by share name: {share_name}."
msg = _(msg)
LOG.error(msg)
raise exception.ShareResourceNotFound(share_id=share['id'])
# storage size logic already in manila/share/api.py extend func
# size param need unit
new_size = ''.join((str(new_size), 'GB'))
self.rest._update_share_size(share_name, new_size)
def shrink_share(self, share, new_size, share_server=None):
"""Shrink share"""
pool, share_name, proto = self._get_share_instance_pnp(share)
share_path = self._generate_share_path(share_name)
backend_share = self._get_share(share_path, proto)
if not backend_share:
msg = f"Can't find the share by share name: {share_name}."
msg = _(msg)
LOG.error(msg)
raise exception.ShareResourceNotFound(share_id=share['id'])
filesystem_info = self.rest._get_filesystem(share_name)
used_size = self._unit_convert_toGB(filesystem_info['usedCapacity'])
if new_size <= used_size:
raise exception.ShareShrinkingPossibleDataLoss(
share_id=share['id'])
# storage size logic already in manila/share/api.py shrink func
new_size = ''.join((str(new_size), 'GB'))
self.rest._update_share_size(share_name, new_size)
def ensure_share(self, share, share_server=None):
"""Enusre that share is exported"""
pool, share_name, proto = self._get_share_instance_pnp(share)
share_path = self._generate_share_path(share_name)
backend_share = self._get_share(share_path, proto)
if not backend_share:
raise exception.ShareResourceNotFound(share_id=share['id'])
location = self._get_location_path(share_path, share_name, proto)
return [location]
def _allow_access(self, share, access, share_server=None):
"""Allow access to the share."""
pool, share_name, proto = self._get_share_instance_pnp(share)
share_path = self._generate_share_path(share_name)
access_level = access['access_level']
share_id = share['id']
if access_level not in ('rw', 'ro'):
raise exception.InvalidShareAccess(
reason=(_('Unsupported access level: %s.') % access_level))
if proto == 'NFS':
self._allow_nfs_access(share_path, share_name, access, share_id)
elif proto == 'CIFS':
self._allow_cifs_access(share_path, share_name, access, share_id)
def _allow_nfs_access(self, share_path, share_name, access, share_id):
"""Allow nfs access."""
access_type = access['access_type']
access_to = access['access_to']
access_level = access['access_level']
# Only use 'ip',
# input "*" replace all, or ip 172.0.1.11 ,
# or ip network segment 172.0.1.11/255.255.0.0 172.0.1.11/16
if access_type != 'ip':
message = (_('NFS shares only allow IP access types. '
'access_type: %(access_type)s') %
{'access_type': access_type})
raise exception.InvalidShareAccess(reason=message)
backend_share = self.rest._get_nfs_share(share_path)
if not backend_share:
msg = (_("Can't find the share by share name: %s.")
% share_name)
LOG.error(msg)
raise exception.ShareResourceNotFound(share_id=share_id)
if access_to == '0.0.0.0/0':
access_to = '*'
share_client = self.rest._get_access_from_nfs_share(share_path,
access_to)
if share_client:
if access_level != share_client['accessRight']:
self.rest._change_nfs_access_rest(share_path,
access_to,
access_level)
else:
self.rest._allow_nfs_access_rest(share_path,
access_to,
access_level)
def _allow_cifs_access(self, share_path, share_name, access, share_id):
"""Allow cifs access."""
access_type = access['access_type']
access_to = access['access_to']
access_level = access['access_level']
if access_type != 'user':
message = _('Only user access type is '
'allowed for CIFS shares.')
raise exception.InvalidShareAccess(reason=message)
backend_share = self.rest._get_cifs_share(share_path)
if not backend_share:
msg = (_("Can't find the share by share name: %s.")
% share_name)
LOG.error(msg)
raise exception.ShareResourceNotFound(share_id=share_id)
share_client = self.rest._get_access_from_cifs_share(share_path,
access_to)
if share_client:
if access_level != share_client['accessRight']:
self.rest._change_cifs_access_rest(share_path,
access_to,
access_level,
share_client['ugType'])
else:
self.rest._allow_cifs_access_rest(share_path,
access_to,
access_level)
def _deny_access(self, share, access, share_server=None):
"""Deny access to the share."""
pool, share_name, proto = self._get_share_instance_pnp(share)
share_path = self._generate_share_path(share_name)
if proto == 'NFS':
self._deny_nfs_access(share_path, share_name, access)
else:
self._deny_cifs_access(share_path, share_name, access)
def _deny_nfs_access(self, share_path, share_name, access):
"""Deny nfs access."""
access_type = access['access_type']
access_to = access['access_to']
if access_type != 'ip':
LOG.error('Only IP access types are allowed '
'for NFS shares.')
return
if access_to == '0.0.0.0/0':
access_to = '*'
share_client = self.rest._get_access_from_nfs_share(share_path,
access_to)
if not share_client:
LOG.error(f'Could not list the share access for share '
f'{share_name}')
return
self.rest._delete_nfs_access_rest(share_path, access_to)
def _deny_cifs_access(self, share_path, share_name, access):
"""Deny cifs access."""
access_type = access['access_type']
access_to = access['access_to']
if access_type != 'user':
LOG.error('Only USER access types are allowed '
'for CIFS shares.')
return
share_client = self.rest._get_access_from_cifs_share(share_path,
access_to)
if not share_client:
LOG.error(f'Could not list the share access for share '
f'{share_name}')
return
self.rest._delete_cifs_access_rest(share_path,
share_client['ugName'],
share_client['ugType'])
def _clear_access(self, share, share_server=None):
"""Remove all access rules of the share"""
pool, share_name, proto = self._get_share_instance_pnp(share)
share_path = self._generate_share_path(share_name)
access_list = self._get_all_access_from_share(share_path, proto)
if not access_list:
LOG.error(f'Could not list the share access for share '
f'{share_name}')
return
if proto == 'NFS':
for share_access in access_list:
if share_access['access_to'] == '172.0.0.2':
continue
self.rest._delete_nfs_access_rest(share_path,
share_access['access_to'])
elif proto == 'CIFS':
for share_access in access_list:
if (share_access['access_to'] == 'manilanobody'
and share_access['ugType'] == '0'):
continue
self.rest._delete_cifs_access_rest(share_path,
share_access['access_to'],
share_access['ugType'])
def update_access(self, share, access_rules, add_rules,
delete_rules, share_server=None):
"""Update access rules list."""
access_updates = {}
if not (add_rules or delete_rules):
self._clear_access(share, share_server)
for access_rule in access_rules:
try:
self._allow_access(share, access_rule, share_server)
except exception.InvalidShareAccess as e:
msg = f'Failed to allow {access_rule["access_level"]} ' \
f'access to {access_rule["access_to"]}, reason {e}'
msg = _(msg)
LOG.error(msg)
access_updates.update(
{access_rule['access_id']: {'state': 'error'}})
else:
for access_rule in delete_rules:
self._deny_access(share, access_rule, share_server)
for access_rule in add_rules:
try:
self._allow_access(share, access_rule, share_server)
except exception.InvalidShareAccess as e:
msg = f'Failed to allow {access_rule["access_level"]} ' \
f'access to {access_rule["access_to"]}, reason {e}'
msg = _(msg)
LOG.error(msg)
access_updates.update(
{access_rule['access_id']: {'state': 'error'}})
return access_updates
def _get_all_access_from_share(self, share_path, share_proto):
access_list = []
if share_proto == 'NFS':
access_list = self.rest._get_all_nfs_access_rest(share_path)
elif share_proto == 'CIFS':
access_list = self.rest._get_all_cifs_access_rest(share_path)
return access_list
def _ensure_user(self, user_name, user_passwd, group_name):
ret_user = self.rest._query_user(user_name)
if ret_user == constants.USER_NOT_EXIST:
ret_group = self.rest._query_group(group_name)
if ret_group not in [constants.GROUP_NOT_EXIST,
constants.GROUP_EXIST]:
msg = f'Failed to use group {group_name}'
msg = _(msg)
raise exception.InvalidInput(reason=msg)
elif ret_group == constants.GROUP_NOT_EXIST:
self.rest._add_localgroup(group_name)
self.rest._add_localuser(user_name, user_passwd, group_name)
return True
elif ret_user == constants.USER_EXIST:
return True
else:
return False
def update_share_stats(self, dict_data):
"""Update pools info"""
result = self.rest._get_all_pool()
dict_data["pools"] = []
for pool_name in self.pools:
pool_capacity = self._get_pool_capacity(pool_name, result)
if pool_capacity:
pool = {
'pool_name': pool_name,
'total_capacity_gb': pool_capacity['totalcapacity'],
'free_capacity_gb': pool_capacity['freecapacity'],
'allocated_capacity_gb':
pool_capacity['allocatedcapacity'],
'reserved_percentage': 0,
'reserved_snapshot_percentage': 0,
'reserved_share_extend_percentage': 0,
'dedupe': False,
'compression': False,
'qos': False,
'thin_provisioning': False,
'snapshot_support': self.snapshot_support,
'create_share_from_snapshot_support':
self.snapshot_support,
}
dict_data["pools"].append(pool)
if not dict_data['pools']:
msg = _("StoragePool is None")
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
def _get_pool_capacity(self, pool_name, result):
"""Get total,allocated,free capacity of the pools"""
pool_info = self._find_pool_info(pool_name, result)
if pool_info:
total_capacity = int(self._unit_convert_toGB(
pool_info['totalcapacity']))
free_capacity = int(self._unit_convert_toGB(
pool_info['freecapacity']))
allocated_capacity = int(self._unit_convert_toGB(
pool_info['allocatedcapacity']))
pool_info['totalcapacity'] = total_capacity
pool_info['freecapacity'] = free_capacity
pool_info['allocatedcapacity'] = allocated_capacity
return pool_info
def _unit_convert_toGB(self, capacity):
"""Convert unit to GB"""
capacity = capacity.upper()
try:
# get unit char array and use join connect to string
unit = re.findall(r'[A-Z]', capacity)
unit = ''.join(unit)
except BaseException:
unit = ''
# get capacity size,unit is GB
capacity = capacity.replace(unit, '')
capacity = float(capacity)
if unit in ['B', '']:
capacity = capacity / units.Gi
elif unit in ['K', 'KB']:
capacity = capacity / units.Mi
elif unit in ['M', 'MB']:
capacity = capacity / units.Ki
elif unit in ['G', 'GB']:
capacity = capacity
elif unit in ['T', 'TB']:
capacity = capacity * units.Ki
elif unit in ['E', 'EB']:
capacity = capacity * units.Mi
capacity = '%.0f' % capacity
return float(capacity)
def _generate_share_name(self, share):
share_name = 'manila_%s' % share['id']
return self._format_name(share_name)
def _format_name(self, name):
"""format name to meet the backend requirements"""
name = name[0: 31]
name = name.replace('-', '_')
return name
def _generate_share_path(self, share_name):
"""add '/' as path"""
share_path = r'/%(path)s/%(dirName)s' % {
'path': share_name.replace("-", "_"),
'dirName': share_name.replace("-", "_")
}
return share_path
def _get_location_path(self, share_path, share_name, share_proto, ip=None):
location = None
if ip is None:
ip = self.configuration.macrosan_nas_ip
share_proto = share_proto.upper()
if share_proto == 'NFS':
location = f'{ip}:{share_path}'
elif share_proto == 'CIFS':
location = f'\\\\{ip}\\{share_name}'
return location
def _get_share_instance_pnp(self, share_instance):
proto = share_instance['share_proto'].upper()
share_name = self._generate_share_name(share_instance)
pool = share_utils.extract_host(share_instance['host'], level='pool')
if not pool:
msg = _("Pool doesn't exist in host field.")
raise exception.InvalidHost(reason=msg)
if proto != 'NFS' and proto != 'CIFS':
msg = f'Share protocol {proto} is not supported.'
msg = _(msg)
raise exception.MacrosanBackendExeption(reason=msg)
return pool, share_name, proto
def _get_share(self, share_path, proto):
return (self.rest._get_nfs_share(share_path)
if proto == 'NFS'
else self.rest._get_cifs_share(share_path))
def _find_pool_info(self, pool_name, result):
if pool_name is None:
return
pool_info = {}
pool_name = pool_name.strip()
for item in result.get('data', []):
if pool_name == item['name']:
pool_info['name'] = item['name']
pool_info['totalcapacity'] = item['size']
pool_info['allocatedcapacity'] = item['allocated']
pool_info['freecapacity'] = item['free']
pool_info['health'] = item['health']
pool_info['rw'] = item['rwStatus']
break
return pool_info

View File

@ -0,0 +1,182 @@
# Copyright (c) 2022 MacroSAN Technologies Co., Ltd.
# 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 Macrosan Storage Array.
"""
import functools
from oslo_config import cfg
from oslo_log import log
from manila.share import driver
from manila.share.drivers.macrosan import macrosan_helper
macrosan_opts = [
cfg.HostAddressOpt('macrosan_nas_ip',
required=True,
help='IP address for the Macrosan NAS server.'),
cfg.PortOpt('macrosan_nas_port',
default=8443,
help='Port number for the Macrosan NAS server.'),
cfg.StrOpt('macrosan_nas_username',
default='manila',
help='Username for the Macrosan NAS server.'),
cfg.StrOpt('macrosan_nas_password',
default=None,
secret=True,
help='Password for the Macrosan NAS server. '),
cfg.StrOpt('macrosan_nas_http_protocol',
default='https',
help='Http protocol for the Macrosan NAS server.'),
cfg.StrOpt('macrosan_nas_prefix',
default='nas',
help='Url prefix for the Macrosan NAS server.'),
cfg.ListOpt('macrosan_share_pools',
required=True,
help='Comma separated list of Macrosan NAS pools.'),
cfg.IntOpt('macrosan_timeout',
default=60,
help='request timeout in seconds.')
]
CONF = cfg.CONF
CONF.register_opts(macrosan_opts)
LOG = log.getLogger(__name__)
def debug_trace(func):
"""Log the dirver invoke method start and leave information
Used in the MacrosanNasDriver class methods.
Ensure func have 'self' argument.
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
driver = args[0]
method_name = "%(class_name)s.%(method)s" \
% {"class_name": driver.__class__.__name__,
"method": func.__name__}
backend_name = driver.configuration.share_backend_name
LOG.debug("[%(backend_name)s]:Start %(method_name)s",
{"backend_name": backend_name, "method_name": method_name})
result = func(*args, **kwargs)
LOG.debug("[%(backend_name)s]:Leave %(method_name)s",
{"backend_name": backend_name, "method_name": method_name})
return result
return wrapper
class MacrosanNasDriver(driver.ShareDriver):
"""Macrosan Share Driver
Driver version history:
V1.0.0: Initial version
Driver support:
share create/delete,
extend size,
shrink size,
update_access.
protocol: NFS/CIFS
"""
VENDOR = 'Macrosan'
VERSION = '1.0.0'
PROTOCOL = 'NFS_CIFS'
def __init__(self, *args, **kwargs):
super(MacrosanNasDriver, self).__init__(False, *args, **kwargs)
self.configuration.append_config_values(macrosan_opts)
self.helper = macrosan_helper.MacrosanHelper(self.configuration)
@debug_trace
def do_setup(self, context):
"""initialization the driver when start"""
self.helper.do_setup()
@debug_trace
def check_for_setup_error(self):
"""Check prerequisites"""
self.helper.check_share_service()
@debug_trace
def create_share(self, context, share, share_server=None):
"""Create a share"""
return self.helper.create_share(share, share_server)
@debug_trace
def delete_share(self, context, share, share_server=None):
"""Delete a share."""
self.helper.delete_share(share, share_server)
@debug_trace
def extend_share(self, share, new_size, share_server=None):
"""Extend share capacity"""
self.helper.extend_share(share, new_size, share_server)
@debug_trace
def shrink_share(self, share, new_size, share_server=None):
"""Shrink share capacity"""
self.helper.shrink_share(share, new_size, share_server)
@debug_trace
def ensure_share(self, context, share, share_server=None):
"""Enusre that share is exported."""
return self.helper.ensure_share(share, share_server)
@debug_trace
def update_access(self, context, share, access_rules, add_rules,
delete_rules, share_server=None):
"""Update access rules list.
:param context: Current context
:param share: Share model with share data.
:param access_rules: All access rules for given share
:param add_rules: Empty List or List of access rules which should be
added. access_rules already contains these rules.
:param delete_rules: Empty List or List of access rules which should be
removed. access_rules doesn't contain these rules.
:param share_server: Not used by this driver.
:returns: None, or a dictionary of ``access_id``, ``access_key`` as
key: value pairs for the rules added, where, ``access_id``
is the UUID (string) of the access rule, and ``access_key``
is the credential (string) of the entity granted access.
During recovery after error, the returned dictionary must
contain ``access_id``, ``access_key`` for all the rules that
the driver is ordered to resync, i.e. rules in the
``access_rules`` parameter.
"""
return self.helper.update_access(share, access_rules,
add_rules, delete_rules,
share_server)
@debug_trace
def _update_share_stats(self):
"""Update backend status ,include driver and pools"""
data = {
'vendor_name': self.VENDOR,
'driver_version': self.VERSION,
'storage_protocol': self.PROTOCOL,
'share_backend_name':
self.configuration.safe_get('share_backend_name'),
}
self.helper.update_share_stats(data)
super(MacrosanNasDriver, self)._update_share_stats(data)

View File

@ -0,0 +1,661 @@
# Copyright (c) 2022 MacroSAN Technologies Co., Ltd.
# 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.
import requests
from oslo_log import log
from manila import exception
from manila.i18n import _
from manila.share.drivers.macrosan import macrosan_constants as constants
from manila import utils
LOG = log.getLogger(__name__)
class RestHelper(object):
def __init__(self, configuration):
self.configuration = configuration
self._protocol = self.configuration.macrosan_nas_http_protocol
self._ip = self.configuration.macrosan_nas_ip
self._port = self.configuration.macrosan_nas_port
self._prefix = self.configuration.macrosan_nas_prefix
self._token = None
self._username = self.configuration.macrosan_nas_username
self._password = self.configuration.macrosan_nas_password
self.request_timeout = self.configuration.macrosan_timeout
# Suppress the Insecure request warnings
requests.packages.urllib3.disable_warnings(
requests.packages.urllib3.exceptions.InsecureRequestWarning)
@utils.synchronized('macrosan_manila')
def call(self, url, data, method):
"""Send requests.
If token is expired,re-login.
"""
header = {'Authorization': self._token}
if self._token is None:
self.login()
result = self.do_request(url, data, method, header)
if result['code'] == constants.TOKEN_EXPIRED:
LOG.error("Token is expired, re-login.")
self.login()
# token refresh, Re-assign
header['Authorization'] = self._token
result = self.do_request(url, data, method, header)
elif (result['code'] == constants.TOKEN_FORMAT_ERROR or
result['code'] == constants.TOKEN_VERIFY_FAILED or
result['code'] == constants.TOKEN_REQUIRED):
msg = _('Token authentication error.')
LOG.error(msg)
raise exception.MacrosanBackendExeption(msg)
return result
def do_request(self, url, data, method, header=None):
final_url = (f'{self._protocol}://{self._ip}:{self._port}/'
f'{self._prefix}/{url}')
LOG.debug(f'Request URL: {final_url}, Method: {method}, Data: {data}')
if method == 'POST':
res = requests.post(final_url, data=data, headers=header,
timeout=self.request_timeout, verify=False)
elif method == 'GET':
res = requests.get(final_url, data=data, headers=header,
timeout=self.request_timeout, verify=False)
elif method == 'PUT':
res = requests.put(final_url, data=data, headers=header,
timeout=self.request_timeout, verify=False)
elif method == 'DELETE':
res = requests.delete(final_url, data=data, headers=header,
timeout=self.request_timeout, verify=False)
else:
msg = (_("Request method %s invalid.") % method)
raise exception.ShareBackendException(msg=msg)
code = res.status_code
if code != 200:
msg = (_('Code: %(code)s, URL: %(url)s, Message: %(msg)s')
% {'code': res.status_code,
'url': final_url,
'msg': res.text})
LOG.error(msg)
raise exception.NetworkException(msg)
response = res.json()
LOG.debug('CODE: %(code)s, RESPONSE: %(response)s',
{'code': code, 'response': response})
return response
def login(self):
"""Login array and return token."""
url = 'rest/token'
data = {'userName': self._username,
'userPasswd': self._password}
result = self.do_request(url, data, 'POST')
if result['code'] != 0:
msg = f"Login failed. code: {result['code']}"
msg = _(msg)
LOG.error(msg)
raise exception.ShareBackendException(msg=msg)
LOG.debug(f'Login successful. URL {self._ip}\n')
self._token = result['data']
def _assert_result_code(self, result, msg):
if (result['code'] != constants.CODE_SUCCESS
and result['code'] != constants.CODE_NOT_FOUND):
error_msg = (_('%(err)s\nresult: %(res)s.') % {'err': msg,
'res': result})
LOG.error(error_msg)
raise exception.ShareBackendException(msg=error_msg)
def _assert_result_data(self, result, msg):
if "data" not in result:
error_msg = (_('Error:"data" not in result. %s') % msg)
LOG.error(error_msg)
raise exception.ShareBackendException(msg=error_msg)
def _create_nfs_share(self, share_path):
url = 'rest/nfsShare'
params = {
'path': share_path,
'authority': 'ro',
'accessClient': '172.0.0.2',
}
result = self.call(url, params, 'POST')
msg = 'Failed to create a nfs share.'
self._assert_result_code(result, msg)
def _get_nfs_share(self, share_path):
# GET method: param need be after url
url = f'rest/nfsShare?path={share_path}'
result = self.call(url, None, 'GET')
msg = 'Failed to get nfs share.'
self._assert_result_code(result, msg)
return result['data']
def _delete_nfs_share(self, share_path):
url = f'rest/nfsShare?path={share_path}'
result = self.call(url, None, 'DELETE')
msg = 'Failed to delete nfs share.'
self._assert_result_code(result, msg)
def _create_cifs_share(self, share_name, share_path,
rw_list, rw_list_type):
url = 'rest/cifsShare'
params = {
'path': share_path,
'cifsName': share_name,
'cifsDescription': '',
'RoList': [],
'RoListType': [],
'RwList': rw_list,
'RwListType': rw_list_type,
'allowList': [],
'denyList': [],
}
result = self.call(url, params, 'POST')
msg = 'Failed to create a CIFS share.'
self._assert_result_code(result, msg)
def _get_cifs_share(self, share_path):
url = f'rest/cifsShare?path={share_path}'
result = self.call(url, None, 'GET')
msg = 'Failed to get the cifs share.'
self._assert_result_code(result, msg)
return result['data']
def _delete_cifs_share(self, share_name, share_path):
url = f'rest/cifsShare?path={share_path}&cifsName={share_name}'
result = self.call(url, None, 'DELETE')
msg = 'Failed to delete the cifs share.'
self._assert_result_code(result, msg)
def _update_share_size(self, fs_name, new_size):
url = f'rest/filesystem/{fs_name}'
params = {
'capacity': new_size,
}
result = self.call(url, params, 'PUT')
msg = 'Failed to update the filesystem size.'
self._assert_result_code(result, msg)
def _create_filesystem(self, fs_name, pool_name, filesystem_quota):
url = 'rest/filesystem'
fsinfo = {
'fsName': fs_name,
'poolName': pool_name,
'createType': '0',
'fileSystemQuota': filesystem_quota,
'fileSystemReserve': filesystem_quota,
'wormStatus': 0,
'defaultTimeStatus': 0,
'defaultTimeNum': 0,
'defaultTimeUnit': 'year',
'isAutoLock': 0,
'isAutoDelete': 0,
'lockTime': 0
}
result = self.call(url, fsinfo, 'POST')
msg = 'Failed to create the filesystem.'
self._assert_result_code(result, msg)
def _delete_filesystem(self, fs_name):
"""Delete filesystem"""
url = f'rest/filesystem/{fs_name}'
result = self.call(url, None, 'DELETE')
msg = 'Failed to delete the filesystem.'
self._assert_result_code(result, msg)
def _get_filesystem(self, fs_name):
"""Get filesystem """
url = f'rest/filesystem/{fs_name}'
result = self.call(url, None, 'GET')
msg = 'Failed to get the filesystem.'
self._assert_result_code(result, msg)
return result['data']
def _create_filesystem_dir(self, share_path):
url = 'rest/fileDir'
slash = share_path.index(r'/', 1)
dir_info = {
'path': share_path[0: slash],
'dirName': share_path[slash + 1:],
}
result = self.call(url, dir_info, 'POST')
msg = 'Failed to create the filesystem directory.'
self._assert_result_code(result, msg)
def _delete_filesystem_dir(self, share_path):
slash = share_path.index(r'/', 1)
url = f'rest/fileDir?path={share_path[0: slash]}' \
f'&dirName={share_path[slash + 1:]}'
result = self.call(url, None, 'DELETE')
msg = 'Failed to delete the filesystem directory.'
self._assert_result_code(result, msg)
def _allow_access_rest(self, share_path, access_to,
access_level, share_proto):
"""Allow access to the share."""
if share_proto == 'NFS':
self._allow_nfs_access_rest(share_path, access_to, access_level)
elif share_proto == 'CIFS':
self._allow_cifs_access_rest(share_path, access_to, access_level)
else:
raise exception.InvalidInput(
reason=(_('Invalid Nas protocol: %s.') % share_proto))
def _allow_nfs_access_rest(self, share_path, access_to, access_level):
url = 'rest/nfsShareClient'
access = {
'path': share_path,
'client': access_to,
'authority': access_level,
}
result = self.call(url, access, 'POST')
msg = 'Failed to allow access to the NFS share.'
self._assert_result_code(result, msg)
def _allow_cifs_access_rest(self, share_path, access_to, access_level):
url = 'rest/cifsShareClient'
ug_type = {
'localUser': '0',
'localGroup': '1',
'adUser': '2',
'adGroup': '3',
}
msg = 'Failed to allow access to the CIFS share.'
access_info = (f'Access info (access_to: {access_to},'
f'access_level: {access_level},'
f'path: {share_path}.)')
def send_rest(rest_access_to, rest_ug_type):
access = {
'path': share_path,
'right': access_level,
'ugName': rest_access_to,
'ugType': rest_ug_type,
}
result = self.call(url, access, 'POST')
err_code = result['code']
if err_code == constants.CODE_SUCCESS:
return True
elif err_code != constants.CODE_SOURCE_NOT_EXIST:
self._assert_result_code(result, msg)
return False
if '/' not in access_to:
# First, try to add local user access
LOG.debug('Attempting to add local user access. %s', access_info)
if send_rest(access_to, ug_type['localUser']):
return
# Second,If add local user access failed,
# try to add local group access
LOG.debug('Failed add local user access,'
' attempting to add local group access. %s', access_info)
if send_rest(access_to, ug_type['localGroup']):
return
else:
str = access_to.index('/')
access_to = access_to[str + 1:]
# First, add domain user access
LOG.debug('Attempting to add domain user access. %s', access_info)
if send_rest(access_to, ug_type['adUser']):
return
# Second, if add domain user access failed,
# try to add domain group access.
LOG.debug('Failed add domain user access, '
'attempting to add domain group access. %s', access_info)
if send_rest(access_to, ug_type['adGroup']):
return
raise exception.InvalidShare(reason=msg)
def _get_access_from_nfs_share(self, path, clientName):
url = f'rest/nfsShareClient?path={path}&client={clientName}'
result = self.call(url, None, 'GET')
msg = 'Failed to get share NFS access.'
self._assert_result_code(result, msg)
share_client = None
if result['data'] is not None:
share_client = {}
share_client['path'] = result['data']['path']
share_client['clientName'] = result['data']['clientName']
share_client['accessRight'] = result['data']['accessRight']
return share_client
def _get_access_from_cifs_share(self, share_path, access_to,
ug_input_type=None):
ug_type = {
'localUser': '0',
'localGroup': '1',
'adUser': '2',
'adGroup': '3',
}
msg = 'Failed to get share cifs access.'
access_info = (f'Access info (access_to: {access_to},'
f'path: {share_path}.)')
def send_rest(access_to, ug_type):
url = f'rest/cifsShareClient?path={share_path}' \
f'&ugName={access_to}&ugType={ug_type}'
result = self.call(url, None, 'GET')
self._assert_result_code(result, msg)
return result
share_client = None
if ug_input_type is not None:
ret = send_rest(access_to, ug_input_type)
if ret['data']:
share_client = {}
share_client['path'] = ret['data']['path']
share_client['ugName'] = ret['data']['ugName']
share_client['ugType'] = ret['data']['ugType']
share_client['accessRight'] = ret['data']['accessRight']
return share_client
elif '/' not in access_to:
LOG.debug('Attempting to get local user access. %s', access_info)
user_ret = send_rest(access_to, ug_type['localUser'])
if user_ret['code'] == constants.CODE_NOT_FOUND:
return share_client
if user_ret['data']:
share_client = {}
share_client['path'] = user_ret['data']['path']
share_client['ugName'] = user_ret['data']['ugName']
share_client['ugType'] = user_ret['data']['ugType']
share_client['accessRight'] = user_ret['data']['accessRight']
return share_client
LOG.debug('Failed get local user access,'
' attempting to get local group access. %s', access_info)
group_ret = send_rest(access_to, ug_type['localGroup'])
if group_ret['data']:
share_client = {}
share_client['path'] = group_ret['data']['path']
share_client['ugName'] = group_ret['data']['ugName']
share_client['ugType'] = group_ret['data']['ugType']
share_client['accessRight'] = group_ret['data']['accessRight']
return share_client
else:
str = access_to.index('/')
access_to = access_to[str + 1:]
LOG.debug('Attempting to get domain user access. %s', access_info)
aduser_ret = send_rest(access_to, ug_type['adUser'])
if aduser_ret['code'] == constants.CODE_NOT_FOUND:
return share_client
if aduser_ret['data']:
share_client = {}
share_client['path'] = aduser_ret['data']['path']
share_client['ugName'] = aduser_ret['data']['ugName']
share_client['ugType'] = aduser_ret['data']['ugType']
share_client['accessRight'] = \
aduser_ret['data']['accessRight']
return share_client
LOG.debug('Failed get domain user access,'
' attempting to get domain group access. %s',
access_info)
adgroup_ret = send_rest(access_to, ug_type['adGroup'])
if adgroup_ret['data']:
share_client = {}
share_client['path'] = adgroup_ret['data']['path']
share_client['ugName'] = adgroup_ret['data']['ugName']
share_client['ugType'] = adgroup_ret['data']['ugType']
share_client['accessRight'] = \
adgroup_ret['data']['accessRight']
return share_client
return share_client
def _get_all_nfs_access_rest(self, share_path):
url = f'rest/allNfsShareClient?path={share_path}'
result = self.call(url, None, 'GET')
msg = 'Get all nfs access error.'
self._assert_result_code(result, msg)
access_list = []
if result['data'] is None:
pass
else:
for item in result.get('data', []):
access = {}
access['share_path'] = item['path']
access['access_to'] = item['clientName']
access['access_level'] = item['accessRight']
access_list.append(access)
return access_list
def _get_all_cifs_access_rest(self, share_path):
url = f'rest/allCifsShareClient?path={share_path}'
result = self.call(url, None, 'GET')
msg = 'Get all cifs access error.'
self._assert_result_code(result, msg)
access_list = []
for item in result.get('data', []):
access = {}
access['share_path'] = item['path']
access['access_to'] = item['ugName']
access['ugType'] = item['ugType']
access['access_level'] = item['accessRight']
access_list.append(access)
return access_list
def _change_nfs_access_rest(self, share_path, access_to, access_level):
url = 'rest/nfsShareClient'
access_info = {
'path': share_path,
'oldNfsClientName': access_to,
'clientName': '',
'accessRight': access_level,
'allSquash': '',
'rootSquash': '',
'secure': '',
'anonuid': '',
'anongid': '',
}
result = self.call(url, access_info, 'PUT')
msg = 'Update nfs acess error.'
self._assert_result_code(result, msg)
def _change_cifs_access_rest(self, share_path, access_to,
access_level, ug_type):
url = 'rest/cifsShareClient'
if '/' in access_to:
str = access_to.index('/')
access_to = access_to[str + 1:]
access_info = {
'path': share_path,
'right': access_level,
'ugName': access_to,
'ugType': ug_type,
}
result = self.call(url, access_info, 'PUT')
msg = 'Update cifs access error.'
self._assert_result_code(result, msg)
def _delete_nfs_access_rest(self, share_path, access_to):
url = f'rest/nfsShareClient?path={share_path}&client={access_to}'
result = self.call(url, None, 'DELETE')
msg = 'Delete nfs access error.'
self._assert_result_code(result, msg)
def _delete_cifs_access_rest(self, share_path, access_to, ug_type):
url = f'rest/cifsShareClient?path={share_path}&ugName={access_to}' \
f'&ugType={ug_type}'
result = self.call(url, None, 'DELETE')
msg = 'Delete cifs access error.'
self._assert_result_code(result, msg)
def _get_nfs_service_status(self):
url = 'rest/nfsService'
result = self.call(url, None, 'GET')
msg = 'Get NFS service stauts error.'
self._assert_result_code(result, msg)
nfs_service = {}
nfs_service['serviceStatus'] = result['data']['serviceStatus']
nfs_service['nfs3Status'] = result['data']['nfs3Status']
nfs_service['nfs4Status'] = result['data']['nfs4Status']
return nfs_service
def _start_nfs_service(self):
url = 'rest/nfsService'
nfs_service_info = {
"openStatus": "1",
}
result = self.call(url, nfs_service_info, 'PUT')
self._assert_result_code(result, 'Start NFS service error.')
def _config_nfs_service(self):
url = 'rest/nfsConfig'
config_nfs = {
'configNfs3': "yes",
'configNfs4': "yes",
}
result = self.call(url, config_nfs, 'PUT')
self._assert_result_code(result, 'Config NFS service error.')
def _get_cifs_service_status(self):
url = 'rest/cifsService'
result = self.call(url, None, 'GET')
msg = 'Get CIFS service status error.'
self._assert_result_code(result, msg)
return result['data']
def _start_cifs_service(self):
url = 'rest/cifsService'
cifs_service_info = {
'openStatus': '1',
}
result = self.call(url, cifs_service_info, 'PUT')
self._assert_result_code(result, 'Start CIFS service error.')
def _config_cifs_service(self):
url = 'rest/cifsConfig'
"""config user mode"""
config_cifs = {
'workName': 'manila',
'description': '',
'access_way': 'user',
'isCache': 'no',
'adsName': '',
'adsIP': '',
'adsUSER': '',
'adsPASSWD': '',
'allowList': [],
'denyList': [],
}
result = self.call(url, config_cifs, 'PUT')
self._assert_result_code(result, 'Config CIFS service error.')
def _get_all_pool(self):
url = 'rest/storagepool'
result = self.call(url, None, 'GET')
msg = 'Query pool info error.'
self._assert_result_code(result, msg)
return result
def _query_user(self, user_name):
url = f'rest/user/{user_name}'
result = self.call(url, None, 'GET')
msg = 'Query user error.'
self._assert_result_code(result, msg)
return result['data']
def _add_localuser(self, user_name, user_passwd, group_name):
url = 'rest/localUser'
user_info = {
'userName': user_name,
'mgGroup': group_name,
'userPasswd': user_passwd,
'unusedGroup': [],
}
result = self.call(url, user_info, 'POST')
msg = 'add localuser error.'
self._assert_result_code(result, msg)
def _query_group(self, group_name):
url = f'rest/group/{group_name}'
result = self.call(url, None, 'GET')
msg = 'Query group error.'
self._assert_result_code(result, msg)
return result['data']
def _add_localgroup(self, group_name):
url = 'rest/localGroup'
group_info = {
'groupName': group_name,
}
result = self.call(url, group_info, 'POST')
msg = 'add localgroup error.'
self._assert_result_code(result, msg)

View File

@ -71,6 +71,9 @@ def set_defaults(conf):
_safe_set_of_opts(conf, 'infortrend_share_pools', 'share-pool-01')
_safe_set_of_opts(conf, 'infortrend_share_channels', '0,1')
_safe_set_of_opts(conf, 'macrosan_nas_ip', '1.1.1.1')
_safe_set_of_opts(conf, 'macrosan_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')

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
---
features:
- Added Manila driver for Macrosan storage system.