diff --git a/doc/source/devref/emc_unity_driver.rst b/doc/source/devref/emc_unity_driver.rst new file mode 100644 index 0000000000..57b19e932e --- /dev/null +++ b/doc/source/devref/emc_unity_driver.rst @@ -0,0 +1,126 @@ +.. + Copyright (c) 2014 EMC Corporation + 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. + +Unity Driver +============ + +EMC manila driver framework (EMCShareDriver) utilizes the EMC storage products +to provide the shared filesystems to OpenStack. The EMC manila driver is a +plugin based driver which is designed to use different plugins to manage +different EMC storage products. + +Unity plugin is the plugin which manages the Unity Storage System to provide +shared filesystems. EMC driver framework with Unity plugin is referred to as +Unity driver in this document. + +This driver performs the operations on Unity by REST API. Each backend manages +one Unity Storage System. Multiple manila backends need to be configured to +manage multiple Unity Storage Systems. + +Requirements +------------ + +- Unity OE 4.0.1 or higher. +- StorOps 0.2.17 or higher is installed on Manila node. +- Following licenses are activated on Unity: + * CIFS/SMB Support + * Network File System (NFS) + * Thin Provisioning + * Fiber Channel (FC) + * Internet Small Computer System Interface (iSCSI) + + +Supported Operations +-------------------- + +In detail, users are allowed to do following operation with EMC Unity +Storage Systems. + +* Create/delete a NFS share. +* Create/delete a CIFS share. +* Extend the size of a share. +* Modify the host access privilege of a NFS share. +* Modify the user access privilege of a CIFS share. +* Take/Delete snapshot of a share. +* Create a new share from snapshot. + + +Supported Network Topologies +---------------------------- + +flat, VLAN + + +Pre-Configurations +------------------ + +On Manila Node +`````````````` + +StorOps library is required to run Unity driver. +Please install it with the pip command. +You may need root privilege to install python libraries. + +:: + + pip install storops + + +Configurations +-------------- + +Following configurations are introduced for the Unity plugin. + +* emc_interface_ports: White list of the ports to be used for connection. + Wild card character is supported. + Examples: spa_eth1, spa_*, * +* emc_nas_server_pool: The pool used to persist the meta-data of created + NAS servers. Wild card character is supported. + Examples: pool_1, pool_*, * + + +API Implementations +------------------- + +Following driver features are implemented in the plugin. + +* create_share: Create a share and export it based on the protocol used + (NFS or CIFS). +* create_share_from_snapshot: Create a share from a snapshot - clone a + snapshot. +* delete_share: Delete a share. +* extend_share: Extend the maximum size of a share. +* create_snapshot: Create a snapshot for the specified share. +* delete_snapshot: Delete the snapshot of the share. +* update_access: recover, add or delete user/host access to a share. +* allow_access: Allow access (read write/read only) of a user to a + CIFS share. Allow access (read write/read only) of a host to a NFS + share. +* deny_access: Remove access (read write/read only) of a user from + a CIFS share. Remove access (read write/read only) of a host from a + NFS share. +* ensure_share: Check whether share exists or not. +* update_share_stats: Retrieve share related statistics from Unity. +* get_network_allocations_number: Returns number of network allocations for + creating VIFs. +* setup_server: Set up and configures share server with given network + parameters. +* teardown_server: Tear down the share server. + +Restrictions +------------ + +* EMC Unity does not support the same IP in different VLANs. diff --git a/doc/source/devref/index.rst b/doc/source/devref/index.rst index c2a0f80459..afc90d5477 100644 --- a/doc/source/devref/index.rst +++ b/doc/source/devref/index.rst @@ -108,6 +108,7 @@ Share backends netapp_cluster_mode_driver emc_isilon_driver emc_vnx_driver + emc_unity_driver generic_driver glusterfs_driver glusterfs_native_driver diff --git a/doc/source/devref/share_back_ends_feature_support_mapping.rst b/doc/source/devref/share_back_ends_feature_support_mapping.rst index 6b6bbd07d3..b9c526054b 100644 --- a/doc/source/devref/share_back_ends_feature_support_mapping.rst +++ b/doc/source/devref/share_back_ends_feature_support_mapping.rst @@ -41,6 +41,8 @@ Mapping of share drivers and share features support +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+ | EMC VNX | J | \- | \- | \- | J | J | \- | +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+ +| EMC Unity | N | \- | N | \- | N | N | \- | ++----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+ | EMC Isilon | K | \- | M | \- | K | K | \- | +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+ | Red Hat GlusterFS | J | \- | \- | \- | volume layout (L) | volume layout (L) | \- | @@ -86,6 +88,8 @@ Mapping of share drivers and share access rules support +----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+ | EMC VNX | NFS (J) | CIFS (J) | \- | \- | NFS (L) | CIFS (L) | \- | \- | +----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+ +| EMC Unity | NFS (N) | CIFS (N) | \- | \- | NFS (N) | CIFS (N) | \- | \- | ++----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+ | EMC Isilon | NFS,CIFS (K) | CIFS (M) | \- | \- | NFS (M) | CIFS (M) | \- | \- | +----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+ | Red Hat GlusterFS | NFS (J) | \- | \- | \- | \- | \- | \- | \- | @@ -129,6 +133,8 @@ Mapping of share drivers and security services support +----------------------------------------+------------------+-----------------+------------------+ | EMC VNX | J | \- | \- | +----------------------------------------+------------------+-----------------+------------------+ +| EMC Unity | N | \- | \- | ++----------------------------------------+------------------+-----------------+------------------+ | EMC Isilon | \- | \- | \- | +----------------------------------------+------------------+-----------------+------------------+ | Red Hat GlusterFS | \- | \- | \- | @@ -172,6 +178,8 @@ Mapping of share drivers and common capabilities +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+ | EMC VNX | J | \- | \- | \- | \- | L | \- | +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+ +| EMC Unity | N | \- | \- | \- | N | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+ | EMC Isilon | \- | K | \- | \- | \- | L | \- | +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+ | Red Hat GlusterFS | \- | J | \- | \- | \- | L | \- | diff --git a/manila/exception.py b/manila/exception.py index a65b009027..dac2c63733 100644 --- a/manila/exception.py +++ b/manila/exception.py @@ -554,6 +554,10 @@ class ShareTypeInUse(ManilaException): "shares present with the type.") +class IPAddressInUse(InUse): + message = _("IP address %(ip)s is already used.") + + class ShareTypeExists(ManilaException): message = _("Share Type %(id)s already exists.") @@ -645,6 +649,10 @@ class EMCVnxInvalidMoverID(ManilaException): message = _("Invalid mover or vdm %(id)s.") +class EMCUnityError(ShareBackendException): + message = _("%(err)s") + + class HPE3ParInvalidClient(Invalid): message = _("%(err)s") diff --git a/manila/share/drivers/emc/driver.py b/manila/share/drivers/emc/driver.py index 868e7390d1..715104f423 100644 --- a/manila/share/drivers/emc/driver.py +++ b/manila/share/drivers/emc/driver.py @@ -26,10 +26,8 @@ from oslo_log import log from manila.share import driver from manila.share.drivers.emc import plugin_manager as manager - LOG = log.getLogger(__name__) - EMC_NAS_OPTS = [ cfg.StrOpt('emc_nas_login', help='User name for the EMC server.'), @@ -44,15 +42,18 @@ EMC_NAS_OPTS = [ default=True, help='Use secure connection to server.'), cfg.StrOpt('emc_share_backend', + ignore_case=True, + choices=['isilon', 'vnx', 'unity'], help='Share backend.'), cfg.StrOpt('emc_nas_server_container', - default='server_2', help='Container of share servers.'), - cfg.StrOpt('emc_nas_pool_names', - deprecated_name='emc_nas_pool_name', - help='EMC pool names.'), + cfg.ListOpt('emc_nas_pool_names', + deprecated_name='emc_nas_pool_name', + help='EMC pool names.'), cfg.StrOpt('emc_nas_root_dir', help='The root directory where shares will be located.'), + cfg.StrOpt('emc_nas_server_pool', + help='Pool to persist the meta-data of NAS server.'), cfg.ListOpt('emc_interface_ports', help='Comma separated list specifying the ports that can be ' 'used for share server interfaces. Members of the list ' @@ -65,6 +66,7 @@ CONF.register_opts(EMC_NAS_OPTS) class EMCShareDriver(driver.ShareDriver): """EMC specific NAS driver. Allows for NFS and CIFS NAS storage usage.""" + def __init__(self, *args, **kwargs): self.configuration = kwargs.get('configuration', None) if self.configuration: diff --git a/manila/share/drivers/emc/plugins/unity/__init__.py b/manila/share/drivers/emc/plugins/unity/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/manila/share/drivers/emc/plugins/unity/client.py b/manila/share/drivers/emc/plugins/unity/client.py new file mode 100644 index 0000000000..1f8e5c97b0 --- /dev/null +++ b/manila/share/drivers/emc/plugins/unity/client.py @@ -0,0 +1,261 @@ +# Copyright (c) 2016 EMC Corporation. +# 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 random +import six + +from oslo_log import log +from oslo_utils import importutils + +storops = importutils.try_import('storops') +if storops: + from storops import exception as storops_ex + from storops.unity import enums + +from manila.common import constants as const +from manila import exception +from manila.i18n import _, _LI, _LE + +LOG = log.getLogger(__name__) + + +class UnityClient(object): + def __init__(self, host, username, password): + if storops is None: + LOG.error(_LE('StorOps is required to run EMC Unity driver.')) + self.system = storops.UnitySystem(host, username, password) + + def create_cifs_share(self, resource, share_name): + """Create CIFS share from the resource. + + :param resource: either UnityFilesystem or UnitySnap object + :param share_name: CIFS share name + :return: UnityCifsShare object + """ + try: + share = resource.create_cifs_share(share_name) + try: + # bug on unity: the enable ace API has bug for snap + # based share. Log the internal error if it happens. + share.enable_ace() + except storops_ex.UnityException: + msg = _LE('Failed to enabled ACE for share: {}.') + LOG.exception(msg.format(share_name)) + return share + except storops_ex.UnitySmbShareNameExistedError: + return self.get_share(share_name, 'CIFS') + + def create_nfs_share(self, resource, share_name): + """Create NFS share from the resource. + + :param resource: either UnityFilesystem or UnitySnap object + :param share_name: NFS share name + :return: UnityNfsShare object + """ + try: + return resource.create_nfs_share(share_name) + except storops_ex.UnityNfsShareNameExistedError: + return self.get_share(share_name, 'NFS') + + def get_share(self, name, share_proto): + # Validate the share protocol + proto = share_proto.upper() + + if proto == 'CIFS': + return self.system.get_cifs_share(name=name) + elif proto == 'NFS': + return self.system.get_nfs_share(name=name) + else: + raise exception.BadConfigurationException( + reason=_('Invalid NAS protocol supplied: %s.') % share_proto) + + @staticmethod + def delete_share(share): + share.delete() + + def create_filesystem(self, pool, nas_server, share_name, size, proto): + try: + return pool.create_filesystem(nas_server, + share_name, + size, + proto=proto) + except storops_ex.UnityFileSystemNameAlreadyExisted: + LOG.debug('Filesystem %s already exists, ' + 'ignoring filesystem creation.', share_name) + return self.system.get_filesystem(name=share_name) + + @staticmethod + def delete_filesystem(filesystem): + try: + filesystem.delete() + except storops_ex.UnityResourceNotFoundError: + LOG.info(_LI('Filesystem %s is already removed.'), filesystem.name) + + def create_nas_server(self, name, sp, pool): + try: + return self.system.create_nas_server(name, sp, pool) + except storops_ex.UnityNasServerNameUsedError: + LOG.info(_LI('Share server %s already exists, ignoring share ' + 'server creation.'), name) + return self.get_nas_server(name) + + def get_nas_server(self, name): + try: + return self.system.get_nas_server(name=name) + except storops_ex.UnityResourceNotFoundError: + LOG.info(_LI('NAS server %s not found.'), name) + raise + + def delete_nas_server(self, name, username=None, password=None): + try: + nas_server = self.get_nas_server(name=name) + nas_server.delete(username=username, password=password) + except storops_ex.UnityResourceNotFoundError: + LOG.info(_LI('NAS server %s not found.'), name) + + @staticmethod + def create_dns_server(nas_server, domain, dns_ip): + try: + nas_server.create_dns_server(domain, dns_ip) + except storops_ex.UnityOneDnsPerNasServerError: + LOG.info(_LI('DNS server %s already exists, ' + 'ignoring DNS server creation.'), domain) + + @staticmethod + def create_interface(nas_server, ip_addr, netmask, gateway, ports, + vlan_id=None): + port_list = list(ports) + random.shuffle(port_list) + try: + nas_server.create_file_interface(port_list[0], + ip_addr, + netmask=netmask, + gateway=gateway, + vlan_id=vlan_id) + except storops_ex.UnityIpAddressUsedError: + raise exception.IPAddressInUse(ip=ip_addr) + + @staticmethod + def enable_cifs_service(nas_server, domain, username, password): + try: + nas_server.enable_cifs_service( + nas_server.file_interface, + domain=domain, + domain_username=username, + domain_password=password) + except storops_ex.UnitySmbNameInUseError: + LOG.info(_LI('CIFS service on NAS server %s is ' + 'already enabled.'), nas_server.name) + + @staticmethod + def enable_nfs_service(nas_server): + try: + nas_server.enable_nfs_service() + except storops_ex.UnityNfsAlreadyEnabledError: + LOG.info(_LI('NFS service on NAS server %s is ' + 'already enabled.'), nas_server.name) + + @staticmethod + def create_snapshot(filesystem, name): + access_type = enums.FilesystemSnapAccessTypeEnum.CHECKPOINT + try: + return filesystem.create_snap(name, fs_access_type=access_type) + except storops_ex.UnitySnapNameInUseError: + LOG.info(_LI('Snapshot %(snap)s on Filesystem %(fs)s already ' + 'exists.'), {'snap': name, 'fs': filesystem.name}) + + def create_snap_of_snap(self, src_snap, dst_snap_name, snap_type): + access_type = enums.FilesystemSnapAccessTypeEnum.PROTOCOL + if snap_type == 'checkpoint': + access_type = enums.FilesystemSnapAccessTypeEnum.CHECKPOINT + + if isinstance(src_snap, six.string_types): + snap = self.get_snapshot(name=src_snap) + else: + snap = src_snap + + try: + return snap.create_snap(dst_snap_name, fs_access_type=access_type) + except storops_ex.UnitySnapNameInUseError: + return self.get_snapshot(dst_snap_name) + + def get_snapshot(self, name): + return self.system.get_snap(name=name) + + @staticmethod + def delete_snapshot(snap): + try: + snap.delete() + except storops_ex.UnityResourceNotFoundError: + LOG.info(_LI('Snapshot %s is already removed.'), snap.name) + + def get_pool(self, name=None): + return self.system.get_pool(name=name) + + def get_storage_processor(self, sp_id): + sp = self.system.get_sp(sp_id) + return sp if sp.existed else None + + def cifs_clear_access(self, share_name, white_list=None): + share = self.system.get_cifs_share(name=share_name) + share.clear_access(white_list) + + def nfs_clear_access(self, share_name, white_list=None): + share = self.system.get_nfs_share(name=share_name) + share.clear_access(white_list, force_create_host=True) + + def cifs_allow_access(self, share_name, user_name, access_level): + share = self.system.get_cifs_share(name=share_name) + + if access_level == const.ACCESS_LEVEL_RW: + cifs_access = enums.ACEAccessLevelEnum.WRITE + else: + cifs_access = enums.ACEAccessLevelEnum.READ + + share.add_ace(user=user_name, access_level=cifs_access) + + def nfs_allow_access(self, share_name, host_ip, access_level): + share = self.system.get_nfs_share(name=share_name) + if access_level == const.ACCESS_LEVEL_RW: + share.allow_read_write_access(host_ip, force_create_host=True) + share.allow_root_access(host_ip, force_create_host=True) + else: + share.allow_read_only_access(host_ip, force_create_host=True) + + def cifs_deny_access(self, share_name, user_name): + share = self.system.get_cifs_share(name=share_name) + + share.delete_ace(user=user_name) + + def nfs_deny_access(self, share_name, host_ip): + share = self.system.get_nfs_share(name=share_name) + + try: + share.delete_access(host_ip) + except storops_ex.UnityHostNotFoundException: + LOG.info(_LI('%(host)s access to %(share)s is already removed.'), + {'host': host_ip, 'share': share_name}) + + def get_ip_ports(self, sp=None): + ports = self.system.get_ip_port() + link_up_ports = [] + for port in ports: + if port.is_link_up and 'eth' in port.id: + if sp and port.sp.id != sp.id: + continue + + link_up_ports.append(port) + + return link_up_ports diff --git a/manila/share/drivers/emc/plugins/unity/connection.py b/manila/share/drivers/emc/plugins/unity/connection.py new file mode 100644 index 0000000000..a262ace702 --- /dev/null +++ b/manila/share/drivers/emc/plugins/unity/connection.py @@ -0,0 +1,632 @@ +# Copyright (c) 2016 EMC Corporation. +# 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. +"""Unity backend for the EMC Manila driver.""" + +from oslo_log import log +from oslo_utils import excutils +from oslo_utils import importutils +from oslo_utils import units + +storops = importutils.try_import('storops') +if storops: + from storops import exception as storops_ex + from storops.unity import enums + +from manila.common import constants as const +from manila import exception +from manila.i18n import _, _LE, _LW, _LI +from manila.share.drivers.emc.plugins import base as driver +from manila.share.drivers.emc.plugins.unity import client +from manila.share.drivers.emc.plugins.unity import utils as unity_utils +from manila.share.drivers.emc.plugins.vnx import utils as emc_utils +from manila.share import utils as share_utils +from manila import utils + +VERSION = "1.0.0" + +LOG = log.getLogger(__name__) +SUPPORTED_NETWORK_TYPES = (None, 'flat', 'vlan') + + +@emc_utils.decorate_all_methods(emc_utils.log_enter_exit, + debug_only=True) +class UnityStorageConnection(driver.StorageConnection): + """Implements Unity specific functionality for EMC Manila driver.""" + + IP_ALLOCATIONS = 2 + + @emc_utils.log_enter_exit + def __init__(self, *args, **kwargs): + super(UnityStorageConnection, self).__init__(*args, **kwargs) + self.client = None + self.pool_set = None + self.port_set = None + self.nas_server_pool = None + self.storage_processor = None + self.reserved_percentage = None + self.max_over_subscription_ratio = None + + # props from super class. + self.driver_handles_share_servers = True + + def connect(self, emc_share_driver, context): + """Connect to Unity storage.""" + config = emc_share_driver.configuration + storage_ip = config.emc_nas_server + username = config.emc_nas_login + password = config.emc_nas_password + sp_name = config.emc_nas_server_container + self.client = client.UnityClient(storage_ip, username, password) + + pool_conf = config.safe_get( + 'emc_nas_pool_names') + self.pool_set = self._get_managed_pools(pool_conf) + + self.reserved_percentage = config.safe_get( + 'reserved_share_percentage') + if self.reserved_percentage is None: + self.reserved_percentage = 0 + + self.max_over_subscription_ratio = config.safe_get( + 'max_over_subscription_ratio') + + self._config_sp(sp_name) + + port_conf = config.safe_get( + 'emc_interface_ports') + self.port_set = self._get_managed_ports( + port_conf, self.storage_processor) + + pool_name = config.emc_nas_server_pool + self._config_pool(pool_name) + + def check_for_setup_error(self): + """Check for setup error.""" + + def create_share(self, context, share, share_server=None): + """Create a share and export it based on protocol used.""" + share_name = share['id'] + size = share['size'] * units.Gi + + # Check share's protocol. + # Throw an exception immediately if it is an invalid protocol. + share_proto = share['share_proto'].upper() + proto_enum = self._get_proto_enum(share_proto) + + # Get pool name from share host field + pool_name = self._get_pool_name_from_host(share['host']) + # Get share server name from share server + server_name = self._get_server_name(share_server) + + pool = self.client.get_pool(pool_name) + try: + nas_server = self.client.get_nas_server(server_name) + except storops_ex.UnityResourceNotFoundError: + message = (_LE("Failed to get NAS server %(server)s when " + "creating the share %(share)s.") % + {'server': server_name, 'share': share_name}) + LOG.error(message) + raise exception.EMCUnityError(err=message) + + filesystem = self.client.create_filesystem( + pool, nas_server, share_name, size, proto=proto_enum) + + locations = None + if share_proto == 'CIFS': + self.client.create_cifs_share(filesystem, share_name) + + locations = self._get_cifs_location( + nas_server.file_interface, share_name) + elif share_proto == 'NFS': + self.client.create_nfs_share(filesystem, share_name) + + locations = self._get_nfs_location( + nas_server.file_interface, share_name) + + return locations + + def create_share_from_snapshot(self, context, share, snapshot, + share_server=None): + """Create a share from a snapshot - clone a snapshot.""" + share_name = share['id'] + + # Check share's protocol. + # Throw an exception immediately if it is an invalid protocol. + share_proto = share['share_proto'].upper() + self._validate_share_protocol(share_proto) + + # Get share server name from share server + server_name = self._get_server_name(share_server) + + try: + nas_server = self.client.get_nas_server(server_name) + except storops_ex.UnityResourceNotFoundError: + message = (_LE("Failed to get NAS server %(server)s when " + "creating the share %(share)s.") % + {'server': server_name, 'share': share_name}) + LOG.error(message) + raise exception.EMCUnityError(err=message) + + backend_snap = self.client.create_snap_of_snap(snapshot['id'], + share_name, + snap_type='snapshot') + + locations = None + if share_proto == 'CIFS': + self.client.create_cifs_share(backend_snap, share_name) + + locations = self._get_cifs_location( + nas_server.file_interface, share_name) + elif share_proto == 'NFS': + self.client.create_nfs_share(backend_snap, share_name) + + locations = self._get_nfs_location( + nas_server.file_interface, share_name) + + return locations + + def delete_share(self, context, share, share_server=None): + """Delete a share.""" + share_name = share['id'] + try: + backend_share = self.client.get_share(share_name, + share['share_proto']) + except storops_ex.UnityResourceNotFoundError: + LOG.warning(_LW("Share %s is not found when deleting the share"), + share_name) + return + + # Share created by the API create_share_from_snapshot() + if self._is_share_from_snapshot(backend_share): + filesystem = backend_share.snap.filesystem + self.client.delete_snapshot(backend_share.snap) + else: + filesystem = backend_share.filesystem + self.client.delete_share(backend_share) + + if self._is_isolated_filesystem(filesystem): + self.client.delete_filesystem(filesystem) + + def extend_share(self, share, new_size, share_server=None): + backend_share = self.client.get_share(share['id'], + share['share_proto']) + + if not self._is_share_from_snapshot(backend_share): + backend_share.filesystem.extend(new_size * units.Gi) + else: + share_id = share['id'] + reason = _LE("Driver does not support extending a " + "snapshot based share.") + raise exception.ShareExtendingError(share_id=share_id, + reason=reason) + + def create_snapshot(self, context, snapshot, share_server=None): + """Create snapshot from share.""" + share_name = snapshot['share_id'] + share_proto = snapshot['share']['share_proto'] + backend_share = self.client.get_share(share_name, share_proto) + + snapshot_name = snapshot['id'] + if self._is_share_from_snapshot(backend_share): + self.client.create_snap_of_snap(backend_share.snap, + snapshot_name, + snap_type='checkpoint') + else: + self.client.create_snapshot(backend_share.filesystem, + snapshot_name) + + def delete_snapshot(self, context, snapshot, share_server=None): + """Delete a snapshot.""" + snap = self.client.get_snapshot(snapshot['id']) + self.client.delete_snapshot(snap) + + def update_access(self, context, share, access_rules, add_rules, + delete_rules, share_server=None): + # adding rules + if add_rules: + for rule in add_rules: + self.allow_access(context, share, rule, share_server) + + # deleting rules + if delete_rules: + for rule in delete_rules: + self.deny_access(context, share, rule, share_server) + + # recovery mode + if not (add_rules or delete_rules): + white_list = [] + for rule in access_rules: + self.allow_access(context, share, rule, share_server) + white_list.append(rule['access_to']) + self.clear_access(share, white_list) + + def clear_access(self, share, white_list=None): + share_proto = share['share_proto'].upper() + share_name = share['id'] + if share_proto == 'CIFS': + self.client.cifs_clear_access(share_name, white_list) + elif share_proto == 'NFS': + self.client.nfs_clear_access(share_name, white_list) + + def allow_access(self, context, share, access, share_server=None): + """Allow access to a share.""" + access_level = access['access_level'] + if access_level not in const.ACCESS_LEVELS: + raise exception.InvalidShareAccessLevel(level=access_level) + + share_proto = share['share_proto'].upper() + + self._validate_share_protocol(share_proto) + self._validate_share_access_type(share, access) + + if share_proto == 'CIFS': + self._cifs_allow_access(share, access) + elif share_proto == 'NFS': + self._nfs_allow_access(share, access) + + def deny_access(self, context, share, access, share_server): + """Deny access to a share.""" + share_proto = share['share_proto'].upper() + + self._validate_share_protocol(share_proto) + self._validate_share_access_type(share, access) + + if share_proto == 'CIFS': + self._cifs_deny_access(share, access) + elif share_proto == 'NFS': + self._nfs_deny_access(share, access) + + def ensure_share(self, context, share, share_server): + """Ensure that the share is exported.""" + share_name = share['id'] + share_proto = share['share_proto'] + + backend_share = self.client.get_share(share_name, share_proto) + if not backend_share.existed: + raise exception.ShareNotFound(share_id=share_name) + + def update_share_stats(self, stats_dict): + """Communicate with EMCNASClient to get the stats.""" + stats_dict['driver_version'] = VERSION + stats_dict['pools'] = [] + + for pool in self.client.get_pool(): + if pool.name in self.pool_set: + # the unit of following numbers are GB + total_size = float(pool.size_total) + used_size = float(pool.size_used) + + pool_stat = { + 'pool_name': pool.name, + 'thin_provisioning': True, + 'total_capacity_gb': total_size, + 'free_capacity_gb': total_size - used_size, + 'allocated_capacity_gb': used_size, + 'provisioned_capacity_gb': float(pool.size_subscribed), + 'qos': False, + 'reserved_percentage': self.reserved_percentage, + 'max_over_subscription_ratio': + self.max_over_subscription_ratio, + } + stats_dict['pools'].append(pool_stat) + + if not stats_dict.get('pools'): + message = _LE("Failed to update storage pool.") + LOG.error(message) + raise exception.EMCUnityError(err=message) + + def get_pool(self, share): + """Get the pool name of the share.""" + backend_share = self.client.get_share( + share['id'], share['share_proto']) + + return backend_share.filesystem.pool.name + + def get_network_allocations_number(self): + """Returns number of network allocations for creating VIFs.""" + return self.IP_ALLOCATIONS + + def setup_server(self, network_info, metadata=None): + """Set up and configures share server with given network parameters.""" + server_name = network_info['server_id'] + nas_server = self.client.create_nas_server(server_name, + self.storage_processor, + self.nas_server_pool) + + try: + for network in network_info['network_allocations']: + self._create_network_interface(nas_server, network) + + self._handle_security_services( + nas_server, network_info['security_services']) + + return {'share_server_name': server_name} + + except Exception as ex: + with excutils.save_and_reraise_exception(): + LOG.error(_LE('Could not setup server. Reason: %s.'), ex) + server_details = {'share_server_name': server_name} + self.teardown_server( + server_details, network_info['security_services']) + + def teardown_server(self, server_details, security_services=None): + """Teardown share server.""" + if not server_details: + LOG.debug('Server details are empty.') + return + + server_name = server_details.get('share_server_name') + if not server_name: + LOG.debug('No share server found for server %s.', + server_details.get('instance_id')) + return + + username = None + password = None + for security_service in security_services: + if security_service['type'] == 'active_directory': + username = security_service['user'] + password = security_service['password'] + break + + self.client.delete_nas_server(server_name, username, password) + + def _cifs_allow_access(self, share, access): + """Allow access to CIFS share.""" + self.client.cifs_allow_access( + share['id'], access['access_to'], access['access_level']) + + def _cifs_deny_access(self, share, access): + """Deny access to CIFS share.""" + self.client.cifs_deny_access(share['id'], access['access_to']) + + def _config_pool(self, pool_name): + try: + self.nas_server_pool = self.client.get_pool(pool_name) + except storops_ex.UnityResourceNotFoundError: + message = (_LE("The storage pools %s to store NAS server " + "configuration do not exist.") % pool_name) + LOG.error(message) + raise exception.BadConfigurationException(reason=message) + + def _config_sp(self, sp_name): + self.storage_processor = self.client.get_storage_processor( + sp_name.lower()) + if self.storage_processor is None: + message = (_LE("The storage processor %s does not exist or " + "is unavailable. Please reconfigure it in " + "manila.conf.") % sp_name) + LOG.error(message) + raise exception.BadConfigurationException(reason=message) + + def _create_network_interface(self, nas_server, network): + ip_addr = network['ip_address'] + netmask = utils.cidr_to_netmask(network['cidr']) + gateway = network['gateway'] + vlan_id = network['segmentation_id'] + if network['network_type'] not in SUPPORTED_NETWORK_TYPES: + msg = _('The specified network type %s is unsupported by ' + 'the EMC Unity driver') + raise exception.NetworkBadConfigurationException( + reason=msg % network['network_type']) + + # Create the interfaces on NAS server + self.client.create_interface(nas_server, + ip_addr, + netmask, + gateway, + ports=self.port_set, + vlan_id=vlan_id) + + @staticmethod + def _get_cifs_location(file_interfaces, share_name): + return [ + {'path': r'\\%(interface)s\%(share_name)s' % { + 'interface': interface.ip_address, + 'share_name': share_name} + } + for interface in file_interfaces + ] + + def _get_managed_pools(self, pool_conf): + # Get the real pools from the backend storage + real_pools = set([pool.name for pool in self.client.get_pool()]) + + if not pool_conf: + LOG.debug("No storage pool is specified, so all pools in storage " + "system will be managed.") + return real_pools + + matched_pools, unmanaged_pools = unity_utils.do_match(real_pools, + pool_conf) + + if not matched_pools: + msg = (_("All the specified storage pools to be managed " + "do not exist. Please check your configuration " + "emc_nas_pool_names in manila.conf. " + "The available pools in the backend are %s") % + ",".join(real_pools)) + raise exception.BadConfigurationException(reason=msg) + + if unmanaged_pools: + LOG.info(_LI("The following specified storage pools " + "are not managed by the backend: " + "%(un_managed)s. This host will only manage " + "the storage pools: %(exist)s"), + {'un_managed': ",".join(unmanaged_pools), + 'exist': ",".join(matched_pools)}) + else: + LOG.debug("Storage pools: %s will be managed.", + ",".join(matched_pools)) + + return matched_pools + + def _get_managed_ports(self, port_conf, sp): + # Get the real ports from the backend storage + real_ports = set([port.id for port in self.client.get_ip_ports(sp)]) + + if not port_conf: + LOG.debug("No ports are specified, so all ports in storage " + "system will be managed.") + return real_ports + + matched_ports, unmanaged_ports = unity_utils.do_match(real_ports, + port_conf) + + if not matched_ports: + msg = (_("All the specified storage ports to be managed " + "do not exist. Please check your configuration " + "emc_interface_ports in manila.conf. " + "The available ports in the backend are %s") % + ",".join(real_ports)) + raise exception.BadConfigurationException(reason=msg) + + if unmanaged_ports: + LOG.info(_LI("The following specified ports " + "are not managed by the backend: " + "%(un_managed)s. This host will only manage " + "the storage ports: %(exist)s"), + {'un_managed': ",".join(unmanaged_ports), + 'exist': ",".join(matched_ports)}) + else: + LOG.debug("Ports: %s will be managed.", + ",".join(matched_ports)) + + return matched_ports + + @staticmethod + def _get_nfs_location(file_interfaces, share_name): + return [ + {'path': '%(interface)s:/%(share_name)s' % { + 'interface': interface.ip_address, + 'share_name': share_name} + } + for interface in file_interfaces + ] + + @staticmethod + def _get_pool_name_from_host(host): + pool_name = share_utils.extract_host(host, level='pool') + if not pool_name: + message = (_("Pool is not available in the share host %s.") % + host) + raise exception.InvalidHost(reason=message) + + return pool_name + + @staticmethod + def _get_proto_enum(share_proto): + share_proto = share_proto.upper() + UnityStorageConnection._validate_share_protocol(share_proto) + + if share_proto == 'CIFS': + return enums.FSSupportedProtocolEnum.CIFS + elif share_proto == 'NFS': + return enums.FSSupportedProtocolEnum.NFS + + @staticmethod + def _get_server_name(share_server): + if not share_server: + msg = _('Share server not provided.') + raise exception.InvalidInput(reason=msg) + + server_name = share_server.get( + 'backend_details', {}).get('share_server_name') + + if server_name is None: + msg = (_LE("Name of the share server %s not found.") + % share_server['id']) + LOG.error(msg) + raise exception.InvalidInput(reason=msg) + + return server_name + + def _handle_security_services(self, nas_server, security_services): + kerberos_enabled = False + # Support 'active_directory' and 'kerberos' + for security_service in security_services: + service_type = security_service['type'] + if service_type == 'active_directory': + # Create DNS server for NAS server + domain = security_service['domain'] + dns_ip = security_service['dns_ip'] + self.client.create_dns_server(nas_server, + domain, + dns_ip) + + # Enable CIFS service + username = security_service['user'] + password = security_service['password'] + self.client.enable_cifs_service(nas_server, + domain=domain, + username=username, + password=password) + elif service_type == 'kerberos': + # Enable NFS service with kerberos + kerberos_enabled = True + # TODO(jay.xu): enable nfs service with kerberos + LOG.warning(_LW('Kerberos is not supported by ' + 'EMC Unity manila driver plugin.')) + elif service_type == 'ldap': + LOG.warning(_LW('LDAP is not supported by ' + 'EMC Unity manila driver plugin.')) + else: + LOG.warning(_LW('Unknown security service type: %s.'), + service_type) + + if not kerberos_enabled: + # Enable NFS service without kerberos + self.client.enable_nfs_service(nas_server) + + def _nfs_allow_access(self, share, access): + """Allow access to NFS share.""" + self.client.nfs_allow_access( + share['id'], access['access_to'], access['access_level']) + + def _nfs_deny_access(self, share, access): + """Deny access to NFS share.""" + self.client.nfs_deny_access(share['id'], access['access_to']) + + @staticmethod + def _is_isolated_filesystem(filesystem): + filesystem.update() + return ( + not filesystem.has_snap() and + not (filesystem.cifs_share or filesystem.nfs_share) + ) + + @staticmethod + def _is_share_from_snapshot(share): + return True if share.snap else False + + @staticmethod + def _validate_share_access_type(share, access): + reason = None + share_proto = share['share_proto'].upper() + + if share_proto == 'CIFS' and access['access_type'] != 'user': + reason = _('Only user access type allowed for CIFS share.') + elif share_proto == 'NFS' and access['access_type'] != 'ip': + reason = _('Only IP access type allowed for NFS share.') + + if reason: + raise exception.InvalidShareAccess(reason=reason) + + @staticmethod + def _validate_share_protocol(share_proto): + if share_proto not in ('NFS', 'CIFS'): + raise exception.InvalidShare( + reason=(_('Invalid NAS protocol supplied: %s.') % + share_proto)) diff --git a/manila/share/drivers/emc/plugins/unity/utils.py b/manila/share/drivers/emc/plugins/unity/utils.py new file mode 100644 index 0000000000..693c12c110 --- /dev/null +++ b/manila/share/drivers/emc/plugins/unity/utils.py @@ -0,0 +1,34 @@ +# Copyright (c) 2016 EMC Corporation. +# 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. +""" Utility module for EMC Unity Manila Driver """ + +import fnmatch + + +def do_match(full, matcher_list): + matched = set() + + full = set([item.strip() for item in full]) + if matcher_list is None: + # default to all + matcher_list = set('*') + else: + matcher_list = set([item.strip() for item in matcher_list]) + + for item in full: + for matcher in matcher_list: + if fnmatch.fnmatchcase(item, matcher): + matched.add(item) + return matched, full - matched diff --git a/manila/share/drivers/emc/plugins/vnx/connection.py b/manila/share/drivers/emc/plugins/vnx/connection.py index a3c2296c7a..b26f563d49 100644 --- a/manila/share/drivers/emc/plugins/vnx/connection.py +++ b/manila/share/drivers/emc/plugins/vnx/connection.py @@ -459,7 +459,7 @@ class VNXStorageConnection(driver.StorageConnection): raise exception.EMCVnxXMLAPIError(err=message) real_pools = set([item for item in backend_pools]) - conf_pools = set([item.strip() for item in pools.split(",")]) + conf_pools = set([item.strip() for item in pools]) matched_pools, unmatched_pools = vnx_utils.do_match_any( real_pools, conf_pools) diff --git a/manila/tests/share/drivers/emc/plugins/unity/__init__.py b/manila/tests/share/drivers/emc/plugins/unity/__init__.py new file mode 100644 index 0000000000..b7a08a4c8a --- /dev/null +++ b/manila/tests/share/drivers/emc/plugins/unity/__init__.py @@ -0,0 +1,20 @@ +# Copyright (c) 2016 EMC Corporation. +# 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 mock +import sys + +sys.modules['storops'] = mock.Mock() +sys.modules['storops.unity'] = mock.Mock() diff --git a/manila/tests/share/drivers/emc/plugins/unity/fake_exceptions.py b/manila/tests/share/drivers/emc/plugins/unity/fake_exceptions.py new file mode 100644 index 0000000000..a743872061 --- /dev/null +++ b/manila/tests/share/drivers/emc/plugins/unity/fake_exceptions.py @@ -0,0 +1,66 @@ +# Copyright (c) 2016 EMC Corporation. +# 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. + + +class UnityFakeException(Exception): + pass + + +class UnityException(UnityFakeException): + pass + + +class UnitySmbShareNameExistedError(UnityException): + pass + + +class UnityFileSystemNameAlreadyExisted(UnityException): + pass + + +class UnityNasServerNameUsedError(UnityException): + pass + + +class UnityNfsShareNameExistedError(UnityException): + pass + + +class UnitySnapNameInUseError(UnityException): + pass + + +class UnityIpAddressUsedError(UnityException): + pass + + +class UnityResourceNotFoundError(UnityException): + pass + + +class UnityOneDnsPerNasServerError(UnityException): + pass + + +class UnitySmbNameInUseError(UnityException): + pass + + +class UnityNfsAlreadyEnabledError(UnityException): + pass + + +class UnityHostNotFoundException(UnityException): + pass diff --git a/manila/tests/share/drivers/emc/plugins/unity/mocked_manila.yaml b/manila/tests/share/drivers/emc/plugins/unity/mocked_manila.yaml new file mode 100644 index 0000000000..44518eea1f --- /dev/null +++ b/manila/tests/share/drivers/emc/plugins/unity/mocked_manila.yaml @@ -0,0 +1,235 @@ +network_allocations: + _type: 'network_allocations' + _properties: &network_allocations_prop + - id: '04ac4c27-9cf7-4406-809c-13edc93e4849' + ip_address: 'fake_ip_addr_1' + cidr: '192.168.1.0/24' + segmentation_id: null + gateway: '192.168.1.1' + network_type: flat + - id: '0cf87de7-5c65-4036-8b6a-e8176c356958' + ip_address: 'fake_ip_addr_2' + cidr: '192.168.1.0/24' + segmentation_id: null + gateway: '192.168.1.1' + network_type: flat + +network_allocations_vlan: + _type: 'network_allocations' + _properties: &network_allocations_vlan_prop + - id: '04ac4c27-9cf7-4406-809c-13edc93e4849' + ip_address: 'fake_ip_addr_1' + cidr: '192.168.1.0/24' + segmentation_id: 160 + gateway: '192.168.1.1' + network_type: vlan + - id: '0cf87de7-5c65-4036-8b6a-e8176c356958' + ip_address: 'fake_ip_addr_2' + cidr: '192.168.1.0/24' + segmentation_id: 160 + gateway: '192.168.1.1' + network_type: vlan + +network_allocations_vxlan: + _type: 'network_allocations' + _properties: &network_allocations_vxlan_prop + - id: '04ac4c27-9cf7-4406-809c-13edc93e4849' + ip_address: 'fake_ip_addr_1' + cidr: '192.168.1.0/24' + segmentation_id: 123 + gateway: '192.168.1.1' + network_type: vxlan + +active_directory: + _type: 'security_service' + _properties: &active_directory_prop + type: 'active_directory' + domain: 'fake_domain_name' + dns_ip: 'fake_dns_ip' + user: 'fake_user' + password: 'fake_password' + +kerberos: + _type: 'security_service' + _properties: &kerberos_prop + <<: *active_directory_prop + type: 'kerberos' + server: 'fake_server' + +security_services: + _type: 'security_services' + _properties: &security_services_prop + services: [*active_directory_prop, *kerberos_prop] + + +network_info__flat: + _type: 'network_info' + _properties: &network_info_flat_prop + name: 'share_network' + neutron_subnet_id: 'a3f3eeac-0b16-4932-8c03-0a37003644ff' + network_type: 'flat' + neutron_net_id: 'e6c96730-2bcf-4ce3-86fa-7cb7740086cb' + ip_version: 4 + id: '232d8218-2743-41d1-832b-4194626e691e' + network_allocations: *network_allocations_prop + server_id: '78fd845f-8e7d-487f-bfde-051d83e78103' + security_services: [] + +network_info__vlan: + _type: 'network_info' + _properties: &network_info__vlan_prop + <<: *network_info_flat_prop + network_type: 'vlan' + network_allocations: *network_allocations_vlan_prop + +network_info__vxlan: + _type: 'network_info' + _properties: &network_info__vxlan_prop + <<: *network_info_flat_prop + network_type: 'vxlan' + network_allocations: *network_allocations_vxlan_prop + +network_info__active_directory: + _type: 'network_info' + _properties: + <<: *network_info__vlan_prop + security_services: [*active_directory_prop] + +network_info__kerberos: + _type: 'network_info' + _properties: + <<: *network_info_flat_prop + security_services: [*kerberos_prop] + +share_server: + _type: 'share_server' + _properties: &share_server_prop + status: 'active' + share_network: *network_info_flat_prop + share_network_id: '232d8218-2743-41d1-832b-4194626e691e' + host: 'openstack@VNX' + backend_details: + share_server_name: '78fd845f-8e7d-487f-bfde-051d83e78103' + network_allocations: *network_allocations_prop + id: '78fd845f-8e7d-487f-bfde-051d83e78103' + +share_server__no_share_server_name: + _type: 'share_server' + _properties: + <<: *share_server_prop + backend_details: + share_server_name: None + id: '78fd845f-8e7d-487f-bfde-051d83e78103' + +server_detail: + _type: 'server_detail' + _properties: &server_detail_prop + share_server_name: '78fd845f-8e7d-487f-bfde-051d83e78103' + +cifs_share: + _type: 'share' + _properties: &cifs_share_prop + share_id: '708e753c-aacb-411f-9c8a-8b8175da4e73' + availability_zone_id: 'de628fb6-1c99-41f6-a06a-adb61ff693b5' + share_network_id: '232d8218-2743-41d1-832b-4194626e691e' + share_server_id: '78fd845f-8e7d-487f-bfde-051d83e78103' + id: '716100cc-e0b4-416b-ac27-d38dd019330d' + size: 1 + user_id: '19bbda71b578471a93363653dcb4c61d' + status: 'creating' + share_type_id: '57679eab-3e67-4052-b180-62b609670e93' + host: 'openstack@VNX#Pool_2' + display_name: 'cifs_share' + share_proto: 'CIFS' + export_locations: [] + is_public: False + +nfs_share: + _type: 'share' + _properties: &nfs_share_prop + share_id: '12eb3777-7008-4721-8243-422507db8f9d' + availability_zone_id: 'de628fb6-1c99-41f6-a06a-adb61ff693b5' + share_network_id: '232d8218-2743-41d1-832b-4194626e691e' + share_server_id: '78fd845f-8e7d-487f-bfde-051d83e78103' + id: 'cb532599-8dc6-4c3e-bb21-74ea54be566c' + size: 1 + user_id: '19bbda71b578471a93363653dcb4c61d' + status: 'creating' + share_type_id: '57679eab-3e67-4052-b180-62b609670e93' + host: 'openstack@VNX#Pool_2' + display_name: 'nfs_share' + share_proto: 'NFS' + export_locations: [] + is_public: False + +invalid_share: + _type: 'share' + _properties: &invalid_share_prop + share_id: '12eb3777-7008-4721-8243-422507db8f9d' + availability_zone_id: 'de628fb6-1c99-41f6-a06a-adb61ff693b5' + share_network_id: '232d8218-2743-41d1-832b-4194626e691e' + share_server_id: '78fd845f-8e7d-487f-bfde-051d83e78103' + id: 'cb532599-8dc6-4c3e-bb21-74ea54be566c' + size: 1 + user_id: '19bbda71b578471a93363653dcb4c61d' + status: 'creating' + share_type_id: '57679eab-3e67-4052-b180-62b609670e93' + host: 'openstack@VNX#Pool_2' + display_name: 'nfs_share' + share_proto: 'fake_proto' + export_locations: [] + is_public: False + +snapshot: + _type: 'snapshot' + _properties: &snapshot_prop + status: 'creating' + share_instance_id: '27e4625e-c336-4749-85bc-634216755fbc' + share: + share_proto: 'CIFS' + snapshot_id: '345476cc-32ab-4565-ba88-e4733b7ffa0e' + progress: '0%' + id: 'ab411797-b1cf-4035-bf14-8771a7bf1805' + share_id: '27e4625e-c336-4749-85bc-634216755fbc' + +cifs_rw_access: + _type: 'access' + _properties: + access_level: 'rw' + access_to: 'administrator' + access_type: 'user' + +cifs_ro_access: + _type: 'access' + _properties: + access_level: 'ro' + access_to: 'administrator' + access_type: 'user' + +nfs_rw_access: + _type: 'access' + _properties: + access_level: 'rw' + access_to: '192.168.1.1' + access_type: 'ip' + +nfs_rw_access_cidr: + _type: 'access' + _properties: + access_level: 'rw' + access_to: '192.168.1.0/24' + access_type: 'ip' + +nfs_ro_access: + _type: 'access' + _properties: + access_level: 'ro' + access_to: '192.168.1.1' + access_type: 'ip' + +invalid_access: + _type: 'access' + _properties: + access_level: 'fake_access_level' + access_to: 'fake_access_to' + access_type: 'fake_type' diff --git a/manila/tests/share/drivers/emc/plugins/unity/mocked_unity.yaml b/manila/tests/share/drivers/emc/plugins/unity/mocked_unity.yaml new file mode 100644 index 0000000000..1b9a39ea76 --- /dev/null +++ b/manila/tests/share/drivers/emc/plugins/unity/mocked_unity.yaml @@ -0,0 +1,958 @@ +sp_a: &sp_a + _properties: + name: 'SPA' + id: 'SPA' + existed: true + +sp_b: &sp_b + _properties: + name: 'SPB' + id: 'SPB' + existed: true + +sp_c: &sp_invalid + _properties: + id: 'SPC' + existed: false + +interface_1: &interface_1 + _properties: + ip_address: 'fake_ip_addr_1' + +interface_2: &interface_2 + _properties: + ip_address: 'fake_ip_addr_2' + +nas_server: &nas_server + _properties: &nas_server_prop + name: '78fd845f-8e7d-487f-bfde-051d83e78103' + file_interface: [*interface_1, *interface_2] + current_sp: *sp_a + +filesystem_base: &filesystem_base + _properties: &filesystem_base_prop + name: 'fake_filesystem_name' + id: 'fake_filesystem_id' + size_total: 50000000000 + is_thin_enabled: true + pool: null + nas_server: null + cifs_share: [] + nfs_share: [] + _methods: + has_snap: False + +snap_base: + _properties: &snap_base_prop + name: 'fake_snap_name' + id: 'fake_snap_id' + size: 50000000000 + filesystem: *filesystem_base + +share_base: + _properties: &share_base_prop + name: 'fake_share_name' + id: 'fake_share_id' + filesystem: null + snap: null + +cifs_share_base: &cifs_share_base + _properties: &cifs_share_base_prop + <<: *share_base_prop + +nfs_share_base: &nfs_share_base + _properties: &nfs_share_base_prop + <<: *share_base_prop + +pool_base: + _properties: &pool_base_prop + name: 'fake_pool_name' + pool_id: 0 + state: Ready + user_capacity_gbs: 1311 + total_subscribed_capacity_gbs: 131 + available_capacity_gbs: 132 + percent_full_threshold: 70 + fast_cache: True + + +pool_1: &pool_1 + _properties: &pool_1_prop + <<: *pool_base_prop + name: 'pool_1' + size_total: 500000 + size_used: 10000 + size_subscribed: 30000 + +pool_2: &pool_2 + _properties: &pool_2_prop + <<: *pool_base_prop + name: 'pool_2' + size_total: 600000 + size_used: 20000 + size_subscribed: 40000 + +nas_server_pool: &nas_server_pool + _properties: + <<: *pool_base_prop + name: 'nas_server_pool' + +port_base: + _properties: &port_base_prop + is_link_up: true + id: 'fake_name' + sp: *sp_a + +port_1: &port_1 + _properties: + <<: *port_base_prop + is_link_up: true + id: 'spa_eth1' + sp: *sp_a + +port_2: &port_2 + _properties: + <<: *port_base_prop + is_link_up: true + id: 'spa_eth2' + sp: *sp_a + +port_3: &port_internal_port + _properties: + <<: *port_base_prop + is_link_up: true + id: 'internal_port' + sp: *sp_a + +unity_base: &unity_base + _methods: &unity_base_method + get_sp: *sp_a + get_pool: + _side_effect: [[*pool_1, *pool_2, *nas_server_pool], *nas_server_pool] + get_ip_port: [*port_1, *port_2] + +test_connect: &test_connect + unity: *unity_base + +test_connect__invalid_sp_configuration: + unity: + _methods: + <<: *unity_base_method + get_sp: *sp_invalid + +test_connect__invalid_pool_configuration: *test_connect + +test_create_nfs_share: + nfs_share: &nfs_share__test_create_nfs_share + _properties: + <<: *nfs_share_base_prop + name: 'cb532599-8dc6-4c3e-bb21-74ea54be566c' + + filesystem: &filesystem__test_create_nfs_share + _properties: &filesystem_prop__test_create_nfs_share + <<: *filesystem_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd4587340' + _methods: + create_nfs_share: *nfs_share__test_create_nfs_share + + pool: &pool__test_create_nfs_share + _properties: + <<: *pool_base_prop + name: 'Pool_2' + _methods: + create_filesystem: *filesystem__test_create_nfs_share + + unity: + _methods: + <<: *unity_base_method + get_pool: + _side_effect: [[*pool_1, *pool_2, *nas_server_pool], + *nas_server_pool, *pool__test_create_nfs_share] + get_nas_server: *nas_server + +test_create_cifs_share: + cifs_share: &cifs_share__test_create_cifs_share + _properties: + <<: *cifs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + _methods: + enable_ace: + + filesystem: &filesystem__test_create_cifs_share + _properties: &filesystem_prop__test_create_cifs_share + <<: *filesystem_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd4587340' + _methods: + create_cifs_share: *cifs_share__test_create_cifs_share + + pool: &pool__test_create_cifs_share + _properties: + <<: *pool_base_prop + name: 'Pool_2' + _methods: + create_filesystem: *filesystem__test_create_cifs_share + + unity: + _methods: + <<: *unity_base_method + get_pool: + _side_effect: [[*pool_1, *pool_2, *nas_server_pool], *nas_server_pool, + *pool__test_create_cifs_share] + get_nas_server: *nas_server + +test_create_share_with_invalid_share_server: + pool: &pool__test_create_share_with_invalid_share_server + _properties: + <<: *pool_base_prop + name: 'Pool_2' + + unity: + _methods: + <<: *unity_base_method + get_pool: + _side_effect: [[*pool_1, *pool_2, *nas_server_pool], *nas_server_pool, + *pool__test_create_share_with_invalid_share_server] + get_nas_server: + _raise: + UnityResourceNotFoundError: 'Failed to get NAS server.' + + +test_delete_share: + filesystem: &filesystem__test_delete_share + _properties: &filesystem_prop__test_delete_share + <<: *filesystem_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + _methods: + delete: + update: + has_snap: False + + cifs_share: &cifs_share__test_delete_share + _properties: + <<: *cifs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + filesystem: *filesystem__test_delete_share + _methods: + delete: + + unity: + _methods: + <<: *unity_base_method + get_cifs_share: *cifs_share__test_delete_share + +test_delete_share__with_invalid_share: + unity: + _methods: + <<: *unity_base_method + get_cifs_share: + _raise: + UnityResourceNotFoundError: 'Failed to get CIFS share.' + +test_delete_share__create_from_snap: + filesystem: &filesystem__test_delete_share__create_from_snap + _properties: &filesystem_prop__test_delete_share__create_from_snap + <<: *filesystem_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd4587340' + _methods: + delete: + update: + has_snap: False + + snap: &snap__test_delete_share__create_from_snap + _properties: &snap_prop__test_delete_share__create_from_snap + <<: *snap_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + filesystem: *filesystem__test_delete_share__create_from_snap + _methods: + delete: + + cifs_share: &cifs_share__test_delete_share__create_from_snap + _properties: + <<: *cifs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + snap: *snap__test_delete_share__create_from_snap + _methods: + delete: + + unity: + _methods: + <<: *unity_base_method + get_cifs_share: *cifs_share__test_delete_share__create_from_snap + get_snap: *snap__test_delete_share__create_from_snap + +test_delete_share__create_from_snap_but_not_isolated: + filesystem: &filesystem__test_delete_share__create_from_snap_but_not_isolated + _properties: &filesystem_prop__test_delete_share__create_from_snap_but_not_isolated + <<: *filesystem_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd4587340' + cifs_share: [*cifs_share_base] + nfs_share: [*nfs_share_base] + _methods: + delete: + update: + has_snap: True + + snap: &snap__test_delete_share__create_from_snap_but_not_isolated + _properties: &snap_prop__test_delete_share__create_from_snap_but_not_isolated + <<: *snap_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + filesystem: *filesystem__test_delete_share__create_from_snap_but_not_isolated + _methods: + delete: + + cifs_share: &cifs_share__test_delete_share__create_from_snap_but_not_isolated + _properties: + <<: *cifs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + snap: *snap__test_delete_share__create_from_snap_but_not_isolated + _methods: + delete: + + unity: + _methods: + <<: *unity_base_method + get_cifs_share: *cifs_share__test_delete_share__create_from_snap_but_not_isolated + +test_delete_share__but_not_isolated: + filesystem: &filesystem__test_delete_share__but_not_isolated + _properties: &filesystem_prop__test_delete_share__but_not_isolated + <<: *filesystem_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd4587340' + _methods: + update: + has_snap: True + + cifs_share: &cifs_share__test_delete_share__but_not_isolated + _properties: + <<: *cifs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + filesystem: *filesystem__test_delete_share__but_not_isolated + _methods: + delete: + + unity: + _methods: + <<: *unity_base_method + get_cifs_share: *cifs_share__test_delete_share__but_not_isolated + +test_extend_cifs_share: + filesystem: &filesystem__test_extend_cifs_share + _properties: &filesystem_prop__test_extend_cifs_share + <<: *filesystem_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + _methods: + extend: + + cifs_share: &cifs_share__test_extend_cifs_share + _properties: + <<: *cifs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + filesystem: *filesystem__test_extend_cifs_share + + unity: + _methods: + <<: *unity_base_method + get_cifs_share: *cifs_share__test_extend_cifs_share + +test_extend_nfs_share: + filesystem: &filesystem__test_extend_nfs_share + _properties: &filesystem_prop__test_extend_nfs_share + <<: *filesystem_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + _methods: + extend: + + cifs_share: &cifs_share__test_extend_nfs_share + _properties: + <<: *cifs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + filesystem: *filesystem__test_extend_nfs_share + + unity: + _methods: + <<: *unity_base_method + get_nfs_share: *cifs_share__test_extend_nfs_share + +test_extend_share__create_from_snap: + snap: &snap__test_extend_share__create_from_snap + _properties: &snap_prop__test_extend_share__create_from_snap + <<: *snap_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + + cifs_share: &cifs_share__test_extend_share__create_from_snap + _properties: + <<: *cifs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + snap: *snap__test_extend_share__create_from_snap + + unity: + _methods: + <<: *unity_base_method + get_cifs_share: *cifs_share__test_extend_share__create_from_snap + +test_create_snapshot_from_filesystem: + filesystem: &filesystem__test_create_snapshot_from_filesystem + _properties: &filesystem_prop__test_create_snapshot_from_filesystem + <<: *filesystem_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + _methods: + create_snap: + + cifs_share: &cifs_share__test_create_snapshot_from_filesystem + _properties: + <<: *cifs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + filesystem: *filesystem__test_create_snapshot_from_filesystem + + unity: + _methods: + <<: *unity_base_method + get_cifs_share: *cifs_share__test_create_snapshot_from_filesystem + +test_create_snapshot_from_snapshot: + snap: &snap__test_create_snapshot_from_snapshot + _properties: &snap_prop__test_create_snapshot_from_snapshot + <<: *snap_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + _methods: + create_snap: + + cifs_share: &cifs_share__test_create_snapshot_from_snapshot + _properties: + <<: *cifs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + snap: *snap__test_create_snapshot_from_snapshot + + unity: + _methods: + <<: *unity_base_method + get_cifs_share: *cifs_share__test_create_snapshot_from_snapshot + get_snap: *snap__test_create_snapshot_from_snapshot + +test_delete_snapshot: + snap: &snap__test_delete_snapshot + _properties: &snap_prop__test_delete_snapshot + <<: *snap_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + _methods: + delete: + + unity: + _methods: + <<: *unity_base_method + get_snap: *snap__test_delete_snapshot + +test_ensure_share_exists: + cifs_share: &cifs_share_ensure_share_exists + _properties: + existed: True + + unity: + _methods: + <<: *unity_base_method + get_cifs_share: *cifs_share_ensure_share_exists + +test_ensure_share_not_exists: + cifs_share: &cifs_share_ensure_share_not_exists + _properties: + existed: False + + unity: + _methods: + <<: *unity_base_method + get_cifs_share: *cifs_share_ensure_share_not_exists + +test_update_share_stats: + unity: + _methods: + <<: *unity_base_method + get_pool: + _side_effect: [[*pool_1, *pool_2], *pool_1, [*pool_1, *pool_2]] + +test_update_share_stats__nonexistent_pools: + unity: + _methods: + <<: *unity_base_method + get_pool: + _side_effect: [[*pool_1, *pool_2], *pool_1, []] + +test_get_pool: + filesystem: &filesystem__test_get_pool + _properties: &filesystem_prop__test_get_pool + <<: *filesystem_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + pool: *pool_1 + + cifs_share: &cifs_share__test_get_pool + _properties: + <<: *cifs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + filesystem: *filesystem__test_get_pool + + unity: + _methods: + <<: *unity_base_method + get_cifs_share: *cifs_share__test_get_pool + +test_setup_server: &test_setup_server + nas_server_1: &nas_server_1__test_setup_server + _properties: + <<: *nas_server_prop + existed: false + + nas_server_2: &nas_server_2__test_setup_server + _properties: + <<: *nas_server_prop + _methods: &nas_server_2__test_setup_server_mehtod + create_file_interface: + enable_nfs_service: + + unity: + _methods: &unity_method__test_setup_server + <<: *unity_base_method + get_nas_server: *nas_server_1__test_setup_server + create_nas_server: *nas_server_2__test_setup_server + +test_setup_server__vlan_network: + <<: *test_setup_server + nas_server: &nas_server__test_setup_server_flat_network + _properties: + <<: *nas_server_prop + existed: true + _methods: + create_file_interface: + create_dns_server: + enable_nfs_service: + + unity: + _methods: + <<: *unity_method__test_setup_server + get_nas_server: *nas_server__test_setup_server_flat_network + +test_setup_server__active_directory: + <<: *test_setup_server + nas_server_2: &nas_server_2__test_setup_server__active_directory + _properties: + <<: *nas_server_prop + _methods: + create_file_interface: + create_dns_server: + enable_cifs_service: + enable_nfs_service: + + unity: + _methods: &unity_method__test_setup_server__active_directory + <<: *unity_method__test_setup_server + create_nas_server: *nas_server_2__test_setup_server__active_directory + +test_setup_server__kerberos: *test_setup_server + +test_setup_server__throw_exception: + <<: *test_setup_server + nas_server_1: &nas_server_1__test_setup_server__throw_exception + _properties: + <<: *nas_server_prop + existed: false + + nas_server_2: &nas_server_2__test_setup_server__throw_exception + _properties: + <<: *nas_server_prop + _methods: + create_file_interface: + create_dns_server: + enable_cifs_service: + enable_nfs_service: + _raise: + UnityException: 'Failed to enable NFS service.' + delete: + + unity: + _methods: + <<: *unity_method__test_setup_server + get_nas_server: *nas_server_2__test_setup_server__throw_exception + create_nas_server: *nas_server_2__test_setup_server__throw_exception + +test_teardown_server: + nas_server: &nas_server__test_teardown_server + _properties: + <<: *nas_server_prop + _methods: + delete: + + unity: + _methods: + <<: *unity_base_method + get_nas_server: *nas_server__test_teardown_server + +test__get_managed_pools: &test__get_managed_pools + unity: + _methods: + <<: *unity_base_method + get_pool: [*pool_1, *pool_2, *nas_server_pool] + +test__get_managed_pools__invalid_pool_configuration: *test__get_managed_pools + +test__get_managed_ports: &test__get_managed_ports + unity: + _methods: + <<: *unity_base_method + get_pool: [*pool_1, *pool_2, *nas_server_pool] + get_ip_port: [*port_1, *port_2, *port_internal_port] + +test__get_managed_pools__invalid_port_configuration: *test__get_managed_ports + + +test_create_cifs_share_from_snapshot: + cifs_share: &cifs_share__test_create_cifs_share_from_snapshot + _properties: + <<: *cifs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + _methods: + enable_ace: + + snapshot_1: &snapshot_1__test_create_cifs_share_from_snapshot + _properties: &snapshot_1_prop__test_create_cifs_share_from_snapshot + <<: *snap_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + _methods: + create_cifs_share: *cifs_share__test_create_cifs_share_from_snapshot + + snapshot_2: &snapshot_2__test_create_cifs_share_from_snapshot + _properties: &snapshot__prop__test_create_cifs_share_from_snapshot + <<: *snap_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd4587340' + _methods: + create_snap: *snapshot_1__test_create_cifs_share_from_snapshot + + unity: + _methods: + <<: *unity_base_method + get_nas_server: *nas_server + get_snap: *snapshot_2__test_create_cifs_share_from_snapshot + +test_create_nfs_share_from_snapshot: + nfs_share: &nfs_share__test_create_nfs_share_from_snapshot + _properties: + <<: *nfs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + _methods: + enable_ace: + + snapshot_1: &snapshot_1__test_create_nfs_share_from_snapshot + _properties: &snapshot_1_prop__test_create_nfs_share_from_snapshot + <<: *snap_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + _methods: + create_nfs_share: *nfs_share__test_create_nfs_share_from_snapshot + + snapshot_2: &snapshot_2__test_create_nfs_share_from_snapshot + _properties: &snapshot__prop__test_create_nfs_share_from_snapshot + <<: *snap_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd4587340' + _methods: + create_snap: *snapshot_1__test_create_nfs_share_from_snapshot + + unity: + _methods: + <<: *unity_base_method + get_nas_server: *nas_server + get_snap: *snapshot_2__test_create_nfs_share_from_snapshot + +test_create_share_from_snapshot_no_server_name: + unity: + _methods: + <<: *unity_base_method + get_nas_server: + _raise: + UnityResourceNotFoundError: 'NAS server is not found' + +test_clear_share_access_cifs: + cifs_share: &cifs_share__test_clear_share_access_cifs + _methods: + clear_access: + _raise: + UnityException: 'clear cifs access invoked' + unity: + _methods: + <<: *unity_base_method + get_cifs_share: *cifs_share__test_clear_share_access_cifs + +test_clear_share_access_nfs: + nfs_share: &nfs_share__test_clear_share_access_nfs + _methods: + clear_access: + _raise: + UnityException: 'clear nfs access invoked' + unity: + _methods: + <<: *unity_base_method + get_nfs_share: *nfs_share__test_clear_share_access_nfs + +test_allow_rw_cifs_share_access: &test_allow_rw_cifs_share_access + cifs_share: &cifs_share__test_allow_rw_cifs_share_access + _properties: + <<: *cifs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + _methods: + add_ace: + + unity: + _methods: + <<: *unity_base_method + get_cifs_share: *cifs_share__test_allow_rw_cifs_share_access + +test_update_access_allow_rw: *test_allow_rw_cifs_share_access + +test_update_access_recovery: + cifs_share: &cifs_share__test_update_access_recovery + _methods: + add_ace: + clear_access: + + unity: + _methods: + <<: *unity_base_method + get_cifs_share: *cifs_share__test_update_access_recovery + +test_allow_ro_cifs_share_access: *test_allow_rw_cifs_share_access + +test_allow_rw_nfs_share_access: + nfs_share: &nfs_share__test_allow_rw_nfs_share_access + _properties: + <<: *nfs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + _methods: + allow_read_write_access: + allow_root_access: + + unity: + _methods: + <<: *unity_base_method + get_nfs_share: *nfs_share__test_allow_rw_nfs_share_access + +test_allow_ro_nfs_share_access: + nfs_share: &nfs_share__test_allow_ro_nfs_share_access + _properties: + <<: *nfs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + _methods: + allow_read_only_access: + + unity: + _methods: + <<: *unity_base_method + get_nfs_share: *nfs_share__test_allow_ro_nfs_share_access + +test_deny_cifs_share_access: + cifs_share: &cifs_share__test_deny_cifs_share_access + _properties: + <<: *cifs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + _methods: + delete_ace: + + unity: + _methods: + <<: *unity_base_method + get_cifs_share: *cifs_share__test_deny_cifs_share_access + +test_deny_nfs_share_access: &test_deny_nfs_share_access + nfs_share: &nfs_share__test_deny_nfs_share_access + _properties: + <<: *nfs_share_base_prop + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + _methods: + delete_access: + + unity: + _methods: + <<: *unity_base_method + get_nfs_share: *nfs_share__test_deny_nfs_share_access + +test_update_access_deny_nfs: *test_deny_nfs_share_access + +# The following test cases are for client.py + +test_create_cifs_share__existed_expt: + filesystem: + _methods: + create_cifs_share: + _raise: + UnitySmbShareNameExistedError: 'CIFS share already exists.' + + cifs_share: &cifs_share__test_create_cifs_share__existed_expt + _properties: + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + + unity: + _methods: + get_cifs_share: *cifs_share__test_create_cifs_share__existed_expt + +test_create_nfs_share__existed_expt: + filesystem: + _methods: + create_nfs_share: + _raise: + UnityNfsShareNameExistedError: 'NFS share already exists.' + + nfs_share: &nfs_share__test_create_nfs_share__existed_expt + _properties: + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + + unity: + _methods: + get_nfs_share: *nfs_share__test_create_nfs_share__existed_expt + +test_get_share_with_invalid_proto: + share: + _properties: + <<: *share_base_prop + +test_create_filesystem__existed_expt: + filesystem: &filesystem__test_create_filesystem__existed_expt + _properties: + name: '716100cc-e0b4-416b-ac27-d38dd019330d' + size: 10 + proto: 'CIFS' + + pool: + _methods: + create_filesystem: + _raise: + UnityFileSystemNameAlreadyExisted: 'Pool already exists.' + + nas_server: + _properties: + <<: *nas_server_prop + + unity: + _methods: + get_filesystem: *filesystem__test_create_filesystem__existed_expt + +test_delete_filesystem__nonexistent_expt: + filesystem: + _properties: + name: already removed filsystem + _methods: + delete: + _raise: + UnityResourceNotFoundError: 'Filesystem is non-existent.' + +test_create_nas_server__existed_expt: + sp: + _properites: + name: 'SP' + + pool: + _properites: + name: 'fake_pool' + + nas_server: &nas_server__test_create_nas_server__existed_expt + _properties: + <<: *nas_server_prop + + unity: + _methods: + create_nas_server: + _raise: + UnityNasServerNameUsedError: 'NAS Server already exists.' + get_nas_server: *nas_server__test_create_nas_server__existed_expt + +test_delete_nas_server__nonexistent_expt: + nas_server: &nas_server__test_delete_nas_server__nonexistent_expt + _properties: + <<: *nas_server_prop + _methods: + delete: + _raise: + UnityResourceNotFoundError: 'NAS server is non-existent.' + + unity: + _methods: + get_nas_server: *nas_server__test_delete_nas_server__nonexistent_expt + +test_create_dns_server__existed_expt: + nas_server: + _methods: + create_dns_server: + _raise: + UnityOneDnsPerNasServerError: 'DNS server already exists.' + +test_create_interface__existed_expt: + nas_server: + _properties: + <<: *nas_server_prop + _methods: + create_file_interface: + _raise: + UnityIpAddressUsedError: 'IP address is already used.' + +test_enable_cifs_service__existed_expt: + nas_server: + _properties: + <<: *nas_server_prop + _methods: + enable_cifs_service: + _raise: + UnitySmbNameInUseError: 'CIFS server already exists.' + +test_enable_nfs_service__existed_expt: + nas_server: + _properties: + <<: *nas_server_prop + _methods: + enable_nfs_service: + _raise: + UnityNfsAlreadyEnabledError: 'NFS server already exists.' + +test_create_snapshot__existed_expt: + filesystem: + _properties: + <<: *filesystem_base_prop + _methods: + create_snap: + _raise: + UnitySnapNameInUseError: 'Snapshot already exists.' + + snapshot: + _properties: + <<: *snap_base_prop + +test_create_snap_of_snap__existed_expt: + src_snapshot: + _methods: + create_snap: + _raise: + UnitySnapNameInUseError: 'Snapshot already exists.' + + dest_snapshot: &dest_snapshot__test_create_snap_of_snap__existed_expt + _properties: + <<: *snap_base_prop + + unity: + _methods: + get_snap: *dest_snapshot__test_create_snap_of_snap__existed_expt + +test_delete_snapshot__nonexistent_expt: + snapshot: + _properties: + <<: *snap_base_prop + _methods: + delete: + _raise: + UnityResourceNotFoundError: 'Snapshot is non-existent.' + +test_nfs_deny_access__nonexistent_expt: + nfs_share: &nfs_share__test_nfs_deny_access__nonexistent_expt + _methods: + delete_access: + _raise: + UnityHostNotFoundException: "Unity Host is non-existent" + unity: + _methods: + get_nfs_share: *nfs_share__test_nfs_deny_access__nonexistent_expt + +test_get_storage_processor: + unity: + _methods: + get_sp: *sp_a diff --git a/manila/tests/share/drivers/emc/plugins/unity/res_mock.py b/manila/tests/share/drivers/emc/plugins/unity/res_mock.py new file mode 100644 index 0000000000..da5943639c --- /dev/null +++ b/manila/tests/share/drivers/emc/plugins/unity/res_mock.py @@ -0,0 +1,337 @@ +# Copyright (c) 2016 EMC Corporation. +# 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 mock +from oslo_log import log + +from manila.share import configuration as conf +from manila.share.drivers.emc.plugins.unity import client +from manila.share.drivers.emc.plugins.unity import connection +from manila.tests.db import fakes as db_fakes +from manila.tests import fake_share +from manila.tests.share.drivers.emc.plugins.unity import fake_exceptions +from manila.tests.share.drivers.emc.plugins.unity import utils + +client.storops_ex = fake_exceptions +connection.storops_ex = fake_exceptions + +LOG = log.getLogger(__name__) + +SYMBOL_TYPE = '_type' +SYMBOL_PROPERTIES = '_properties' +SYMBOL_METHODS = '_methods' +SYMBOL_SIDE_EFFECT = '_side_effect' +SYMBOL_RAISE = '_raise' + + +def _has_side_effect(node): + return isinstance(node, dict) and SYMBOL_SIDE_EFFECT in node + + +def _has_raise(node): + return isinstance(node, dict) and SYMBOL_RAISE in node + + +def fake_share_server(**kwargs): + share_server = { + 'instance_id': 'fake_instance_id', + 'backend_details': {}, + } + + share_server.update(kwargs) + + return db_fakes.FakeModel(share_server) + + +def fake_network_info(**kwargs): + network_info = { + 'id': 'fake_net_id', + 'name': 'net_name', + 'subnet': [], + } + network_info.update(kwargs) + return network_info + + +def fake_server_detail(**kwargs): + server_detail = { + 'share_server_name': 'fake_server_name', + } + server_detail.update(kwargs) + return server_detail + + +def fake_security_services(**kwargs): + return kwargs['services'] + + +def fake_access(**kwargs): + access = {} + access.update(kwargs) + return access + + +class FakeEMCShareDriver(object): + def __init__(self): + self.configuration = conf.Configuration(None) + self.configuration.emc_share_backend = 'unity' + self.configuration.emc_nas_server_container = 'SPA' + self.configuration.emc_nas_server = '192.168.1.1' + self.configuration.emc_nas_login = 'fake_user' + self.configuration.emc_nas_password = 'fake_password' + self.configuration.share_backend_name = 'EMC_NAS_Storage' + self.configuration.emc_nas_server_pool = 'nas_server_pool' + self.configuration.local_conf.max_over_subscription_ratio = 20 + + +STATS = dict( + share_backend_name='Unity', + vendor_name='EMC', + storage_protocol='NFS_CIFS', + driver_version='2.0.0,', + pools=[], +) + + +class DriverResourceMock(dict): + fake_func_mapping = {} + + def __init__(self, yaml_file): + yaml_dict = utils.load_yaml(yaml_file) + if isinstance(yaml_dict, dict): + for name, body in yaml_dict.items(): + if isinstance(body, dict): + props = body[SYMBOL_PROPERTIES] + if isinstance(props, dict): + for prop_name, prop_value in props.items(): + if isinstance(prop_value, dict) and prop_value: + # get the first key as the convert function + func_name = list(prop_value.keys())[0] + if func_name.startswith('_'): + func = getattr(self, func_name) + props[prop_name] = ( + func(**prop_value[func_name])) + if body[SYMBOL_TYPE] in self.fake_func_mapping: + self[name] = ( + self.fake_func_mapping[body[SYMBOL_TYPE]](**props)) + + +class ManilaResourceMock(DriverResourceMock): + fake_func_mapping = { + 'share': fake_share.fake_share, + 'snapshot': fake_share.fake_snapshot, + 'network_info': fake_network_info, + 'share_server': fake_share_server, + 'server_detail': fake_server_detail, + 'security_services': fake_security_services, + 'access': fake_access, + } + + def __init__(self, yaml_file): + super(ManilaResourceMock, self).__init__(yaml_file) + + +class StorageObjectMock(object): + PROPS = 'props' + + def __init__(self, yaml_dict): + self.__dict__[StorageObjectMock.PROPS] = {} + props = yaml_dict.get(SYMBOL_PROPERTIES, None) + if props: + for k, v in props.items(): + setattr(self, k, StoragePropertyMock(k, v)()) + + methods = yaml_dict.get(SYMBOL_METHODS, None) + if methods: + for k, v in methods.items(): + setattr(self, k, StorageMethodMock(k, v)) + + def __setattr__(self, key, value): + self.__dict__[StorageObjectMock.PROPS][key] = value + + def __getattr__(self, item): + try: + super(StorageObjectMock, self).__getattr__(item) + except AttributeError: + return self.__dict__[StorageObjectMock.PROPS][item] + except KeyError: + raise KeyError('No such method or property for mock object.') + + +class StoragePropertyMock(mock.PropertyMock): + def __init__(self, name, property_body): + return_value = property_body + side_effect = None + + # only support return_value and side_effect for property + if _has_side_effect(property_body): + side_effect = property_body[SYMBOL_SIDE_EFFECT] + return_value = None + + if side_effect: + super(StoragePropertyMock, self).__init__( + name=name, + side_effect=side_effect) + elif return_value: + super(StoragePropertyMock, self).__init__( + name=name, + return_value=_build_mock_object(return_value)) + else: + super(StoragePropertyMock, self).__init__( + name=name, + return_value=return_value) + + +class StorageMethodMock(mock.Mock): + def __init__(self, name, method_body): + return_value = method_body + exception = None + side_effect = None + + # support return_value, side_effect and exception for method + if _has_side_effect(method_body) or _has_raise(method_body): + exception = method_body.get(SYMBOL_RAISE, None) + side_effect = method_body.get(SYMBOL_SIDE_EFFECT, None) + return_value = None + + if exception: + if isinstance(exception, dict) and exception: + ex_name = list(exception.keys())[0] + ex = getattr(fake_exceptions, ex_name) + super(StorageMethodMock, self).__init__( + name=name, + side_effect=ex(exception[ex_name])) + elif side_effect: + super(StorageMethodMock, self).__init__( + name=name, + side_effect=_build_mock_object(side_effect)) + elif return_value: + super(StorageMethodMock, self).__init__( + name=name, + return_value=_build_mock_object(return_value)) + else: + super(StorageMethodMock, self).__init__( + name=name, return_value=None) + + +class StorageResourceMock(dict): + def __init__(self, yaml_file): + yaml_dict = utils.load_yaml(yaml_file) + if isinstance(yaml_dict, dict): + for section, sec_body in yaml_dict.items(): + self[section] = {} + if isinstance(sec_body, dict): + for obj_name, obj_body in sec_body.items(): + self[section][obj_name] = _build_mock_object(obj_body) + + +def _is_mock_object(yaml_info): + return (isinstance(yaml_info, dict) and + (SYMBOL_PROPERTIES in yaml_info or SYMBOL_METHODS in yaml_info)) + + +def _build_mock_object(yaml_dict): + if _is_mock_object(yaml_dict): + return StorageObjectMock(yaml_dict) + elif isinstance(yaml_dict, dict): + return {k: _build_mock_object(v) for k, v in yaml_dict.items()} + elif isinstance(yaml_dict, list): + return [_build_mock_object(each) for each in yaml_dict] + else: + return yaml_dict + + +manila_res = ManilaResourceMock('mocked_manila.yaml') +unity_res = StorageResourceMock('mocked_unity.yaml') +STORAGE_RES_MAPPING = { + 'TestClient': unity_res, + 'TestConnection': unity_res, +} + + +def mock_input(resource): + def inner_dec(func): + def decorated(cls, *args, **kwargs): + if cls._testMethodName in resource: + storage_res = resource[cls._testMethodName] + return func(cls, storage_res, *args, **kwargs) + + return decorated + + return inner_dec + + +mock_client_input = mock_input(unity_res) + + +def patch_client(func): + def client_decorator(cls, *args, **kwargs): + storage_res = {} + if func.__name__ in STORAGE_RES_MAPPING[cls.__class__.__name__]: + storage_res = ( + STORAGE_RES_MAPPING[cls.__class__.__name__][func.__name__]) + with utils.patch_system as patched_system: + if 'unity' in storage_res: + patched_system.return_value = storage_res['unity'] + _client = client.UnityClient(host='fake_host', + username='fake_user', + password='fake_passwd') + return func(cls, _client, *args, **kwargs) + + return client_decorator + + +def mock_driver_input(resource): + def inner_dec(func): + def decorated(cls, *args, **kwargs): + return func(cls, resource, *args, **kwargs) + + return decorated + + return inner_dec + + +mock_manila_input = mock_driver_input(manila_res) + + +def patch_connection_init(func): + def connection_decorator(cls, *args, **kwargs): + storage_res = {} + if func.__name__ in STORAGE_RES_MAPPING[cls.__class__.__name__]: + storage_res = ( + STORAGE_RES_MAPPING[cls.__class__.__name__][func.__name__]) + with utils.patch_system as patched_system: + if 'unity' in storage_res: + patched_system.return_value = storage_res['unity'] + conn = connection.UnityStorageConnection(LOG) + return func(cls, conn, *args, **kwargs) + + return connection_decorator + + +def patch_connection(func): + def connection_decorator(cls, *args, **kwargs): + storage_res = {} + if func.__name__ in STORAGE_RES_MAPPING[cls.__class__.__name__]: + storage_res = ( + STORAGE_RES_MAPPING[cls.__class__.__name__][func.__name__]) + with utils.patch_system as patched_system: + if 'unity' in storage_res: + patched_system.return_value = storage_res['unity'] + conn = connection.UnityStorageConnection(LOG) + conn.connect(FakeEMCShareDriver(), None) + return func(cls, conn, *args, **kwargs) + + return connection_decorator diff --git a/manila/tests/share/drivers/emc/plugins/unity/test_client.py b/manila/tests/share/drivers/emc/plugins/unity/test_client.py new file mode 100644 index 0000000000..02652d5e65 --- /dev/null +++ b/manila/tests/share/drivers/emc/plugins/unity/test_client.py @@ -0,0 +1,158 @@ +# Copyright (c) 2016 EMC Corporation. +# 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. + +from manila import exception +from manila import test +from manila.tests.share.drivers.emc.plugins.unity import res_mock + + +class TestClient(test.TestCase): + @res_mock.mock_client_input + @res_mock.patch_client + def test_create_cifs_share__existed_expt(self, client, mocked_input): + resource = mocked_input['filesystem'] + share = mocked_input['cifs_share'] + + new_share = client.create_cifs_share(resource, share.name) + + self.assertEqual(share.name, new_share.name) + + @res_mock.mock_client_input + @res_mock.patch_client + def test_create_nfs_share__existed_expt(self, client, mocked_input): + resource = mocked_input['filesystem'] + share = mocked_input['nfs_share'] + + new_share = client.create_nfs_share(resource, share.name) + + self.assertEqual(share.name, new_share.name) + + @res_mock.mock_client_input + @res_mock.patch_client + def test_get_share_with_invalid_proto(self, client, mocked_input): + share = mocked_input['share'] + + self.assertRaises(exception.BadConfigurationException, + client.get_share, + share.name, + 'fake_proto') + + @res_mock.mock_client_input + @res_mock.patch_client + def test_create_filesystem__existed_expt(self, client, mocked_input): + pool = mocked_input['pool'] + nas_server = mocked_input['nas_server'] + filesystem = mocked_input['filesystem'] + + new_filesystem = client.create_filesystem(pool, + nas_server, + filesystem.name, + filesystem.size, + filesystem.proto) + + self.assertEqual(filesystem.name, new_filesystem.name) + + @res_mock.mock_client_input + @res_mock.patch_client + def test_delete_filesystem__nonexistent_expt(self, client, mocked_input): + filesystem = mocked_input['filesystem'] + + client.delete_filesystem(filesystem) + + @res_mock.mock_client_input + @res_mock.patch_client + def test_create_nas_server__existed_expt(self, client, mocked_input): + sp = mocked_input['sp'] + pool = mocked_input['pool'] + nas_server = mocked_input['nas_server'] + + new_nas_server = client.create_nas_server(nas_server.name, sp, pool) + + self.assertEqual(nas_server.name, new_nas_server.name) + + @res_mock.mock_client_input + @res_mock.patch_client + def test_delete_nas_server__nonexistent_expt(self, client, mocked_input): + nas_server = mocked_input['nas_server'] + + client.delete_nas_server(nas_server.name) + + @res_mock.mock_client_input + @res_mock.patch_client + def test_create_dns_server__existed_expt(self, client, mocked_input): + nas_server = mocked_input['nas_server'] + + client.create_dns_server(nas_server, 'fake_domain', 'fake_dns_ip') + + @res_mock.mock_client_input + @res_mock.patch_client + def test_create_interface__existed_expt(self, client, mocked_input): + nas_server = mocked_input['nas_server'] + port_set = ('fake_port',) + + self.assertRaises(exception.IPAddressInUse, client.create_interface, + nas_server, 'fake_ip_addr', 'fake_mask', + 'fake_gateway', ports=port_set) + + @res_mock.mock_client_input + @res_mock.patch_client + def test_enable_cifs_service__existed_expt(self, client, mocked_input): + nas_server = mocked_input['nas_server'] + + client.enable_cifs_service( + nas_server, 'domain_name', 'fake_user', 'fake_passwd') + + @res_mock.mock_client_input + @res_mock.patch_client + def test_enable_nfs_service__existed_expt(self, client, mocked_input): + nas_server = mocked_input['nas_server'] + + client.enable_nfs_service(nas_server) + + @res_mock.mock_client_input + @res_mock.patch_client + def test_create_snapshot__existed_expt(self, client, mocked_input): + nas_server = mocked_input['filesystem'] + exp_snap = mocked_input['snapshot'] + + client.create_snapshot(nas_server, exp_snap.name) + + @res_mock.mock_client_input + @res_mock.patch_client + def test_create_snap_of_snap__existed_expt(self, client, mocked_input): + snapshot = mocked_input['src_snapshot'] + dest_snap = mocked_input['dest_snapshot'] + + new_snap = client.create_snap_of_snap( + snapshot, dest_snap.name, 'checkpoint') + + self.assertEqual(dest_snap.name, new_snap.name) + + @res_mock.mock_client_input + @res_mock.patch_client + def test_delete_snapshot__nonexistent_expt(self, client, mocked_input): + snapshot = mocked_input['snapshot'] + + client.delete_snapshot(snapshot) + + @res_mock.patch_client + def test_nfs_deny_access__nonexistent_expt(self, client): + client.nfs_deny_access('fake_share_name', 'fake_ip_addr') + + @res_mock.patch_client + def test_get_storage_processor(self, client): + sp = client.get_storage_processor(sp_id='SPA') + + self.assertEqual('SPA', sp.name) diff --git a/manila/tests/share/drivers/emc/plugins/unity/test_connection.py b/manila/tests/share/drivers/emc/plugins/unity/test_connection.py new file mode 100644 index 0000000000..6ea2826c3e --- /dev/null +++ b/manila/tests/share/drivers/emc/plugins/unity/test_connection.py @@ -0,0 +1,617 @@ +# Copyright (c) 2016 EMC Corporation. +# 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 copy +import ddt +import mock +from oslo_utils import units +import six + +from manila import exception +from manila import test +from manila.tests.share.drivers.emc.plugins.unity import fake_exceptions +from manila.tests.share.drivers.emc.plugins.unity import res_mock + + +@ddt.ddt +class TestConnection(test.TestCase): + client = None + + @classmethod + def setUpClass(cls): + cls.emc_share_driver = res_mock.FakeEMCShareDriver() + + @res_mock.patch_connection_init + def test_connect(self, connection): + connection.connect(res_mock.FakeEMCShareDriver(), None) + + @res_mock.patch_connection_init + def test_connect__invalid_sp_configuration(self, connection): + self.assertRaises(exception.BadConfigurationException, + connection.connect, + res_mock.FakeEMCShareDriver(), None) + + @res_mock.patch_connection + def test_connect__invalid_pool_configuration(self, connection): + f = connection.client.system.get_pool + f.side_effect = fake_exceptions.UnityResourceNotFoundError() + + self.assertRaises(exception.BadConfigurationException, + connection._config_pool, + 'faked_pool_name') + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_create_nfs_share(self, connection, mocked_input): + share = mocked_input['nfs_share'] + share_server = mocked_input['share_server'] + + location = connection.create_share(None, share, share_server) + + exp_location = [ + {'path': 'fake_ip_addr_1:/cb532599-8dc6-4c3e-bb21-74ea54be566c'}, + {'path': 'fake_ip_addr_2:/cb532599-8dc6-4c3e-bb21-74ea54be566c'}, + ] + exp_location = sorted(exp_location, key=lambda x: sorted(x['path'])) + location = sorted(location, key=lambda x: sorted(x['path'])) + self.assertEqual(exp_location, location) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_create_cifs_share(self, connection, mocked_input): + share = mocked_input['cifs_share'] + share_server = mocked_input['share_server'] + + location = connection.create_share(None, share, share_server) + + exp_location = [ + {'path': r'\\fake_ip_addr_1\716100cc-e0b4-416b-ac27-d38dd019330d'}, + {'path': r'\\fake_ip_addr_2\716100cc-e0b4-416b-ac27-d38dd019330d'}, + ] + exp_location = sorted(exp_location, key=lambda x: sorted(x['path'])) + location = sorted(location, key=lambda x: sorted(x['path'])) + self.assertEqual(exp_location, location) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_create_share_with_invalid_proto(self, connection, mocked_input): + share = mocked_input['invalid_share'] + share_server = mocked_input['share_server'] + + self.assertRaises(exception.InvalidShare, + connection.create_share, + None, + share, + share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_create_share_without_share_server(self, connection, + mocked_input): + share = mocked_input['cifs_share'] + + self.assertRaises(exception.InvalidInput, + connection.create_share, + None, + share, + None) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_create_share__no_server_name_in_backend_details(self, connection, + mocked_input): + share = mocked_input['cifs_share'] + share_server = { + 'backend_details': {'share_server_name': None}, + 'id': 'test', + } + + self.assertRaises(exception.InvalidInput, + connection.create_share, + None, + share, + share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_create_share_with_invalid_share_server(self, connection, + mocked_input): + share = mocked_input['cifs_share'] + share_server = mocked_input['share_server'] + + self.assertRaises(exception.EMCUnityError, + connection.create_share, + None, + share, + share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_delete_share(self, connection, mocked_input): + share = mocked_input['cifs_share'] + share_server = mocked_input['share_server'] + + connection.delete_share(None, share, share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_delete_share__with_invalid_share(self, connection, mocked_input): + share = mocked_input['cifs_share'] + + connection.delete_share(None, share, None) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_delete_share__create_from_snap(self, connection, + mocked_input): + share = mocked_input['cifs_share'] + share_server = mocked_input['share_server'] + + connection.delete_share(None, share, share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_delete_share__create_from_snap_but_not_isolated(self, + connection, + mocked_input): + share = mocked_input['cifs_share'] + share_server = mocked_input['share_server'] + + connection.delete_share(None, share, share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_delete_share__but_not_isolated(self, connection, + mocked_input): + share = mocked_input['cifs_share'] + share_server = mocked_input['share_server'] + + connection.delete_share(None, share, share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_extend_cifs_share(self, connection, mocked_input): + share = mocked_input['cifs_share'] + share_server = mocked_input['share_server'] + new_size = 50 * units.Gi + + connection.extend_share(share, new_size, share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_extend_nfs_share(self, connection, mocked_input): + share = mocked_input['nfs_share'] + share_server = mocked_input['share_server'] + new_size = 50 * units.Gi + + connection.extend_share(share, new_size, share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_extend_share__create_from_snap(self, connection, mocked_input): + share = mocked_input['cifs_share'] + share_server = mocked_input['share_server'] + new_size = 50 * units.Gi + + self.assertRaises(exception.ShareExtendingError, + connection.extend_share, + share, + new_size, + share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_create_snapshot_from_filesystem(self, connection, mocked_input): + snapshot = mocked_input['snapshot'] + share_server = mocked_input['share_server'] + + connection.create_snapshot(None, snapshot, share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_create_snapshot_from_snapshot(self, connection, mocked_input): + snapshot = mocked_input['snapshot'] + share_server = mocked_input['share_server'] + + connection.create_snapshot(None, snapshot, share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_delete_snapshot(self, connection, mocked_input): + snapshot = mocked_input['snapshot'] + share_server = mocked_input['share_server'] + + connection.delete_snapshot(None, snapshot, share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_ensure_share_exists(self, connection, mocked_input): + share = mocked_input['cifs_share'] + + connection.ensure_share(None, share, None) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_ensure_share_not_exists(self, connection, mocked_input): + share = mocked_input['cifs_share'] + + self.assertRaises(exception.ShareNotFound, + connection.ensure_share, + None, + share, + None) + + @res_mock.patch_connection + def test_update_share_stats(self, connection): + stat_dict = copy.deepcopy(res_mock.STATS) + + connection.update_share_stats(stat_dict) + self.assertEqual(5, len(stat_dict)) + pool = stat_dict['pools'][0] + self.assertEqual('pool_1', pool['pool_name']) + self.assertEqual(500000.0, pool['total_capacity_gb']) + self.assertEqual(False, pool['qos']) + self.assertEqual(30000.0, pool['provisioned_capacity_gb']) + self.assertEqual(20, pool['max_over_subscription_ratio']) + self.assertEqual(10000.0, pool['allocated_capacity_gb']) + self.assertEqual(0, pool['reserved_percentage']) + self.assertTrue(pool['thin_provisioning']) + self.assertEqual(490000.0, pool['free_capacity_gb']) + + @res_mock.patch_connection + def test_update_share_stats__nonexistent_pools(self, connection): + stat_dict = copy.deepcopy(res_mock.STATS) + + self.assertRaises(exception.EMCUnityError, + connection.update_share_stats, + stat_dict) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_get_pool(self, connection, mocked_input): + share = mocked_input['cifs_share'] + + connection.get_pool(share) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_setup_server(self, connection, mocked_input): + network_info = mocked_input['network_info__flat'] + + connection.setup_server(network_info) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_setup_server__vlan_network(self, connection, mocked_input): + network_info = mocked_input['network_info__vlan'] + + connection.setup_server(network_info) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_setup_server__vxlan_network(self, connection, mocked_input): + network_info = mocked_input['network_info__vxlan'] + + self.assertRaises(exception.NetworkBadConfigurationException, + connection.setup_server, + network_info) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_setup_server__active_directory(self, connection, mocked_input): + network_info = mocked_input['network_info__active_directory'] + + connection.setup_server(network_info) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_setup_server__kerberos(self, connection, mocked_input): + network_info = mocked_input['network_info__kerberos'] + + connection.setup_server(network_info) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_setup_server__throw_exception(self, connection, mocked_input): + network_info = mocked_input['network_info__flat'] + + self.assertRaises(fake_exceptions.UnityException, + connection.setup_server, + network_info) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_teardown_server(self, connection, mocked_input): + server_detail = mocked_input['server_detail'] + security_services = mocked_input['security_services'] + + connection.teardown_server(server_detail, security_services) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_teardown_server__no_server_detail(self, connection, mocked_input): + security_services = mocked_input['security_services'] + + connection.teardown_server(None, security_services) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_teardown_server__no_share_server_name(self, connection, + mocked_input): + server_detail = {'share_server_name': None} + security_services = mocked_input['security_services'] + + connection.teardown_server(server_detail, security_services) + + @ddt.data({'configured_pools': None, + 'matched_pools': {'pool_1', 'pool_2', 'nas_server_pool'}}, + {'configured_pools': ['*'], + 'matched_pools': {'pool_1', 'pool_2', 'nas_server_pool'}}, + {'configured_pools': ['pool_*'], + 'matched_pools': {'pool_1', 'pool_2'}}, + {'configured_pools': ['*pool'], + 'matched_pools': {'nas_server_pool'}}, + {'configured_pools': ['nas_server_pool'], + 'matched_pools': {'nas_server_pool'}}, + {'configured_pools': ['nas_*', 'pool_*'], + 'matched_pools': {'pool_1', 'pool_2', 'nas_server_pool'}}) + @res_mock.patch_connection + @ddt.unpack + def test__get_managed_pools(self, connection, mocked_input): + configured_pools = mocked_input['configured_pools'] + matched_pool = mocked_input['matched_pools'] + + pools = connection._get_managed_pools(configured_pools) + + self.assertEqual(matched_pool, pools) + + @res_mock.patch_connection + def test__get_managed_pools__invalid_pool_configuration(self, connection): + configured_pools = 'fake_pool' + + self.assertRaises(exception.BadConfigurationException, + connection._get_managed_pools, + configured_pools) + + @ddt.data({'configured_ports': None, + 'matched_ports': {'spa_eth1', 'spa_eth2'}}, + {'configured_ports': ['*'], + 'matched_ports': {'spa_eth1', 'spa_eth2'}}, + {'configured_ports': ['spa_*'], + 'matched_ports': {'spa_eth1', 'spa_eth2'}}, + {'configured_ports': ['*_eth1'], + 'matched_ports': {'spa_eth1'}}, + {'configured_ports': ['spa_eth1'], + 'matched_ports': {'spa_eth1'}}, + {'configured_ports': ['spa_eth1', 'spa_eth2'], + 'matched_ports': {'spa_eth1', 'spa_eth2'}}) + @res_mock.patch_connection + @ddt.unpack + def test__get_managed_ports(self, connection, mocked_input): + sp = mock.Mock() + sp.id = 'SPA' + configured_ports = mocked_input['configured_ports'] + matched_ports = mocked_input['matched_ports'] + + ports = connection._get_managed_ports(configured_ports, sp) + + self.assertEqual(matched_ports, ports) + + @res_mock.patch_connection + def test__get_managed_ports__invalid_port_configuration(self, connection): + configured_ports = 'fake_port' + sp = mock.Mock() + sp.id = 'SPA' + + self.assertRaises(exception.BadConfigurationException, + connection._get_managed_ports, + configured_ports, + sp) + + @res_mock.patch_connection + def test__get_pool_name_from_host__no_pool_name(self, connection): + host = 'openstack@Unity' + + self.assertRaises(exception.InvalidHost, + connection._get_pool_name_from_host, + host) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_create_cifs_share_from_snapshot(self, connection, mocked_input): + share = mocked_input['cifs_share'] + snapshot = mocked_input['snapshot'] + share_server = mocked_input['share_server'] + + connection.create_share_from_snapshot(None, share, snapshot, + share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_create_nfs_share_from_snapshot(self, connection, mocked_input): + share = mocked_input['nfs_share'] + snapshot = mocked_input['snapshot'] + share_server = mocked_input['share_server'] + + connection.create_share_from_snapshot(None, share, snapshot, + share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_create_share_from_snapshot_no_server_name(self, + connection, + mocked_input): + share = mocked_input['nfs_share'] + snapshot = mocked_input['snapshot'] + share_server = mocked_input['share_server__no_share_server_name'] + + self.assertRaises(exception.EMCUnityError, + connection.create_share_from_snapshot, + None, + share, + snapshot, + share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_clear_share_access_cifs(self, connection, mocked_input): + share = mocked_input['cifs_share'] + + self.assertRaises(fake_exceptions.UnityException, + connection.clear_access, + share) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_clear_share_access_nfs(self, connection, mocked_input): + share = mocked_input['nfs_share'] + + self.assertRaises(fake_exceptions.UnityException, + connection.clear_access, + share) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_allow_rw_cifs_share_access(self, connection, mocked_input): + share = mocked_input['cifs_share'] + rw_access = mocked_input['cifs_rw_access'] + share_server = mocked_input['share_server'] + + connection.allow_access(None, share, rw_access, share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_update_access_allow_rw(self, connection, mocked_input): + share = mocked_input['cifs_share'] + rw_access = mocked_input['cifs_rw_access'] + share_server = mocked_input['share_server'] + + connection.update_access(None, share, None, [rw_access], None, + share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_update_access_recovery(self, connection, mocked_input): + share = mocked_input['cifs_share'] + rw_access = mocked_input['cifs_rw_access'] + share_server = mocked_input['share_server'] + + connection.update_access(None, share, [rw_access], None, None, + share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_allow_ro_cifs_share_access(self, connection, mocked_input): + share = mocked_input['cifs_share'] + rw_access = mocked_input['cifs_ro_access'] + share_server = mocked_input['share_server'] + + connection.allow_access(None, share, rw_access, share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_allow_rw_nfs_share_access(self, connection, mocked_input): + share = mocked_input['nfs_share'] + rw_access = mocked_input['nfs_rw_access'] + share_server = mocked_input['share_server'] + + connection.allow_access(None, share, rw_access, share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_allow_rw_nfs_share_access_cidr(self, connection, mocked_input): + share = mocked_input['nfs_share'] + rw_access = mocked_input['nfs_rw_access_cidr'] + share_server = mocked_input['share_server'] + + connection.allow_access(None, share, rw_access, share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_allow_ro_nfs_share_access(self, connection, mocked_input): + share = mocked_input['nfs_share'] + ro_access = mocked_input['nfs_ro_access'] + share_server = mocked_input['share_server'] + + connection.allow_access(None, share, ro_access, share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_deny_cifs_share_access(self, connection, mocked_input): + share = mocked_input['cifs_share'] + rw_access = mocked_input['cifs_rw_access'] + share_server = mocked_input['share_server'] + + connection.deny_access(None, share, rw_access, share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_deny_nfs_share_access(self, connection, mocked_input): + share = mocked_input['nfs_share'] + rw_access = mocked_input['nfs_rw_access'] + share_server = mocked_input['share_server'] + + connection.deny_access(None, share, rw_access, share_server) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_update_access_deny_nfs(self, connection, mocked_input): + share = mocked_input['nfs_share'] + rw_access = mocked_input['nfs_rw_access'] + + connection.update_access(None, share, None, None, [rw_access], None) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test__validate_cifs_share_access_type(self, connection, mocked_input): + share = mocked_input['cifs_share'] + rw_access = mocked_input['invalid_access'] + + self.assertRaises(exception.InvalidShareAccess, + connection._validate_share_access_type, + share, + rw_access) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test__validate_nfs_share_access_type(self, connection, mocked_input): + share = mocked_input['nfs_share'] + rw_access = mocked_input['invalid_access'] + + self.assertRaises(exception.InvalidShareAccess, + connection._validate_share_access_type, + share, + rw_access) + + @res_mock.patch_connection + def test_get_network_allocations_number(self, connection): + self.assertEqual(2, connection.get_network_allocations_number()) + + @res_mock.patch_connection + def test_get_proto_enum(self, connection): + self.assertIn('FSSupportedProtocolEnum.CIFS', + six.text_type(connection._get_proto_enum('CIFS'))) + self.assertIn('FSSupportedProtocolEnum.NFS', + six.text_type(connection._get_proto_enum('nfs'))) + + @res_mock.mock_manila_input + @res_mock.patch_connection + def test_allow_access_error_access_level(self, connection, mocked_input): + share = mocked_input['nfs_share'] + rw_access = mocked_input['invalid_access'] + + self.assertRaises(exception.InvalidShareAccessLevel, + connection.allow_access, + None, share, rw_access) diff --git a/manila/tests/share/drivers/emc/plugins/unity/test_utils.py b/manila/tests/share/drivers/emc/plugins/unity/test_utils.py new file mode 100644 index 0000000000..3d97024bde --- /dev/null +++ b/manila/tests/share/drivers/emc/plugins/unity/test_utils.py @@ -0,0 +1,50 @@ +# Copyright (c) 2016 EMC Corporation. +# 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 ddt + +from manila.share.drivers.emc.plugins.unity import utils +from manila import test + + +@ddt.ddt +class TestUtils(test.TestCase): + @ddt.data({'matcher': None, + 'matched': {'pool_1', 'pool_2', 'nas_server_pool'}, + 'not_matched': set()}, + {'matcher': ['*'], + 'matched': {'pool_1', 'pool_2', 'nas_server_pool'}, + 'not_matched': set()}, + {'matcher': ['pool_*'], + 'matched': {'pool_1', 'pool_2'}, + 'not_matched': {'nas_server_pool'}}, + {'matcher': ['*pool'], + 'matched': {'nas_server_pool'}, + 'not_matched': {'pool_1', 'pool_2'}}, + {'matcher': ['nas_server_pool'], + 'matched': {'nas_server_pool'}, + 'not_matched': {'pool_1', 'pool_2'}}, + {'matcher': ['nas_*', 'pool_*'], + 'matched': {'pool_1', 'pool_2', 'nas_server_pool'}, + 'not_matched': set()}) + def test_do_match(self, data): + full = ['pool_1 ', ' pool_2', ' nas_server_pool '] + matcher = data['matcher'] + expected_matched = data['matched'] + expected_not_matched = data['not_matched'] + + matched, not_matched = utils.do_match(full, matcher) + self.assertEqual(expected_matched, matched) + self.assertEqual(expected_not_matched, not_matched) diff --git a/manila/tests/share/drivers/emc/plugins/unity/utils.py b/manila/tests/share/drivers/emc/plugins/unity/utils.py new file mode 100644 index 0000000000..613a7cdbe7 --- /dev/null +++ b/manila/tests/share/drivers/emc/plugins/unity/utils.py @@ -0,0 +1,32 @@ +# Copyright (c) 2016 EMC Corporation. +# 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. + +from os import path +import yaml + +import mock +from oslo_log import log + +LOG = log.getLogger(__name__) + +patch_system = mock.patch('storops.UnitySystem') + + +def load_yaml(file_name): + yaml_file = '{}/{}'.format(path.dirname(path.abspath(__file__)), file_name) + with open(yaml_file) as f: + res = yaml.load(f) + LOG.debug('Loaded yaml mock objects from %s.', yaml_file) + return res diff --git a/manila/tests/share/drivers/emc/plugins/vnx/test_connection.py b/manila/tests/share/drivers/emc/plugins/vnx/test_connection.py index ce1173908a..39960284c7 100644 --- a/manila/tests/share/drivers/emc/plugins/vnx/test_connection.py +++ b/manila/tests/share/drivers/emc/plugins/vnx/test_connection.py @@ -84,16 +84,19 @@ class StorageConnectionTestCase(test.TestCase): @ddt.data({'pool_conf': None, 'real_pools': ['fake_pool', 'nas_pool'], 'matched_pool': set()}, - {'pool_conf': '*', + {'pool_conf': [], + 'real_pools': ['fake_pool', 'nas_pool'], + 'matched_pool': set()}, + {'pool_conf': ['*'], 'real_pools': ['fake_pool', 'nas_pool'], 'matched_pool': {'fake_pool', 'nas_pool'}}, - {'pool_conf': 'fake_*', + {'pool_conf': ['fake_*'], 'real_pools': ['fake_pool', 'nas_pool', 'Perf_Pool'], 'matched_pool': {'fake_pool'}}, - {'pool_conf': '*pool', + {'pool_conf': ['*pool'], 'real_pools': ['fake_pool', 'NAS_Pool', 'Perf_POOL'], 'matched_pool': {'fake_pool'}}, - {'pool_conf': 'nas_pool', + {'pool_conf': ['nas_pool'], 'real_pools': ['fake_pool', 'nas_pool', 'perf_pool'], 'matched_pool': {'nas_pool'}}) @ddt.unpack @@ -120,11 +123,11 @@ class StorageConnectionTestCase(test.TestCase): xml_req_mock.assert_has_calls(expected_calls) @ddt.data( - {'pool_conf': 'fake_*', + {'pool_conf': ['fake_*'], 'real_pools': ['nas_pool', 'Perf_Pool']}, - {'pool_conf': '*pool', + {'pool_conf': ['*pool'], 'real_pools': ['NAS_Pool', 'Perf_POOL']}, - {'pool_conf': 'nas_pool', + {'pool_conf': ['nas_pool'], 'real_pools': ['fake_pool', 'perf_pool']}, ) @ddt.unpack diff --git a/releasenotes/notes/emc-unity-manila-support-d4f5a410501cfdae.yaml b/releasenotes/notes/emc-unity-manila-support-d4f5a410501cfdae.yaml new file mode 100644 index 0000000000..e2c47bfc6f --- /dev/null +++ b/releasenotes/notes/emc-unity-manila-support-d4f5a410501cfdae.yaml @@ -0,0 +1,11 @@ +--- +prelude: > + Add a new EMC Unity plugin in manila which allows user to create NFS/CIFS + share with a EMC Unity backend. +features: + - | + Add a new Unity plugin in manila which allows user to create NFS/CIFS + share with a EMC Unity backend. This plugin performs the operations on + Unity by REST API. +issues: + - EMC Unity does not support the same IP in different VLANs. diff --git a/setup.cfg b/setup.cfg index 95ab90381b..30adb6edd6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -60,6 +60,7 @@ oslo.config.opts.defaults = manila = manila.common.config:set_middleware_defaults manila.share.drivers.emc.plugins = vnx = manila.share.drivers.emc.plugins.vnx.connection:VNXStorageConnection + unity = manila.share.drivers.emc.plugins.unity.connection:UnityStorageConnection isilon = manila.share.drivers.emc.plugins.isilon.isilon:IsilonStorageConnection manila.tests.scheduler.fakes = FakeWeigher1 = manila.tests.scheduler.fakes:FakeWeigher1