diff --git a/doc/source/admin/emc_vnx_driver.rst b/doc/source/admin/emc_vnx_driver.rst index 5f4b16b6d5..00bbbb857e 100644 --- a/doc/source/admin/emc_vnx_driver.rst +++ b/doc/source/admin/emc_vnx_driver.rst @@ -189,6 +189,7 @@ for the VNX driver: emc_nas_pool_name = emc_interface_ports = share_driver = manila.share.drivers.dell_emc.driver.EMCShareDriver + driver_handles_share_servers = True - `emc_share_backend` is the plugin name. Set it to `vnx` for the VNX driver. - `emc_nas_server` is the control station IP address of the VNX system to be @@ -204,10 +205,36 @@ for the VNX driver: Members of the list can be Unix-style glob expressions (supports Unix shell-style wildcards). This list is optional. In the absence of this option, any of the ports on the Data Mover can be used. +- `driver_handles_share_servers` must be True, the driver will choose a port + from port list which configured in emc_interface_ports. Restart of :term:`manila-share` service is needed for the configuration changes to take effect. +IPv6 support +------------ + +IPv6 support for VNX driver is introduced in Queens release. The feature is divided +into two parts: + +1. The driver is able to manage share or snapshot in the Neutron IPv6 network. +2. The driver is able to connect VNX management interface using its IPv6 address. + +Pre-Configurations for IPv6 support +=================================== + +The following parameters need to be configured in `/etc/manila/manila.conf` +for the VNX driver: + + network_plugin_ipv6_enabled = True + +- `network_plugin_ipv6_enabled` indicates IPv6 is enabled. + +If you want to connect VNX using IPv6 address, you should configure IPv6 address +by `nas_cs` command for VNX and specify the address in `/etc/manila/manila.conf`: + + emc_nas_server = + Restrictions ------------ diff --git a/doc/source/admin/share_back_ends_feature_support_mapping.rst b/doc/source/admin/share_back_ends_feature_support_mapping.rst index 6b8d19f162..9983ae6cbd 100644 --- a/doc/source/admin/share_back_ends_feature_support_mapping.rst +++ b/doc/source/admin/share_back_ends_feature_support_mapping.rst @@ -104,7 +104,7 @@ Mapping of share drivers and share access rules support +----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+ | EMC VMAX | NFS (O) | \- | CIFS (O) | \- | \- | NFS (O) | \- | CIFS (O) | \- | \- | +----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+ -| EMC VNX | NFS (J) | \- | CIFS (J) | \- | \- | NFS (L) | \- | CIFS (L) | \- | \- | +| EMC VNX | NFS (J) | NFS (Q) | CIFS (J) | \- | \- | NFS (L) | NFS (Q) | CIFS (L) | \- | \- | +----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+ | EMC Unity | NFS (N) | \- | CIFS (N) | \- | \- | NFS (N) | \- | CIFS (N) | \- | \- | +----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+ @@ -224,7 +224,7 @@ More information: :ref:`capabilities_and_extra_specs` +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ | EMC VMAX | O | \- | \- | \- | \- | O | \- | O | \- | \- | P | \- | +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ -| EMC VNX | J | \- | \- | \- | \- | L | \- | J | \- | \- | P | \- | +| EMC VNX | J | \- | \- | \- | \- | L | \- | J | \- | \- | P | Q | +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ | EMC Unity | N | \- | \- | \- | N | \- | \- | N | \- | \- | P | \- | +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ diff --git a/manila/share/drivers/dell_emc/common/enas/connector.py b/manila/share/drivers/dell_emc/common/enas/connector.py index 72a11ba605..1a90444040 100644 --- a/manila/share/drivers/dell_emc/common/enas/connector.py +++ b/manila/share/drivers/dell_emc/common/enas/connector.py @@ -35,7 +35,8 @@ LOG = log.getLogger(__name__) class XMLAPIConnector(object): def __init__(self, configuration, debug=True): super(XMLAPIConnector, self).__init__() - self.storage_ip = configuration.emc_nas_server + self.storage_ip = enas_utils.convert_ipv6_format_if_needed( + configuration.emc_nas_server) self.username = configuration.emc_nas_login self.password = configuration.emc_nas_password self.debug = debug diff --git a/manila/share/drivers/dell_emc/common/enas/utils.py b/manila/share/drivers/dell_emc/common/enas/utils.py index 3bcd7f6901..5a22fbecad 100644 --- a/manila/share/drivers/dell_emc/common/enas/utils.py +++ b/manila/share/drivers/dell_emc/common/enas/utils.py @@ -18,6 +18,7 @@ import types from oslo_config import cfg from oslo_log import log from oslo_utils import fnmatch +from oslo_utils import netutils from oslo_utils import timeutils import ssl @@ -103,3 +104,78 @@ def create_ssl_context(configuration): 'version of Python, ssl verification is disabled.') context = None return context + + +def parse_ipaddr(text): + """Parse the output of VNX server_export command, get IPv4/IPv6 addresses. + + Example: + input: 192.168.100.102:[fdf8:f53b:82e4::57]:[fdf8:f53b:82e4::54] + output: ['192.168.100.102', '[fdf8:f53b:82e4::57]', '[fdf8:f53b:82e4::54]'] + + :param text: The output of VNX server_export command. + :return: The list of IPv4/IPv6 addresses. The IPv6 address enclosed by []. + """ + rst = [] + stk = [] + + ipaddr = '' + it = iter(text) + + try: + while True: + i = next(it) + if i == ':' and not stk and ipaddr: + rst.append(ipaddr) + ipaddr = '' + elif i == ':' and not ipaddr: + continue + elif i == '[': + stk.append(i) + elif i == ']': + rst.append('[%s]' % ipaddr) + stk.pop() + ipaddr = '' + else: + ipaddr += i + except StopIteration: + if ipaddr: + rst.append(ipaddr) + + return rst + + +def convert_ipv6_format_if_needed(ip_addr): + """Convert IPv6 address format if needed. The IPv6 address enclosed by []. + + For the invalid IPv6 cidr, its format will not be changed. + + :param ip_addr: IPv6 address. + :return: Converted IPv6 address. + """ + if netutils.is_valid_ipv6_cidr(ip_addr): + ip_addr = '[%s]' % ip_addr + return ip_addr + + +def export_unc_path(ip_addr): + """Convert IPv6 address to valid UNC path. + + In Microsoft Windows OS, UNC (Uniform Naming Convention) specifies a + common syntax to describe the location of a network resource. + + The colon which used by IPv6 is an illegal character in a UNC path name. + So the IPv6 address need to be converted to valid UNC path. + + References: + - https://en.wikipedia.org/wiki/IPv6_address + #Literal_IPv6_addresses_in_UNC_path_names + - https://en.wikipedia.org/wiki/Path_(computing)#Uniform_Naming_Convention + + :param ip_addr: IPv6 address. + :return: UNC path. + """ + unc_suffix = '.ipv6-literal.net' + if netutils.is_valid_ipv6(ip_addr): + ip_addr = ip_addr.replace(':', '-') + unc_suffix + return ip_addr diff --git a/manila/share/drivers/dell_emc/driver.py b/manila/share/drivers/dell_emc/driver.py index ec6f9ef398..46d684e225 100644 --- a/manila/share/drivers/dell_emc/driver.py +++ b/manila/share/drivers/dell_emc/driver.py @@ -78,6 +78,9 @@ class EMCShareDriver(driver.ShareDriver): super(EMCShareDriver, self).__init__( self.plugin.driver_handles_share_servers, *args, **kwargs) + if hasattr(self.plugin, 'ipv6_implemented'): + self.ipv6_implemented = self.plugin.ipv6_implemented + def create_share(self, context, share, share_server=None): """Is called to create share.""" location = self.plugin.create_share(context, share, share_server) @@ -159,3 +162,9 @@ class EMCShareDriver(driver.ShareDriver): def _teardown_server(self, server_details, security_services=None): """Teardown share server.""" return self.plugin.teardown_server(server_details, security_services) + + def get_configured_ip_versions(self): + if self.ipv6_implemented: + return [4, 6] + else: + return [4] diff --git a/manila/share/drivers/dell_emc/plugins/vnx/connection.py b/manila/share/drivers/dell_emc/plugins/vnx/connection.py index 74bd188c7b..65b41ce2ae 100644 --- a/manila/share/drivers/dell_emc/plugins/vnx/connection.py +++ b/manila/share/drivers/dell_emc/plugins/vnx/connection.py @@ -16,6 +16,7 @@ import copy import random +import six from oslo_config import cfg from oslo_log import log @@ -37,8 +38,9 @@ from manila import utils 2.0.0 - Bumped the version for Mitaka 3.0.0 - Bumped the version for Ocata 4.0.0 - Bumped the version for Pike + 5.0.0 - Bumped the version for Queens """ -VERSION = "4.0.0" +VERSION = "5.0.0" LOG = log.getLogger(__name__) @@ -79,6 +81,7 @@ class VNXStorageConnection(driver.StorageConnection): self.reserved_percentage = None self.driver_handles_share_servers = True self.port_conf = None + self.ipv6_implemented = True def create_share(self, context, share, share_server=None): """Create a share and export it based on protocol used.""" @@ -179,7 +182,7 @@ class VNXStorageConnection(driver.StorageConnection): LOG.error(message) raise exception.EMCVnxXMLAPIError(err=message) - interface = server['interfaces'][0] + interface = enas_utils.export_unc_path(server['interfaces'][0]) self._get_context('CIFSShare').create(share_name, server['name'], vdm_name) @@ -199,8 +202,11 @@ class VNXStorageConnection(driver.StorageConnection): self._get_context('NFSShare').create(share_name, vdm_name) + nfs_if = enas_utils.convert_ipv6_format_if_needed( + share_server['backend_details']['nfs_if']) + return ('%(nfs_if)s:/%(share_name)s' - % {'nfs_if': share_server['backend_details']['nfs_if'], + % {'nfs_if': nfs_if, 'share_name': share_name}) def create_share_from_snapshot(self, context, share, snapshot, @@ -228,10 +234,13 @@ class VNXStorageConnection(driver.StorageConnection): self._allocate_container_from_snapshot( share, snapshot, share_server, pool_name) + nfs_if = enas_utils.convert_ipv6_format_if_needed( + share_server['backend_details']['nfs_if']) + if share_proto == 'NFS': self._create_nfs_share(share_name, share_server) location = ('%(nfs_if)s:/%(share_name)s' - % {'nfs_if': share_server['backend_details']['nfs_if'], + % {'nfs_if': nfs_if, 'share_name': share_name}) elif share_proto == 'CIFS': location = self._create_cifs_share(share_name, share_server) @@ -243,9 +252,9 @@ class VNXStorageConnection(driver.StorageConnection): share_name = snapshot['share_id'] status, filesystem = self._get_context('FileSystem').get(share_name) if status != constants.STATUS_OK: - message = (_("File System %s not found.") % share_name) - LOG.error(message) - raise exception.EMCVnxXMLAPIError(err=message) + message = (_("File System %s not found.") % share_name) + LOG.error(message) + raise exception.EMCVnxXMLAPIError(err=message) pool_id = filesystem['pools_id'][0] @@ -371,9 +380,9 @@ class VNXStorageConnection(driver.StorageConnection): status, server = self._get_context('CIFSServer').get(server_name, vdm_name) if status != constants.STATUS_OK: - message = (_("CIFS server %s not found.") % server_name) - LOG.error(message) - raise exception.EMCVnxXMLAPIError(err=message) + message = (_("CIFS server %s not found.") % server_name) + LOG.error(message) + raise exception.EMCVnxXMLAPIError(err=message) self._get_context('CIFSShare').allow_share_access( vdm_name, @@ -413,7 +422,9 @@ class VNXStorageConnection(driver.StorageConnection): white_list = [] for rule in access_rules: self.allow_access(context, share, rule, share_server) - white_list.append(rule['access_to']) + white_list.append( + enas_utils.convert_ipv6_format_if_needed( + rule['access_to'])) self.clear_access(share, share_server, white_list) def clear_access(self, share, share_server, white_list): @@ -488,9 +499,9 @@ class VNXStorageConnection(driver.StorageConnection): status, server = self._get_context('CIFSServer').get(server_name, vdm_name) if status != constants.STATUS_OK: - message = (_("CIFS server %s not found.") % server_name) - LOG.error(message) - raise exception.EMCVnxXMLAPIError(err=message) + message = (_("CIFS server %s not found.") % server_name) + LOG.error(message) + raise exception.EMCVnxXMLAPIError(err=message) self._get_context('CIFSShare').deny_share_access( vdm_name, @@ -509,7 +520,7 @@ class VNXStorageConnection(driver.StorageConnection): reason = _('Only ip access type allowed.') raise exception.InvalidShareAccess(reason=reason) - host_ip = access['access_to'] + host_ip = enas_utils.convert_ipv6_format_if_needed(access['access_to']) self._get_context('NFSShare').deny_share_access(share['id'], host_ip, vdm_name) @@ -681,21 +692,29 @@ class VNXStorageConnection(driver.StorageConnection): 'share server...', vdm_name) self._get_context('VDM').create(vdm_name, self.mover_name) - netmask = utils.cidr_to_netmask(network_info['cidr']) - devices = self.get_managed_ports() for net_info in network_info['network_allocations']: random.shuffle(devices) + + ip_version = net_info['ip_version'] + interface = { 'name': net_info['id'][-12:], 'device_name': devices[0], 'ip': net_info['ip_address'], 'mover_name': self.mover_name, - 'net_mask': netmask, 'vlan_id': vlan_id if vlan_id else -1, } + if ip_version == 6: + interface['ip_version'] = ip_version + interface['net_mask'] = six.text_type( + utils.cidr_to_prefixlen(network_info['cidr'])) + else: + interface['net_mask'] = utils.cidr_to_netmask( + network_info['cidr']) + self._get_context('MoverInterface').create(interface) allocated_interfaces.append(interface) diff --git a/manila/share/drivers/dell_emc/plugins/vnx/object_manager.py b/manila/share/drivers/dell_emc/plugins/vnx/object_manager.py index ef3ac3c98a..aac1866318 100644 --- a/manila/share/drivers/dell_emc/plugins/vnx/object_manager.py +++ b/manila/share/drivers/dell_emc/plugins/vnx/object_manager.py @@ -27,15 +27,15 @@ from manila import exception from manila.i18n import _ from manila.share.drivers.dell_emc.common.enas import connector from manila.share.drivers.dell_emc.common.enas import constants -from manila.share.drivers.dell_emc.common.enas import utils as vnx_utils +from manila.share.drivers.dell_emc.common.enas import utils as enas_utils from manila.share.drivers.dell_emc.common.enas import xml_api_parser as parser from manila import utils LOG = log.getLogger(__name__) -@vnx_utils.decorate_all_methods(vnx_utils.log_enter_exit, - debug_only=True) +@enas_utils.decorate_all_methods(enas_utils.log_enter_exit, + debug_only=True) class StorageObjectManager(object): def __init__(self, configuration): self.context = dict() @@ -211,8 +211,8 @@ class StorageObject(object): return self.manager.getStorageContext(type) -@vnx_utils.decorate_all_methods(vnx_utils.log_enter_exit, - debug_only=True) +@enas_utils.decorate_all_methods(enas_utils.log_enter_exit, + debug_only=True) class FileSystem(StorageObject): def __init__(self, conn, elt_maker, xml_parser, manager): super(FileSystem, self).__init__(conn, elt_maker, xml_parser, manager) @@ -479,8 +479,8 @@ class FileSystem(StorageObject): self._execute_cmd(rw_mount_cmd) -@vnx_utils.decorate_all_methods(vnx_utils.log_enter_exit, - debug_only=True) +@enas_utils.decorate_all_methods(enas_utils.log_enter_exit, + debug_only=True) class StoragePool(StorageObject): def __init__(self, conn, elt_maker, xml_parser, manager): super(StoragePool, self).__init__(conn, elt_maker, xml_parser, manager) @@ -541,8 +541,8 @@ class StoragePool(StorageObject): return out['id'] -@vnx_utils.decorate_all_methods(vnx_utils.log_enter_exit, - debug_only=True) +@enas_utils.decorate_all_methods(enas_utils.log_enter_exit, + debug_only=True) class MountPoint(StorageObject): def __init__(self, conn, elt_maker, xml_parser, manager): super(MountPoint, self).__init__(conn, elt_maker, xml_parser, manager) @@ -682,8 +682,8 @@ class MountPoint(StorageObject): return False -@vnx_utils.decorate_all_methods(vnx_utils.log_enter_exit, - debug_only=True) +@enas_utils.decorate_all_methods(enas_utils.log_enter_exit, + debug_only=True) class Mover(StorageObject): def __init__(self, conn, elt_maker, xml_parser, manager): super(Mover, self).__init__(conn, elt_maker, xml_parser, manager) @@ -847,8 +847,8 @@ class Mover(StorageObject): return physical_network_devices -@vnx_utils.decorate_all_methods(vnx_utils.log_enter_exit, - debug_only=True) +@enas_utils.decorate_all_methods(enas_utils.log_enter_exit, + debug_only=True) class VDM(StorageObject): def __init__(self, conn, elt_maker, xml_parser, manager): super(VDM, self).__init__(conn, elt_maker, xml_parser, manager) @@ -1022,8 +1022,8 @@ class VDM(StorageObject): return interfaces -@vnx_utils.decorate_all_methods(vnx_utils.log_enter_exit, - debug_only=True) +@enas_utils.decorate_all_methods(enas_utils.log_enter_exit, + debug_only=True) class Snapshot(StorageObject): def __init__(self, conn, elt_maker, xml_parser, manager): super(Snapshot, self).__init__(conn, elt_maker, xml_parser, manager) @@ -1137,8 +1137,8 @@ class Snapshot(StorageObject): return self.snap_map[name]['id'] -@vnx_utils.decorate_all_methods(vnx_utils.log_enter_exit, - debug_only=True) +@enas_utils.decorate_all_methods(enas_utils.log_enter_exit, + debug_only=True) class MoverInterface(StorageObject): def __init__(self, conn, elt_maker, xml_parser, manager): super(MoverInterface, self).__init__(conn, elt_maker, xml_parser, @@ -1159,18 +1159,21 @@ class MoverInterface(StorageObject): mover_id = self._get_mover_id(mover_name, False) + params = dict(device=device_name, + ipAddress=six.text_type(ip_addr), + mover=mover_id, + name=name, + netMask=net_mask, + vlanid=six.text_type(vlan_id)) + + if interface.get('ip_version') == 6: + params['ipVersion'] = 'IPv6' + if self.xml_retry: self.xml_retry = False request = self._build_task_package( - self.elt_maker.NewMoverInterface( - device=device_name, - ipAddress=six.text_type(ip_addr), - mover=mover_id, - name=name, - netMask=net_mask, - vlanid=six.text_type(vlan_id) - ) + self.elt_maker.NewMoverInterface(**params) ) response = self._send_request(request) @@ -1261,8 +1264,8 @@ class MoverInterface(StorageObject): raise exception.EMCVnxXMLAPIError(err=message) -@vnx_utils.decorate_all_methods(vnx_utils.log_enter_exit, - debug_only=True) +@enas_utils.decorate_all_methods(enas_utils.log_enter_exit, + debug_only=True) class DNSDomain(StorageObject): def __init__(self, conn, elt_maker, xml_parser, manager): super(DNSDomain, self).__init__(conn, elt_maker, xml_parser, manager) @@ -1323,8 +1326,8 @@ class DNSDomain(StorageObject): {'name': name, 'err': response['problems']}) -@vnx_utils.decorate_all_methods(vnx_utils.log_enter_exit, - debug_only=True) +@enas_utils.decorate_all_methods(enas_utils.log_enter_exit, + debug_only=True) class CIFSServer(StorageObject): def __init__(self, conn, elt_maker, xml_parser, manager): super(CIFSServer, self).__init__(conn, elt_maker, xml_parser, manager) @@ -1544,8 +1547,8 @@ class CIFSServer(StorageObject): self.cifs_server_map[mover_name].pop(computer_name) -@vnx_utils.decorate_all_methods(vnx_utils.log_enter_exit, - debug_only=True) +@enas_utils.decorate_all_methods(enas_utils.log_enter_exit, + debug_only=True) class CIFSShare(StorageObject): def __init__(self, conn, elt_maker, xml_parser, manager): super(CIFSShare, self).__init__(conn, elt_maker, xml_parser, manager) @@ -1771,8 +1774,8 @@ class CIFSShare(StorageObject): return users_to_remove -@vnx_utils.decorate_all_methods(vnx_utils.log_enter_exit, - debug_only=True) +@enas_utils.decorate_all_methods(enas_utils.log_enter_exit, + debug_only=True) class NFSShare(StorageObject): def __init__(self, conn, elt_maker, xml_parser, manager): super(NFSShare, self).__init__(conn, elt_maker, xml_parser, manager) @@ -1872,13 +1875,14 @@ class NFSShare(StorageObject): for field in fields: field = field.strip() if field.startswith('rw='): - nfs_share['RwHosts'] = field[3:].split(":") + nfs_share['RwHosts'] = enas_utils.parse_ipaddr(field[3:]) elif field.startswith('access='): - nfs_share['AccessHosts'] = field[7:].split(":") + nfs_share['AccessHosts'] = enas_utils.parse_ipaddr( + field[7:]) elif field.startswith('root='): - nfs_share['RootHosts'] = field[5:].split(":") + nfs_share['RootHosts'] = enas_utils.parse_ipaddr(field[5:]) elif field.startswith('ro='): - nfs_share['RoHosts'] = field[3:].split(":") + nfs_share['RoHosts'] = enas_utils.parse_ipaddr(field[3:]) self.nfs_share_map[name] = nfs_share else: @@ -1899,6 +1903,9 @@ class NFSShare(StorageObject): changed = False rwhosts = share['RwHosts'] rohosts = share['RoHosts'] + + host_ip = enas_utils.convert_ipv6_format_if_needed(host_ip) + if access_level == const.ACCESS_LEVEL_RW: if host_ip not in rwhosts: rwhosts.append(host_ip) @@ -1942,7 +1949,6 @@ class NFSShare(StorageObject): do_allow_access(share_name, host_ip, mover_name, access_level) def deny_share_access(self, share_name, host_ip, mover_name): - @utils.synchronized('emc-shareaccess-' + share_name) def do_deny_access(share_name, host_ip, mover_name): status, share = self.get(share_name, mover_name) diff --git a/manila/tests/share/drivers/dell_emc/common/enas/fakes.py b/manila/tests/share/drivers/dell_emc/common/enas/fakes.py index 3a8cac533e..a5ec9d5b5c 100644 --- a/manila/tests/share/drivers/dell_emc/common/enas/fakes.py +++ b/manila/tests/share/drivers/dell_emc/common/enas/fakes.py @@ -18,6 +18,7 @@ from oslo_utils import units from manila.common import constants as const from manila.share import configuration as conf +from manila.share.drivers.dell_emc.common.enas import utils from manila.tests import fake_share @@ -77,15 +78,26 @@ class FakeData(object): # Share network information share_network_id = 'c5b3a865-56d0-4d88-abe5-879965e099c9' cidr = '192.168.1.0/24' + cidr_v6 = 'fdf8:f53b:82e1::/64' segmentation_id = 100 network_allocations_id1 = '132dbb10-9a36-46f2-8d89-3d909830c356' network_allocations_id2 = '7eabdeed-bad2-46ea-bd0f-a33884c869e0' + network_allocations_id3 = '98c9e490-a842-4e59-b59a-a6042069d35b' + network_allocations_id4 = '6319a917-ab95-4b65-a498-773ae33c5550' network_allocations_ip1 = '192.168.1.1' network_allocations_ip2 = '192.168.1.2' + network_allocations_ip3 = 'fdf8:f53b:82e1::1' + network_allocations_ip4 = 'fdf8:f53b:82e1::2' + + network_allocations_ip_version1 = 4 + network_allocations_ip_version2 = 4 + network_allocations_ip_version3 = 6 + network_allocations_ip_version4 = 6 domain_name = 'fake_domain' domain_user = 'administrator' domain_password = 'password' dns_ip_address = '192.168.1.200' + dns_ipv6_address = 'fdf8:f53b:82e1::f' # Share server information share_server_id = '56aafd02-4d44-43d7-b784-57fc88167224' @@ -104,8 +116,11 @@ class FakeData(object): mover_id = 'fake_mover_id' interface_name1 = network_allocations_id1[-12:] interface_name2 = network_allocations_id2[-12:] + interface_name3 = network_allocations_id3[-12:] + interface_name4 = network_allocations_id4[-12:] long_interface_name = network_allocations_id1 net_mask = '255.255.255.0' + net_mask_v6 = 64 device_name = 'cge-1-0' interconnect_id = '2001' @@ -123,6 +138,9 @@ class FakeData(object): rw_hosts = ['192.168.1.1', '192.168.1.2'] ro_hosts = ['192.168.1.3', '192.168.1.4'] nfs_host_ip = '192.168.1.5' + rw_hosts_ipv6 = ['fdf8:f53b:82e1::1', 'fdf8:f53b:82e1::2'] + ro_hosts_ipv6 = ['fdf8:f53b:82e1::3', 'fdf8:f53b:82e1::4'] + nfs_host_ipv6 = 'fdf8:f53b:82e1::5' fake_output = '' @@ -175,10 +193,15 @@ class StorageObjectTestData(object): self.interface_name1 = FakeData.interface_name1 self.interface_name2 = FakeData.interface_name2 + self.interface_name3 = FakeData.interface_name3 + self.interface_name4 = FakeData.interface_name4 self.long_interface_name = FakeData.long_interface_name self.ip_address1 = FakeData.network_allocations_ip1 self.ip_address2 = FakeData.network_allocations_ip2 + self.ip_address3 = FakeData.network_allocations_ip3 + self.ip_address4 = FakeData.network_allocations_ip4 self.net_mask = FakeData.net_mask + self.net_mask_v6 = FakeData.net_mask_v6 self.vlan_id = FakeData.segmentation_id self.cifs_server_name = FakeData.vdm_name @@ -196,6 +219,10 @@ class StorageObjectTestData(object): self.ro_hosts = FakeData.ro_hosts self.nfs_host_ip = FakeData.nfs_host_ip + self.rw_hosts_ipv6 = FakeData.rw_hosts_ipv6 + self.ro_hosts_ipv6 = FakeData.ro_hosts_ipv6 + self.nfs_host_ipv6 = FakeData.nfs_host_ipv6 + self.fake_output = FakeData.fake_output @response @@ -710,10 +737,16 @@ class VDMTestData(StorageObjectTestData): return '' @response - def resp_get_succeed(self, name=None): - if not name: + def resp_get_succeed(self, name=None, interface1=None, interface2=None): + if name is None: name = self.vdm_name + if interface1 is None: + interface1 = self.interface_name1 + + if interface2 is None: + interface2 = self.interface_name2 + return ( '' '' % {'vdmid': self.vdm_id} - def cmd_attach_nfs_interface(self): + def cmd_attach_nfs_interface(self, interface=None): + if interface is None: + interface = self.interface_name2 + return [ 'env', 'NAS_DB=/nas', '/nas/bin/nas_server', '-vdm', self.vdm_name, - '-attach', self.interface_name2, + '-attach', interface, ] def cmd_detach_nfs_interface(self): @@ -967,6 +1003,23 @@ class MoverTestData(StorageObjectTestData): 'net_mask': self.net_mask} ) + @start_task + def req_create_interface_with_ipv6(self, + if_name=FakeData.interface_name3, + ip=FakeData.network_allocations_ip3): + return ( + '' + % {'if_name': if_name, + 'vlan': self.vlan_id, + 'ip': ip, + 'mover_id': self.mover_id, + 'device_name': self.device_name, + 'net_mask': self.net_mask_v6} + ) + @response def resp_create_interface_but_name_already_exist(self): return ( @@ -1087,13 +1140,16 @@ class DNSDomainTestData(StorageObjectTestData): super(DNSDomainTestData, self).__init__() @start_task - def req_create(self): + def req_create(self, ip_addr=None): + if ip_addr is None: + ip_addr = self.dns_ip_address + return ( '' % {'mover_id': self.mover_id, 'domain_name': self.domain_name, - 'server_ips': self.dns_ip_address} + 'server_ips': ip_addr} ) @start_task @@ -1111,7 +1167,10 @@ class CIFSServerTestData(StorageObjectTestData): super(CIFSServerTestData, self).__init__() @start_task - def req_create(self, mover_id, is_vdm=True): + def req_create(self, mover_id, is_vdm=True, ip_addr=None): + if ip_addr is None: + ip_addr = self.ip_address1 + return ( '' @@ -1120,7 +1179,7 @@ class CIFSServerTestData(StorageObjectTestData): '' '' - % {'ip': self.ip_address1, + % {'ip': ip_addr, 'comp_name': self.cifs_server_name, 'name': self.cifs_server_name[-14:], 'mover_id': mover_id, @@ -1143,9 +1202,14 @@ class CIFSServerTestData(StorageObjectTestData): @response def resp_get_succeed(self, mover_id, is_vdm, join_domain, - cifs_server_name=None): + cifs_server_name=None, + ip_addr=None): if cifs_server_name is None: cifs_server_name = self.cifs_server_name + + if ip_addr is None: + ip_addr = self.ip_address1 + return ( '' '' % {'mover_id': mover_id, 'cifsserver': self.cifs_server_name[-14:], - 'ip': self.ip_address1, + 'ip': ip_addr, 'is_vdm': 'true' if is_vdm else 'false', 'alias': self.cifs_server_name[-12:], 'domain': self.domain_name, @@ -1405,6 +1469,11 @@ class NFSShareTestData(StorageObjectTestData): ] def output_get_succeed(self, rw_hosts, ro_hosts): + rw_hosts = [utils.convert_ipv6_format_if_needed(ip_addr) for ip_addr in + rw_hosts] + ro_hosts = [utils.convert_ipv6_format_if_needed(ip_addr) for ip_addr in + ro_hosts] + if rw_hosts and ro_hosts: return ( '%(mover_name)s :\nexport "%(path)s" ' @@ -1466,6 +1535,11 @@ class NFSShareTestData(StorageObjectTestData): % self.vdm_name) def cmd_set_access(self, rw_hosts, ro_hosts): + rw_hosts = [utils.convert_ipv6_format_if_needed(ip_addr) for ip_addr in + rw_hosts] + ro_hosts = [utils.convert_ipv6_format_if_needed(ip_addr) for ip_addr in + ro_hosts] + access_str = ("access=-0.0.0.0/0.0.0.0:%(access_hosts)s," "root=%(root_hosts)s,rw=%(rw_hosts)s,ro=%(ro_hosts)s" % {'rw_hosts': ":".join(rw_hosts), @@ -1531,11 +1605,21 @@ NFS_RW_ACCESS = fake_share.fake_access( access_to=FakeData.nfs_host_ip, access_level='rw') +NFS_RW_ACCESS_IPV6 = fake_share.fake_access( + access_type='ip', + access_to=FakeData.nfs_host_ipv6, + access_level='rw') + NFS_RO_ACCESS = fake_share.fake_access( access_type='ip', access_to=FakeData.nfs_host_ip, access_level='ro') +NFS_RO_ACCESS_IPV6 = fake_share.fake_access( + access_type='ip', + access_to=FakeData.nfs_host_ipv6, + access_level='ro') + SHARE_SERVER = { 'id': FakeData.share_server_id, 'share_network': { @@ -1550,12 +1634,32 @@ SHARE_SERVER = { } } +SHARE_SERVER_IPV6 = { + 'id': FakeData.share_server_id, + 'share_network': { + 'name': 'fake_share_network', + 'id': FakeData.share_network_id + }, + 'share_network_id': FakeData.share_network_id, + 'backend_details': { + 'share_server_name': FakeData.vdm_name, + 'cifs_if': FakeData.network_allocations_ip3, + 'nfs_if': FakeData.network_allocations_ip4, + } +} + SERVER_DETAIL = { 'share_server_name': FakeData.vdm_name, 'cifs_if': FakeData.network_allocations_ip1, 'nfs_if': FakeData.network_allocations_ip2, } +SERVER_DETAIL_IPV6 = { + 'share_server_name': FakeData.vdm_name, + 'cifs_if': FakeData.network_allocations_ip3, + 'nfs_if': FakeData.network_allocations_ip4, +} + SECURITY_SERVICE = [ { 'type': 'active_directory', @@ -1566,6 +1670,16 @@ SECURITY_SERVICE = [ }, ] +SECURITY_SERVICE_IPV6 = [ + { + 'type': 'active_directory', + 'domain': FakeData.domain_name, + 'dns_ip': FakeData.dns_ipv6_address, + 'user': FakeData.domain_user, + 'password': FakeData.domain_password + }, +] + NETWORK_INFO = { 'server_id': FakeData.share_server_id, 'cidr': FakeData.cidr, @@ -1580,9 +1694,33 @@ NETWORK_INFO = { 'network_type': 'vlan', 'network_allocations': [ {'id': FakeData.network_allocations_id1, - 'ip_address': FakeData.network_allocations_ip1}, + 'ip_address': FakeData.network_allocations_ip1, + 'ip_version': FakeData.network_allocations_ip_version1}, {'id': FakeData.network_allocations_id2, - 'ip_address': FakeData.network_allocations_ip2} + 'ip_address': FakeData.network_allocations_ip2, + 'ip_version': FakeData.network_allocations_ip_version2} + ] +} + +NETWORK_INFO_IPV6 = { + 'server_id': FakeData.share_server_id, + 'cidr': FakeData.cidr_v6, + 'security_services': [ + {'type': 'active_directory', + 'domain': FakeData.domain_name, + 'dns_ip': FakeData.dns_ipv6_address, + 'user': FakeData.domain_user, + 'password': FakeData.domain_password}, + ], + 'segmentation_id': FakeData.segmentation_id, + 'network_type': 'vlan', + 'network_allocations': [ + {'id': FakeData.network_allocations_id3, + 'ip_address': FakeData.network_allocations_ip3, + 'ip_version': FakeData.network_allocations_ip_version3}, + {'id': FakeData.network_allocations_id4, + 'ip_address': FakeData.network_allocations_ip4, + 'ip_version': FakeData.network_allocations_ip_version4} ] } diff --git a/manila/tests/share/drivers/dell_emc/common/enas/test_utils.py b/manila/tests/share/drivers/dell_emc/common/enas/test_utils.py index e84af62b5a..a3e20be59d 100644 --- a/manila/tests/share/drivers/dell_emc/common/enas/test_utils.py +++ b/manila/tests/share/drivers/dell_emc/common/enas/test_utils.py @@ -71,3 +71,71 @@ class SslContextTestCase(test.TestCase): mock.Mock(side_effect=AttributeError)) context = utils.create_ssl_context(configuration) self.assertIsNone(context) + + +@ddt.ddt +class ParseIpaddrTestCase(test.TestCase): + + @ddt.data({'lst_ipaddr': ['192.168.100.101', + '192.168.100.102', + '192.168.100.103']}, + {'lst_ipaddr': ['[fdf8:f53b:82e4::57]', + '[fdf8:f53b:82e4::54]', + '[fdf8:f53b:82e4::55]']}, + {'lst_ipaddr': ['[fdf8:f53b:82e4::57]', + '[fdf8:f53b:82e4::54]', + '192.168.100.103', + '[fdf8:f53b:82e4::55]']}, + {'lst_ipaddr': ['192.168.100.101', + '[fdf8:f53b:82e4::57]', + '[fdf8:f53b:82e4::54]', + '192.168.100.101', + '[fdf8:f53b:82e4::55]', + '192.168.100.102']},) + @ddt.unpack + def test_parse_ipv4_addr(self, lst_ipaddr): + self.assertEqual(lst_ipaddr, utils.parse_ipaddr(':'.join(lst_ipaddr))) + + +@ddt.ddt +class ConvertIPv6FormatTestCase(test.TestCase): + + @ddt.data({'ip_addr': 'fdf8:f53b:82e4::55'}, + {'ip_addr': 'fdf8:f53b:82e4::55/64'}, + {'ip_addr': 'fdf8:f53b:82e4::55/128'}) + @ddt.unpack + def test_ipv6_addr(self, ip_addr): + expected_ip_addr = '[%s]' % ip_addr + self.assertEqual(expected_ip_addr, + utils.convert_ipv6_format_if_needed(ip_addr)) + + @ddt.data({'ip_addr': '192.168.1.100'}, + {'ip_addr': '192.168.1.100/24'}, + {'ip_addr': '192.168.1.100/32'}, + {'ip_addr': '[fdf8:f53b:82e4::55]'}) + @ddt.unpack + def test_invalid_ipv6_addr(self, ip_addr): + self.assertEqual(ip_addr, utils.convert_ipv6_format_if_needed(ip_addr)) + + +@ddt.ddt +class ExportUncPathTestCase(test.TestCase): + + @ddt.data({'ip_addr': 'fdf8:f53b:82e4::55'}, + {'ip_addr': 'fdf8:f53b:82e4::'}, + {'ip_addr': '2018::'}) + @ddt.unpack + def test_ipv6_addr(self, ip_addr): + expected_ip_addr = '%s.ipv6-literal.net' % ip_addr.replace(':', '-') + self.assertEqual(expected_ip_addr, + utils.export_unc_path(ip_addr)) + + @ddt.data({'ip_addr': '192.168.1.100'}, + {'ip_addr': '192.168.1.100/24'}, + {'ip_addr': '192.168.1.100/32'}, + {'ip_addr': 'fdf8:f53b:82e4::55/64'}, + {'ip_addr': 'fdf8:f53b:82e4::55/128'}, + {'ip_addr': '[fdf8:f53b:82e4::55]'}) + @ddt.unpack + def test_invalid_ipv6_addr(self, ip_addr): + self.assertEqual(ip_addr, utils.export_unc_path(ip_addr)) diff --git a/manila/tests/share/drivers/dell_emc/plugins/vnx/test_connection.py b/manila/tests/share/drivers/dell_emc/plugins/vnx/test_connection.py index c2dbcffdf2..f1f00cad61 100644 --- a/manila/tests/share/drivers/dell_emc/plugins/vnx/test_connection.py +++ b/manila/tests/share/drivers/dell_emc/plugins/vnx/test_connection.py @@ -173,7 +173,51 @@ class StorageConnectionTestCase(test.TestCase): ssh_calls = [mock.call(self.cifs_share.cmd_disable_access(), True)] ssh_cmd_mock.assert_has_calls(ssh_calls) - self.assertEqual(location, r'\\192.168.1.1\%s' % share['name'], + self.assertEqual(location, r'\\%s\%s' % + (fakes.FakeData.network_allocations_ip1, + share['name']), + 'CIFS export path is incorrect') + + def test_create_cifs_share_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + share = fakes.CIFS_SHARE + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed( + interface1=fakes.FakeData.interface_name3, + interface2=fakes.FakeData.interface_name4)) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True, + ip_addr=fakes.FakeData.network_allocations_ip3)) + hook.append(self.pool.resp_get_succeed()) + hook.append(self.fs.resp_task_succeed()) + hook.append(self.cifs_share.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append() + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + location = self.connection.create_share(None, share, share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + mock.call(self.pool.req_get()), + mock.call(self.fs.req_create_on_vdm()), + mock.call(self.cifs_share.req_create(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [mock.call(self.cifs_share.cmd_disable_access(), True)] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + self.assertEqual(location, r'\\%s.ipv6-literal.net\%s' % + (fakes.FakeData.network_allocations_ip3.replace(':', + '-'), + share['name']), 'CIFS export path is incorrect') def test_create_nfs_share(self): @@ -207,6 +251,41 @@ class StorageConnectionTestCase(test.TestCase): self.assertEqual(location, '192.168.1.2:/%s' % share['name'], 'NFS export path is incorrect') + def test_create_nfs_share_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + share = fakes.NFS_SHARE + + hook = utils.RequestSideEffect() + hook.append(self.pool.resp_get_succeed()) + hook.append(self.vdm.resp_get_succeed( + interface1=fakes.FakeData.interface_name3, + interface2=fakes.FakeData.interface_name4)) + hook.append(self.fs.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.nfs_share.output_create()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + location = self.connection.create_share(None, share, share_server) + + expected_calls = [ + mock.call(self.pool.req_get()), + mock.call(self.vdm.req_get()), + mock.call(self.fs.req_create_on_vdm()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [mock.call(self.nfs_share.cmd_create(), True)] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + self.assertEqual(location, '[%s]:/%s' % + (fakes.FakeData.network_allocations_ip4, + share['name']), + 'NFS export path is incorrect') + def test_create_cifs_share_without_share_server(self): share = fakes.CIFS_SHARE @@ -336,6 +415,70 @@ class StorageConnectionTestCase(test.TestCase): self.assertEqual(location, r'\\192.168.1.1\%s' % share['name'], 'CIFS export path is incorrect') + def test_create_cifs_share_from_snapshot_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + share = fakes.CIFS_SHARE + snapshot = fake_share.fake_snapshot( + name=fakes.FakeData.src_snap_name, + share_name=fakes.FakeData.src_share_name, + share_id=fakes.FakeData.src_share_name, + id=fakes.FakeData.src_snap_name) + + hook = utils.RequestSideEffect() + hook.append(self.fs.resp_get_succeed()) + hook.append(self.vdm.resp_get_succeed( + interface1=fakes.FakeData.interface_name3, + interface2=fakes.FakeData.interface_name4)) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True, + ip_addr=fakes.FakeData.network_allocations_ip3)) + hook.append(self.cifs_share.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.mover.output_get_interconnect_id()) + ssh_hook.append() + ssh_hook.append() + ssh_hook.append(self.fs.output_copy_ckpt) + ssh_hook.append(self.fs.output_info()) + ssh_hook.append() + ssh_hook.append() + ssh_hook.append() + ssh_hook.append() + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + location = self.connection.create_share_from_snapshot( + None, share, snapshot, share_server) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + mock.call(self.cifs_share.req_create(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.mover.cmd_get_interconnect_id(), False), + mock.call(self.fs.cmd_create_from_ckpt(), False), + mock.call(self.mount.cmd_server_mount('ro'), False), + mock.call(self.fs.cmd_copy_ckpt(), True), + mock.call(self.fs.cmd_nas_fs_info(), False), + mock.call(self.mount.cmd_server_umount(), False), + mock.call(self.fs.cmd_delete(), False), + mock.call(self.mount.cmd_server_mount('rw'), False), + mock.call(self.cifs_share.cmd_disable_access(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + self.assertEqual(location, r'\\%s.ipv6-literal.net\%s' % + (fakes.FakeData.network_allocations_ip3.replace(':', + '-'), + share['name']), + 'CIFS export path is incorrect') + def test_create_nfs_share_from_snapshot(self): share_server = fakes.SHARE_SERVER share = fakes.NFS_SHARE @@ -385,6 +528,57 @@ class StorageConnectionTestCase(test.TestCase): self.assertEqual(location, '192.168.1.2:/%s' % share['name'], 'NFS export path is incorrect') + def test_create_nfs_share_from_snapshot_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + share = fakes.NFS_SHARE + snapshot = fake_share.fake_snapshot( + name=fakes.FakeData.src_snap_name, + share_name=fakes.FakeData.src_share_name, + share_id=fakes.FakeData.src_share_name, + id=fakes.FakeData.src_snap_name) + + hook = utils.RequestSideEffect() + hook.append(self.fs.resp_get_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.mover.output_get_interconnect_id()) + ssh_hook.append() + ssh_hook.append() + ssh_hook.append(self.fs.output_copy_ckpt) + ssh_hook.append(self.fs.output_info()) + ssh_hook.append() + ssh_hook.append() + ssh_hook.append() + ssh_hook.append(self.nfs_share.output_create()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + location = self.connection.create_share_from_snapshot( + None, share, snapshot, share_server) + + expected_calls = [mock.call(self.fs.req_get())] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.mover.cmd_get_interconnect_id(), False), + mock.call(self.fs.cmd_create_from_ckpt(), False), + mock.call(self.mount.cmd_server_mount('ro'), False), + mock.call(self.fs.cmd_copy_ckpt(), True), + mock.call(self.fs.cmd_nas_fs_info(), False), + mock.call(self.mount.cmd_server_umount(), False), + mock.call(self.fs.cmd_delete(), False), + mock.call(self.mount.cmd_server_mount('rw'), False), + mock.call(self.nfs_share.cmd_create(), True) + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + + self.assertEqual(location, '[%s]:/%s' % + (fakes.FakeData.network_allocations_ip4, + share['name']), + 'NFS export path is incorrect') + def test_create_share_with_incorrect_proto(self): share_server = fakes.SHARE_SERVER share = fake_share.fake_share(share_proto='FAKE_PROTO') @@ -440,6 +634,34 @@ class StorageConnectionTestCase(test.TestCase): ] xml_req_mock.assert_has_calls(expected_calls) + def test_delete_cifs_share_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + share = fakes.CIFS_SHARE + + hook = utils.RequestSideEffect() + hook.append(self.cifs_share.resp_get_succeed(self.vdm.vdm_id)) + hook.append(self.vdm.resp_get_succeed( + interface1=fakes.FakeData.interface_name3, + interface2=fakes.FakeData.interface_name4)) + hook.append(self.cifs_share.resp_task_succeed()) + hook.append(self.mount.resp_task_succeed()) + hook.append(self.fs.resp_get_succeed()) + hook.append(self.fs.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.connection.delete_share(None, share, share_server) + + expected_calls = [ + mock.call(self.cifs_share.req_get()), + mock.call(self.vdm.req_get()), + mock.call(self.cifs_share.req_delete(self.vdm.vdm_id)), + mock.call(self.mount.req_delete(self.vdm.vdm_id)), + mock.call(self.fs.req_get()), + mock.call(self.fs.req_delete()), + ] + xml_req_mock.assert_has_calls(expected_calls) + def test_delete_nfs_share(self): share_server = fakes.SHARE_SERVER share = fakes.NFS_SHARE @@ -476,6 +698,44 @@ class StorageConnectionTestCase(test.TestCase): ] ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_delete_nfs_share_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + share = fakes.NFS_SHARE + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed( + interface1=fakes.FakeData.interface_name3, + interface2=fakes.FakeData.interface_name4)) + hook.append(self.mount.resp_task_succeed()) + hook.append(self.fs.resp_get_succeed()) + hook.append(self.fs.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=self.nfs_share.rw_hosts, + ro_hosts=self.nfs_share.ro_hosts)) + ssh_hook.append(self.nfs_share.output_delete_succeed()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.delete_share(None, share, share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.mount.req_delete(self.vdm.vdm_id)), + mock.call(self.fs.req_get()), + mock.call(self.fs.req_delete()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get(), False), + mock.call(self.nfs_share.cmd_delete(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_delete_share_without_share_server(self): share = fakes.CIFS_SHARE @@ -538,6 +798,27 @@ class StorageConnectionTestCase(test.TestCase): ] xml_req_mock.assert_has_calls(expected_calls) + def test_extend_share_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + share = fakes.CIFS_SHARE + new_size = fakes.FakeData.new_size + + hook = utils.RequestSideEffect() + hook.append(self.fs.resp_get_succeed()) + hook.append(self.pool.resp_get_succeed()) + hook.append(self.fs.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.connection.extend_share(share, new_size, share_server) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.pool.req_get()), + mock.call(self.fs.req_extend()), + ] + xml_req_mock.assert_has_calls(expected_calls) + def test_extend_share_without_pool_name(self): share_server = fakes.SHARE_SERVER share = fake_share.fake_share(host='HostA@BackendB', @@ -569,6 +850,27 @@ class StorageConnectionTestCase(test.TestCase): ] xml_req_mock.assert_has_calls(expected_calls) + def test_create_snapshot_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + snapshot = fake_share.fake_snapshot( + id=fakes.FakeData.snapshot_name, + share_id=fakes.FakeData.filesystem_name, + share_name=fakes.FakeData.share_name) + + hook = utils.RequestSideEffect() + hook.append(self.fs.resp_get_succeed()) + hook.append(self.snap.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.connection.create_snapshot(None, snapshot, share_server) + + expected_calls = [ + mock.call(self.fs.req_get()), + mock.call(self.snap.req_create()), + ] + xml_req_mock.assert_has_calls(expected_calls) + def test_create_snapshot_with_incorrect_share_info(self): share_server = fakes.SHARE_SERVER snapshot = fake_share.fake_snapshot( @@ -609,6 +911,27 @@ class StorageConnectionTestCase(test.TestCase): ] xml_req_mock.assert_has_calls(expected_calls) + def test_delete_snapshot_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + snapshot = fake_share.fake_snapshot( + id=fakes.FakeData.snapshot_name, + share_id=fakes.FakeData.filesystem_name, + share_name=fakes.FakeData.share_name) + + hook = utils.RequestSideEffect() + hook.append(self.snap.resp_get_succeed()) + hook.append(self.snap.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + self.connection.delete_snapshot(None, snapshot, share_server) + + expected_calls = [ + mock.call(self.snap.req_get()), + mock.call(self.snap.req_delete()), + ] + xml_req_mock.assert_has_calls(expected_calls) + @utils.patch_get_managed_ports_vnx(return_value=['cge-1-0']) def test_setup_server(self): hook = utils.RequestSideEffect() @@ -630,8 +953,8 @@ class StorageConnectionTestCase(test.TestCase): self.connection.setup_server(fakes.NETWORK_INFO, None) - if_name_1 = fakes.FakeData.network_allocations_id1[-12:] - if_name_2 = fakes.FakeData.network_allocations_id2[-12:] + if_name_1 = fakes.FakeData.interface_name1 + if_name_2 = fakes.FakeData.interface_name2 expected_calls = [ mock.call(self.vdm.req_get()), @@ -654,6 +977,59 @@ class StorageConnectionTestCase(test.TestCase): ] ssh_cmd_mock.assert_has_calls(ssh_calls) + @utils.patch_get_managed_ports_vnx(return_value=['cge-1-0']) + def test_setup_server_with_ipv6(self): + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_but_not_found()) + hook.append(self.mover.resp_get_ref_succeed()) + hook.append(self.vdm.resp_task_succeed()) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.dns.resp_task_succeed()) + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append() + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.setup_server(fakes.NETWORK_INFO_IPV6, None) + + if_name_1 = fakes.FakeData.interface_name3 + if_name_2 = fakes.FakeData.interface_name4 + + expect_ip_1 = fakes.FakeData.network_allocations_ip3 + expect_ip_2 = fakes.FakeData.network_allocations_ip4 + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.mover.req_get_ref()), + mock.call(self.vdm.req_create()), + mock.call(self.mover.req_create_interface_with_ipv6( + if_name=if_name_1, + ip=expect_ip_1)), + mock.call(self.mover.req_create_interface_with_ipv6( + if_name=if_name_2, + ip=expect_ip_2)), + mock.call(self.dns.req_create( + ip_addr=fakes.FakeData.dns_ipv6_address)), + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_create( + self.vdm.vdm_id, + ip_addr=fakes.FakeData.network_allocations_ip3)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.vdm.cmd_attach_nfs_interface( + interface=fakes.FakeData.interface_name4), False), + ] + + ssh_cmd_mock.assert_has_calls(ssh_calls) + @utils.patch_get_managed_ports_vnx(return_value=['cge-1-0']) def test_setup_server_with_existing_vdm(self): hook = utils.RequestSideEffect() @@ -834,6 +1210,51 @@ class StorageConnectionTestCase(test.TestCase): ] ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_teardown_server_with_ipv6(self): + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed()) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True)) + hook.append(self.cifs_server.resp_task_succeed()) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=False)) + hook.append(self.mover.resp_get_ref_succeed()) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.mover.resp_task_succeed()) + hook.append(self.vdm.resp_task_succeed()) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.vdm.output_get_interfaces_vdm()) + ssh_hook.append() + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.teardown_server(fakes.SERVER_DETAIL_IPV6, + fakes.SECURITY_SERVICE_IPV6) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + mock.call(self.cifs_server.req_modify( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=False)), + mock.call(self.cifs_server.req_delete(self.vdm.vdm_id)), + mock.call(self.mover.req_get_ref()), + mock.call(self.mover.req_delete_interface( + fakes.FakeData.network_allocations_ip3)), + mock.call(self.mover.req_delete_interface( + fakes.FakeData.network_allocations_ip4)), + mock.call(self.vdm.req_delete()), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.vdm.cmd_get_interfaces(), False), + mock.call(self.vdm.cmd_detach_nfs_interface(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_teardown_server_without_server_detail(self): self.connection.teardown_server(None, fakes.SECURITY_SERVICE) @@ -1006,6 +1427,40 @@ class StorageConnectionTestCase(test.TestCase): ] ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_update_access_add_cifs_rw_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + share = fakes.CIFS_SHARE + access = fakes.CIFS_RW_ACCESS + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed( + interface1=fakes.FakeData.interface_name3, + interface2=fakes.FakeData.interface_name4)) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True, + ip_addr=fakes.FakeData.network_allocations_ip3)) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.cifs_share.output_allow_access()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.update_access(None, share, [], [access], [], + share_server=share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_change_access(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_update_access_deny_nfs(self): share_server = fakes.SHARE_SERVER share = fakes.NFS_SHARE @@ -1037,6 +1492,37 @@ class StorageConnectionTestCase(test.TestCase): ] ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_update_access_deny_nfs_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + share = fakes.NFS_SHARE + access = fakes.NFS_RW_ACCESS + + rw_hosts = copy.deepcopy(fakes.FakeData.rw_hosts_ipv6) + rw_hosts.append(access['access_to']) + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=rw_hosts, + ro_hosts=fakes.FakeData.ro_hosts_ipv6)) + ssh_hook.append(self.nfs_share.output_set_access_success()) + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=fakes.FakeData.rw_hosts_ipv6, + ro_hosts=fakes.FakeData.ro_hosts_ipv6)) + ssh_cmd_mock = utils.EMCNFSShareMock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.update_access(None, share, [], [], [access], + share_server=share_server) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get(), True), + mock.call(self.nfs_share.cmd_set_access( + rw_hosts=self.nfs_share.rw_hosts_ipv6, + ro_hosts=self.nfs_share.ro_hosts_ipv6), True), + mock.call(self.nfs_share.cmd_get(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_update_access_recover_nfs_rule(self): share_server = fakes.SHARE_SERVER share = fakes.NFS_SHARE @@ -1069,6 +1555,38 @@ class StorageConnectionTestCase(test.TestCase): ] ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_update_access_recover_nfs_rule_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + share = fakes.NFS_SHARE + access = fakes.NFS_RW_ACCESS_IPV6 + hosts = ['fdf8:f53b:82e1::5'] + + rw_hosts = copy.deepcopy(fakes.FakeData.rw_hosts_ipv6) + rw_hosts.append(access['access_to']) + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=rw_hosts, + ro_hosts=fakes.FakeData.ro_hosts_ipv6)) + ssh_hook.append(self.nfs_share.output_set_access_success()) + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=hosts, + ro_hosts=[])) + ssh_cmd_mock = utils.EMCNFSShareMock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.update_access(None, share, [access], [], [], + share_server=share_server) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get(), True), + mock.call(self.nfs_share.cmd_set_access( + rw_hosts=hosts, + ro_hosts=[]), True), + mock.call(self.nfs_share.cmd_get(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_update_access_recover_cifs_rule(self): share_server = fakes.SHARE_SERVER share = fakes.CIFS_SHARE @@ -1106,6 +1624,46 @@ class StorageConnectionTestCase(test.TestCase): ] ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_update_access_recover_cifs_rule_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + share = fakes.CIFS_SHARE + access = fakes.CIFS_RW_ACCESS + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed( + interface1=fakes.FakeData.interface_name3, + interface2=fakes.FakeData.interface_name4)) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True, + ip_addr=fakes.FakeData.network_allocations_ip3)) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.cifs_share.output_allow_access()) + ssh_hook.append(fakes.FakeData.cifs_access) + ssh_hook.append('Command succeeded') + + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.update_access(None, share, [access], [], [], + share_server=share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_change_access(), True), + mock.call(self.cifs_share.cmd_get_access(), True), + mock.call(self.cifs_share.cmd_change_access( + action='revoke', user='guest'), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_cifs_clear_access_server_not_found(self): server = fakes.SHARE_SERVER @@ -1157,6 +1715,39 @@ class StorageConnectionTestCase(test.TestCase): ] ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_allow_cifs_rw_access_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + share = fakes.CIFS_SHARE + access = fakes.CIFS_RW_ACCESS + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed( + interface1=fakes.FakeData.interface_name3, + interface2=fakes.FakeData.interface_name4)) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True, + ip_addr=fakes.FakeData.network_allocations_ip3)) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.cifs_share.output_allow_access()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.allow_access(None, share, access, share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_change_access(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_allow_cifs_ro_access(self): share_server = fakes.SHARE_SERVER share = fakes.CIFS_SHARE @@ -1187,6 +1778,39 @@ class StorageConnectionTestCase(test.TestCase): ] ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_allow_cifs_ro_access_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + share = fakes.CIFS_SHARE + access = fakes.CIFS_RO_ACCESS + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed( + interface1=fakes.FakeData.interface_name3, + interface2=fakes.FakeData.interface_name4)) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True, + ip_addr=fakes.FakeData.network_allocations_ip3)) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.cifs_share.output_allow_access()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.allow_access(None, share, access, share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_change_access('ro'), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_allow_ro_access_without_share_server_name(self): share = fakes.CIFS_SHARE share_server = copy.deepcopy(fakes.SHARE_SERVER) @@ -1277,6 +1901,37 @@ class StorageConnectionTestCase(test.TestCase): ] ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_allow_nfs_access_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + share = fakes.NFS_SHARE + access = fakes.NFS_RW_ACCESS_IPV6 + + rw_hosts = copy.deepcopy(fakes.FakeData.rw_hosts_ipv6) + rw_hosts.append(access['access_to']) + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=fakes.FakeData.rw_hosts_ipv6, + ro_hosts=fakes.FakeData.ro_hosts_ipv6)) + ssh_hook.append(self.nfs_share.output_set_access_success()) + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=rw_hosts, + ro_hosts=fakes.FakeData.ro_hosts_ipv6)) + ssh_cmd_mock = utils.EMCNFSShareMock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.allow_access(None, share, access, share_server) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get(), True), + mock.call(self.nfs_share.cmd_set_access( + rw_hosts=rw_hosts, + ro_hosts=self.nfs_share.ro_hosts_ipv6), + True), + mock.call(self.nfs_share.cmd_get(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_allow_cifs_access_with_incorrect_access_type(self): share_server = fakes.SHARE_SERVER share = fakes.CIFS_SHARE @@ -1335,6 +1990,40 @@ class StorageConnectionTestCase(test.TestCase): ] ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_deny_cifs_rw_access_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + share = fakes.CIFS_SHARE + access = fakes.CIFS_RW_ACCESS + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed( + interface1=fakes.FakeData.interface_name3, + interface2=fakes.FakeData.interface_name4)) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True, + ip_addr=fakes.FakeData.network_allocations_ip3)) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.cifs_share.output_allow_access()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.deny_access(None, share, access, share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_change_access(action='revoke'), + True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_deny_cifs_ro_access(self): share_server = fakes.SHARE_SERVER share = fakes.CIFS_SHARE @@ -1365,6 +2054,39 @@ class StorageConnectionTestCase(test.TestCase): ] ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_deny_cifs_ro_access_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + share = fakes.CIFS_SHARE + access = fakes.CIFS_RO_ACCESS + + hook = utils.RequestSideEffect() + hook.append(self.vdm.resp_get_succeed( + interface1=fakes.FakeData.interface_name3, + interface2=fakes.FakeData.interface_name4)) + hook.append(self.cifs_server.resp_get_succeed( + mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True, + ip_addr=fakes.FakeData.network_allocations_ip3)) + xml_req_mock = utils.EMCMock(side_effect=hook) + self.connection.manager.connectors['XML'].request = xml_req_mock + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.cifs_share.output_allow_access()) + ssh_cmd_mock = mock.Mock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.deny_access(None, share, access, share_server) + + expected_calls = [ + mock.call(self.vdm.req_get()), + mock.call(self.cifs_server.req_get(self.vdm.vdm_id)), + ] + xml_req_mock.assert_has_calls(expected_calls) + + ssh_calls = [ + mock.call(self.cifs_share.cmd_change_access('ro', 'revoke'), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_deny_cifs_access_with_invliad_share_server_name(self): share_server = fakes.SHARE_SERVER share = fakes.CIFS_SHARE @@ -1416,6 +2138,36 @@ class StorageConnectionTestCase(test.TestCase): ] ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_deny_nfs_access_with_ipv6(self): + share_server = fakes.SHARE_SERVER_IPV6 + share = fakes.NFS_SHARE + access = fakes.NFS_RW_ACCESS_IPV6 + + rw_hosts = copy.deepcopy(fakes.FakeData.rw_hosts_ipv6) + rw_hosts.append(access['access_to']) + + ssh_hook = utils.SSHSideEffect() + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=rw_hosts, + ro_hosts=fakes.FakeData.ro_hosts_ipv6)) + ssh_hook.append(self.nfs_share.output_set_access_success()) + ssh_hook.append(self.nfs_share.output_get_succeed( + rw_hosts=fakes.FakeData.rw_hosts_ipv6, + ro_hosts=fakes.FakeData.ro_hosts_ipv6)) + ssh_cmd_mock = utils.EMCNFSShareMock(side_effect=ssh_hook) + self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock + + self.connection.deny_access(None, share, access, share_server) + + ssh_calls = [ + mock.call(self.nfs_share.cmd_get(), True), + mock.call(self.nfs_share.cmd_set_access( + rw_hosts=self.nfs_share.rw_hosts_ipv6, + ro_hosts=self.nfs_share.ro_hosts_ipv6), True), + mock.call(self.nfs_share.cmd_get(), True), + ] + ssh_cmd_mock.assert_has_calls(ssh_calls) + def test_deny_access_with_incorrect_proto(self): share_server = fakes.SHARE_SERVER share = fake_share.fake_share(share_proto='FAKE_PROTO') diff --git a/manila/tests/test_utils.py b/manila/tests/test_utils.py index 99fc6cb4e3..e89980150a 100644 --- a/manila/tests/test_utils.py +++ b/manila/tests/test_utils.py @@ -345,54 +345,58 @@ class SSHPoolTestCase(test.TestCase): paramiko.SSHClient.assert_called_once_with() +@ddt.ddt class CidrToNetmaskTestCase(test.TestCase): """Unit test for cidr to netmask.""" - def test_cidr_to_netmask_01(self): - cidr = '10.0.0.0/0' - expected_netmask = '0.0.0.0' + @ddt.data( + ('10.0.0.0/0', '0.0.0.0'), + ('10.0.0.0/24', '255.255.255.0'), + ('10.0.0.0/5', '248.0.0.0'), + ('10.0.0.0/32', '255.255.255.255'), + ('10.0.0.1', '255.255.255.255'), + ) + @ddt.unpack + def test_cidr_to_netmask(self, cidr, expected_netmask): result = utils.cidr_to_netmask(cidr) self.assertEqual(expected_netmask, result) - def test_cidr_to_netmask_02(self): - cidr = '10.0.0.0/24' - expected_netmask = '255.255.255.0' - result = utils.cidr_to_netmask(cidr) - self.assertEqual(expected_netmask, result) - - def test_cidr_to_netmask_03(self): - cidr = '10.0.0.0/5' - expected_netmask = '248.0.0.0' - result = utils.cidr_to_netmask(cidr) - self.assertEqual(expected_netmask, result) - - def test_cidr_to_netmask_04(self): - cidr = '10.0.0.0/32' - expected_netmask = '255.255.255.255' - result = utils.cidr_to_netmask(cidr) - self.assertEqual(expected_netmask, result) - - def test_cidr_to_netmask_05(self): - cidr = '10.0.0.1' - expected_netmask = '255.255.255.255' - result = utils.cidr_to_netmask(cidr) - self.assertEqual(expected_netmask, result) - - def test_cidr_to_netmask_invalid_01(self): - cidr = '10.0.0.0/33' + @ddt.data( + '10.0.0.0/33', + '', + '10.0.0.555/33' + ) + def test_cidr_to_netmask_invalid(self, cidr): self.assertRaises(exception.InvalidInput, utils.cidr_to_netmask, cidr) - def test_cidr_to_netmask_invalid_02(self): - cidr = '' - self.assertRaises(exception.InvalidInput, utils.cidr_to_netmask, cidr) - def test_cidr_to_netmask_invalid_03(self): - cidr = '10.0.0.0/33' - self.assertRaises(exception.InvalidInput, utils.cidr_to_netmask, cidr) +@ddt.ddt +class CidrToPrefixLenTestCase(test.TestCase): + """Unit test for cidr to prefix length.""" - def test_cidr_to_netmask_invalid_04(self): - cidr = '10.0.0.555/33' - self.assertRaises(exception.InvalidInput, utils.cidr_to_netmask, cidr) + @ddt.data( + ('10.0.0.0/0', 0), + ('10.0.0.0/24', 24), + ('10.0.0.1', 32), + ('fdf8:f53b:82e1::1/0', 0), + ('fdf8:f53b:82e1::1/64', 64), + ('fdf8:f53b:82e1::1', 128), + ) + @ddt.unpack + def test_cidr_to_prefixlen(self, cidr, expected_prefixlen): + result = utils.cidr_to_prefixlen(cidr) + self.assertEqual(expected_prefixlen, result) + + @ddt.data( + '10.0.0.0/33', + '', + '10.0.0.555/33', + 'fdf8:f53b:82e1::1/129', + 'fdf8:f53b:82e1::fffff' + ) + def test_cidr_to_prefixlen_invalid(self, cidr): + self.assertRaises(exception.InvalidInput, + utils.cidr_to_prefixlen, cidr) @ddt.ddt diff --git a/manila/utils.py b/manila/utils.py index 2cf94fc22f..c85c06c4ee 100644 --- a/manila/utils.py +++ b/manila/utils.py @@ -376,15 +376,25 @@ def walk_class_hierarchy(clazz, encountered=None): yield subclass -def cidr_to_netmask(cidr): - """Convert cidr to netmask.""" +def cidr_to_network(cidr): + """Convert cidr to network.""" try: network = netaddr.IPNetwork(cidr) - return str(network.netmask) + return network except netaddr.AddrFormatError: raise exception.InvalidInput(_("Invalid cidr supplied %s") % cidr) +def cidr_to_netmask(cidr): + """Convert cidr to netmask.""" + return six.text_type(cidr_to_network(cidr).netmask) + + +def cidr_to_prefixlen(cidr): + """Convert cidr to prefix length.""" + return cidr_to_network(cidr).prefixlen + + def is_valid_ip_address(ip_address, ip_version): ip_version = ([int(ip_version)] if not isinstance(ip_version, list) else ip_version) diff --git a/releasenotes/notes/vnx-manila-ipv6-support-9ae986431549cc63.yaml b/releasenotes/notes/vnx-manila-ipv6-support-9ae986431549cc63.yaml new file mode 100644 index 0000000000..4b0beb3e12 --- /dev/null +++ b/releasenotes/notes/vnx-manila-ipv6-support-9ae986431549cc63.yaml @@ -0,0 +1,3 @@ +--- +features: + - IPv6 support for Dell EMC VNX Manila driver.