diff --git a/doc/source/admin/index.rst b/doc/source/admin/index.rst index dc08f83954..724fabadec 100644 --- a/doc/source/admin/index.rst +++ b/doc/source/admin/index.rst @@ -94,6 +94,7 @@ each back end. hitachi_hnas_driver hpe_3par_driver infortrend_driver + macrosan_driver purestorage_flashblade_driver tegile_driver nexentastor5_driver diff --git a/doc/source/admin/macrosan_driver.rst b/doc/source/admin/macrosan_driver.rst new file mode 100644 index 0000000000..44c8c62746 --- /dev/null +++ b/doc/source/admin/macrosan_driver.rst @@ -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 `__ 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` = +- `share_driver` = manila.share.drivers.macrosan.macrosan_nas.MacrosanNasDriver +- `driver_handles_share_servers` = False +- `macrosan_nas_ip` = +- `macrosan_nas_port` = +- `macrosan_nas_user` = +- `macrosan_nas_password` = +- `macrosan_share_pools` = + + +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 diff --git a/doc/source/admin/share_back_ends_feature_support_mapping.rst b/doc/source/admin/share_back_ends_feature_support_mapping.rst index 6c533371ba..f0d93c4a95 100644 --- a/doc/source/admin/share_back_ends_feature_support_mapping.rst +++ b/doc/source/admin/share_back_ends_feature_support_mapping.rst @@ -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 | \- | \- | diff --git a/manila/exception.py b/manila/exception.py index 204e85124a..b74ad6975a 100644 --- a/manila/exception.py +++ b/manila/exception.py @@ -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") diff --git a/manila/opts.py b/manila/opts.py index 8dceb2e1ba..4121bf9841 100644 --- a/manila/opts.py +++ b/manila/opts.py @@ -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, diff --git a/manila/share/drivers/macrosan/__init__.py b/manila/share/drivers/macrosan/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/manila/share/drivers/macrosan/macrosan_constants.py b/manila/share/drivers/macrosan/macrosan_constants.py new file mode 100644 index 0000000000..dc0fb61ccf --- /dev/null +++ b/manila/share/drivers/macrosan/macrosan_constants.py @@ -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 diff --git a/manila/share/drivers/macrosan/macrosan_helper.py b/manila/share/drivers/macrosan/macrosan_helper.py new file mode 100644 index 0000000000..c5e704943e --- /dev/null +++ b/manila/share/drivers/macrosan/macrosan_helper.py @@ -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 diff --git a/manila/share/drivers/macrosan/macrosan_nas.py b/manila/share/drivers/macrosan/macrosan_nas.py new file mode 100644 index 0000000000..3f8700975f --- /dev/null +++ b/manila/share/drivers/macrosan/macrosan_nas.py @@ -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) diff --git a/manila/share/drivers/macrosan/rest_helper.py b/manila/share/drivers/macrosan/rest_helper.py new file mode 100644 index 0000000000..4b77182199 --- /dev/null +++ b/manila/share/drivers/macrosan/rest_helper.py @@ -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) diff --git a/manila/tests/conf_fixture.py b/manila/tests/conf_fixture.py index 61f3506446..915cb7670e 100644 --- a/manila/tests/conf_fixture.py +++ b/manila/tests/conf_fixture.py @@ -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') diff --git a/manila/tests/share/drivers/macrosan/__init__.py b/manila/tests/share/drivers/macrosan/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/manila/tests/share/drivers/macrosan/test_macrosan_nas.py b/manila/tests/share/drivers/macrosan/test_macrosan_nas.py new file mode 100644 index 0000000000..49b3fdae75 --- /dev/null +++ b/manila/tests/share/drivers/macrosan/test_macrosan_nas.py @@ -0,0 +1,2287 @@ +# 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 test for Macrosan Storage Array. +""" + +import ddt +import requests + +from oslo_config import cfg +from unittest import mock + +from manila import context +from manila import exception +from manila import test + +from manila.share import configuration +from manila.share import driver +from manila.share.drivers.macrosan import macrosan_constants as constants +from manila.share.drivers.macrosan import macrosan_helper +from manila.share.drivers.macrosan import macrosan_nas +from manila.share.drivers.macrosan import rest_helper +from manila.tests import fake_share + +CONF = cfg.CONF + + +class FakeResponse(object): + def __init__(self, status, result): + self.status_code = status + self.text = 'return message' + self.response = result + + def json(self): + return self.response + + def close(self): + pass + + +@ddt.ddt +class MacrosanShareDriverTestCase(test.TestCase): + + def setUp(self): + self.mock_object(macrosan_nas.CONF, '_check_required_opts') + super(MacrosanShareDriverTestCase, self).setUp() + + def _safe_get(opt): + return getattr(self.configuration, opt) + + self._context = context.get_admin_context() + self.configuration = mock.Mock(spec=configuration.Configuration) + self.configuration.safe_get = mock.Mock(side_effect=_safe_get) + self.configuration.driver_handles_share_servers = False + self.configuration.share_backend_name = 'fake_share_backend_name' + self.configuration.macrosan_nas_http_protocol = 'https' + self.configuration.macrosan_nas_ip = 'fake_ip' + self.configuration.macrosan_nas_port = 'fake_port' + self.configuration.macrosan_nas_username = 'fake_username' + self.configuration.macrosan_nas_password = 'fake_password' + self.configuration.macrosan_nas_prefix = 'nas' + self.configuration.macrosan_share_pools = ['fake_pool'] + self.configuration.macrosan_timeout = 60 + + self.configuration.network_config_group = 'fake_network_config_group' + self.configuration.admin_network_config_group = ( + 'fake_admin_network_config_group') + self.configuration.config_group = 'fake_config_group' + self.configuration.reserved_share_percentage = 0 + self.configuration.filter_function = None + self.configuration.goodness_function = None + self.driver = macrosan_nas.MacrosanNasDriver( + configuration=self.configuration) + self.resutl_success_storage_pools = { + 'code': 0, + 'message': 'success', + 'data': [{ + 'name': 'fake_pool', + 'size': '1000.0G', + 'allocated': '100G', + 'free': '900G', + 'health': 'ONLINE', + 'rwStatus': 'off' + }] + } + + def test_do_setup(self): + mock_login = self.mock_object(rest_helper.RestHelper, 'login') + self.driver.do_setup(self._context) + mock_login.assert_called_once() + + def test_do_setup_login_fail(self): + mock_login = self.mock_object( + rest_helper.RestHelper, 'login', + mock.Mock( + side_effect=exception.ShareBackendException( + msg='fake_exception'))) + self.assertRaises(exception.ShareBackendException, + self.driver.do_setup, + self._context) + mock_login.assert_called_once() + + @ddt.data({'nfs_status': constants.NFS_NON_CONFIG, + 'cifs_status': constants.CIFS_NON_CONFIG}, + {'nfs_status': constants.NFS_DISABLED, + 'cifs_status': constants.CIFS_DISABLED}, + {'nfs_status': constants.NFS_ENABLED, + 'cifs_status': constants.CIFS_ENABLED}, + {'nfs_status': constants.NFS_ENABLED, + 'cifs_status': constants.CIFS_SHARE_MODE}) + @ddt.unpack + def test_check_for_setup_error_non_config(self, nfs_status, cifs_status): + mock_gnss = self.mock_object( + rest_helper.RestHelper, '_get_nfs_service_status', + mock.Mock(return_value={ + "serviceStatus": nfs_status, + "nfs3Status": constants.NFS_NON_SUPPORTED, + "nfs4Status": constants.NFS_NON_SUPPORTED + })) + + mock_cns = self.mock_object(rest_helper.RestHelper, + '_config_nfs_service') + mock_sns = self.mock_object(rest_helper.RestHelper, + '_start_nfs_service') + if cifs_status == constants.CIFS_DISABLED: + mock_gcss = self.mock_object( + rest_helper.RestHelper, '_get_cifs_service_status', + mock.Mock(side_effect=[cifs_status, + constants.CIFS_SHARE_MODE])) + else: + mock_gcss = self.mock_object( + rest_helper.RestHelper, '_get_cifs_service_status', + mock.Mock(return_value=cifs_status)) + mock_ccs = self.mock_object(rest_helper.RestHelper, + '_config_cifs_service') + mock_scs = self.mock_object(rest_helper.RestHelper, + '_start_cifs_service') + self.driver.check_for_setup_error() + if (nfs_status == constants.NFS_NON_CONFIG or + nfs_status == constants.NFS_DISABLED): + mock_cns.assert_called_once() + mock_sns.assert_called_once() + else: + mock_cns.assert_called_once() + mock_gnss.assert_called_once() + if cifs_status == constants.CIFS_NON_CONFIG: + mock_gcss.assert_called_once() + mock_ccs.assert_called_once() + mock_scs.assert_called_once() + elif cifs_status == constants.CIFS_DISABLED: + mock_gcss.assert_called() + mock_ccs.assert_called_once() + mock_scs.assert_called_once() + elif cifs_status == constants.CIFS_SHARE_MODE: + mock_gcss.assert_called_once() + mock_ccs.assert_called_once() + else: + mock_gcss.assert_called_once() + + def test_check_for_setup_error_nfs_service_error(self): + mock_gnss = self.mock_object( + rest_helper.RestHelper, '_get_nfs_service_status', + mock.Mock(return_value={ + "serviceStatus": constants.NFS_EXCEPTION, + "nfs3Status": constants.NFS_NON_SUPPORTED, + "nfs4Status": constants.NFS_NON_SUPPORTED + })) + self.assertRaises(exception.MacrosanBackendExeption, + self.driver.check_for_setup_error) + mock_gnss.assert_called_once() + + def test_check_for_setup_error_cifs_service_error(self): + mock_gnss = self.mock_object( + rest_helper.RestHelper, '_get_nfs_service_status', + mock.Mock(return_value={ + "serviceStatus": constants.NFS_ENABLED, + "nfs3Status": constants.NFS_SUPPORTED, + "nfs4Status": constants.NFS_SUPPORTED + })) + mock_gcss = self.mock_object( + rest_helper.RestHelper, '_get_cifs_service_status', + mock.Mock(return_value=constants.CIFS_EXCEPTION)) + self.assertRaises(exception.MacrosanBackendExeption, + self.driver.check_for_setup_error) + mock_gnss.assert_called_once() + mock_gcss.assert_called_once() + + @ddt.data('nfs', 'cifs') + def test_create_share(self, share_proto): + share = fake_share.fake_share( + share_proto=share_proto, host="fake_host@fake_backend#fake_pool") + self.mock_object(rest_helper.RestHelper, '_get_all_pool') + self.mock_object( + macrosan_helper.MacrosanHelper, '_find_pool_info', + mock.Mock(return_value={ + "name": "fake_pool", + "totalcapacity": "10G", + "allocatedcapacity": "0G", + "freecapacity": "10G", + "health": "ONLINE", + "rw": "off" + })) + mock_cf = self.mock_object(rest_helper.RestHelper, + '_create_filesystem') + mock_cfd = self.mock_object(rest_helper.RestHelper, + '_create_filesystem_dir') + mock_cns = self.mock_object(rest_helper.RestHelper, + '_create_nfs_share') + self.mock_object(macrosan_helper.MacrosanHelper, + '_ensure_user', + mock.Mock(return_value=True)) + mock_ccs = self.mock_object(rest_helper.RestHelper, + '_create_cifs_share') + self.driver.helper.configuration.macrosan_nas_ip = "172.0.0.1" + + location = self.driver.create_share(self._context, share) + if share_proto == 'nfs': + expect_location = r'172.0.0.1:/manila_fakeid/manila_fakeid' + print('test location:', location) + self.assertEqual(location, expect_location) + else: + expect_location = r'\\172.0.0.1\manila_fakeid' + self.assertEqual(location, expect_location) + mock_cf.assert_called_once_with(fs_name='manila_fakeid', + pool_name='fake_pool', + filesystem_quota='1GB') + mock_cf.assert_called() + share_path = self.driver.helper._generate_share_path('manila_fakeid') + mock_cfd.assert_called_once_with(share_path) + if share_proto == 'nfs': + mock_cns.assert_called_once_with(share_path=share_path) + else: + mock_ccs.assert_called_once() + + def test_create_share_pool_fail(self): + share = fake_share.fake_share( + share_proto='nfs', host="fake_host@fake_backend#fake_pool") + self.mock_object(rest_helper.RestHelper, '_get_all_pool') + self.mock_object(macrosan_helper.MacrosanHelper, '_find_pool_info', + mock.Mock(return_value=None)) + self.mock_object( + rest_helper.RestHelper, '_create_filesystem') + self.mock_object( + rest_helper.RestHelper, '_create_nfs_share') + + self.assertRaises(exception.InvalidHost, + self.driver.create_share, + self._context, + share) + + def test_create_share_user_error(self): + share = fake_share.fake_share( + share_proto='cifs', host="fake_host@fake_backend#fake_pool") + self.mock_object(rest_helper.RestHelper, '_get_all_pool') + self.mock_object( + macrosan_helper.MacrosanHelper, '_find_pool_info', + mock.Mock(return_value={ + "name": "fake_pool", + "totalcapacity": "10G", + "allocatedcapacity": "0G", + "freecapacity": "10G", + "health": "ONLINE", + "rw": "off" + })) + mock_cf = self.mock_object(rest_helper.RestHelper, + '_create_filesystem') + mock_cfd = self.mock_object(rest_helper.RestHelper, + '_create_filesystem_dir') + self.mock_object(macrosan_helper.MacrosanHelper, + '_ensure_user', + mock.Mock(return_value=False)) + mock_df = self.mock_object(rest_helper.RestHelper, + '_delete_filesystem') + self.assertRaises(exception.MacrosanBackendExeption, + self.driver.create_share, + self._context, + share) + mock_cf.assert_called_once() + share_path = self.driver.helper._generate_share_path('manila_fakeid') + mock_cfd.assert_called_once_with(share_path) + mock_df.assert_called_once_with('manila_fakeid') + + @ddt.data('nfs', 'cifs') + def test_delete_share(self, share_proto): + share = fake_share.fake_share( + share_proto=share_proto, host="fake_host@fake_backend#fake_pool") + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + + mock_gns = self.mock_object(rest_helper.RestHelper, '_get_nfs_share', + mock.Mock(return_value={ + "path": "fake_path", + "clients": ["client"], + "protocol": "fake_protocol" + })) + mock_dns = self.mock_object( + rest_helper.RestHelper, '_delete_nfs_share') + mock_gcs = self.mock_object(rest_helper.RestHelper, '_get_cifs_share', + mock.Mock(return_value={ + "path": "fake_path", + "cifsname": "fake_cifsname", + "protocol": "fake_protocol", + "roList": ["fake_ro"], + "rwList": ["fake_rw"], + "allowList": ["fake_allow"], + "denyList": ["fake_deny"] + })) + mock_dcs = self.mock_object(rest_helper.RestHelper, + '_delete_cifs_share') + mock_df = self.mock_object(rest_helper.RestHelper, + '_delete_filesystem') + self.driver.delete_share(self._context, share) + + if share_proto == "nfs": + mock_gns.assert_called_once_with(expect_share_path) + mock_dns.assert_called_once_with(expect_share_path) + else: + mock_gcs.assert_called_once_with(expect_share_path) + mock_dcs.assert_called_once_with('manila_fakeid', + expect_share_path) + mock_df.assert_called_once_with('manila_fakeid') + + @ddt.data('nfs', 'cifs') + def test_delete_share_not_exist(self, share_proto): + share = fake_share.fake_share(share_proto=share_proto, + host="fake_host@fake_backend#fake_pool") + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + mock_gns = self.mock_object(rest_helper.RestHelper, '_get_nfs_share', + mock.Mock(return_value=None)) + mock_gf = self.mock_object(rest_helper.RestHelper, '_get_filesystem', + mock.Mock(return_value={ + "name": "fake_name", + "poolName": "fake_pool", + "quotaStatus": "1GB" + })) + mock_gcs = self.mock_object(rest_helper.RestHelper, '_get_cifs_share', + mock.Mock(return_value=None)) + mock_df = self.mock_object(rest_helper.RestHelper, + '_delete_filesystem') + self.driver.delete_share(self._context, share) + + if share_proto == 'nfs': + mock_gns.assert_called_once_with(expect_share_path) + else: + mock_gcs.assert_called_once_with(expect_share_path) + + mock_gf.assert_called_once_with('manila_fakeid') + mock_df.assert_called_once_with('manila_fakeid') + + @ddt.data('nfs', 'cifs') + def test_extend_share(self, share_proto): + share = fake_share.fake_share(share_proto=share_proto, + host="fake_host@fake_backend#fake_pool") + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + mock_gns = self.mock_object(rest_helper.RestHelper, '_get_nfs_share', + mock.Mock(return_value={ + "path": "fake_path", + "clients": ["client"], + "protocol": "fake_protocol" + })) + mock_gcs = self.mock_object(rest_helper.RestHelper, '_get_cifs_share', + mock.Mock(return_value={ + "path": "fake_path", + "cifsname": "fake_cifsname", + "protocol": "fake_protocol", + "roList": ["fake_ro"], + "rwList": ["fake_rw"], + "allowList": ["fake_allow"], + "denyList": ["fake_deny"] + })) + mock_uss = self.mock_object(rest_helper.RestHelper, + '_update_share_size') + self.driver.extend_share(share, 2) + + if share_proto == 'nfs': + mock_gns.assert_called_once_with(expect_share_path) + else: + mock_gcs.assert_called_once_with(expect_share_path) + + mock_uss.assert_called_once_with('manila_fakeid', '2GB') + + def test_extend_share_not_exist(self): + share = fake_share.fake_share(share_proto='nfs', + size=1, + host="fake_host@fake_backend#fake_pool") + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + mock_gns = self.mock_object(rest_helper.RestHelper, + '_get_nfs_share', + mock.Mock(return_value=None)) + self.assertRaises(exception.ShareResourceNotFound, + self.driver.extend_share, + share, + 2) + + mock_gns.assert_called_once_with(expect_share_path) + + @ddt.data('nfs', 'cifs') + def test_shrink_share(self, share_proto): + share = fake_share.fake_share(share_proto=share_proto, + size=5, + host="fake_host@fake_backend#fake_pool") + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + mock_gns = self.mock_object(rest_helper.RestHelper, + '_get_nfs_share', + mock.Mock(return_value={ + "path": "fake_path", + "clients": ["client"], + "protocol": "fake_protocol" + })) + mock_gcs = self.mock_object(rest_helper.RestHelper, '_get_cifs_share', + mock.Mock(return_value={ + "path": "fake_path", + "cifsname": "fake_cifsname", + "protocol": "fake_protocol", + "roList": ["fake_ro"], + "rwList": ["fake_rw"], + "allowList": ["fake_allow"], + "denyList": ["fake_deny"] + })) + mock_gf = self.mock_object(rest_helper.RestHelper, '_get_filesystem', + mock.Mock(return_value={ + "name": "fake_name", + "poolName": "fake_pool", + "quotaStatus": "5GB", + "usedCapacity": '1GB' + })) + mock_uss = self.mock_object(rest_helper.RestHelper, + '_update_share_size') + self.driver.shrink_share(share, 3) + if share_proto == 'nfs': + mock_gns.assert_called_once_with(expect_share_path) + else: + mock_gcs.assert_called_once_with(expect_share_path) + + mock_gf.assert_called_once_with('manila_fakeid') + mock_uss.assert_called_once_with('manila_fakeid', '3GB') + + @ddt.data('nfs', 'cifs') + def test_shrink_share_not_exist(self, share_proto): + share = fake_share.fake_share(share_proto=share_proto, + size=3, + host="fake_host@fake_backend#fake_pool") + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + mock_gns = self.mock_object(rest_helper.RestHelper, '_get_nfs_share', + mock.Mock(return_value=None)) + mock_gcs = self.mock_object(rest_helper.RestHelper, '_get_cifs_share', + mock.Mock(return_value=None)) + + self.assertRaises(exception.ShareResourceNotFound, + self.driver.shrink_share, + share, + 1) + if share_proto == 'nfs': + mock_gns.assert_called_once_with(expect_share_path) + elif share_proto == 'cifs': + mock_gcs.assert_called_once_with(expect_share_path) + + def test_shrink_share_size_fail(self): + share = fake_share.fake_share(share_proto='nfs', + size=3, + host="fake_host@fake_backend#fake_pool") + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + mock_gns = self.mock_object(rest_helper.RestHelper, + '_get_nfs_share', + mock.Mock(return_value={ + "path": "fake_path", + "clients": ["client"], + "protocol": "fake_protocol" + })) + mock_gf = self.mock_object(rest_helper.RestHelper, '_get_filesystem', + mock.Mock(return_value={ + "name": "fake_name", + "poolName": "fake_pool", + "quotaStatus": "3GB", + "usedCapacity": '2GB' + })) + self.assertRaises(exception.ShareShrinkingPossibleDataLoss, + self.driver.shrink_share, + share, + 1) + mock_gf.assert_called_once_with('manila_fakeid') + mock_gns.assert_called_once_with(expect_share_path) + + @ddt.data('nfs', 'cifs') + def test_ensure_share(self, share_proto): + share = fake_share.fake_share(share_proto=share_proto, + host="fake_host@fake_backend#fake_pool") + mock_gns = self.mock_object(rest_helper.RestHelper, '_get_nfs_share', + mock.Mock(return_value={ + "path": "fake_path", + "clients": ["client"], + "protocol": "fake_protocol" + })) + mock_gcs = self.mock_object(rest_helper.RestHelper, '_get_cifs_share', + mock.Mock(return_value={ + "path": "fake_path", + "cifsname": "fake_cifsname", + "protocol": "fake_protocol", + "roList": ["fake_ro"], + "rwList": ["fake_rw"], + "allowList": ["fake_allow"], + "denyList": ["fake_deny"], + })) + self.driver.helper.configuration.macrosan_nas_ip = "172.0.0.1" + locations = self.driver.ensure_share(self._context, share) + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + if share_proto == 'nfs': + expect_locations = [r'172.0.0.1:/manila_fakeid/manila_fakeid'] + self.assertEqual(locations, expect_locations) + mock_gns.assert_called_once_with(expect_share_path) + else: + expect_locations = [r'\\172.0.0.1\manila_fakeid'] + self.assertEqual(locations, expect_locations) + mock_gcs.assert_called_once_with(expect_share_path) + + def test_ensure_share_proto_fail(self): + share = fake_share.fake_share(host="fake_host@fake_backend#fake_pool") + self.mock_object(rest_helper.RestHelper, '_get_nfs_share', + mock.Mock(return_value={ + "path": "fake_path", + "clients": ["client"], + "protocol": "fake_protocol" + })) + self.assertRaises(exception.MacrosanBackendExeption, + self.driver.ensure_share, + self._context, + share) + + def test_ensure_share_not_exist(self): + share = fake_share.fake_share(share_proto='nfs', + host="fake_host@fake_backend#fake_pool") + mock_gns = self.mock_object(rest_helper.RestHelper, '_get_nfs_share', + mock.Mock(return_value=None)) + self.assertRaises(exception.ShareResourceNotFound, + self.driver.ensure_share, + self._context, + share) + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + mock_gns.assert_called_once_with(expect_share_path) + + @ddt.data('nfs', 'cifs') + def test_allow_access_success(self, share_proto): + share = fake_share.fake_share(share_proto=share_proto, + host="fake_host@fake_backend#fake_pool") + if share_proto == 'nfs': + access = { + 'access_type': 'ip', + 'access_to': '0.0.0.0/0', + 'access_level': 'rw', + } + else: + access = { + 'access_type': 'user', + 'access_to': 'fake_user', + 'access_level': 'rw', + } + mock_gns = self.mock_object(rest_helper.RestHelper, + '_get_nfs_share', + mock.Mock(return_value={ + "path": "fake_path", + "clients": ["client"], + "protocol": "fake_protocol" + })) + mock_gafns = self.mock_object(rest_helper.RestHelper, + '_get_access_from_nfs_share', + mock.Mock(return_value=None)) + mock_anar = self.mock_object(rest_helper.RestHelper, + '_allow_nfs_access_rest') + mock_gcs = self.mock_object(rest_helper.RestHelper, + '_get_cifs_share', + mock.Mock(return_value={ + "path": "fake_path", + "cifsname": "fake_cifsname", + "protocol": "fake_protocol", + "roList": ["fake_ro"], + "rwList": ["fake_rw"], + "allowList": ["fake_allow"], + "denyList": ["fake_deny"], + })) + mock_gafcs = self.mock_object(rest_helper.RestHelper, + '_get_access_from_cifs_share', + mock.Mock(return_value=None)) + mock_acar = self.mock_object(rest_helper.RestHelper, + '_allow_cifs_access_rest') + self.driver.helper._allow_access(share, access) + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + if access['access_to'] == '0.0.0.0/0': + access['access_to'] = '*' + if share_proto == 'nfs': + mock_gns.assert_called_once_with(expect_share_path) + mock_gafns.assert_called_once_with(expect_share_path, + access['access_to']) + mock_anar.assert_called_once_with(expect_share_path, + access['access_to'], + access['access_level']) + else: + mock_gcs.assert_called_once_with(expect_share_path) + mock_gafcs.assert_called_once_with(expect_share_path, + access['access_to']) + mock_acar.assert_called_once_with(expect_share_path, + access['access_to'], + access['access_level']) + + def test_allow_access_nfs_change(self): + share = fake_share.fake_share(share_proto='nfs', + host="fake_host@fake_backend#fake_pool") + access = { + 'access_type': 'ip', + 'access_to': '172.0.0.1', + 'access_level': 'rw', + } + mock_gns = self.mock_object(rest_helper.RestHelper, '_get_nfs_share', + mock.Mock(return_value={ + "path": "/manila_fakeid", + "clients": ["client"], + "protocol": "fake_protocol" + })) + mock_gafns = self.mock_object(rest_helper.RestHelper, + '_get_access_from_nfs_share', + mock.Mock(return_value={ + "path": "/manila_fakeid", + "clientName": "fake_client_name", + "accessRight": "ro", + })) + mock_cnar = self.mock_object(rest_helper.RestHelper, + '_change_nfs_access_rest') + self.driver.helper._allow_access(share, access) + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + mock_gns.assert_called_once_with(expect_share_path) + mock_gafns.assert_called_once_with(expect_share_path, + access['access_to']) + mock_cnar.assert_called_once_with(expect_share_path, + access['access_to'], + access['access_level']) + + def test_allow_access_cifs_change(self): + share = fake_share.fake_share(share_proto='cifs', + host="fake_host@fake_backend#fake_pool") + access = { + 'access_type': 'user', + 'access_to': 'fake_user', + 'access_level': 'rw', + } + mock_gcs = self.mock_object(rest_helper.RestHelper, + '_get_cifs_share', + mock.Mock(return_value={ + "path": "fake_path", + "cifsname": "fake_cifsname", + "protocol": "fake_protocol", + "roList": ["fake_ro"], + "rwList": ["fake_rw"], + "allowList": ["fake_allow"], + "denyList": ["fake_deny"], + })) + mock_gafcs = self.mock_object(rest_helper.RestHelper, + '_get_access_from_cifs_share', + mock.Mock(return_value={ + "path": "fake_path", + "ugName": "fake_user", + "ugType": "0", + "accessRight": "ro", + })) + mock_ccar = self.mock_object(rest_helper.RestHelper, + '_change_cifs_access_rest') + self.driver.helper._allow_access(share, access) + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + mock_gcs.assert_called_once_with(expect_share_path) + mock_gafcs.assert_called_once_with(expect_share_path, + access['access_to']) + mock_ccar.assert_called_once_with(expect_share_path, + access['access_to'], + access['access_level'], + '0') + + @ddt.data( + { + 'access_type': 'user', + 'access_to': 'user_name', + 'access_level': 'rw', + }, + { + 'access_type': 'user', + 'access_to': 'group_name', + 'access_level': 'rw', + }, + { + 'access_type': 'user', + 'access_to': '/domain_user', + 'access_level': 'rw', + }, + { + 'access_type': 'user', + 'access_to': '/domain_group', + 'access_level': 'rw', + }, + ) + def test_allow_access_cifs(self, access): + share = fake_share.fake_share(share_proto='cifs', + host="fake_host@fake_backend#fake_pool") + mock_gcs = self.mock_object(rest_helper.RestHelper, + '_get_cifs_share', + mock.Mock(return_value={ + "path": "fake_path", + "cifsname": "fake_cifsname", + "protocol": "fake_protocol", + "roList": ["fake_ro"], + "rwList": ["fake_rw"], + "allowList": ["fake_allow"], + "denyList": ["fake_deny"], + })) + mock_gafcs = self.mock_object(rest_helper.RestHelper, + '_get_access_from_cifs_share', + mock.Mock(return_value=None)) + mock_acar = self.mock_object(rest_helper.RestHelper, + '_allow_cifs_access_rest') + + self.driver.helper._allow_access(share, access) + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + mock_gcs.assert_called_once_with(expect_share_path) + mock_gafcs.assert_called_once_with(expect_share_path, + access['access_to']) + mock_acar.assert_called_once_with(expect_share_path, + access['access_to'], + access['access_level']) + + @ddt.data('nfs', 'cifs') + def test_allow_access_share_not_exist(self, share_proto): + share = fake_share.fake_share(share_proto=share_proto, + host="fake_host@fake_backend#fake_pool") + access = {} + if share_proto == 'nfs': + access = { + 'access_type': 'ip', + 'access_to': '172.0.0.1', + 'access_level': 'rw', + + } + else: + access = { + 'access_type': 'user', + 'access_to': 'fake_user', + 'access_level': 'rw', + } + mock_gns = self.mock_object(rest_helper.RestHelper, '_get_nfs_share', + mock.Mock(return_value=None)) + mock_gcs = self.mock_object(rest_helper.RestHelper, '_get_cifs_share', + mock.Mock(return_value=None)) + self.assertRaises(exception.ShareResourceNotFound, + self.driver.helper._allow_access, + share, + access) + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + if share_proto == 'nfs': + mock_gns.assert_called_once_with(expect_share_path) + else: + mock_gcs.assert_called_once_with(expect_share_path) + + def test_allow_access_proto_fail(self): + share = fake_share.fake_share(host="fake_host@fake_backend#fake_pool") + access = { + 'access_type': 'user', + 'access_to': 'fake_user', + 'access_level': 'rw', + } + self.assertRaises(exception.MacrosanBackendExeption, + self.driver.helper._allow_access, + share, + access) + + def test_allow_access_nfs_user_fail(self): + share = fake_share.fake_share(share_proto='nfs', + host="fake_host@fake_backend#fake_pool") + access = { + 'access_type': 'user', + 'access_to': 'fake_user', + 'access_level': 'rw', + } + self.assertRaises(exception.InvalidShareAccess, + self.driver.helper._allow_access, + share, + access) + + def test_allow_access_cifs_ip_fail(self): + share = fake_share.fake_share(share_proto='cifs', + host="fake_host@fake_backend#fake_pool") + access = { + 'access_type': 'ip', + 'access_to': '172.0.0.1', + 'access_level': 'rw', + } + self.assertRaises(exception.InvalidShareAccess, + self.driver.helper._allow_access, + share, + access) + + def test_allow_access_nfs_level_fail(self): + share = fake_share.fake_share(share_proto='nfs', + host="fake_host@fake_backend#fake_pool") + access = { + 'access_type': 'ip', + 'access_to': '172.0.0.1', + 'access_level': 'r', + } + self.assertRaises(exception.InvalidShareAccess, + self.driver.helper._allow_access, + share, + access) + + @ddt.data('nfs', 'cifs') + def test_deny_access(self, share_proto): + share = fake_share.fake_share(share_proto=share_proto, + host="fake_host@fake_backend#fake_pool") + if share_proto == 'nfs': + access = { + 'access_type': 'ip', + 'access_to': '0.0.0.0/0', + 'access_level': 'rw', + } + else: + access = { + 'access_type': 'user', + 'access_to': 'fake_user', + 'access_level': 'rw', + } + mock_gafns = self.mock_object(rest_helper.RestHelper, + '_get_access_from_nfs_share', + mock.Mock(return_value={ + "path": "fake_path", + "clientName": "fake_client_name", + "accessRight": "rw", + })) + mock_dnar = self.mock_object(rest_helper.RestHelper, + '_delete_nfs_access_rest') + mock_gafcs = self.mock_object(rest_helper.RestHelper, + '_get_access_from_cifs_share', + mock.Mock(return_value={ + "path": "fake_path", + "ugName": "fake_user", + "ugType": "0", + "accessRight": "rw", + })) + mock_dcar = self.mock_object(rest_helper.RestHelper, + '_delete_cifs_access_rest') + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + self.driver.helper._deny_access(share, access) + if access['access_to'] == '0.0.0.0/0': + access['access_to'] = '*' + if share_proto == 'nfs': + mock_gafns.assert_called_once_with(expect_share_path, + access['access_to']) + mock_dnar.assert_called_once_with(expect_share_path, + access['access_to']) + else: + mock_gafcs.assert_called_once_with(expect_share_path, + access['access_to']) + mock_dcar.assert_called_once_with(expect_share_path, + "fake_user", "0") + + def test_deny_access_nfs_type_fail(self): + share = fake_share.fake_share(share_proto='nfs', + host="fake_host@fake_backend#fake_pool") + access = { + 'access_type': 'fake_type', + 'access_to': '172.0.0.1', + 'access_level': 'rw', + } + result = self.driver.helper._deny_access(share, access) + self.assertIsNone(result) + + def test_deny_access_nfs_share_not_exist(self): + share = fake_share.fake_share(share_proto='nfs', + host="fake_host@fake_backend#fake_pool") + access = { + 'access_type': 'ip', + 'access_to': '172.0.0.1', + 'access_level': 'rw', + } + mock_gafns = self.mock_object(rest_helper.RestHelper, + '_get_access_from_nfs_share', + mock.Mock(return_value=None)) + result = self.driver.helper._deny_access(share, access) + self.assertIsNone(result) + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + mock_gafns.assert_called_once_with(expect_share_path, + access['access_to']) + + def test_deny_access_cifs_type_fail(self): + share = fake_share.fake_share(share_proto='cifs', + host="fake_host@fake_backend#fake_pool") + access = { + 'access_type': 'fake_type', + 'access_to': 'fake_user', + 'access_level': 'rw', + } + result = self.driver.helper._deny_access(share, access) + self.assertIsNone(result) + + def test_deny_access_cifs_share_not_exist(self): + share = fake_share.fake_share(share_proto='cifs', + host="fake_host@fake_backend#fake_pool") + access = { + 'access_type': 'user', + 'access_to': 'fake_user', + 'access_level': 'rw', + } + mock_gafcs = self.mock_object(rest_helper.RestHelper, + '_get_access_from_cifs_share', + mock.Mock(return_value=None)) + result = self.driver.helper._deny_access(share, access) + self.assertIsNone(result) + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + mock_gafcs.assert_called_once_with(expect_share_path, + access['access_to']) + + def test_update_access_add_delete(self): + share = fake_share.fake_share(share_proto='nfs', + host="fake_host@fake_backend#fake_pool") + add_rules = [{'access_type': 'ip', + 'access_to': '172.0.2.1', + 'access_level': 'rw', }] + delete_rules = [{'access_type': 'ip', + 'access_to': '172.0.2.2', + 'access_level': 'rw', }] + self.mock_object(macrosan_helper.MacrosanHelper, + '_allow_access') + self.mock_object(macrosan_helper.MacrosanHelper, + '_deny_access') + self.driver.update_access(self._context, share, + None, add_rules, delete_rules) + + @ddt.data('nfs', 'cifs') + def test_update_access_nfs(self, proto): + share = fake_share.fake_share(share_proto=proto, + host="fake_host@fake_backend#fake_pool") + if proto == 'nfs': + access_rules = [{'access_type': 'ip', + 'access_to': '172.0.3.1', + 'access_level': 'rw', }, + {'access_type': 'ip', + 'access_to': '172.0.3.2', + 'access_level': 'rw', }] + else: + access_rules = [{'access_type': 'user', + 'access_to': 'user_l', + 'access_level': 'rw', }, + {'access_type': 'user', + 'access_to': 'user_a', + 'access_level': 'rw', }] + mock_ca = self.mock_object(macrosan_helper.MacrosanHelper, + '_clear_access') + self.mock_object(macrosan_helper.MacrosanHelper, + '_allow_access') + self.driver.update_access(self._context, share, + access_rules, {}, {}) + mock_ca.assert_called_once_with(share, None) + + def test_update_access_fail(self): + share = fake_share.fake_share(share_proto='nfs', + host="fake_host@fake_backend#fake_pool") + access_rules = [{'access_id': 'fakeid', + 'access_type': 'ip', + 'access_to': '172.0.3.1', + 'access_level': 'rw', }] + mock_ca = self.mock_object(macrosan_helper.MacrosanHelper, + '_clear_access') + self.mock_object(macrosan_helper.MacrosanHelper, + '_allow_access', + mock.Mock(side_effect=exception.InvalidShareAccess( + reason='fake_exception'))) + result = self.driver.update_access(self._context, share, + access_rules, None, None) + expect = { + 'fakeid': { + 'state': 'error', + } + } + self.assertEqual(result, expect) + mock_ca.assert_called_once_with(share, None) + + def test_update_access_add_fail(self): + share = fake_share.fake_share(share_proto='nfs', + host="fake_host@fake_backend#fake_pool") + add_rules = [{'access_id': 'fakeid', + 'access_type': 'ip', + 'access_to': '172.0.2.1', + 'access_level': 'rw', }] + delete_rules = [] + self.mock_object(macrosan_helper.MacrosanHelper, + '_allow_access', + mock.Mock(side_effect=exception.InvalidShareAccess( + reason='fake_exception'))) + self.mock_object(macrosan_helper.MacrosanHelper, + '_deny_access') + result = self.driver.update_access(self._context, share, + None, add_rules, delete_rules) + expect = { + 'fakeid': { + 'state': 'error' + } + } + self.assertEqual(result, expect) + + @ddt.data('nfs', 'cifs') + def test__clear_access(self, share_proto): + share = fake_share.fake_share(share_proto=share_proto, + host="fake_host@fake_backend#fake_pool") + fake_nfs_share_backend = [ + { + 'share_path': 'fake_path', + 'access_to': '172.0.0.1', + 'access_level': 'rw' + }, + { + 'share_path': 'default_path', + 'access_to': '172.0.0.2', + 'access_level': 'rw' + }] + fake_cifs_share_backend = [ + { + 'share_path': 'fake_path', + 'access_to': 'user_name', + 'ugType': '0', + 'access_level': 'rw' + }, + { + 'share_path': 'default_path', + 'access_to': 'manilanobody', + 'ugType': '0', + 'access_level': 'rw' + }] + mock_ganar = self.mock_object( + rest_helper.RestHelper, + '_get_all_nfs_access_rest', + mock.Mock(return_value=fake_nfs_share_backend)) + mock_gacar = self.mock_object( + rest_helper.RestHelper, '_get_all_cifs_access_rest', + mock.Mock(return_value=fake_cifs_share_backend)) + self.mock_object(rest_helper.RestHelper, + '_delete_nfs_access_rest') + self.mock_object(rest_helper.RestHelper, + '_delete_cifs_access_rest') + self.driver.helper._clear_access(share) + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + if share_proto == 'nfs': + mock_ganar.assert_called_once_with(expect_share_path) + else: + mock_gacar.assert_called_once_with(expect_share_path) + + @ddt.data('nfs', 'cifs') + def test__clear_access_no_access_list(self, share_proto): + share = fake_share.fake_share(share_proto=share_proto, + host="fake_host@fake_backend#fake_pool") + mock_ganar = self.mock_object( + rest_helper.RestHelper, + '_get_all_nfs_access_rest', + mock.Mock(return_value=[])) + mock_gacar = self.mock_object( + rest_helper.RestHelper, '_get_all_cifs_access_rest', + mock.Mock(return_value=[])) + self.driver.helper._clear_access(share) + expect_share_path = self.driver.helper._generate_share_path( + 'manila_fakeid') + if share_proto == 'nfs': + mock_ganar.assert_called_once_with(expect_share_path) + else: + mock_gacar.assert_called_once_with(expect_share_path) + + @ddt.data(constants.USER_NOT_EXIST, + constants.USER_EXIST, + constants.USER_FORMAT_ERROR) + def test__ensure_user(self, query_result): + mock_qu = self.mock_object(rest_helper.RestHelper, + '_query_user', + mock.Mock(return_value=query_result)) + mock_qg = self.mock_object( + rest_helper.RestHelper, + '_query_group', + mock.Mock(return_value=constants.GROUP_NOT_EXIST)) + mock_alg = self.mock_object(rest_helper.RestHelper, + '_add_localgroup') + mock_alu = self.mock_object(rest_helper.RestHelper, + '_add_localuser') + result = self.driver.helper._ensure_user('fake_user', + 'fake_passwd', + 'fake_group') + if query_result == constants.USER_NOT_EXIST: + mock_qg.assert_called_once_with('fake_group') + mock_alg.assert_called_once_with('fake_group') + mock_alu.assert_called_once_with('fake_user', + 'fake_passwd', + 'fake_group') + self.assertTrue(result) + elif query_result == constants.USER_EXIST: + self.assertTrue(result) + else: + self.assertFalse(result) + mock_qu.assert_called_once_with('fake_user') + + def test__ensure_user_fail(self): + mock_qu = self.mock_object( + rest_helper.RestHelper, + '_query_user', + mock.Mock(return_value=constants.USER_NOT_EXIST)) + mock_qg = self.mock_object( + rest_helper.RestHelper, + '_query_group', + mock.Mock(return_value=constants.GROUP_FORMAT_ERROR)) + self.assertRaises(exception.InvalidInput, + self.driver.helper._ensure_user, + 'fake_user', + 'fake_passwd', + 'fake_group') + mock_qu.assert_called_once_with('fake_user') + mock_qg.assert_called_once_with('fake_group') + + def test__update_share_stats(self): + self.driver.helper.pools = ['fake_pool'] + mock_gap = self.mock_object(rest_helper.RestHelper, + '_get_all_pool', + mock.Mock(return_value='fake_result')) + mock_gpc = self.mock_object(macrosan_helper.MacrosanHelper, + '_get_pool_capacity', + mock.Mock(return_value={ + "totalcapacity": 10, + "freecapacity": 9, + "allocatedcapacity": 1, + })) + mock_uss = self.mock_object(driver.ShareDriver, '_update_share_stats') + + self.driver._update_share_stats() + + data = {} + data['vendor_name'] = self.driver.VENDOR + data['driver_version'] = self.driver.VERSION + data['storage_protocol'] = self.driver.PROTOCOL + data['share_backend_name'] = 'fake_share_backend_name' + data['pools'] = [{ + 'pool_name': 'fake_pool', + 'total_capacity_gb': 10, + 'free_capacity_gb': 9, + 'allocated_capacity_gb': 1, + 'reserved_percentage': 0, + 'reserved_snapshot_percentage': 0, + 'reserved_share_extend_percentage': 0, + 'dedupe': False, + 'compression': False, + 'qos': False, + 'thin_provisioning': False, + 'snapshot_support': False, + 'create_share_from_snapshot_support': + False, + }] + mock_gap.assert_called_once() + mock_gpc.assert_called_once_with('fake_pool', 'fake_result') + mock_uss.assert_called_once_with(data) + + def test__update_share_stats_pool_not_exist(self): + self.driver.helper.pools = ['fake_pool'] + self.mock_object(rest_helper.RestHelper, '_get_all_pool', + mock.Mock(return_value='fake_result')) + self.mock_object(macrosan_helper.MacrosanHelper, + '_get_pool_capacity', + mock.Mock(return_value={})) + self.assertRaises(exception.InvalidInput, + self.driver._update_share_stats + ) + + def test__get_pool_capacity(self): + self.mock_object(macrosan_helper.MacrosanHelper, + '_find_pool_info', + mock.Mock(return_value={ + "name": "fake_pool", + "totalcapacity": "100.0G", + "allocatedcapacity": "22G", + "freecapacity": "78G", + "health": "ONLINE", + "rw": "off", + })) + res = self.driver.helper._get_pool_capacity("fake_pool", + "fake_result") + self.assertEqual(100, res['totalcapacity']) + self.assertEqual(78, res['freecapacity']) + self.assertEqual(22, res['allocatedcapacity']) + + def test__generate_share_name(self): + share = fake_share.fake_share(host="fake_host@fake_backend#fake_pool") + result = self.driver.helper._generate_share_name(share) + self.assertEqual("manila_fakeid", result) + + def test__format_name(self): + a = 'fake-1234567890-1234567890-1234567890' + expect = 'fake_1234567890_1234567890_1234' + result = self.driver.helper._format_name(a) + self.assertEqual(expect, result) + + def test__generate_share_path(self): + share_name = 'manila_fakeid' + result = self.driver.helper._generate_share_path(share_name) + + self.assertEqual(r'/manila_fakeid/manila_fakeid', result) + + @ddt.data('nfs', 'cifs') + def test__get_location_path(self, share_proto): + self.driver.helper.configuration.macrosan_nas_ip = "172.0.0.1" + result = self.driver.helper._get_location_path('fake_path', + 'fake_name', + share_proto) + if share_proto == 'nfs': + expect = r'172.0.0.1:fake_path' + elif share_proto == 'cifs': + expect = r'\\172.0.0.1\fake_name' + self.assertEqual(expect, result) + + def test__get_share_instance_pnp_pool_error(self): + share = fake_share.fake_share( + share_proto="nfs", host="fake_host@fake_backend") + self.assertRaises(exception.InvalidHost, + self.driver.helper._get_share_instance_pnp, + share) + + def test__get_share_instance_pnp_proto_error(self): + share = fake_share.fake_share( + share_proto="CephFS", host="fake_host@fake_backend#fake_pool") + self.assertRaises(exception.MacrosanBackendExeption, + self.driver.helper._get_share_instance_pnp, + share) + + @ddt.data('2000000000', '2000000KB', '2000MB', '20GB', '2TB') + def test__unit_convert_toGB(self, capacity): + convert = {'2000000000': '%.0f' % (float(2000000000) / 1024 ** 3), + '2000000KB': '%.0f' % (float(2000000) / 1024 ** 2), + '2000MB': '%.0f' % (float(2000) / 1024), + '20GB': '%.0f' % float(20), + '2TB': '%.0f' % (float(2) * 1024)} + expect = float(convert[capacity]) + result = self.driver.helper._unit_convert_toGB(capacity) + self.assertEqual(expect, result) + + @ddt.data('nfs', 'cifs') + def test__get_share(self, proto): + proto = proto.upper() + mock_gns = self.mock_object(rest_helper.RestHelper, + '_get_nfs_share', + mock.Mock(return_value={ + "path": "/manila_fakeid", + "clients": ["client"], + "protocol": "NFS" + })) + mock_gcs = self.mock_object(rest_helper.RestHelper, + '_get_cifs_share', + mock.Mock(return_value={ + "path": "fake_path", + "cifsname": "fake_cifsname", + "protocol": "CIFS", + "roList": ["fake_ro"], + "rwList": ["fake_rw"], + "allowList": ["fake_allow"], + "denyList": ["fake_deny"], + })) + expect_nfs = { + "path": "/manila_fakeid", + "clients": ["client"], + "protocol": "NFS"} + expect_cifs = { + "path": "fake_path", + "cifsname": "fake_cifsname", + "protocol": "CIFS", + "roList": ["fake_ro"], + "rwList": ["fake_rw"], + "allowList": ["fake_allow"], + "denyList": ["fake_deny"]} + result = self.driver.helper._get_share('fake_path', proto) + if proto == 'NFS': + mock_gns.assert_called_once_with('fake_path') + self.assertEqual(expect_nfs, result) + elif proto == 'CIFS': + mock_gcs.assert_called_once_with('fake_path') + self.assertEqual(expect_cifs, result) + + def test__find_pool_info(self): + pool_info = self.driver.helper._find_pool_info( + 'fake_pool', + self.resutl_success_storage_pools) + self.assertIsNotNone(pool_info) + + def test__find_pool_info_fail(self): + pool_info = self.driver.helper._find_pool_info( + 'error_pool', + self.resutl_success_storage_pools) + expect = {} + self.assertEqual(expect, pool_info) + + +@ddt.ddt +class RestHelperTestCase(test.TestCase): + + def setUp(self): + self.mock_object(CONF, '_check_required_opts') + super(RestHelperTestCase, self).setUp() + + def _safe_get(opt): + return getattr(self.configuration, opt) + + self.configuration = mock.Mock(spec=configuration.Configuration) + self.configuration.safe_get = mock.Mock(side_effect=_safe_get) + self.configuration.macrosan_nas_http_protocol = 'https' + self.configuration.macrosan_nas_ip = 'fake_ip' + self.configuration.macrosan_nas_port = 'fake_port' + self.configuration.macrosan_nas_prefix = 'nas' + self.configuration.macrosan_nas_username = 'fake_username' + self.configuration.macrosan_nas_password = 'fake_password' + self.configuration.macrosan_timeout = 60 + self.resthelper = rest_helper.RestHelper( + configuration=self.configuration) + self.post = 'POST' + self.get = 'GET' + self.delete = 'DELETE' + self.put = 'PUT' + self.fake_message = 'fake_message' + self.result_success = { + 'code': 0, + 'message': 'success', + 'data': 'fake_data' + } + self.result_success_return_0 = { + 'code': 0, + 'message': 'success', + 'data': '0' + } + self.result_success_return_1 = { + 'code': 0, + 'message': 'success', + 'data': '1' + } + self.result_failed = { + 'code': 1, + 'message': 'failed', + 'data': 'fake_data' + } + self.result_failed_not_exist = { + 'code': constants.CODE_SOURCE_NOT_EXIST, + 'message': 'failed', + 'data': '', + } + self.resutl_success_storage_pools = { + 'code': 0, + 'message': 'success', + 'data': [{ + 'name': 'fake_pool', + 'size': '1000.0G', + 'allocated': '100G', + 'free': '900G', + 'health': 'ONLINE', + 'rwStatus': 'off' + }] + } + + @ddt.data( + {'url': 'fake_url', 'data': {'fake_data': 'fake_value'}, + 'method': 'POST'}, + {'url': 'fake_url', 'data': None, + 'method': 'GET'}, + {'url': 'fake_url', 'data': {'fake_data': 'fake_value'}, + 'method': 'DELETE'}, + {'url': 'fake_url', 'data': {'fake_data': 'fake_value'}, + 'method': 'PUT'}, + ) + @ddt.unpack + def test_call(self, url, data, method): + self.resthelper._token = 'fake_token' + request_method = method.lower() + fake_response = FakeResponse(200, self.result_success) + mock_request = self.mock_object(requests, request_method, + mock.Mock(return_value=fake_response)) + self.resthelper.call(url, data, method) + expected_url = ('https://%(ip)s:%(port)s/%(rest)s/%(url)s' + % {'ip': 'fake_ip', + 'port': 'fake_port', + 'rest': 'nas', + 'url': 'fake_url'}) + header = {'Authorization': 'fake_token'} + mock_request.assert_called_once_with( + expected_url, data=data, headers=header, + timeout=self.configuration.macrosan_timeout, + verify=False) + + def test_call_method_fail(self): + self.resthelper._token = 'fake_token' + self.assertRaises(exception.ShareBackendException, + self.resthelper.call, + 'fake_url', + 'fake_data', + 'error_method') + + def test_call_token_fail(self): + self.resthelper._token = 'fake_token' + fake_result_fail = { + 'code': 302, + 'message': 'fake_message', + 'data': 'fake_data' + } + self.mock_object(self.resthelper, 'do_request', + mock.Mock(return_value=fake_result_fail)) + self.assertRaises(exception.MacrosanBackendExeption, + self.resthelper.call, + 'fake_url', + 'fake_data', + self.post) + + def test_call_token_none(self): + self.resthelper._token = None + self.mock_object(self.resthelper, 'do_request', + mock.Mock(return_value=self.result_success)) + mock_l = self.mock_object(self.resthelper, 'login', + mock.Mock(return_value='fake_token')) + self.resthelper.call('fake_url', 'fake_data', self.post) + mock_l.assert_called_once() + + def test_call_token_expired(self): + self.resthelper._token = 'fake_token' + fake_result = { + 'code': 301, + 'message': 'token expired', + 'data': 'fake_data' + } + self.mock_object( + self.resthelper, 'do_request', + mock.Mock(side_effect=[fake_result, self.result_success])) + mock_l = self.mock_object(self.resthelper, 'login', + mock.Mock(return_value='fake_token')) + self.resthelper.call('fake_url', 'fake_data', self.post) + mock_l.assert_called_once() + + def test_call_fail(self): + self.resthelper._token = 'fake_token' + fake_response = FakeResponse(302, self.result_success) + self.mock_object(requests, 'post', + mock.Mock(return_value=fake_response)) + self.assertRaises(exception.NetworkException, + self.resthelper.call, + 'fake_url', + 'fake_data', + self.post) + + def test_login(self): + fake_result = { + 'code': 0, + 'message': 'Login success', + 'data': 'fake_token' + } + mock_rd = self.mock_object(self.resthelper, 'do_request', + mock.Mock(return_value=fake_result)) + self.resthelper.login() + login_data = {'userName': self.configuration.macrosan_nas_username, + 'userPasswd': self.configuration.macrosan_nas_password} + mock_rd.assert_called_once_with('rest/token', login_data, + self.post) + self.assertEqual('fake_token', self.resthelper._token) + + def test_login_fail(self): + mock_rd = self.mock_object(self.resthelper, 'do_request', + mock.Mock(return_value=self.result_failed)) + + self.assertRaises(exception.ShareBackendException, + self.resthelper.login) + login_data = {'userName': self.configuration.macrosan_nas_username, + 'userPasswd': self.configuration.macrosan_nas_password} + mock_rd.assert_called_once_with('rest/token', login_data, + self.post) + + def test__assert_result_code(self): + self.resthelper._assert_result_code(self.result_success, + self.fake_message) + + def test__assert_result_code_fail(self): + self.assertRaises(exception.ShareBackendException, + self.resthelper._assert_result_code, + self.result_failed, + self.fake_message) + + def test__assert_result_data(self): + self.resthelper._assert_result_data(self.result_success, + self.fake_message) + + def test__assert_result_data_fail(self): + fake_result = { + 'code': 0, + 'message': 'fake_message' + } + self.assertRaises(exception.ShareBackendException, + self.resthelper._assert_result_data, + fake_result, + self.fake_message) + + def test__create_nfs_share(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._create_nfs_share('fake_path') + url = 'rest/nfsShare' + data = { + 'path': 'fake_path', + 'authority': 'ro', + 'accessClient': '172.0.0.2', + } + mock_call.assert_called_once_with(url, data, self.post) + + def test__get_nfs_share(self): + fake_result = { + 'code': 0, + 'message': 'success', + 'data': { + "path": "fake_path", + "clients": ["client"], + "protocol": "fake_protocol" + } + } + mock_call = self.mock_object(self.resthelper, + 'call', + mock.Mock(return_value=fake_result)) + self.mock_object(self.resthelper, + '_assert_result_code') + result = self.resthelper._get_nfs_share('fake_path') + expect = { + "path": "fake_path", + "clients": ["client"], + "protocol": "fake_protocol" + } + self.assertEqual(expect, result) + url = 'rest/nfsShare?path=fake_path' + mock_call.assert_called_once_with(url, None, self.get) + + def test__delete_nfs_share(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._delete_nfs_share('fake_path') + url = 'rest/nfsShare?path=fake_path' + mock_call.assert_called_once_with(url, None, self.delete) + + def test__create_cifs_share(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._create_cifs_share('fake_name', + 'fake_path', + ['fake_user'], + ['0']) + url = 'rest/cifsShare' + data = { + 'path': 'fake_path', + 'cifsName': 'fake_name', + 'cifsDescription': '', + 'RoList': [], + 'RoListType': [], + 'RwList': ['fake_user'], + 'RwListType': ['0'], + 'allowList': [], + 'denyList': [], + } + mock_call.assert_called_once_with(url, data, self.post) + + def test__get_cifs_share(self): + fake_result = { + 'code': 0, + 'message': 'success', + 'data': { + "path": "fake_path", + "cifsname": "fake_cifsname", + "protocol": "fake_protocol", + "roList": ["fake_ro"], + "rwList": ["fake_rw"], + "allowList": ["fake_allow"], + "denyList": ["fake_deny"] + } + } + mock_call = self.mock_object(self.resthelper, + 'call', + mock.Mock(return_value=fake_result)) + self.mock_object(self.resthelper, + '_assert_result_code') + result = self.resthelper._get_cifs_share('fake_path') + expect = { + "path": "fake_path", + "cifsname": "fake_cifsname", + "protocol": "fake_protocol", + "roList": ["fake_ro"], + "rwList": ["fake_rw"], + "allowList": ["fake_allow"], + "denyList": ["fake_deny"] + } + self.assertEqual(expect, result) + url = 'rest/cifsShare?path=fake_path' + mock_call.assert_called_once_with(url, None, self.get) + + def test__delete_cifs_share(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._delete_cifs_share('fake_name', 'fake_path') + url = 'rest/cifsShare?path=fake_path&cifsName=fake_name' + mock_call.assert_called_once_with(url, None, self.delete) + + def test__update_share_size(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._update_share_size('fake_filesystem', '2GB') + url = 'rest/filesystem/fake_filesystem' + data = { + 'capacity': '2GB', + } + mock_call.assert_called_once_with(url, data, self.put) + + def test___create_filesystem(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._create_filesystem('fake_filesystem', + 'fake_pool', + '1GB') + url = 'rest/filesystem' + data = { + 'fsName': 'fake_filesystem', + 'poolName': 'fake_pool', + 'createType': '0', + 'fileSystemQuota': '1GB', + 'fileSystemReserve': '1GB', + 'wormStatus': 0, + 'defaultTimeStatus': 0, + 'defaultTimeNum': 0, + 'defaultTimeUnit': 'year', + 'isAutoLock': 0, + 'isAutoDelete': 0, + 'lockTime': 0 + } + mock_call.assert_called_once_with(url, data, self.post) + + def test__delete_filesystem(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._delete_filesystem('fake_filesystem') + url = 'rest/filesystem/fake_filesystem' + mock_call.assert_called_once_with(url, None, self.delete) + + def test__get_filesystem(self): + fake_result = { + 'code': 0, + 'message': 'success', + 'data': { + 'name': 'fake_filesystem', + 'poolName': 'fake_pool', + } + } + mock_call = self.mock_object(self.resthelper, + 'call', + mock.Mock(return_value=fake_result)) + self.mock_object(self.resthelper, + '_assert_result_code') + result = self.resthelper._get_filesystem('fake_filesystem') + expect = { + 'name': 'fake_filesystem', + 'poolName': 'fake_pool', + } + self.assertEqual(expect, result) + url = 'rest/filesystem/fake_filesystem' + mock_call.assert_called_once_with(url, None, self.get) + + def test__create_filesystem_dir(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._create_filesystem_dir('/fake_path/fake_dir') + url = 'rest/fileDir' + data = { + 'path': '/fake_path', + 'dirName': 'fake_dir', + } + mock_call.assert_called_once_with(url, data, self.post) + + def test__delete_filesystem_dir(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._delete_filesystem_dir('/fake_path/fake_dir') + url = 'rest/fileDir?path=/fake_path&dirName=fake_dir' + mock_call.assert_called_once_with(url, None, self.delete) + + @ddt.data('nfs', 'cifs') + def test__allow_access_rest(self, share_proto): + share_proto = share_proto.upper() + mock_anar = self.mock_object(self.resthelper, + '_allow_nfs_access_rest') + mock_acar = self.mock_object(self.resthelper, + '_allow_cifs_access_rest') + self.resthelper._allow_access_rest('fake_path', 'fake_access', + 'rw', share_proto) + if share_proto == 'NFS': + mock_anar.assert_called_once_with('fake_path', + 'fake_access', + 'rw') + elif share_proto == 'CIFS': + mock_acar.assert_called_once_with('fake_path', + 'fake_access', + 'rw') + + def test__allow_access_rest_proto_error(self): + self.assertRaises(exception.InvalidInput, + self.resthelper._allow_access_rest, + 'fake_path', + 'fake_access', + 'rw', + 'error_proto') + + def test__allow_nfs_access_rest(self): + mock_call = self.mock_object( + self.resthelper, + 'call', + mock.Mock(return_value=self.result_success)) + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._allow_nfs_access_rest('fake_path', '172.0.0.1', 'rw') + url = 'rest/nfsShareClient' + data = { + 'path': 'fake_path', + 'client': '172.0.0.1', + 'authority': 'rw', + } + mock_call.assert_called_once_with(url, data, self.post) + + @ddt.data( + {'access_to': 'fake_user', + 'group': False}, + {'access_to': 'fake_group', + 'group': True}, + {'access_to': '/fake_user', + 'group': False}, + {'access_to': '/fake_group', + 'group': True} + ) + @ddt.unpack + def test__allow_cifs_access_rest(self, access_to, group): + ug_type_list = { + 'localUser': '0', + 'localGroup': '1', + 'adUser': '2', + 'adGroup': '3', + } + if not group: + mock_call = self.mock_object( + self.resthelper, + 'call', + mock.Mock(return_value=self.result_success)) + else: + mock_call = self.mock_object( + self.resthelper, + 'call', + mock.Mock(side_effect=[self.result_failed_not_exist, + self.result_success])) + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._allow_cifs_access_rest('fake_path', + access_to, + 'rw') + url = 'rest/cifsShareClient' + actual_type = ug_type_list["localUser"] + if '/' not in access_to: + if not group: + actual_type = ug_type_list["localUser"] + access_to = access_to + else: + if not group: + actual_type = ug_type_list["adUser"] + access_to = access_to[access_to.index('/') + 1:] + data = { + 'path': 'fake_path', + 'right': 'rw', + 'ugName': access_to, + 'ugType': actual_type, + } + if not group: + mock_call.assert_called_once_with(url, data, self.post) + else: + mock_call.assert_called() + + def test__allow_cifs_access_rest_fail(self): + mock_call = self.mock_object( + self.resthelper, + 'call', + mock.Mock(side_effect=[self.result_failed_not_exist, + self.result_failed_not_exist])) + self.assertRaises(exception.InvalidShare, + self.resthelper._allow_cifs_access_rest, + 'fake_path', + 'fake_user', + 'rw') + mock_call.assert_called() + + def test__get_access_from_nfs_share(self): + fake_result = { + 'code': 0, + 'message': 'success', + 'data': { + "path": "fake_path", + "clientName": "fake_client", + "accessRight": "rw", + } + } + mock_call = self.mock_object(self.resthelper, + 'call', + mock.Mock(return_value=fake_result)) + self.mock_object(self.resthelper, + '_assert_result_code') + result = self.resthelper._get_access_from_nfs_share('fake_path', + 'fake_client') + expect = { + "path": "fake_path", + "clientName": "fake_client", + "accessRight": "rw", + } + self.assertEqual(expect, result) + url = 'rest/nfsShareClient?path=fake_path&client=fake_client' + mock_call.assert_called_once_with(url, None, self.get) + + @ddt.data({'access_to': 'fake_user', + 'ug_type': '0', + 'code': 0, + 'group': False}, + {'access_to': 'fake_user', + 'ug_type': None, + 'code': 0, + 'group': False}, + {'access_to': 'fake_group', + 'ug_type': None, + 'code': 0, + 'group': True}, + {'access_to': 'fake_user', + 'ug_type': None, + 'code': 4, + 'group': False}, + {'access_to': '/fake_user', + 'ug_type': None, + 'code': 0, + 'group': False}, + {'access_to': '/fake_group', + 'ug_type': None, + 'code': 0, + 'group': True}, + {'access_to': '/fake_user', + 'ug_type': None, + 'code': 4, + 'group': False}) + @ddt.unpack + def test__get_access_from_cifs_share(self, + access_to, ug_type, code, group): + fake_result_failed = { + 'code': code, + 'message': 'failed', + 'data': {} + } + fake_result = { + 'code': code, + 'message': 'success', + 'data': { + 'path': 'fake_path', + 'ugName': 'fake_user', + 'ugType': '0', + 'accessRight': 'rw' + } + } + fake_result_group = { + 'code': code, + 'message': 'success', + 'data': { + 'path': 'fake_path', + 'ugName': 'fake_group', + 'ugType': '1', + 'accessRight': 'rw' + } + } + if code == 4: + fake_result = fake_result_failed + ug_type_list = { + 'localUser': '0', + 'localGroup': '1', + 'adUser': '2', + 'adGroup': '3', + } + expect = { + 'path': 'fake_path', + 'ugName': 'fake_user', + 'ugType': '0', + 'accessRight': 'rw' + } + expect_group = { + 'path': 'fake_path', + 'ugName': 'fake_group', + 'ugType': '1', + 'accessRight': 'rw' + } + if '/' in access_to: + expect['ugType'] = '2' + expect_group['ugType'] = '3' + fake_result['data']['ugType'] = '2' + fake_result_group['data']['ugType'] = '3' + if ug_type is not None: + mock_call = self.mock_object(self.resthelper, + 'call', + mock.Mock(return_value=fake_result)) + else: + if not group: + mock_call = self.mock_object( + self.resthelper, + 'call', + mock.Mock(return_value=fake_result)) + else: + mock_call = self.mock_object( + self.resthelper, + 'call', + mock.Mock(side_effect=[fake_result_failed, + fake_result_group])) + + self.mock_object(self.resthelper, + '_assert_result_code') + result = self.resthelper._get_access_from_cifs_share('fake_path', + access_to, + ug_type) + if ug_type: + self.assertEqual(expect, result) + url = f'rest/cifsShareClient?path=fake_path&' \ + f'ugName={access_to}&ugType={ug_type}' + mock_call.assert_called_once_with(url, None, self.get) + else: + if '/' not in access_to: + if not group: + actual_type = ug_type_list["localUser"] + actual_access = access_to + else: + if not group: + actual_type = ug_type_list["adUser"] + actual_access = access_to[access_to.index('/') + 1:] + if code == 4: + self.assertIsNone(result) + else: + if not group: + self.assertEqual(expect, result) + url = f'rest/cifsShareClient?path=fake_path&' \ + f'ugName={actual_access}&' \ + f'ugType={actual_type}' + mock_call.assert_called_once_with(url, None, self.get) + else: + self.assertEqual(expect_group, result) + mock_call.assert_called() + + def test__get_all_nfs_access_rest(self): + fake_result = { + 'code': 0, + 'message': 'success', + 'data': [ + { + 'path': 'fake_path', + 'clientName': '172.0.0.1', + 'accessRight': 'rw' + }, + { + 'path': 'default_path', + 'clientName': '172.0.0.2', + 'accessRight': 'rw' + }] + } + mock_call = self.mock_object(self.resthelper, + 'call', + mock.Mock(return_value=fake_result)) + self.mock_object(self.resthelper, + '_assert_result_code') + result = self.resthelper._get_all_nfs_access_rest( + '/manila_fakeid/manila_fakeid') + expect = [ + { + 'share_path': 'fake_path', + 'access_to': '172.0.0.1', + 'access_level': 'rw' + }, + { + 'share_path': 'default_path', + 'access_to': '172.0.0.2', + 'access_level': 'rw' + }] + self.assertEqual(expect, result) + url = 'rest/allNfsShareClient?path=/manila_fakeid/manila_fakeid' + mock_call.assert_called_once_with(url, None, self.get) + + def test__get_all_cifs_access_rest(self): + fake_result = { + 'code': 0, + 'message': 'success', + 'data': [ + { + 'path': 'fake_path', + 'ugName': 'user_name', + 'ugType': '0', + 'accessRight': 'rw' + }, + { + 'path': 'default_path', + 'ugName': 'manilanobody', + 'ugType': '0', + 'accessRight': 'rw' + }] + } + mock_call = self.mock_object(self.resthelper, + 'call', + mock.Mock(return_value=fake_result)) + self.mock_object(self.resthelper, + '_assert_result_code') + result = self.resthelper._get_all_cifs_access_rest( + '/manila_fakeid/manila_fakeid') + expect = [ + { + 'share_path': 'fake_path', + 'access_to': 'user_name', + 'ugType': '0', + 'access_level': 'rw' + }, + { + 'share_path': 'default_path', + 'access_to': 'manilanobody', + 'ugType': '0', + 'access_level': 'rw' + }] + self.assertEqual(expect, result) + url = 'rest/allCifsShareClient?path=/manila_fakeid/manila_fakeid' + mock_call.assert_called_once_with(url, None, self.get) + + def test__change_nfs_access_rest(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._change_nfs_access_rest( + '/manila_fakeid/manila_fakeid', '172.0.0.1', 'rw') + url = 'rest/nfsShareClient' + data = { + 'path': '/manila_fakeid/manila_fakeid', + 'oldNfsClientName': '172.0.0.1', + 'clientName': '', + 'accessRight': 'rw', + 'allSquash': '', + 'rootSquash': '', + 'secure': '', + 'anonuid': '', + 'anongid': '', + } + mock_call.assert_called_once_with(url, data, self.put) + + def test__change_cifs_access_rest(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._change_cifs_access_rest( + '/manila_fakeid/manila_fakeid', '/fake_user', 'rw', '0') + url = 'rest/cifsShareClient' + data = { + 'path': '/manila_fakeid/manila_fakeid', + 'right': 'rw', + 'ugName': 'fake_user', + 'ugType': '0', + } + mock_call.assert_called_once_with(url, data, self.put) + + def test__delete_nfs_access_rest(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._delete_nfs_access_rest( + '/manila_fakeid/manila_fakeid', '*') + url = 'rest/nfsShareClient?path=/manila_fakeid/manila_fakeid&client=*' + mock_call.assert_called_once_with(url, None, self.delete) + + def test__delete_cifs_access_rest(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._delete_cifs_access_rest( + '/manila_fakeid/manila_fakeid', 'fake_user', '0') + url = 'rest/cifsShareClient?path=/manila_fakeid/manila_fakeid' \ + '&ugName=fake_user&ugType=0' + mock_call.assert_called_once_with(url, None, self.delete) + + def test__get_nfs_service_status(self): + fake_result = { + 'code': 0, + 'message': 'success', + 'data': { + 'serviceStatus': constants.NFS_ENABLED, + 'nfs3Status': constants.NFS_SUPPORTED, + 'nfs4Status': constants.NFS_SUPPORTED + } + } + mock_call = self.mock_object(self.resthelper, + 'call', + mock.Mock(return_value=fake_result)) + self.mock_object(self.resthelper, + '_assert_result_code') + result = self.resthelper._get_nfs_service_status() + expect = { + 'serviceStatus': constants.NFS_ENABLED, + 'nfs3Status': constants.NFS_SUPPORTED, + 'nfs4Status': constants.NFS_SUPPORTED + } + self.assertEqual(expect, result) + url = 'rest/nfsService' + mock_call.assert_called_once_with(url, None, self.get) + + def test__start_nfs_service(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._start_nfs_service() + url = 'rest/nfsService' + data = { + "openStatus": "1", + } + mock_call.assert_called_once_with(url, data, self.put) + + def test__config_nfs_service(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._config_nfs_service() + url = 'rest/nfsConfig' + data = { + 'configNfs3': "yes", + 'configNfs4': "yes", + } + mock_call.assert_called_once_with(url, data, self.put) + + def test__get_cifs_service_status(self): + mock_call = self.mock_object( + self.resthelper, + 'call', + mock.Mock(return_value=self.result_success_return_1)) + self.mock_object(self.resthelper, + '_assert_result_code') + result = self.resthelper._get_cifs_service_status() + self.assertEqual('1', result) + url = 'rest/cifsService' + mock_call.assert_called_once_with(url, None, self.get) + + def test__start_cifs_service(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._start_cifs_service() + url = 'rest/cifsService' + data = { + 'openStatus': '1', + } + mock_call.assert_called_once_with(url, data, self.put) + + def test__config_cifs_service(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._config_cifs_service() + url = 'rest/cifsConfig' + data = { + 'workName': 'manila', + 'description': '', + 'access_way': 'user', + 'isCache': 'no', + 'adsName': '', + 'adsIP': '', + 'adsUSER': '', + 'adsPASSWD': '', + 'allowList': [], + 'denyList': [], + } + mock_call.assert_called_once_with(url, data, self.put) + + def test__get_all_pool(self): + mock_call = self.mock_object( + self.resthelper, + 'call', + mock.Mock(return_value=self.resutl_success_storage_pools)) + self.mock_object(self.resthelper, + '_assert_result_code') + result = self.resthelper._get_all_pool() + self.assertEqual(self.resutl_success_storage_pools, result) + url = 'rest/storagepool' + mock_call.assert_called_once_with(url, None, self.get) + + def test__query_user(self): + mock_call = self.mock_object( + self.resthelper, + 'call', + mock.Mock(return_value=self.result_success_return_0)) + self.mock_object(self.resthelper, + '_assert_result_code') + result = self.resthelper._query_user('fake_user') + self.assertEqual('0', result) + url = 'rest/user/fake_user' + mock_call.assert_called_once_with(url, None, self.get) + + def test__add_localuser(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._add_localuser('fake_user', + 'fake_passwd', 'fake_group') + url = 'rest/localUser' + data = { + 'userName': 'fake_user', + 'mgGroup': 'fake_group', + 'userPasswd': 'fake_passwd', + 'unusedGroup': []} + mock_call.assert_called_once_with(url, data, self.post) + + def test__query_group(self): + mock_call = self.mock_object( + self.resthelper, + 'call', + mock.Mock(return_value=self.result_success_return_0)) + self.mock_object(self.resthelper, + '_assert_result_code') + result = self.resthelper._query_group('fake_group') + self.assertEqual('0', result) + url = 'rest/group/fake_group' + mock_call.assert_called_once_with(url, None, self.get) + + def test__add_localgroup(self): + mock_call = self.mock_object(self.resthelper, + 'call') + self.mock_object(self.resthelper, + '_assert_result_code') + self.resthelper._add_localgroup('fake_group') + url = 'rest/localGroup' + data = {'groupName': 'fake_group'} + mock_call.assert_called_once_with(url, data, self.post) diff --git a/releasenotes/notes/macrosan-manila-driver-4644ed2cdd51b030.yaml b/releasenotes/notes/macrosan-manila-driver-4644ed2cdd51b030.yaml new file mode 100644 index 0000000000..eae09d60ae --- /dev/null +++ b/releasenotes/notes/macrosan-manila-driver-4644ed2cdd51b030.yaml @@ -0,0 +1,3 @@ +--- +features: + - Added Manila driver for Macrosan storage system.