Merge "Add Macrosan Manila Driver"
This commit is contained in:
commit
30cc734f0f
@ -94,6 +94,7 @@ each back end.
|
||||
hitachi_hnas_driver
|
||||
hpe_3par_driver
|
||||
infortrend_driver
|
||||
macrosan_driver
|
||||
purestorage_flashblade_driver
|
||||
tegile_driver
|
||||
nexentastor5_driver
|
||||
|
103
doc/source/admin/macrosan_driver.rst
Normal file
103
doc/source/admin/macrosan_driver.rst
Normal 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
|
@ -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 | \- | \- |
|
||||
|
@ -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")
|
||||
|
@ -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,
|
||||
|
0
manila/share/drivers/macrosan/__init__.py
Normal file
0
manila/share/drivers/macrosan/__init__.py
Normal file
44
manila/share/drivers/macrosan/macrosan_constants.py
Normal file
44
manila/share/drivers/macrosan/macrosan_constants.py
Normal 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
|
550
manila/share/drivers/macrosan/macrosan_helper.py
Normal file
550
manila/share/drivers/macrosan/macrosan_helper.py
Normal 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
|
182
manila/share/drivers/macrosan/macrosan_nas.py
Normal file
182
manila/share/drivers/macrosan/macrosan_nas.py
Normal 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)
|
661
manila/share/drivers/macrosan/rest_helper.py
Normal file
661
manila/share/drivers/macrosan/rest_helper.py
Normal 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)
|
@ -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')
|
||||
|
0
manila/tests/share/drivers/macrosan/__init__.py
Normal file
0
manila/tests/share/drivers/macrosan/__init__.py
Normal file
2287
manila/tests/share/drivers/macrosan/test_macrosan_nas.py
Normal file
2287
manila/tests/share/drivers/macrosan/test_macrosan_nas.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Added Manila driver for Macrosan storage system.
|
Loading…
Reference in New Issue
Block a user