From 1b0042f0529b032756f1fe26047bb9acbff8e4ae Mon Sep 17 00:00:00 2001 From: xing-yang Date: Mon, 14 Nov 2016 09:52:01 -0500 Subject: [PATCH] VMAX manila plugin - Support for VMAX in Manila VMAX plugin is the plugin which manages the VMAX to provide shared filesystems using the Dell EMC manila driver framework. DocImpact Change-Id: I0b69f013443217f2053bbbfdec36dff226664b34 Implements: blueprint vmax-manila-support --- ...hare_back_ends_feature_support_mapping.rst | 8 + manila/exception.py | 12 + manila/opts.py | 2 + manila/share/drivers/dell_emc/driver.py | 2 +- .../drivers/dell_emc/plugins/vmax/__init__.py | 0 .../dell_emc/plugins/vmax/connection.py | 880 +++++ .../dell_emc/plugins/vmax/connector.py | 167 + .../dell_emc/plugins/vmax/constants.py | 55 + .../dell_emc/plugins/vmax/object_manager.py | 2052 +++++++++++ .../drivers/dell_emc/plugins/vmax/utils.py | 83 + .../dell_emc/plugins/vmax/xml_api_parser.py | 317 ++ .../drivers/dell_emc/plugins/vmax/__init__.py | 0 .../drivers/dell_emc/plugins/vmax/fakes.py | 1563 ++++++++ .../dell_emc/plugins/vmax/test_connection.py | 1636 +++++++++ .../dell_emc/plugins/vmax/test_connector.py | 224 ++ .../plugins/vmax/test_object_manager.py | 3186 +++++++++++++++++ .../dell_emc/plugins/vmax/test_utils.py | 44 + .../drivers/dell_emc/plugins/vmax/utils.py | 167 + .../vmax-manila-support-7c655fc094c09367.yaml | 3 + setup.cfg | 1 + 20 files changed, 10401 insertions(+), 1 deletion(-) create mode 100644 manila/share/drivers/dell_emc/plugins/vmax/__init__.py create mode 100644 manila/share/drivers/dell_emc/plugins/vmax/connection.py create mode 100644 manila/share/drivers/dell_emc/plugins/vmax/connector.py create mode 100644 manila/share/drivers/dell_emc/plugins/vmax/constants.py create mode 100644 manila/share/drivers/dell_emc/plugins/vmax/object_manager.py create mode 100644 manila/share/drivers/dell_emc/plugins/vmax/utils.py create mode 100644 manila/share/drivers/dell_emc/plugins/vmax/xml_api_parser.py create mode 100644 manila/tests/share/drivers/dell_emc/plugins/vmax/__init__.py create mode 100644 manila/tests/share/drivers/dell_emc/plugins/vmax/fakes.py create mode 100644 manila/tests/share/drivers/dell_emc/plugins/vmax/test_connection.py create mode 100644 manila/tests/share/drivers/dell_emc/plugins/vmax/test_connector.py create mode 100644 manila/tests/share/drivers/dell_emc/plugins/vmax/test_object_manager.py create mode 100644 manila/tests/share/drivers/dell_emc/plugins/vmax/test_utils.py create mode 100644 manila/tests/share/drivers/dell_emc/plugins/vmax/utils.py create mode 100644 releasenotes/notes/vmax-manila-support-7c655fc094c09367.yaml 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 2963d49d39..95460163c7 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 +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ | NetApp Clustered Data ONTAP | J | L | L | L | J | J | N | \- | +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +| EMC VMAX | O | \- | O | \- | O | O | \- | \- | ++----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ | EMC VNX | J | \- | \- | \- | J | J | \- | \- | +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ | EMC Unity | N | \- | N | \- | N | N | \- | \- | @@ -99,6 +101,8 @@ Mapping of share drivers and share access rules support +----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+ | NetApp Clustered Data ONTAP | NFS (J) | CIFS (J) | \- | \- | NFS (K) | CIFS (M) | \- | \- | +----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+ +| EMC VMAX | NFS (O) | CIFS (O) | \- | \- | NFS (O) | CIFS (O) | \- | \- | ++----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+ | EMC VNX | NFS (J) | CIFS (J) | \- | \- | NFS (L) | CIFS (L) | \- | \- | +----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+ | EMC Unity | NFS (N) | CIFS (N) | \- | \- | NFS (N) | CIFS (N) | \- | \- | @@ -154,6 +158,8 @@ Mapping of share drivers and security services support +----------------------------------------+------------------+-----------------+------------------+ | NetApp Clustered Data ONTAP | J | J | J | +----------------------------------------+------------------+-----------------+------------------+ +| EMC VMAX | O | \- | \- | ++----------------------------------------+------------------+-----------------+------------------+ | EMC VNX | J | \- | \- | +----------------------------------------+------------------+-----------------+------------------+ | EMC Unity | N | \- | \- | @@ -212,6 +218,8 @@ More information: :ref:`capabilities_and_extra_specs` +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ | NetApp Clustered Data ONTAP | J | K | M | M | M | L | \- | J | \- | +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +| EMC VMAX | O | \- | \- | \- | \- | O | \- | O | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ | EMC VNX | J | \- | \- | \- | \- | L | \- | J | \- | +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ | EMC Unity | N | \- | \- | \- | N | \- | \- | N | \- | diff --git a/manila/exception.py b/manila/exception.py index f26c97f60d..7346e63f02 100644 --- a/manila/exception.py +++ b/manila/exception.py @@ -650,6 +650,18 @@ class VserverNotSpecified(NetAppException): message = _("Vserver not specified.") +class EMCVmaxXMLAPIError(Invalid): + message = _("%(err)s") + + +class EMCVmaxLockRequiredException(ManilaException): + message = _("Unable to acquire lock(s).") + + +class EMCVmaxInvalidMoverID(ManilaException): + message = _("Invalid mover or vdm %(id)s.") + + class EMCVnxXMLAPIError(Invalid): message = _("%(err)s") diff --git a/manila/opts.py b/manila/opts.py index 4366cf3549..a8ddd4400f 100644 --- a/manila/opts.py +++ b/manila/opts.py @@ -54,6 +54,7 @@ import manila.share.drivers.container.driver import manila.share.drivers.container.storage_helper import manila.share.drivers.dell_emc.driver import manila.share.drivers.dell_emc.plugins.isilon.isilon +import manila.share.drivers.dell_emc.plugins.vmax.connection import manila.share.drivers.generic import manila.share.drivers.glusterfs import manila.share.drivers.glusterfs.common @@ -123,6 +124,7 @@ _global_opt_lists = [ manila.share.drivers.container.driver.container_opts, manila.share.drivers.container.storage_helper.lv_opts, manila.share.drivers.dell_emc.driver.EMC_NAS_OPTS, + manila.share.drivers.dell_emc.plugins.vmax.connection.VMAX_OPTS, manila.share.drivers.generic.share_opts, manila.share.drivers.glusterfs.common.glusterfs_common_opts, manila.share.drivers.glusterfs.GlusterfsManilaShare_opts, diff --git a/manila/share/drivers/dell_emc/driver.py b/manila/share/drivers/dell_emc/driver.py index ea30f41ccd..f1c722944b 100644 --- a/manila/share/drivers/dell_emc/driver.py +++ b/manila/share/drivers/dell_emc/driver.py @@ -40,7 +40,7 @@ EMC_NAS_OPTS = [ help='Use secure connection to server.'), cfg.StrOpt('emc_share_backend', ignore_case=True, - choices=['isilon', 'vnx', 'unity'], + choices=['isilon', 'vnx', 'unity', 'vmax'], help='Share backend.'), cfg.StrOpt('emc_nas_root_dir', help='The root directory where shares will be located.') diff --git a/manila/share/drivers/dell_emc/plugins/vmax/__init__.py b/manila/share/drivers/dell_emc/plugins/vmax/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/manila/share/drivers/dell_emc/plugins/vmax/connection.py b/manila/share/drivers/dell_emc/plugins/vmax/connection.py new file mode 100644 index 0000000000..ae6f7d76d5 --- /dev/null +++ b/manila/share/drivers/dell_emc/plugins/vmax/connection.py @@ -0,0 +1,880 @@ +# Copyright (c) 2016 Dell Inc. or its subsidiaries. +# 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. +"""VMAX backend for the EMC Manila driver.""" + +import copy +import random + +from oslo_config import cfg +from oslo_log import log +from oslo_utils import excutils +from oslo_utils import units + +from manila.common import constants as const +from manila import exception +from manila.i18n import _, _LE, _LI, _LW +from manila.share.drivers.dell_emc.plugins import base as driver +from manila.share.drivers.dell_emc.plugins.vmax import ( + object_manager as manager) +from manila.share.drivers.dell_emc.plugins.vmax import constants +from manila.share.drivers.dell_emc.plugins.vmax import utils as vmax_utils +from manila.share import utils as share_utils +from manila import utils + +VERSION = "1.0.0" + +LOG = log.getLogger(__name__) + +VMAX_OPTS = [ + cfg.StrOpt('vmax_server_container', + help='Data mover to host the NAS server.'), + cfg.ListOpt('vmax_share_data_pools', + help='Comma separated list of pools that can be used to ' + 'persist share data.'), + cfg.ListOpt('vmax_ethernet_ports', + help='Comma separated list of ports that can be used for ' + 'share server interfaces. Members of the list ' + 'can be Unix-style glob expressions.') +] + +CONF = cfg.CONF +CONF.register_opts(VMAX_OPTS) + + +@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit, + debug_only=True) +class VMAXStorageConnection(driver.StorageConnection): + """Implements vmax specific functionality for EMC Manila driver.""" + + @vmax_utils.log_enter_exit + def __init__(self, *args, **kwargs): + super(VMAXStorageConnection, self).__init__(*args, **kwargs) + if 'configuration' in kwargs: + kwargs['configuration'].append_config_values(VMAX_OPTS) + + self.mover_name = None + self.pools = None + self.manager = None + self.pool_conf = None + self.reserved_percentage = None + self.driver_handles_share_servers = True + self.port_conf = None + + 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.Ki + + share_proto = share['share_proto'].upper() + + # Validate the share protocol + if share_proto not in ('NFS', 'CIFS'): + raise exception.InvalidShare( + reason=(_('Invalid NAS protocol supplied: %s.') + % share_proto)) + + # Get the pool name from share host field + pool_name = share_utils.extract_host(share['host'], level='pool') + if not pool_name: + message = (_("Pool is not available in the share host %s.") % + share['host']) + raise exception.InvalidHost(reason=message) + + # Validate share server + self._share_server_validation(share_server) + + if share_proto == 'CIFS': + vdm_name = self._get_share_server_name(share_server) + server_name = vdm_name + + # Check if CIFS server exists. + status, server = self._get_context('CIFSServer').get(server_name, + vdm_name) + if status != constants.STATUS_OK: + message = (_("CIFS server %s not found.") % server_name) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + self._allocate_container(share_name, size, share_server, pool_name) + + if share_proto == 'NFS': + location = self._create_nfs_share(share_name, share_server) + elif share_proto == 'CIFS': + location = self._create_cifs_share(share_name, share_server) + + return location + + def _share_server_validation(self, share_server): + """Validate the share server.""" + if not share_server: + msg = _('Share server not provided') + raise exception.InvalidInput(reason=msg) + + backend_details = share_server.get('backend_details') + vdm = backend_details.get( + 'share_server_name') if backend_details else None + + if vdm is None: + message = _("No share server found.") + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + def _allocate_container(self, share_name, size, share_server, pool_name): + """Allocate file system for share.""" + vdm_name = self._get_share_server_name(share_server) + + self._get_context('FileSystem').create( + share_name, size, pool_name, vdm_name) + + def _allocate_container_from_snapshot(self, share, snapshot, share_server, + pool_name): + """Allocate file system from snapshot.""" + vdm_name = self._get_share_server_name(share_server) + + interconn_id = self._get_context('Mover').get_interconnect_id( + self.mover_name, self.mover_name) + + self._get_context('FileSystem').create_from_snapshot( + share['id'], snapshot['id'], snapshot['share_id'], + pool_name, vdm_name, interconn_id) + + nwe_size = share['size'] * units.Ki + self._get_context('FileSystem').extend(share['id'], pool_name, + nwe_size) + + @vmax_utils.log_enter_exit + def _create_cifs_share(self, share_name, share_server): + """Create CIFS share.""" + vdm_name = self._get_share_server_name(share_server) + server_name = vdm_name + + # Get available CIFS Server and interface (one CIFS server per VDM) + status, server = self._get_context('CIFSServer').get(server_name, + vdm_name) + + if 'interfaces' not in server or len(server['interfaces']) == 0: + message = (_("CIFS server %s doesn't have interface, " + "so the share is inaccessible.") + % server['compName']) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + interface = server['interfaces'][0] + + self._get_context('CIFSShare').create(share_name, server['name'], + vdm_name) + + self._get_context('CIFSShare').disable_share_access(share_name, + vdm_name) + locations = [] + location = (r'\\%(interface)s\%(name)s' % + {'interface': interface, 'name': share_name}) + + locations.append(location) + + return locations + + @vmax_utils.log_enter_exit + def _create_nfs_share(self, share_name, share_server): + """Create NFS share.""" + vdm_name = self._get_share_server_name(share_server) + + self._get_context('NFSShare').create(share_name, vdm_name) + + return ('%(nfs_if)s:/%(share_name)s' + % {'nfs_if': share_server['backend_details']['nfs_if'], + 'share_name': share_name}) + + 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'] + + share_proto = share['share_proto'].upper() + + # Validate the share protocol + if share_proto not in ('NFS', 'CIFS'): + raise exception.InvalidShare( + reason=(_('Invalid NAS protocol supplied: %s.') + % share_proto)) + + # Get the pool name from share host field + pool_name = share_utils.extract_host(share['host'], level='pool') + if not pool_name: + message = (_("Pool is not available in the share host %s.") % + share['host']) + raise exception.InvalidHost(reason=message) + + self._share_server_validation(share_server) + + self._allocate_container_from_snapshot( + share, snapshot, share_server, pool_name) + + if share_proto == 'NFS': + self._create_nfs_share(share_name, share_server) + location = ('%(nfs_if)s:/%(share_name)s' + % {'nfs_if': share_server['backend_details']['nfs_if'], + 'share_name': share_name}) + elif share_proto == 'CIFS': + location = self._create_cifs_share(share_name, share_server) + + return location + + def create_snapshot(self, context, snapshot, share_server=None): + """Create snapshot from share.""" + share_name = snapshot['share_id'] + status, filesystem = self._get_context('FileSystem').get(share_name) + if status != constants.STATUS_OK: + message = (_("File System %s not found.") % share_name) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + pool_id = filesystem['pools_id'][0] + + self._get_context('Snapshot').create(snapshot['id'], + snapshot['share_id'], + pool_id) + + def delete_share(self, context, share, share_server=None): + """Delete a share.""" + if share_server is None: + LOG.warning(_LW("Share network should be specified for " + "share deletion.")) + return + + share_proto = share['share_proto'].upper() + + if share_proto == 'NFS': + self._delete_nfs_share(share, share_server) + elif share_proto == 'CIFS': + self._delete_cifs_share(share, share_server) + else: + raise exception.InvalidShare( + reason=_('Unsupported share protocol')) + + @vmax_utils.log_enter_exit + def _delete_cifs_share(self, share, share_server): + """Delete CIFS share.""" + vdm_name = self._get_share_server_name(share_server) + + name = share['id'] + + self._get_context('CIFSShare').delete(name, vdm_name) + + self._deallocate_container(name, vdm_name) + + @vmax_utils.log_enter_exit + def _delete_nfs_share(self, share, share_server): + """Delete NFS share.""" + vdm_name = self._get_share_server_name(share_server) + + name = share['id'] + + self._get_context('NFSShare').delete(name, vdm_name) + + self._deallocate_container(name, vdm_name) + + @vmax_utils.log_enter_exit + def _deallocate_container(self, share_name, vdm_name): + """Delete underneath objects of the share.""" + path = '/' + share_name + + try: + # Delete mount point + self._get_context('MountPoint').delete(path, vdm_name) + except exception.EMCVmaxXMLAPIError as e: + LOG.exception(_LE("CIFS server %(name)s on mover %(mover_name)s " + "not found due to error %(err)s. Skip the " + "deletion."), + {'name': path, 'mover_name': vdm_name, + 'err': e.message}) + + try: + # Delete file system + self._get_context('FileSystem').delete(share_name) + except exception.EMCVmaxXMLAPIError as e: + LOG.exception(_LE("File system %(share_name)s not found due to" + "error %(err)s. Skip the deletion."), + {'share_name': share_name, + 'err': e.message}) + + def delete_snapshot(self, context, snapshot, share_server=None): + """Delete a snapshot.""" + self._get_context('Snapshot').delete(snapshot['id']) + + def ensure_share(self, context, share, share_server=None): + """Ensure that the share is exported.""" + + def extend_share(self, share, new_size, share_server=None): + # Get the pool name from share host field + pool_name = share_utils.extract_host(share['host'], level='pool') + if not pool_name: + message = (_("Pool is not available in the share host %s.") % + share['host']) + raise exception.InvalidHost(reason=message) + + share_name = share['id'] + + self._get_context('FileSystem').extend( + share_name, pool_name, new_size * units.Ki) + + 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'] + + if share_proto == 'NFS': + self._nfs_allow_access(context, share, access, share_server) + elif share_proto == 'CIFS': + self._cifs_allow_access(context, share, access, share_server) + else: + raise exception.InvalidShare( + reason=(_('Invalid NAS protocol supplied: %s.') + % share_proto)) + + @vmax_utils.log_enter_exit + def _cifs_allow_access(self, context, share, access, share_server): + """Allow access to CIFS share.""" + vdm_name = self._get_share_server_name(share_server) + share_name = share['id'] + + if access['access_type'] != 'user': + reason = _('Only user access type allowed for CIFS share') + raise exception.InvalidShareAccess(reason=reason) + + user_name = access['access_to'] + + access_level = access['access_level'] + if access_level == const.ACCESS_LEVEL_RW: + cifs_access = constants.CIFS_ACL_FULLCONTROL + else: + cifs_access = constants.CIFS_ACL_READ + + # Check if CIFS server exists. + server_name = vdm_name + status, server = self._get_context('CIFSServer').get(server_name, + vdm_name) + if status != constants.STATUS_OK: + message = (_("CIFS server %s not found.") % server_name) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + self._get_context('CIFSShare').allow_share_access( + vdm_name, + share_name, + user_name, + server['domain'], + access=cifs_access) + + @vmax_utils.log_enter_exit + def _nfs_allow_access(self, context, share, access, share_server): + """Allow access to NFS share.""" + vdm_name = self._get_share_server_name(share_server) + + access_type = access['access_type'] + if access_type != 'ip': + reason = _('Only ip access type allowed.') + raise exception.InvalidShareAccess(reason=reason) + + host_ip = access['access_to'] + access_level = access['access_level'] + + self._get_context('NFSShare').allow_share_access( + share['id'], host_ip, vdm_name, access_level) + + def update_access(self, context, share, access_rules, add_rules, + delete_rules, share_server=None): + # deleting rules + for rule in delete_rules: + self.deny_access(context, share, rule, share_server) + + # adding rules + for rule in add_rules: + self.allow_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, share_server, white_list) + + def clear_access(self, share, share_server, white_list): + share_proto = share['share_proto'].upper() + share_name = share['id'] + if share_proto == 'CIFS': + self._cifs_clear_access(share_name, share_server, white_list) + elif share_proto == 'NFS': + self._nfs_clear_access(share_name, share_server, white_list) + + @vmax_utils.log_enter_exit + def _cifs_clear_access(self, share_name, share_server, white_list): + """Clear access for CIFS share except hosts in the white list.""" + vdm_name = self._get_share_server_name(share_server) + + # Check if CIFS server exists. + server_name = vdm_name + status, server = self._get_context('CIFSServer').get(server_name, + vdm_name) + if status != constants.STATUS_OK: + message = (_("CIFS server %(server_name)s has issue. " + "Detail: %(status)s") % + {'server_name': server_name, 'status': status}) + raise exception.EMCVmaxXMLAPIError(err=message) + + self._get_context('CIFSShare').clear_share_access( + share_name=share_name, + mover_name=vdm_name, + domain=server['domain'], + white_list_users=white_list) + + @vmax_utils.log_enter_exit + def _nfs_clear_access(self, share_name, share_server, white_list): + """Clear access for NFS share except hosts in the white list.""" + self._get_context('NFSShare').clear_share_access( + share_name=share_name, + mover_name=self._get_share_server_name(share_server), + white_list_hosts=white_list) + + def deny_access(self, context, share, access, share_server=None): + """Deny access to a share.""" + share_proto = share['share_proto'] + + if share_proto == 'NFS': + self._nfs_deny_access(share, access, share_server) + elif share_proto == 'CIFS': + self._cifs_deny_access(share, access, share_server) + else: + raise exception.InvalidShare( + reason=_('Unsupported share protocol')) + + @vmax_utils.log_enter_exit + def _cifs_deny_access(self, share, access, share_server): + """Deny access to CIFS share.""" + vdm_name = self._get_share_server_name(share_server) + share_name = share['id'] + + if access['access_type'] != 'user': + LOG.warning(_LW("Only user access type allowed for CIFS share.")) + return + + user_name = access['access_to'] + + access_level = access['access_level'] + if access_level == const.ACCESS_LEVEL_RW: + cifs_access = constants.CIFS_ACL_FULLCONTROL + else: + cifs_access = constants.CIFS_ACL_READ + + # Check if CIFS server exists. + server_name = vdm_name + status, server = self._get_context('CIFSServer').get(server_name, + vdm_name) + if status != constants.STATUS_OK: + message = (_("CIFS server %s not found.") % server_name) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + self._get_context('CIFSShare').deny_share_access( + vdm_name, + share_name, + user_name, + server['domain'], + access=cifs_access) + + @vmax_utils.log_enter_exit + def _nfs_deny_access(self, share, access, share_server): + """Deny access to NFS share.""" + vdm_name = self._get_share_server_name(share_server) + + access_type = access['access_type'] + if access_type != 'ip': + LOG.warning(_LW("Only ip access type allowed.")) + return + + host_ip = access['access_to'] + + self._get_context('NFSShare').deny_share_access(share['id'], host_ip, + vdm_name) + + def check_for_setup_error(self): + """Check for setup error.""" + # To verify the input from Manila configuration + status, out = self._get_context('Mover').get_ref(self.mover_name, + True) + if constants.STATUS_ERROR == status: + message = (_("Could not find Data Mover by name: %s.") % + self.mover_name) + LOG.error(message) + raise exception.InvalidParameterValue(err=message) + + self.pools = self._get_managed_storage_pools(self.pool_conf) + + def _get_managed_storage_pools(self, pools): + matched_pools = set() + if pools: + # Get the real pools from the backend storage + status, backend_pools = self._get_context('StoragePool').get_all() + if status != constants.STATUS_OK: + message = (_("Failed to get storage pool information. " + "Reason: %s") % backend_pools) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + real_pools = set([item for item in backend_pools]) + conf_pools = set([item.strip() for item in pools]) + matched_pools, unmatched_pools = vmax_utils.do_match_any( + real_pools, conf_pools) + + if not matched_pools: + msg = (_("None of the specified storage pools to be managed " + "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.InvalidParameterValue(err=msg) + + LOG.info(_LI("Storage pools: %s will be managed."), + ",".join(matched_pools)) + else: + LOG.debug("No storage pool is specified, so all pools " + "in storage system will be managed.") + return matched_pools + + def connect(self, emc_share_driver, context): + """Connect to VMAX NAS server.""" + config = emc_share_driver.configuration + config.append_config_values(VMAX_OPTS) + self.mover_name = config.vmax_server_container + + self.pool_conf = config.safe_get('vmax_share_data_pools') + + self.reserved_percentage = config.safe_get('reserved_share_percentage') + if self.reserved_percentage is None: + self.reserved_percentage = 0 + + self.manager = manager.StorageObjectManager(config) + self.port_conf = config.safe_get('emc_interface_ports') + + def get_managed_ports(self): + # Get the real ports(devices) list from the backend storage + real_ports = self._get_physical_devices(self.mover_name) + + if not self.port_conf: + LOG.debug("No ports are specified, so any of the ports on the " + "Data Mover can be used.") + return real_ports + + matched_ports, unmanaged_ports = vmax_utils.do_match_any( + real_ports, self.port_conf) + + if not matched_ports: + msg = (_("None of the specified network ports exist. " + "Please check your configuration emc_interface_ports " + "in manila.conf. The available ports on the Data Mover " + "are %s.") % + ",".join(real_ports)) + raise exception.BadConfigurationException(reason=msg) + + LOG.debug("Ports: %s can be used.", ",".join(matched_ports)) + + return list(matched_ports) + + def update_share_stats(self, stats_dict): + """Communicate with EMCNASClient to get the stats.""" + stats_dict['driver_version'] = VERSION + + self._get_context('Mover').get_ref(self.mover_name, True) + + stats_dict['pools'] = [] + + status, pools = self._get_context('StoragePool').get_all() + for name, pool in pools.items(): + if not self.pools or pool['name'] in self.pools: + total_size = float(pool['total_size']) + used_size = float(pool['used_size']) + + pool_stat = { + 'pool_name': pool['name'], + 'total_capacity_gb': total_size, + 'free_capacity_gb': total_size - used_size, + 'qos': False, + 'reserved_percentage': self.reserved_percentage, + 'snapshot_support': True, + 'create_share_from_snapshot_support': True, + 'revert_to_snapshot_support': False, + } + stats_dict['pools'].append(pool_stat) + + if not stats_dict['pools']: + message = _("Failed to update storage pool.") + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + def get_pool(self, share): + """Get the pool name of the share.""" + share_name = share['id'] + status, filesystem = self._get_context('FileSystem').get(share_name) + if status != constants.STATUS_OK: + message = (_("File System %(name)s not found. " + "Reason: %(err)s") % + {'name': share_name, 'err': filesystem}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + pool_id = filesystem['pools_id'][0] + + # Get the real pools from the backend storage + status, backend_pools = self._get_context('StoragePool').get_all() + if status != constants.STATUS_OK: + message = (_("Failed to get storage pool information. " + "Reason: %s") % backend_pools) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + for name, pool_info in backend_pools.items(): + if pool_info['id'] == pool_id: + return name + + available_pools = [item for item in backend_pools] + message = (_("No matched pool name for share: %(share)s. " + "Available pools: %(pools)s") % + {'share': share_name, 'pools': available_pools}) + raise exception.EMCVmaxXMLAPIError(err=message) + + def get_network_allocations_number(self): + """Returns number of network allocations for creating VIFs.""" + return constants.IP_ALLOCATIONS + + def setup_server(self, network_info, metadata=None): + """Set up and configure share server. + + Sets up and configures share server with given network parameters. + """ + # Only support single security service with type 'active_directory' + vdm_name = network_info['server_id'] + vlan_id = network_info['segmentation_id'] + active_directory = None + allocated_interfaces = [] + + if network_info.get('security_services'): + is_valid, active_directory = self._get_valid_security_service( + network_info['security_services']) + + if not is_valid: + raise exception.EMCVmaxXMLAPIError(err=active_directory) + + try: + if not self._vdm_exist(vdm_name): + LOG.debug('Share server %s not found, creating ' + 'share server...', vdm_name) + self._get_context('VDM').create(vdm_name, self.mover_name) + + netmask = utils.cidr_to_netmask(network_info['cidr']) + + devices = self.get_managed_ports() + + for net_info in network_info['network_allocations']: + random.shuffle(devices) + interface = { + 'name': net_info['id'][-12:], + 'device_name': devices[0], + 'ip': net_info['ip_address'], + 'mover_name': self.mover_name, + 'net_mask': netmask, + 'vlan_id': vlan_id if vlan_id else -1, + } + + self._get_context('MoverInterface').create(interface) + + allocated_interfaces.append(interface) + + cifs_interface = allocated_interfaces[0] + nfs_interface = allocated_interfaces[1] + if active_directory: + self._configure_active_directory( + active_directory, vdm_name, cifs_interface) + + self._get_context('VDM').attach_nfs_interface( + vdm_name, nfs_interface['name']) + + return { + 'share_server_name': vdm_name, + 'cifs_if': cifs_interface['ip'], + 'nfs_if': nfs_interface['ip'], + } + + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE('Could not setup server')) + server_details = self._construct_backend_details( + vdm_name, allocated_interfaces) + self.teardown_server( + server_details, network_info['security_services']) + + def _construct_backend_details(self, vdm_name, interfaces): + if_number = len(interfaces) + cifs_if = interfaces[0]['ip'] if if_number > 0 else None + nfs_if = interfaces[1]['ip'] if if_number > 1 else None + + return { + 'share_server_name': vdm_name, + 'cifs_if': cifs_if, + 'nfs_if': nfs_if, + } + + @vmax_utils.log_enter_exit + def _vdm_exist(self, name): + status, out = self._get_context('VDM').get(name) + if constants.STATUS_OK != status: + return False + + return True + + def _get_physical_devices(self, mover_name): + """Get a proper network device to create interface.""" + devices = self._get_context('Mover').get_physical_devices(mover_name) + if not devices: + message = (_("Could not get physical device port on mover %s.") % + self.mover_name) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + return devices + + def _configure_active_directory( + self, security_service, vdm_name, interface): + + domain = security_service['domain'] + server = security_service['dns_ip'] + + self._get_context('DNSDomain').create(self.mover_name, domain, server) + + cifs_server_args = { + 'name': vdm_name, + 'interface_ip': interface['ip'], + 'domain_name': security_service['domain'], + 'user_name': security_service['user'], + 'password': security_service['password'], + 'mover_name': vdm_name, + 'is_vdm': True, + } + + self._get_context('CIFSServer').create(cifs_server_args) + + def teardown_server(self, server_details, security_services=None): + """Teardown share server.""" + if not server_details: + LOG.debug('Server details are empty.') + return + + vdm_name = server_details.get('share_server_name') + if not vdm_name: + LOG.debug('No share server found in server details.') + return + + cifs_if = server_details.get('cifs_if') + nfs_if = server_details.get('nfs_if') + + status, vdm = self._get_context('VDM').get(vdm_name) + if constants.STATUS_OK != status: + LOG.debug('Share server %s not found.', vdm_name) + return + + interfaces = self._get_context('VDM').get_interfaces(vdm_name) + + for if_name in interfaces['nfs']: + self._get_context('VDM').detach_nfs_interface(vdm_name, if_name) + + if security_services: + # Only support single security service with type 'active_directory' + is_valid, active_directory = self._get_valid_security_service( + security_services) + if is_valid: + status, servers = self._get_context('CIFSServer').get_all( + vdm_name) + if constants.STATUS_OK != status: + LOG.error(_LE('Could not find CIFS server by name: %s.'), + vdm_name) + else: + cifs_servers = copy.deepcopy(servers) + for name, server in cifs_servers.items(): + # Unjoin CIFS Server from domain + cifs_server_args = { + 'name': server['name'], + 'join_domain': False, + 'user_name': active_directory['user'], + 'password': active_directory['password'], + 'mover_name': vdm_name, + 'is_vdm': True, + } + + try: + self._get_context('CIFSServer').modify( + cifs_server_args) + except exception.EMCVmaxXMLAPIError as expt: + LOG.debug("Failed to modify CIFS server " + "%(server)s. Reason: %(err)s.", + {'server': server, 'err': expt}) + + self._get_context('CIFSServer').delete(name, vdm_name) + + # Delete interface from Data Mover + if cifs_if: + self._get_context('MoverInterface').delete(cifs_if, + self.mover_name) + + if nfs_if: + self._get_context('MoverInterface').delete(nfs_if, + self.mover_name) + + # Delete Virtual Data Mover + self._get_context('VDM').delete(vdm_name) + + def _get_valid_security_service(self, security_services): + """Validate security services and return a supported security service. + + :param security_services: + :returns: (, ) -- is true to indicate + security_services includes zero or single security service for + active directory. Otherwise, it would return false. return + error message when is false. Otherwise, it will + return zero or single security service for active directory. + """ + + # Only support single security service with type 'active_directory' + if (len(security_services) > 1 or + (security_services and + security_services[0]['type'] != 'active_directory')): + return False, _("Unsupported security services. " + "Only support single security service and " + "only support type 'active_directory'") + + return True, security_services[0] + + def _get_share_server_name(self, share_server): + try: + return share_server['backend_details']['share_server_name'] + except Exception: + LOG.debug("Didn't get share server name from share_server %s.", + share_server) + return share_server['id'] + + def _get_context(self, context_type): + return self.manager.getStorageContext(context_type) diff --git a/manila/share/drivers/dell_emc/plugins/vmax/connector.py b/manila/share/drivers/dell_emc/plugins/vmax/connector.py new file mode 100644 index 0000000000..d32b970e44 --- /dev/null +++ b/manila/share/drivers/dell_emc/plugins/vmax/connector.py @@ -0,0 +1,167 @@ +# Copyright (c) 2016 Dell Inc. or its subsidiaries. +# 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 pipes + +from oslo_concurrency import processutils +from oslo_log import log +from oslo_utils import excutils +import six +from six.moves import http_cookiejar +from six.moves.urllib import error as url_error # pylint: disable=E0611 +from six.moves.urllib import request as url_request # pylint: disable=E0611 + +from manila import exception +from manila.i18n import _ +from manila.i18n import _LE +from manila.share.drivers.dell_emc.plugins.vmax import constants +from manila import utils + +LOG = log.getLogger(__name__) + + +class XMLAPIConnector(object): + def __init__(self, configuration, debug=True): + super(XMLAPIConnector, self).__init__() + self.storage_ip = configuration.emc_nas_server + self.username = configuration.emc_nas_login + self.password = configuration.emc_nas_password + self.debug = debug + self.auth_url = 'https://' + self.storage_ip + '/Login' + self._url = ('https://' + self.storage_ip + + '/servlets/CelerraManagementServices') + https_handler = url_request.HTTPSHandler() + cookie_handler = url_request.HTTPCookieProcessor( + http_cookiejar.CookieJar()) + self.url_opener = url_request.build_opener(https_handler, + cookie_handler) + self._do_setup() + + def _do_setup(self): + credential = ('user=' + self.username + + '&password=' + self.password + + '&Login=Login') + req = url_request.Request(self.auth_url, credential, + constants.CONTENT_TYPE_URLENCODE) + resp = self.url_opener.open(req) + resp_body = resp.read() + self._http_log_resp(resp, resp_body) + + def _http_log_req(self, req): + if not self.debug: + return + + string_parts = ['curl -i'] + string_parts.append(' -X %s' % req.get_method()) + + for k in req.headers: + header = ' -H "%s: %s"' % (k, req.headers[k]) + string_parts.append(header) + + if req.data: + string_parts.append(" -d '%s'" % req.data) + string_parts.append(' ' + req.get_full_url()) + LOG.debug("\nREQ: %s.\n", "".join(string_parts)) + + def _http_log_resp(self, resp, body): + if not self.debug: + return + + headers = six.text_type(resp.headers).replace('\n', '\\n') + + LOG.debug( + 'RESP: [%(code)s] %(resp_hdrs)s\n' + 'RESP BODY: %(resp_b)s.\n', + { + 'code': resp.getcode(), + 'resp_hdrs': headers, + 'resp_b': body, + } + ) + + def _request(self, req_body=None, method=None, + header=constants.CONTENT_TYPE_URLENCODE): + req = url_request.Request(self._url, req_body, header) + if method not in (None, 'GET', 'POST'): + req.get_method = lambda: method + self._http_log_req(req) + try: + resp = self.url_opener.open(req) + resp_body = resp.read() + self._http_log_resp(resp, resp_body) + except url_error.HTTPError as http_err: + if '403' == six.text_type(http_err.code): + raise exception.NotAuthorized() + else: + err = {'errorCode': -1, + 'httpStatusCode': http_err.code, + 'messages': six.text_type(http_err), + 'request': req_body} + msg = (_("The request is invalid. Reason: %(reason)s") % + {'reason': err}) + raise exception.ManilaException(message=msg) + + return resp_body + + def request(self, req_body=None, method=None, + header=constants.CONTENT_TYPE_URLENCODE): + try: + resp_body = self._request(req_body, method, header) + except exception.NotAuthorized: + LOG.debug("Login again because client certification " + "may be expired.") + self._do_setup() + resp_body = self._request(req_body, method, header) + + return resp_body + + +class SSHConnector(object): + def __init__(self, configuration, debug=True): + super(SSHConnector, self).__init__() + self.storage_ip = configuration.emc_nas_server + self.username = configuration.emc_nas_login + self.password = configuration.emc_nas_password + self.debug = debug + + self.sshpool = utils.SSHPool(ip=self.storage_ip, + port=22, + conn_timeout=None, + login=self.username, + password=self.password) + + def run_ssh(self, cmd_list, check_exit_code=False): + command = ' '.join(pipes.quote(cmd_arg) for cmd_arg in cmd_list) + + with self.sshpool.item() as ssh: + try: + out, err = processutils.ssh_execute( + ssh, command, check_exit_code=check_exit_code) + self.log_request(command, out, err) + + return out, err + except processutils.ProcessExecutionError as e: + with excutils.save_and_reraise_exception(): + LOG.error(_LE('Error running SSH command: %(cmd)s. ' + 'Error: %(excmsg)s.'), + {'cmd': command, 'excmsg': e}) + + def log_request(self, cmd, out, err): + if not self.debug: + return + + LOG.debug("\nSSH command: %s.\n", cmd) + LOG.debug("SSH command output: out=%(out)s, err=%(err)s.\n", + {'out': out, 'err': err}) diff --git a/manila/share/drivers/dell_emc/plugins/vmax/constants.py b/manila/share/drivers/dell_emc/plugins/vmax/constants.py new file mode 100644 index 0000000000..78dbf23b4b --- /dev/null +++ b/manila/share/drivers/dell_emc/plugins/vmax/constants.py @@ -0,0 +1,55 @@ +# Copyright (c) 2016 Dell Inc. or its subsidiaries. +# 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. + +STATUS_OK = 'ok' +STATUS_INFO = 'info' +STATUS_DEBUG = 'debug' +STATUS_WARNING = 'warning' +STATUS_ERROR = 'error' +STATUS_NOT_FOUND = 'not_found' + +MSG_GENERAL_ERROR = '13690601492' +MSG_INVALID_VDM_ID = '14227341325' +MSG_INVALID_MOVER_ID = '14227341323' + +MSG_FILESYSTEM_NOT_FOUND = "18522112101" +MSG_FILESYSTEM_EXIST = '13691191325' + +MSG_VDM_EXIST = '13421840550' + +MSG_SNAP_EXIST = '13690535947' + +MSG_INTERFACE_NAME_EXIST = '13421840550' +MSG_INTERFACE_EXIST = '13691781136' +MSG_INTERFACE_INVALID_VLAN_ID = '13421850371' +MSG_INTERFACE_NON_EXISTENT = '13691781134' + +MSG_JOIN_DOMAIN = '13157007726' +MSG_UNJOIN_DOMAIN = '13157007723' + +# Necessary to retry when VMAX database is locked for provisioning operation +MSG_CODE_RETRY = '13421840537' + +IP_ALLOCATIONS = 2 + +CONTENT_TYPE_URLENCODE = {'Content-Type': 'application/x-www-form-urlencoded'} + +XML_HEADER = '' +XML_NAMESPACE = 'http://www.emc.com/schemas/celerra/xml_api' + +CIFS_ACL_FULLCONTROL = 'fullcontrol' +CIFS_ACL_READ = 'read' + +SSH_DEFAULT_RETRY_PATTERN = r'Error 2201:.*: unable to acquire lock\(s\)' diff --git a/manila/share/drivers/dell_emc/plugins/vmax/object_manager.py b/manila/share/drivers/dell_emc/plugins/vmax/object_manager.py new file mode 100644 index 0000000000..73260c1285 --- /dev/null +++ b/manila/share/drivers/dell_emc/plugins/vmax/object_manager.py @@ -0,0 +1,2052 @@ +# Copyright (c) 2016 Dell Inc. or its subsidiaries. +# 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 re + +from lxml import builder +from lxml import etree as ET +from oslo_concurrency import processutils +from oslo_log import log +import six + +from manila.common import constants as const +from manila import exception +from manila.i18n import _, _LI, _LW, _LE +from manila.share.drivers.dell_emc.plugins.vmax import connector +from manila.share.drivers.dell_emc.plugins.vmax import constants +from manila.share.drivers.dell_emc.plugins.vmax import utils as vmax_utils +from manila.share.drivers.dell_emc.plugins.vmax import xml_api_parser as parser +from manila import utils + +LOG = log.getLogger(__name__) + + +@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit, + debug_only=True) +class StorageObjectManager(object): + def __init__(self, configuration): + self.context = {} + + self.connectors = {} + self.connectors['XML'] = connector.XMLAPIConnector(configuration) + self.connectors['SSH'] = connector.SSHConnector(configuration) + + elt_maker = builder.ElementMaker(nsmap={None: constants.XML_NAMESPACE}) + xml_parser = parser.XMLAPIParser() + + obj_types = StorageObject.__subclasses__() # pylint: disable=E1101 + for item in obj_types: + key = item.__name__ + self.context[key] = eval(key)(self.connectors, + elt_maker, + xml_parser, + self) + + def getStorageContext(self, type): + if type in self.context: + return self.context[type] + else: + message = (_("Invalid storage object type %s.") % type) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + +class StorageObject(object): + def __init__(self, conn, elt_maker, xml_parser, manager): + self.conn = conn + self.elt_maker = elt_maker + self.xml_parser = xml_parser + self.manager = manager + self.xml_retry = False + self.ssh_retry_patterns = [ + ( + constants.SSH_DEFAULT_RETRY_PATTERN, + exception.EMCVmaxLockRequiredException() + ), + ] + + def _translate_response(self, response): + """Translate different status to ok/error status.""" + if (constants.STATUS_OK == response['maxSeverity'] or + constants.STATUS_ERROR == response['maxSeverity']): + return + + old_Severity = response['maxSeverity'] + if response['maxSeverity'] in (constants.STATUS_DEBUG, + constants.STATUS_INFO): + response['maxSeverity'] = constants.STATUS_OK + + LOG.warning(_LW("Translated status from %(old)s to %(new)s. " + "Message: %(info)s."), + {'old': old_Severity, + 'new': response['maxSeverity'], + 'info': response}) + + def _response_validation(self, response, error_code): + """Validates whether a response includes a certain error code.""" + msg_codes = self._get_problem_message_codes(response['problems']) + + for code in msg_codes: + if code == error_code: + return True + + return False + + def _get_problem_message_codes(self, problems): + message_codes = [] + for problem in problems: + if 'messageCode' in problem: + message_codes.append(problem['messageCode']) + + return message_codes + + def _get_problem_messages(self, problems): + messages = [] + for problem in problems: + if 'message' in problem: + messages.append(problem['message']) + + return messages + + def _get_problem_diags(self, problems): + diags = [] + + for problem in problems: + if 'Diagnostics' in problem: + diags.append(problem['Diagnostics']) + + return diags + + def _build_query_package(self, body): + return self.elt_maker.RequestPacket( + self.elt_maker.Request( + self.elt_maker.Query(body) + ) + ) + + def _build_task_package(self, body): + return self.elt_maker.RequestPacket( + self.elt_maker.Request( + self.elt_maker.StartTask(body, timeout='300') + ) + ) + + @utils.retry(exception.EMCVmaxLockRequiredException) + def _send_request(self, req): + req_xml = constants.XML_HEADER + ET.tostring(req).decode('utf-8') + + rsp_xml = self.conn['XML'].request(str(req_xml)) + + response = self.xml_parser.parse(rsp_xml) + + self._translate_response(response) + + if (response['maxSeverity'] != constants.STATUS_OK and + self._response_validation(response, + constants.MSG_CODE_RETRY)): + raise exception.EMCVmaxLockRequiredException + + return response + + @utils.retry(exception.EMCVmaxLockRequiredException) + def _execute_cmd(self, cmd, retry_patterns=None, check_exit_code=False): + """Execute NAS command via SSH. + + :param retry_patterns: list of tuples,where each tuple contains a reg + expression and a exception. + :param check_exit_code: Boolean. Raise + processutils.ProcessExecutionError if the command failed to + execute and this parameter is set to True. + """ + if retry_patterns is None: + retry_patterns = self.ssh_retry_patterns + + try: + out, err = self.conn['SSH'].run_ssh(cmd, check_exit_code) + except processutils.ProcessExecutionError as e: + for pattern in retry_patterns: + if re.search(pattern[0], e.stdout): + raise pattern[1] + + raise + + return out, err + + def _copy_properties(self, source, target, property_map, deep_copy=True): + for prop in property_map: + if isinstance(prop, tuple): + target_key, src_key = prop + else: + target_key = src_key = prop + + if src_key in source: + if deep_copy and isinstance(source[src_key], list): + target[target_key] = copy.deepcopy(source[src_key]) + else: + target[target_key] = source[src_key] + else: + target[target_key] = None + + def _get_mover_id(self, mover_name, is_vdm): + if is_vdm: + return self.get_context('VDM').get_id(mover_name) + else: + return self.get_context('Mover').get_id(mover_name, + self.xml_retry) + + def get_context(self, type): + return self.manager.getStorageContext(type) + + +@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit, + debug_only=True) +class FileSystem(StorageObject): + def __init__(self, conn, elt_maker, xml_parser, manager): + super(FileSystem, self).__init__(conn, elt_maker, xml_parser, manager) + self.filesystem_map = {} + + @utils.retry(exception.EMCVmaxInvalidMoverID) + def create(self, name, size, pool_name, mover_name, is_vdm=True): + pool_id = self.get_context('StoragePool').get_id(pool_name) + + mover_id = self._get_mover_id(mover_name, is_vdm) + if is_vdm: + mover = self.elt_maker.Vdm(vdm=mover_id) + else: + mover = self.elt_maker.Mover(mover=mover_id) + + if self.xml_retry: + self.xml_retry = False + + request = self._build_task_package( + self.elt_maker.NewFileSystem( + mover, + self.elt_maker.StoragePool( + pool=pool_id, + size=six.text_type(size), + mayContainSlices='true' + ), + name=name + ) + ) + + response = self._send_request(request) + + if (self._response_validation(response, + constants.MSG_INVALID_MOVER_ID) and + not self.xml_retry): + self.xml_retry = True + raise exception.EMCVmaxInvalidMoverID(id=mover_id) + elif self._response_validation( + response, constants.MSG_FILESYSTEM_EXIST): + LOG.warning(_LW("File system %s already exists. " + "Skip the creation."), name) + return + elif constants.STATUS_OK != response['maxSeverity']: + message = (_("Failed to create file system %(name)s. " + "Reason: %(err)s.") % + {'name': name, 'err': response['problems']}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + def get(self, name): + if name not in self.filesystem_map: + request = self._build_query_package( + self.elt_maker.FileSystemQueryParams( + self.elt_maker.AspectSelection( + fileSystems='true', + fileSystemCapacityInfos='true' + ), + self.elt_maker.Alias(name=name) + ) + ) + + response = self._send_request(request) + + if constants.STATUS_OK != response['maxSeverity']: + if self._is_filesystem_nonexistent(response): + return constants.STATUS_NOT_FOUND, response['problems'] + else: + return response['maxSeverity'], response['problems'] + + if not response['objects']: + return constants.STATUS_NOT_FOUND, response['problems'] + + src = response['objects'][0] + filesystem = {} + property_map = ( + 'name', + ('pools_id', 'storagePools'), + ('volume_id', 'volume'), + ('size', 'volumeSize'), + ('id', 'fileSystem'), + 'type', + 'dataServicePolicies', + ) + + self._copy_properties(src, filesystem, property_map) + + self.filesystem_map[name] = filesystem + + return constants.STATUS_OK, self.filesystem_map[name] + + def delete(self, name): + status, out = self.get(name) + if constants.STATUS_NOT_FOUND == status: + LOG.warning(_LW("File system %s not found. Skip the deletion."), + name) + return + elif constants.STATUS_OK != status: + message = (_("Failed to get file system by name %(name)s. " + "Reason: %(err)s.") % + {'name': name, 'err': out}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + enas_id = self.filesystem_map[name]['id'] + + request = self._build_task_package( + self.elt_maker.DeleteFileSystem(fileSystem=enas_id) + ) + + response = self._send_request(request) + + if constants.STATUS_OK != response['maxSeverity']: + message = (_("Failed to delete file system %(name)s. " + "Reason: %(err)s.") % + {'name': name, 'err': response['problems']}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + self.filesystem_map.pop(name) + + def extend(self, name, pool_name, new_size): + status, out = self.get(name) + if constants.STATUS_OK != status: + message = (_("Failed to get file system by name %(name)s. " + "Reason: %(err)s.") % + {'name': name, 'err': out}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + enas_id = out['id'] + size = int(out['size']) + if new_size < size: + message = (_("Failed to extend file system %(name)s because new " + "size %(new_size)d is smaller than old size " + "%(size)d.") % + {'name': name, 'new_size': new_size, 'size': size}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + elif new_size == size: + return + + pool_id = self.get_context('StoragePool').get_id(pool_name) + + request = self._build_task_package( + self.elt_maker.ExtendFileSystem( + self.elt_maker.StoragePool( + pool=pool_id, + size=six.text_type(new_size - size) + ), + fileSystem=enas_id, + ) + ) + + response = self._send_request(request) + + if constants.STATUS_OK != response['maxSeverity']: + message = (_("Failed to extend file system %(name)s to new size " + "%(new_size)d. Reason: %(err)s.") % + {'name': name, + 'new_size': new_size, + 'err': response['problems']}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + def get_id(self, name): + status, out = self.get(name) + if constants.STATUS_OK != status: + message = (_("Failed to get file system by name %(name)s. " + "Reason: %(err)s.") % + {'name': name, 'err': out}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + return self.filesystem_map[name]['id'] + + def _is_filesystem_nonexistent(self, response): + """Translate different status to ok/error status.""" + msg_codes = self._get_problem_message_codes(response['problems']) + diags = self._get_problem_diags(response['problems']) + + for code, diagnose in zip(msg_codes, diags): + if (code == constants.MSG_FILESYSTEM_NOT_FOUND and + diagnose.find('File system not found.') != -1): + return True + + return False + + def create_from_snapshot(self, name, snap_name, source_fs_name, pool_name, + mover_name, connect_id): + create_fs_cmd = [ + 'env', 'NAS_DB=/nas', '/nas/bin/nas_fs', + '-name', name, + '-type', 'uxfs', + '-create', + 'samesize=' + source_fs_name, + 'pool=%s' % pool_name, + 'storage=SINGLE', + 'worm=off', + '-thin', 'no', + '-option', 'slice=y', + ] + + self._execute_cmd(create_fs_cmd) + + ro_mount_cmd = [ + 'env', 'NAS_DB=/nas', '/nas/bin/server_mount', mover_name, + '-option', 'ro', + name, + '/%s' % name, + ] + self._execute_cmd(ro_mount_cmd) + + session_name = name + ':' + snap_name + copy_ckpt_cmd = [ + 'env', 'NAS_DB=/nas', '/nas/bin/nas_copy', + '-name', session_name[0:63], + '-source', '-ckpt', snap_name, + '-destination', '-fs', name, + '-interconnect', + 'id=%s' % connect_id, + '-overwrite_destination', + '-full_copy', + ] + + try: + self._execute_cmd(copy_ckpt_cmd, check_exit_code=True) + except processutils.ProcessExecutionError as expt: + LOG.error(_LE("Failed to copy content from snapshot %(snap)s to " + "file system %(filesystem)s. Reason: %(err)s."), + {'snap': snap_name, + 'filesystem': name, + 'err': expt}) + + # When an error happens during nas_copy, we need to continue + # deleting the checkpoint of the target file system if it exists. + query_fs_cmd = [ + 'env', 'NAS_DB=/nas', '/nas/bin/nas_fs', + '-info', name, + ] + out, err = self._execute_cmd(query_fs_cmd) + re_ckpts = r'ckpts\s*=\s*(.*)\s*' + m = re.search(re_ckpts, out) + if m is not None: + ckpts = m.group(1) + for ckpt in re.split(',', ckpts): + umount_ckpt_cmd = [ + 'env', 'NAS_DB=/nas', + '/nas/bin/server_umount', mover_name, + '-perm', ckpt, + ] + self._execute_cmd(umount_ckpt_cmd) + delete_ckpt_cmd = [ + 'env', 'NAS_DB=/nas', '/nas/bin/nas_fs', + '-delete', ckpt, + '-Force', + ] + self._execute_cmd(delete_ckpt_cmd) + + rw_mount_cmd = [ + 'env', 'NAS_DB=/nas', '/nas/bin/server_mount', mover_name, + '-option', 'rw', + name, + '/%s' % name, + ] + self._execute_cmd(rw_mount_cmd) + + +@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit, + debug_only=True) +class StoragePool(StorageObject): + def __init__(self, conn, elt_maker, xml_parser, manager): + super(StoragePool, self).__init__(conn, elt_maker, xml_parser, manager) + self.pool_map = {} + + def get(self, name, force=False): + if name not in self.pool_map or force: + status, out = self.get_all() + if constants.STATUS_OK != status: + return status, out + + if name not in self.pool_map: + return constants.STATUS_NOT_FOUND, None + + return constants.STATUS_OK, self.pool_map[name] + + def get_all(self): + self.pool_map.clear() + + request = self._build_query_package( + self.elt_maker.StoragePoolQueryParams() + ) + + response = self._send_request(request) + + if constants.STATUS_OK != response['maxSeverity']: + return response['maxSeverity'], response['problems'] + + if not response['objects']: + return constants.STATUS_NOT_FOUND, response['problems'] + + for item in response['objects']: + pool = {} + property_map = ( + 'name', + ('movers_id', 'movers'), + ('total_size', 'autoSize'), + ('used_size', 'usedSize'), + 'diskType', + 'dataServicePolicies', + ('id', 'pool'), + ) + self._copy_properties(item, pool, property_map) + self.pool_map[item['name']] = pool + + return constants.STATUS_OK, self.pool_map + + def get_id(self, name): + status, out = self.get(name) + + if constants.STATUS_OK != status: + message = (_("Failed to get storage pool by name %(name)s. " + "Reason: %(err)s.") % + {'name': name, 'err': out}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + return out['id'] + + +@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit, + debug_only=True) +class MountPoint(StorageObject): + def __init__(self, conn, elt_maker, xml_parser, manager): + super(MountPoint, self).__init__(conn, elt_maker, xml_parser, manager) + + @utils.retry(exception.EMCVmaxInvalidMoverID) + def create(self, mount_path, fs_name, mover_name, is_vdm=True): + fs_id = self.get_context('FileSystem').get_id(fs_name) + + mover_id = self._get_mover_id(mover_name, is_vdm) + + if self.xml_retry: + self.xml_retry = False + + request = self._build_task_package( + self.elt_maker.NewMount( + self.elt_maker.MoverOrVdm( + mover=mover_id, + moverIdIsVdm='true' if is_vdm else 'false', + ), + fileSystem=fs_id, + path=mount_path + ) + ) + + response = self._send_request(request) + + if (self._response_validation(response, + constants.MSG_INVALID_MOVER_ID) and + not self.xml_retry): + self.xml_retry = True + raise exception.EMCVmaxInvalidMoverID(id=mover_id) + elif self._is_mount_point_already_existent(response): + LOG.warning(_LW("Mount Point %(mount)s already exists. " + "Skip the creation."), {'mount': mount_path}) + return + elif constants.STATUS_OK != response['maxSeverity']: + message = (_('Failed to create Mount Point %(mount)s for ' + 'file system %(fs_name)s. Reason: %(err)s.') % + {'mount': mount_path, + 'fs_name': fs_name, + 'err': response['problems']}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + @utils.retry(exception.EMCVmaxInvalidMoverID) + def get(self, mover_name, is_vdm=True): + mover_id = self._get_mover_id(mover_name, is_vdm) + + if self.xml_retry: + self.xml_retry = False + + request = self._build_query_package( + self.elt_maker.MountQueryParams( + self.elt_maker.MoverOrVdm( + mover=mover_id, + moverIdIsVdm='true' if is_vdm else 'false' + ) + ) + ) + + response = self._send_request(request) + + if (self._response_validation(response, + constants.MSG_INVALID_MOVER_ID) and + not self.xml_retry): + self.xml_retry = True + raise exception.EMCVmaxInvalidMoverID(id=mover_id) + elif constants.STATUS_OK != response['maxSeverity']: + return response['maxSeverity'], response['objects'] + + if not response['objects']: + return constants.STATUS_NOT_FOUND, None + else: + return constants.STATUS_OK, response['objects'] + + @utils.retry(exception.EMCVmaxInvalidMoverID) + def delete(self, mount_path, mover_name, is_vdm=True): + mover_id = self._get_mover_id(mover_name, is_vdm) + + if self.xml_retry: + self.xml_retry = False + + request = self._build_task_package( + self.elt_maker.DeleteMount( + mover=mover_id, + moverIdIsVdm='true' if is_vdm else 'false', + path=mount_path + ) + ) + + response = self._send_request(request) + + if (self._response_validation(response, + constants.MSG_INVALID_MOVER_ID) and + not self.xml_retry): + self.xml_retry = True + raise exception.EMCVmaxInvalidMoverID(id=mover_id) + elif self._is_mount_point_nonexistent(response): + LOG.warning(_LW('Mount point %(mount)s on mover %(mover_name)s ' + 'not found.'), + {'mount': mount_path, 'mover_name': mover_name}) + + return + elif constants.STATUS_OK != response['maxSeverity']: + message = (_('Failed to delete mount point %(mount)s on mover ' + '%(mover_name)s. Reason: %(err)s.') % + {'mount': mount_path, + 'mover_name': mover_name, + 'err': response}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + def _is_mount_point_nonexistent(self, response): + """Translate different status to ok/error status.""" + msg_codes = self._get_problem_message_codes(response['problems']) + message = self._get_problem_messages(response['problems']) + + for code, msg in zip(msg_codes, message): + if ((code == constants.MSG_GENERAL_ERROR and msg.find( + 'No such path or invalid operation') != -1) or + code == constants.MSG_INVALID_VDM_ID or + code == constants.MSG_INVALID_MOVER_ID): + return True + + return False + + def _is_mount_point_already_existent(self, response): + """Translate different status to ok/error status.""" + msg_codes = self._get_problem_message_codes(response['problems']) + message = self._get_problem_messages(response['problems']) + + for code, msg in zip(msg_codes, message): + if ((code == constants.MSG_GENERAL_ERROR and msg.find( + 'Mount already exists') != -1)): + return True + + return False + + +@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit, + debug_only=True) +class Mover(StorageObject): + def __init__(self, conn, elt_maker, xml_parser, manager): + super(Mover, self).__init__(conn, elt_maker, xml_parser, manager) + self.mover_map = {} + self.mover_ref_map = {} + + def get_ref(self, name, force=False): + if name not in self.mover_ref_map or force: + self.mover_ref_map.clear() + + request = self._build_query_package( + self.elt_maker.MoverQueryParams( + self.elt_maker.AspectSelection(movers='true') + ) + ) + + response = self._send_request(request) + + if constants.STATUS_ERROR == response['maxSeverity']: + return response['maxSeverity'], response['problems'] + + for item in response['objects']: + mover = {} + property_map = ('name', ('id', 'mover')) + self._copy_properties(item, mover, property_map) + if mover: + self.mover_ref_map[mover['name']] = mover + + if (name not in self.mover_ref_map or + self.mover_ref_map[name]['id'] == ''): + return constants.STATUS_NOT_FOUND, None + + return constants.STATUS_OK, self.mover_ref_map[name] + + def get(self, name, force=False): + if name not in self.mover_map or force: + if name in self.mover_ref_map and not force: + mover_id = self.mover_ref_map[name]['id'] + else: + mover_id = self.get_id(name, force) + + if name in self.mover_map: + self.mover_map.pop(name) + + request = self._build_query_package( + self.elt_maker.MoverQueryParams( + self.elt_maker.AspectSelection( + moverDeduplicationSettings='true', + moverDnsDomains='true', + moverInterfaces='true', + moverNetworkDevices='true', + moverNisDomains='true', + moverRoutes='true', + movers='true', + moverStatuses='true' + ), + mover=mover_id + ) + ) + + response = self._send_request(request) + if constants.STATUS_ERROR == response['maxSeverity']: + return response['maxSeverity'], response['problems'] + + if not response['objects']: + return constants.STATUS_NOT_FOUND, response['problems'] + + mover = {} + src = response['objects'][0] + property_map = ( + 'name', + ('id', 'mover'), + ('Status', 'maxSeverity'), + 'version', + 'uptime', + 'role', + ('interfaces', 'MoverInterface'), + ('devices', 'LogicalNetworkDevice'), + ('dns_domain', 'MoverDnsDomain'), + ) + + self._copy_properties(src, mover, property_map) + + internal_devices = [] + if mover['interfaces']: + for interface in mover['interfaces']: + if self._is_internal_device(interface['device']): + internal_devices.append(interface) + + mover['interfaces'] = [var for var in mover['interfaces'] if + var not in internal_devices] + + self.mover_map[name] = mover + + return constants.STATUS_OK, self.mover_map[name] + + def get_id(self, name, force=False): + status, mover_ref = self.get_ref(name, force) + if constants.STATUS_OK != status: + message = (_("Failed to get mover by name %(name)s.") % + {'name': name}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + return mover_ref['id'] + + def _is_internal_device(self, device): + for device_type in ('mge', 'fxg', 'tks', 'fsn'): + if device.find(device_type) == 0: + return True + return False + + def get_interconnect_id(self, source, destination): + header = [ + 'id', + 'name', + 'source_server', + 'destination_system', + 'destination_server', + ] + + conn_id = None + + command_nas_cel = [ + 'env', 'NAS_DB=/nas', '/nas/bin/nas_cel', + '-interconnect', '-l', + ] + out, err = self._execute_cmd(command_nas_cel) + + lines = out.strip().split('\n') + for line in lines: + if line.strip().split() == header: + LOG.info(_LI('Found the header of the command ' + '/nas/bin/nas_cel -interconnect -l.')) + else: + interconn = line.strip().split() + if interconn[2] == source and interconn[4] == destination: + conn_id = interconn[0] + + return conn_id + + def get_physical_devices(self, mover_name): + + physical_network_devices = [] + + cmd_sysconfig = [ + 'env', 'NAS_DB=/nas', '/nas/bin/server_sysconfig', mover_name, + '-pci' + ] + + out, err = self._execute_cmd(cmd_sysconfig) + + re_pattern = ('0:\s*(?P\S+)\s*IRQ:\s*(?P\d+)\n' + '.*\n' + '\s*Link:\s*(?P[A-Za-z]+)') + + for device in re.finditer(re_pattern, out): + if 'Up' in device.group('link'): + physical_network_devices.append(device.group('name')) + + return physical_network_devices + + +@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit, + debug_only=True) +class VDM(StorageObject): + def __init__(self, conn, elt_maker, xml_parser, manager): + super(VDM, self).__init__(conn, elt_maker, xml_parser, manager) + self.vdm_map = {} + + @utils.retry(exception.EMCVmaxInvalidMoverID) + def create(self, name, mover_name): + mover_id = self._get_mover_id(mover_name, False) + + if self.xml_retry: + self.xml_retry = False + + request = self._build_task_package( + self.elt_maker.NewVdm(mover=mover_id, name=name) + ) + + response = self._send_request(request) + + if (self._response_validation(response, + constants.MSG_INVALID_MOVER_ID) and + not self.xml_retry): + self.xml_retry = True + raise exception.EMCVmaxInvalidMoverID(id=mover_id) + elif self._response_validation(response, constants.MSG_VDM_EXIST): + LOG.warning(_LW("VDM %(name)s already exists. Skip the creation."), + {'name': name}) + elif constants.STATUS_OK != response['maxSeverity']: + message = (_("Failed to create VDM %(name)s on mover " + "%(mover_name)s. Reason: %(err)s.") % + {'name': name, + 'mover_name': mover_name, + 'err': response['problems']}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + def get(self, name): + if name not in self.vdm_map: + request = self._build_query_package( + self.elt_maker.VdmQueryParams() + ) + + response = self._send_request(request) + + if constants.STATUS_OK != response['maxSeverity']: + return response['maxSeverity'], response['problems'] + elif not response['objects']: + return constants.STATUS_NOT_FOUND, response['problems'] + + for item in response['objects']: + vdm = {} + property_map = ( + 'name', + ('id', 'vdm'), + 'state', + ('host_mover_id', 'mover'), + ('interfaces', 'Interfaces'), + ) + self._copy_properties(item, vdm, property_map) + self.vdm_map[item['name']] = vdm + + if name not in self.vdm_map: + return constants.STATUS_NOT_FOUND, None + + return constants.STATUS_OK, self.vdm_map[name] + + def delete(self, name): + status, out = self.get(name) + if constants.STATUS_NOT_FOUND == status: + LOG.warning(_LW("VDM %s not found. Skip the deletion."), + name) + return + elif constants.STATUS_OK != status: + message = (_("Failed to get VDM by name %(name)s. " + "Reason: %(err)s.") % + {'name': name, 'err': out}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + vdm_id = self.vdm_map[name]['id'] + + request = self._build_task_package( + self.elt_maker.DeleteVdm(vdm=vdm_id) + ) + + response = self._send_request(request) + + if constants.STATUS_OK != response['maxSeverity']: + message = (_("Failed to delete VDM %(name)s. " + "Reason: %(err)s.") % + {'name': name, 'err': response['problems']}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + self.vdm_map.pop(name) + + def get_id(self, name): + status, vdm = self.get(name) + if constants.STATUS_OK != status: + message = (_("Failed to get VDM by name %(name)s.") % + {'name': name}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + return vdm['id'] + + def attach_nfs_interface(self, vdm_name, if_name): + + command_attach_nfs_interface = [ + 'env', 'NAS_DB=/nas', '/nas/bin/nas_server', + '-vdm', vdm_name, + '-attach', if_name, + ] + + self._execute_cmd(command_attach_nfs_interface) + + def detach_nfs_interface(self, vdm_name, if_name): + + command_detach_nfs_interface = [ + 'env', 'NAS_DB=/nas', '/nas/bin/nas_server', + '-vdm', vdm_name, + '-detach', if_name, + ] + + try: + self._execute_cmd(command_detach_nfs_interface, + check_exit_code=True) + except processutils.ProcessExecutionError: + interfaces = self.get_interfaces(vdm_name) + if if_name not in interfaces['nfs']: + LOG.debug("Failed to detach interface %(interface)s " + "from mover %(mover_name)s.", + {'interface': if_name, 'mover_name': vdm_name}) + else: + message = (_("Failed to detach interface %(interface)s " + "from mover %(mover_name)s.") % + {'interface': if_name, 'mover_name': vdm_name}) + LOG.exception(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + def get_interfaces(self, vdm_name): + interfaces = { + 'cifs': [], + 'nfs': [], + } + + re_pattern = ('Interfaces to services mapping:' + '\s*(?P(\s*interface=.*)*)') + + command_get_interfaces = [ + 'env', 'NAS_DB=/nas', '/nas/bin/nas_server', + '-i', + '-vdm', vdm_name, + ] + + out, err = self._execute_cmd(command_get_interfaces) + + m = re.search(re_pattern, out) + if m: + if_list = m.group('interfaces').split('\n') + for i in if_list: + m_if = re.search('\s*interface=(?P.*)\s*:' + '\s*(?P.*)\s*', i) + if m_if: + if_name = m_if.group('if').strip() + if 'cifs' == m_if.group('type') and if_name != '': + interfaces['cifs'].append(if_name) + elif 'vdm' == m_if.group('type') and if_name != '': + interfaces['nfs'].append(if_name) + + return interfaces + + +@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit, + debug_only=True) +class Snapshot(StorageObject): + def __init__(self, conn, elt_maker, xml_parser, manager): + super(Snapshot, self).__init__(conn, elt_maker, xml_parser, manager) + self.snap_map = {} + + def create(self, name, fs_name, pool_id, ckpt_size=None): + fs_id = self.get_context('FileSystem').get_id(fs_name) + + if ckpt_size: + elt_pool = self.elt_maker.StoragePool( + pool=pool_id, + size=six.text_type(ckpt_size) + ) + else: + elt_pool = self.elt_maker.StoragePool(pool=pool_id) + + new_ckpt = self.elt_maker.NewCheckpoint( + self.elt_maker.SpaceAllocationMethod( + elt_pool + ), + checkpointOf=fs_id, + name=name + ) + + request = self._build_task_package(new_ckpt) + + response = self._send_request(request) + + if self._response_validation(response, constants.MSG_SNAP_EXIST): + LOG.warning(_LW("Snapshot %(name)s already exists. " + "Skip the creation."), + {'name': name}) + elif constants.STATUS_OK != response['maxSeverity']: + message = (_("Failed to create snapshot %(name)s on " + "filesystem %(fs_name)s. Reason: %(err)s.") % + {'name': name, + 'fs_name': fs_name, + 'err': response['problems']}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + def get(self, name): + if name not in self.snap_map: + request = self._build_query_package( + self.elt_maker.CheckpointQueryParams( + self.elt_maker.Alias(name=name) + ) + ) + + response = self._send_request(request) + + if constants.STATUS_OK != response['maxSeverity']: + return response['maxSeverity'], response['problems'] + + if not response['objects']: + return constants.STATUS_NOT_FOUND, response['problems'] + + src = response['objects'][0] + snap = {} + property_map = ( + 'name', + ('id', 'checkpoint'), + 'checkpointOf', + 'state', + ) + self._copy_properties(src, snap, property_map) + + self.snap_map[name] = snap + + return constants.STATUS_OK, self.snap_map[name] + + def delete(self, name): + status, out = self.get(name) + if constants.STATUS_NOT_FOUND == status: + LOG.warning(_LW("Snapshot %s not found. Skip the deletion."), + name) + return + elif constants.STATUS_OK != status: + message = (_("Failed to get snapshot by name %(name)s. " + "Reason: %(err)s.") % + {'name': name, 'err': out}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + chpt_id = self.snap_map[name]['id'] + + request = self._build_task_package( + self.elt_maker.DeleteCheckpoint(checkpoint=chpt_id) + ) + + response = self._send_request(request) + if constants.STATUS_OK != response['maxSeverity']: + message = (_("Failed to delete snapshot %(name)s. " + "Reason: %(err)s.") % + {'name': name, 'err': response['problems']}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + self.snap_map.pop(name) + + def get_id(self, name): + status, out = self.get(name) + + if constants.STATUS_OK != status: + message = (_("Failed to get snapshot by %(name)s. " + "Reason: %(err)s.") % + {'name': name, 'err': out}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + return self.snap_map[name]['id'] + + +@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit, + debug_only=True) +class MoverInterface(StorageObject): + def __init__(self, conn, elt_maker, xml_parser, manager): + super(MoverInterface, self).__init__(conn, elt_maker, xml_parser, + manager) + + @utils.retry(exception.EMCVmaxInvalidMoverID) + def create(self, interface): + # Maximum of 32 characters for mover interface name + name = interface['name'] + if len(name) > 32: + name = name[0:31] + + device_name = interface['device_name'] + ip_addr = interface['ip'] + mover_name = interface['mover_name'] + net_mask = interface['net_mask'] + vlan_id = interface['vlan_id'] if interface['vlan_id'] else -1 + + mover_id = self._get_mover_id(mover_name, False) + + if self.xml_retry: + self.xml_retry = False + + request = self._build_task_package( + self.elt_maker.NewMoverInterface( + device=device_name, + ipAddress=six.text_type(ip_addr), + mover=mover_id, + name=name, + netMask=net_mask, + vlanid=six.text_type(vlan_id) + ) + ) + + response = self._send_request(request) + + if (self._response_validation(response, + constants.MSG_INVALID_MOVER_ID) and + not self.xml_retry): + self.xml_retry = True + raise exception.EMCVmaxInvalidMoverID(id=mover_id) + elif self._response_validation( + response, constants.MSG_INTERFACE_NAME_EXIST): + LOG.warning(_LW("Mover interface name %s already exists. " + "Skip the creation."), name) + elif self._response_validation( + response, constants.MSG_INTERFACE_EXIST): + LOG.warning(_LW("Mover interface IP %s already exists. " + "Skip the creation."), ip_addr) + elif self._response_validation( + response, constants.MSG_INTERFACE_INVALID_VLAN_ID): + # When fail to create a mover interface with the specified + # vlan id, VMAX will leave a interface with vlan id 0 in the + # backend. So we should explicitly remove the interface. + try: + self.delete(six.text_type(ip_addr), mover_name) + except exception.EMCVmaxXMLAPIError: + pass + message = (_("Invalid vlan id %s. Other interfaces on this " + "subnet are in a different vlan.") % vlan_id) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + elif constants.STATUS_OK != response['maxSeverity']: + message = (_("Failed to create mover interface %(interface)s. " + "Reason: %(err)s.") % + {'interface': interface, + 'err': response['problems']}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + def get(self, name, mover_name): + # Maximum of 32 characters for mover interface name + if len(name) > 32: + name = name[0:31] + + status, mover = self.manager.getStorageContext('Mover').get( + mover_name, True) + if constants.STATUS_OK == status: + for interface in mover['interfaces']: + if name == interface['name']: + return constants.STATUS_OK, interface + + return constants.STATUS_NOT_FOUND, None + + @utils.retry(exception.EMCVmaxInvalidMoverID) + def delete(self, ip_addr, mover_name): + mover_id = self._get_mover_id(mover_name, False) + + if self.xml_retry: + self.xml_retry = False + + request = self._build_task_package( + self.elt_maker.DeleteMoverInterface( + ipAddress=six.text_type(ip_addr), + mover=mover_id + ) + ) + + response = self._send_request(request) + + if (self._response_validation(response, + constants.MSG_INVALID_MOVER_ID) and + not self.xml_retry): + self.xml_retry = True + raise exception.EMCVmaxInvalidMoverID(id=mover_id) + elif self._response_validation( + response, constants.MSG_INTERFACE_NON_EXISTENT): + LOG.warning(_LW("Mover interface %s not found. " + "Skip the deletion."), ip_addr) + return + elif constants.STATUS_OK != response['maxSeverity']: + message = (_("Failed to delete mover interface %(ip)s on mover " + "%(mover)s. Reason: %(err)s.") % + {'ip': ip_addr, + 'mover': mover_name, + 'err': response['problems']}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + +@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit, + debug_only=True) +class DNSDomain(StorageObject): + def __init__(self, conn, elt_maker, xml_parser, manager): + super(DNSDomain, self).__init__(conn, elt_maker, xml_parser, manager) + + @utils.retry(exception.EMCVmaxInvalidMoverID) + def create(self, mover_name, name, servers, protocol='udp'): + mover_id = self._get_mover_id(mover_name, False) + + if self.xml_retry: + self.xml_retry = False + + request = self._build_task_package( + self.elt_maker.NewMoverDnsDomain( + mover=mover_id, + name=name, + servers=servers, + protocol=protocol + ) + ) + + response = self._send_request(request) + + if (self._response_validation(response, + constants.MSG_INVALID_MOVER_ID) and + not self.xml_retry): + self.xml_retry = True + raise exception.EMCVmaxInvalidMoverID(id=mover_id) + elif constants.STATUS_OK != response['maxSeverity']: + message = (_("Failed to create DNS domain %(name)s. " + "Reason: %(err)s.") % + {'name': name, 'err': response['problems']}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + @utils.retry(exception.EMCVmaxInvalidMoverID) + def delete(self, mover_name, name): + mover_id = self._get_mover_id(mover_name, False) + + if self.xml_retry: + self.xml_retry = False + + request = self._build_task_package( + self.elt_maker.DeleteMoverDnsDomain( + mover=mover_id, + name=name + ) + ) + + response = self._send_request(request) + if (self._response_validation(response, + constants.MSG_INVALID_MOVER_ID) and + not self.xml_retry): + self.xml_retry = True + raise exception.EMCVmaxInvalidMoverID(id=mover_id) + elif constants.STATUS_OK != response['maxSeverity']: + LOG.warning(_LW("Failed to delete DNS domain %(name)s. " + "Reason: %(err)s."), + {'name': name, 'err': response['problems']}) + + +@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit, + debug_only=True) +class CIFSServer(StorageObject): + def __init__(self, conn, elt_maker, xml_parser, manager): + super(CIFSServer, self).__init__(conn, elt_maker, xml_parser, manager) + self.cifs_server_map = {} + + @utils.retry(exception.EMCVmaxInvalidMoverID) + def create(self, server_args): + compName = server_args['name'] + # Maximum of 14 characters for netBIOS name + name = server_args['name'][-14:] + # Maximum of 12 characters for alias name + alias_name = server_args['name'][-12:] + interfaces = server_args['interface_ip'] + domain_name = server_args['domain_name'] + user_name = server_args['user_name'] + password = server_args['password'] + mover_name = server_args['mover_name'] + is_vdm = server_args['is_vdm'] + + mover_id = self._get_mover_id(mover_name, is_vdm) + + if self.xml_retry: + self.xml_retry = False + + alias_name_list = [self.elt_maker.li(alias_name)] + + request = self._build_task_package( + self.elt_maker.NewW2KCifsServer( + self.elt_maker.MoverOrVdm( + mover=mover_id, + moverIdIsVdm='true' if server_args['is_vdm'] else 'false' + ), + self.elt_maker.Aliases(*alias_name_list), + self.elt_maker.JoinDomain(userName=user_name, + password=password), + compName=compName, + domain=domain_name, + interfaces=interfaces, + name=name + ) + ) + + response = self._send_request(request) + + if (self._response_validation(response, + constants.MSG_INVALID_MOVER_ID) and + not self.xml_retry): + self.xml_retry = True + raise exception.EMCVmaxInvalidMoverID(id=mover_id) + if constants.STATUS_OK != response['maxSeverity']: + status, out = self.get(compName, mover_name, is_vdm) + if constants.STATUS_OK == status and out['domainJoined'] == 'true': + return + else: + message = (_("Failed to create CIFS server %(name)s. " + "Reason: %(err)s.") % + {'name': name, + 'err': response['problems']}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + @utils.retry(exception.EMCVmaxInvalidMoverID) + def get_all(self, mover_name, is_vdm=True): + mover_id = self._get_mover_id(mover_name, is_vdm) + + if self.xml_retry: + self.xml_retry = False + + request = self._build_query_package( + self.elt_maker.CifsServerQueryParams( + self.elt_maker.MoverOrVdm( + mover=mover_id, + moverIdIsVdm='true' if is_vdm else 'false' + ) + ) + ) + + response = self._send_request(request) + if (self._response_validation(response, + constants.MSG_INVALID_MOVER_ID) and + not self.xml_retry): + self.xml_retry = True + raise exception.EMCVmaxInvalidMoverID(id=mover_id) + elif constants.STATUS_OK != response['maxSeverity']: + return response['maxSeverity'], response['objects'] + + if mover_name in self.cifs_server_map: + self.cifs_server_map.pop(mover_name) + + self.cifs_server_map[mover_name] = {} + + for item in response['objects']: + self.cifs_server_map[mover_name][item['compName'].lower()] = item + + return constants.STATUS_OK, self.cifs_server_map[mover_name] + + def get(self, name, mover_name, is_vdm=True, force=False): + # name is compName + name = name.lower() + + if (mover_name in self.cifs_server_map and + name in self.cifs_server_map[mover_name]) and not force: + return constants.STATUS_OK, self.cifs_server_map[mover_name][name] + + self.get_all(mover_name, is_vdm) + + if mover_name in self.cifs_server_map: + for compName, server in self.cifs_server_map[mover_name].items(): + if name == compName: + return constants.STATUS_OK, server + + return constants.STATUS_NOT_FOUND, None + + @utils.retry(exception.EMCVmaxInvalidMoverID) + def modify(self, server_args): + """Make CIFS server join or un-join the domain. + + :param server_args: Dictionary for CIFS server modification + name: CIFS server name instead of compName + join_domain: True for joining the domain, false for un-joining + user_name: User name under which the domain is joined + password: Password associated with the user name + mover_name: mover or VDM name + is_vdm: Boolean to indicate mover or VDM + :raises exception.EMCVmaxXMLAPIError: if modification fails. + """ + name = server_args['name'] + join_domain = server_args['join_domain'] + user_name = server_args['user_name'] + password = server_args['password'] + mover_name = server_args['mover_name'] + + if 'is_vdm' in server_args.keys(): + is_vdm = server_args['is_vdm'] + else: + is_vdm = True + + mover_id = self._get_mover_id(mover_name, is_vdm) + + if self.xml_retry: + self.xml_retry = False + + request = self._build_task_package( + self.elt_maker.ModifyW2KCifsServer( + self.elt_maker.DomainSetting( + joinDomain='true' if join_domain else 'false', + password=password, + userName=user_name, + ), + mover=mover_id, + moverIdIsVdm='true' if is_vdm else 'false', + name=name + ) + ) + + response = self._send_request(request) + + if (self._response_validation(response, + constants.MSG_INVALID_MOVER_ID) and + not self.xml_retry): + self.xml_retry = True + raise exception.EMCVmaxInvalidMoverID(id=mover_id) + elif self._ignore_modification_error(response, join_domain): + return + elif constants.STATUS_OK != response['maxSeverity']: + message = (_("Failed to modify CIFS server %(name)s. " + "Reason: %(err)s.") % + {'name': name, + 'err': response['problems']}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + def _ignore_modification_error(self, response, join_domain): + if self._response_validation(response, constants.MSG_JOIN_DOMAIN): + return join_domain + elif self._response_validation(response, constants.MSG_UNJOIN_DOMAIN): + return not join_domain + + return False + + def delete(self, computer_name, mover_name, is_vdm=True): + try: + status, out = self.get( + computer_name.lower(), mover_name, is_vdm, self.xml_retry) + if constants.STATUS_NOT_FOUND == status: + LOG.warning(_LW("CIFS server %(name)s on mover %(mover_name)s " + "not found. Skip the deletion."), + {'name': computer_name, 'mover_name': mover_name}) + return + except exception.EMCVmaxXMLAPIError: + LOG.warning(_LW("CIFS server %(name)s on mover %(mover_name)s " + "not found. Skip the deletion."), + {'name': computer_name, 'mover_name': mover_name}) + return + + server_name = out['name'] + + mover_id = self._get_mover_id(mover_name, is_vdm) + + request = self._build_task_package( + self.elt_maker.DeleteCifsServer( + mover=mover_id, + moverIdIsVdm='true' if is_vdm else 'false', + name=server_name + ) + ) + + response = self._send_request(request) + + if constants.STATUS_OK != response['maxSeverity']: + message = (_("Failed to delete CIFS server %(name)s. " + "Reason: %(err)s.") % + {'name': computer_name, 'err': response['problems']}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + self.cifs_server_map[mover_name].pop(computer_name) + + +@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit, + debug_only=True) +class CIFSShare(StorageObject): + def __init__(self, conn, elt_maker, xml_parser, manager): + super(CIFSShare, self).__init__(conn, elt_maker, xml_parser, manager) + self.cifs_share_map = {} + + @utils.retry(exception.EMCVmaxInvalidMoverID) + def create(self, name, server_name, mover_name, is_vdm=True): + mover_id = self._get_mover_id(mover_name, is_vdm) + + if self.xml_retry: + self.xml_retry = False + + share_path = '/' + name + + request = self._build_task_package( + self.elt_maker.NewCifsShare( + self.elt_maker.MoverOrVdm( + mover=mover_id, + moverIdIsVdm='true' if is_vdm else 'false' + ), + self.elt_maker.CifsServers(self.elt_maker.li(server_name)), + name=name, + path=share_path + ) + ) + + response = self._send_request(request) + + if (self._response_validation(response, + constants.MSG_INVALID_MOVER_ID) and + not self.xml_retry): + self.xml_retry = True + raise exception.EMCVmaxInvalidMoverID(id=mover_id) + elif constants.STATUS_OK != response['maxSeverity']: + message = (_("Failed to create file share %(name)s. " + "Reason: %(err)s.") % + {'name': name, 'err': response['problems']}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + def get(self, name): + if name not in self.cifs_share_map: + request = self._build_query_package( + self.elt_maker.CifsShareQueryParams(name=name) + ) + + response = self._send_request(request) + + if constants.STATUS_OK != response['maxSeverity']: + return response['maxSeverity'], response['problems'] + + if not response['objects']: + return constants.STATUS_NOT_FOUND, None + + self.cifs_share_map[name] = response['objects'][0] + + return constants.STATUS_OK, self.cifs_share_map[name] + + @utils.retry(exception.EMCVmaxInvalidMoverID) + def delete(self, name, mover_name, is_vdm=True): + status, out = self.get(name) + if constants.STATUS_NOT_FOUND == status: + LOG.warning(_LW("CIFS share %s not found. Skip the deletion."), + name) + return + elif constants.STATUS_OK != status: + message = (_("Failed to get CIFS share by name %(name)s. " + "Reason: %(err)s.") % + {'name': name, 'err': out}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + mover_id = self._get_mover_id(mover_name, is_vdm) + + if self.xml_retry: + self.xml_retry = False + + netbios_names = self.cifs_share_map[name]['CifsServers'] + + request = self._build_task_package( + self.elt_maker.DeleteCifsShare( + self.elt_maker.CifsServers(*map(lambda a: self.elt_maker.li(a), + netbios_names)), + mover=mover_id, + moverIdIsVdm='true' if is_vdm else 'false', + name=name + ) + ) + + response = self._send_request(request) + + if (self._response_validation(response, + constants.MSG_INVALID_MOVER_ID) and + not self.xml_retry): + self.xml_retry = True + raise exception.EMCVmaxInvalidMoverID(id=mover_id) + elif constants.STATUS_OK != response['maxSeverity']: + message = (_("Failed to delete file system %(name)s. " + "Reason: %(err)s.") % + {'name': name, 'err': response['problems']}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + self.cifs_share_map.pop(name) + + def disable_share_access(self, share_name, mover_name): + cmd_str = 'sharesd %s set noaccess' % share_name + disable_access = [ + 'env', 'NAS_DB=/nas', '/nas/bin/.server_config', mover_name, + '-v', "%s" % cmd_str, + ] + + try: + self._execute_cmd(disable_access, check_exit_code=True) + except processutils.ProcessExecutionError: + message = (_('Failed to disable the access to CIFS share ' + '%(name)s.') % + {'name': share_name}) + LOG.exception(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + def allow_share_access(self, mover_name, share_name, user_name, domain, + access=constants.CIFS_ACL_FULLCONTROL): + account = user_name + "@" + domain + allow_str = ('sharesd %(share_name)s grant %(account)s=%(access)s' + % {'share_name': share_name, + 'account': account, + 'access': access}) + + allow_access = [ + 'env', 'NAS_DB=/nas', '/nas/bin/.server_config', mover_name, + '-v', "%s" % allow_str, + ] + + try: + self._execute_cmd(allow_access, check_exit_code=True) + except processutils.ProcessExecutionError as expt: + dup_msg = re.compile(r'ACE for %(domain)s\\%(user)s unchanged' % + {'domain': domain, 'user': user_name}, re.I) + if re.search(dup_msg, expt.stdout): + LOG.warning(_LW("Duplicate access control entry, " + "skipping allow...")) + else: + message = (_('Failed to allow the access %(access)s to ' + 'CIFS share %(name)s. Reason: %(err)s.') % + {'access': access, 'name': share_name, 'err': expt}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + def deny_share_access(self, mover_name, share_name, user_name, domain, + access=constants.CIFS_ACL_FULLCONTROL): + account = user_name + "@" + domain + revoke_str = ('sharesd %(share_name)s revoke %(account)s=%(access)s' + % {'share_name': share_name, + 'account': account, + 'access': access}) + + allow_access = [ + 'env', 'NAS_DB=/nas', '/nas/bin/.server_config', mover_name, + '-v', "%s" % revoke_str, + ] + try: + self._execute_cmd(allow_access, check_exit_code=True) + except processutils.ProcessExecutionError as expt: + not_found_msg = re.compile( + r'No ACE found for %(domain)s\\%(user)s' + % {'domain': domain, 'user': user_name}, re.I) + user_err_msg = re.compile( + r'Cannot get mapping for %(domain)s\\%(user)s' + % {'domain': domain, 'user': user_name}, re.I) + + if re.search(not_found_msg, expt.stdout): + LOG.warning(_LW("No access control entry found, " + "skipping deny...")) + elif re.search(user_err_msg, expt.stdout): + LOG.warning(_LW("User not found on domain, skipping deny...")) + else: + message = (_('Failed to deny the access %(access)s to ' + 'CIFS share %(name)s. Reason: %(err)s.') % + {'access': access, 'name': share_name, 'err': expt}) + LOG.exception(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + def get_share_access(self, mover_name, share_name): + get_str = 'sharesd %s dump' % share_name + get_access = [ + 'env', 'NAS_DB=/nas', '/nas/bin/.server_config', mover_name, + '-v', "%s" % get_str, + ] + + try: + out, err = self._execute_cmd(get_access, check_exit_code=True) + except processutils.ProcessExecutionError: + msg = _('Failed to get access list of CIFS share %s.') % share_name + LOG.exception(msg) + raise exception.EMCVmaxXMLAPIError(err=msg) + + ret = {} + name_pattern = re.compile(r"Unix user '(.+?)'") + access_pattern = re.compile(r"ALLOWED:(.+?):") + + name = None + for line in out.splitlines(): + if name is None: + names = name_pattern.findall(line) + if names: + name = names[0].lower() + else: + accesses = access_pattern.findall(line) + if accesses: + ret[name] = accesses[0].lower() + name = None + return ret + + def clear_share_access(self, mover_name, share_name, domain, + white_list_users): + existing_users = self.get_share_access(mover_name, share_name) + white_list_users_set = set(user.lower() for user in white_list_users) + users_to_remove = set(existing_users.keys()) - white_list_users_set + for user in users_to_remove: + self.deny_share_access(mover_name, share_name, user, domain, + existing_users[user]) + return users_to_remove + + +@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit, + debug_only=True) +class NFSShare(StorageObject): + def __init__(self, conn, elt_maker, xml_parser, manager): + super(NFSShare, self).__init__(conn, elt_maker, xml_parser, manager) + self.nfs_share_map = {} + + def create(self, name, mover_name): + share_path = '/' + name + create_nfs_share_cmd = [ + 'env', 'NAS_DB=/nas', '/nas/bin/server_export', mover_name, + '-option', 'access=-0.0.0.0/0.0.0.0', + share_path, + ] + + try: + self._execute_cmd(create_nfs_share_cmd, check_exit_code=True) + except processutils.ProcessExecutionError as expt: + message = (_('Failed to create NFS share %(name)s on mover ' + '%(mover_name)s. Reason: %(err)s.') % + {'name': name, 'mover_name': mover_name, 'err': expt}) + LOG.exception(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + def delete(self, name, mover_name): + path = '/' + name + + status, out = self.get(name, mover_name) + if constants.STATUS_NOT_FOUND == status: + LOG.warning(_LW("NFS share %s not found. Skip the deletion."), + path) + return + + delete_nfs_share_cmd = [ + 'env', 'NAS_DB=/nas', '/nas/bin/server_export', mover_name, + '-unexport', + '-perm', + path, + ] + + try: + self._execute_cmd(delete_nfs_share_cmd, check_exit_code=True) + except processutils.ProcessExecutionError as expt: + message = (_('Failed to delete NFS share %(name)s on ' + '%(mover_name)s. Reason: %(err)s.') % + {'name': name, 'mover_name': mover_name, 'err': expt}) + LOG.exception(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + self.nfs_share_map.pop(name) + + def get(self, name, mover_name, force=False, check_exit_code=False): + if name in self.nfs_share_map and not force: + return constants.STATUS_OK, self.nfs_share_map[name] + + path = '/' + name + + nfs_share = { + "mover_name": '', + "path": '', + 'AccessHosts': [], + 'RwHosts': [], + 'RoHosts': [], + 'RootHosts': [], + 'readOnly': '', + } + + nfs_query_cmd = [ + 'env', 'NAS_DB=/nas', '/nas/bin/server_export', mover_name, + '-P', 'nfs', + '-list', path, + ] + + try: + out, err = self._execute_cmd(nfs_query_cmd, + check_exit_code=check_exit_code) + except processutils.ProcessExecutionError as expt: + dup_msg = (r'%(mover_name)s : No such file or directory' % + {'mover_name': mover_name}) + if re.search(dup_msg, expt.stdout): + LOG.warning(_LW("NFS share %s not found."), name) + return constants.STATUS_NOT_FOUND, None + else: + message = (_('Failed to list NFS share %(name)s on ' + '%(mover_name)s. Reason: %(err)s.') % + {'name': name, + 'mover_name': mover_name, + 'err': expt}) + LOG.exception(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + re_exports = '%s\s*:\s*\nexport\s*(.*)\n' % mover_name + m = re.search(re_exports, out) + if m is not None: + nfs_share['path'] = path + nfs_share['mover_name'] = mover_name + export = m.group(1) + fields = export.split(" ") + for field in fields: + field = field.strip() + if field.startswith('rw='): + nfs_share['RwHosts'] = field[3:].split(":") + elif field.startswith('access='): + nfs_share['AccessHosts'] = field[7:].split(":") + elif field.startswith('root='): + nfs_share['RootHosts'] = field[5:].split(":") + elif field.startswith('ro='): + nfs_share['RoHosts'] = field[3:].split(":") + + self.nfs_share_map[name] = nfs_share + else: + return constants.STATUS_NOT_FOUND, None + + return constants.STATUS_OK, self.nfs_share_map[name] + + def allow_share_access(self, share_name, host_ip, mover_name, + access_level=const.ACCESS_LEVEL_RW): + @utils.synchronized('emc-shareaccess-' + share_name) + def do_allow_access(share_name, host_ip, mover_name, access_level): + status, share = self.get(share_name, mover_name) + if constants.STATUS_NOT_FOUND == status: + message = (_('NFS share %s not found.') % share_name) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + changed = False + rwhosts = share['RwHosts'] + rohosts = share['RoHosts'] + if access_level == const.ACCESS_LEVEL_RW: + if host_ip not in rwhosts: + rwhosts.append(host_ip) + changed = True + if host_ip in rohosts: + rohosts.remove(host_ip) + changed = True + if access_level == const.ACCESS_LEVEL_RO: + if host_ip not in rohosts: + rohosts.append(host_ip) + changed = True + if host_ip in rwhosts: + rwhosts.remove(host_ip) + changed = True + + roothosts = share['RootHosts'] + if host_ip not in roothosts: + roothosts.append(host_ip) + changed = True + accesshosts = share['AccessHosts'] + if host_ip not in accesshosts: + accesshosts.append(host_ip) + changed = True + + if not changed: + LOG.debug("%(host)s is already in access list of share " + "%(name)s.", {'host': host_ip, 'name': share_name}) + else: + path = '/' + share_name + self._set_share_access(path, + mover_name, + rwhosts, + rohosts, + roothosts, + accesshosts) + + # Update self.nfs_share_map + self.get(share_name, mover_name, force=True, + check_exit_code=True) + + do_allow_access(share_name, host_ip, mover_name, access_level) + + def deny_share_access(self, share_name, host_ip, mover_name): + + @utils.synchronized('emc-shareaccess-' + share_name) + def do_deny_access(share_name, host_ip, mover_name): + status, share = self.get(share_name, mover_name) + if constants.STATUS_OK != status: + message = (_('Query nfs share %(path)s failed. ' + 'Reason %(err)s.') % + {'path': share_name, 'err': share}) + LOG.error(message) + raise exception.EMCVmaxXMLAPIError(err=message) + + changed = False + rwhosts = set(share['RwHosts']) + if host_ip in rwhosts: + rwhosts.remove(host_ip) + changed = True + roothosts = set(share['RootHosts']) + if host_ip in roothosts: + roothosts.remove(host_ip) + changed = True + accesshosts = set(share['AccessHosts']) + if host_ip in accesshosts: + accesshosts.remove(host_ip) + changed = True + rohosts = set(share['RoHosts']) + if host_ip in rohosts: + rohosts.remove(host_ip) + changed = True + if not changed: + LOG.debug("%(host)s is already in access list of share " + "%(name)s.", {'host': host_ip, 'name': share_name}) + else: + path = '/' + share_name + self._set_share_access(path, + mover_name, + rwhosts, + rohosts, + roothosts, + accesshosts) + + # Update self.nfs_share_map + self.get(share_name, mover_name, force=True, + check_exit_code=True) + + do_deny_access(share_name, host_ip, mover_name) + + def clear_share_access(self, share_name, mover_name, white_list_hosts): + @utils.synchronized('emc-shareaccess-' + share_name) + def do_clear_access(share_name, mover_name, white_list_hosts): + def hosts_to_remove(orig_list): + if white_list_hosts is None: + ret = set() + else: + ret = set(white_list_hosts).intersection(set(orig_list)) + return ret + + status, share = self.get(share_name, mover_name) + if constants.STATUS_OK != status: + message = (_('Query nfs share %(path)s failed. ' + 'Reason %(err)s.') % + {'path': share_name, 'err': status}) + raise exception.EMCVmaxXMLAPIError(err=message) + + self._set_share_access('/' + share_name, + mover_name, + hosts_to_remove(share['RwHosts']), + hosts_to_remove(share['RoHosts']), + hosts_to_remove(share['RootHosts']), + hosts_to_remove(share['AccessHosts'])) + + # Update self.nfs_share_map + self.get(share_name, mover_name, force=True, + check_exit_code=True) + + do_clear_access(share_name, mover_name, white_list_hosts) + + def _set_share_access(self, path, mover_name, rw_hosts, ro_hosts, + root_hosts, access_hosts): + + if access_hosts is None: + access_hosts = set() + + if '-0.0.0.0/0.0.0.0' not in access_hosts: + access_hosts.add('-0.0.0.0/0.0.0.0') + + access_str = ('access=%(access)s' + % {'access': ':'.join(access_hosts)}) + if root_hosts: + access_str += (',root=%(root)s' % {'root': ':'.join(root_hosts)}) + if rw_hosts: + access_str += ',rw=%(rw)s' % {'rw': ':'.join(rw_hosts)} + if ro_hosts: + access_str += ',ro=%(ro)s' % {'ro': ':'.join(ro_hosts)} + set_nfs_share_access_cmd = [ + 'env', 'NAS_DB=/nas', '/nas/bin/server_export', mover_name, + '-ignore', + '-option', access_str, + path, + ] + + try: + self._execute_cmd(set_nfs_share_access_cmd, check_exit_code=True) + except processutils.ProcessExecutionError as expt: + message = (_('Failed to set NFS share %(name)s access on ' + '%(mover_name)s. Reason: %(err)s.') % + {'name': path[1:], + 'mover_name': mover_name, + 'err': expt}) + LOG.exception(message) + raise exception.EMCVmaxXMLAPIError(err=message) diff --git a/manila/share/drivers/dell_emc/plugins/vmax/utils.py b/manila/share/drivers/dell_emc/plugins/vmax/utils.py new file mode 100644 index 0000000000..cb08490d4e --- /dev/null +++ b/manila/share/drivers/dell_emc/plugins/vmax/utils.py @@ -0,0 +1,83 @@ +# Copyright (c) 2016 Dell Inc. or its subsidiaries. +# 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 types + +from oslo_config import cfg +from oslo_log import log +from oslo_utils import fnmatch +from oslo_utils import timeutils + +CONF = cfg.CONF +LOG = log.getLogger(__name__) + + +def decorate_all_methods(decorator, debug_only=False): + if debug_only and not CONF.debug: + return lambda cls: cls + + def _decorate_all_methods(cls): + for attr_name, attr_val in cls.__dict__.items(): + if (isinstance(attr_val, types.FunctionType) and + not attr_name.startswith("_")): + setattr(cls, attr_name, decorator(attr_val)) + return cls + + return _decorate_all_methods + + +def log_enter_exit(func): + if not CONF.debug: + return func + + def inner(self, *args, **kwargs): + LOG.debug("Entering %(cls)s.%(method)s.", + {'cls': self.__class__.__name__, + 'method': func.__name__}) + start = timeutils.utcnow() + ret = func(self, *args, **kwargs) + end = timeutils.utcnow() + LOG.debug("Exiting %(cls)s.%(method)s. " + "Spent %(duration)s sec. " + "Return %(return)s.", + {'cls': self.__class__.__name__, + 'duration': timeutils.delta_seconds(start, end), + 'method': func.__name__, + 'return': ret}) + return ret + + return inner + + +def do_match_any(full, matcher_list): + """Finds items that match any of the matchers. + + :param full: Full item list + :param matcher_list: The list of matchers. Each matcher supports + Unix shell-style wildcards + :return: The matched items set and the unmatched items set + """ + matched = set() + not_matched = set() + + full = set([item.strip() for item in full]) + matcher_list = set([item.strip() for item in matcher_list]) + + for matcher in matcher_list: + for item in full: + if fnmatch.fnmatchcase(item, matcher): + matched.add(item) + not_matched = full - matched + return matched, not_matched diff --git a/manila/share/drivers/dell_emc/plugins/vmax/xml_api_parser.py b/manila/share/drivers/dell_emc/plugins/vmax/xml_api_parser.py new file mode 100644 index 0000000000..7b3c3e61ea --- /dev/null +++ b/manila/share/drivers/dell_emc/plugins/vmax/xml_api_parser.py @@ -0,0 +1,317 @@ +# Copyright (c) 2016 Dell Inc. or its subsidiaries. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import re + +from lxml import etree +import six + + +class XMLAPIParser(object): + def __init__(self): + # The following Boolean acts as the flag for the common sub-element. + # For instance: + # + #
  • server_1
  • + #
    + # + #
  • interface_1
  • + #
    + self.is_QueryStatus = False + self.is_CifsServers = False + self.is_Aliases = False + self.is_MoverStatus = False + self.is_TaskResponse = False + self.is_Vdm = False + self.is_Interfaces = False + + self.elt = {} + + def _remove_ns(self, tag): + i = tag.find('}') + if i >= 0: + tag = tag[i + 1:] + return tag + + def parse(self, xml): + result = { + 'type': None, + 'taskId': None, + 'maxSeverity': None, + 'objects': [], + 'problems': [], + } + + events = ("start", "end") + + context = etree.iterparse(six.BytesIO(xml.encode('utf-8')), + events=events) + for action, elem in context: + self.tag = self._remove_ns(elem.tag) + + func = self._get_func(action, self.tag) + if func in vars(XMLAPIParser): + if action == 'start': + eval('self.' + func)(elem, result) + elif action == 'end': + eval('self.' + func)() + + return result + + def _get_func(self, action, tag): + if tag == 'W2KServerData': + return action + '_' + 'w2k_server_data' + + temp_list = re.sub(r"([A-Z])", r" \1", tag).split() + if temp_list: + func_name = action + '_' + '_'.join(temp_list) + else: + func_name = action + '_' + tag + return func_name.lower() + + def _copy_property(self, source, target, property, list_property=None): + for key in property: + if key in source: + target[key] = source[key] + + if list_property: + for key in list_property: + if key in source: + target[key] = source[key].split() + + def _append_elm_property(self, elm, result, property, identifier): + for obj in result['objects']: + if (identifier in obj and identifier in elm.attrib and + elm.attrib[identifier] == obj[identifier]): + for key, value in elm.attrib.items(): + if key in property: + obj[key] = value + + def _append_element(self, elm, result, property, list_property, + identifier): + sub_elm = {} + self._copy_property(elm.attrib, sub_elm, property, list_property) + + for obj in result['objects']: + if (identifier in obj and identifier in elm.attrib and + elm.attrib[identifier] == obj[identifier]): + if self.tag in obj: + obj[self.tag].append(sub_elm) + else: + obj[self.tag] = [sub_elm] + + def start_task_response(self, elm, result): + self.is_TaskResponse = True + result['type'] = 'TaskResponse' + self._copy_property(elm.attrib, result, ['taskId']) + + def end_task_response(self): + self.is_TaskResponse = False + + def start_fault(self, elm, result): + result['type'] = 'Fault' + + def start_status(self, elm, result): + if self.is_TaskResponse: + result['maxSeverity'] = elm.attrib['maxSeverity'] + elif self.is_MoverStatus or self.is_Vdm: + self.elt['maxSeverity'] = elm.attrib['maxSeverity'] + + def start_query_status(self, elm, result): + self.is_QueryStatus = True + result['type'] = 'QueryStatus' + self._copy_property(elm.attrib, result, ['maxSeverity']) + + def end_query_status(self): + self.is_QueryStatus = False + + def start_problem(self, elm, result): + self.elt = {} + properties = ('message', 'messageCode') + + self._copy_property(elm.attrib, self.elt, properties) + result['problems'].append(self.elt) + + def start_description(self, elm, result): + self.elt['Description'] = elm.text + + def start_action(self, elm, result): + self.elt['Action'] = elm.text + + def start_diagnostics(self, elm, result): + self.elt['Diagnostics'] = elm.text + + def start_file_system(self, elm, result): + self.elt = {} + property = ( + 'fileSystem', + 'name', + 'type', + 'storages', + 'volume', + 'dataServicePolicies', + 'internalUse', + ) + list_property = ('storagePools',) + + self._copy_property(elm.attrib, self.elt, property, list_property) + result['objects'].append(self.elt) + + def start_file_system_capacity_info(self, elm, result): + property = ('volumeSize',) + + identifier = 'fileSystem' + + self._append_elm_property(elm, result, property, identifier) + + def start_storage_pool(self, elm, result): + self.elt = {} + property = ('name', 'autoSize', 'usedSize', 'diskType', 'pool', + 'dataServicePolicies', 'virtualProvisioning') + list_property = ('movers',) + + self._copy_property(elm.attrib, self.elt, property, list_property) + result['objects'].append(self.elt) + + def start_system_storage_pool_data(self, elm, result): + property = ('greedy', 'isBackendPool') + + self._copy_property(elm.attrib, self.elt, property) + + def start_mover(self, elm, result): + self.elt = {} + property = ('name', 'host', 'mover', 'role') + list_property = ('ntpServers', 'standbyFors', 'standbys') + + self._copy_property(elm.attrib, self.elt, property, list_property) + result['objects'].append(self.elt) + + def start_mover_status(self, elm, result): + self.is_MoverStatus = True + + property = ('version', 'csTime', 'clock', 'timezone', 'uptime') + + identifier = 'mover' + + self._append_elm_property(elm, result, property, identifier) + + def end_mover_status(self): + self.is_MoverStatus = False + + def start_mover_dns_domain(self, elm, result): + property = ('name', 'protocol') + list_property = ('servers',) + + identifier = 'mover' + + self._append_element(elm, result, property, list_property, identifier) + + def start_mover_interface(self, elm, result): + property = ( + 'name', + 'device', + 'up', + 'ipVersion', + 'netMask', + 'ipAddress', + 'vlanid', + ) + + identifier = 'mover' + + self._append_element(elm, result, property, None, identifier) + + def start_logical_network_device(self, elm, result): + property = ('name', 'type', 'speed') + list_property = ('interfaces',) + identifier = 'mover' + + self._append_element(elm, result, property, list_property, identifier) + + def start_vdm(self, elm, result): + self.is_Vdm = True + + self.elt = {} + property = ('name', 'state', 'mover', 'vdm') + + self._copy_property(elm.attrib, self.elt, property) + result['objects'].append(self.elt) + + def end_vdm(self): + self.is_Vdm = False + + def start_interfaces(self, elm, result): + self.is_Interfaces = True + self.elt['Interfaces'] = [] + + def end_interfaces(self): + self.is_Interfaces = False + + def start_li(self, elm, result): + if self.is_CifsServers: + self.elt['CifsServers'].append(elm.text) + elif self.is_Aliases: + self.elt['Aliases'].append(elm.text) + elif self.is_Interfaces: + self.elt['Interfaces'].append(elm.text) + + def start_cifs_server(self, elm, result): + self.elt = {} + property = ('type', 'localUsers', 'name', 'mover', 'moverIdIsVdm') + + list_property = ('interfaces',) + + self._copy_property(elm.attrib, self.elt, property, list_property) + result['objects'].append(self.elt) + + def start_aliases(self, elm, result): + self.is_Aliases = True + self.elt['Aliases'] = [] + + def end_aliases(self): + self.is_Aliases = False + + def start_w2k_server_data(self, elm, result): + property = ('domain', 'compName', 'domainJoined') + + self._copy_property(elm.attrib, self.elt, property) + + def start_cifs_share(self, elm, result): + self.elt = {} + property = ('path', 'fileSystem', 'name', 'mover', 'moverIdIsVdm') + + self._copy_property(elm.attrib, self.elt, property) + result['objects'].append(self.elt) + + def start_cifs_servers(self, elm, result): + self.is_CifsServers = True + self.elt['CifsServers'] = [] + + def end_cifs_servers(self): + self.is_CifsServers = False + + def start_checkpoint(self, elm, result): + self.elt = {} + property = ('checkpointOf', 'name', 'checkpoint', 'state') + + self._copy_property(elm.attrib, self.elt, property) + result['objects'].append(self.elt) + + def start_mount(self, elm, result): + self.elt = {} + property = ('fileSystem', 'path', 'mover', 'moverIdIsVdm') + + self._copy_property(elm.attrib, self.elt, property) + result['objects'].append(self.elt) diff --git a/manila/tests/share/drivers/dell_emc/plugins/vmax/__init__.py b/manila/tests/share/drivers/dell_emc/plugins/vmax/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/manila/tests/share/drivers/dell_emc/plugins/vmax/fakes.py b/manila/tests/share/drivers/dell_emc/plugins/vmax/fakes.py new file mode 100644 index 0000000000..e99a95f816 --- /dev/null +++ b/manila/tests/share/drivers/dell_emc/plugins/vmax/fakes.py @@ -0,0 +1,1563 @@ +# Copyright (c) 2016 Dell Inc. or its subsidiaries. +# 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_utils import units + +from manila.common import constants as const +from manila.share import configuration as conf +from manila.tests import fake_share + + +def query(func): + def inner(*args, **kwargs): + return ( + '' + '' + + func(*args, **kwargs) + + '' + ) + + return inner + + +def start_task(func): + def inner(*args, **kwargs): + return ( + '' + '' + + func(*args, **kwargs) + + '') + + return inner + + +def response(func): + def inner(*args, **kwargs): + return ( + '' + '' + + func(*args, **kwargs) + + '' + ) + + return inner + + +class FakeData(object): + # Share information + share_id = '7cf7c200_d3af_4e05_b87e_9167c95df4f9' + host = 'HostA@BackendB#fake_pool_name' + share_name = share_id + share_size = 10 + new_size = 20 + src_share_name = '7cf7c200_d3af_4e05_b87e_9167c95df4f0' + + # Snapshot information + snapshot_name = 'de4c9050-e2f9-4ce1-ade4-5ed0c9f26451' + src_snap_name = 'de4c9050-e2f9-4ce1-ade4-5ed0c9f26452' + snapshot_id = 'fake_snap_id' + snapshot_size = 10 * units.Ki + + # Share network information + share_network_id = 'c5b3a865-56d0-4d88-abe5-879965e099c9' + cidr = '192.168.1.0/24' + segmentation_id = 100 + network_allocations_id1 = '132dbb10-9a36-46f2-8d89-3d909830c356' + network_allocations_id2 = '7eabdeed-bad2-46ea-bd0f-a33884c869e0' + network_allocations_ip1 = '192.168.1.1' + network_allocations_ip2 = '192.168.1.2' + domain_name = 'fake_domain' + domain_user = 'administrator' + domain_password = 'password' + dns_ip_address = '192.168.1.200' + + # Share server information + share_server_id = '56aafd02-4d44-43d7-b784-57fc88167224' + + # Filesystem information + filesystem_name = share_name + filesystem_id = 'fake_filesystem_id' + filesystem_size = 10 * units.Ki + filesystem_new_size = 20 * units.Ki + + # Mountpoint information + path = '/' + share_name + + # Mover information + mover_name = 'server_2' + mover_id = 'fake_mover_id' + interface_name1 = network_allocations_id1[-12:] + interface_name2 = network_allocations_id2[-12:] + long_interface_name = network_allocations_id1 + net_mask = '255.255.255.0' + device_name = 'cge-1-0' + interconnect_id = '2001' + + # VDM information + vdm_name = share_server_id + vdm_id = 'fake_vdm_id' + + # Pool information + pool_name = 'fake_pool_name' + pool_id = 'fake_pool_id' + pool_used_size = 20480 + pool_total_size = 511999 + + # NFS share access information + rw_hosts = ['192.168.1.1', '192.168.1.2'] + ro_hosts = ['192.168.1.3', '192.168.1.4'] + nfs_host_ip = '192.168.1.5' + + fake_output = '' + + fake_error_msg = 'fake error message' + + emc_share_backend = 'vmax' + emc_nas_server = '192.168.1.20' + emc_nas_login = 'fakename' + emc_nas_password = 'fakepassword' + share_backend_name = 'EMC_NAS_Storage' + + cifs_access = """ + 1478607389: SMB:11: Unix user 'Guest' UID=32769 + 1478607389: SMB:10: FindUserUid:Access_Password 'Guest',1=0x8001 T=0 + 1478607389: SHARE: 6: ALLOWED:fullcontrol:S-1-5-15-3399d125-6dcdf5f4 + 1478607389: SMB:11: Unix user 'Administrator' UID=32768 + 1478607389: SMB:10: FindUserUid:Access_Password 'Administrator', + 1478607389: SHARE: 6: ALLOWED:fullcontrol:S-1-5-15-3399d125 + """ + + +class StorageObjectTestData(object): + def __init__(self): + self.share_name = FakeData.share_name + + self.filesystem_name = FakeData.filesystem_name + self.filesystem_id = FakeData.filesystem_id + self.filesystem_size = 10 * units.Ki + self.filesystem_new_size = 20 * units.Ki + + self.path = FakeData.path + + self.snapshot_name = FakeData.snapshot_name + self.snapshot_id = FakeData.snapshot_id + self.snapshot_size = 10 * units.Ki + + self.src_snap_name = FakeData.src_snap_name + self.src_fileystems_name = FakeData.src_share_name + + self.mover_name = FakeData.mover_name + self.mover_id = FakeData.mover_id + self.vdm_name = FakeData.vdm_name + self.vdm_id = FakeData.vdm_id + + self.pool_name = FakeData.pool_name + self.pool_id = FakeData.pool_id + self.pool_used_size = FakeData.pool_used_size + self.pool_total_size = FakeData.pool_total_size + + self.interface_name1 = FakeData.interface_name1 + self.interface_name2 = FakeData.interface_name2 + self.long_interface_name = FakeData.long_interface_name + self.ip_address1 = FakeData.network_allocations_ip1 + self.ip_address2 = FakeData.network_allocations_ip2 + self.net_mask = FakeData.net_mask + self.vlan_id = FakeData.segmentation_id + + self.cifs_server_name = FakeData.vdm_name + + self.domain_name = FakeData.domain_name + self.domain_user = FakeData.domain_user + self.domain_password = FakeData.domain_password + self.dns_ip_address = FakeData.dns_ip_address + + self.device_name = FakeData.device_name + + self.interconnect_id = FakeData.interconnect_id + + self.rw_hosts = FakeData.rw_hosts + self.ro_hosts = FakeData.ro_hosts + self.nfs_host_ip = FakeData.nfs_host_ip + + self.fake_output = FakeData.fake_output + + @response + def resp_get_error(self): + return ( + '' + '' + 'Fake description.' + 'Fake action.' + 'Fake diagnostics.' + '' + '' + 'Fake description.' + 'Fake action.' + 'Fake diagnostics.' + '' + ' ' + ) + + @response + def resp_get_without_value(self): + return ( + '' + ) + + @response + def resp_task_succeed(self): + return ( + '' + '' + '' + ) + + @response + def resp_task_error(self): + return ( + '' + '' + '' + ) + + @response + def resp_invalid_mover_id(self): + return ( + '' + '' + 'The Mover ID supplied with the request is invalid.' + '' + 'Refer to the XML API v2 schema/documentation and correct ' + 'your user program logic.' + ' Exception tag: 14fb692e556 Exception ' + 'message: com.emc.nas.ccmd.common.MessageInstanceImpl@5004000d ' + '' + '' + ' ' + ) + + @response + def resp_need_retry(self): + return ('' + '' + '' + ' fake desp. ' + 'fake action ' + '') + + @start_task + def req_fake_start_task(self): + return '' + + +class FileSystemTestData(StorageObjectTestData): + def __init__(self): + super(FileSystemTestData, self).__init__() + + @start_task + def req_create_on_vdm(self): + return ( + '' + '' + '' + '' + % {'name': self.filesystem_name, + 'id': self.vdm_id, + 'pool_id': self.pool_id, + 'size': self.filesystem_size} + ) + + @start_task + def req_create_on_mover(self): + return ( + '' + '' + '' + '' + % {'name': self.filesystem_name, + 'id': self.mover_id, + 'pool_id': self.pool_id, + 'size': self.filesystem_size} + ) + + @response + def resp_create_but_already_exist(self): + return ( + ' ' + '' + '' + '' + '' + '' + '' + ' ' + ) + + @start_task + def req_delete(self): + return ( + '' % + {'id': self.filesystem_id} + ) + + @response + def resp_delete_but_failed(self): + return ( + '' + '' + 'The file system ID supplied with the request is ' + 'invalid.' + 'Refer to the XML API v2 schema/documentation and correct ' + 'your user program logic.' + ' Exception tag: 14fb6b6a7b8 Exception ' + 'message: com.emc.nas.ccmd.common.MessageInstanceImpl@5004000e ' + '' + '' + ' ' + ) + + @start_task + def req_extend(self): + return ( + '' + '' + '' % + {'id': self.filesystem_id, + 'pool_id': self.pool_id, + 'size': self.filesystem_new_size - self.filesystem_size} + ) + + @response + def resp_extend_but_error(self): + return ( + '' + '' + 'Fake description.' + 'Fake action.' + ' Fake diagnostics.' + '' + ' ' + ) + + @query + def req_get(self): + return ( + '' + '' + '' + '' % + {'name': self.filesystem_name} + ) + + @response + def resp_get_succeed(self): + return ( + '' + '' + '' + '' + '' % + {'name': self.filesystem_name, + 'id': self.filesystem_id, + 'size': self.filesystem_size, + 'pool_id': self.pool_id} + ) + + @response + def resp_get_but_miss_property(self): + return ( + '' + '' + '' + '' + '' % + {'name': self.filesystem_name, + 'id': self.filesystem_id, + 'size': self.filesystem_size, + 'pool_id': self.pool_id} + ) + + @response + def resp_get_but_not_found(self): + return ( + '' + '' + 'The query may be incomplete because some of the ' + 'Celerra components are unavailable or do not exist. Another ' + 'reason may be application error. ' + 'If the entire Celerra is functioning correctly, ' + 'check your client application logic. ' + 'File system not found.' + '' + '' + 'The query may be incomplete because some of the ' + 'Celerra components are unavailable or do not exist. Another ' + 'reason may be application error.' + 'If the entire Celerra is functioning correctly, ' + 'check your client application logic.' + 'Migration file system not found.' + '' + ' ' + ) + + def cmd_create_from_ckpt(self): + return [ + 'env', 'NAS_DB=/nas', '/nas/bin/nas_fs', + '-name', self.filesystem_name, + '-type', 'uxfs', + '-create', + 'samesize=' + self.src_fileystems_name, + 'pool=' + self.pool_name, + 'storage=SINGLE', + 'worm=off', + '-thin', 'no', + '-option', 'slice=y', + ] + + def cmd_copy_ckpt(self): + session_name = self.filesystem_name + ':' + self.src_snap_name + + return [ + 'env', 'NAS_DB=/nas', '/nas/bin/nas_copy', + '-name', session_name[0:63], + '-source', '-ckpt', self.src_snap_name, + '-destination', '-fs', self.filesystem_name, + '-interconnect', "id=" + self.interconnect_id, + '-overwrite_destination', + '-full_copy', + ] + + output_copy_ckpt = "OK" + error_copy_ckpt = "ERROR" + + def cmd_nas_fs_info(self): + return [ + 'env', 'NAS_DB=/nas', '/nas/bin/nas_fs', + '-info', self.filesystem_name, + ] + + def output_info(self): + return ( + """output = id = 515 + name = %(share_name)s + acl = 0 + in_use = True + type = uxfs + worm = off + volume = v993 + deduplication = Off + thin_storage = True + tiering_policy = Auto-Tier/Optimize Pool + compressed= False + mirrored = False + ckpts = %(ckpt)s + stor_devs = FNM00124500890-004B + disks = d7 + disk=d7 fakeinfo""" % + {'share_name': self.filesystem_name, + 'ckpt': self.snapshot_name}) + + def cmd_delete(self): + return [ + 'env', 'NAS_DB=/nas', '/nas/bin/nas_fs', + '-delete', self.snapshot_name, + '-Force', + ] + + +class SnapshotTestData(StorageObjectTestData): + def __init__(self): + super(SnapshotTestData, self).__init__() + + @start_task + def req_create(self): + return ( + '' + '' + '' + % {'fsid': self.filesystem_id, + 'name': self.snapshot_name, + 'pool_id': self.pool_id} + ) + + @start_task + def req_create_with_size(self): + return ( + '' + '' + '' + '' + % {'fsid': self.filesystem_id, + 'name': self.snapshot_name, + 'pool_id': self.pool_id, + 'size': self.snapshot_size} + ) + + @response + def resp_create_but_already_exist(self): + return ( + '' + '' + '' + '' + '' + '' + ) + + @query + def req_get(self): + return ( + '' + '' + % {'name': self.snapshot_name} + ) + + @response + def resp_get_succeed(self): + return ( + '' + '' + % {'name': self.snapshot_name, + 'fs_id': self.filesystem_id, + 'snap_id': self.snapshot_id} + ) + + @start_task + def req_delete(self): + return ( + '' % + {'id': self.snapshot_id} + ) + + +class MountPointTestData(StorageObjectTestData): + def __init__(self): + super(MountPointTestData, self).__init__() + + @start_task + def req_create(self, mover_id, is_vdm=True): + return ( + '' + '' + '' % + {'path': self.path, + 'fs_id': self.filesystem_id, + 'mover_id': mover_id, + 'is_vdm': 'true' if is_vdm else 'false'} + ) + + @response + def resp_create_but_already_exist(self): + return ( + '' + '' + '' + ' ' + '' + '' + ' ') + + @start_task + def req_delete(self, mover_id, is_vdm=True): + return ( + '' % + {'path': self.path, + 'mover_id': mover_id, + 'is_vdm': 'true' if is_vdm else 'false'} + ) + + @response + def resp_delete_but_nonexistent(self): + return ( + '' + ' ' + ' ' + '' + '' + ' ' + ) + + @query + def req_get(self, mover_id, is_vdm=True): + return ( + '' % + {'mover_id': mover_id, + 'is_vdm': 'true' if is_vdm else 'false'} + ) + + @response + def resp_get_succeed(self, mover_id, is_vdm=True): + return ( + '' + '' + '' + '' + % {'path': self.path, + 'fsID': self.filesystem_id, + 'mover_id': mover_id, + 'is_vdm': 'true' if is_vdm else 'false'} + ) + + def cmd_server_mount(self, mode): + return [ + 'env', 'NAS_DB=/nas', '/nas/bin/server_mount', self.vdm_name, + '-option', mode, + self.filesystem_name, + self.path, + ] + + def cmd_server_umount(self): + return [ + 'env', 'NAS_DB=/nas', '/nas/bin/server_umount', self.vdm_name, + '-perm', self.snapshot_name, + ] + + +class VDMTestData(StorageObjectTestData): + def __init__(self): + super(VDMTestData, self).__init__() + + @start_task + def req_create(self): + return ( + '' % + {'mover_id': self.mover_id, 'vdm_name': self.vdm_name} + ) + + @response + def resp_create_but_already_exist(self): + return ( + '' + '' + '' + 'Duplicate name specified' + 'Specify a unqiue name' + '' + '' + 'Duplicate name specified' + 'Specify a unqiue name' + '' + '' + ' ' + ) + + @query + def req_get(self): + return '' + + @response + def resp_get_succeed(self, name=None): + if not name: + name = self.vdm_name + + return ( + '' + '' + '' + '
  • %(interface1)s
  • %(interface2)s
  • ' + '
    ' % + {'vdm_name': name, + 'vdm_id': self.vdm_id, + 'mover_id': self.mover_id, + 'interface1': self.interface_name1, + 'interface2': self.interface_name2} + ) + + @response + def resp_get_but_not_found(self): + return ( + '' + ) + + @start_task + def req_delete(self): + return '' % {'vdmid': self.vdm_id} + + def cmd_attach_nfs_interface(self): + return [ + 'env', 'NAS_DB=/nas', '/nas/bin/nas_server', + '-vdm', self.vdm_name, + '-attach', self.interface_name2, + ] + + def cmd_detach_nfs_interface(self): + return [ + 'env', 'NAS_DB=/nas', '/nas/bin/nas_server', + '-vdm', self.vdm_name, + '-detach', self.interface_name2, + ] + + def cmd_get_interfaces(self): + return [ + 'env', 'NAS_DB=/nas', '/nas/bin/nas_server', + '-i', + '-vdm', self.vdm_name, + ] + + def output_get_interfaces(self, cifs_interface=FakeData.interface_name1, + nfs_interface=FakeData.interface_name2): + return ( + """id = %(vdmid)s + name = %(name)s + acl = 0 + type = vdm + server = server_2 + rootfs = root_fs_vdm_vdm-fakeid + I18N mode = UNICODE + mountedfs = + member_of = + status : + defined = enabled + actual = loaded, active + Interfaces to services mapping: + interface=%(nfs_if_name)s :vdm + interface=%(cifs_if_name)s :cifs""" % + {'vdmid': self.vdm_id, + 'name': self.vdm_name, + 'nfs_if_name': nfs_interface, + 'cifs_if_name': cifs_interface} + ) + + +class PoolTestData(StorageObjectTestData): + def __init__(self): + super(PoolTestData, self).__init__() + + @query + def req_get(self): + return ( + '' + ) + + @response + def resp_get_succeed(self, name=None, id=None): + if not name: + name = self.pool_name + if not id: + id = self.pool_id + return ( + '' + '' + '' + '' + '' + '' + '' % + {'name': name, + 'id': id, + 'pool_used_size': self.pool_used_size, + 'pool_total_size': self.pool_total_size} + ) + + +class MoverTestData(StorageObjectTestData): + def __init__(self): + super(MoverTestData, self).__init__() + + @query + def req_get_ref(self): + return ( + '' + '' + '' + ) + + @response + def resp_get_ref_succeed(self, name=None): + if not name: + name = self.mover_name + return ( + '' + '' + 'The query may be incomplete because some of the ' + 'Celerra components are unavailable or do not exist. Another ' + 'reason may be application error.' + 'If the entire Celerra is functioning correctly, ' + 'check your client application logic.' + 'Standby Data Mover server_2.faulted.server_3 is ' + 'out of service.' + '' + '' + '' + '' % + {'name': name, 'id': self.mover_id} + ) + + @query + def req_get(self): + return ( + '' + '' + '' % + {'id': self.mover_id} + ) + + @response + def resp_get_succeed(self, name=None): + if not name: + name = self.mover_name + return ( + '' + '' + '' + '' + '' + '' + '' + '' + '' + % {'id': self.mover_id, + 'name': name, + 'long_interface_name': self.long_interface_name[:31], + 'interface_name1': self.interface_name1, + 'interface_name2': self.interface_name2} + ) + + @start_task + def req_create_interface(self, + if_name=FakeData.interface_name1, + ip=FakeData.network_allocations_ip1): + return ( + '' + % {'if_name': if_name, + 'vlan': self.vlan_id, + 'ip': ip, + 'mover_id': self.mover_id, + 'device_name': self.device_name, + 'net_mask': self.net_mask} + ) + + @response + def resp_create_interface_but_name_already_exist(self): + return ( + '' + '' + 'Duplicate name specified' + 'Specify a unqiue name' + '' + '' % {'interface_name': self.interface_name1} + ) + + @response + def resp_create_interface_but_ip_already_exist(self): + return ( + '' + '' + '' + '' + '' % {'ip': self.ip_address1} + ) + + @response + def resp_create_interface_with_conflicted_vlan_id(self): + return ( + '' + '' + 'The operation cannot complete because other ' + 'interfaces on the same subnet are in a different VLAN. ' + 'The Data Mover requires all interfaces in the same subnet ' + 'to be in the same VLAN.' + 'Specify a VLAN to match other interfaces in the same ' + 'subnet. To move multiple interfaces to a different VLAN, ' + 'first set the VLAN id on each interface to 0, ' + 'and then set their VLAN id\'s to the new VLAN number.' + '' + '' + ) + + @start_task + def req_delete_interface(self, ip=FakeData.network_allocations_ip1): + + return ( + '' % + {'ip': ip, + 'mover_id': self.mover_id, } + ) + + @response + def resp_delete_interface_but_nonexistent(self): + return ( + '' + '' + '' + '' + '' + '' + ) + + def cmd_get_interconnect_id(self): + return [ + 'env', 'NAS_DB=/nas', '/nas/bin/nas_cel', + '-interconnect', '-l', + ] + + def output_get_interconnect_id(self): + return ( + 'id name source_server destination_system destination_server\n' + '%(id)s loopback %(src_server)s nas149 %(dest_server)s\n' % + {'id': self.interconnect_id, + 'src_server': self.mover_name, + 'dest_server': self.mover_name} + ) + + def cmd_get_physical_devices(self): + return [ + 'env', 'NAS_DB=/nas', '/nas/bin/server_sysconfig', + self.mover_name, '-pci', + ] + + def output_get_physical_devices(self): + return ( + 'server_2 : PCI DEVICES:\n' + 'On Board:\n' + ' PMC QE8 Fibre Channel Controller\n' + ' 0: fcp-0-0 IRQ: 20 addr: 5006016047a00245\n' + ' 0: fcp-0-1 IRQ: 21 addr: 5006016147a00245\n' + ' 0: fcp-0-2 IRQ: 22 addr: 5006016247a00245\n' + ' 0: fcp-0-3 IRQ: 23 addr: 5006016347a00245\n' + ' Broadcom Gigabit Ethernet Controller\n' + ' 0: cge-1-0 IRQ: 24\n' + ' speed=auto duplex=auto txflowctl=disable rxflowctl=disable\n' + ' Link: Up\n' + ' 0: cge-1-1 IRQ: 25\n' + ' speed=auto duplex=auto txflowctl=disable rxflowctl=disable\n' + ' Link: Down\n' + ' 0: cge-1-2 IRQ: 26\n' + ' speed=auto duplex=auto txflowctl=disable rxflowctl=disable\n' + ' Link: Down\n' + ' 0: cge-1-3 IRQ: 27\n' + ' speed=auto duplex=auto txflowctl=disable rxflowctl=disable\n' + ' Link: Up\n' + 'Slot: 4\n' + ' PLX PCI-Express Switch Controller\n' + ' 1: PLX PEX8648 IRQ: 10\n' + ) + + +class DNSDomainTestData(StorageObjectTestData): + def __init__(self): + super(DNSDomainTestData, self).__init__() + + @start_task + def req_create(self): + return ( + '' % + {'mover_id': self.mover_id, + 'domain_name': self.domain_name, + 'server_ips': self.dns_ip_address} + ) + + @start_task + def req_delete(self): + return ( + '' % + {'mover_id': self.mover_id, + 'domain_name': self.domain_name} + ) + + +class CIFSServerTestData(StorageObjectTestData): + def __init__(self): + super(CIFSServerTestData, self).__init__() + + @start_task + def req_create(self, mover_id, is_vdm=True): + return ( + '' + '' + '
  • %(alias)s
  • ' + '' + '
    ' + % {'ip': self.ip_address1, + 'comp_name': self.cifs_server_name, + 'name': self.cifs_server_name[-14:], + 'mover_id': mover_id, + 'alias': self.cifs_server_name[-12:], + 'domain_user': self.domain_user, + 'domain_password': self.domain_password, + 'domain': self.domain_name, + 'is_vdm': 'true' if is_vdm else 'false'} + ) + + @query + def req_get(self, mover_id, is_vdm=True): + return ( + '' + '' + '' % + {'mover_id': mover_id, + 'is_vdm': 'true' if is_vdm else 'false'} + ) + + @response + def resp_get_succeed(self, mover_id, is_vdm, join_domain, + cifs_server_name=None): + if cifs_server_name is None: + cifs_server_name = self.cifs_server_name + return ( + '' + '' + '
  • %(alias)s
  • ' + % {'mover_id': mover_id, + 'cifsserver': self.cifs_server_name[-14:], + 'ip': self.ip_address1, + 'is_vdm': 'true' if is_vdm else 'false', + 'alias': self.cifs_server_name[-12:], + 'domain': self.domain_name, + 'join_domain': 'true' if join_domain else 'false', + 'comp_name': cifs_server_name} + ) + + @response + def resp_get_without_interface(self, mover_id, is_vdm, join_domain): + return ( + '' + '' + '
  • %(alias)s
  • ' + '
    ' + % {'mover_id': mover_id, + 'cifsserver': self.cifs_server_name[-14:], + 'is_vdm': 'true' if is_vdm else 'false', + 'alias': self.cifs_server_name[-12:], + 'domain': self.domain_name, + 'join_domain': 'true' if join_domain else 'false', + 'comp_name': self.cifs_server_name} + ) + + @start_task + def req_modify(self, mover_id, is_vdm=True, join_domain=False): + return ( + '' + '' + '' + % {'mover_id': mover_id, + 'is_vdm': 'true' if is_vdm else 'false', + 'join_domain': 'true' if join_domain else 'false', + 'cifsserver': self.cifs_server_name[-14:], + 'username': self.domain_user, + 'pw': self.domain_password} + ) + + @response + def resp_modify_but_already_join_domain(self): + return ( + ' ' + '' + 'Fake description' + 'Fake action.' + '' + ' ' + ) + + @response + def resp_modify_but_unjoin_domain(self): + return ( + ' ' + '' + 'Fake description' + 'Fake action.' + '' + ' ' + ) + + @start_task + def req_delete(self, mover_id, is_vdm=True): + return ( + '' + % {'mover_id': mover_id, + 'is_vdm': 'true' if is_vdm else 'false', + 'cifsserver': self.cifs_server_name[-14:]} + ) + + +class CIFSShareTestData(StorageObjectTestData): + def __init__(self): + super(CIFSShareTestData, self).__init__() + + @start_task + def req_create(self, mover_id, is_vdm=True): + return ( + '' + '' + '
  • %(cifsserver)s
  • ' + '
    ' % + {'path': '/' + self.share_name, + 'share_name': self.share_name, + 'mover_id': mover_id, + 'is_vdm': 'true' if is_vdm else 'false', + 'cifsserver': self.cifs_server_name[-14:]} + ) + + @start_task + def req_delete(self, mover_id, is_vdm=True): + return ( + '' + '
  • %(cifsserver)s
  • ' + '
    ' % + {'share_name': self.share_name, + 'mover_id': mover_id, + 'is_vdm': 'true' if is_vdm else 'false', + 'cifsserver': self.cifs_server_name[-12:]} + ) + + @query + def req_get(self): + return '' % self.share_name + + @response + def resp_get_succeed(self, mover_id, is_vdm=True): + return ( + '' + '' + '
  • %(alias)s
  • ' + '
    ' + '
    ' % + {'path': self.path, + 'fsid': self.filesystem_id, + 'name': self.share_name, + 'moverid': mover_id, + 'is_vdm': 'true' if is_vdm else 'false', + 'alias': self.cifs_server_name[-12:]} + ) + + def cmd_disable_access(self): + cmd_str = 'sharesd %s set noaccess' % self.share_name + return [ + 'env', 'NAS_DB=/nas', + '/nas/bin/.server_config', self.vdm_name, + '-v', '%s' % cmd_str, + ] + + def cmd_change_access(self, access_level=const.ACCESS_LEVEL_RW, + action='grant', user=None): + if user is None: + user = self.domain_user + account = user + '@' + self.domain_name + + if access_level == const.ACCESS_LEVEL_RW: + str_access = 'fullcontrol' + else: + str_access = 'read' + + allow_str = ( + 'sharesd %(share_name)s %(action)s %(account)s=%(access)s' + % {'share_name': self.share_name, + 'action': action, + 'account': account, + 'access': str_access} + ) + return [ + 'env', 'NAS_DB=/nas', + '/nas/bin/.server_config', self.vdm_name, + '-v', '%s' % allow_str, + ] + + def cmd_get_access(self): + get_str = 'sharesd %s dump' % self.share_name + return [ + 'env', 'NAS_DB=/nas', + '/nas/bin/.server_config', self.vdm_name, + '-v', '%s' % get_str, + ] + + def output_allow_access(self): + return ( + "Command succeeded: :3 sharesd %(share)s grant " + "%(user)s@%(domain)s=fullcontrol" + % {'share': self.share_name, + 'user': self.domain_user, + 'domain': self.domain_name} + ) + + def output_allow_access_but_duplicate_ace(self): + return ( + '%(vdm_name)s : commands processed: 1' + 'output is complete' + '1443422844: SMB: 6: ACE for %(domain)s\\%(user)s ' + 'unchanged' + '1443422844: ADMIN: 3: ' + 'Command failed: :23 ' + 'sharesd %(share)s grant %(user)s@%(domain)s=read' + 'Error 4020: %(vdm_name)s : failed to complete command"' + % {'share': self.share_name, + 'user': self.domain_user, + 'domain': self.domain_name, + 'vdm_name': self.vdm_name} + ) + + def output_deny_access_but_no_ace(self): + return ( + '%(vdm_name)s : commands processed: 1' + 'output is complete' + '1443515516: SMB: 6: No ACE found for %(domain)s\\%(user)s ' + '1443515516: ADMIN: 3: ' + 'Command failed: :26 ' + 'sharesd %(share)s revoke %(user)s@%(domain)s=read' + 'Error 4020: %(vdm_name)s : failed to complete command"' + % {'share': self.share_name, + 'user': self.domain_user, + 'domain': self.domain_name, + 'vdm_name': self.vdm_name} + ) + + def output_deny_access_but_no_user_found(self): + return ( + '%(vdm_name)s : commands processed: 1' + 'output is complete' + '1443520322: SMB: 6: Cannot get mapping for %(domain)s\\%(user)s ' + '1443520322: ADMIN: 3: ' + 'Command failed: :26 ' + 'sharesd %(share)s revoke %(user)s@%(domain)s=read' + 'Error 4020: %(vdm_name)s : failed to complete command"' + % {'share': self.share_name, + 'user': self.domain_user, + 'domain': self.domain_name, + 'vdm_name': self.vdm_name} + ) + + +class NFSShareTestData(StorageObjectTestData): + def __init__(self): + super(NFSShareTestData, self).__init__() + + def cmd_create(self): + default_access = 'access=-0.0.0.0/0.0.0.0' + return [ + 'env', 'NAS_DB=/nas', '/nas/bin/server_export', self.vdm_name, + '-option', default_access, + self.path, + ] + + def output_create(self): + return "%s : done" % self.vdm_name + + def cmd_get(self): + return [ + 'env', 'NAS_DB=/nas', '/nas/bin/server_export', self.vdm_name, + '-P', 'nfs', + '-list', self.path, + ] + + def output_get_succeed(self, rw_hosts, ro_hosts): + if rw_hosts and ro_hosts: + return ( + '%(mover_name)s :\nexport "%(path)s" ' + 'access=-0.0.0.0/0.0.0.0:%(host)s root=%(host)s ' + 'rw=%(rw_host)s ro=%(ro_host)s\n' + % {'mover_name': self.vdm_name, + 'path': self.path, + 'host': ":".join(rw_hosts + ro_hosts), + 'rw_host': ":".join(rw_hosts), + 'ro_host': ":".join(ro_hosts)} + ) + elif rw_hosts: + return ( + '%(mover_name)s :\nexport "%(path)s" ' + 'access=-0.0.0.0/0.0.0.0:%(host)s root=%(host)s ' + 'rw=%(rw_host)s\n' + % {'mover_name': self.vdm_name, + 'host': ":".join(rw_hosts), + 'path': self.path, + 'rw_host': ":".join(rw_hosts)} + ) + elif ro_hosts: + return ( + '%(mover_name)s :\nexport "%(path)s" ' + 'access=-0.0.0.0/0.0.0.0:%(host)s root=%(host)s ' + 'ro=%(ro_host)s\n' + % {'mover_name': self.vdm_name, + 'host': ":".join(ro_hosts), + 'path': self.path, + 'ro_host': ":".join(ro_hosts)} + ) + else: + return ( + '%(mover_name)s :\nexport "%(path)s" ' + 'access=-0.0.0.0/0.0.0.0\n' + % {'mover_name': self.vdm_name, + 'path': self.path} + ) + + def output_get_but_not_found(self): + return ( + '%(mover_name)s : \nError 2: %(mover_name)s : ' + 'No such file or directory \n' % {'mover_name': self.vdm_name} + ) + + def cmd_delete(self): + return [ + 'env', 'NAS_DB=/nas', '/nas/bin/server_export', self.vdm_name, + '-unexport', + '-perm', + self.path, + ] + + def output_delete_succeed(self): + return "%s : done" % self.vdm_name + + def output_delete_but_locked(self): + return ("Error 2201: %s : unable to acquire lock(s), try later" + % self.vdm_name) + + def cmd_set_access(self, rw_hosts, ro_hosts): + access_str = ("access=-0.0.0.0/0.0.0.0:%(access_hosts)s," + "root=%(root_hosts)s,rw=%(rw_hosts)s,ro=%(ro_hosts)s" % + {'rw_hosts': ":".join(rw_hosts), + 'ro_hosts': ":".join(ro_hosts), + 'root_hosts': ":".join(rw_hosts + ro_hosts), + 'access_hosts': ":".join(rw_hosts + ro_hosts)}) + + return [ + 'env', 'NAS_DB=/nas', '/nas/bin/server_export', self.vdm_name, + '-ignore', + '-option', access_str, + self.path, + ] + + def output_set_access_success(self): + return "%s : done" % self.vdm_name + + +class FakeEMCShareDriver(object): + def __init__(self): + self.configuration = conf.Configuration(None) + self.configuration.append_config_values = mock.Mock(return_value=0) + self.configuration.emc_share_backend = FakeData.emc_share_backend + self.configuration.vmax_server_container = FakeData.mover_name + self.configuration.emc_nas_server = FakeData.emc_nas_server + self.configuration.emc_nas_login = FakeData.emc_nas_login + self.configuration.emc_nas_password = FakeData.emc_nas_password + self.configuration.share_backend_name = FakeData.share_backend_name + +CIFS_SHARE = fake_share.fake_share( + id=FakeData.share_id, + name=FakeData.share_name, + size=FakeData.share_size, + share_network_id=FakeData.share_network_id, + share_server_id=FakeData.share_server_id, + host=FakeData.host, + share_proto='CIFS') + +NFS_SHARE = fake_share.fake_share( + id=FakeData.share_id, + name=FakeData.share_name, + size=FakeData.share_size, + share_network_id=FakeData.share_network_id, + share_server_id=FakeData.share_server_id, + host=FakeData.host, + share_proto='NFS') + +CIFS_RW_ACCESS = fake_share.fake_access( + access_type='user', + access_to=FakeData.domain_user, + access_level='rw') + +CIFS_RO_ACCESS = fake_share.fake_access( + access_type='user', + access_to=FakeData.domain_user, + access_level='ro') + +NFS_RW_ACCESS = fake_share.fake_access( + access_type='ip', + access_to=FakeData.nfs_host_ip, + access_level='rw') + +NFS_RO_ACCESS = fake_share.fake_access( + access_type='ip', + access_to=FakeData.nfs_host_ip, + access_level='ro') + +SHARE_SERVER = { + 'id': FakeData.share_server_id, + 'share_network': { + 'name': 'fake_share_network', + 'id': FakeData.share_network_id + }, + 'share_network_id': FakeData.share_network_id, + 'backend_details': { + 'share_server_name': FakeData.vdm_name, + 'cifs_if': FakeData.network_allocations_ip1, + 'nfs_if': FakeData.network_allocations_ip2, + } +} + +SERVER_DETAIL = { + 'share_server_name': FakeData.vdm_name, + 'cifs_if': FakeData.network_allocations_ip1, + 'nfs_if': FakeData.network_allocations_ip2, +} + +SECURITY_SERVICE = [ + { + 'type': 'active_directory', + 'domain': FakeData.domain_name, + 'dns_ip': FakeData.dns_ip_address, + 'user': FakeData.domain_user, + 'password': FakeData.domain_password + }, +] + +NETWORK_INFO = { + 'server_id': FakeData.share_server_id, + 'cidr': FakeData.cidr, + 'security_services': [ + {'type': 'active_directory', + 'domain': FakeData.domain_name, + 'dns_ip': FakeData.dns_ip_address, + 'user': FakeData.domain_user, + 'password': FakeData.domain_password}, + ], + 'segmentation_id': FakeData.segmentation_id, + 'network_type': 'vlan', + 'network_allocations': [ + {'id': FakeData.network_allocations_id1, + 'ip_address': FakeData.network_allocations_ip1}, + {'id': FakeData.network_allocations_id2, + 'ip_address': FakeData.network_allocations_ip2} + ] +} + +STATS = dict( + share_backend_name='VMAX', + vendor_name='EMC', + storage_protocol='NFS_CIFS', + driver_version='2.0.0,') diff --git a/manila/tests/share/drivers/dell_emc/plugins/vmax/test_connection.py b/manila/tests/share/drivers/dell_emc/plugins/vmax/test_connection.py new file mode 100644 index 0000000000..db4ef19783 --- /dev/null +++ b/manila/tests/share/drivers/dell_emc/plugins/vmax/test_connection.py @@ -0,0 +1,1636 @@ +# Copyright (c) 2016 Dell Inc. or its subsidiaries. +# 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_log import log + +from manila import exception +from manila.share.drivers.dell_emc.plugins.vmax import connection +from manila.share.drivers.dell_emc.plugins.vmax import connector +from manila.share.drivers.dell_emc.plugins.vmax import object_manager +from manila import test +from manila.tests import fake_share +from manila.tests.share.drivers.dell_emc.plugins.vmax import fakes +from manila.tests.share.drivers.dell_emc.plugins.vmax import utils + +LOG = log.getLogger(__name__) + + +@ddt.ddt +class StorageConnectionTestCase(test.TestCase): + @mock.patch.object(connector.XMLAPIConnector, "_do_setup", mock.Mock()) + def setUp(self): + super(StorageConnectionTestCase, self).setUp() + self.emc_share_driver = fakes.FakeEMCShareDriver() + + self.connection = connection.VMAXStorageConnection(LOG) + + self.pool = fakes.PoolTestData() + self.vdm = fakes.VDMTestData() + self.mover = fakes.MoverTestData() + self.fs = fakes.FileSystemTestData() + self.mount = fakes.MountPointTestData() + self.snap = fakes.SnapshotTestData() + self.cifs_share = fakes.CIFSShareTestData() + self.nfs_share = fakes.NFSShareTestData() + self.cifs_server = fakes.CIFSServerTestData() + self.dns = fakes.DNSDomainTestData() + + with mock.patch.object(connector.XMLAPIConnector, 'request', + mock.Mock()): + self.connection.connect(self.emc_share_driver, None) + + def test_check_for_setup_error(self): + hook = utils.RequestSideEffect() + hook.append(self.mover.resp_get_ref_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + with mock.patch.object(connection.VMAXStorageConnection, + '_get_managed_storage_pools', + mock.Mock()): + self.connection.check_for_setup_error() + + expected_calls = [mock.call(self.mover.req_get_ref())] + xml_req_mock.assert_has_calls(expected_calls) + + def test_check_for_setup_error_with_invalid_mover_name(self): + hook = utils.RequestSideEffect() + hook.append(self.mover.resp_get_error()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.assertRaises(exception.InvalidParameterValue, + self.connection.check_for_setup_error) + + expected_calls = [mock.call(self.mover.req_get_ref())] + xml_req_mock.assert_has_calls(expected_calls) + + @ddt.data({'pool_conf': None, + 'real_pools': ['fake_pool', 'nas_pool'], + 'matched_pool': set()}, + {'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_*'], + 'real_pools': ['fake_pool', 'nas_pool', 'Perf_Pool'], + 'matched_pool': {'fake_pool'}}, + {'pool_conf': ['*pool'], + 'real_pools': ['fake_pool', 'NAS_Pool', 'Perf_POOL'], + 'matched_pool': {'fake_pool'}}, + {'pool_conf': ['nas_pool'], + 'real_pools': ['fake_pool', 'nas_pool', 'perf_pool'], + 'matched_pool': {'nas_pool'}}) + @ddt.unpack + def test__get_managed_storage_pools(self, pool_conf, real_pools, + matched_pool): + with mock.patch.object(object_manager.StoragePool, + 'get_all', + mock.Mock(return_value=('ok', real_pools))): + pool = self.connection._get_managed_storage_pools(pool_conf) + self.assertEqual(matched_pool, pool) + + def test__get_managed_storage_pools_failed_to_get_pool_info(self): + hook = utils.RequestSideEffect() + hook.append(self.pool.resp_get_error()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + pool_conf = fakes.FakeData.pool_name + self.assertRaises(exception.EMCVmaxXMLAPIError, + self.connection._get_managed_storage_pools, + pool_conf) + + expected_calls = [mock.call(self.pool.req_get())] + xml_req_mock.assert_has_calls(expected_calls) + + @ddt.data( + {'pool_conf': ['fake_*'], + 'real_pools': ['nas_pool', 'Perf_Pool']}, + {'pool_conf': ['*pool'], + 'real_pools': ['NAS_Pool', 'Perf_POOL']}, + {'pool_conf': ['nas_pool'], + 'real_pools': ['fake_pool', 'perf_pool']}, + ) + @ddt.unpack + def test__get_managed_storage_pools_without_matched_pool(self, pool_conf, + real_pools): + with mock.patch.object(object_manager.StoragePool, + 'get_all', + mock.Mock(return_value=('ok', real_pools))): + self.assertRaises(exception.InvalidParameterValue, + self.connection._get_managed_storage_pools, + pool_conf) + + def test_create_cifs_share(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + hook.append(self.pool.resp_get_succeed()) + hook.append(self.fs.resp_task_succeed()) + hook.append(self.cifs_share.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append() + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + location = self.connection.create_share(None, share, share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + mock.call(self.pool.req_get()), + mock.call(self.fs.req_create_on_vdm()), + mock.call(self.cifs_share.req_create(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [mock.call(self.cifs_share.cmd_disable_access(), True)] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + self.assertEqual([r'\\192.168.1.1\%s' % share['name']], location, + 'CIFS export path is incorrect') + + def test_create_nfs_share(self): + share_server = fakes.SHARE_SERVER + share = fakes.NFS_SHARE + + hook = utils.RequestSideEffect() + hook.append(self.pool.resp_get_succeed()) + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.fs.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.nfs_share.output_create()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + location = self.connection.create_share(None, share, share_server) + + expected_calls = [ + mock.call(self.pool.req_get()), + mock.call(self.vdm.req_get()), + mock.call(self.fs.req_create_on_vdm()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [mock.call(self.nfs_share.cmd_create(), True)] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + self.assertEqual('192.168.1.2:/%s' % share['name'], location, + 'NFS export path is incorrect') + + def test_create_cifs_share_without_share_server(self): + share = fakes.CIFS_SHARE + + self.assertRaises(exception.InvalidInput, + self.connection.create_share, + None, share, None) + + def test_create_cifs_share_without_share_server_name(self): + share = fakes.CIFS_SHARE + share_server = copy.deepcopy(fakes.SHARE_SERVER) + share_server['backend_details']['share_server_name'] = None + + self.assertRaises(exception.EMCVmaxXMLAPIError, + self.connection.create_share, + None, share, share_server) + + def test_create_cifs_share_with_invalide_cifs_server_name(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_error()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.assertRaises(exception.EMCVmaxXMLAPIError, + self.connection.create_share, + None, share, share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + def test_create_cifs_share_without_interface_in_cifs_server(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_without_interface( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + hook.append(self.pool.resp_get_succeed()) + hook.append(self.fs.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.assertRaises(exception.EMCVmaxXMLAPIError, + self.connection.create_share, + None, share, share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + mock.call(self.pool.req_get()), + mock.call(self.fs.req_create_on_vdm()), + + ] + xml_req_mock.assert_has_calls(expected_calls) + + def test_create_cifs_share_without_pool_name(self): + share_server = fakes.SHARE_SERVER + share = fake_share.fake_share(host='HostA@BackendB', + share_proto='CIFS') + + self.assertRaises(exception.InvalidHost, + self.connection.create_share, + None, share, share_server) + + def test_create_cifs_share_from_snapshot(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + snapshot = fake_share.fake_snapshot( + name=fakes.FakeData.src_snap_name, + share_name=fakes.FakeData.src_share_name, + share_id=fakes.FakeData.src_share_name, + id=fakes.FakeData.src_snap_name) + + hook = utils.RequestSideEffect() + hook.append(self.fs.resp_get_succeed()) + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + hook.append(self.cifs_share.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.mover.output_get_interconnect_id()) + ssh_hook.append() + ssh_hook.append() + ssh_hook.append(self.fs.output_copy_ckpt) + ssh_hook.append(self.fs.output_info()) + ssh_hook.append() + ssh_hook.append() + ssh_hook.append() + ssh_hook.append() + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + location = self.connection.create_share_from_snapshot( + None, share, snapshot, share_server) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + mock.call(self.cifs_share.req_create(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.mover.cmd_get_interconnect_id(), False), + mock.call(self.fs.cmd_create_from_ckpt(), False), + mock.call(self.mount.cmd_server_mount('ro'), False), + mock.call(self.fs.cmd_copy_ckpt(), True), + mock.call(self.fs.cmd_nas_fs_info(), False), + mock.call(self.mount.cmd_server_umount(), False), + mock.call(self.fs.cmd_delete(), False), + mock.call(self.mount.cmd_server_mount('rw'), False), + mock.call(self.cifs_share.cmd_disable_access(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + self.assertEqual([r'\\192.168.1.1\%s' % share['name']], location, + 'CIFS export path is incorrect') + + def test_create_nfs_share_from_snapshot(self): + share_server = fakes.SHARE_SERVER + share = fakes.NFS_SHARE + snapshot = fake_share.fake_snapshot( + name=fakes.FakeData.src_snap_name, + share_name=fakes.FakeData.src_share_name, + share_id=fakes.FakeData.src_share_name, + id=fakes.FakeData.src_snap_name) + + hook = utils.RequestSideEffect() + hook.append(self.fs.resp_get_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.mover.output_get_interconnect_id()) + ssh_hook.append() + ssh_hook.append() + ssh_hook.append(self.fs.output_copy_ckpt) + ssh_hook.append(self.fs.output_info()) + ssh_hook.append() + ssh_hook.append() + ssh_hook.append() + ssh_hook.append(self.nfs_share.output_create()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + location = self.connection.create_share_from_snapshot( + None, share, snapshot, share_server) + + expected_calls = [mock.call(self.fs.req_get())] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.mover.cmd_get_interconnect_id(), False), + mock.call(self.fs.cmd_create_from_ckpt(), False), + mock.call(self.mount.cmd_server_mount('ro'), False), + mock.call(self.fs.cmd_copy_ckpt(), True), + mock.call(self.fs.cmd_nas_fs_info(), False), + mock.call(self.mount.cmd_server_umount(), False), + mock.call(self.fs.cmd_delete(), False), + mock.call(self.mount.cmd_server_mount('rw'), False), + mock.call(self.nfs_share.cmd_create(), True) + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + self.assertEqual('192.168.1.2:/%s' % share['name'], location, + 'NFS export path is incorrect') + + def test_create_share_with_incorrect_proto(self): + share_server = fakes.SHARE_SERVER + share = fake_share.fake_share(share_proto='FAKE_PROTO') + + self.assertRaises(exception.InvalidShare, + self.connection.create_share, + context=None, + share=share, + share_server=share_server) + + def test_create_share_from_snapshot_with_incorrect_proto(self): + share_server = fakes.SHARE_SERVER + share = fake_share.fake_share(share_proto='FAKE_PROTO') + snapshot = fake_share.fake_snapshot() + + self.assertRaises(exception.InvalidShare, + self.connection.create_share_from_snapshot, + None, share, snapshot, share_server) + + def test_create_share_from_snapshot_without_pool_name(self): + share_server = fakes.SHARE_SERVER + share = fake_share.fake_share(host='HostA@BackendB', + share_proto='CIFS') + snapshot = fake_share.fake_snapshot() + + self.assertRaises(exception.InvalidHost, + self.connection.create_share_from_snapshot, + None, share, snapshot, share_server) + + def test_delete_cifs_share(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + + hook = utils.RequestSideEffect() + hook.append(self.cifs_share.resp_get_succeed(self.vdm.vdm_id)) + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_share.resp_task_succeed()) + hook.append(self.mount.resp_task_succeed()) + hook.append(self.fs.resp_get_succeed()) + hook.append(self.fs.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.connection.delete_share(None, share, share_server) + + expected_calls = [ + mock.call(self.cifs_share.req_get()), + mock.call(self.vdm.req_get()), + mock.call(self.cifs_share.req_delete(self.vdm.vdm_id)), + mock.call(self.mount.req_delete(self.vdm.vdm_id)), + mock.call(self.fs.req_get()), + mock.call(self.fs.req_delete()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + def test_delete_nfs_share(self): + share_server = fakes.SHARE_SERVER + share = fakes.NFS_SHARE + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.mount.resp_task_succeed()) + hook.append(self.fs.resp_get_succeed()) + hook.append(self.fs.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=self.nfs_share.rw_hosts, + ro_hosts=self.nfs_share.ro_hosts)) + ssh_hook.append(self.nfs_share.output_delete_succeed()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.delete_share(None, share, share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.mount.req_delete(self.vdm.vdm_id)), + mock.call(self.fs.req_get()), + mock.call(self.fs.req_delete()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get(), False), + mock.call(self.nfs_share.cmd_delete(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_delete_share_without_share_server(self): + share = fakes.CIFS_SHARE + + self.connection.delete_share(None, share) + + def test_delete_share_with_incorrect_proto(self): + share_server = fakes.SHARE_SERVER + share = fake_share.fake_share(share_proto='FAKE_PROTO') + + self.assertRaises(exception.InvalidShare, + self.connection.delete_share, + context=None, + share=share, + share_server=share_server) + + def test_delete_cifs_share_with_nonexistent_mount_and_filesystem(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + + hook = utils.RequestSideEffect() + hook.append(self.cifs_share.resp_get_succeed(self.vdm.vdm_id)) + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_share.resp_task_succeed()) + hook.append(self.mount.resp_task_error()) + hook.append(self.fs.resp_get_succeed()) + hook.append(self.fs.resp_task_error()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.connection.delete_share(None, share, share_server) + + expected_calls = [ + mock.call(self.cifs_share.req_get()), + mock.call(self.vdm.req_get()), + mock.call(self.cifs_share.req_delete(self.vdm.vdm_id)), + mock.call(self.mount.req_delete(self.vdm.vdm_id)), + mock.call(self.fs.req_get()), + mock.call(self.fs.req_delete()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + def test_extend_share(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + new_size = fakes.FakeData.new_size + + hook = utils.RequestSideEffect() + hook.append(self.fs.resp_get_succeed()) + hook.append(self.pool.resp_get_succeed()) + hook.append(self.fs.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.connection.extend_share(share, new_size, share_server) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.pool.req_get()), + mock.call(self.fs.req_extend()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + def test_extend_share_without_pool_name(self): + share_server = fakes.SHARE_SERVER + share = fake_share.fake_share(host='HostA@BackendB', + share_proto='CIFS') + new_size = fakes.FakeData.new_size + + self.assertRaises(exception.InvalidHost, + self.connection.extend_share, + share, new_size, share_server) + + def test_create_snapshot(self): + share_server = fakes.SHARE_SERVER + snapshot = fake_share.fake_snapshot( + id=fakes.FakeData.snapshot_name, + share_id=fakes.FakeData.filesystem_name, + share_name=fakes.FakeData.share_name) + + hook = utils.RequestSideEffect() + hook.append(self.fs.resp_get_succeed()) + hook.append(self.snap.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.connection.create_snapshot(None, snapshot, share_server) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.snap.req_create()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + def test_create_snapshot_with_incorrect_share_info(self): + share_server = fakes.SHARE_SERVER + snapshot = fake_share.fake_snapshot( + id=fakes.FakeData.snapshot_name, + share_id=fakes.FakeData.filesystem_name, + share_name=fakes.FakeData.share_name) + + hook = utils.RequestSideEffect() + hook.append(self.fs.resp_get_but_not_found()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.assertRaises(exception.EMCVmaxXMLAPIError, + self.connection.create_snapshot, + None, snapshot, share_server) + + expected_calls = [mock.call(self.fs.req_get())] + xml_req_mock.assert_has_calls(expected_calls) + + def test_delete_snapshot(self): + share_server = fakes.SHARE_SERVER + snapshot = fake_share.fake_snapshot( + id=fakes.FakeData.snapshot_name, + share_id=fakes.FakeData.filesystem_name, + share_name=fakes.FakeData.share_name) + + hook = utils.RequestSideEffect() + hook.append(self.snap.resp_get_succeed()) + hook.append(self.snap.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.connection.delete_snapshot(None, snapshot, share_server) + + expected_calls = [ + mock.call(self.snap.req_get()), + mock.call(self.snap.req_delete()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + @utils.patch_get_managed_ports(return_value=['cge-1-0']) + def test_setup_server(self): + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_but_not_found()) + hook.append(self.mover.resp_get_ref_succeed()) + hook.append(self.vdm.resp_task_succeed()) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.dns.resp_task_succeed()) + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append() + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.setup_server(fakes.NETWORK_INFO, None) + + if_name_1 = fakes.FakeData.network_allocations_id1[-12:] + if_name_2 = fakes.FakeData.network_allocations_id2[-12:] + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.mover.req_get_ref()), + mock.call(self.vdm.req_create()), + mock.call(self.mover.req_create_interface( + if_name=if_name_1, + ip=fakes.FakeData.network_allocations_ip1)), + mock.call(self.mover.req_create_interface( + if_name=if_name_2, + ip=fakes.FakeData.network_allocations_ip2)), + mock.call(self.dns.req_create()), + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_create(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.vdm.cmd_attach_nfs_interface(), False), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + @utils.patch_get_managed_ports(return_value=['cge-1-0']) + def test_setup_server_with_existing_vdm(self): + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.mover.resp_get_ref_succeed()) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.dns.resp_task_succeed()) + hook.append(self.cifs_server.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append() + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + self.connection.setup_server(fakes.NETWORK_INFO, None) + + if_name_1 = fakes.FakeData.network_allocations_id1[-12:] + if_name_2 = fakes.FakeData.network_allocations_id2[-12:] + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_create_interface( + if_name=if_name_1, + ip=fakes.FakeData.network_allocations_ip1)), + mock.call(self.mover.req_create_interface( + if_name=if_name_2, + ip=fakes.FakeData.network_allocations_ip2)), + mock.call(self.dns.req_create()), + mock.call(self.cifs_server.req_create(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.vdm.cmd_attach_nfs_interface(), False), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_setup_server_with_invalid_security_service(self): + network_info = copy.deepcopy(fakes.NETWORK_INFO) + network_info['security_services'][0]['type'] = 'fake_type' + + self.assertRaises(exception.EMCVmaxXMLAPIError, + self.connection.setup_server, + network_info, None) + + @utils.patch_get_managed_ports( + side_effect=exception.EMCVmaxXMLAPIError( + err="Get managed ports fail.")) + def test_setup_server_without_valid_physical_device(self): + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_but_not_found()) + hook.append(self.mover.resp_get_ref_succeed()) + hook.append(self.vdm.resp_task_succeed()) + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_without_value()) + hook.append(self.vdm.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.vdm.output_get_interfaces(nfs_interface='')) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.assertRaises(exception.EMCVmaxXMLAPIError, + self.connection.setup_server, + fakes.NETWORK_INFO, None) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.mover.req_get_ref()), + mock.call(self.vdm.req_create()), + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + mock.call(self.vdm.req_delete()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.vdm.cmd_get_interfaces(), False), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + @utils.patch_get_managed_ports(return_value=['cge-1-0']) + def test_setup_server_with_exception(self): + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_but_not_found()) + hook.append(self.mover.resp_get_ref_succeed()) + hook.append(self.vdm.resp_task_succeed()) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.mover.resp_task_error()) + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_without_value()) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.vdm.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.vdm.output_get_interfaces(nfs_interface='')) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.assertRaises(exception.EMCVmaxXMLAPIError, + self.connection.setup_server, + fakes.NETWORK_INFO, None) + + if_name_1 = fakes.FakeData.network_allocations_id1[-12:] + if_name_2 = fakes.FakeData.network_allocations_id2[-12:] + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.mover.req_get_ref()), + mock.call(self.vdm.req_create()), + mock.call(self.mover.req_create_interface( + if_name=if_name_1, + ip=fakes.FakeData.network_allocations_ip1)), + mock.call(self.mover.req_create_interface( + if_name=if_name_2, + ip=fakes.FakeData.network_allocations_ip2)), + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + mock.call(self.mover.req_delete_interface( + fakes.FakeData.network_allocations_ip1)), + mock.call(self.vdm.req_delete()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.vdm.cmd_get_interfaces(), False), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_teardown_server(self): + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + hook.append(self.cifs_server.resp_task_succeed()) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=False)) + hook.append(self.mover.resp_get_ref_succeed()) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.vdm.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.vdm.output_get_interfaces()) + ssh_hook.append() + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.teardown_server(fakes.SERVER_DETAIL, + fakes.SECURITY_SERVICE) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + mock.call(self.cifs_server.req_modify( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=False)), + mock.call(self.cifs_server.req_delete(self.vdm.vdm_id)), + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_delete_interface( + fakes.FakeData.network_allocations_ip1)), + mock.call(self.mover.req_delete_interface( + fakes.FakeData.network_allocations_ip2)), + mock.call(self.vdm.req_delete()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.vdm.cmd_get_interfaces(), False), + mock.call(self.vdm.cmd_detach_nfs_interface(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_teardown_server_without_server_detail(self): + self.connection.teardown_server(None, fakes.SECURITY_SERVICE) + + def test_teardown_server_without_security_services(self): + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.mover.resp_get_ref_succeed()) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.vdm.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.vdm.output_get_interfaces()) + ssh_hook.append() + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.teardown_server(fakes.SERVER_DETAIL, []) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_delete_interface( + fakes.FakeData.network_allocations_ip1)), + mock.call(self.mover.req_delete_interface( + fakes.FakeData.network_allocations_ip2)), + mock.call(self.vdm.req_delete()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.vdm.cmd_get_interfaces(), False), + mock.call(self.vdm.cmd_detach_nfs_interface(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_teardown_server_without_share_server_name_in_server_detail(self): + server_detail = { + 'cifs_if': fakes.FakeData.network_allocations_ip1, + 'nfs_if': fakes.FakeData.network_allocations_ip2, + } + self.connection.teardown_server(server_detail, fakes.SECURITY_SERVICE) + + def test_teardown_server_with_invalid_server_name(self): + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_error()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.connection.teardown_server(fakes.SERVER_DETAIL, + fakes.SECURITY_SERVICE) + + expected_calls = [mock.call(self.vdm.req_get())] + xml_req_mock.assert_has_calls(expected_calls) + + def test_teardown_server_without_cifs_server(self): + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_error()) + hook.append(self.mover.resp_get_ref_succeed()) + hook.append(self.cifs_server.resp_task_succeed()) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=False)) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.vdm.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.vdm.output_get_interfaces()) + ssh_hook.append() + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.teardown_server(fakes.SERVER_DETAIL, + fakes.SECURITY_SERVICE) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_delete_interface( + fakes.FakeData.network_allocations_ip1)), + mock.call(self.mover.req_delete_interface( + fakes.FakeData.network_allocations_ip2)), + mock.call(self.vdm.req_delete()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.vdm.cmd_get_interfaces(), False), + mock.call(self.vdm.cmd_detach_nfs_interface(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_teardown_server_with_invalid_cifs_server_modification(self): + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + hook.append(self.cifs_server.resp_task_error()) + hook.append(self.cifs_server.resp_task_succeed()) + hook.append(self.mover.resp_get_ref_succeed()) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.vdm.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.vdm.output_get_interfaces()) + ssh_hook.append() + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.teardown_server(fakes.SERVER_DETAIL, + fakes.SECURITY_SERVICE) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + mock.call(self.cifs_server.req_modify(self.vdm.vdm_id)), + mock.call(self.cifs_server.req_delete(self.vdm.vdm_id)), + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_delete_interface( + fakes.FakeData.network_allocations_ip1)), + mock.call(self.mover.req_delete_interface( + fakes.FakeData.network_allocations_ip2)), + mock.call(self.vdm.req_delete()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.vdm.cmd_get_interfaces(), False), + mock.call(self.vdm.cmd_detach_nfs_interface(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_update_access_add_cifs_rw(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + access = fakes.CIFS_RW_ACCESS + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.cifs_share.output_allow_access()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.update_access(None, share, [], [access], [], + share_server=share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_change_access(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_update_access_deny_nfs(self): + share_server = fakes.SHARE_SERVER + share = fakes.NFS_SHARE + access = fakes.NFS_RW_ACCESS + + rw_hosts = copy.deepcopy(fakes.FakeData.rw_hosts) + rw_hosts.append(access['access_to']) + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=rw_hosts, + ro_hosts=fakes.FakeData.ro_hosts)) + ssh_hook.append(self.nfs_share.output_set_access_success()) + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=fakes.FakeData.rw_hosts, + ro_hosts=fakes.FakeData.ro_hosts)) + ssh_cmd_mock = utils.EMCNFSShareMock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.update_access(None, share, [], [], [access], + share_server=share_server) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get(), True), + mock.call(self.nfs_share.cmd_set_access( + rw_hosts=self.nfs_share.rw_hosts, + ro_hosts=self.nfs_share.ro_hosts), True), + mock.call(self.nfs_share.cmd_get(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_update_access_recover_nfs_rule(self): + share_server = fakes.SHARE_SERVER + share = fakes.NFS_SHARE + access = fakes.NFS_RW_ACCESS + hosts = ['192.168.1.5'] + + rw_hosts = copy.deepcopy(fakes.FakeData.rw_hosts) + rw_hosts.append(access['access_to']) + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=rw_hosts, + ro_hosts=fakes.FakeData.ro_hosts)) + ssh_hook.append(self.nfs_share.output_set_access_success()) + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=hosts, + ro_hosts=[])) + ssh_cmd_mock = utils.EMCNFSShareMock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.update_access(None, share, [access], [], [], + share_server=share_server) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get(), True), + mock.call(self.nfs_share.cmd_set_access( + rw_hosts=hosts, + ro_hosts=[]), True), + mock.call(self.nfs_share.cmd_get(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_update_access_recover_cifs_rule(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + access = fakes.CIFS_RW_ACCESS + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.cifs_share.output_allow_access()) + ssh_hook.append(fakes.FakeData.cifs_access) + ssh_hook.append('Command succeeded') + + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.update_access(None, share, [access], [], [], + share_server=share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_change_access(), True), + mock.call(self.cifs_share.cmd_get_access(), True), + mock.call(self.cifs_share.cmd_change_access( + action='revoke', user='guest'), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_cifs_clear_access_server_not_found(self): + server = fakes.SHARE_SERVER + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True, + cifs_server_name='cifs_server_name')) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.assertRaises(exception.EMCVmaxXMLAPIError, + self.connection._cifs_clear_access, + 'share_name', server, None) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + def test_allow_cifs_rw_access(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + access = fakes.CIFS_RW_ACCESS + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.cifs_share.output_allow_access()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.allow_access(None, share, access, share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_change_access(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_allow_cifs_ro_access(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + access = fakes.CIFS_RO_ACCESS + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.cifs_share.output_allow_access()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.allow_access(None, share, access, share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_change_access('ro'), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_allow_ro_access_without_share_server_name(self): + share = fakes.CIFS_SHARE + share_server = copy.deepcopy(fakes.SHARE_SERVER) + share_server['backend_details'].pop('share_server_name') + access = fakes.CIFS_RO_ACCESS + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.cifs_share.output_allow_access()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.allow_access(None, share, access, share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_change_access('ro'), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_allow_access_with_invalid_access_level(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + access = fake_share.fake_access(access_level='fake_level') + + self.assertRaises(exception.InvalidShareAccessLevel, + self.connection.allow_access, + None, share, access, share_server) + + def test_allow_access_with_invalid_share_server_name(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + access = fakes.CIFS_RW_ACCESS + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_error()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.assertRaises(exception.EMCVmaxXMLAPIError, + self.connection.allow_access, + None, share, access, share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + def test_allow_nfs_access(self): + share_server = fakes.SHARE_SERVER + share = fakes.NFS_SHARE + access = fakes.NFS_RW_ACCESS + + rw_hosts = copy.deepcopy(fakes.FakeData.rw_hosts) + rw_hosts.append(access['access_to']) + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=fakes.FakeData.rw_hosts, + ro_hosts=fakes.FakeData.ro_hosts)) + ssh_hook.append(self.nfs_share.output_set_access_success()) + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=rw_hosts, + ro_hosts=fakes.FakeData.ro_hosts)) + ssh_cmd_mock = utils.EMCNFSShareMock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.allow_access(None, share, access, share_server) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get(), True), + mock.call(self.nfs_share.cmd_set_access( + rw_hosts=rw_hosts, ro_hosts=self.nfs_share.ro_hosts), True), + mock.call(self.nfs_share.cmd_get(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_allow_cifs_access_with_incorrect_access_type(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + access = fake_share.fake_access(access_type='fake_type') + + self.assertRaises(exception.InvalidShareAccess, + self.connection.allow_access, + None, share, access, share_server) + + def test_allow_nfs_access_with_incorrect_access_type(self): + share_server = fakes.SHARE_SERVER + share = fakes.NFS_SHARE + access = fake_share.fake_access(access_type='fake_type') + + self.assertRaises(exception.InvalidShareAccess, + self.connection.allow_access, + None, share, access, share_server) + + def test_allow_access_with_incorrect_proto(self): + share_server = fakes.SHARE_SERVER + share = fake_share.fake_share(share_proto='FAKE_PROTO') + access = fake_share.fake_access() + + self.assertRaises(exception.InvalidShare, + self.connection.allow_access, + None, share, access, share_server) + + def test_deny_cifs_rw_access(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + access = fakes.CIFS_RW_ACCESS + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.cifs_share.output_allow_access()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.deny_access(None, share, access, share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_change_access(action='revoke'), + True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_deny_cifs_ro_access(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + access = fakes.CIFS_RO_ACCESS + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.cifs_share.output_allow_access()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.deny_access(None, share, access, share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_change_access('ro', 'revoke'), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_deny_cifs_access_with_invliad_share_server_name(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + access = fakes.CIFS_RW_ACCESS + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_error()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.assertRaises(exception.EMCVmaxXMLAPIError, + self.connection.deny_access, + None, share, access, share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + def test_deny_nfs_access(self): + share_server = fakes.SHARE_SERVER + share = fakes.NFS_SHARE + access = fakes.NFS_RW_ACCESS + + rw_hosts = copy.deepcopy(fakes.FakeData.rw_hosts) + rw_hosts.append(access['access_to']) + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=rw_hosts, + ro_hosts=fakes.FakeData.ro_hosts)) + ssh_hook.append(self.nfs_share.output_set_access_success()) + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=fakes.FakeData.rw_hosts, + ro_hosts=fakes.FakeData.ro_hosts)) + ssh_cmd_mock = utils.EMCNFSShareMock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.deny_access(None, share, access, share_server) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get(), True), + mock.call(self.nfs_share.cmd_set_access( + rw_hosts=self.nfs_share.rw_hosts, + ro_hosts=self.nfs_share.ro_hosts), True), + mock.call(self.nfs_share.cmd_get(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_deny_access_with_incorrect_proto(self): + share_server = fakes.SHARE_SERVER + share = fake_share.fake_share(share_proto='FAKE_PROTO') + access = fakes.CIFS_RW_ACCESS + + self.assertRaises(exception.InvalidShare, + self.connection.deny_access, + None, share, access, share_server) + + def test_deny_cifs_access_with_incorrect_access_type(self): + share_server = fakes.SHARE_SERVER + share = fakes.CIFS_SHARE + access = fake_share.fake_access(access_type='fake_type') + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.cifs_share.output_allow_access()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.deny_access(None, share, access, share_server) + + ssh_calls = [] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + expected_calls = [] + xml_req_mock.assert_has_calls(expected_calls) + + def test_deny_nfs_access_with_incorrect_access_type(self): + share_server = fakes.SHARE_SERVER + share = fakes.NFS_SHARE + access = fake_share.fake_access(access_type='fake_type') + + rw_hosts = copy.deepcopy(fakes.FakeData.rw_hosts) + rw_hosts.append(access['access_to']) + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=rw_hosts, + ro_hosts=fakes.FakeData.ro_hosts)) + ssh_hook.append(self.nfs_share.output_set_access_success()) + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=fakes.FakeData.rw_hosts, + ro_hosts=fakes.FakeData.ro_hosts)) + ssh_cmd_mock = utils.EMCNFSShareMock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.deny_access(None, share, access, share_server) + + ssh_calls = [] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + def test_update_share_stats(self): + hook = utils.RequestSideEffect() + hook.append(self.mover.resp_get_ref_succeed()) + hook.append(self.pool.resp_get_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.connection.update_share_stats(fakes.STATS) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.pool.req_get()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + for pool in fakes.STATS['pools']: + if pool['pool_name'] == fakes.FakeData.pool_name: + self.assertEqual(fakes.FakeData.pool_total_size, + pool['total_capacity_gb']) + + free_size = (fakes.FakeData.pool_total_size - + fakes.FakeData.pool_used_size) + self.assertEqual(free_size, pool['free_capacity_gb']) + + def test_update_share_stats_without_matched_config_pools(self): + self.connection.pools = set('fake_pool') + + hook = utils.RequestSideEffect() + hook.append(self.mover.resp_get_ref_succeed()) + hook.append(self.pool.resp_get_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.assertRaises(exception.EMCVmaxXMLAPIError, + self.connection.update_share_stats, + fakes.STATS) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.pool.req_get()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + def test_get_pool(self): + share = fakes.CIFS_SHARE + + hook = utils.RequestSideEffect() + hook.append(self.fs.resp_get_succeed()) + hook.append(self.pool.resp_get_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + pool_name = self.connection.get_pool(share) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.pool.req_get()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + self.assertEqual(fakes.FakeData.pool_name, pool_name) + + def test_get_pool_failed_to_get_filesystem_info(self): + share = fakes.CIFS_SHARE + + hook = utils.RequestSideEffect() + hook.append(self.fs.resp_get_error()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.assertRaises(exception.EMCVmaxXMLAPIError, + self.connection.get_pool, + share) + + expected_calls = [mock.call(self.fs.req_get())] + xml_req_mock.assert_has_calls(expected_calls) + + def test_get_pool_failed_to_get_pool_info(self): + share = fakes.CIFS_SHARE + + hook = utils.RequestSideEffect() + hook.append(self.fs.resp_get_succeed()) + hook.append(self.pool.resp_get_error()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.assertRaises(exception.EMCVmaxXMLAPIError, + self.connection.get_pool, + share) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.pool.req_get()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + def test_get_pool_failed_to_find_matched_pool_name(self): + share = fakes.CIFS_SHARE + + hook = utils.RequestSideEffect() + hook.append(self.fs.resp_get_succeed()) + hook.append(self.pool.resp_get_succeed(name='unmatch_pool_name', + id='unmatch_pool_id')) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.assertRaises(exception.EMCVmaxXMLAPIError, + self.connection.get_pool, + share) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.pool.req_get()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + @ddt.data({'port_conf': None, + 'managed_ports': ['cge-1-0', 'cge-1-3']}, + {'port_conf': '*', + 'managed_ports': ['cge-1-0', 'cge-1-3']}, + {'port_conf': ['cge-1-*'], + 'managed_ports': ['cge-1-0', 'cge-1-3']}, + {'port_conf': ['cge-1-3'], + 'managed_ports': ['cge-1-3']}) + @ddt.unpack + def test_get_managed_ports_one_port(self, port_conf, managed_ports): + hook = utils.SSHSideEffect() + hook.append(self.mover.output_get_physical_devices()) + + ssh_cmd_mock = mock.Mock(side_effect=hook) + expected_calls = [ + mock.call(self.mover.cmd_get_physical_devices(), False), + ] + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + self.connection.port_conf = port_conf + ports = self.connection.get_managed_ports() + self.assertIsInstance(ports, list) + self.assertEqual(sorted(managed_ports), sorted(ports)) + ssh_cmd_mock.assert_has_calls(expected_calls) + + def test_get_managed_ports_no_valid_port(self): + hook = utils.SSHSideEffect() + hook.append(self.mover.output_get_physical_devices()) + + ssh_cmd_mock = mock.Mock(side_effect=hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + self.connection.port_conf = ['cge-2-0'] + + self.assertRaises(exception.BadConfigurationException, + self.connection.get_managed_ports) + + def test_get_managed_ports_query_devices_failed(self): + hook = utils.SSHSideEffect() + hook.append(self.mover.fake_output) + ssh_cmd_mock = mock.Mock(side_effect=hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + self.connection.port_conf = ['cge-2-0'] + + self.assertRaises(exception.EMCVmaxXMLAPIError, + self.connection.get_managed_ports) diff --git a/manila/tests/share/drivers/dell_emc/plugins/vmax/test_connector.py b/manila/tests/share/drivers/dell_emc/plugins/vmax/test_connector.py new file mode 100644 index 0000000000..12613ffc6e --- /dev/null +++ b/manila/tests/share/drivers/dell_emc/plugins/vmax/test_connector.py @@ -0,0 +1,224 @@ +# Copyright (c) 2016 Dell Inc. or its subsidiaries. +# 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 eventlet import greenthread +import mock +from oslo_concurrency import processutils +from six.moves.urllib import error as url_error # pylint: disable=E0611 +from six.moves.urllib import request as url_request # pylint: disable=E0611 + +from manila import exception +from manila.share import configuration as conf +from manila.share.drivers.dell_emc.plugins.vmax import connector +from manila import test +from manila.tests.share.drivers.dell_emc.plugins.vmax import fakes +from manila.tests.share.drivers.dell_emc.plugins.vmax import utils as emc_utils +from manila import utils + + +class XMLAPIConnectorTestData(object): + FAKE_BODY = '' + FAKE_RESP = '' + FAKE_METHOD = 'fake_method' + + FAKE_KEY = 'key' + FAKE_VALUE = 'value' + + @staticmethod + def req_auth_url(): + return 'https://' + fakes.FakeData.emc_nas_server + '/Login' + + @staticmethod + def req_credential(): + return ( + 'user=' + fakes.FakeData.emc_nas_login + + '&password=' + fakes.FakeData.emc_nas_password + + '&Login=Login' + ) + + @staticmethod + def req_url_encode(): + return {'Content-Type': 'application/x-www-form-urlencoded'} + + @staticmethod + def req_url(): + return ( + 'https://' + + fakes.FakeData.emc_nas_server + + '/servlets/CelerraManagementServices' + ) + + +XML_CONN_TD = XMLAPIConnectorTestData + + +class XMLAPIConnectorTest(test.TestCase): + @mock.patch.object(url_request, 'Request', mock.Mock()) + def setUp(self): + super(XMLAPIConnectorTest, self).setUp() + + emc_share_driver = fakes.FakeEMCShareDriver() + + self.configuration = emc_share_driver.configuration + + xml_socket = mock.Mock() + xml_socket.read = mock.Mock(return_value=XML_CONN_TD.FAKE_RESP) + opener = mock.Mock() + opener.open = mock.Mock(return_value=xml_socket) + + with mock.patch.object(url_request, 'build_opener', + mock.Mock(return_value=opener)): + self.XmlConnector = connector.XMLAPIConnector( + configuration=self.configuration, debug=False) + + expected_calls = [ + mock.call(XML_CONN_TD.req_auth_url(), + XML_CONN_TD.req_credential(), + XML_CONN_TD.req_url_encode()), + ] + + url_request.Request.assert_has_calls(expected_calls) + + def test_request_with_debug(self): + self.XmlConnector.debug = True + + request = mock.Mock() + request.headers = {XML_CONN_TD.FAKE_KEY: XML_CONN_TD.FAKE_VALUE} + request.get_full_url = mock.Mock( + return_value=XML_CONN_TD.FAKE_VALUE) + + with mock.patch.object(url_request, 'Request', + mock.Mock(return_value=request)): + rsp = self.XmlConnector.request(XML_CONN_TD.FAKE_BODY, + XML_CONN_TD.FAKE_METHOD) + + self.assertEqual(XML_CONN_TD.FAKE_RESP, rsp) + + def test_request_with_no_authorized_exception(self): + xml_socket = mock.Mock() + xml_socket.read = mock.Mock(return_value=XML_CONN_TD.FAKE_RESP) + + hook = emc_utils.RequestSideEffect() + hook.append(ex=url_error.HTTPError(XML_CONN_TD.req_url(), + '403', 'fake_message', None, None)) + hook.append(xml_socket) + hook.append(xml_socket) + + self.XmlConnector.url_opener.open = mock.Mock(side_effect=hook) + + self.XmlConnector.request(XML_CONN_TD.FAKE_BODY) + + def test_request_with_general_exception(self): + hook = emc_utils.RequestSideEffect() + hook.append(ex=url_error.HTTPError(XML_CONN_TD.req_url(), + 'error_code', 'fake_message', + None, None)) + self.XmlConnector.url_opener.open = mock.Mock(side_effect=hook) + + self.assertRaises(exception.ManilaException, + self.XmlConnector.request, + XML_CONN_TD.FAKE_BODY) + + +class MockSSH(object): + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + pass + + +class MockSSHPool(object): + def __init__(self): + self.ssh = MockSSH() + + def item(self): + try: + return self.ssh + finally: + pass + + +class CmdConnectorTest(test.TestCase): + def setUp(self): + super(CmdConnectorTest, self).setUp() + + self.configuration = conf.Configuration(None) + self.configuration.append_config_values = mock.Mock(return_value=0) + self.configuration.emc_nas_login = fakes.FakeData.emc_nas_login + self.configuration.emc_nas_password = fakes.FakeData.emc_nas_password + self.configuration.emc_nas_server = fakes.FakeData.emc_nas_server + + self.sshpool = MockSSHPool() + with mock.patch.object(utils, "SSHPool", + mock.Mock(return_value=self.sshpool)): + self.CmdHelper = connector.SSHConnector( + configuration=self.configuration, debug=False) + + utils.SSHPool.assert_called_once_with( + ip=fakes.FakeData.emc_nas_server, + port=22, + conn_timeout=None, + login=fakes.FakeData.emc_nas_login, + password=fakes.FakeData.emc_nas_password) + + def test_run_ssh(self): + with mock.patch.object(processutils, "ssh_execute", + mock.Mock(return_value=('fake_output', ''))): + cmd_list = ['fake', 'cmd'] + self.CmdHelper.run_ssh(cmd_list) + + processutils.ssh_execute.assert_called_once_with( + self.sshpool.item(), 'fake cmd', check_exit_code=False) + + def test_run_ssh_with_debug(self): + self.CmdHelper.debug = True + + with mock.patch.object(processutils, "ssh_execute", + mock.Mock(return_value=('fake_output', ''))): + cmd_list = ['fake', 'cmd'] + self.CmdHelper.run_ssh(cmd_list) + + processutils.ssh_execute.assert_called_once_with( + self.sshpool.item(), 'fake cmd', check_exit_code=False) + + @mock.patch.object( + processutils, "ssh_execute", + mock.Mock(side_effect=processutils.ProcessExecutionError)) + def test_run_ssh_exception(self): + cmd_list = ['fake', 'cmd'] + + self.mock_object(greenthread, 'sleep', mock.Mock()) + + sshpool = MockSSHPool() + + with mock.patch.object(utils, "SSHPool", + mock.Mock(return_value=sshpool)): + self.CmdHelper = connector.SSHConnector(self.configuration) + + self.assertRaises(processutils.ProcessExecutionError, + self.CmdHelper.run_ssh, + cmd_list, + True) + + utils.SSHPool.assert_called_once_with( + ip=fakes.FakeData.emc_nas_server, + port=22, + conn_timeout=None, + login=fakes.FakeData.emc_nas_login, + password=fakes.FakeData.emc_nas_password) + + processutils.ssh_execute.assert_called_once_with( + sshpool.item(), 'fake cmd', check_exit_code=True) diff --git a/manila/tests/share/drivers/dell_emc/plugins/vmax/test_object_manager.py b/manila/tests/share/drivers/dell_emc/plugins/vmax/test_object_manager.py new file mode 100644 index 0000000000..4679d0aa72 --- /dev/null +++ b/manila/tests/share/drivers/dell_emc/plugins/vmax/test_object_manager.py @@ -0,0 +1,3186 @@ +# Copyright (c) 2016 Dell Inc. or its subsidiaries. +# 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 +from lxml import builder +import mock +from oslo_concurrency import processutils + +from manila.common import constants as const +from manila import exception +from manila.share.drivers.dell_emc.plugins.vmax import ( + object_manager as manager) +from manila.share.drivers.dell_emc.plugins.vmax import connector +from manila.share.drivers.dell_emc.plugins.vmax import constants +from manila.share.drivers.dell_emc.plugins.vmax import xml_api_parser as parser +from manila import test +from manila.tests.share.drivers.dell_emc.plugins.vmax import fakes +from manila.tests.share.drivers.dell_emc.plugins.vmax import utils + + +class StorageObjectManagerTestCase(test.TestCase): + @mock.patch.object(connector, "XMLAPIConnector", mock.Mock()) + @mock.patch.object(connector, "SSHConnector", mock.Mock()) + def setUp(self): + super(StorageObjectManagerTestCase, self).setUp() + + emd_share_driver = fakes.FakeEMCShareDriver() + + self.manager = manager.StorageObjectManager( + emd_share_driver.configuration) + + def test_get_storage_context(self): + type_map = { + 'FileSystem': manager.FileSystem, + 'StoragePool': manager.StoragePool, + 'MountPoint': manager.MountPoint, + 'Mover': manager.Mover, + 'VDM': manager.VDM, + 'Snapshot': manager.Snapshot, + 'MoverInterface': manager.MoverInterface, + 'DNSDomain': manager.DNSDomain, + 'CIFSServer': manager.CIFSServer, + 'CIFSShare': manager.CIFSShare, + 'NFSShare': manager.NFSShare, + } + + for key, value in type_map.items(): + self.assertTrue( + isinstance(self.manager.getStorageContext(key), value)) + + for key in self.manager.context.keys(): + self.assertIn(key, type_map) + + def test_get_storage_context_invalid_type(self): + + fake_type = 'fake_type' + + self.assertRaises(exception.EMCVmaxXMLAPIError, + self.manager.getStorageContext, + fake_type) + + +class StorageObjectTestCaseBase(test.TestCase): + @mock.patch.object(connector, "XMLAPIConnector", mock.Mock()) + @mock.patch.object(connector, "SSHConnector", mock.Mock()) + def setUp(self): + super(StorageObjectTestCaseBase, self).setUp() + + emd_share_driver = fakes.FakeEMCShareDriver() + + self.manager = manager.StorageObjectManager( + emd_share_driver.configuration) + self.base = fakes.StorageObjectTestData() + self.pool = fakes.PoolTestData() + self.vdm = fakes.VDMTestData() + self.mover = fakes.MoverTestData() + self.fs = fakes.FileSystemTestData() + self.mount = fakes.MountPointTestData() + self.snap = fakes.SnapshotTestData() + self.cifs_share = fakes.CIFSShareTestData() + self.nfs_share = fakes.NFSShareTestData() + self.cifs_server = fakes.CIFSServerTestData() + self.dns = fakes.DNSDomainTestData() + + +class StorageObjectTestCase(StorageObjectTestCaseBase): + + def test_xml_api_retry(self): + hook = utils.RequestSideEffect() + hook.append(self.base.resp_need_retry()) + hook.append(self.base.resp_task_succeed()) + elt_maker = builder.ElementMaker(nsmap={None: constants.XML_NAMESPACE}) + xml_parser = parser.XMLAPIParser() + storage_object = manager.StorageObject(self.manager.connectors, + elt_maker, xml_parser, + self.manager) + storage_object.conn['XML'].request = utils.EMCMock(side_effect=hook) + fake_req = storage_object._build_task_package( + elt_maker.StartFake(name='foo') + ) + resp = storage_object._send_request(fake_req) + self.assertEqual('ok', resp['maxSeverity']) + + expected_calls = [ + mock.call(self.base.req_fake_start_task()), + mock.call(self.base.req_fake_start_task()) + ] + storage_object.conn['XML'].request.assert_has_calls(expected_calls) + + +class FileSystemTestCase(StorageObjectTestCaseBase): + def setUp(self): + super(self.__class__, self).setUp() + self.hook = utils.RequestSideEffect() + self.ssh_hook = utils.SSHSideEffect() + + def test_create_file_system_on_vdm(self): + self.hook.append(self.pool.resp_get_succeed()) + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.fs.resp_task_succeed()) + + context = self.manager.getStorageContext('FileSystem') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.create(name=self.fs.filesystem_name, + size=self.fs.filesystem_size, + pool_name=self.pool.pool_name, + mover_name=self.vdm.vdm_name, + is_vdm=True) + + expected_calls = [ + mock.call(self.pool.req_get()), + mock.call(self.vdm.req_get()), + mock.call(self.fs.req_create_on_vdm()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_create_file_system_on_mover(self): + self.hook.append(self.pool.resp_get_succeed()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.fs.resp_task_succeed()) + + context = self.manager.getStorageContext('FileSystem') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.create(name=self.fs.filesystem_name, + size=self.fs.filesystem_size, + pool_name=self.pool.pool_name, + mover_name=self.mover.mover_name, + is_vdm=False) + + expected_calls = [ + mock.call(self.pool.req_get()), + mock.call(self.mover.req_get_ref()), + mock.call(self.fs.req_create_on_mover()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_create_file_system_but_already_exist(self): + self.hook.append(self.pool.resp_get_succeed()) + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.fs.resp_create_but_already_exist()) + + context = self.manager.getStorageContext('FileSystem') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.create(name=self.fs.filesystem_name, + size=self.fs.filesystem_size, + pool_name=self.pool.pool_name, + mover_name=self.vdm.vdm_name, + is_vdm=True) + + expected_calls = [ + mock.call(self.pool.req_get()), + mock.call(self.vdm.req_get()), + mock.call(self.fs.req_create_on_vdm()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + @mock.patch('time.sleep') + def test_create_file_system_invalid_mover_id(self, sleep_mock): + self.hook.append(self.pool.resp_get_succeed()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.fs.resp_invalid_mover_id()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.fs.resp_task_succeed()) + + context = self.manager.getStorageContext('FileSystem') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.create(name=self.fs.filesystem_name, + size=self.fs.filesystem_size, + pool_name=self.pool.pool_name, + mover_name=self.mover.mover_name, + is_vdm=False) + + expected_calls = [ + mock.call(self.pool.req_get()), + mock.call(self.mover.req_get_ref()), + mock.call(self.fs.req_create_on_mover()), + mock.call(self.mover.req_get_ref()), + mock.call(self.fs.req_create_on_mover()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + self.assertTrue(sleep_mock.called) + + def test_create_file_system_with_error(self): + self.hook.append(self.pool.resp_get_succeed()) + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.fs.resp_task_error()) + + context = self.manager.getStorageContext('FileSystem') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.create, + name=self.fs.filesystem_name, + size=self.fs.filesystem_size, + pool_name=self.pool.pool_name, + mover_name=self.vdm.vdm_name, + is_vdm=True) + + expected_calls = [ + mock.call(self.pool.req_get()), + mock.call(self.vdm.req_get()), + mock.call(self.fs.req_create_on_vdm()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_file_system(self): + self.hook.append(self.fs.resp_get_succeed()) + + context = self.manager.getStorageContext('FileSystem') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(self.fs.filesystem_name) + self.assertEqual(constants.STATUS_OK, status) + self.assertIn(self.fs.filesystem_name, context.filesystem_map) + property_map = [ + 'name', + 'pools_id', + 'volume_id', + 'size', + 'id', + 'type', + 'dataServicePolicies', + ] + for prop in property_map: + self.assertIn(prop, out) + + id = context.get_id(self.fs.filesystem_name) + self.assertEqual(self.fs.filesystem_id, id) + + expected_calls = [mock.call(self.fs.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_file_system_but_not_found(self): + self.hook.append(self.fs.resp_get_but_not_found()) + self.hook.append(self.fs.resp_get_without_value()) + self.hook.append(self.fs.resp_get_error()) + self.hook.append(self.fs.resp_get_but_not_found()) + + context = self.manager.getStorageContext('FileSystem') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(self.fs.filesystem_name) + self.assertEqual(constants.STATUS_NOT_FOUND, status) + + status, out = context.get(self.fs.filesystem_name) + self.assertEqual(constants.STATUS_NOT_FOUND, status) + + status, out = context.get(self.fs.filesystem_name) + self.assertEqual(constants.STATUS_ERROR, status) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.get_id, + self.fs.filesystem_name) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.fs.req_get()), + mock.call(self.fs.req_get()), + mock.call(self.fs.req_get()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_file_system_but_miss_property(self): + self.hook.append(self.fs.resp_get_but_miss_property()) + + context = self.manager.getStorageContext('FileSystem') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(self.fs.filesystem_name) + self.assertEqual(constants.STATUS_OK, status) + self.assertIn(self.fs.filesystem_name, context.filesystem_map) + property_map = [ + 'name', + 'pools_id', + 'volume_id', + 'size', + 'id', + 'type', + 'dataServicePolicies', + ] + for prop in property_map: + self.assertIn(prop, out) + + self.assertIsNone(out['dataServicePolicies']) + + id = context.get_id(self.fs.filesystem_name) + self.assertEqual(self.fs.filesystem_id, id) + + expected_calls = [mock.call(self.fs.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_file_system(self): + self.hook.append(self.fs.resp_get_succeed()) + self.hook.append(self.fs.resp_task_succeed()) + + context = self.manager.getStorageContext('FileSystem') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(self.fs.filesystem_name) + self.assertNotIn(self.fs.filesystem_name, context.filesystem_map) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.fs.req_delete()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + self.assertNotIn(self.fs.filesystem_name, context.filesystem_map) + + def test_delete_file_system_but_not_found(self): + self.hook.append(self.fs.resp_get_but_not_found()) + + context = self.manager.getStorageContext('FileSystem') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(self.fs.filesystem_name) + + expected_calls = [mock.call(self.fs.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_file_system_but_get_file_system_error(self): + self.hook.append(self.fs.resp_get_error()) + + context = self.manager.getStorageContext('FileSystem') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.delete, + self.fs.filesystem_name) + + expected_calls = [mock.call(self.fs.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_file_system_with_error(self): + self.hook.append(self.fs.resp_get_succeed()) + self.hook.append(self.fs.resp_delete_but_failed()) + + context = self.manager.getStorageContext('FileSystem') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.delete, + self.fs.filesystem_name) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.fs.req_delete()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + self.assertIn(self.fs.filesystem_name, context.filesystem_map) + + def test_extend_file_system(self): + self.hook.append(self.fs.resp_get_succeed()) + self.hook.append(self.pool.resp_get_succeed()) + self.hook.append(self.fs.resp_task_succeed()) + + context = self.manager.getStorageContext('FileSystem') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.extend(name=self.fs.filesystem_name, + pool_name=self.pool.pool_name, + new_size=self.fs.filesystem_new_size) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.pool.req_get()), + mock.call(self.fs.req_extend()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_extend_file_system_but_not_found(self): + self.hook.append(self.fs.resp_get_but_not_found()) + + context = self.manager.getStorageContext('FileSystem') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.extend, + name=self.fs.filesystem_name, + pool_name=self.fs.pool_name, + new_size=self.fs.filesystem_new_size) + + expected_calls = [mock.call(self.fs.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_extend_file_system_with_small_size(self): + self.hook.append(self.fs.resp_get_succeed()) + + context = self.manager.getStorageContext('FileSystem') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.extend, + name=self.fs.filesystem_name, + pool_name=self.pool.pool_name, + new_size=1) + + expected_calls = [mock.call(self.fs.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_extend_file_system_with_same_size(self): + self.hook.append(self.fs.resp_get_succeed()) + + context = self.manager.getStorageContext('FileSystem') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.extend(name=self.fs.filesystem_name, + pool_name=self.pool.pool_name, + new_size=self.fs.filesystem_size) + + expected_calls = [mock.call(self.fs.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_extend_file_system_with_error(self): + self.hook.append(self.fs.resp_get_succeed()) + self.hook.append(self.pool.resp_get_succeed()) + self.hook.append(self.fs.resp_extend_but_error()) + + context = self.manager.getStorageContext('FileSystem') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.extend, + name=self.fs.filesystem_name, + pool_name=self.pool.pool_name, + new_size=self.fs.filesystem_new_size) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.pool.req_get()), + mock.call(self.fs.req_extend()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_create_filesystem_from_snapshot(self): + self.ssh_hook.append() + self.ssh_hook.append() + self.ssh_hook.append(self.fs.output_copy_ckpt) + self.ssh_hook.append(self.fs.output_info()) + self.ssh_hook.append() + self.ssh_hook.append() + self.ssh_hook.append() + + context = self.manager.getStorageContext('FileSystem') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + context.create_from_snapshot(self.fs.filesystem_name, + self.snap.src_snap_name, + self.fs.src_fileystems_name, + self.pool.pool_name, + self.vdm.vdm_name, + self.mover.interconnect_id,) + + ssh_calls = [ + mock.call(self.fs.cmd_create_from_ckpt(), False), + mock.call(self.mount.cmd_server_mount('ro'), False), + mock.call(self.fs.cmd_copy_ckpt(), True), + mock.call(self.fs.cmd_nas_fs_info(), False), + mock.call(self.mount.cmd_server_umount(), False), + mock.call(self.fs.cmd_delete(), False), + mock.call(self.mount.cmd_server_mount('rw'), False), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_create_filesystem_from_snapshot_with_error(self): + self.ssh_hook.append() + self.ssh_hook.append() + self.ssh_hook.append(ex=processutils.ProcessExecutionError( + stdout=self.fs.fake_output, stderr=None)) + self.ssh_hook.append(self.fs.output_info()) + self.ssh_hook.append() + self.ssh_hook.append() + self.ssh_hook.append() + + context = self.manager.getStorageContext('FileSystem') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + context.create_from_snapshot( + self.fs.filesystem_name, + self.snap.src_snap_name, + self.fs.src_fileystems_name, + self.pool.pool_name, + self.vdm.vdm_name, + self.mover.interconnect_id, ) + + ssh_calls = [ + mock.call(self.fs.cmd_create_from_ckpt(), False), + mock.call(self.mount.cmd_server_mount('ro'), False), + mock.call(self.fs.cmd_copy_ckpt(), True), + mock.call(self.fs.cmd_nas_fs_info(), False), + mock.call(self.mount.cmd_server_umount(), False), + mock.call(self.fs.cmd_delete(), False), + mock.call(self.mount.cmd_server_mount('rw'), False), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + +class MountPointTestCase(StorageObjectTestCaseBase): + def setUp(self): + super(self.__class__, self).setUp() + self.hook = utils.RequestSideEffect() + + def test_create_mount_point_on_vdm(self): + self.hook.append(self.fs.resp_get_succeed()) + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.mount.resp_task_succeed()) + + context = self.manager.getStorageContext('MountPoint') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.create(mount_path=self.mount.path, + fs_name=self.fs.filesystem_name, + mover_name=self.vdm.vdm_name, + is_vdm=True) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.vdm.req_get()), + mock.call(self.mount.req_create(self.vdm.vdm_id, True)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_create_mount_point_on_mover(self): + self.hook.append(self.fs.resp_get_succeed()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mount.resp_task_succeed()) + + context = self.manager.getStorageContext('MountPoint') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.create(mount_path=self.mount.path, + fs_name=self.fs.filesystem_name, + mover_name=self.mover.mover_name, + is_vdm=False) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.mover.req_get_ref()), + mock.call(self.mount.req_create(self.mover.mover_id, False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_create_mount_point_but_already_exist(self): + self.hook.append(self.fs.resp_get_succeed()) + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.mount.resp_create_but_already_exist()) + + context = self.manager.getStorageContext('MountPoint') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.create(mount_path=self.mount.path, + fs_name=self.fs.filesystem_name, + mover_name=self.vdm.vdm_name, + is_vdm=True) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.vdm.req_get()), + mock.call(self.mount.req_create(self.vdm.vdm_id)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + @mock.patch('time.sleep') + def test_create_mount_point_invalid_mover_id(self, sleep_mock): + self.hook.append(self.fs.resp_get_succeed()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mount.resp_invalid_mover_id()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mount.resp_task_succeed()) + + context = self.manager.getStorageContext('MountPoint') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.create(mount_path=self.mount.path, + fs_name=self.fs.filesystem_name, + mover_name=self.mover.mover_name, + is_vdm=False) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.mover.req_get_ref()), + mock.call(self.mount.req_create(self.mover.mover_id, False)), + mock.call(self.mover.req_get_ref()), + mock.call(self.mount.req_create(self.mover.mover_id, False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + self.assertTrue(sleep_mock.called) + + def test_create_mount_point_with_error(self): + self.hook.append(self.fs.resp_get_succeed()) + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.mount.resp_task_error()) + + context = self.manager.getStorageContext('MountPoint') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.create, + mount_path=self.mount.path, + fs_name=self.fs.filesystem_name, + mover_name=self.vdm.vdm_name, + is_vdm=True) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.vdm.req_get()), + mock.call(self.mount.req_create(self.vdm.vdm_id)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_mount_point_on_vdm(self): + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.mount.resp_task_succeed()) + + context = self.manager.getStorageContext('MountPoint') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(mount_path=self.mount.path, + mover_name=self.vdm.vdm_name, + is_vdm=True) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.mount.req_delete(self.vdm.vdm_id)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_mount_point_on_mover(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mount.resp_task_succeed()) + + context = self.manager.getStorageContext('MountPoint') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(mount_path=self.mount.path, + mover_name=self.mover.mover_name, + is_vdm=False) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mount.req_delete(self.mover.mover_id, False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_mount_point_but_nonexistent(self): + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.mount.resp_delete_but_nonexistent()) + + context = self.manager.getStorageContext('MountPoint') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(mount_path=self.mount.path, + mover_name=self.vdm.vdm_name, + is_vdm=True) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.mount.req_delete(self.vdm.vdm_id)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + @mock.patch('time.sleep') + def test_delete_mount_point_invalid_mover_id(self, sleep_mock): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mount.resp_invalid_mover_id()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mount.resp_task_succeed()) + + context = self.manager.getStorageContext('MountPoint') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(mount_path=self.mount.path, + mover_name=self.mover.mover_name, + is_vdm=False) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mount.req_delete(self.mover.mover_id, False)), + mock.call(self.mover.req_get_ref()), + mock.call(self.mount.req_delete(self.mover.mover_id, False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + self.assertTrue(sleep_mock.called) + + def test_delete_mount_point_with_error(self): + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.mount.resp_task_error()) + + context = self.manager.getStorageContext('MountPoint') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.delete, + mount_path=self.mount.path, + mover_name=self.vdm.vdm_name, + is_vdm=True) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.mount.req_delete(self.vdm.vdm_id)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_mount_points(self): + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.mount.resp_get_succeed(self.vdm.vdm_id)) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mount.resp_get_succeed(self.mover.mover_id, + False)) + + context = self.manager.getStorageContext('MountPoint') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(self.vdm.vdm_name) + self.assertEqual(constants.STATUS_OK, status) + property_map = [ + 'path', + 'mover', + 'moverIdIsVdm', + 'fileSystem', + ] + for item in out: + for prop in property_map: + self.assertIn(prop, item) + + status, out = context.get(self.mover.mover_name, False) + self.assertEqual(constants.STATUS_OK, status) + property_map = [ + 'path', + 'mover', + 'moverIdIsVdm', + 'fileSystem', + ] + for item in out: + for prop in property_map: + self.assertIn(prop, item) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.mount.req_get(self.vdm.vdm_id)), + mock.call(self.mover.req_get_ref()), + mock.call(self.mount.req_get(self.mover.mover_id, False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_mount_points_but_not_found(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mount.resp_get_without_value()) + + context = self.manager.getStorageContext('MountPoint') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(self.mover.mover_name, False) + self.assertEqual(constants.STATUS_NOT_FOUND, status) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mount.req_get(self.mover.mover_id, False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + @mock.patch('time.sleep') + def test_get_mount_points_invalid_mover_id(self, sleep_mock): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mount.resp_invalid_mover_id()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mount.resp_get_succeed(self.mover.mover_id, + False)) + + context = self.manager.getStorageContext('MountPoint') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(self.mover.mover_name, False) + self.assertEqual(constants.STATUS_OK, status) + + property_map = [ + 'path', + 'mover', + 'moverIdIsVdm', + 'fileSystem', + ] + for item in out: + for prop in property_map: + self.assertIn(prop, item) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mount.req_get(self.mover.mover_id, False)), + mock.call(self.mover.req_get_ref()), + mock.call(self.mount.req_get(self.mover.mover_id, False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + self.assertTrue(sleep_mock.called) + + def test_get_mount_points_with_error(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mount.resp_get_error()) + + context = self.manager.getStorageContext('MountPoint') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(self.mover.mover_name, False) + self.assertEqual(constants.STATUS_ERROR, status) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mount.req_get(self.mover.mover_id, False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + +class VDMTestCase(StorageObjectTestCaseBase): + def setUp(self): + super(self.__class__, self).setUp() + self.hook = utils.RequestSideEffect() + self.ssh_hook = utils.SSHSideEffect() + + def test_create_vdm(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.vdm.resp_task_succeed()) + + context = self.manager.getStorageContext('VDM') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.create(self.vdm.vdm_name, self.mover.mover_name) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.vdm.req_create()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_create_vdm_but_already_exist(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.vdm.resp_create_but_already_exist()) + + context = self.manager.getStorageContext('VDM') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + # Create VDM which already exists. + context.create(self.vdm.vdm_name, self.mover.mover_name) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.vdm.req_create()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + @mock.patch('time.sleep') + def test_create_vdm_invalid_mover_id(self, sleep_mock): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.vdm.resp_invalid_mover_id()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.vdm.resp_task_succeed()) + + context = self.manager.getStorageContext('VDM') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + # Create VDM with invalid mover ID + context.create(self.vdm.vdm_name, self.mover.mover_name) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.vdm.req_create()), + mock.call(self.mover.req_get_ref()), + mock.call(self.vdm.req_create()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + self.assertTrue(sleep_mock.called) + + def test_create_vdm_with_error(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.vdm.resp_task_error()) + + context = self.manager.getStorageContext('VDM') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + # Create VDM with invalid mover ID + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.create, + name=self.vdm.vdm_name, + mover_name=self.mover.mover_name) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.vdm.req_create()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_vdm(self): + self.hook.append(self.vdm.resp_get_succeed()) + + context = self.manager.getStorageContext('VDM') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(self.vdm.vdm_name) + self.assertEqual(constants.STATUS_OK, status) + self.assertIn(self.vdm.vdm_name, context.vdm_map) + property_map = [ + 'name', + 'id', + 'state', + 'host_mover_id', + 'interfaces', + ] + for prop in property_map: + self.assertIn(prop, out) + + expected_calls = [mock.call(self.vdm.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_vdm_with_error(self): + self.hook.append(self.vdm.resp_get_error()) + + context = self.manager.getStorageContext('VDM') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + # Get VDM with error + status, out = context.get(self.vdm.vdm_name) + self.assertEqual(constants.STATUS_ERROR, status) + + expected_calls = [mock.call(self.vdm.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_vdm_but_not_found(self): + self.hook.append(self.vdm.resp_get_without_value()) + self.hook.append(self.vdm.resp_get_succeed('fake')) + + context = self.manager.getStorageContext('VDM') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + # Get VDM which does not exist + status, out = context.get(self.vdm.vdm_name) + self.assertEqual(constants.STATUS_NOT_FOUND, status) + + status, out = context.get(self.vdm.vdm_name) + self.assertEqual(constants.STATUS_NOT_FOUND, status) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.vdm.req_get()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_vdm_id_with_error(self): + self.hook.append(self.vdm.resp_get_error()) + + context = self.manager.getStorageContext('VDM') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.get_id, + self.vdm.vdm_name) + + expected_calls = [mock.call(self.vdm.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_vdm(self): + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.vdm.resp_task_succeed()) + + context = self.manager.getStorageContext('VDM') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(self.vdm.vdm_name) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.vdm.req_delete()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_vdm_but_not_found(self): + self.hook.append(self.vdm.resp_get_but_not_found()) + + context = self.manager.getStorageContext('VDM') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(self.vdm.vdm_name) + + expected_calls = [mock.call(self.vdm.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_vdm_but_failed_to_get_vdm(self): + self.hook.append(self.vdm.resp_get_error()) + + context = self.manager.getStorageContext('VDM') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.delete, + self.vdm.vdm_name) + + expected_calls = [mock.call(self.vdm.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_vdm_with_error(self): + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.vdm.resp_task_error()) + + context = self.manager.getStorageContext('VDM') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.delete, + self.vdm.vdm_name) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.vdm.req_delete()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_attach_detach_nfs_interface(self): + self.ssh_hook.append() + self.ssh_hook.append() + + context = self.manager.getStorageContext('VDM') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + context.attach_nfs_interface(self.vdm.vdm_name, + self.mover.interface_name2) + context.detach_nfs_interface(self.vdm.vdm_name, + self.mover.interface_name2) + + ssh_calls = [ + mock.call(self.vdm.cmd_attach_nfs_interface(), False), + mock.call(self.vdm.cmd_detach_nfs_interface(), True), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_detach_nfs_interface_with_error(self): + self.ssh_hook.append(ex=processutils.ProcessExecutionError( + stdout=self.vdm.fake_output)) + self.ssh_hook.append(self.vdm.output_get_interfaces( + self.mover.interface_name2)) + self.ssh_hook.append(ex=processutils.ProcessExecutionError( + stdout=self.vdm.fake_output)) + self.ssh_hook.append(self.vdm.output_get_interfaces( + nfs_interface=fakes.FakeData.interface_name1)) + + context = self.manager.getStorageContext('VDM') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.detach_nfs_interface, + self.vdm.vdm_name, + self.mover.interface_name2) + + context.detach_nfs_interface(self.vdm.vdm_name, + self.mover.interface_name2) + + ssh_calls = [ + mock.call(self.vdm.cmd_detach_nfs_interface(), True), + mock.call(self.vdm.cmd_get_interfaces(), False), + mock.call(self.vdm.cmd_detach_nfs_interface(), True), + mock.call(self.vdm.cmd_get_interfaces(), False), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_get_cifs_nfs_interface(self): + self.ssh_hook.append(self.vdm.output_get_interfaces()) + + context = self.manager.getStorageContext('VDM') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + interfaces = context.get_interfaces(self.vdm.vdm_name) + self.assertIsNotNone(interfaces['cifs']) + self.assertIsNotNone(interfaces['nfs']) + + ssh_calls = [mock.call(self.vdm.cmd_get_interfaces(), False)] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + +class StoragePoolTestCase(StorageObjectTestCaseBase): + def setUp(self): + super(self.__class__, self).setUp() + self.hook = utils.RequestSideEffect() + + def test_get_pool(self): + self.hook.append(self.pool.resp_get_succeed()) + + context = self.manager.getStorageContext('StoragePool') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(self.pool.pool_name) + self.assertEqual(constants.STATUS_OK, status) + self.assertIn(self.pool.pool_name, context.pool_map) + property_map = [ + 'name', + 'movers_id', + 'total_size', + 'used_size', + 'diskType', + 'dataServicePolicies', + 'id', + ] + for prop in property_map: + self.assertIn(prop, out) + + expected_calls = [mock.call(self.pool.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_pool_with_error(self): + self.hook.append(self.pool.resp_get_error()) + self.hook.append(self.pool.resp_get_without_value()) + self.hook.append(self.pool.resp_get_succeed(name='other')) + + context = self.manager.getStorageContext('StoragePool') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(self.pool.pool_name) + self.assertEqual(constants.STATUS_ERROR, status) + + status, out = context.get(self.pool.pool_name) + self.assertEqual(constants.STATUS_NOT_FOUND, status) + + status, out = context.get(self.pool.pool_name) + self.assertEqual(constants.STATUS_NOT_FOUND, status) + + expected_calls = [ + mock.call(self.pool.req_get()), + mock.call(self.pool.req_get()), + mock.call(self.pool.req_get()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_pool_id_with_error(self): + self.hook.append(self.pool.resp_get_error()) + + context = self.manager.getStorageContext('StoragePool') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.get_id, + self.pool.pool_name) + + expected_calls = [mock.call(self.pool.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + +class MoverTestCase(StorageObjectTestCaseBase): + def setUp(self): + super(self.__class__, self).setUp() + self.hook = utils.RequestSideEffect() + self.ssh_hook = utils.SSHSideEffect() + + def test_get_mover(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mover.resp_get_succeed()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mover.resp_get_succeed()) + + context = self.manager.getStorageContext('Mover') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(self.mover.mover_name) + self.assertEqual(constants.STATUS_OK, status) + self.assertIn(self.mover.mover_name, context.mover_map) + property_map = [ + 'name', + 'id', + 'Status', + 'version', + 'uptime', + 'role', + 'interfaces', + 'devices', + 'dns_domain', + ] + for prop in property_map: + self.assertIn(prop, out) + + status, out = context.get(self.mover.mover_name) + self.assertEqual(constants.STATUS_OK, status) + + status, out = context.get(self.mover.mover_name, True) + self.assertEqual(constants.STATUS_OK, status) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_get()), + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_get()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_mover_ref_not_found(self): + self.hook.append(self.mover.resp_get_ref_succeed(name='other')) + + context = self.manager.getStorageContext('Mover') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get_ref(self.mover.mover_name) + self.assertEqual(constants.STATUS_NOT_FOUND, status) + + expected_calls = [mock.call(self.mover.req_get_ref())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_mover_ref_with_error(self): + self.hook.append(self.mover.resp_get_error()) + + context = self.manager.getStorageContext('Mover') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get_ref(self.mover.mover_name) + self.assertEqual(constants.STATUS_ERROR, status) + + expected_calls = [mock.call(self.mover.req_get_ref())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_mover_ref_and_mover(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mover.resp_get_succeed()) + + context = self.manager.getStorageContext('Mover') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get_ref(self.mover.mover_name) + self.assertEqual(constants.STATUS_OK, status) + property_map = ['name', 'id'] + for prop in property_map: + self.assertIn(prop, out) + + status, out = context.get(self.mover.mover_name) + self.assertEqual(constants.STATUS_OK, status) + self.assertIn(self.mover.mover_name, context.mover_map) + property_map = [ + 'name', + 'id', + 'Status', + 'version', + 'uptime', + 'role', + 'interfaces', + 'devices', + 'dns_domain', + ] + for prop in property_map: + self.assertIn(prop, out) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_get()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_mover_failed_to_get_mover_ref(self): + self.hook.append(self.mover.resp_get_error()) + + context = self.manager.getStorageContext('Mover') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.get, + self.mover.mover_name) + + expected_calls = [mock.call(self.mover.req_get_ref())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_mover_but_not_found(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mover.resp_get_without_value()) + + context = self.manager.getStorageContext('Mover') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(name=self.mover.mover_name, force=True) + self.assertEqual(constants.STATUS_NOT_FOUND, status) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_get()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_mover_with_error(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mover.resp_get_error()) + + context = self.manager.getStorageContext('Mover') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(self.mover.mover_name) + self.assertEqual(constants.STATUS_ERROR, status) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_get()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_interconnect_id(self): + self.ssh_hook.append(self.mover.output_get_interconnect_id()) + + context = self.manager.getStorageContext('Mover') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + conn_id = context.get_interconnect_id(self.mover.mover_name, + self.mover.mover_name) + self.assertEqual(self.mover.interconnect_id, conn_id) + + ssh_calls = [mock.call(self.mover.cmd_get_interconnect_id(), False)] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_get_physical_devices(self): + self.ssh_hook.append(self.mover.output_get_physical_devices()) + + context = self.manager.getStorageContext('Mover') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + devices = context.get_physical_devices(self.mover.mover_name) + self.assertIn(self.mover.device_name, devices) + + ssh_calls = [mock.call(self.mover.cmd_get_physical_devices(), False)] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + +class SnapshotTestCase(StorageObjectTestCaseBase): + def setUp(self): + super(self.__class__, self).setUp() + self.hook = utils.RequestSideEffect() + + def test_create_snapshot(self): + self.hook.append(self.fs.resp_get_succeed()) + self.hook.append(self.snap.resp_task_succeed()) + + context = self.manager.getStorageContext('Snapshot') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.create(name=self.snap.snapshot_name, + fs_name=self.fs.filesystem_name, + pool_id=self.pool.pool_id) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.snap.req_create()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_create_snapshot_but_already_exist(self): + self.hook.append(self.fs.resp_get_succeed()) + self.hook.append(self.snap.resp_create_but_already_exist()) + + context = self.manager.getStorageContext('Snapshot') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.create(name=self.snap.snapshot_name, + fs_name=self.fs.filesystem_name, + pool_id=self.pool.pool_id, + ckpt_size=self.snap.snapshot_size) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.snap.req_create_with_size()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_create_snapshot_with_error(self): + self.hook.append(self.fs.resp_get_succeed()) + self.hook.append(self.snap.resp_task_error()) + + context = self.manager.getStorageContext('Snapshot') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.create, + name=self.snap.snapshot_name, + fs_name=self.fs.filesystem_name, + pool_id=self.pool.pool_id, + ckpt_size=self.snap.snapshot_size) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.snap.req_create_with_size()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_snapshot(self): + self.hook.append(self.snap.resp_get_succeed()) + + context = self.manager.getStorageContext('Snapshot') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(self.snap.snapshot_name) + self.assertEqual(constants.STATUS_OK, status) + self.assertIn(self.snap.snapshot_name, context.snap_map) + property_map = [ + 'name', + 'id', + 'checkpointOf', + 'state', + ] + for prop in property_map: + self.assertIn(prop, out) + + expected_calls = [mock.call(self.snap.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_snapshot_but_not_found(self): + self.hook.append(self.snap.resp_get_without_value()) + + context = self.manager.getStorageContext('Snapshot') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(self.snap.snapshot_name) + self.assertEqual(constants.STATUS_NOT_FOUND, status) + + expected_calls = [mock.call(self.snap.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_snapshot_with_error(self): + self.hook.append(self.snap.resp_get_error()) + + context = self.manager.getStorageContext('Snapshot') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(self.snap.snapshot_name) + self.assertEqual(constants.STATUS_ERROR, status) + + expected_calls = [mock.call(self.snap.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_snapshot(self): + self.hook.append(self.snap.resp_get_succeed()) + self.hook.append(self.snap.resp_task_succeed()) + + context = self.manager.getStorageContext('Snapshot') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(self.snap.snapshot_name) + self.assertNotIn(self.snap.snapshot_name, context.snap_map) + + expected_calls = [ + mock.call(self.snap.req_get()), + mock.call(self.snap.req_delete()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_snapshot_failed_to_get_snapshot(self): + self.hook.append(self.snap.resp_get_error()) + + context = self.manager.getStorageContext('Snapshot') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.delete, + self.snap.snapshot_name) + + expected_calls = [mock.call(self.snap.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_snapshot_but_not_found(self): + self.hook.append(self.snap.resp_get_without_value()) + + context = self.manager.getStorageContext('Snapshot') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(self.snap.snapshot_name) + self.assertNotIn(self.snap.snapshot_name, context.snap_map) + + expected_calls = [mock.call(self.snap.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_snapshot_with_error(self): + self.hook.append(self.snap.resp_get_succeed()) + self.hook.append(self.snap.resp_task_error()) + + context = self.manager.getStorageContext('Snapshot') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.delete, + self.snap.snapshot_name) + + expected_calls = [ + mock.call(self.snap.req_get()), + mock.call(self.snap.req_delete()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_snapshot_id(self): + self.hook.append(self.snap.resp_get_succeed()) + + context = self.manager.getStorageContext('Snapshot') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + id = context.get_id(self.snap.snapshot_name) + self.assertEqual(self.snap.snapshot_id, id) + + expected_calls = [mock.call(self.snap.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_snapshot_id_with_error(self): + self.hook.append(self.snap.resp_get_error()) + + context = self.manager.getStorageContext('Snapshot') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.get_id, + self.snap.snapshot_name) + + expected_calls = [mock.call(self.snap.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + +@ddt.ddt +class MoverInterfaceTestCase(StorageObjectTestCaseBase): + def setUp(self): + super(self.__class__, self).setUp() + self.hook = utils.RequestSideEffect() + + def test_create_mover_interface(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mover.resp_task_succeed()) + self.hook.append(self.mover.resp_task_succeed()) + + context = self.manager.getStorageContext('MoverInterface') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + interface = { + 'name': self.mover.interface_name1, + 'device_name': self.mover.device_name, + 'ip': self.mover.ip_address1, + 'mover_name': self.mover.mover_name, + 'net_mask': self.mover.net_mask, + 'vlan_id': self.mover.vlan_id, + } + context.create(interface) + + interface['name'] = self.mover.long_interface_name + context.create(interface) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_create_interface()), + mock.call(self.mover.req_create_interface( + self.mover.long_interface_name[:31])), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_create_mover_interface_name_already_exist(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append( + self.mover.resp_create_interface_but_name_already_exist()) + + context = self.manager.getStorageContext('MoverInterface') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + interface = { + 'name': self.mover.interface_name1, + 'device_name': self.mover.device_name, + 'ip': self.mover.ip_address1, + 'mover_name': self.mover.mover_name, + 'net_mask': self.mover.net_mask, + 'vlan_id': self.mover.vlan_id, + } + context.create(interface) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_create_interface()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_create_mover_interface_ip_already_exist(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append( + self.mover.resp_create_interface_but_ip_already_exist()) + + context = self.manager.getStorageContext('MoverInterface') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + interface = { + 'name': self.mover.interface_name1, + 'device_name': self.mover.device_name, + 'ip': self.mover.ip_address1, + 'mover_name': self.mover.mover_name, + 'net_mask': self.mover.net_mask, + 'vlan_id': self.mover.vlan_id, + } + context.create(interface) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_create_interface()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + @ddt.data(fakes.MoverTestData().resp_task_succeed(), + fakes.MoverTestData().resp_task_error()) + def test_create_mover_interface_with_conflict_vlan_id(self, xml_resp): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append( + self.mover.resp_create_interface_with_conflicted_vlan_id()) + self.hook.append(xml_resp) + + context = self.manager.getStorageContext('MoverInterface') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + interface = { + 'name': self.mover.interface_name1, + 'device_name': self.mover.device_name, + 'ip': self.mover.ip_address1, + 'mover_name': self.mover.mover_name, + 'net_mask': self.mover.net_mask, + 'vlan_id': self.mover.vlan_id, + } + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.create, + interface) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_create_interface()), + mock.call(self.mover.req_delete_interface()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + @mock.patch('time.sleep') + def test_create_mover_interface_invalid_mover_id(self, sleep_mock): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mover.resp_invalid_mover_id()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mover.resp_task_succeed()) + + context = self.manager.getStorageContext('MoverInterface') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + interface = { + 'name': self.mover.interface_name1, + 'device_name': self.mover.device_name, + 'ip': self.mover.ip_address1, + 'mover_name': self.mover.mover_name, + 'net_mask': self.mover.net_mask, + 'vlan_id': self.mover.vlan_id, + } + context.create(interface) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_create_interface()), + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_create_interface()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + self.assertTrue(sleep_mock.called) + + def test_create_mover_interface_with_error(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mover.resp_task_error()) + + context = self.manager.getStorageContext('MoverInterface') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + interface = { + 'name': self.mover.interface_name1, + 'device_name': self.mover.device_name, + 'ip': self.mover.ip_address1, + 'mover_name': self.mover.mover_name, + 'net_mask': self.mover.net_mask, + 'vlan_id': self.mover.vlan_id, + } + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.create, + interface) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_create_interface()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_mover_interface(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mover.resp_get_succeed()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mover.resp_get_succeed()) + + context = self.manager.getStorageContext('MoverInterface') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(name=self.mover.interface_name1, + mover_name=self.mover.mover_name) + self.assertEqual(constants.STATUS_OK, status) + property_map = [ + 'name', + 'device', + 'up', + 'ipVersion', + 'netMask', + 'ipAddress', + 'vlanid', + ] + for prop in property_map: + self.assertIn(prop, out) + + context.get(name=self.mover.long_interface_name, + mover_name=self.mover.mover_name) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_get()), + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_get()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_mover_interface_not_found(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mover.resp_get_without_value()) + + context = self.manager.getStorageContext('MoverInterface') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(name=self.mover.interface_name1, + mover_name=self.mover.mover_name) + self.assertEqual(constants.STATUS_NOT_FOUND, status) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_get()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_mover_interface(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mover.resp_task_succeed()) + + context = self.manager.getStorageContext('MoverInterface') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(ip_addr=self.mover.ip_address1, + mover_name=self.mover.mover_name) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_delete_interface()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_mover_interface_but_nonexistent(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mover.resp_delete_interface_but_nonexistent()) + + context = self.manager.getStorageContext('MoverInterface') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(ip_addr=self.mover.ip_address1, + mover_name=self.mover.mover_name) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_delete_interface()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + @mock.patch('time.sleep') + def test_delete_mover_interface_invalid_mover_id(self, sleep_mock): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mover.resp_invalid_mover_id()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mover.resp_task_succeed()) + + context = self.manager.getStorageContext('MoverInterface') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(ip_addr=self.mover.ip_address1, + mover_name=self.mover.mover_name) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_delete_interface()), + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_delete_interface()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + self.assertTrue(sleep_mock.called) + + def test_delete_mover_interface_with_error(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.mover.resp_task_error()) + + context = self.manager.getStorageContext('MoverInterface') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.delete, + ip_addr=self.mover.ip_address1, + mover_name=self.mover.mover_name) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_delete_interface()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + +class DNSDomainTestCase(StorageObjectTestCaseBase): + def setUp(self): + super(self.__class__, self).setUp() + self.hook = utils.RequestSideEffect() + + def test_create_dns_domain(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.dns.resp_task_succeed()) + + context = self.manager.getStorageContext('DNSDomain') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.create(mover_name=self.mover.mover_name, + name=self.dns.domain_name, + servers=self.dns.dns_ip_address) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.dns.req_create()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + @mock.patch('time.sleep') + def test_create_dns_domain_invalid_mover_id(self, sleep_mock): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.dns.resp_invalid_mover_id()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.dns.resp_task_succeed()) + + context = self.manager.getStorageContext('DNSDomain') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.create(mover_name=self.mover.mover_name, + name=self.dns.domain_name, + servers=self.dns.dns_ip_address) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.dns.req_create()), + mock.call(self.mover.req_get_ref()), + mock.call(self.dns.req_create()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + self.assertTrue(sleep_mock.called) + + def test_create_dns_domain_with_error(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.dns.resp_task_error()) + + context = self.manager.getStorageContext('DNSDomain') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.create, + mover_name=self.mover.mover_name, + name=self.mover.domain_name, + servers=self.dns.dns_ip_address) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.dns.req_create()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_dns_domain(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.dns.resp_task_succeed()) + self.hook.append(self.dns.resp_task_error()) + + context = self.manager.getStorageContext('DNSDomain') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(mover_name=self.mover.mover_name, + name=self.mover.domain_name) + + context.delete(mover_name=self.mover.mover_name, + name=self.mover.domain_name) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.dns.req_delete()), + mock.call(self.dns.req_delete()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + @mock.patch('time.sleep') + def test_delete_dns_domain_invalid_mover_id(self, sleep_mock): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.dns.resp_invalid_mover_id()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.dns.resp_task_succeed()) + + context = self.manager.getStorageContext('DNSDomain') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(mover_name=self.mover.mover_name, + name=self.mover.domain_name) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.dns.req_delete()), + mock.call(self.mover.req_get_ref()), + mock.call(self.dns.req_delete()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + self.assertTrue(sleep_mock.called) + + +class CIFSServerTestCase(StorageObjectTestCaseBase): + def setUp(self): + super(self.__class__, self).setUp() + self.hook = utils.RequestSideEffect() + + def test_create_cifs_server(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.cifs_server.resp_task_succeed()) + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.cifs_server.resp_task_succeed()) + self.hook.append(self.cifs_server.resp_task_error()) + self.hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + + context = self.manager.getStorageContext('CIFSServer') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + # Create CIFS server on mover + cifs_server_args = { + 'name': self.cifs_server.cifs_server_name, + 'interface_ip': self.cifs_server.ip_address1, + 'domain_name': self.cifs_server.domain_name, + 'user_name': self.cifs_server.domain_user, + 'password': self.cifs_server.domain_password, + 'mover_name': self.mover.mover_name, + 'is_vdm': False, + } + context.create(cifs_server_args) + + # Create CIFS server on VDM + cifs_server_args = { + 'name': self.cifs_server.cifs_server_name, + 'interface_ip': self.cifs_server.ip_address1, + 'domain_name': self.cifs_server.domain_name, + 'user_name': self.cifs_server.domain_user, + 'password': self.cifs_server.domain_password, + 'mover_name': self.vdm.vdm_name, + 'is_vdm': True, + } + context.create(cifs_server_args) + + # Create CIFS server on VDM + cifs_server_args = { + 'name': self.cifs_server.cifs_server_name, + 'interface_ip': self.cifs_server.ip_address1, + 'domain_name': self.cifs_server.domain_name, + 'user_name': self.cifs_server.domain_user, + 'password': self.cifs_server.domain_password, + 'mover_name': self.vdm.vdm_name, + 'is_vdm': True, + } + context.create(cifs_server_args) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.cifs_server.req_create(self.mover.mover_id, False)), + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_create(self.vdm.vdm_id)), + mock.call(self.cifs_server.req_create(self.vdm.vdm_id)), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_create_cifs_server_already_exist(self): + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.cifs_server.resp_task_error()) + self.hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + + context = self.manager.getStorageContext('CIFSServer') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + @mock.patch('time.sleep') + def test_create_cifs_server_invalid_mover_id(self, sleep_mock): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.cifs_server.resp_invalid_mover_id()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.cifs_server.resp_task_succeed()) + + context = self.manager.getStorageContext('CIFSServer') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + # Create CIFS server on mover + cifs_server_args = { + 'name': self.cifs_server.cifs_server_name, + 'interface_ip': self.cifs_server.ip_address1, + 'domain_name': self.cifs_server.domain_name, + 'user_name': self.cifs_server.domain_user, + 'password': self.cifs_server.domain_password, + 'mover_name': self.mover.mover_name, + 'is_vdm': False, + } + context.create(cifs_server_args) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.cifs_server.req_create(self.mover.mover_id, False)), + mock.call(self.mover.req_get_ref()), + mock.call(self.cifs_server.req_create(self.mover.mover_id, False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + self.assertTrue(sleep_mock.called) + + def test_create_cifs_server_with_error(self): + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.cifs_server.resp_task_error()) + self.hook.append(self.cifs_server.resp_get_error()) + + context = self.manager.getStorageContext('CIFSServer') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + # Create CIFS server on VDM + cifs_server_args = { + 'name': self.cifs_server.cifs_server_name, + 'interface_ip': self.cifs_server.ip_address1, + 'domain_name': self.cifs_server.domain_name, + 'user_name': self.cifs_server.domain_user, + 'password': self.cifs_server.domain_password, + 'mover_name': self.vdm.vdm_name, + 'is_vdm': True, + } + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.create, + cifs_server_args) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_create(self.vdm.vdm_id)), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_all_cifs_server(self): + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + self.hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + + context = self.manager.getStorageContext('CIFSServer') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get_all(self.vdm.vdm_name) + self.assertEqual(constants.STATUS_OK, status) + self.assertIn(self.vdm.vdm_name, context.cifs_server_map) + + # Get CIFS server from the cache + status, out = context.get_all(self.vdm.vdm_name) + self.assertEqual(constants.STATUS_OK, status) + self.assertIn(self.vdm.vdm_name, context.cifs_server_map) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + @mock.patch('time.sleep') + def test_get_all_cifs_server_invalid_mover_id(self, sleep_mock): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.cifs_server.resp_invalid_mover_id()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.mover.mover_id, is_vdm=False, join_domain=True)) + + context = self.manager.getStorageContext('CIFSServer') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get_all(self.mover.mover_name, False) + self.assertEqual(constants.STATUS_OK, status) + self.assertIn(self.mover.mover_name, context.cifs_server_map) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.cifs_server.req_get(self.mover.mover_id, False)), + mock.call(self.mover.req_get_ref()), + mock.call(self.cifs_server.req_get(self.mover.mover_id, False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + self.assertTrue(sleep_mock.called) + + def test_get_cifs_server(self): + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + + context = self.manager.getStorageContext('CIFSServer') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + status, out = context.get(name=self.cifs_server.cifs_server_name, + mover_name=self.vdm.vdm_name) + self.assertEqual(constants.STATUS_OK, status) + property_map = { + 'name', + 'compName', + 'Aliases', + 'type', + 'interfaces', + 'domain', + 'domainJoined', + 'mover', + 'moverIdIsVdm', + } + for prop in property_map: + self.assertIn(prop, out) + + context.get(name=self.cifs_server.cifs_server_name, + mover_name=self.vdm.vdm_name) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_modify_cifs_server(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.cifs_server.resp_task_succeed()) + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.cifs_server.resp_task_succeed()) + + context = self.manager.getStorageContext('CIFSServer') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + cifs_server_args = { + 'name': self.cifs_server.cifs_server_name[-14:], + 'join_domain': True, + 'user_name': self.cifs_server.domain_user, + 'password': self.cifs_server.domain_password, + 'mover_name': self.mover.mover_name, + 'is_vdm': False, + } + context.modify(cifs_server_args) + + cifs_server_args = { + 'name': self.cifs_server.cifs_server_name[-14:], + 'join_domain': False, + 'user_name': self.cifs_server.domain_user, + 'password': self.cifs_server.domain_password, + 'mover_name': self.vdm.vdm_name, + } + context.modify(cifs_server_args) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.cifs_server.req_modify( + mover_id=self.mover.mover_id, is_vdm=False, join_domain=True)), + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_modify( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_modify_cifs_server_but_unjoin_domain(self): + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.cifs_server.resp_modify_but_unjoin_domain()) + + context = self.manager.getStorageContext('CIFSServer') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + cifs_server_args = { + 'name': self.cifs_server.cifs_server_name[-14:], + 'join_domain': False, + 'user_name': self.cifs_server.domain_user, + 'password': self.cifs_server.domain_password, + 'mover_name': self.vdm.vdm_name, + } + + context.modify(cifs_server_args) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_modify( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_modify_cifs_server_but_already_join_domain(self): + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append( + self.cifs_server.resp_modify_but_already_join_domain()) + + context = self.manager.getStorageContext('CIFSServer') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + cifs_server_args = { + 'name': self.cifs_server.cifs_server_name[-14:], + 'join_domain': True, + 'user_name': self.cifs_server.domain_user, + 'password': self.cifs_server.domain_password, + 'mover_name': self.vdm.vdm_name, + } + + context.modify(cifs_server_args) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_modify( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + @mock.patch('time.sleep') + def test_modify_cifs_server_invalid_mover_id(self, sleep_mock): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.cifs_server.resp_invalid_mover_id()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.cifs_server.resp_task_succeed()) + + context = self.manager.getStorageContext('CIFSServer') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + cifs_server_args = { + 'name': self.cifs_server.cifs_server_name[-14:], + 'join_domain': True, + 'user_name': self.cifs_server.domain_user, + 'password': self.cifs_server.domain_password, + 'mover_name': self.mover.mover_name, + 'is_vdm': False, + } + context.modify(cifs_server_args) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.cifs_server.req_modify( + mover_id=self.mover.mover_id, is_vdm=False, join_domain=True)), + mock.call(self.mover.req_get_ref()), + mock.call(self.cifs_server.req_modify( + mover_id=self.mover.mover_id, is_vdm=False, join_domain=True)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + self.assertTrue(sleep_mock.called) + + def test_modify_cifs_server_with_error(self): + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.cifs_server.resp_task_error()) + + context = self.manager.getStorageContext('CIFSServer') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + cifs_server_args = { + 'name': self.cifs_server.cifs_server_name[-14:], + 'join_domain': False, + 'user_name': self.cifs_server.domain_user, + 'password': self.cifs_server.domain_password, + 'mover_name': self.vdm.vdm_name, + } + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.modify, + cifs_server_args) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_modify( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_cifs_server(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.mover.mover_id, is_vdm=False, join_domain=True)) + self.hook.append(self.cifs_server.resp_task_succeed()) + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=False)) + self.hook.append(self.cifs_server.resp_task_succeed()) + + context = self.manager.getStorageContext('CIFSServer') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(computer_name=self.cifs_server.cifs_server_name, + mover_name=self.mover.mover_name, + is_vdm=False) + + context.delete(computer_name=self.cifs_server.cifs_server_name, + mover_name=self.vdm.vdm_name) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.cifs_server.req_get(self.mover.mover_id, False)), + mock.call(self.cifs_server.req_delete(self.mover.mover_id, False)), + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + mock.call(self.cifs_server.req_delete(self.vdm.vdm_id)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_cifs_server_but_not_found(self): + self.hook.append(self.mover.resp_get_without_value()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.cifs_server.resp_get_without_value()) + + context = self.manager.getStorageContext('CIFSServer') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(computer_name=self.cifs_server.cifs_server_name, + mover_name=self.mover.mover_name, + is_vdm=False) + + context.delete(computer_name=self.cifs_server.cifs_server_name, + mover_name=self.mover.mover_name, + is_vdm=False) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_get_ref()), + mock.call(self.cifs_server.req_get(self.mover.mover_id, False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_cifs_server_with_error(self): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.mover.mover_id, is_vdm=False, join_domain=True)) + self.hook.append(self.cifs_server.resp_task_error()) + + context = self.manager.getStorageContext('CIFSServer') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.delete, + computer_name=self.cifs_server.cifs_server_name, + mover_name=self.mover.mover_name, + is_vdm=False) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.cifs_server.req_get(self.mover.mover_id, False)), + mock.call(self.cifs_server.req_delete(self.mover.mover_id, False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + +class CIFSShareTestCase(StorageObjectTestCaseBase): + def setUp(self): + super(self.__class__, self).setUp() + self.hook = utils.RequestSideEffect() + self.ssh_hook = utils.SSHSideEffect() + + def test_create_cifs_share(self): + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.cifs_share.resp_task_succeed()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.cifs_share.resp_task_succeed()) + + context = self.manager.getStorageContext('CIFSShare') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.create(name=self.cifs_share.share_name, + server_name=self.cifs_share.cifs_server_name[-14:], + mover_name=self.vdm.vdm_name, + is_vdm=True) + + context.create(name=self.cifs_share.share_name, + server_name=self.cifs_share.cifs_server_name[-14:], + mover_name=self.mover.mover_name, + is_vdm=False) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_share.req_create(self.vdm.vdm_id)), + mock.call(self.mover.req_get_ref()), + mock.call(self.cifs_share.req_create(self.mover.mover_id, False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + @mock.patch('time.sleep') + def test_create_cifs_share_invalid_mover_id(self, sleep_mock): + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.cifs_share.resp_invalid_mover_id()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.cifs_share.resp_task_succeed()) + + context = self.manager.getStorageContext('CIFSShare') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.create(name=self.cifs_share.share_name, + server_name=self.cifs_share.cifs_server_name[-14:], + mover_name=self.mover.mover_name, + is_vdm=False) + + expected_calls = [ + mock.call(self.mover.req_get_ref()), + mock.call(self.cifs_share.req_create(self.mover.mover_id, False)), + mock.call(self.mover.req_get_ref()), + mock.call(self.cifs_share.req_create(self.mover.mover_id, False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + self.assertTrue(sleep_mock.called) + + def test_create_cifs_share_with_error(self): + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.cifs_share.resp_task_error()) + + context = self.manager.getStorageContext('CIFSShare') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.create, + name=self.cifs_share.share_name, + server_name=self.cifs_share.cifs_server_name[-14:], + mover_name=self.vdm.vdm_name, + is_vdm=True) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_share.req_create(self.vdm.vdm_id)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_cifs_share(self): + self.hook.append(self.cifs_share.resp_get_succeed(self.vdm.vdm_id)) + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.cifs_share.resp_task_succeed()) + self.hook.append(self.cifs_share.resp_get_succeed(self.mover.mover_id, + False)) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.cifs_share.resp_task_succeed()) + + context = self.manager.getStorageContext('CIFSShare') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(name=self.cifs_share.share_name, + mover_name=self.vdm.vdm_name, + is_vdm=True) + + context.delete(name=self.cifs_share.share_name, + mover_name=self.mover.mover_name, + is_vdm=False) + + expected_calls = [ + mock.call(self.cifs_share.req_get()), + mock.call(self.vdm.req_get()), + mock.call(self.cifs_share.req_delete(self.vdm.vdm_id)), + mock.call(self.cifs_share.req_get()), + mock.call(self.mover.req_get_ref()), + mock.call(self.cifs_share.req_delete(self.mover.mover_id, False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_delete_cifs_share_not_found(self): + self.hook.append(self.cifs_share.resp_get_error()) + self.hook.append(self.cifs_share.resp_get_without_value()) + + context = self.manager.getStorageContext('CIFSShare') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.delete, + name=self.cifs_share.share_name, + mover_name=self.vdm.vdm_name, + is_vdm=True) + + context.delete(name=self.cifs_share.share_name, + mover_name=self.vdm.vdm_name, + is_vdm=True) + + expected_calls = [ + mock.call(self.cifs_share.req_get()), + mock.call(self.cifs_share.req_get()), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + @mock.patch('time.sleep') + def test_delete_cifs_share_invalid_mover_id(self, sleep_mock): + self.hook.append(self.cifs_share.resp_get_succeed(self.mover.mover_id, + False)) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.cifs_share.resp_invalid_mover_id()) + self.hook.append(self.mover.resp_get_ref_succeed()) + self.hook.append(self.cifs_share.resp_task_succeed()) + + context = self.manager.getStorageContext('CIFSShare') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.delete(name=self.cifs_share.share_name, + mover_name=self.mover.mover_name, + is_vdm=False) + + expected_calls = [ + mock.call(self.cifs_share.req_get()), + mock.call(self.mover.req_get_ref()), + mock.call(self.cifs_share.req_delete(self.mover.mover_id, False)), + mock.call(self.mover.req_get_ref()), + mock.call(self.cifs_share.req_delete(self.mover.mover_id, False)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + self.assertTrue(sleep_mock.called) + + def test_delete_cifs_share_with_error(self): + self.hook.append(self.cifs_share.resp_get_succeed(self.vdm.vdm_id)) + self.hook.append(self.vdm.resp_get_succeed()) + self.hook.append(self.cifs_share.resp_task_error()) + + context = self.manager.getStorageContext('CIFSShare') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.delete, + name=self.cifs_share.share_name, + mover_name=self.vdm.vdm_name, + is_vdm=True) + + expected_calls = [ + mock.call(self.cifs_share.req_get()), + mock.call(self.vdm.req_get()), + mock.call(self.cifs_share.req_delete(self.vdm.vdm_id)), + ] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_get_cifs_share(self): + self.hook.append(self.cifs_share.resp_get_succeed(self.vdm.vdm_id)) + + context = self.manager.getStorageContext('CIFSShare') + context.conn['XML'].request = utils.EMCMock(side_effect=self.hook) + + context.get(self.cifs_share.share_name) + + expected_calls = [mock.call(self.cifs_share.req_get())] + context.conn['XML'].request.assert_has_calls(expected_calls) + + def test_disable_share_access(self): + self.ssh_hook.append('Command succeeded') + + context = self.manager.getStorageContext('CIFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + context.disable_share_access(share_name=self.cifs_share.share_name, + mover_name=self.vdm.vdm_name) + + ssh_calls = [mock.call(self.cifs_share.cmd_disable_access(), True)] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_disable_share_access_with_error(self): + self.ssh_hook.append(ex=processutils.ProcessExecutionError( + stdout=self.cifs_share.fake_output)) + + context = self.manager.getStorageContext('CIFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.disable_share_access, + share_name=self.cifs_share.share_name, + mover_name=self.vdm.vdm_name) + + ssh_calls = [mock.call(self.cifs_share.cmd_disable_access(), True)] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_allow_share_access(self): + self.ssh_hook.append(self.cifs_share.output_allow_access()) + + context = self.manager.getStorageContext('CIFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + context.allow_share_access(mover_name=self.vdm.vdm_name, + share_name=self.cifs_share.share_name, + user_name=self.cifs_server.domain_user, + domain=self.cifs_server.domain_name, + access=constants.CIFS_ACL_FULLCONTROL) + + ssh_calls = [mock.call(self.cifs_share.cmd_change_access(), True)] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_allow_share_access_duplicate_ACE(self): + expt_dup_ace = processutils.ProcessExecutionError( + stdout=self.cifs_share.output_allow_access_but_duplicate_ace()) + self.ssh_hook.append(ex=expt_dup_ace) + + context = self.manager.getStorageContext('CIFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + context.allow_share_access(mover_name=self.vdm.vdm_name, + share_name=self.cifs_share.share_name, + user_name=self.cifs_server.domain_user, + domain=self.cifs_server.domain_name, + access=constants.CIFS_ACL_FULLCONTROL) + + ssh_calls = [mock.call(self.cifs_share.cmd_change_access(), True)] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_allow_share_access_with_error(self): + expt_err = processutils.ProcessExecutionError( + self.cifs_share.fake_output) + self.ssh_hook.append(ex=expt_err) + + context = self.manager.getStorageContext('CIFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.allow_share_access, + mover_name=self.vdm.vdm_name, + share_name=self.cifs_share.share_name, + user_name=self.cifs_server.domain_user, + domain=self.cifs_server.domain_name, + access=constants.CIFS_ACL_FULLCONTROL) + + ssh_calls = [mock.call(self.cifs_share.cmd_change_access(), True)] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_deny_share_access(self): + self.ssh_hook.append('Command succeeded') + + context = self.manager.getStorageContext('CIFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + context.deny_share_access(mover_name=self.vdm.vdm_name, + share_name=self.cifs_share.share_name, + user_name=self.cifs_server.domain_user, + domain=self.cifs_server.domain_name, + access=constants.CIFS_ACL_FULLCONTROL) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_change_access(action='revoke'), + True), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_deny_share_access_no_ace(self): + expt_no_ace = processutils.ProcessExecutionError( + stdout=self.cifs_share.output_deny_access_but_no_ace()) + self.ssh_hook.append(ex=expt_no_ace) + + context = self.manager.getStorageContext('CIFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + context.deny_share_access(mover_name=self.vdm.vdm_name, + share_name=self.cifs_share.share_name, + user_name=self.cifs_server.domain_user, + domain=self.cifs_server.domain_name, + access=constants.CIFS_ACL_FULLCONTROL) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_change_access(action='revoke'), + True), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_deny_share_access_but_no_user_found(self): + expt_no_user = processutils.ProcessExecutionError( + stdout=self.cifs_share.output_deny_access_but_no_user_found()) + self.ssh_hook.append(ex=expt_no_user) + + context = self.manager.getStorageContext('CIFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + context.deny_share_access(mover_name=self.vdm.vdm_name, + share_name=self.cifs_share.share_name, + user_name=self.cifs_server.domain_user, + domain=self.cifs_server.domain_name, + access=constants.CIFS_ACL_FULLCONTROL) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_change_access(action='revoke'), + True), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_deny_share_access_with_error(self): + expt_err = processutils.ProcessExecutionError( + self.cifs_share.fake_output) + self.ssh_hook.append(ex=expt_err) + + context = self.manager.getStorageContext('CIFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.deny_share_access, + mover_name=self.vdm.vdm_name, + share_name=self.cifs_share.share_name, + user_name=self.cifs_server.domain_user, + domain=self.cifs_server.domain_name, + access=constants.CIFS_ACL_FULLCONTROL) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_change_access(action='revoke'), + True), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_get_share_access(self): + self.ssh_hook.append(fakes.FakeData.cifs_access) + + context = self.manager.getStorageContext('CIFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + ret = context.get_share_access( + mover_name=self.vdm.vdm_name, + share_name=self.cifs_share.share_name) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_get_access(), True), + ] + self.assertEqual(2, len(ret)) + self.assertEqual(constants.CIFS_ACL_FULLCONTROL, ret['administrator']) + self.assertEqual(constants.CIFS_ACL_FULLCONTROL, ret['guest']) + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_get_share_access_failed(self): + expt_err = processutils.ProcessExecutionError( + stdout=self.nfs_share.fake_output) + self.ssh_hook.append(ex=expt_err) + + context = self.manager.getStorageContext('CIFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.get_share_access, + mover_name=self.vdm.vdm_name, + share_name=self.cifs_share.share_name) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_get_access(), True), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_clear_share_access_has_white_list(self): + self.ssh_hook.append(fakes.FakeData.cifs_access) + self.ssh_hook.append('Command succeeded') + + context = self.manager.getStorageContext('CIFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + to_remove = context.clear_share_access( + mover_name=self.vdm.vdm_name, + share_name=self.cifs_share.share_name, + domain=self.cifs_server.domain_name, + white_list_users=['guest']) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_get_access(), True), + mock.call(self.cifs_share.cmd_change_access(action='revoke'), + True), + ] + self.assertEqual({'administrator'}, to_remove) + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + +class NFSShareTestCase(StorageObjectTestCaseBase): + def setUp(self): + super(self.__class__, self).setUp() + self.ssh_hook = utils.SSHSideEffect() + + def test_create_nfs_share(self): + self.ssh_hook.append(self.nfs_share.output_create()) + + context = self.manager.getStorageContext('NFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + context.create(name=self.nfs_share.share_name, + mover_name=self.vdm.vdm_name) + + ssh_calls = [mock.call(self.nfs_share.cmd_create(), True)] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_create_nfs_share_with_error(self): + expt_err = processutils.ProcessExecutionError( + stdout=self.nfs_share.fake_output) + self.ssh_hook.append(ex=expt_err) + + context = self.manager.getStorageContext('NFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.create, + name=self.nfs_share.share_name, + mover_name=self.vdm.vdm_name) + + ssh_calls = [mock.call(self.nfs_share.cmd_create(), True)] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_delete_nfs_share(self): + self.ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=self.nfs_share.rw_hosts, + ro_hosts=self.nfs_share.ro_hosts)) + self.ssh_hook.append(self.nfs_share.output_delete_succeed()) + + context = self.manager.getStorageContext('NFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + context.delete(name=self.nfs_share.share_name, + mover_name=self.vdm.vdm_name) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get(), False), + mock.call(self.nfs_share.cmd_delete(), True), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_delete_nfs_share_not_found(self): + expt_not_found = processutils.ProcessExecutionError( + stdout=self.nfs_share.output_get_but_not_found()) + self.ssh_hook.append(ex=expt_not_found) + + context = self.manager.getStorageContext('NFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + context.delete(name=self.nfs_share.share_name, + mover_name=self.vdm.vdm_name) + + ssh_calls = [mock.call(self.nfs_share.cmd_get(), False)] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + @mock.patch('time.sleep') + def test_delete_nfs_share_locked(self, sleep_mock): + self.ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=self.nfs_share.rw_hosts, + ro_hosts=self.nfs_share.ro_hosts)) + expt_locked = processutils.ProcessExecutionError( + stdout=self.nfs_share.output_delete_but_locked()) + self.ssh_hook.append(ex=expt_locked) + self.ssh_hook.append(self.nfs_share.output_delete_succeed()) + + context = self.manager.getStorageContext('NFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + context.delete(name=self.nfs_share.share_name, + mover_name=self.vdm.vdm_name) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get(), False), + mock.call(self.nfs_share.cmd_delete(), True), + mock.call(self.nfs_share.cmd_delete(), True), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + self.assertTrue(sleep_mock.called) + + def test_delete_nfs_share_with_error(self): + self.ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=self.nfs_share.rw_hosts, + ro_hosts=self.nfs_share.ro_hosts)) + expt_err = processutils.ProcessExecutionError( + stdout=self.nfs_share.fake_output) + self.ssh_hook.append(ex=expt_err) + + context = self.manager.getStorageContext('NFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.delete, + name=self.nfs_share.share_name, + mover_name=self.vdm.vdm_name) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get(), False), + mock.call(self.nfs_share.cmd_delete(), True), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_get_nfs_share(self): + self.ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=self.nfs_share.rw_hosts, + ro_hosts=self.nfs_share.ro_hosts)) + + context = self.manager.getStorageContext('NFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + context.get(name=self.nfs_share.share_name, + mover_name=self.vdm.vdm_name) + + # Get NFS share from cache + context.get(name=self.nfs_share.share_name, + mover_name=self.vdm.vdm_name) + + ssh_calls = [mock.call(self.nfs_share.cmd_get(), False)] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_get_nfs_share_not_found(self): + expt_not_found = processutils.ProcessExecutionError( + stdout=self.nfs_share.output_get_but_not_found()) + self.ssh_hook.append(ex=expt_not_found) + self.ssh_hook.append(self.nfs_share.output_get_but_not_found()) + + context = self.manager.getStorageContext('NFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + context.get(name=self.nfs_share.share_name, + mover_name=self.vdm.vdm_name) + + context.get(name=self.nfs_share.share_name, + mover_name=self.vdm.vdm_name) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get(), False), + mock.call(self.nfs_share.cmd_get(), False), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_get_nfs_share_with_error(self): + expt_err = processutils.ProcessExecutionError( + stdout=self.nfs_share.fake_output) + self.ssh_hook.append(ex=expt_err) + + context = self.manager.getStorageContext('NFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.get, + name=self.nfs_share.share_name, + mover_name=self.vdm.vdm_name) + + ssh_calls = [mock.call(self.nfs_share.cmd_get(), False)] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_allow_share_access(self): + rw_hosts = copy.deepcopy(self.nfs_share.rw_hosts) + rw_hosts.append(self.nfs_share.nfs_host_ip) + + ro_hosts = copy.deepcopy(self.nfs_share.ro_hosts) + ro_hosts.append(self.nfs_share.nfs_host_ip) + + self.ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=self.nfs_share.rw_hosts, + ro_hosts=self.nfs_share.ro_hosts)) + self.ssh_hook.append(self.nfs_share.output_set_access_success()) + self.ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=rw_hosts, ro_hosts=self.nfs_share.ro_hosts)) + self.ssh_hook.append(self.nfs_share.output_set_access_success()) + self.ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=self.nfs_share.rw_hosts, ro_hosts=ro_hosts)) + self.ssh_hook.append(self.nfs_share.output_set_access_success()) + self.ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=rw_hosts, ro_hosts=self.nfs_share.ro_hosts)) + + context = self.manager.getStorageContext('NFSShare') + context.conn['SSH'].run_ssh = utils.EMCNFSShareMock( + side_effect=self.ssh_hook) + + context.allow_share_access(share_name=self.nfs_share.share_name, + host_ip=self.nfs_share.nfs_host_ip, + mover_name=self.vdm.vdm_name, + access_level=const.ACCESS_LEVEL_RW) + + context.allow_share_access(share_name=self.nfs_share.share_name, + host_ip=self.nfs_share.nfs_host_ip, + mover_name=self.vdm.vdm_name, + access_level=const.ACCESS_LEVEL_RO) + + context.allow_share_access(share_name=self.nfs_share.share_name, + host_ip=self.nfs_share.nfs_host_ip, + mover_name=self.vdm.vdm_name, + access_level=const.ACCESS_LEVEL_RW) + + context.allow_share_access(share_name=self.nfs_share.share_name, + host_ip=self.nfs_share.nfs_host_ip, + mover_name=self.vdm.vdm_name, + access_level=const.ACCESS_LEVEL_RW) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get()), + mock.call(self.nfs_share.cmd_set_access( + rw_hosts=rw_hosts, ro_hosts=self.nfs_share.ro_hosts)), + mock.call(self.nfs_share.cmd_get()), + mock.call(self.nfs_share.cmd_set_access( + rw_hosts=self.nfs_share.rw_hosts, ro_hosts=ro_hosts)), + mock.call(self.nfs_share.cmd_get()), + mock.call(self.nfs_share.cmd_set_access( + rw_hosts=rw_hosts, ro_hosts=self.nfs_share.ro_hosts)), + mock.call(self.nfs_share.cmd_get()), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_allow_share_access_not_found(self): + expt_not_found = processutils.ProcessExecutionError( + stdout=self.nfs_share.output_get_but_not_found()) + self.ssh_hook.append(ex=expt_not_found) + + context = self.manager.getStorageContext('NFSShare') + context.conn['SSH'].run_ssh = utils.EMCNFSShareMock( + side_effect=self.ssh_hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.allow_share_access, + share_name=self.nfs_share.share_name, + host_ip=self.nfs_share.nfs_host_ip, + mover_name=self.vdm.vdm_name, + access_level=const.ACCESS_LEVEL_RW) + + ssh_calls = [mock.call(self.nfs_share.cmd_get())] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_deny_rw_share_access(self): + rw_hosts = copy.deepcopy(self.nfs_share.rw_hosts) + rw_hosts.append(self.nfs_share.nfs_host_ip) + + self.ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=rw_hosts, ro_hosts=self.nfs_share.ro_hosts)) + self.ssh_hook.append(self.nfs_share.output_set_access_success()) + self.ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=self.nfs_share.rw_hosts, + ro_hosts=self.nfs_share.ro_hosts)) + + context = self.manager.getStorageContext('NFSShare') + context.conn['SSH'].run_ssh = utils.EMCNFSShareMock( + side_effect=self.ssh_hook) + + context.deny_share_access(share_name=self.nfs_share.share_name, + host_ip=self.nfs_share.nfs_host_ip, + mover_name=self.vdm.vdm_name) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get()), + mock.call(self.nfs_share.cmd_set_access(self.nfs_share.rw_hosts, + self.nfs_share.ro_hosts)), + mock.call(self.nfs_share.cmd_get()), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_clear_share_access(self): + hosts = ['192.168.1.1', '192.168.1.3'] + + self.ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=self.nfs_share.rw_hosts, + ro_hosts=self.nfs_share.ro_hosts)) + self.ssh_hook.append(self.nfs_share.output_set_access_success()) + self.ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=[hosts[0]], ro_hosts=[hosts[1]])) + + context = self.manager.getStorageContext('NFSShare') + context.conn['SSH'].run_ssh = utils.EMCNFSShareMock( + side_effect=self.ssh_hook) + + context.clear_share_access(share_name=self.nfs_share.share_name, + mover_name=self.vdm.vdm_name, + white_list_hosts=hosts) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get()), + mock.call(self.nfs_share.cmd_set_access( + rw_hosts=[hosts[0]], ro_hosts=[hosts[1]])), + mock.call(self.nfs_share.cmd_get()), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_deny_ro_share_access(self): + ro_hosts = copy.deepcopy(self.nfs_share.ro_hosts) + ro_hosts.append(self.nfs_share.nfs_host_ip) + + self.ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=self.nfs_share.rw_hosts, ro_hosts=ro_hosts)) + self.ssh_hook.append(self.nfs_share.output_set_access_success()) + self.ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=self.nfs_share.rw_hosts, + ro_hosts=self.nfs_share.ro_hosts)) + + context = self.manager.getStorageContext('NFSShare') + context.conn['SSH'].run_ssh = utils.EMCNFSShareMock( + side_effect=self.ssh_hook) + + context.deny_share_access(share_name=self.nfs_share.share_name, + host_ip=self.nfs_share.nfs_host_ip, + mover_name=self.vdm.vdm_name) + + context.deny_share_access(share_name=self.nfs_share.share_name, + host_ip=self.nfs_share.nfs_host_ip, + mover_name=self.vdm.vdm_name) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get()), + mock.call(self.nfs_share.cmd_set_access(self.nfs_share.rw_hosts, + self.nfs_share.ro_hosts)), + mock.call(self.nfs_share.cmd_get()), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_deny_share_not_found(self): + expt_not_found = processutils.ProcessExecutionError( + stdout=self.nfs_share.output_get_but_not_found()) + self.ssh_hook.append(ex=expt_not_found) + + context = self.manager.getStorageContext('NFSShare') + context.conn['SSH'].run_ssh = utils.EMCNFSShareMock( + side_effect=self.ssh_hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.deny_share_access, + share_name=self.nfs_share.share_name, + host_ip=self.nfs_share.nfs_host_ip, + mover_name=self.vdm.vdm_name) + + ssh_calls = [mock.call(self.nfs_share.cmd_get())] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_deny_rw_share_with_error(self): + rw_hosts = copy.deepcopy(self.nfs_share.rw_hosts) + rw_hosts.append(self.nfs_share.nfs_host_ip) + + self.ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=rw_hosts, ro_hosts=self.nfs_share.ro_hosts)) + expt_not_found = processutils.ProcessExecutionError( + stdout=self.nfs_share.output_get_but_not_found()) + self.ssh_hook.append(ex=expt_not_found) + + context = self.manager.getStorageContext('NFSShare') + context.conn['SSH'].run_ssh = utils.EMCNFSShareMock( + side_effect=self.ssh_hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.deny_share_access, + share_name=self.nfs_share.share_name, + host_ip=self.nfs_share.nfs_host_ip, + mover_name=self.vdm.vdm_name) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get()), + mock.call(self.nfs_share.cmd_set_access(self.nfs_share.rw_hosts, + self.nfs_share.ro_hosts)), + ] + context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls) + + def test_clear_share_access_failed_to_get_share(self): + self.ssh_hook.append("no output.") + + context = self.manager.getStorageContext('NFSShare') + context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook) + + self.assertRaises(exception.EMCVmaxXMLAPIError, + context.clear_share_access, + share_name=self.nfs_share.share_name, + mover_name=self.vdm.vdm_name, + white_list_hosts=None) + + context.conn['SSH'].run_ssh.assert_called_once_with( + self.nfs_share.cmd_get(), False) diff --git a/manila/tests/share/drivers/dell_emc/plugins/vmax/test_utils.py b/manila/tests/share/drivers/dell_emc/plugins/vmax/test_utils.py new file mode 100644 index 0000000000..0815580982 --- /dev/null +++ b/manila/tests/share/drivers/dell_emc/plugins/vmax/test_utils.py @@ -0,0 +1,44 @@ +# Copyright (c) 2016 Dell Inc. or its subsidiaries. +# 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.dell_emc.plugins.vmax import utils +from manila import test + + +@ddt.ddt +class VMAXUtilsTestCase(test.TestCase): + + @ddt.data({'full': ['cge-1-0', 'cge-1-1', 'cge-3-0', + 'cge-3-1', 'cge-12-3'], + 'matchers': ['cge-?-0', 'cge-3*', 'foo'], + 'matched': set(['cge-1-0', 'cge-3-0', + 'cge-3-1']), + 'unmatched': set(['cge-1-1', 'cge-12-3'])}, + {'full': ['cge-1-0', 'cge-1-1'], + 'matchers': ['cge-1-0'], + 'matched': set(['cge-1-0']), + 'unmatched': set(['cge-1-1'])}, + {'full': ['cge-1-0', 'cge-1-1'], + 'matchers': ['foo'], + 'matched': set([]), + 'unmatched': set(['cge-1-0', 'cge-1-1'])}) + @ddt.unpack + def test_do_match_any(self, full, matchers, matched, unmatched): + real_matched, real_unmatched = utils.do_match_any( + full, matchers) + self.assertEqual(matched, real_matched) + self.assertEqual(unmatched, real_unmatched) diff --git a/manila/tests/share/drivers/dell_emc/plugins/vmax/utils.py b/manila/tests/share/drivers/dell_emc/plugins/vmax/utils.py new file mode 100644 index 0000000000..a98b0d9370 --- /dev/null +++ b/manila/tests/share/drivers/dell_emc/plugins/vmax/utils.py @@ -0,0 +1,167 @@ +# Copyright (c) 2016 Dell Inc. or its subsidiaries. +# 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 doctest + +from lxml import doctestcompare +import mock +import six + + +CHECKER = doctestcompare.LXMLOutputChecker() +PARSE_XML = doctest.register_optionflag('PARSE_XML') + + +class RequestSideEffect(object): + def __init__(self): + self.actions = [] + self.started = False + + def append(self, resp=None, ex=None): + if not self.started: + self.actions.append((resp, ex)) + + def __call__(self, *args, **kwargs): + if not self.started: + self.started = True + self.actions.reverse() + item = self.actions.pop() + if item[1]: + raise item[1] + else: + return item[0] + + +class SSHSideEffect(object): + def __init__(self): + self.actions = [] + self.started = False + + def append(self, resp=None, err=None, ex=None): + if not self.started: + self.actions.append((resp, err, ex)) + + def __call__(self, rel_url, req_data=None, method=None, + return_rest_err=True, *args, **kwargs): + if not self.started: + self.started = True + self.actions.reverse() + item = self.actions.pop() + if item[2]: + raise item[2] + else: + if return_rest_err: + return item[0:2] + else: + return item[1] + + +class EMCMock(mock.Mock): + def _get_req_from_call(self, call): + if len(call) == 3: + return call[1][0] + elif len(call) == 2: + return call[0][0] + + def assert_has_calls(self, calls): + if len(calls) != len(self.mock_calls): + raise AssertionError( + 'Mismatch error.\nExpected: %r\n' + 'Actual: %r' % (calls, self.mock_calls) + ) + + iter_expect = iter(calls) + iter_actual = iter(self.mock_calls) + + while True: + try: + expect = self._get_req_from_call(next(iter_expect)) + actual = self._get_req_from_call(next(iter_actual)) + except StopIteration: + return True + + if not isinstance(expect, six.binary_type): + expect = six.b(expect) + if not isinstance(actual, six.binary_type): + actual = six.b(actual) + if not CHECKER.check_output(expect, actual, PARSE_XML): + raise AssertionError( + 'Mismatch error.\nExpected: %r\n' + 'Actual: %r' % (calls, self.mock_calls) + ) + + +class EMCNFSShareMock(mock.Mock): + def assert_has_calls(self, calls): + if len(calls) != len(self.mock_calls): + raise AssertionError( + 'Mismatch error.\nExpected: %r\n' + 'Actual: %r' % (calls, self.mock_calls) + ) + + iter_expect = iter(calls) + iter_actual = iter(self.mock_calls) + + while True: + try: + expect = next(iter_expect)[1][0] + actual = next(iter_actual)[1][0] + except StopIteration: + return True + + if not self._option_check(expect, actual): + raise AssertionError( + 'Mismatch error.\nExpected: %r\n' + 'Actual: %r' % (calls, self.mock_calls) + ) + + def _option_parser(self, option): + option_map = {} + for item in option.split(','): + key, value = item.split('=') + option_map[key] = value + + return option_map + + @staticmethod + def _opt_value_from_map(opt_map, key): + value = opt_map.get(key) + if value: + ret = set(value.split(':')) + else: + ret = set() + return ret + + def _option_check(self, expect, actual): + if '-option' in actual and '-option' in expect: + exp_option = expect[expect.index('-option') + 1] + act_option = actual[actual.index('-option') + 1] + + exp_opt_map = self._option_parser(exp_option) + act_opt_map = self._option_parser(act_option) + + for key in exp_opt_map: + exp_set = self._opt_value_from_map(exp_opt_map, key) + act_set = self._opt_value_from_map(act_opt_map, key) + if exp_set != act_set: + return False + + return True + + +def patch_get_managed_ports(*arg, **kwargs): + return mock.patch('manila.share.drivers.dell_emc.plugins.vmax.connection.' + 'VMAXStorageConnection.get_managed_ports', + mock.Mock(*arg, **kwargs)) diff --git a/releasenotes/notes/vmax-manila-support-7c655fc094c09367.yaml b/releasenotes/notes/vmax-manila-support-7c655fc094c09367.yaml new file mode 100644 index 0000000000..12842804b5 --- /dev/null +++ b/releasenotes/notes/vmax-manila-support-7c655fc094c09367.yaml @@ -0,0 +1,3 @@ +--- +features: + - Support for VMAX in Manila. diff --git a/setup.cfg b/setup.cfg index 420051e066..7d6da73bb4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -62,6 +62,7 @@ manila.share.drivers.dell_emc.plugins = vnx = manila.share.drivers.dell_emc.plugins.vnx.connection:VNXStorageConnection unity = manila.share.drivers.dell_emc.plugins.unity.connection:UnityStorageConnection isilon = manila.share.drivers.dell_emc.plugins.isilon.isilon:IsilonStorageConnection + vmax = manila.share.drivers.dell_emc.plugins.vmax.connection:VMAXStorageConnection manila.tests.scheduler.fakes = FakeWeigher1 = manila.tests.scheduler.fakes:FakeWeigher1 FakeWeigher2 = manila.tests.scheduler.fakes:FakeWeigher2