Add EMC Unity Driver for Manila

EMC Unity arrays are capable of support manila.
Add a new Unity plugin in manila which allows user to create NFS/CIFS
share with a Unity backend.

The plugin should support following APIs:

* connect: Connect to the Unity Storage.
* check_for_setup_error: No implementation.
* create_share: Create a share and export it based on the protocol used
  (NFS or CIFS).
* create_share_from_snapshot: Create a share from a snapshot - clone a
  snapshot.
* delete_share: Delete a share.
* extend_share: Extend the maximum size of a share.
* create_snapshot: Create a snapshot for the specified share.
* delete_snapshot: Delete the snapshot of the share.
* allow_access: Allow access of a user/host to a share.
* deny_access: Remove the access of a user/host to the share.
* ensure_share: Check whether share exists or not.
* update_share_stats: Retrieve share related statistics from Unity.
* get_network_allocatins_number: Returns number of network allocations
  for creating VIFs.
* setup_server: Set up and configures share server with given network
  parameters.
* teardown_server: Tear down the share server.

DocImpact
Co-Authored-By: Cedric Zhuang <cedric.zhuang@emc.com>
Change-Id: Ic520539341fa19ec5c6c6b85c3c1dcecf70e5141
Implements: blueprint emc-unity-manila-support
This commit is contained in:
Jay Xu 2016-04-15 18:08:45 +08:00 committed by Cedric Zhuang
parent 20adb836c3
commit 86383ff900
22 changed files with 3574 additions and 14 deletions

View File

@ -0,0 +1,126 @@
..
Copyright (c) 2014 EMC Corporation
All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Unity Driver
============
EMC manila driver framework (EMCShareDriver) utilizes the EMC storage products
to provide the shared filesystems to OpenStack. The EMC manila driver is a
plugin based driver which is designed to use different plugins to manage
different EMC storage products.
Unity plugin is the plugin which manages the Unity Storage System to provide
shared filesystems. EMC driver framework with Unity plugin is referred to as
Unity driver in this document.
This driver performs the operations on Unity by REST API. Each backend manages
one Unity Storage System. Multiple manila backends need to be configured to
manage multiple Unity Storage Systems.
Requirements
------------
- Unity OE 4.0.1 or higher.
- StorOps 0.2.17 or higher is installed on Manila node.
- Following licenses are activated on Unity:
* CIFS/SMB Support
* Network File System (NFS)
* Thin Provisioning
* Fiber Channel (FC)
* Internet Small Computer System Interface (iSCSI)
Supported Operations
--------------------
In detail, users are allowed to do following operation with EMC Unity
Storage Systems.
* Create/delete a NFS share.
* Create/delete a CIFS share.
* Extend the size of a share.
* Modify the host access privilege of a NFS share.
* Modify the user access privilege of a CIFS share.
* Take/Delete snapshot of a share.
* Create a new share from snapshot.
Supported Network Topologies
----------------------------
flat, VLAN
Pre-Configurations
------------------
On Manila Node
``````````````
StorOps library is required to run Unity driver.
Please install it with the pip command.
You may need root privilege to install python libraries.
::
pip install storops
Configurations
--------------
Following configurations are introduced for the Unity plugin.
* emc_interface_ports: White list of the ports to be used for connection.
Wild card character is supported.
Examples: spa_eth1, spa_*, *
* emc_nas_server_pool: The pool used to persist the meta-data of created
NAS servers. Wild card character is supported.
Examples: pool_1, pool_*, *
API Implementations
-------------------
Following driver features are implemented in the plugin.
* create_share: Create a share and export it based on the protocol used
(NFS or CIFS).
* create_share_from_snapshot: Create a share from a snapshot - clone a
snapshot.
* delete_share: Delete a share.
* extend_share: Extend the maximum size of a share.
* create_snapshot: Create a snapshot for the specified share.
* delete_snapshot: Delete the snapshot of the share.
* update_access: recover, add or delete user/host access to a share.
* allow_access: Allow access (read write/read only) of a user to a
CIFS share. Allow access (read write/read only) of a host to a NFS
share.
* deny_access: Remove access (read write/read only) of a user from
a CIFS share. Remove access (read write/read only) of a host from a
NFS share.
* ensure_share: Check whether share exists or not.
* update_share_stats: Retrieve share related statistics from Unity.
* get_network_allocations_number: Returns number of network allocations for
creating VIFs.
* setup_server: Set up and configures share server with given network
parameters.
* teardown_server: Tear down the share server.
Restrictions
------------
* EMC Unity does not support the same IP in different VLANs.

View File

@ -108,6 +108,7 @@ Share backends
netapp_cluster_mode_driver
emc_isilon_driver
emc_vnx_driver
emc_unity_driver
generic_driver
glusterfs_driver
glusterfs_native_driver

View File

@ -41,6 +41,8 @@ Mapping of share drivers and share features support
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
| EMC VNX | J | \- | \- | \- | J | J | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
| EMC Unity | N | \- | N | \- | N | N | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
| EMC Isilon | K | \- | M | \- | K | K | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
| Red Hat GlusterFS | J | \- | \- | \- | volume layout (L) | volume layout (L) | \- |
@ -86,6 +88,8 @@ Mapping of share drivers and share access rules support
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| EMC VNX | NFS (J) | CIFS (J) | \- | \- | NFS (L) | CIFS (L) | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| EMC Unity | NFS (N) | CIFS (N) | \- | \- | NFS (N) | CIFS (N) | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| EMC Isilon | NFS,CIFS (K) | CIFS (M) | \- | \- | NFS (M) | CIFS (M) | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| Red Hat GlusterFS | NFS (J) | \- | \- | \- | \- | \- | \- | \- |
@ -129,6 +133,8 @@ Mapping of share drivers and security services support
+----------------------------------------+------------------+-----------------+------------------+
| EMC VNX | J | \- | \- |
+----------------------------------------+------------------+-----------------+------------------+
| EMC Unity | N | \- | \- |
+----------------------------------------+------------------+-----------------+------------------+
| EMC Isilon | \- | \- | \- |
+----------------------------------------+------------------+-----------------+------------------+
| Red Hat GlusterFS | \- | \- | \- |
@ -172,6 +178,8 @@ Mapping of share drivers and common capabilities
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+
| EMC VNX | J | \- | \- | \- | \- | L | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+
| EMC Unity | N | \- | \- | \- | N | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+
| EMC Isilon | \- | K | \- | \- | \- | L | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+
| Red Hat GlusterFS | \- | J | \- | \- | \- | L | \- |

View File

@ -554,6 +554,10 @@ class ShareTypeInUse(ManilaException):
"shares present with the type.")
class IPAddressInUse(InUse):
message = _("IP address %(ip)s is already used.")
class ShareTypeExists(ManilaException):
message = _("Share Type %(id)s already exists.")
@ -645,6 +649,10 @@ class EMCVnxInvalidMoverID(ManilaException):
message = _("Invalid mover or vdm %(id)s.")
class EMCUnityError(ShareBackendException):
message = _("%(err)s")
class HPE3ParInvalidClient(Invalid):
message = _("%(err)s")

View File

@ -26,10 +26,8 @@ from oslo_log import log
from manila.share import driver
from manila.share.drivers.emc import plugin_manager as manager
LOG = log.getLogger(__name__)
EMC_NAS_OPTS = [
cfg.StrOpt('emc_nas_login',
help='User name for the EMC server.'),
@ -44,15 +42,18 @@ EMC_NAS_OPTS = [
default=True,
help='Use secure connection to server.'),
cfg.StrOpt('emc_share_backend',
ignore_case=True,
choices=['isilon', 'vnx', 'unity'],
help='Share backend.'),
cfg.StrOpt('emc_nas_server_container',
default='server_2',
help='Container of share servers.'),
cfg.StrOpt('emc_nas_pool_names',
deprecated_name='emc_nas_pool_name',
help='EMC pool names.'),
cfg.ListOpt('emc_nas_pool_names',
deprecated_name='emc_nas_pool_name',
help='EMC pool names.'),
cfg.StrOpt('emc_nas_root_dir',
help='The root directory where shares will be located.'),
cfg.StrOpt('emc_nas_server_pool',
help='Pool to persist the meta-data of NAS server.'),
cfg.ListOpt('emc_interface_ports',
help='Comma separated list specifying the ports that can be '
'used for share server interfaces. Members of the list '
@ -65,6 +66,7 @@ CONF.register_opts(EMC_NAS_OPTS)
class EMCShareDriver(driver.ShareDriver):
"""EMC specific NAS driver. Allows for NFS and CIFS NAS storage usage."""
def __init__(self, *args, **kwargs):
self.configuration = kwargs.get('configuration', None)
if self.configuration:

View File

@ -0,0 +1,261 @@
# Copyright (c) 2016 EMC Corporation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import random
import six
from oslo_log import log
from oslo_utils import importutils
storops = importutils.try_import('storops')
if storops:
from storops import exception as storops_ex
from storops.unity import enums
from manila.common import constants as const
from manila import exception
from manila.i18n import _, _LI, _LE
LOG = log.getLogger(__name__)
class UnityClient(object):
def __init__(self, host, username, password):
if storops is None:
LOG.error(_LE('StorOps is required to run EMC Unity driver.'))
self.system = storops.UnitySystem(host, username, password)
def create_cifs_share(self, resource, share_name):
"""Create CIFS share from the resource.
:param resource: either UnityFilesystem or UnitySnap object
:param share_name: CIFS share name
:return: UnityCifsShare object
"""
try:
share = resource.create_cifs_share(share_name)
try:
# bug on unity: the enable ace API has bug for snap
# based share. Log the internal error if it happens.
share.enable_ace()
except storops_ex.UnityException:
msg = _LE('Failed to enabled ACE for share: {}.')
LOG.exception(msg.format(share_name))
return share
except storops_ex.UnitySmbShareNameExistedError:
return self.get_share(share_name, 'CIFS')
def create_nfs_share(self, resource, share_name):
"""Create NFS share from the resource.
:param resource: either UnityFilesystem or UnitySnap object
:param share_name: NFS share name
:return: UnityNfsShare object
"""
try:
return resource.create_nfs_share(share_name)
except storops_ex.UnityNfsShareNameExistedError:
return self.get_share(share_name, 'NFS')
def get_share(self, name, share_proto):
# Validate the share protocol
proto = share_proto.upper()
if proto == 'CIFS':
return self.system.get_cifs_share(name=name)
elif proto == 'NFS':
return self.system.get_nfs_share(name=name)
else:
raise exception.BadConfigurationException(
reason=_('Invalid NAS protocol supplied: %s.') % share_proto)
@staticmethod
def delete_share(share):
share.delete()
def create_filesystem(self, pool, nas_server, share_name, size, proto):
try:
return pool.create_filesystem(nas_server,
share_name,
size,
proto=proto)
except storops_ex.UnityFileSystemNameAlreadyExisted:
LOG.debug('Filesystem %s already exists, '
'ignoring filesystem creation.', share_name)
return self.system.get_filesystem(name=share_name)
@staticmethod
def delete_filesystem(filesystem):
try:
filesystem.delete()
except storops_ex.UnityResourceNotFoundError:
LOG.info(_LI('Filesystem %s is already removed.'), filesystem.name)
def create_nas_server(self, name, sp, pool):
try:
return self.system.create_nas_server(name, sp, pool)
except storops_ex.UnityNasServerNameUsedError:
LOG.info(_LI('Share server %s already exists, ignoring share '
'server creation.'), name)
return self.get_nas_server(name)
def get_nas_server(self, name):
try:
return self.system.get_nas_server(name=name)
except storops_ex.UnityResourceNotFoundError:
LOG.info(_LI('NAS server %s not found.'), name)
raise
def delete_nas_server(self, name, username=None, password=None):
try:
nas_server = self.get_nas_server(name=name)
nas_server.delete(username=username, password=password)
except storops_ex.UnityResourceNotFoundError:
LOG.info(_LI('NAS server %s not found.'), name)
@staticmethod
def create_dns_server(nas_server, domain, dns_ip):
try:
nas_server.create_dns_server(domain, dns_ip)
except storops_ex.UnityOneDnsPerNasServerError:
LOG.info(_LI('DNS server %s already exists, '
'ignoring DNS server creation.'), domain)
@staticmethod
def create_interface(nas_server, ip_addr, netmask, gateway, ports,
vlan_id=None):
port_list = list(ports)
random.shuffle(port_list)
try:
nas_server.create_file_interface(port_list[0],
ip_addr,
netmask=netmask,
gateway=gateway,
vlan_id=vlan_id)
except storops_ex.UnityIpAddressUsedError:
raise exception.IPAddressInUse(ip=ip_addr)
@staticmethod
def enable_cifs_service(nas_server, domain, username, password):
try:
nas_server.enable_cifs_service(
nas_server.file_interface,
domain=domain,
domain_username=username,
domain_password=password)
except storops_ex.UnitySmbNameInUseError:
LOG.info(_LI('CIFS service on NAS server %s is '
'already enabled.'), nas_server.name)
@staticmethod
def enable_nfs_service(nas_server):
try:
nas_server.enable_nfs_service()
except storops_ex.UnityNfsAlreadyEnabledError:
LOG.info(_LI('NFS service on NAS server %s is '
'already enabled.'), nas_server.name)
@staticmethod
def create_snapshot(filesystem, name):
access_type = enums.FilesystemSnapAccessTypeEnum.CHECKPOINT
try:
return filesystem.create_snap(name, fs_access_type=access_type)
except storops_ex.UnitySnapNameInUseError:
LOG.info(_LI('Snapshot %(snap)s on Filesystem %(fs)s already '
'exists.'), {'snap': name, 'fs': filesystem.name})
def create_snap_of_snap(self, src_snap, dst_snap_name, snap_type):
access_type = enums.FilesystemSnapAccessTypeEnum.PROTOCOL
if snap_type == 'checkpoint':
access_type = enums.FilesystemSnapAccessTypeEnum.CHECKPOINT
if isinstance(src_snap, six.string_types):
snap = self.get_snapshot(name=src_snap)
else:
snap = src_snap
try:
return snap.create_snap(dst_snap_name, fs_access_type=access_type)
except storops_ex.UnitySnapNameInUseError:
return self.get_snapshot(dst_snap_name)
def get_snapshot(self, name):
return self.system.get_snap(name=name)
@staticmethod
def delete_snapshot(snap):
try:
snap.delete()
except storops_ex.UnityResourceNotFoundError:
LOG.info(_LI('Snapshot %s is already removed.'), snap.name)
def get_pool(self, name=None):
return self.system.get_pool(name=name)
def get_storage_processor(self, sp_id):
sp = self.system.get_sp(sp_id)
return sp if sp.existed else None
def cifs_clear_access(self, share_name, white_list=None):
share = self.system.get_cifs_share(name=share_name)
share.clear_access(white_list)
def nfs_clear_access(self, share_name, white_list=None):
share = self.system.get_nfs_share(name=share_name)
share.clear_access(white_list, force_create_host=True)
def cifs_allow_access(self, share_name, user_name, access_level):
share = self.system.get_cifs_share(name=share_name)
if access_level == const.ACCESS_LEVEL_RW:
cifs_access = enums.ACEAccessLevelEnum.WRITE
else:
cifs_access = enums.ACEAccessLevelEnum.READ
share.add_ace(user=user_name, access_level=cifs_access)
def nfs_allow_access(self, share_name, host_ip, access_level):
share = self.system.get_nfs_share(name=share_name)
if access_level == const.ACCESS_LEVEL_RW:
share.allow_read_write_access(host_ip, force_create_host=True)
share.allow_root_access(host_ip, force_create_host=True)
else:
share.allow_read_only_access(host_ip, force_create_host=True)
def cifs_deny_access(self, share_name, user_name):
share = self.system.get_cifs_share(name=share_name)
share.delete_ace(user=user_name)
def nfs_deny_access(self, share_name, host_ip):
share = self.system.get_nfs_share(name=share_name)
try:
share.delete_access(host_ip)
except storops_ex.UnityHostNotFoundException:
LOG.info(_LI('%(host)s access to %(share)s is already removed.'),
{'host': host_ip, 'share': share_name})
def get_ip_ports(self, sp=None):
ports = self.system.get_ip_port()
link_up_ports = []
for port in ports:
if port.is_link_up and 'eth' in port.id:
if sp and port.sp.id != sp.id:
continue
link_up_ports.append(port)
return link_up_ports

View File

@ -0,0 +1,632 @@
# Copyright (c) 2016 EMC Corporation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Unity backend for the EMC Manila driver."""
from oslo_log import log
from oslo_utils import excutils
from oslo_utils import importutils
from oslo_utils import units
storops = importutils.try_import('storops')
if storops:
from storops import exception as storops_ex
from storops.unity import enums
from manila.common import constants as const
from manila import exception
from manila.i18n import _, _LE, _LW, _LI
from manila.share.drivers.emc.plugins import base as driver
from manila.share.drivers.emc.plugins.unity import client
from manila.share.drivers.emc.plugins.unity import utils as unity_utils
from manila.share.drivers.emc.plugins.vnx import utils as emc_utils
from manila.share import utils as share_utils
from manila import utils
VERSION = "1.0.0"
LOG = log.getLogger(__name__)
SUPPORTED_NETWORK_TYPES = (None, 'flat', 'vlan')
@emc_utils.decorate_all_methods(emc_utils.log_enter_exit,
debug_only=True)
class UnityStorageConnection(driver.StorageConnection):
"""Implements Unity specific functionality for EMC Manila driver."""
IP_ALLOCATIONS = 2
@emc_utils.log_enter_exit
def __init__(self, *args, **kwargs):
super(UnityStorageConnection, self).__init__(*args, **kwargs)
self.client = None
self.pool_set = None
self.port_set = None
self.nas_server_pool = None
self.storage_processor = None
self.reserved_percentage = None
self.max_over_subscription_ratio = None
# props from super class.
self.driver_handles_share_servers = True
def connect(self, emc_share_driver, context):
"""Connect to Unity storage."""
config = emc_share_driver.configuration
storage_ip = config.emc_nas_server
username = config.emc_nas_login
password = config.emc_nas_password
sp_name = config.emc_nas_server_container
self.client = client.UnityClient(storage_ip, username, password)
pool_conf = config.safe_get(
'emc_nas_pool_names')
self.pool_set = self._get_managed_pools(pool_conf)
self.reserved_percentage = config.safe_get(
'reserved_share_percentage')
if self.reserved_percentage is None:
self.reserved_percentage = 0
self.max_over_subscription_ratio = config.safe_get(
'max_over_subscription_ratio')
self._config_sp(sp_name)
port_conf = config.safe_get(
'emc_interface_ports')
self.port_set = self._get_managed_ports(
port_conf, self.storage_processor)
pool_name = config.emc_nas_server_pool
self._config_pool(pool_name)
def check_for_setup_error(self):
"""Check for setup error."""
def create_share(self, context, share, share_server=None):
"""Create a share and export it based on protocol used."""
share_name = share['id']
size = share['size'] * units.Gi
# Check share's protocol.
# Throw an exception immediately if it is an invalid protocol.
share_proto = share['share_proto'].upper()
proto_enum = self._get_proto_enum(share_proto)
# Get pool name from share host field
pool_name = self._get_pool_name_from_host(share['host'])
# Get share server name from share server
server_name = self._get_server_name(share_server)
pool = self.client.get_pool(pool_name)
try:
nas_server = self.client.get_nas_server(server_name)
except storops_ex.UnityResourceNotFoundError:
message = (_LE("Failed to get NAS server %(server)s when "
"creating the share %(share)s.") %
{'server': server_name, 'share': share_name})
LOG.error(message)
raise exception.EMCUnityError(err=message)
filesystem = self.client.create_filesystem(
pool, nas_server, share_name, size, proto=proto_enum)
locations = None
if share_proto == 'CIFS':
self.client.create_cifs_share(filesystem, share_name)
locations = self._get_cifs_location(
nas_server.file_interface, share_name)
elif share_proto == 'NFS':
self.client.create_nfs_share(filesystem, share_name)
locations = self._get_nfs_location(
nas_server.file_interface, share_name)
return locations
def create_share_from_snapshot(self, context, share, snapshot,
share_server=None):
"""Create a share from a snapshot - clone a snapshot."""
share_name = share['id']
# Check share's protocol.
# Throw an exception immediately if it is an invalid protocol.
share_proto = share['share_proto'].upper()
self._validate_share_protocol(share_proto)
# Get share server name from share server
server_name = self._get_server_name(share_server)
try:
nas_server = self.client.get_nas_server(server_name)
except storops_ex.UnityResourceNotFoundError:
message = (_LE("Failed to get NAS server %(server)s when "
"creating the share %(share)s.") %
{'server': server_name, 'share': share_name})
LOG.error(message)
raise exception.EMCUnityError(err=message)
backend_snap = self.client.create_snap_of_snap(snapshot['id'],
share_name,
snap_type='snapshot')
locations = None
if share_proto == 'CIFS':
self.client.create_cifs_share(backend_snap, share_name)
locations = self._get_cifs_location(
nas_server.file_interface, share_name)
elif share_proto == 'NFS':
self.client.create_nfs_share(backend_snap, share_name)
locations = self._get_nfs_location(
nas_server.file_interface, share_name)
return locations
def delete_share(self, context, share, share_server=None):
"""Delete a share."""
share_name = share['id']
try:
backend_share = self.client.get_share(share_name,
share['share_proto'])
except storops_ex.UnityResourceNotFoundError:
LOG.warning(_LW("Share %s is not found when deleting the share"),
share_name)
return
# Share created by the API create_share_from_snapshot()
if self._is_share_from_snapshot(backend_share):
filesystem = backend_share.snap.filesystem
self.client.delete_snapshot(backend_share.snap)
else:
filesystem = backend_share.filesystem
self.client.delete_share(backend_share)
if self._is_isolated_filesystem(filesystem):
self.client.delete_filesystem(filesystem)
def extend_share(self, share, new_size, share_server=None):
backend_share = self.client.get_share(share['id'],
share['share_proto'])
if not self._is_share_from_snapshot(backend_share):
backend_share.filesystem.extend(new_size * units.Gi)
else:
share_id = share['id']
reason = _LE("Driver does not support extending a "
"snapshot based share.")
raise exception.ShareExtendingError(share_id=share_id,
reason=reason)
def create_snapshot(self, context, snapshot, share_server=None):
"""Create snapshot from share."""
share_name = snapshot['share_id']
share_proto = snapshot['share']['share_proto']
backend_share = self.client.get_share(share_name, share_proto)
snapshot_name = snapshot['id']
if self._is_share_from_snapshot(backend_share):
self.client.create_snap_of_snap(backend_share.snap,
snapshot_name,
snap_type='checkpoint')
else:
self.client.create_snapshot(backend_share.filesystem,
snapshot_name)
def delete_snapshot(self, context, snapshot, share_server=None):
"""Delete a snapshot."""
snap = self.client.get_snapshot(snapshot['id'])
self.client.delete_snapshot(snap)
def update_access(self, context, share, access_rules, add_rules,
delete_rules, share_server=None):
# adding rules
if add_rules:
for rule in add_rules:
self.allow_access(context, share, rule, share_server)
# deleting rules
if delete_rules:
for rule in delete_rules:
self.deny_access(context, share, rule, share_server)
# recovery mode
if not (add_rules or delete_rules):
white_list = []
for rule in access_rules:
self.allow_access(context, share, rule, share_server)
white_list.append(rule['access_to'])
self.clear_access(share, white_list)
def clear_access(self, share, white_list=None):
share_proto = share['share_proto'].upper()
share_name = share['id']
if share_proto == 'CIFS':
self.client.cifs_clear_access(share_name, white_list)
elif share_proto == 'NFS':
self.client.nfs_clear_access(share_name, white_list)
def allow_access(self, context, share, access, share_server=None):
"""Allow access to a share."""
access_level = access['access_level']
if access_level not in const.ACCESS_LEVELS:
raise exception.InvalidShareAccessLevel(level=access_level)
share_proto = share['share_proto'].upper()
self._validate_share_protocol(share_proto)
self._validate_share_access_type(share, access)
if share_proto == 'CIFS':
self._cifs_allow_access(share, access)
elif share_proto == 'NFS':
self._nfs_allow_access(share, access)
def deny_access(self, context, share, access, share_server):
"""Deny access to a share."""
share_proto = share['share_proto'].upper()
self._validate_share_protocol(share_proto)
self._validate_share_access_type(share, access)
if share_proto == 'CIFS':
self._cifs_deny_access(share, access)
elif share_proto == 'NFS':
self._nfs_deny_access(share, access)
def ensure_share(self, context, share, share_server):
"""Ensure that the share is exported."""
share_name = share['id']
share_proto = share['share_proto']
backend_share = self.client.get_share(share_name, share_proto)
if not backend_share.existed:
raise exception.ShareNotFound(share_id=share_name)
def update_share_stats(self, stats_dict):
"""Communicate with EMCNASClient to get the stats."""
stats_dict['driver_version'] = VERSION
stats_dict['pools'] = []
for pool in self.client.get_pool():
if pool.name in self.pool_set:
# the unit of following numbers are GB
total_size = float(pool.size_total)
used_size = float(pool.size_used)
pool_stat = {
'pool_name': pool.name,
'thin_provisioning': True,
'total_capacity_gb': total_size,
'free_capacity_gb': total_size - used_size,
'allocated_capacity_gb': used_size,
'provisioned_capacity_gb': float(pool.size_subscribed),
'qos': False,
'reserved_percentage': self.reserved_percentage,
'max_over_subscription_ratio':
self.max_over_subscription_ratio,
}
stats_dict['pools'].append(pool_stat)
if not stats_dict.get('pools'):
message = _LE("Failed to update storage pool.")
LOG.error(message)
raise exception.EMCUnityError(err=message)
def get_pool(self, share):
"""Get the pool name of the share."""
backend_share = self.client.get_share(
share['id'], share['share_proto'])
return backend_share.filesystem.pool.name
def get_network_allocations_number(self):
"""Returns number of network allocations for creating VIFs."""
return self.IP_ALLOCATIONS
def setup_server(self, network_info, metadata=None):
"""Set up and configures share server with given network parameters."""
server_name = network_info['server_id']
nas_server = self.client.create_nas_server(server_name,
self.storage_processor,
self.nas_server_pool)
try:
for network in network_info['network_allocations']:
self._create_network_interface(nas_server, network)
self._handle_security_services(
nas_server, network_info['security_services'])
return {'share_server_name': server_name}
except Exception as ex:
with excutils.save_and_reraise_exception():
LOG.error(_LE('Could not setup server. Reason: %s.'), ex)
server_details = {'share_server_name': server_name}
self.teardown_server(
server_details, network_info['security_services'])
def teardown_server(self, server_details, security_services=None):
"""Teardown share server."""
if not server_details:
LOG.debug('Server details are empty.')
return
server_name = server_details.get('share_server_name')
if not server_name:
LOG.debug('No share server found for server %s.',
server_details.get('instance_id'))
return
username = None
password = None
for security_service in security_services:
if security_service['type'] == 'active_directory':
username = security_service['user']
password = security_service['password']
break
self.client.delete_nas_server(server_name, username, password)
def _cifs_allow_access(self, share, access):
"""Allow access to CIFS share."""
self.client.cifs_allow_access(
share['id'], access['access_to'], access['access_level'])
def _cifs_deny_access(self, share, access):
"""Deny access to CIFS share."""
self.client.cifs_deny_access(share['id'], access['access_to'])
def _config_pool(self, pool_name):
try:
self.nas_server_pool = self.client.get_pool(pool_name)
except storops_ex.UnityResourceNotFoundError:
message = (_LE("The storage pools %s to store NAS server "
"configuration do not exist.") % pool_name)
LOG.error(message)
raise exception.BadConfigurationException(reason=message)
def _config_sp(self, sp_name):
self.storage_processor = self.client.get_storage_processor(
sp_name.lower())
if self.storage_processor is None:
message = (_LE("The storage processor %s does not exist or "
"is unavailable. Please reconfigure it in "
"manila.conf.") % sp_name)
LOG.error(message)
raise exception.BadConfigurationException(reason=message)
def _create_network_interface(self, nas_server, network):
ip_addr = network['ip_address']
netmask = utils.cidr_to_netmask(network['cidr'])
gateway = network['gateway']
vlan_id = network['segmentation_id']
if network['network_type'] not in SUPPORTED_NETWORK_TYPES:
msg = _('The specified network type %s is unsupported by '
'the EMC Unity driver')
raise exception.NetworkBadConfigurationException(
reason=msg % network['network_type'])
# Create the interfaces on NAS server
self.client.create_interface(nas_server,
ip_addr,
netmask,
gateway,
ports=self.port_set,
vlan_id=vlan_id)
@staticmethod
def _get_cifs_location(file_interfaces, share_name):
return [
{'path': r'\\%(interface)s\%(share_name)s' % {
'interface': interface.ip_address,
'share_name': share_name}
}
for interface in file_interfaces
]
def _get_managed_pools(self, pool_conf):
# Get the real pools from the backend storage
real_pools = set([pool.name for pool in self.client.get_pool()])
if not pool_conf:
LOG.debug("No storage pool is specified, so all pools in storage "
"system will be managed.")
return real_pools
matched_pools, unmanaged_pools = unity_utils.do_match(real_pools,
pool_conf)
if not matched_pools:
msg = (_("All the specified storage pools to be managed "
"do not exist. Please check your configuration "
"emc_nas_pool_names in manila.conf. "
"The available pools in the backend are %s") %
",".join(real_pools))
raise exception.BadConfigurationException(reason=msg)
if unmanaged_pools:
LOG.info(_LI("The following specified storage pools "
"are not managed by the backend: "
"%(un_managed)s. This host will only manage "
"the storage pools: %(exist)s"),
{'un_managed': ",".join(unmanaged_pools),
'exist': ",".join(matched_pools)})
else:
LOG.debug("Storage pools: %s will be managed.",
",".join(matched_pools))
return matched_pools
def _get_managed_ports(self, port_conf, sp):
# Get the real ports from the backend storage
real_ports = set([port.id for port in self.client.get_ip_ports(sp)])
if not port_conf:
LOG.debug("No ports are specified, so all ports in storage "
"system will be managed.")
return real_ports
matched_ports, unmanaged_ports = unity_utils.do_match(real_ports,
port_conf)
if not matched_ports:
msg = (_("All the specified storage ports to be managed "
"do not exist. Please check your configuration "
"emc_interface_ports in manila.conf. "
"The available ports in the backend are %s") %
",".join(real_ports))
raise exception.BadConfigurationException(reason=msg)
if unmanaged_ports:
LOG.info(_LI("The following specified ports "
"are not managed by the backend: "
"%(un_managed)s. This host will only manage "
"the storage ports: %(exist)s"),
{'un_managed': ",".join(unmanaged_ports),
'exist': ",".join(matched_ports)})
else:
LOG.debug("Ports: %s will be managed.",
",".join(matched_ports))
return matched_ports
@staticmethod
def _get_nfs_location(file_interfaces, share_name):
return [
{'path': '%(interface)s:/%(share_name)s' % {
'interface': interface.ip_address,
'share_name': share_name}
}
for interface in file_interfaces
]
@staticmethod
def _get_pool_name_from_host(host):
pool_name = share_utils.extract_host(host, level='pool')
if not pool_name:
message = (_("Pool is not available in the share host %s.") %
host)
raise exception.InvalidHost(reason=message)
return pool_name
@staticmethod
def _get_proto_enum(share_proto):
share_proto = share_proto.upper()
UnityStorageConnection._validate_share_protocol(share_proto)
if share_proto == 'CIFS':
return enums.FSSupportedProtocolEnum.CIFS
elif share_proto == 'NFS':
return enums.FSSupportedProtocolEnum.NFS
@staticmethod
def _get_server_name(share_server):
if not share_server:
msg = _('Share server not provided.')
raise exception.InvalidInput(reason=msg)
server_name = share_server.get(
'backend_details', {}).get('share_server_name')
if server_name is None:
msg = (_LE("Name of the share server %s not found.")
% share_server['id'])
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
return server_name
def _handle_security_services(self, nas_server, security_services):
kerberos_enabled = False
# Support 'active_directory' and 'kerberos'
for security_service in security_services:
service_type = security_service['type']
if service_type == 'active_directory':
# Create DNS server for NAS server
domain = security_service['domain']
dns_ip = security_service['dns_ip']
self.client.create_dns_server(nas_server,
domain,
dns_ip)
# Enable CIFS service
username = security_service['user']
password = security_service['password']
self.client.enable_cifs_service(nas_server,
domain=domain,
username=username,
password=password)
elif service_type == 'kerberos':
# Enable NFS service with kerberos
kerberos_enabled = True
# TODO(jay.xu): enable nfs service with kerberos
LOG.warning(_LW('Kerberos is not supported by '
'EMC Unity manila driver plugin.'))
elif service_type == 'ldap':
LOG.warning(_LW('LDAP is not supported by '
'EMC Unity manila driver plugin.'))
else:
LOG.warning(_LW('Unknown security service type: %s.'),
service_type)
if not kerberos_enabled:
# Enable NFS service without kerberos
self.client.enable_nfs_service(nas_server)
def _nfs_allow_access(self, share, access):
"""Allow access to NFS share."""
self.client.nfs_allow_access(
share['id'], access['access_to'], access['access_level'])
def _nfs_deny_access(self, share, access):
"""Deny access to NFS share."""
self.client.nfs_deny_access(share['id'], access['access_to'])
@staticmethod
def _is_isolated_filesystem(filesystem):
filesystem.update()
return (
not filesystem.has_snap() and
not (filesystem.cifs_share or filesystem.nfs_share)
)
@staticmethod
def _is_share_from_snapshot(share):
return True if share.snap else False
@staticmethod
def _validate_share_access_type(share, access):
reason = None
share_proto = share['share_proto'].upper()
if share_proto == 'CIFS' and access['access_type'] != 'user':
reason = _('Only user access type allowed for CIFS share.')
elif share_proto == 'NFS' and access['access_type'] != 'ip':
reason = _('Only IP access type allowed for NFS share.')
if reason:
raise exception.InvalidShareAccess(reason=reason)
@staticmethod
def _validate_share_protocol(share_proto):
if share_proto not in ('NFS', 'CIFS'):
raise exception.InvalidShare(
reason=(_('Invalid NAS protocol supplied: %s.') %
share_proto))

View File

@ -0,0 +1,34 @@
# Copyright (c) 2016 EMC Corporation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
""" Utility module for EMC Unity Manila Driver """
import fnmatch
def do_match(full, matcher_list):
matched = set()
full = set([item.strip() for item in full])
if matcher_list is None:
# default to all
matcher_list = set('*')
else:
matcher_list = set([item.strip() for item in matcher_list])
for item in full:
for matcher in matcher_list:
if fnmatch.fnmatchcase(item, matcher):
matched.add(item)
return matched, full - matched

View File

@ -459,7 +459,7 @@ class VNXStorageConnection(driver.StorageConnection):
raise exception.EMCVnxXMLAPIError(err=message)
real_pools = set([item for item in backend_pools])
conf_pools = set([item.strip() for item in pools.split(",")])
conf_pools = set([item.strip() for item in pools])
matched_pools, unmatched_pools = vnx_utils.do_match_any(
real_pools, conf_pools)

View File

@ -0,0 +1,20 @@
# Copyright (c) 2016 EMC Corporation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import sys
sys.modules['storops'] = mock.Mock()
sys.modules['storops.unity'] = mock.Mock()

View File

@ -0,0 +1,66 @@
# Copyright (c) 2016 EMC Corporation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
class UnityFakeException(Exception):
pass
class UnityException(UnityFakeException):
pass
class UnitySmbShareNameExistedError(UnityException):
pass
class UnityFileSystemNameAlreadyExisted(UnityException):
pass
class UnityNasServerNameUsedError(UnityException):
pass
class UnityNfsShareNameExistedError(UnityException):
pass
class UnitySnapNameInUseError(UnityException):
pass
class UnityIpAddressUsedError(UnityException):
pass
class UnityResourceNotFoundError(UnityException):
pass
class UnityOneDnsPerNasServerError(UnityException):
pass
class UnitySmbNameInUseError(UnityException):
pass
class UnityNfsAlreadyEnabledError(UnityException):
pass
class UnityHostNotFoundException(UnityException):
pass

View File

@ -0,0 +1,235 @@
network_allocations:
_type: 'network_allocations'
_properties: &network_allocations_prop
- id: '04ac4c27-9cf7-4406-809c-13edc93e4849'
ip_address: 'fake_ip_addr_1'
cidr: '192.168.1.0/24'
segmentation_id: null
gateway: '192.168.1.1'
network_type: flat
- id: '0cf87de7-5c65-4036-8b6a-e8176c356958'
ip_address: 'fake_ip_addr_2'
cidr: '192.168.1.0/24'
segmentation_id: null
gateway: '192.168.1.1'
network_type: flat
network_allocations_vlan:
_type: 'network_allocations'
_properties: &network_allocations_vlan_prop
- id: '04ac4c27-9cf7-4406-809c-13edc93e4849'
ip_address: 'fake_ip_addr_1'
cidr: '192.168.1.0/24'
segmentation_id: 160
gateway: '192.168.1.1'
network_type: vlan
- id: '0cf87de7-5c65-4036-8b6a-e8176c356958'
ip_address: 'fake_ip_addr_2'
cidr: '192.168.1.0/24'
segmentation_id: 160
gateway: '192.168.1.1'
network_type: vlan
network_allocations_vxlan:
_type: 'network_allocations'
_properties: &network_allocations_vxlan_prop
- id: '04ac4c27-9cf7-4406-809c-13edc93e4849'
ip_address: 'fake_ip_addr_1'
cidr: '192.168.1.0/24'
segmentation_id: 123
gateway: '192.168.1.1'
network_type: vxlan
active_directory:
_type: 'security_service'
_properties: &active_directory_prop
type: 'active_directory'
domain: 'fake_domain_name'
dns_ip: 'fake_dns_ip'
user: 'fake_user'
password: 'fake_password'
kerberos:
_type: 'security_service'
_properties: &kerberos_prop
<<: *active_directory_prop
type: 'kerberos'
server: 'fake_server'
security_services:
_type: 'security_services'
_properties: &security_services_prop
services: [*active_directory_prop, *kerberos_prop]
network_info__flat:
_type: 'network_info'
_properties: &network_info_flat_prop
name: 'share_network'
neutron_subnet_id: 'a3f3eeac-0b16-4932-8c03-0a37003644ff'
network_type: 'flat'
neutron_net_id: 'e6c96730-2bcf-4ce3-86fa-7cb7740086cb'
ip_version: 4
id: '232d8218-2743-41d1-832b-4194626e691e'
network_allocations: *network_allocations_prop
server_id: '78fd845f-8e7d-487f-bfde-051d83e78103'
security_services: []
network_info__vlan:
_type: 'network_info'
_properties: &network_info__vlan_prop
<<: *network_info_flat_prop
network_type: 'vlan'
network_allocations: *network_allocations_vlan_prop
network_info__vxlan:
_type: 'network_info'
_properties: &network_info__vxlan_prop
<<: *network_info_flat_prop
network_type: 'vxlan'
network_allocations: *network_allocations_vxlan_prop
network_info__active_directory:
_type: 'network_info'
_properties:
<<: *network_info__vlan_prop
security_services: [*active_directory_prop]
network_info__kerberos:
_type: 'network_info'
_properties:
<<: *network_info_flat_prop
security_services: [*kerberos_prop]
share_server:
_type: 'share_server'
_properties: &share_server_prop
status: 'active'
share_network: *network_info_flat_prop
share_network_id: '232d8218-2743-41d1-832b-4194626e691e'
host: 'openstack@VNX'
backend_details:
share_server_name: '78fd845f-8e7d-487f-bfde-051d83e78103'
network_allocations: *network_allocations_prop
id: '78fd845f-8e7d-487f-bfde-051d83e78103'
share_server__no_share_server_name:
_type: 'share_server'
_properties:
<<: *share_server_prop
backend_details:
share_server_name: None
id: '78fd845f-8e7d-487f-bfde-051d83e78103'
server_detail:
_type: 'server_detail'
_properties: &server_detail_prop
share_server_name: '78fd845f-8e7d-487f-bfde-051d83e78103'
cifs_share:
_type: 'share'
_properties: &cifs_share_prop
share_id: '708e753c-aacb-411f-9c8a-8b8175da4e73'
availability_zone_id: 'de628fb6-1c99-41f6-a06a-adb61ff693b5'
share_network_id: '232d8218-2743-41d1-832b-4194626e691e'
share_server_id: '78fd845f-8e7d-487f-bfde-051d83e78103'
id: '716100cc-e0b4-416b-ac27-d38dd019330d'
size: 1
user_id: '19bbda71b578471a93363653dcb4c61d'
status: 'creating'
share_type_id: '57679eab-3e67-4052-b180-62b609670e93'
host: 'openstack@VNX#Pool_2'
display_name: 'cifs_share'
share_proto: 'CIFS'
export_locations: []
is_public: False
nfs_share:
_type: 'share'
_properties: &nfs_share_prop
share_id: '12eb3777-7008-4721-8243-422507db8f9d'
availability_zone_id: 'de628fb6-1c99-41f6-a06a-adb61ff693b5'
share_network_id: '232d8218-2743-41d1-832b-4194626e691e'
share_server_id: '78fd845f-8e7d-487f-bfde-051d83e78103'
id: 'cb532599-8dc6-4c3e-bb21-74ea54be566c'
size: 1
user_id: '19bbda71b578471a93363653dcb4c61d'
status: 'creating'
share_type_id: '57679eab-3e67-4052-b180-62b609670e93'
host: 'openstack@VNX#Pool_2'
display_name: 'nfs_share'
share_proto: 'NFS'
export_locations: []
is_public: False
invalid_share:
_type: 'share'
_properties: &invalid_share_prop
share_id: '12eb3777-7008-4721-8243-422507db8f9d'
availability_zone_id: 'de628fb6-1c99-41f6-a06a-adb61ff693b5'
share_network_id: '232d8218-2743-41d1-832b-4194626e691e'
share_server_id: '78fd845f-8e7d-487f-bfde-051d83e78103'
id: 'cb532599-8dc6-4c3e-bb21-74ea54be566c'
size: 1
user_id: '19bbda71b578471a93363653dcb4c61d'
status: 'creating'
share_type_id: '57679eab-3e67-4052-b180-62b609670e93'
host: 'openstack@VNX#Pool_2'
display_name: 'nfs_share'
share_proto: 'fake_proto'
export_locations: []
is_public: False
snapshot:
_type: 'snapshot'
_properties: &snapshot_prop
status: 'creating'
share_instance_id: '27e4625e-c336-4749-85bc-634216755fbc'
share:
share_proto: 'CIFS'
snapshot_id: '345476cc-32ab-4565-ba88-e4733b7ffa0e'
progress: '0%'
id: 'ab411797-b1cf-4035-bf14-8771a7bf1805'
share_id: '27e4625e-c336-4749-85bc-634216755fbc'
cifs_rw_access:
_type: 'access'
_properties:
access_level: 'rw'
access_to: 'administrator'
access_type: 'user'
cifs_ro_access:
_type: 'access'
_properties:
access_level: 'ro'
access_to: 'administrator'
access_type: 'user'
nfs_rw_access:
_type: 'access'
_properties:
access_level: 'rw'
access_to: '192.168.1.1'
access_type: 'ip'
nfs_rw_access_cidr:
_type: 'access'
_properties:
access_level: 'rw'
access_to: '192.168.1.0/24'
access_type: 'ip'
nfs_ro_access:
_type: 'access'
_properties:
access_level: 'ro'
access_to: '192.168.1.1'
access_type: 'ip'
invalid_access:
_type: 'access'
_properties:
access_level: 'fake_access_level'
access_to: 'fake_access_to'
access_type: 'fake_type'

View File

@ -0,0 +1,958 @@
sp_a: &sp_a
_properties:
name: 'SPA'
id: 'SPA'
existed: true
sp_b: &sp_b
_properties:
name: 'SPB'
id: 'SPB'
existed: true
sp_c: &sp_invalid
_properties:
id: 'SPC'
existed: false
interface_1: &interface_1
_properties:
ip_address: 'fake_ip_addr_1'
interface_2: &interface_2
_properties:
ip_address: 'fake_ip_addr_2'
nas_server: &nas_server
_properties: &nas_server_prop
name: '78fd845f-8e7d-487f-bfde-051d83e78103'
file_interface: [*interface_1, *interface_2]
current_sp: *sp_a
filesystem_base: &filesystem_base
_properties: &filesystem_base_prop
name: 'fake_filesystem_name'
id: 'fake_filesystem_id'
size_total: 50000000000
is_thin_enabled: true
pool: null
nas_server: null
cifs_share: []
nfs_share: []
_methods:
has_snap: False
snap_base:
_properties: &snap_base_prop
name: 'fake_snap_name'
id: 'fake_snap_id'
size: 50000000000
filesystem: *filesystem_base
share_base:
_properties: &share_base_prop
name: 'fake_share_name'
id: 'fake_share_id'
filesystem: null
snap: null
cifs_share_base: &cifs_share_base
_properties: &cifs_share_base_prop
<<: *share_base_prop
nfs_share_base: &nfs_share_base
_properties: &nfs_share_base_prop
<<: *share_base_prop
pool_base:
_properties: &pool_base_prop
name: 'fake_pool_name'
pool_id: 0
state: Ready
user_capacity_gbs: 1311
total_subscribed_capacity_gbs: 131
available_capacity_gbs: 132
percent_full_threshold: 70
fast_cache: True
pool_1: &pool_1
_properties: &pool_1_prop
<<: *pool_base_prop
name: 'pool_1'
size_total: 500000
size_used: 10000
size_subscribed: 30000
pool_2: &pool_2
_properties: &pool_2_prop
<<: *pool_base_prop
name: 'pool_2'
size_total: 600000
size_used: 20000
size_subscribed: 40000
nas_server_pool: &nas_server_pool
_properties:
<<: *pool_base_prop
name: 'nas_server_pool'
port_base:
_properties: &port_base_prop
is_link_up: true
id: 'fake_name'
sp: *sp_a
port_1: &port_1
_properties:
<<: *port_base_prop
is_link_up: true
id: 'spa_eth1'
sp: *sp_a
port_2: &port_2
_properties:
<<: *port_base_prop
is_link_up: true
id: 'spa_eth2'
sp: *sp_a
port_3: &port_internal_port
_properties:
<<: *port_base_prop
is_link_up: true
id: 'internal_port'
sp: *sp_a
unity_base: &unity_base
_methods: &unity_base_method
get_sp: *sp_a
get_pool:
_side_effect: [[*pool_1, *pool_2, *nas_server_pool], *nas_server_pool]
get_ip_port: [*port_1, *port_2]
test_connect: &test_connect
unity: *unity_base
test_connect__invalid_sp_configuration:
unity:
_methods:
<<: *unity_base_method
get_sp: *sp_invalid
test_connect__invalid_pool_configuration: *test_connect
test_create_nfs_share:
nfs_share: &nfs_share__test_create_nfs_share
_properties:
<<: *nfs_share_base_prop
name: 'cb532599-8dc6-4c3e-bb21-74ea54be566c'
filesystem: &filesystem__test_create_nfs_share
_properties: &filesystem_prop__test_create_nfs_share
<<: *filesystem_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd4587340'
_methods:
create_nfs_share: *nfs_share__test_create_nfs_share
pool: &pool__test_create_nfs_share
_properties:
<<: *pool_base_prop
name: 'Pool_2'
_methods:
create_filesystem: *filesystem__test_create_nfs_share
unity:
_methods:
<<: *unity_base_method
get_pool:
_side_effect: [[*pool_1, *pool_2, *nas_server_pool],
*nas_server_pool, *pool__test_create_nfs_share]
get_nas_server: *nas_server
test_create_cifs_share:
cifs_share: &cifs_share__test_create_cifs_share
_properties:
<<: *cifs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
_methods:
enable_ace:
filesystem: &filesystem__test_create_cifs_share
_properties: &filesystem_prop__test_create_cifs_share
<<: *filesystem_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd4587340'
_methods:
create_cifs_share: *cifs_share__test_create_cifs_share
pool: &pool__test_create_cifs_share
_properties:
<<: *pool_base_prop
name: 'Pool_2'
_methods:
create_filesystem: *filesystem__test_create_cifs_share
unity:
_methods:
<<: *unity_base_method
get_pool:
_side_effect: [[*pool_1, *pool_2, *nas_server_pool], *nas_server_pool,
*pool__test_create_cifs_share]
get_nas_server: *nas_server
test_create_share_with_invalid_share_server:
pool: &pool__test_create_share_with_invalid_share_server
_properties:
<<: *pool_base_prop
name: 'Pool_2'
unity:
_methods:
<<: *unity_base_method
get_pool:
_side_effect: [[*pool_1, *pool_2, *nas_server_pool], *nas_server_pool,
*pool__test_create_share_with_invalid_share_server]
get_nas_server:
_raise:
UnityResourceNotFoundError: 'Failed to get NAS server.'
test_delete_share:
filesystem: &filesystem__test_delete_share
_properties: &filesystem_prop__test_delete_share
<<: *filesystem_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
_methods:
delete:
update:
has_snap: False
cifs_share: &cifs_share__test_delete_share
_properties:
<<: *cifs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
filesystem: *filesystem__test_delete_share
_methods:
delete:
unity:
_methods:
<<: *unity_base_method
get_cifs_share: *cifs_share__test_delete_share
test_delete_share__with_invalid_share:
unity:
_methods:
<<: *unity_base_method
get_cifs_share:
_raise:
UnityResourceNotFoundError: 'Failed to get CIFS share.'
test_delete_share__create_from_snap:
filesystem: &filesystem__test_delete_share__create_from_snap
_properties: &filesystem_prop__test_delete_share__create_from_snap
<<: *filesystem_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd4587340'
_methods:
delete:
update:
has_snap: False
snap: &snap__test_delete_share__create_from_snap
_properties: &snap_prop__test_delete_share__create_from_snap
<<: *snap_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
filesystem: *filesystem__test_delete_share__create_from_snap
_methods:
delete:
cifs_share: &cifs_share__test_delete_share__create_from_snap
_properties:
<<: *cifs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
snap: *snap__test_delete_share__create_from_snap
_methods:
delete:
unity:
_methods:
<<: *unity_base_method
get_cifs_share: *cifs_share__test_delete_share__create_from_snap
get_snap: *snap__test_delete_share__create_from_snap
test_delete_share__create_from_snap_but_not_isolated:
filesystem: &filesystem__test_delete_share__create_from_snap_but_not_isolated
_properties: &filesystem_prop__test_delete_share__create_from_snap_but_not_isolated
<<: *filesystem_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd4587340'
cifs_share: [*cifs_share_base]
nfs_share: [*nfs_share_base]
_methods:
delete:
update:
has_snap: True
snap: &snap__test_delete_share__create_from_snap_but_not_isolated
_properties: &snap_prop__test_delete_share__create_from_snap_but_not_isolated
<<: *snap_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
filesystem: *filesystem__test_delete_share__create_from_snap_but_not_isolated
_methods:
delete:
cifs_share: &cifs_share__test_delete_share__create_from_snap_but_not_isolated
_properties:
<<: *cifs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
snap: *snap__test_delete_share__create_from_snap_but_not_isolated
_methods:
delete:
unity:
_methods:
<<: *unity_base_method
get_cifs_share: *cifs_share__test_delete_share__create_from_snap_but_not_isolated
test_delete_share__but_not_isolated:
filesystem: &filesystem__test_delete_share__but_not_isolated
_properties: &filesystem_prop__test_delete_share__but_not_isolated
<<: *filesystem_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd4587340'
_methods:
update:
has_snap: True
cifs_share: &cifs_share__test_delete_share__but_not_isolated
_properties:
<<: *cifs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
filesystem: *filesystem__test_delete_share__but_not_isolated
_methods:
delete:
unity:
_methods:
<<: *unity_base_method
get_cifs_share: *cifs_share__test_delete_share__but_not_isolated
test_extend_cifs_share:
filesystem: &filesystem__test_extend_cifs_share
_properties: &filesystem_prop__test_extend_cifs_share
<<: *filesystem_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
_methods:
extend:
cifs_share: &cifs_share__test_extend_cifs_share
_properties:
<<: *cifs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
filesystem: *filesystem__test_extend_cifs_share
unity:
_methods:
<<: *unity_base_method
get_cifs_share: *cifs_share__test_extend_cifs_share
test_extend_nfs_share:
filesystem: &filesystem__test_extend_nfs_share
_properties: &filesystem_prop__test_extend_nfs_share
<<: *filesystem_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
_methods:
extend:
cifs_share: &cifs_share__test_extend_nfs_share
_properties:
<<: *cifs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
filesystem: *filesystem__test_extend_nfs_share
unity:
_methods:
<<: *unity_base_method
get_nfs_share: *cifs_share__test_extend_nfs_share
test_extend_share__create_from_snap:
snap: &snap__test_extend_share__create_from_snap
_properties: &snap_prop__test_extend_share__create_from_snap
<<: *snap_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
cifs_share: &cifs_share__test_extend_share__create_from_snap
_properties:
<<: *cifs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
snap: *snap__test_extend_share__create_from_snap
unity:
_methods:
<<: *unity_base_method
get_cifs_share: *cifs_share__test_extend_share__create_from_snap
test_create_snapshot_from_filesystem:
filesystem: &filesystem__test_create_snapshot_from_filesystem
_properties: &filesystem_prop__test_create_snapshot_from_filesystem
<<: *filesystem_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
_methods:
create_snap:
cifs_share: &cifs_share__test_create_snapshot_from_filesystem
_properties:
<<: *cifs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
filesystem: *filesystem__test_create_snapshot_from_filesystem
unity:
_methods:
<<: *unity_base_method
get_cifs_share: *cifs_share__test_create_snapshot_from_filesystem
test_create_snapshot_from_snapshot:
snap: &snap__test_create_snapshot_from_snapshot
_properties: &snap_prop__test_create_snapshot_from_snapshot
<<: *snap_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
_methods:
create_snap:
cifs_share: &cifs_share__test_create_snapshot_from_snapshot
_properties:
<<: *cifs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
snap: *snap__test_create_snapshot_from_snapshot
unity:
_methods:
<<: *unity_base_method
get_cifs_share: *cifs_share__test_create_snapshot_from_snapshot
get_snap: *snap__test_create_snapshot_from_snapshot
test_delete_snapshot:
snap: &snap__test_delete_snapshot
_properties: &snap_prop__test_delete_snapshot
<<: *snap_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
_methods:
delete:
unity:
_methods:
<<: *unity_base_method
get_snap: *snap__test_delete_snapshot
test_ensure_share_exists:
cifs_share: &cifs_share_ensure_share_exists
_properties:
existed: True
unity:
_methods:
<<: *unity_base_method
get_cifs_share: *cifs_share_ensure_share_exists
test_ensure_share_not_exists:
cifs_share: &cifs_share_ensure_share_not_exists
_properties:
existed: False
unity:
_methods:
<<: *unity_base_method
get_cifs_share: *cifs_share_ensure_share_not_exists
test_update_share_stats:
unity:
_methods:
<<: *unity_base_method
get_pool:
_side_effect: [[*pool_1, *pool_2], *pool_1, [*pool_1, *pool_2]]
test_update_share_stats__nonexistent_pools:
unity:
_methods:
<<: *unity_base_method
get_pool:
_side_effect: [[*pool_1, *pool_2], *pool_1, []]
test_get_pool:
filesystem: &filesystem__test_get_pool
_properties: &filesystem_prop__test_get_pool
<<: *filesystem_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
pool: *pool_1
cifs_share: &cifs_share__test_get_pool
_properties:
<<: *cifs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
filesystem: *filesystem__test_get_pool
unity:
_methods:
<<: *unity_base_method
get_cifs_share: *cifs_share__test_get_pool
test_setup_server: &test_setup_server
nas_server_1: &nas_server_1__test_setup_server
_properties:
<<: *nas_server_prop
existed: false
nas_server_2: &nas_server_2__test_setup_server
_properties:
<<: *nas_server_prop
_methods: &nas_server_2__test_setup_server_mehtod
create_file_interface:
enable_nfs_service:
unity:
_methods: &unity_method__test_setup_server
<<: *unity_base_method
get_nas_server: *nas_server_1__test_setup_server
create_nas_server: *nas_server_2__test_setup_server
test_setup_server__vlan_network:
<<: *test_setup_server
nas_server: &nas_server__test_setup_server_flat_network
_properties:
<<: *nas_server_prop
existed: true
_methods:
create_file_interface:
create_dns_server:
enable_nfs_service:
unity:
_methods:
<<: *unity_method__test_setup_server
get_nas_server: *nas_server__test_setup_server_flat_network
test_setup_server__active_directory:
<<: *test_setup_server
nas_server_2: &nas_server_2__test_setup_server__active_directory
_properties:
<<: *nas_server_prop
_methods:
create_file_interface:
create_dns_server:
enable_cifs_service:
enable_nfs_service:
unity:
_methods: &unity_method__test_setup_server__active_directory
<<: *unity_method__test_setup_server
create_nas_server: *nas_server_2__test_setup_server__active_directory
test_setup_server__kerberos: *test_setup_server
test_setup_server__throw_exception:
<<: *test_setup_server
nas_server_1: &nas_server_1__test_setup_server__throw_exception
_properties:
<<: *nas_server_prop
existed: false
nas_server_2: &nas_server_2__test_setup_server__throw_exception
_properties:
<<: *nas_server_prop
_methods:
create_file_interface:
create_dns_server:
enable_cifs_service:
enable_nfs_service:
_raise:
UnityException: 'Failed to enable NFS service.'
delete:
unity:
_methods:
<<: *unity_method__test_setup_server
get_nas_server: *nas_server_2__test_setup_server__throw_exception
create_nas_server: *nas_server_2__test_setup_server__throw_exception
test_teardown_server:
nas_server: &nas_server__test_teardown_server
_properties:
<<: *nas_server_prop
_methods:
delete:
unity:
_methods:
<<: *unity_base_method
get_nas_server: *nas_server__test_teardown_server
test__get_managed_pools: &test__get_managed_pools
unity:
_methods:
<<: *unity_base_method
get_pool: [*pool_1, *pool_2, *nas_server_pool]
test__get_managed_pools__invalid_pool_configuration: *test__get_managed_pools
test__get_managed_ports: &test__get_managed_ports
unity:
_methods:
<<: *unity_base_method
get_pool: [*pool_1, *pool_2, *nas_server_pool]
get_ip_port: [*port_1, *port_2, *port_internal_port]
test__get_managed_pools__invalid_port_configuration: *test__get_managed_ports
test_create_cifs_share_from_snapshot:
cifs_share: &cifs_share__test_create_cifs_share_from_snapshot
_properties:
<<: *cifs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
_methods:
enable_ace:
snapshot_1: &snapshot_1__test_create_cifs_share_from_snapshot
_properties: &snapshot_1_prop__test_create_cifs_share_from_snapshot
<<: *snap_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
_methods:
create_cifs_share: *cifs_share__test_create_cifs_share_from_snapshot
snapshot_2: &snapshot_2__test_create_cifs_share_from_snapshot
_properties: &snapshot__prop__test_create_cifs_share_from_snapshot
<<: *snap_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd4587340'
_methods:
create_snap: *snapshot_1__test_create_cifs_share_from_snapshot
unity:
_methods:
<<: *unity_base_method
get_nas_server: *nas_server
get_snap: *snapshot_2__test_create_cifs_share_from_snapshot
test_create_nfs_share_from_snapshot:
nfs_share: &nfs_share__test_create_nfs_share_from_snapshot
_properties:
<<: *nfs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
_methods:
enable_ace:
snapshot_1: &snapshot_1__test_create_nfs_share_from_snapshot
_properties: &snapshot_1_prop__test_create_nfs_share_from_snapshot
<<: *snap_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
_methods:
create_nfs_share: *nfs_share__test_create_nfs_share_from_snapshot
snapshot_2: &snapshot_2__test_create_nfs_share_from_snapshot
_properties: &snapshot__prop__test_create_nfs_share_from_snapshot
<<: *snap_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd4587340'
_methods:
create_snap: *snapshot_1__test_create_nfs_share_from_snapshot
unity:
_methods:
<<: *unity_base_method
get_nas_server: *nas_server
get_snap: *snapshot_2__test_create_nfs_share_from_snapshot
test_create_share_from_snapshot_no_server_name:
unity:
_methods:
<<: *unity_base_method
get_nas_server:
_raise:
UnityResourceNotFoundError: 'NAS server is not found'
test_clear_share_access_cifs:
cifs_share: &cifs_share__test_clear_share_access_cifs
_methods:
clear_access:
_raise:
UnityException: 'clear cifs access invoked'
unity:
_methods:
<<: *unity_base_method
get_cifs_share: *cifs_share__test_clear_share_access_cifs
test_clear_share_access_nfs:
nfs_share: &nfs_share__test_clear_share_access_nfs
_methods:
clear_access:
_raise:
UnityException: 'clear nfs access invoked'
unity:
_methods:
<<: *unity_base_method
get_nfs_share: *nfs_share__test_clear_share_access_nfs
test_allow_rw_cifs_share_access: &test_allow_rw_cifs_share_access
cifs_share: &cifs_share__test_allow_rw_cifs_share_access
_properties:
<<: *cifs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
_methods:
add_ace:
unity:
_methods:
<<: *unity_base_method
get_cifs_share: *cifs_share__test_allow_rw_cifs_share_access
test_update_access_allow_rw: *test_allow_rw_cifs_share_access
test_update_access_recovery:
cifs_share: &cifs_share__test_update_access_recovery
_methods:
add_ace:
clear_access:
unity:
_methods:
<<: *unity_base_method
get_cifs_share: *cifs_share__test_update_access_recovery
test_allow_ro_cifs_share_access: *test_allow_rw_cifs_share_access
test_allow_rw_nfs_share_access:
nfs_share: &nfs_share__test_allow_rw_nfs_share_access
_properties:
<<: *nfs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
_methods:
allow_read_write_access:
allow_root_access:
unity:
_methods:
<<: *unity_base_method
get_nfs_share: *nfs_share__test_allow_rw_nfs_share_access
test_allow_ro_nfs_share_access:
nfs_share: &nfs_share__test_allow_ro_nfs_share_access
_properties:
<<: *nfs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
_methods:
allow_read_only_access:
unity:
_methods:
<<: *unity_base_method
get_nfs_share: *nfs_share__test_allow_ro_nfs_share_access
test_deny_cifs_share_access:
cifs_share: &cifs_share__test_deny_cifs_share_access
_properties:
<<: *cifs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
_methods:
delete_ace:
unity:
_methods:
<<: *unity_base_method
get_cifs_share: *cifs_share__test_deny_cifs_share_access
test_deny_nfs_share_access: &test_deny_nfs_share_access
nfs_share: &nfs_share__test_deny_nfs_share_access
_properties:
<<: *nfs_share_base_prop
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
_methods:
delete_access:
unity:
_methods:
<<: *unity_base_method
get_nfs_share: *nfs_share__test_deny_nfs_share_access
test_update_access_deny_nfs: *test_deny_nfs_share_access
# The following test cases are for client.py
test_create_cifs_share__existed_expt:
filesystem:
_methods:
create_cifs_share:
_raise:
UnitySmbShareNameExistedError: 'CIFS share already exists.'
cifs_share: &cifs_share__test_create_cifs_share__existed_expt
_properties:
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
unity:
_methods:
get_cifs_share: *cifs_share__test_create_cifs_share__existed_expt
test_create_nfs_share__existed_expt:
filesystem:
_methods:
create_nfs_share:
_raise:
UnityNfsShareNameExistedError: 'NFS share already exists.'
nfs_share: &nfs_share__test_create_nfs_share__existed_expt
_properties:
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
unity:
_methods:
get_nfs_share: *nfs_share__test_create_nfs_share__existed_expt
test_get_share_with_invalid_proto:
share:
_properties:
<<: *share_base_prop
test_create_filesystem__existed_expt:
filesystem: &filesystem__test_create_filesystem__existed_expt
_properties:
name: '716100cc-e0b4-416b-ac27-d38dd019330d'
size: 10
proto: 'CIFS'
pool:
_methods:
create_filesystem:
_raise:
UnityFileSystemNameAlreadyExisted: 'Pool already exists.'
nas_server:
_properties:
<<: *nas_server_prop
unity:
_methods:
get_filesystem: *filesystem__test_create_filesystem__existed_expt
test_delete_filesystem__nonexistent_expt:
filesystem:
_properties:
name: already removed filsystem
_methods:
delete:
_raise:
UnityResourceNotFoundError: 'Filesystem is non-existent.'
test_create_nas_server__existed_expt:
sp:
_properites:
name: 'SP'
pool:
_properites:
name: 'fake_pool'
nas_server: &nas_server__test_create_nas_server__existed_expt
_properties:
<<: *nas_server_prop
unity:
_methods:
create_nas_server:
_raise:
UnityNasServerNameUsedError: 'NAS Server already exists.'
get_nas_server: *nas_server__test_create_nas_server__existed_expt
test_delete_nas_server__nonexistent_expt:
nas_server: &nas_server__test_delete_nas_server__nonexistent_expt
_properties:
<<: *nas_server_prop
_methods:
delete:
_raise:
UnityResourceNotFoundError: 'NAS server is non-existent.'
unity:
_methods:
get_nas_server: *nas_server__test_delete_nas_server__nonexistent_expt
test_create_dns_server__existed_expt:
nas_server:
_methods:
create_dns_server:
_raise:
UnityOneDnsPerNasServerError: 'DNS server already exists.'
test_create_interface__existed_expt:
nas_server:
_properties:
<<: *nas_server_prop
_methods:
create_file_interface:
_raise:
UnityIpAddressUsedError: 'IP address is already used.'
test_enable_cifs_service__existed_expt:
nas_server:
_properties:
<<: *nas_server_prop
_methods:
enable_cifs_service:
_raise:
UnitySmbNameInUseError: 'CIFS server already exists.'
test_enable_nfs_service__existed_expt:
nas_server:
_properties:
<<: *nas_server_prop
_methods:
enable_nfs_service:
_raise:
UnityNfsAlreadyEnabledError: 'NFS server already exists.'
test_create_snapshot__existed_expt:
filesystem:
_properties:
<<: *filesystem_base_prop
_methods:
create_snap:
_raise:
UnitySnapNameInUseError: 'Snapshot already exists.'
snapshot:
_properties:
<<: *snap_base_prop
test_create_snap_of_snap__existed_expt:
src_snapshot:
_methods:
create_snap:
_raise:
UnitySnapNameInUseError: 'Snapshot already exists.'
dest_snapshot: &dest_snapshot__test_create_snap_of_snap__existed_expt
_properties:
<<: *snap_base_prop
unity:
_methods:
get_snap: *dest_snapshot__test_create_snap_of_snap__existed_expt
test_delete_snapshot__nonexistent_expt:
snapshot:
_properties:
<<: *snap_base_prop
_methods:
delete:
_raise:
UnityResourceNotFoundError: 'Snapshot is non-existent.'
test_nfs_deny_access__nonexistent_expt:
nfs_share: &nfs_share__test_nfs_deny_access__nonexistent_expt
_methods:
delete_access:
_raise:
UnityHostNotFoundException: "Unity Host is non-existent"
unity:
_methods:
get_nfs_share: *nfs_share__test_nfs_deny_access__nonexistent_expt
test_get_storage_processor:
unity:
_methods:
get_sp: *sp_a

View File

@ -0,0 +1,337 @@
# Copyright (c) 2016 EMC Corporation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from oslo_log import log
from manila.share import configuration as conf
from manila.share.drivers.emc.plugins.unity import client
from manila.share.drivers.emc.plugins.unity import connection
from manila.tests.db import fakes as db_fakes
from manila.tests import fake_share
from manila.tests.share.drivers.emc.plugins.unity import fake_exceptions
from manila.tests.share.drivers.emc.plugins.unity import utils
client.storops_ex = fake_exceptions
connection.storops_ex = fake_exceptions
LOG = log.getLogger(__name__)
SYMBOL_TYPE = '_type'
SYMBOL_PROPERTIES = '_properties'
SYMBOL_METHODS = '_methods'
SYMBOL_SIDE_EFFECT = '_side_effect'
SYMBOL_RAISE = '_raise'
def _has_side_effect(node):
return isinstance(node, dict) and SYMBOL_SIDE_EFFECT in node
def _has_raise(node):
return isinstance(node, dict) and SYMBOL_RAISE in node
def fake_share_server(**kwargs):
share_server = {
'instance_id': 'fake_instance_id',
'backend_details': {},
}
share_server.update(kwargs)
return db_fakes.FakeModel(share_server)
def fake_network_info(**kwargs):
network_info = {
'id': 'fake_net_id',
'name': 'net_name',
'subnet': [],
}
network_info.update(kwargs)
return network_info
def fake_server_detail(**kwargs):
server_detail = {
'share_server_name': 'fake_server_name',
}
server_detail.update(kwargs)
return server_detail
def fake_security_services(**kwargs):
return kwargs['services']
def fake_access(**kwargs):
access = {}
access.update(kwargs)
return access
class FakeEMCShareDriver(object):
def __init__(self):
self.configuration = conf.Configuration(None)
self.configuration.emc_share_backend = 'unity'
self.configuration.emc_nas_server_container = 'SPA'
self.configuration.emc_nas_server = '192.168.1.1'
self.configuration.emc_nas_login = 'fake_user'
self.configuration.emc_nas_password = 'fake_password'
self.configuration.share_backend_name = 'EMC_NAS_Storage'
self.configuration.emc_nas_server_pool = 'nas_server_pool'
self.configuration.local_conf.max_over_subscription_ratio = 20
STATS = dict(
share_backend_name='Unity',
vendor_name='EMC',
storage_protocol='NFS_CIFS',
driver_version='2.0.0,',
pools=[],
)
class DriverResourceMock(dict):
fake_func_mapping = {}
def __init__(self, yaml_file):
yaml_dict = utils.load_yaml(yaml_file)
if isinstance(yaml_dict, dict):
for name, body in yaml_dict.items():
if isinstance(body, dict):
props = body[SYMBOL_PROPERTIES]
if isinstance(props, dict):
for prop_name, prop_value in props.items():
if isinstance(prop_value, dict) and prop_value:
# get the first key as the convert function
func_name = list(prop_value.keys())[0]
if func_name.startswith('_'):
func = getattr(self, func_name)
props[prop_name] = (
func(**prop_value[func_name]))
if body[SYMBOL_TYPE] in self.fake_func_mapping:
self[name] = (
self.fake_func_mapping[body[SYMBOL_TYPE]](**props))
class ManilaResourceMock(DriverResourceMock):
fake_func_mapping = {
'share': fake_share.fake_share,
'snapshot': fake_share.fake_snapshot,
'network_info': fake_network_info,
'share_server': fake_share_server,
'server_detail': fake_server_detail,
'security_services': fake_security_services,
'access': fake_access,
}
def __init__(self, yaml_file):
super(ManilaResourceMock, self).__init__(yaml_file)
class StorageObjectMock(object):
PROPS = 'props'
def __init__(self, yaml_dict):
self.__dict__[StorageObjectMock.PROPS] = {}
props = yaml_dict.get(SYMBOL_PROPERTIES, None)
if props:
for k, v in props.items():
setattr(self, k, StoragePropertyMock(k, v)())
methods = yaml_dict.get(SYMBOL_METHODS, None)
if methods:
for k, v in methods.items():
setattr(self, k, StorageMethodMock(k, v))
def __setattr__(self, key, value):
self.__dict__[StorageObjectMock.PROPS][key] = value
def __getattr__(self, item):
try:
super(StorageObjectMock, self).__getattr__(item)
except AttributeError:
return self.__dict__[StorageObjectMock.PROPS][item]
except KeyError:
raise KeyError('No such method or property for mock object.')
class StoragePropertyMock(mock.PropertyMock):
def __init__(self, name, property_body):
return_value = property_body
side_effect = None
# only support return_value and side_effect for property
if _has_side_effect(property_body):
side_effect = property_body[SYMBOL_SIDE_EFFECT]
return_value = None
if side_effect:
super(StoragePropertyMock, self).__init__(
name=name,
side_effect=side_effect)
elif return_value:
super(StoragePropertyMock, self).__init__(
name=name,
return_value=_build_mock_object(return_value))
else:
super(StoragePropertyMock, self).__init__(
name=name,
return_value=return_value)
class StorageMethodMock(mock.Mock):
def __init__(self, name, method_body):
return_value = method_body
exception = None
side_effect = None
# support return_value, side_effect and exception for method
if _has_side_effect(method_body) or _has_raise(method_body):
exception = method_body.get(SYMBOL_RAISE, None)
side_effect = method_body.get(SYMBOL_SIDE_EFFECT, None)
return_value = None
if exception:
if isinstance(exception, dict) and exception:
ex_name = list(exception.keys())[0]
ex = getattr(fake_exceptions, ex_name)
super(StorageMethodMock, self).__init__(
name=name,
side_effect=ex(exception[ex_name]))
elif side_effect:
super(StorageMethodMock, self).__init__(
name=name,
side_effect=_build_mock_object(side_effect))
elif return_value:
super(StorageMethodMock, self).__init__(
name=name,
return_value=_build_mock_object(return_value))
else:
super(StorageMethodMock, self).__init__(
name=name, return_value=None)
class StorageResourceMock(dict):
def __init__(self, yaml_file):
yaml_dict = utils.load_yaml(yaml_file)
if isinstance(yaml_dict, dict):
for section, sec_body in yaml_dict.items():
self[section] = {}
if isinstance(sec_body, dict):
for obj_name, obj_body in sec_body.items():
self[section][obj_name] = _build_mock_object(obj_body)
def _is_mock_object(yaml_info):
return (isinstance(yaml_info, dict) and
(SYMBOL_PROPERTIES in yaml_info or SYMBOL_METHODS in yaml_info))
def _build_mock_object(yaml_dict):
if _is_mock_object(yaml_dict):
return StorageObjectMock(yaml_dict)
elif isinstance(yaml_dict, dict):
return {k: _build_mock_object(v) for k, v in yaml_dict.items()}
elif isinstance(yaml_dict, list):
return [_build_mock_object(each) for each in yaml_dict]
else:
return yaml_dict
manila_res = ManilaResourceMock('mocked_manila.yaml')
unity_res = StorageResourceMock('mocked_unity.yaml')
STORAGE_RES_MAPPING = {
'TestClient': unity_res,
'TestConnection': unity_res,
}
def mock_input(resource):
def inner_dec(func):
def decorated(cls, *args, **kwargs):
if cls._testMethodName in resource:
storage_res = resource[cls._testMethodName]
return func(cls, storage_res, *args, **kwargs)
return decorated
return inner_dec
mock_client_input = mock_input(unity_res)
def patch_client(func):
def client_decorator(cls, *args, **kwargs):
storage_res = {}
if func.__name__ in STORAGE_RES_MAPPING[cls.__class__.__name__]:
storage_res = (
STORAGE_RES_MAPPING[cls.__class__.__name__][func.__name__])
with utils.patch_system as patched_system:
if 'unity' in storage_res:
patched_system.return_value = storage_res['unity']
_client = client.UnityClient(host='fake_host',
username='fake_user',
password='fake_passwd')
return func(cls, _client, *args, **kwargs)
return client_decorator
def mock_driver_input(resource):
def inner_dec(func):
def decorated(cls, *args, **kwargs):
return func(cls, resource, *args, **kwargs)
return decorated
return inner_dec
mock_manila_input = mock_driver_input(manila_res)
def patch_connection_init(func):
def connection_decorator(cls, *args, **kwargs):
storage_res = {}
if func.__name__ in STORAGE_RES_MAPPING[cls.__class__.__name__]:
storage_res = (
STORAGE_RES_MAPPING[cls.__class__.__name__][func.__name__])
with utils.patch_system as patched_system:
if 'unity' in storage_res:
patched_system.return_value = storage_res['unity']
conn = connection.UnityStorageConnection(LOG)
return func(cls, conn, *args, **kwargs)
return connection_decorator
def patch_connection(func):
def connection_decorator(cls, *args, **kwargs):
storage_res = {}
if func.__name__ in STORAGE_RES_MAPPING[cls.__class__.__name__]:
storage_res = (
STORAGE_RES_MAPPING[cls.__class__.__name__][func.__name__])
with utils.patch_system as patched_system:
if 'unity' in storage_res:
patched_system.return_value = storage_res['unity']
conn = connection.UnityStorageConnection(LOG)
conn.connect(FakeEMCShareDriver(), None)
return func(cls, conn, *args, **kwargs)
return connection_decorator

View File

@ -0,0 +1,158 @@
# Copyright (c) 2016 EMC Corporation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from manila import exception
from manila import test
from manila.tests.share.drivers.emc.plugins.unity import res_mock
class TestClient(test.TestCase):
@res_mock.mock_client_input
@res_mock.patch_client
def test_create_cifs_share__existed_expt(self, client, mocked_input):
resource = mocked_input['filesystem']
share = mocked_input['cifs_share']
new_share = client.create_cifs_share(resource, share.name)
self.assertEqual(share.name, new_share.name)
@res_mock.mock_client_input
@res_mock.patch_client
def test_create_nfs_share__existed_expt(self, client, mocked_input):
resource = mocked_input['filesystem']
share = mocked_input['nfs_share']
new_share = client.create_nfs_share(resource, share.name)
self.assertEqual(share.name, new_share.name)
@res_mock.mock_client_input
@res_mock.patch_client
def test_get_share_with_invalid_proto(self, client, mocked_input):
share = mocked_input['share']
self.assertRaises(exception.BadConfigurationException,
client.get_share,
share.name,
'fake_proto')
@res_mock.mock_client_input
@res_mock.patch_client
def test_create_filesystem__existed_expt(self, client, mocked_input):
pool = mocked_input['pool']
nas_server = mocked_input['nas_server']
filesystem = mocked_input['filesystem']
new_filesystem = client.create_filesystem(pool,
nas_server,
filesystem.name,
filesystem.size,
filesystem.proto)
self.assertEqual(filesystem.name, new_filesystem.name)
@res_mock.mock_client_input
@res_mock.patch_client
def test_delete_filesystem__nonexistent_expt(self, client, mocked_input):
filesystem = mocked_input['filesystem']
client.delete_filesystem(filesystem)
@res_mock.mock_client_input
@res_mock.patch_client
def test_create_nas_server__existed_expt(self, client, mocked_input):
sp = mocked_input['sp']
pool = mocked_input['pool']
nas_server = mocked_input['nas_server']
new_nas_server = client.create_nas_server(nas_server.name, sp, pool)
self.assertEqual(nas_server.name, new_nas_server.name)
@res_mock.mock_client_input
@res_mock.patch_client
def test_delete_nas_server__nonexistent_expt(self, client, mocked_input):
nas_server = mocked_input['nas_server']
client.delete_nas_server(nas_server.name)
@res_mock.mock_client_input
@res_mock.patch_client
def test_create_dns_server__existed_expt(self, client, mocked_input):
nas_server = mocked_input['nas_server']
client.create_dns_server(nas_server, 'fake_domain', 'fake_dns_ip')
@res_mock.mock_client_input
@res_mock.patch_client
def test_create_interface__existed_expt(self, client, mocked_input):
nas_server = mocked_input['nas_server']
port_set = ('fake_port',)
self.assertRaises(exception.IPAddressInUse, client.create_interface,
nas_server, 'fake_ip_addr', 'fake_mask',
'fake_gateway', ports=port_set)
@res_mock.mock_client_input
@res_mock.patch_client
def test_enable_cifs_service__existed_expt(self, client, mocked_input):
nas_server = mocked_input['nas_server']
client.enable_cifs_service(
nas_server, 'domain_name', 'fake_user', 'fake_passwd')
@res_mock.mock_client_input
@res_mock.patch_client
def test_enable_nfs_service__existed_expt(self, client, mocked_input):
nas_server = mocked_input['nas_server']
client.enable_nfs_service(nas_server)
@res_mock.mock_client_input
@res_mock.patch_client
def test_create_snapshot__existed_expt(self, client, mocked_input):
nas_server = mocked_input['filesystem']
exp_snap = mocked_input['snapshot']
client.create_snapshot(nas_server, exp_snap.name)
@res_mock.mock_client_input
@res_mock.patch_client
def test_create_snap_of_snap__existed_expt(self, client, mocked_input):
snapshot = mocked_input['src_snapshot']
dest_snap = mocked_input['dest_snapshot']
new_snap = client.create_snap_of_snap(
snapshot, dest_snap.name, 'checkpoint')
self.assertEqual(dest_snap.name, new_snap.name)
@res_mock.mock_client_input
@res_mock.patch_client
def test_delete_snapshot__nonexistent_expt(self, client, mocked_input):
snapshot = mocked_input['snapshot']
client.delete_snapshot(snapshot)
@res_mock.patch_client
def test_nfs_deny_access__nonexistent_expt(self, client):
client.nfs_deny_access('fake_share_name', 'fake_ip_addr')
@res_mock.patch_client
def test_get_storage_processor(self, client):
sp = client.get_storage_processor(sp_id='SPA')
self.assertEqual('SPA', sp.name)

View File

@ -0,0 +1,617 @@
# Copyright (c) 2016 EMC Corporation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import ddt
import mock
from oslo_utils import units
import six
from manila import exception
from manila import test
from manila.tests.share.drivers.emc.plugins.unity import fake_exceptions
from manila.tests.share.drivers.emc.plugins.unity import res_mock
@ddt.ddt
class TestConnection(test.TestCase):
client = None
@classmethod
def setUpClass(cls):
cls.emc_share_driver = res_mock.FakeEMCShareDriver()
@res_mock.patch_connection_init
def test_connect(self, connection):
connection.connect(res_mock.FakeEMCShareDriver(), None)
@res_mock.patch_connection_init
def test_connect__invalid_sp_configuration(self, connection):
self.assertRaises(exception.BadConfigurationException,
connection.connect,
res_mock.FakeEMCShareDriver(), None)
@res_mock.patch_connection
def test_connect__invalid_pool_configuration(self, connection):
f = connection.client.system.get_pool
f.side_effect = fake_exceptions.UnityResourceNotFoundError()
self.assertRaises(exception.BadConfigurationException,
connection._config_pool,
'faked_pool_name')
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_create_nfs_share(self, connection, mocked_input):
share = mocked_input['nfs_share']
share_server = mocked_input['share_server']
location = connection.create_share(None, share, share_server)
exp_location = [
{'path': 'fake_ip_addr_1:/cb532599-8dc6-4c3e-bb21-74ea54be566c'},
{'path': 'fake_ip_addr_2:/cb532599-8dc6-4c3e-bb21-74ea54be566c'},
]
exp_location = sorted(exp_location, key=lambda x: sorted(x['path']))
location = sorted(location, key=lambda x: sorted(x['path']))
self.assertEqual(exp_location, location)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_create_cifs_share(self, connection, mocked_input):
share = mocked_input['cifs_share']
share_server = mocked_input['share_server']
location = connection.create_share(None, share, share_server)
exp_location = [
{'path': r'\\fake_ip_addr_1\716100cc-e0b4-416b-ac27-d38dd019330d'},
{'path': r'\\fake_ip_addr_2\716100cc-e0b4-416b-ac27-d38dd019330d'},
]
exp_location = sorted(exp_location, key=lambda x: sorted(x['path']))
location = sorted(location, key=lambda x: sorted(x['path']))
self.assertEqual(exp_location, location)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_create_share_with_invalid_proto(self, connection, mocked_input):
share = mocked_input['invalid_share']
share_server = mocked_input['share_server']
self.assertRaises(exception.InvalidShare,
connection.create_share,
None,
share,
share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_create_share_without_share_server(self, connection,
mocked_input):
share = mocked_input['cifs_share']
self.assertRaises(exception.InvalidInput,
connection.create_share,
None,
share,
None)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_create_share__no_server_name_in_backend_details(self, connection,
mocked_input):
share = mocked_input['cifs_share']
share_server = {
'backend_details': {'share_server_name': None},
'id': 'test',
}
self.assertRaises(exception.InvalidInput,
connection.create_share,
None,
share,
share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_create_share_with_invalid_share_server(self, connection,
mocked_input):
share = mocked_input['cifs_share']
share_server = mocked_input['share_server']
self.assertRaises(exception.EMCUnityError,
connection.create_share,
None,
share,
share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_delete_share(self, connection, mocked_input):
share = mocked_input['cifs_share']
share_server = mocked_input['share_server']
connection.delete_share(None, share, share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_delete_share__with_invalid_share(self, connection, mocked_input):
share = mocked_input['cifs_share']
connection.delete_share(None, share, None)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_delete_share__create_from_snap(self, connection,
mocked_input):
share = mocked_input['cifs_share']
share_server = mocked_input['share_server']
connection.delete_share(None, share, share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_delete_share__create_from_snap_but_not_isolated(self,
connection,
mocked_input):
share = mocked_input['cifs_share']
share_server = mocked_input['share_server']
connection.delete_share(None, share, share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_delete_share__but_not_isolated(self, connection,
mocked_input):
share = mocked_input['cifs_share']
share_server = mocked_input['share_server']
connection.delete_share(None, share, share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_extend_cifs_share(self, connection, mocked_input):
share = mocked_input['cifs_share']
share_server = mocked_input['share_server']
new_size = 50 * units.Gi
connection.extend_share(share, new_size, share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_extend_nfs_share(self, connection, mocked_input):
share = mocked_input['nfs_share']
share_server = mocked_input['share_server']
new_size = 50 * units.Gi
connection.extend_share(share, new_size, share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_extend_share__create_from_snap(self, connection, mocked_input):
share = mocked_input['cifs_share']
share_server = mocked_input['share_server']
new_size = 50 * units.Gi
self.assertRaises(exception.ShareExtendingError,
connection.extend_share,
share,
new_size,
share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_create_snapshot_from_filesystem(self, connection, mocked_input):
snapshot = mocked_input['snapshot']
share_server = mocked_input['share_server']
connection.create_snapshot(None, snapshot, share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_create_snapshot_from_snapshot(self, connection, mocked_input):
snapshot = mocked_input['snapshot']
share_server = mocked_input['share_server']
connection.create_snapshot(None, snapshot, share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_delete_snapshot(self, connection, mocked_input):
snapshot = mocked_input['snapshot']
share_server = mocked_input['share_server']
connection.delete_snapshot(None, snapshot, share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_ensure_share_exists(self, connection, mocked_input):
share = mocked_input['cifs_share']
connection.ensure_share(None, share, None)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_ensure_share_not_exists(self, connection, mocked_input):
share = mocked_input['cifs_share']
self.assertRaises(exception.ShareNotFound,
connection.ensure_share,
None,
share,
None)
@res_mock.patch_connection
def test_update_share_stats(self, connection):
stat_dict = copy.deepcopy(res_mock.STATS)
connection.update_share_stats(stat_dict)
self.assertEqual(5, len(stat_dict))
pool = stat_dict['pools'][0]
self.assertEqual('pool_1', pool['pool_name'])
self.assertEqual(500000.0, pool['total_capacity_gb'])
self.assertEqual(False, pool['qos'])
self.assertEqual(30000.0, pool['provisioned_capacity_gb'])
self.assertEqual(20, pool['max_over_subscription_ratio'])
self.assertEqual(10000.0, pool['allocated_capacity_gb'])
self.assertEqual(0, pool['reserved_percentage'])
self.assertTrue(pool['thin_provisioning'])
self.assertEqual(490000.0, pool['free_capacity_gb'])
@res_mock.patch_connection
def test_update_share_stats__nonexistent_pools(self, connection):
stat_dict = copy.deepcopy(res_mock.STATS)
self.assertRaises(exception.EMCUnityError,
connection.update_share_stats,
stat_dict)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_get_pool(self, connection, mocked_input):
share = mocked_input['cifs_share']
connection.get_pool(share)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_setup_server(self, connection, mocked_input):
network_info = mocked_input['network_info__flat']
connection.setup_server(network_info)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_setup_server__vlan_network(self, connection, mocked_input):
network_info = mocked_input['network_info__vlan']
connection.setup_server(network_info)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_setup_server__vxlan_network(self, connection, mocked_input):
network_info = mocked_input['network_info__vxlan']
self.assertRaises(exception.NetworkBadConfigurationException,
connection.setup_server,
network_info)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_setup_server__active_directory(self, connection, mocked_input):
network_info = mocked_input['network_info__active_directory']
connection.setup_server(network_info)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_setup_server__kerberos(self, connection, mocked_input):
network_info = mocked_input['network_info__kerberos']
connection.setup_server(network_info)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_setup_server__throw_exception(self, connection, mocked_input):
network_info = mocked_input['network_info__flat']
self.assertRaises(fake_exceptions.UnityException,
connection.setup_server,
network_info)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_teardown_server(self, connection, mocked_input):
server_detail = mocked_input['server_detail']
security_services = mocked_input['security_services']
connection.teardown_server(server_detail, security_services)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_teardown_server__no_server_detail(self, connection, mocked_input):
security_services = mocked_input['security_services']
connection.teardown_server(None, security_services)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_teardown_server__no_share_server_name(self, connection,
mocked_input):
server_detail = {'share_server_name': None}
security_services = mocked_input['security_services']
connection.teardown_server(server_detail, security_services)
@ddt.data({'configured_pools': None,
'matched_pools': {'pool_1', 'pool_2', 'nas_server_pool'}},
{'configured_pools': ['*'],
'matched_pools': {'pool_1', 'pool_2', 'nas_server_pool'}},
{'configured_pools': ['pool_*'],
'matched_pools': {'pool_1', 'pool_2'}},
{'configured_pools': ['*pool'],
'matched_pools': {'nas_server_pool'}},
{'configured_pools': ['nas_server_pool'],
'matched_pools': {'nas_server_pool'}},
{'configured_pools': ['nas_*', 'pool_*'],
'matched_pools': {'pool_1', 'pool_2', 'nas_server_pool'}})
@res_mock.patch_connection
@ddt.unpack
def test__get_managed_pools(self, connection, mocked_input):
configured_pools = mocked_input['configured_pools']
matched_pool = mocked_input['matched_pools']
pools = connection._get_managed_pools(configured_pools)
self.assertEqual(matched_pool, pools)
@res_mock.patch_connection
def test__get_managed_pools__invalid_pool_configuration(self, connection):
configured_pools = 'fake_pool'
self.assertRaises(exception.BadConfigurationException,
connection._get_managed_pools,
configured_pools)
@ddt.data({'configured_ports': None,
'matched_ports': {'spa_eth1', 'spa_eth2'}},
{'configured_ports': ['*'],
'matched_ports': {'spa_eth1', 'spa_eth2'}},
{'configured_ports': ['spa_*'],
'matched_ports': {'spa_eth1', 'spa_eth2'}},
{'configured_ports': ['*_eth1'],
'matched_ports': {'spa_eth1'}},
{'configured_ports': ['spa_eth1'],
'matched_ports': {'spa_eth1'}},
{'configured_ports': ['spa_eth1', 'spa_eth2'],
'matched_ports': {'spa_eth1', 'spa_eth2'}})
@res_mock.patch_connection
@ddt.unpack
def test__get_managed_ports(self, connection, mocked_input):
sp = mock.Mock()
sp.id = 'SPA'
configured_ports = mocked_input['configured_ports']
matched_ports = mocked_input['matched_ports']
ports = connection._get_managed_ports(configured_ports, sp)
self.assertEqual(matched_ports, ports)
@res_mock.patch_connection
def test__get_managed_ports__invalid_port_configuration(self, connection):
configured_ports = 'fake_port'
sp = mock.Mock()
sp.id = 'SPA'
self.assertRaises(exception.BadConfigurationException,
connection._get_managed_ports,
configured_ports,
sp)
@res_mock.patch_connection
def test__get_pool_name_from_host__no_pool_name(self, connection):
host = 'openstack@Unity'
self.assertRaises(exception.InvalidHost,
connection._get_pool_name_from_host,
host)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_create_cifs_share_from_snapshot(self, connection, mocked_input):
share = mocked_input['cifs_share']
snapshot = mocked_input['snapshot']
share_server = mocked_input['share_server']
connection.create_share_from_snapshot(None, share, snapshot,
share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_create_nfs_share_from_snapshot(self, connection, mocked_input):
share = mocked_input['nfs_share']
snapshot = mocked_input['snapshot']
share_server = mocked_input['share_server']
connection.create_share_from_snapshot(None, share, snapshot,
share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_create_share_from_snapshot_no_server_name(self,
connection,
mocked_input):
share = mocked_input['nfs_share']
snapshot = mocked_input['snapshot']
share_server = mocked_input['share_server__no_share_server_name']
self.assertRaises(exception.EMCUnityError,
connection.create_share_from_snapshot,
None,
share,
snapshot,
share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_clear_share_access_cifs(self, connection, mocked_input):
share = mocked_input['cifs_share']
self.assertRaises(fake_exceptions.UnityException,
connection.clear_access,
share)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_clear_share_access_nfs(self, connection, mocked_input):
share = mocked_input['nfs_share']
self.assertRaises(fake_exceptions.UnityException,
connection.clear_access,
share)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_allow_rw_cifs_share_access(self, connection, mocked_input):
share = mocked_input['cifs_share']
rw_access = mocked_input['cifs_rw_access']
share_server = mocked_input['share_server']
connection.allow_access(None, share, rw_access, share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_update_access_allow_rw(self, connection, mocked_input):
share = mocked_input['cifs_share']
rw_access = mocked_input['cifs_rw_access']
share_server = mocked_input['share_server']
connection.update_access(None, share, None, [rw_access], None,
share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_update_access_recovery(self, connection, mocked_input):
share = mocked_input['cifs_share']
rw_access = mocked_input['cifs_rw_access']
share_server = mocked_input['share_server']
connection.update_access(None, share, [rw_access], None, None,
share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_allow_ro_cifs_share_access(self, connection, mocked_input):
share = mocked_input['cifs_share']
rw_access = mocked_input['cifs_ro_access']
share_server = mocked_input['share_server']
connection.allow_access(None, share, rw_access, share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_allow_rw_nfs_share_access(self, connection, mocked_input):
share = mocked_input['nfs_share']
rw_access = mocked_input['nfs_rw_access']
share_server = mocked_input['share_server']
connection.allow_access(None, share, rw_access, share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_allow_rw_nfs_share_access_cidr(self, connection, mocked_input):
share = mocked_input['nfs_share']
rw_access = mocked_input['nfs_rw_access_cidr']
share_server = mocked_input['share_server']
connection.allow_access(None, share, rw_access, share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_allow_ro_nfs_share_access(self, connection, mocked_input):
share = mocked_input['nfs_share']
ro_access = mocked_input['nfs_ro_access']
share_server = mocked_input['share_server']
connection.allow_access(None, share, ro_access, share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_deny_cifs_share_access(self, connection, mocked_input):
share = mocked_input['cifs_share']
rw_access = mocked_input['cifs_rw_access']
share_server = mocked_input['share_server']
connection.deny_access(None, share, rw_access, share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_deny_nfs_share_access(self, connection, mocked_input):
share = mocked_input['nfs_share']
rw_access = mocked_input['nfs_rw_access']
share_server = mocked_input['share_server']
connection.deny_access(None, share, rw_access, share_server)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_update_access_deny_nfs(self, connection, mocked_input):
share = mocked_input['nfs_share']
rw_access = mocked_input['nfs_rw_access']
connection.update_access(None, share, None, None, [rw_access], None)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test__validate_cifs_share_access_type(self, connection, mocked_input):
share = mocked_input['cifs_share']
rw_access = mocked_input['invalid_access']
self.assertRaises(exception.InvalidShareAccess,
connection._validate_share_access_type,
share,
rw_access)
@res_mock.mock_manila_input
@res_mock.patch_connection
def test__validate_nfs_share_access_type(self, connection, mocked_input):
share = mocked_input['nfs_share']
rw_access = mocked_input['invalid_access']
self.assertRaises(exception.InvalidShareAccess,
connection._validate_share_access_type,
share,
rw_access)
@res_mock.patch_connection
def test_get_network_allocations_number(self, connection):
self.assertEqual(2, connection.get_network_allocations_number())
@res_mock.patch_connection
def test_get_proto_enum(self, connection):
self.assertIn('FSSupportedProtocolEnum.CIFS',
six.text_type(connection._get_proto_enum('CIFS')))
self.assertIn('FSSupportedProtocolEnum.NFS',
six.text_type(connection._get_proto_enum('nfs')))
@res_mock.mock_manila_input
@res_mock.patch_connection
def test_allow_access_error_access_level(self, connection, mocked_input):
share = mocked_input['nfs_share']
rw_access = mocked_input['invalid_access']
self.assertRaises(exception.InvalidShareAccessLevel,
connection.allow_access,
None, share, rw_access)

View File

@ -0,0 +1,50 @@
# Copyright (c) 2016 EMC Corporation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import ddt
from manila.share.drivers.emc.plugins.unity import utils
from manila import test
@ddt.ddt
class TestUtils(test.TestCase):
@ddt.data({'matcher': None,
'matched': {'pool_1', 'pool_2', 'nas_server_pool'},
'not_matched': set()},
{'matcher': ['*'],
'matched': {'pool_1', 'pool_2', 'nas_server_pool'},
'not_matched': set()},
{'matcher': ['pool_*'],
'matched': {'pool_1', 'pool_2'},
'not_matched': {'nas_server_pool'}},
{'matcher': ['*pool'],
'matched': {'nas_server_pool'},
'not_matched': {'pool_1', 'pool_2'}},
{'matcher': ['nas_server_pool'],
'matched': {'nas_server_pool'},
'not_matched': {'pool_1', 'pool_2'}},
{'matcher': ['nas_*', 'pool_*'],
'matched': {'pool_1', 'pool_2', 'nas_server_pool'},
'not_matched': set()})
def test_do_match(self, data):
full = ['pool_1 ', ' pool_2', ' nas_server_pool ']
matcher = data['matcher']
expected_matched = data['matched']
expected_not_matched = data['not_matched']
matched, not_matched = utils.do_match(full, matcher)
self.assertEqual(expected_matched, matched)
self.assertEqual(expected_not_matched, not_matched)

View File

@ -0,0 +1,32 @@
# Copyright (c) 2016 EMC Corporation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from os import path
import yaml
import mock
from oslo_log import log
LOG = log.getLogger(__name__)
patch_system = mock.patch('storops.UnitySystem')
def load_yaml(file_name):
yaml_file = '{}/{}'.format(path.dirname(path.abspath(__file__)), file_name)
with open(yaml_file) as f:
res = yaml.load(f)
LOG.debug('Loaded yaml mock objects from %s.', yaml_file)
return res

View File

@ -84,16 +84,19 @@ class StorageConnectionTestCase(test.TestCase):
@ddt.data({'pool_conf': None,
'real_pools': ['fake_pool', 'nas_pool'],
'matched_pool': set()},
{'pool_conf': '*',
{'pool_conf': [],
'real_pools': ['fake_pool', 'nas_pool'],
'matched_pool': set()},
{'pool_conf': ['*'],
'real_pools': ['fake_pool', 'nas_pool'],
'matched_pool': {'fake_pool', 'nas_pool'}},
{'pool_conf': 'fake_*',
{'pool_conf': ['fake_*'],
'real_pools': ['fake_pool', 'nas_pool', 'Perf_Pool'],
'matched_pool': {'fake_pool'}},
{'pool_conf': '*pool',
{'pool_conf': ['*pool'],
'real_pools': ['fake_pool', 'NAS_Pool', 'Perf_POOL'],
'matched_pool': {'fake_pool'}},
{'pool_conf': 'nas_pool',
{'pool_conf': ['nas_pool'],
'real_pools': ['fake_pool', 'nas_pool', 'perf_pool'],
'matched_pool': {'nas_pool'}})
@ddt.unpack
@ -120,11 +123,11 @@ class StorageConnectionTestCase(test.TestCase):
xml_req_mock.assert_has_calls(expected_calls)
@ddt.data(
{'pool_conf': 'fake_*',
{'pool_conf': ['fake_*'],
'real_pools': ['nas_pool', 'Perf_Pool']},
{'pool_conf': '*pool',
{'pool_conf': ['*pool'],
'real_pools': ['NAS_Pool', 'Perf_POOL']},
{'pool_conf': 'nas_pool',
{'pool_conf': ['nas_pool'],
'real_pools': ['fake_pool', 'perf_pool']},
)
@ddt.unpack

View File

@ -0,0 +1,11 @@
---
prelude: >
Add a new EMC Unity plugin in manila which allows user to create NFS/CIFS
share with a EMC Unity backend.
features:
- |
Add a new Unity plugin in manila which allows user to create NFS/CIFS
share with a EMC Unity backend. This plugin performs the operations on
Unity by REST API.
issues:
- EMC Unity does not support the same IP in different VLANs.

View File

@ -60,6 +60,7 @@ oslo.config.opts.defaults =
manila = manila.common.config:set_middleware_defaults
manila.share.drivers.emc.plugins =
vnx = manila.share.drivers.emc.plugins.vnx.connection:VNXStorageConnection
unity = manila.share.drivers.emc.plugins.unity.connection:UnityStorageConnection
isilon = manila.share.drivers.emc.plugins.isilon.isilon:IsilonStorageConnection
manila.tests.scheduler.fakes =
FakeWeigher1 = manila.tests.scheduler.fakes:FakeWeigher1