diff --git a/doc/source/devref/huawei_nas_driver.rst b/doc/source/devref/huawei_nas_driver.rst index 2a4d6317c0..7f70fba542 100644 --- a/doc/source/devref/huawei_nas_driver.rst +++ b/doc/source/devref/huawei_nas_driver.rst @@ -40,7 +40,7 @@ The following operations is supported on V3 storage: - Delete CIFS/NFS Share - Allow CIFS/NFS Share access - * Only IP access type is supported for NFS(ro/rw). + * IP and USER access types are supported for NFS(ro/rw). * Only USER access type is supported for CIFS(ro/rw). - Deny CIFS/NFS Share access - Create snapshot @@ -70,6 +70,7 @@ storage systems, the driver configuration file is as follows: V3 x.x.x.x + abc;CTE0.A.H1 https://x.x.x.x:8088/deviceManager/rest/; https://x.x.x.x:8088/deviceManager/rest/ xxxxxxxxx @@ -85,6 +86,10 @@ storage systems, the driver configuration file is as follows: - `Product` is a type of a storage product. Set it to `V3`. - `LogicalPortIP` is an IP address of the logical port. +- `Port` is a port name list of bond port or ETH port, used to + create vlan and logical port. Multi Ports can be configured in + (separated by ";"). If is not configured, then will choose + an online port on the array. - `RestURL` is an access address of the REST interface. Multi RestURLs can be configured in (separated by ";"). When one of the RestURL failed to connect, driver will retry another automatically. @@ -105,14 +110,16 @@ Example for configuring a storage system: - `share_driver` = manila.share.drivers.huawei.huawei_nas.HuaweiNasDriver - `manila_huawei_conf_file` = /etc/manila/manila_huawei_conf.xml -- `driver_handles_share_servers` = False +- `driver_handles_share_servers` = True or False .. note:: - As far as Manila requires `share type` for creation of shares, make sure that - used `share type` has extra spec `driver_handles_share_servers` set to `False` - otherwise Huawei backend will be filtered by `manila-scheduler`. - If you do not provide `share type` with share creation request then default - `share type` and its extra specs will be used. + - If `driver_handles_share_servers` is True, the driver will choose a port + in to create vlan and logical port for each tenant network. + And the share type with the DHSS extra spec should be set to True when + creating shares. + - If `driver_handles_share_servers` is False, then will use the IP in + . Also the share type with the DHSS extra spec should be + set to False when creating shares. Restart of manila-share service is needed for the configuration changes to take effect. @@ -195,10 +202,14 @@ Restrictions The Huawei driver has the following restrictions: -- Only IP access type is supported for NFS. +- IP and USER access types are supported for NFS. + +- Only LDAP domain is supported for NFS. - Only USER access type is supported for CIFS. +- Only AD domain is supported for CIFS. + The :mod:`manila.share.drivers.huawei.huawei_nas` Module ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/source/devref/share_back_ends_feature_support_mapping.rst b/doc/source/devref/share_back_ends_feature_support_mapping.rst index cb9de2a561..30985cfa2a 100644 --- a/doc/source/devref/share_back_ends_feature_support_mapping.rst +++ b/doc/source/devref/share_back_ends_feature_support_mapping.rst @@ -51,7 +51,7 @@ Mapping of share drivers and share features support +----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+ | HPE 3PAR | DHSS = True (L) & False (K) | \- | \- | \- | K | K | +----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+ -| Huawei | DHSS = False(K) | L | L | L | K | \- | +| Huawei | DHSS = True (M) & False(K) | L | L | L | K | \- | +----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+ | IBM GPFS | DHSS = False(K) | \- | L | \- | K | K | +----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+ @@ -69,39 +69,39 @@ Mapping of share drivers and share features support Mapping of share drivers and share access rules support ------------------------------------------------------- -+----------------------------------------+----------------------------------------+----------------------------------------+ -| | Read & Write | Read Only | -+ Driver name +--------------+------------+------------+--------------+------------+------------+ -| | IP | USER | Cert | IP | USER | Cert | -+========================================+==============+============+============+==============+============+============+ -| Generic (Cinder as back-end) | NFS,CIFS (J) | \- | \- | NFS (K) | \- | \- | -+----------------------------------------+--------------+------------+------------+--------------+------------+------------+ -| NetApp Clustered Data ONTAP | NFS (J) | CIFS (J) | \- | NFS (K) | CIFS (M) | \- | -+----------------------------------------+--------------+------------+------------+--------------+------------+------------+ -| EMC VNX | NFS (J) | CIFS (J) | \- | NFS (L) | CIFS (L) | \- | -+----------------------------------------+--------------+------------+------------+--------------+------------+------------+ -| EMC Isilon | NFS,CIFS (K) | \- | \- | \- | \- | \- | -+----------------------------------------+--------------+------------+------------+--------------+------------+------------+ -| Red Hat GlusterFS | NFS (J) | \- | \- | \- | \- | \- | -+----------------------------------------+--------------+------------+------------+--------------+------------+------------+ -| Red Hat GlusterFS-Native | \- | \- | J | \- | \- | \- | -+----------------------------------------+--------------+------------+------------+--------------+------------+------------+ -| HDFS | \- | HDFS(K) | \- | \- | HDFS(K) | \- | -+----------------------------------------+--------------+------------+------------+--------------+------------+------------+ -| Hitachi HNAS | NFS (L) | \- | \- | NFS (L) | \- | \- | -+----------------------------------------+--------------+------------+------------+--------------+------------+------------+ -| HPE 3PAR | NFS,CIFS (K) | CIFS (K) | \- | \- | \- | \- | -+----------------------------------------+--------------+------------+------------+--------------+------------+------------+ -| Huawei | NFS (K) | CIFS (K) | \- | NFS (K) | CIFS (K) | \- | -+----------------------------------------+--------------+------------+------------+--------------+------------+------------+ -| Quobyte | NFS (K) | \- | \- | NFS (K) | \- | \- | -+----------------------------------------+--------------+------------+------------+--------------+------------+------------+ -| Windows SMB | \- | CIFS (L) | \- | \- | CIFS (L) | \- | -+----------------------------------------+--------------+------------+------------+--------------+------------+------------+ -| IBM GPFS | NFS (K) | \- | \- | NFS (K) | \- | \- | -+----------------------------------------+--------------+------------+------------+--------------+------------+------------+ -| ZFS | ? | ? | ? | ? | ? | ? | -+----------------------------------------+--------------+------------+------------+--------------+------------+------------+ ++----------------------------------------+--------------------------------------------+--------------------------------------------+ +| | Read & Write | Read Only | ++ Driver name +--------------+----------------+------------+--------------+----------------+------------+ +| | IP | USER | Cert | IP | USER | Cert | ++========================================+==============+================+============+==============+================+============+ +| Generic (Cinder as back-end) | NFS,CIFS (J) | \- | \- | NFS (K) | \- | \- | ++----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+ +| NetApp Clustered Data ONTAP | NFS (J) | CIFS (J) | \- | NFS (K) | CIFS (M) | \- | ++----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+ +| EMC VNX | NFS (J) | CIFS (J) | \- | NFS (L) | CIFS (L) | \- | ++----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+ +| EMC Isilon | NFS,CIFS (K) | \- | \- | \- | \- | \- | ++----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+ +| Red Hat GlusterFS | NFS (J) | \- | \- | \- | \- | \- | ++----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+ +| Red Hat GlusterFS-Native | \- | \- | J | \- | \- | \- | ++----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+ +| HDFS | \- | HDFS(K) | \- | \- | HDFS(K) | \- | ++----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+ +| Hitachi HNAS | NFS (L) | \- | \- | NFS (L) | \- | \- | ++----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+ +| HPE 3PAR | NFS,CIFS (K) | CIFS (K) | \- | \- | \- | \- | ++----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+ +| Huawei | NFS (K) |NFS (M),CIFS (K)| \- | NFS (K) |NFS (M),CIFS (K)| \- | ++----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+ +| Quobyte | NFS (K) | \- | \- | NFS (K) | \- | \- | ++----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+ +| Windows SMB | \- | CIFS (L) | \- | \- | CIFS (L) | \- | ++----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+ +| IBM GPFS | NFS (K) | \- | \- | NFS (K) | \- | \- | ++----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+ +| ZFS | ? | ? | ? | ? | ? | ? | ++----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+ Mapping of share drivers and security services support ------------------------------------------------------ @@ -127,7 +127,7 @@ Mapping of share drivers and security services support +----------------------------------------+------------------+-----------------+------------------+ | HPE 3PAR | \- | \- | \- | +----------------------------------------+------------------+-----------------+------------------+ -| Huawei | \- | \- | \- | +| Huawei | M | M | \- | +----------------------------------------+------------------+-----------------+------------------+ | Quobyte | \- | \- | \- | +----------------------------------------+------------------+-----------------+------------------+ diff --git a/manila/share/drivers/huawei/base.py b/manila/share/drivers/huawei/base.py index 84d02c9350..095dc1d919 100644 --- a/manila/share/drivers/huawei/base.py +++ b/manila/share/drivers/huawei/base.py @@ -73,3 +73,11 @@ class HuaweiBase(object): def update_share_stats(self, stats_dict): """Retrieve stats info from share group.""" + + @abc.abstractmethod + def setup_server(self, network_info, metadata=None): + """Set up share server with given network parameters.""" + + @abc.abstractmethod + def teardown_server(self, server_details, security_services=None): + """Teardown share server.""" diff --git a/manila/share/drivers/huawei/constants.py b/manila/share/drivers/huawei/constants.py index 1cf77cc7fd..283438d6ff 100644 --- a/manila/share/drivers/huawei/constants.py +++ b/manila/share/drivers/huawei/constants.py @@ -12,16 +12,22 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + +STATUS_ETH_RUNNING = "10" STATUS_FS_HEALTH = "1" STATUS_FS_RUNNING = "27" +STATUS_JOIN_DOMAIN = '1' +STATUS_EXIT_DOMAIN = '0' STATUS_SERVICE_RUNNING = "2" DEFAULT_WAIT_INTERVAL = 3 DEFAULT_TIMEOUT = 60 MSG_SNAPSHOT_NOT_FOUND = 1073754118 -IP_ALLOCATIONS = 0 +IP_ALLOCATIONS_DHSS_FALSE = 0 +IP_ALLOCATIONS_DHSS_TRUE = 1 SOCKET_TIMEOUT = 52 LOGIN_SOCKET_TIMEOUT = 4 +SYSTEM_NAME_PREFIX = "Array-" ACCESS_NFS_RW = "1" ACCESS_NFS_RO = "0" @@ -30,6 +36,12 @@ ACCESS_CIFS_RO = "0" ERROR_CONNECT_TO_SERVER = -403 ERROR_UNAUTHORIZED_TO_SERVER = -401 +ERROR_LOGICAL_PORT_EXIST = 1073813505 +ERROR_USER_OR_GROUP_NOT_EXIST = 1077939723 + +PORT_TYPE_ETH = '1' +PORT_TYPE_BOND = '7' +PORT_TYPE_VLAN = '8' ALLOC_TYPE_THIN_FLAG = "1" ALLOC_TYPE_THICK_FLAG = "0" diff --git a/manila/share/drivers/huawei/huawei_nas.py b/manila/share/drivers/huawei/huawei_nas.py index f9227231a9..2381d44d84 100644 --- a/manila/share/drivers/huawei/huawei_nas.py +++ b/manila/share/drivers/huawei/huawei_nas.py @@ -52,12 +52,13 @@ class HuaweiNasDriver(driver.ShareDriver): Add share level(ro). Add smartx capabilities. Support multi pools in one backend. + 1.2 - Add share server support. """ def __init__(self, *args, **kwargs): """Do initialization.""" LOG.debug("Enter into init function.") - super(HuaweiNasDriver, self).__init__(False, *args, **kwargs) + super(HuaweiNasDriver, self).__init__((True, False), *args, **kwargs) self.configuration = kwargs.get('configuration', None) if self.configuration: self.configuration.append_config_values(huawei_opts) @@ -169,10 +170,18 @@ class HuaweiNasDriver(driver.ShareDriver): data = dict( share_backend_name=backend_name or 'HUAWEI_NAS_Driver', vendor_name='Huawei', - driver_version='1.1', + driver_version='1.2', storage_protocol='NFS_CIFS', total_capacity_gb=0.0, free_capacity_gb=0.0) self.plugin.update_share_stats(data) super(HuaweiNasDriver, self)._update_share_stats(data) + + def _setup_server(self, network_info, metadata=None): + """Set up share server with given network parameters.""" + return self.plugin.setup_server(network_info, metadata) + + def _teardown_server(self, server_details, security_services=None): + """Teardown share server.""" + return self.plugin.teardown_server(server_details, security_services) diff --git a/manila/share/drivers/huawei/v3/connection.py b/manila/share/drivers/huawei/v3/connection.py index 0010cddbd7..fae5f67b75 100644 --- a/manila/share/drivers/huawei/v3/connection.py +++ b/manila/share/drivers/huawei/v3/connection.py @@ -13,9 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. +import random +import string import time from oslo_log import log +from oslo_serialization import jsonutils from oslo_utils import strutils from oslo_utils import units @@ -31,7 +34,7 @@ from manila.share.drivers.huawei.v3 import helper from manila.share.drivers.huawei.v3 import smartx from manila.share import share_types from manila.share import utils as share_utils - +from manila import utils LOG = log.getLogger(__name__) @@ -108,9 +111,20 @@ class V3StorageConnection(driver.HuaweiBase): reason=(_('Failed to create share %(name)s. Reason: %(err)s.') % {'name': share_name, 'err': err})) - location = self._get_location_path(share_name, share_proto) + ip = self._get_share_ip(share_server) + location = self._get_location_path(share_name, share_proto, ip) return location + def _get_share_ip(self, share_server): + """"Get share logical ip.""" + if share_server: + ip = share_server['backend_details'].get('ip') + else: + root = self.helper._read_xml() + ip = root.findtext('Storage/LogicalPortIP').strip() + + return ip + def extend_share(self, share, new_size, share_server): share_proto = share['share_proto'] share_name = share['name'] @@ -296,7 +310,10 @@ class V3StorageConnection(driver.HuaweiBase): def get_network_allocations_number(self): """Get number of network interfaces to be created.""" - return constants.IP_ALLOCATIONS + if self.configuration.driver_handles_share_servers: + return constants.IP_ALLOCATIONS_DHSS_TRUE + else: + return constants.IP_ALLOCATIONS_DHSS_FALSE def _get_capacity(self, pool_name, result): """Get free capacity and total capacity of the pools.""" @@ -374,8 +391,9 @@ class V3StorageConnection(driver.HuaweiBase): share_url_type = self.helper._get_share_url_type(share_proto) share_client_type = self.helper._get_share_client_type(share_proto) access_type = access['access_type'] - if share_proto == 'NFS' and access_type != 'ip': - LOG.warning(_LW('Only IP access type is allowed for NFS shares.')) + if share_proto == 'NFS' and access_type not in ('ip', 'user'): + LOG.warning(_LW('Only IP or USER access types are allowed for ' + 'NFS shares.')) return elif share_proto == 'CIFS' and access_type != 'user': LOG.warning(_LW('Only USER access type is allowed for' @@ -404,6 +422,7 @@ class V3StorageConnection(driver.HuaweiBase): share_url_type = self.helper._get_share_url_type(share_proto) access_type = access['access_type'] access_level = access['access_level'] + access_to = access['access_to'] if access_level not in common_constants.ACCESS_LEVELS: raise exception.InvalidShareAccess( @@ -411,14 +430,18 @@ class V3StorageConnection(driver.HuaweiBase): access_level)) if share_proto == 'NFS': - if access_type == 'ip': - if access_level == common_constants.ACCESS_LEVEL_RW: - access_level = constants.ACCESS_NFS_RW - else: - access_level = constants.ACCESS_NFS_RO - else: - message = _('Only IP access type is allowed for NFS shares.') + if access_type == 'user': + # Use 'user' as 'netgroup' for NFS. + # A group name starts with @. + access_to = '@' + access_to + elif access_type != 'ip': + message = _('Only IP or USER access types ' + 'are allowed for NFS shares.') raise exception.InvalidShareAccess(reason=message) + if access_level == common_constants.ACCESS_LEVEL_RW: + access_level = constants.ACCESS_NFS_RW + else: + access_level = constants.ACCESS_NFS_RO elif share_proto == 'CIFS': if access_type == 'user': if access_level == common_constants.ACCESS_LEVEL_RW: @@ -438,7 +461,6 @@ class V3StorageConnection(driver.HuaweiBase): raise exception.InvalidShareAccess(reason=err_msg) share_id = share['ID'] - access_to = access['access_to'] self.helper._allow_access_rest(share_id, access_to, share_proto, access_level) @@ -723,17 +745,15 @@ class V3StorageConnection(driver.HuaweiBase): "new_compression": new_compression}) LOG.info(msg) - def _get_location_path(self, share_name, share_proto): - root = self.helper._read_xml() - target_ip = root.findtext('Storage/LogicalPortIP').strip() - + def _get_location_path(self, share_name, share_proto, ip=None): location = None + if ip is None: + root = self.helper._read_xml() + ip = root.findtext('Storage/LogicalPortIP').strip() if share_proto == 'NFS': - location = '%s:/%s' % (target_ip, - share_name.replace("-", "_")) + location = '%s:/%s' % (ip, share_name.replace("-", "_")) elif share_proto == 'CIFS': - location = '\\\\%s\\%s' % (target_ip, - share_name.replace("-", "_")) + location = '\\\\%s\\%s' % (ip, share_name.replace("-", "_")) else: raise exception.InvalidShareAccess( reason=(_('Invalid NAS protocol supplied: %s.') @@ -775,6 +795,7 @@ class V3StorageConnection(driver.HuaweiBase): pwd = root.findtext('Storage/UserPassword') product = root.findtext('Storage/Product') pool_node = root.findtext('Filesystem/StoragePool') + logical_port_ip = root.findtext('Storage/LogicalPortIP') if product != "V3": err_msg = (_( @@ -797,6 +818,14 @@ class V3StorageConnection(driver.HuaweiBase): LOG.error(err_msg) raise exception.InvalidInput(err_msg) + if not (self.configuration.driver_handles_share_servers + or logical_port_ip): + err_msg = (_( + 'check_conf_file: Config file invalid. LogicalPortIP ' + 'must be set when driver_handles_share_servers is False.')) + LOG.error(err_msg) + raise exception.InvalidInput(reason=err_msg) + def check_service(self): running_status = self.helper._get_cifs_service_status() if running_status != constants.STATUS_SERVICE_RUNNING: @@ -807,3 +836,381 @@ class V3StorageConnection(driver.HuaweiBase): (service['SUPPORTV3'] == 'false') or (service['SUPPORTV4'] == 'false')): self.helper._start_nfs_service_status() + + def setup_server(self, network_info, metadata=None): + """Set up share server with given network parameters.""" + self._check_network_type_validate(network_info['network_type']) + + vlan_tag = network_info['segmentation_id'] or 0 + ip = network_info['network_allocations'][0]['ip_address'] + subnet = utils.cidr_to_netmask(network_info['cidr']) + if not utils.is_valid_ip_address(ip, '4'): + err_msg = (_( + "IP (%s) is invalid. Only IPv4 addresses are supported.") % ip) + LOG.error(err_msg) + raise exception.InvalidInput(reason=err_msg) + + ad_created = False + ldap_created = False + try: + if network_info.get('security_services'): + active_directory, ldap = self._get_valid_security_service( + network_info.get('security_services')) + + # Configure AD or LDAP Domain. + if active_directory: + self._configure_AD_domain(active_directory) + ad_created = True + if ldap: + self._configure_LDAP_domain(ldap) + ldap_created = True + + # Create vlan and logical_port. + vlan_id, logical_port_id = ( + self._create_vlan_and_logical_port(vlan_tag, ip, subnet)) + except exception.ManilaException: + if ad_created: + dns_ip_list = [] + user = active_directory['user'] + password = active_directory['password'] + self.helper.set_DNS_ip_address(dns_ip_list) + self.helper.delete_AD_config(user, password) + self._check_AD_expected_status(constants.STATUS_EXIT_DOMAIN) + if ldap_created: + self.helper.delete_LDAP_config() + raise + + return { + 'share_server_name': network_info['server_id'], + 'share_server_id': network_info['server_id'], + 'vlan_id': vlan_id, + 'logical_port_id': logical_port_id, + 'ip': ip, + 'subnet': subnet, + 'vlan_tag': vlan_tag, + 'ad_created': ad_created, + 'ldap_created': ldap_created, + } + + def _check_network_type_validate(self, network_type): + if network_type not in ('flat', 'vlan'): + err_msg = (_( + 'Invalid network type. Network type must be flat or vlan.')) + raise exception.NetworkBadConfigurationException(reason=err_msg) + + def _get_valid_security_service(self, security_services): + """Validate security services and return AD/LDAP config.""" + service_number = len(security_services) + err_msg = _("Unsupported security services. " + "Only AD and LDAP are supported.") + if service_number > 2: + LOG.error(err_msg) + raise exception.InvalidInput(reason=err_msg) + + active_directory = None + ldap = None + for ss in security_services: + if ss['type'] == 'active_directory': + active_directory = ss + elif ss['type'] == 'ldap': + ldap = ss + else: + LOG.error(err_msg) + raise exception.InvalidInput(reason=err_msg) + + return active_directory, ldap + + def _configure_AD_domain(self, active_directory): + dns_ip = active_directory['dns_ip'] + user = active_directory['user'] + password = active_directory['password'] + domain = active_directory['domain'] + if not (dns_ip and user and password and domain): + raise exception.InvalidInput( + reason=_("dns_ip or user or password or domain " + "in security_services is None.")) + + # Check DNS server exists or not. + ip_address = self.helper.get_DNS_ip_address() + if ip_address and ip_address[0]: + err_msg = (_("DNS server (%s) has already been configured.") + % ip_address[0]) + LOG.error(err_msg) + raise exception.InvalidInput(reason=err_msg) + + # Check AD config exists or not. + ad_exists, AD_domain = self.helper.get_AD_domain_name() + if ad_exists: + err_msg = (_("AD domain (%s) has already been configured.") + % AD_domain) + LOG.error(err_msg) + raise exception.InvalidInput(reason=err_msg) + + # Set DNS server ip. + dns_ip_list = dns_ip.split(",") + DNS_config = self.helper.set_DNS_ip_address(dns_ip_list) + + # Set AD config. + digits = string.digits + random_id = ''.join([random.choice(digits) for i in range(9)]) + system_name = constants.SYSTEM_NAME_PREFIX + random_id + + try: + self.helper.add_AD_config(user, password, domain, system_name) + self._check_AD_expected_status(constants.STATUS_JOIN_DOMAIN) + except exception.ManilaException as err: + if DNS_config: + dns_ip_list = [] + self.helper.set_DNS_ip_address(dns_ip_list) + raise exception.InvalidShare( + reason=(_('Failed to add AD config. ' + 'Reason: %s.') % err)) + + def _check_AD_expected_status(self, expected_status): + wait_interval = self._get_wait_interval() + timeout = self._get_timeout() + retries = timeout / wait_interval + interval = wait_interval + backoff_rate = 1 + + @utils.retry(exception.InvalidShare, + interval, + retries, + backoff_rate) + def _check_AD_status(): + ad = self.helper.get_AD_config() + if ad['DOMAINSTATUS'] != expected_status: + raise exception.InvalidShare( + reason=(_('AD domain (%s) status is not expected.') + % ad['FULLDOMAINNAME'])) + + _check_AD_status() + + def _configure_LDAP_domain(self, ldap): + server = ldap['server'] + domain = ldap['domain'] + if not server or not domain: + raise exception.InvalidInput(reason=_("Server or domain is None.")) + + # Check LDAP config exists or not. + ldap_exists, LDAP_domain = self.helper.get_LDAP_domain_server() + if ldap_exists: + err_msg = (_("LDAP domain (%s) has already been configured.") + % LDAP_domain) + LOG.error(err_msg) + raise exception.InvalidInput(reason=err_msg) + + # Set LDAP config. + server_number = len(server.split(',')) + if server_number == 1: + server = server + ",," + elif server_number == 2: + server = server + "," + elif server_number > 3: + raise exception.InvalidInput( + reason=_("Cannot support more than three LDAP servers.")) + + self.helper.add_LDAP_config(server, domain) + + def _create_vlan_and_logical_port(self, vlan_tag, ip, subnet): + optimal_port, port_type = self._get_optimal_port() + port_id = self.helper.get_port_id(optimal_port, port_type) + home_port_id = port_id + home_port_type = port_type + vlan_id = 0 + vlan_exists = True + + if port_type is None or port_id is None: + err_msg = _("No appropriate port found to create logical port.") + LOG.error(err_msg) + raise exception.InvalidInput(reason=err_msg) + if vlan_tag: + vlan_exists, vlan_id = self.helper.get_vlan(port_id, vlan_tag) + if not vlan_exists: + # Create vlan. + vlan_id = self.helper.create_vlan( + port_id, port_type, vlan_tag) + home_port_id = vlan_id + home_port_type = constants.PORT_TYPE_VLAN + + logical_port_exists, logical_port_id = ( + self.helper.get_logical_port(home_port_id, ip, subnet)) + if not logical_port_exists: + try: + # Create logical port. + logical_port_id = ( + self.helper.create_logical_port( + home_port_id, home_port_type, ip, subnet)) + except exception.ManilaException as err: + if not vlan_exists: + self.helper.delete_vlan(vlan_id) + raise exception.InvalidShare( + reason=(_('Failed to create logical port. ' + 'Reason: %s.') % err)) + + return vlan_id, logical_port_id + + def _get_optimal_port(self): + """Get an optimal physical port or bond port.""" + root = self.helper._read_xml() + port_info = [] + port_list = root.findtext('Storage/Port') + if port_list: + port_list = port_list.split(";") + for port in port_list: + port = port.strip().strip('\n') + if port: + port_info.append(port) + + eth_port, bond_port = self._get_online_port(port_info) + optimal_port, port_type = ( + self._get_least_vlan_port(eth_port, bond_port)) + + if not optimal_port: + err_msg = (_("Cannot find optimal port. port_info: %s.") + % port_info) + LOG.error(err_msg) + raise exception.InvalidInput(reason=err_msg) + + return optimal_port, port_type + + def _get_online_port(self, all_port_list): + eth_port = self.helper.get_all_eth_port() + bond_port = self.helper.get_all_bond_port() + + eth_status = constants.STATUS_ETH_RUNNING + online_eth_port = [] + for eth in eth_port: + if (eth_status == eth['RUNNINGSTATUS'] + and not eth['IPV4ADDR'] and not eth['BONDNAME']): + online_eth_port.append(eth['LOCATION']) + + online_bond_port = [] + for bond in bond_port: + if eth_status == bond['RUNNINGSTATUS']: + port_id = jsonutils.loads(bond['PORTIDLIST']) + bond_eth_port = self.helper.get_eth_port_by_id(port_id[0]) + if bond_eth_port and not bond_eth_port['IPV4ADDR']: + online_bond_port.append(bond['NAME']) + + filtered_eth_port = [] + filtered_bond_port = [] + if len(all_port_list) == 0: + filtered_eth_port = online_eth_port + filtered_bond_port = online_bond_port + else: + all_port_list = list(set(all_port_list)) + for port in all_port_list: + is_eth_port = False + for eth in online_eth_port: + if port == eth: + filtered_eth_port.append(port) + is_eth_port = True + break + if is_eth_port: + continue + for bond in online_bond_port: + if port == bond: + filtered_bond_port.append(port) + break + + return filtered_eth_port, filtered_bond_port + + def _get_least_vlan_port(self, eth_port, bond_port): + sorted_eth = [] + sorted_bond = [] + + if eth_port: + sorted_eth = self._get_sorted_least_port(eth_port) + if bond_port: + sorted_bond = self._get_sorted_least_port(bond_port) + + if sorted_eth and sorted_bond: + if sorted_eth[1] >= sorted_bond[1]: + return sorted_bond[0], constants.PORT_TYPE_BOND + else: + return sorted_eth[0], constants.PORT_TYPE_ETH + elif sorted_eth and not sorted_bond: + return sorted_eth[0], constants.PORT_TYPE_ETH + elif not sorted_eth and sorted_bond: + return sorted_bond[0], constants.PORT_TYPE_BOND + else: + return None, None + + def _get_sorted_least_port(self, port_list): + if not port_list: + return None + + vlan_list = self.helper.get_all_vlan() + count = {} + for item in port_list: + count[item] = 0 + + for item in port_list: + for vlan in vlan_list: + pos = vlan['NAME'].rfind('.') + if vlan['NAME'][:pos] == item: + count[item] += 1 + + sort_port = sorted(count.items(), key=lambda count: count[1]) + + return sort_port[0] + + def teardown_server(self, server_details, security_services=None): + if not server_details: + LOG.debug('Server details are empty.') + return + + logical_port_id = server_details.get('logical_port_id') + vlan_id = server_details.get('vlan_id') + ad_created = server_details.get('ad_created') + ldap_created = server_details.get('ldap_created') + + # Delete logical_port. + if logical_port_id: + logical_port_exists = ( + self.helper.check_logical_port_exists_by_id(logical_port_id)) + if logical_port_exists: + self.helper.delete_logical_port(logical_port_id) + + # Delete vlan. + if vlan_id and vlan_id != '0': + vlan_exists = self.helper.check_vlan_exists_by_id(vlan_id) + if vlan_exists: + self.helper.delete_vlan(vlan_id) + + if security_services: + active_directory, ldap = ( + self._get_valid_security_service(security_services)) + + if ad_created and ad_created == '1' and active_directory: + dns_ip = active_directory['dns_ip'] + user = active_directory['user'] + password = active_directory['password'] + domain = active_directory['domain'] + + # Check DNS server exists or not. + ip_address = self.helper.get_DNS_ip_address() + if ip_address and ip_address[0] == dns_ip: + dns_ip_list = [] + self.helper.set_DNS_ip_address(dns_ip_list) + + # Check AD config exists or not. + ad_exists, AD_domain = self.helper.get_AD_domain_name() + if ad_exists and AD_domain == domain: + self.helper.delete_AD_config(user, password) + self._check_AD_expected_status( + constants.STATUS_EXIT_DOMAIN) + + if ldap_created and ldap_created == '1' and ldap: + server = ldap['server'] + domain = ldap['domain'] + + # Check LDAP config exists or not. + ldap_exists, LDAP_domain = ( + self.helper.get_LDAP_domain_server()) + if ldap_exists: + LDAP_config = self.helper.get_LDAP_config() + if (LDAP_config['LDAPSERVER'] == server + and LDAP_config['BASEDN'] == domain): + self.helper.delete_LDAP_config() \ No newline at end of file diff --git a/manila/share/drivers/huawei/v3/helper.py b/manila/share/drivers/huawei/v3/helper.py index 815181f57c..e53b35d72d 100644 --- a/manila/share/drivers/huawei/v3/helper.py +++ b/manila/share/drivers/huawei/v3/helper.py @@ -25,6 +25,7 @@ from six.moves.urllib import request as urlreq # pylint: disable=E0611 from manila import exception from manila.i18n import _ from manila.i18n import _LE +from manila.i18n import _LW from manila.share.drivers.huawei import constants from manila import utils @@ -419,39 +420,93 @@ class RestHelper(object): self._assert_rest_result(result, 'Get access id by share error!') for item in result.get('data', []): - if access_to == item['NAME']: + if item['NAME'] in (access_to, '@' + access_to): return item['ID'] def _allow_access_rest(self, share_id, access_to, share_proto, access_level): """Allow access to the share.""" - access_type = self._get_share_client_type(share_proto) - url = "/" + access_type + if share_proto == 'NFS': + self._allow_nfs_access_rest(share_id, access_to, access_level) + elif share_proto == 'CIFS': + self._allow_cifs_access_rest(share_id, access_to, access_level) + else: + raise exception.InvalidInput( + reason=(_('Invalid NAS protocol supplied: %s.') + % share_proto)) - access = {} - if access_type == "NFS_SHARE_AUTH_CLIENT": - access = { - "TYPE": "16409", - "NAME": access_to, - "PARENTID": share_id, - "ACCESSVAL": access_level, - "SYNC": "0", - "ALLSQUASH": "1", - "ROOTSQUASH": "0", - } - elif access_type == "CIFS_SHARE_AUTH_CLIENT": - access = { - "NAME": access_to, - "PARENTID": share_id, - "PERMISSION": access_level, - "DOMAINTYPE": "2", - } + def _allow_nfs_access_rest(self, share_id, access_to, access_level): + url = "/NFS_SHARE_AUTH_CLIENT" + access = { + "TYPE": "16409", + "NAME": access_to, + "PARENTID": share_id, + "ACCESSVAL": access_level, + "SYNC": "0", + "ALLSQUASH": "1", + "ROOTSQUASH": "0", + } data = jsonutils.dumps(access) result = self.call(url, data, "POST") msg = 'Allow access error.' self._assert_rest_result(result, msg) + def _allow_cifs_access_rest(self, share_id, access_to, access_level): + url = "/CIFS_SHARE_AUTH_CLIENT" + domain_type = { + 'local': '2', + 'ad': '0' + } + error_msg = 'Allow access error.' + access_info = ('Access info (access_to: %(access_to)s, ' + 'access_level: %(access_level)s, share_id: %(id)s)' + % {'access_to': access_to, + 'access_level': access_level, + 'id': share_id}) + + def send_rest(access_to, domain_type): + access = { + "NAME": access_to, + "PARENTID": share_id, + "PERMISSION": access_level, + "DOMAINTYPE": domain_type, + } + data = jsonutils.dumps(access) + result = self.call(url, data, "POST") + error_code = result['error']['code'] + if error_code == 0: + return True + elif error_code != constants.ERROR_USER_OR_GROUP_NOT_EXIST: + self._assert_rest_result(result, error_msg) + return False + + if '\\' not in access_to: + # First, try to add user access. + LOG.debug('Try to add user access. %s.', access_info) + if send_rest(access_to, domain_type['local']): + return + # Second, if add user access failed, + # try to add group access. + LOG.debug('Failed with add user access, ' + 'try to add group access. %s.', access_info) + # Group name starts with @. + if send_rest('@' + access_to, domain_type['local']): + return + else: + LOG.debug('Try to add domain user access. %s.', access_info) + if send_rest(access_to, domain_type['ad']): + return + # If add domain user access failed, + # try to add domain group access. + LOG.debug('Failed with add domain user access, ' + 'try to add domain group access. %s.', access_info) + # Group name starts with @. + if send_rest('@' + access_to, domain_type['ad']): + return + + raise exception.InvalidShare(reason=error_msg) + def _get_share_client_type(self, share_proto): share_client_type = None if share_proto == 'NFS': @@ -766,3 +821,276 @@ class RestHelper(object): self._assert_rest_result(result, _('Remove filesystem from cache error.')) + + def get_all_eth_port(self): + url = "/ETH_PORT" + result = self.call(url, None, 'GET') + self._assert_rest_result(result, _('Get all eth port error.')) + + all_eth = {} + if "data" in result: + all_eth = result['data'] + + return all_eth + + def get_eth_port_by_id(self, port_id): + url = "/ETH_PORT/" + port_id + result = self.call(url, None, 'GET') + self._assert_rest_result(result, _('Get eth port by id error.')) + + if "data" in result: + return result['data'] + + return None + + def get_all_bond_port(self): + url = "/BOND_PORT" + result = self.call(url, None, 'GET') + self._assert_rest_result(result, _('Get all bond port error.')) + + all_bond = {} + if "data" in result: + all_bond = result['data'] + + return all_bond + + def get_port_id(self, port_name, port_type): + if port_type == constants.PORT_TYPE_ETH: + all_eth = self.get_all_eth_port() + for item in all_eth: + if port_name == item['LOCATION']: + return item['ID'] + elif port_type == constants.PORT_TYPE_BOND: + all_bond = self.get_all_bond_port() + for item in all_bond: + if port_name == item['NAME']: + return item['ID'] + + return None + + def get_all_vlan(self): + url = "/vlan" + result = self.call(url, None, 'GET') + self._assert_rest_result(result, _('Get all vlan error.')) + + all_vlan = {} + if "data" in result: + all_vlan = result['data'] + + return all_vlan + + def get_vlan(self, port_id, vlan_tag): + url = "/vlan" + result = self.call(url, None, 'GET') + self._assert_rest_result(result, _('Get vlan error.')) + + vlan_tag = six.text_type(vlan_tag) + if "data" in result: + for item in result['data']: + if port_id == item['PORTID'] and vlan_tag == item['TAG']: + return True, item['ID'] + + return False, None + + def create_vlan(self, port_id, port_type, vlan_tag): + url = "/vlan" + data = jsonutils.dumps({"PORTID": port_id, + "PORTTYPE": port_type, + "TAG": six.text_type(vlan_tag), + "TYPE": "280"}) + result = self.call(url, data, "POST") + self._assert_rest_result(result, _('Create vlan error.')) + + return result['data']['ID'] + + def check_vlan_exists_by_id(self, vlan_id): + all_vlan = self.get_all_vlan() + return any(vlan['ID'] == vlan_id for vlan in all_vlan) + + def delete_vlan(self, vlan_id): + url = "/vlan/" + vlan_id + result = self.call(url, None, 'DELETE') + if result['error']['code'] == constants.ERROR_LOGICAL_PORT_EXIST: + LOG.warning(_LW('Cannot delete vlan because there is ' + 'a logical port on vlan.')) + return + + self._assert_rest_result(result, _('Delete vlan error.')) + + def get_logical_port(self, home_port_id, ip, subnet): + url = "/LIF" + result = self.call(url, None, 'GET') + self._assert_rest_result(result, _('Get logical port error.')) + + if "data" not in result: + return False, None + + for item in result['data']: + if (home_port_id == item['HOMEPORTID'] + and ip == item['IPV4ADDR'] + and subnet == item['IPV4MASK']): + if item['OPERATIONALSTATUS'] != 'true': + self._activate_logical_port(item['ID']) + return True, item['ID'] + + return False, None + + def _activate_logical_port(self, logical_port_id): + url = "/LIF/" + logical_port_id + data = jsonutils.dumps({"OPERATIONALSTATUS": "true"}) + result = self.call(url, data, 'PUT') + self._assert_rest_result(result, _('Activate logical port error.')) + + def create_logical_port(self, home_port_id, home_port_type, ip, subnet): + url = "/LIF" + info = { + "ADDRESSFAMILY": 0, + "CANFAILOVER": "true", + "HOMEPORTID": home_port_id, + "HOMEPORTTYPE": home_port_type, + "IPV4ADDR": ip, + "IPV4GATEWAY": "", + "IPV4MASK": subnet, + "NAME": ip, + "OPERATIONALSTATUS": "true", + "ROLE": 2, + "SUPPORTPROTOCOL": 3, + "TYPE": "279", + } + + data = jsonutils.dumps(info) + result = self.call(url, data, 'POST') + self._assert_rest_result(result, _('Create logical port error.')) + + return result['data']['ID'] + + def check_logical_port_exists_by_id(self, logical_port_id): + all_logical_port = self.get_all_logical_port() + return any(port['ID'] == logical_port_id for port in all_logical_port) + + def get_all_logical_port(self): + url = "/LIF" + result = self.call(url, None, 'GET') + self._assert_rest_result(result, _('Get all logical port error.')) + + all_logical_port = {} + if "data" in result: + all_logical_port = result['data'] + + return all_logical_port + + def delete_logical_port(self, logical_port_id): + url = "/LIF/" + logical_port_id + result = self.call(url, None, 'DELETE') + self._assert_rest_result(result, _('Delete logical port error.')) + + def set_DNS_ip_address(self, dns_ip_list): + if len(dns_ip_list) > 3: + message = _('Most three ips can be set to DNS.') + LOG.error(message) + raise exception.InvalidInput(reason=message) + + url = "/DNS_Server" + dns_info = { + "ADDRESS": jsonutils.dumps(dns_ip_list), + "TYPE": "260", + } + data = jsonutils.dumps(dns_info) + result = self.call(url, data, 'PUT') + self._assert_rest_result(result, _('Set DNS ip address error.')) + + if "data" in result: + return result['data'] + + return None + + def get_DNS_ip_address(self): + url = "/DNS_Server" + result = self.call(url, None, 'GET') + self._assert_rest_result(result, _('Get DNS ip address error.')) + + ip_address = {} + if "data" in result: + ip_address = jsonutils.loads(result['data']['ADDRESS']) + + return ip_address + + def add_AD_config(self, user, password, domain, system_name): + url = "/AD_CONFIG" + info = { + "ADMINNAME": user, + "ADMINPWD": password, + "DOMAINSTATUS": 1, + "FULLDOMAINNAME": domain, + "OU": "", + "SYSTEMNAME": system_name, + "TYPE": "16414", + } + data = jsonutils.dumps(info) + result = self.call(url, data, 'PUT') + self._assert_rest_result(result, _('Add AD config error.')) + + def delete_AD_config(self, user, password): + url = "/AD_CONFIG" + info = { + "ADMINNAME": user, + "ADMINPWD": password, + "DOMAINSTATUS": 0, + "TYPE": "16414", + } + data = jsonutils.dumps(info) + result = self.call(url, data, 'PUT') + self._assert_rest_result(result, _('Delete AD config error.')) + + def get_AD_config(self): + url = "/AD_CONFIG" + result = self.call(url, None, 'GET') + self._assert_rest_result(result, _('Get AD config error.')) + + if "data" in result: + return result['data'] + + return None + + def get_AD_domain_name(self): + result = self.get_AD_config() + if result and result['DOMAINSTATUS'] == '1': + return True, result['FULLDOMAINNAME'] + + return False, None + + def add_LDAP_config(self, server, domain): + url = "/LDAP_CONFIG" + info = { + "BASEDN": domain, + "LDAPSERVER": server, + "PORTNUM": 389, + "TRANSFERTYPE": "1", + "TYPE": "16413", + "USERNAME": "", + } + data = jsonutils.dumps(info) + result = self.call(url, data, 'PUT') + self._assert_rest_result(result, _('Add LDAP config error.')) + + def delete_LDAP_config(self): + url = "/LDAP_CONFIG" + result = self.call(url, None, 'DELETE') + self._assert_rest_result(result, _('Delete LDAP config error.')) + + def get_LDAP_config(self): + url = "/LDAP_CONFIG" + result = self.call(url, None, 'GET') + self._assert_rest_result(result, _('Get LDAP config error.')) + + if "data" in result: + return result['data'] + + return None + + def get_LDAP_domain_server(self): + result = self.get_LDAP_config() + if result and result['LDAPSERVER']: + return True, result['LDAPSERVER'] + + return False, None diff --git a/manila/tests/share/drivers/huawei/test_huawei_nas.py b/manila/tests/share/drivers/huawei/test_huawei_nas.py index 9ccee584dd..16ec16345c 100644 --- a/manila/tests/share/drivers/huawei/test_huawei_nas.py +++ b/manila/tests/share/drivers/huawei/test_huawei_nas.py @@ -231,46 +231,45 @@ def filesystem_inpartition(method, data, fs_status_flag): def allow_access(type, method, data): allow_ro_flag = False allow_rw_flag = False - access_nfs = { - "TYPE": "16409", - "NAME": "1.2.3.4", - "PARENTID": "1", - "ACCESSVAL": "0", - "SYNC": "0", - "ALLSQUASH": "1", - "ROOTSQUASH": "0", - } - access_nfs_ro_data = jsonutils.dumps(access_nfs) - access_nfs["NAME"] = "100.112.0.1" - access_nfs["ACCESSVAL"] = "1" - access_nfs_rw_data = jsonutils.dumps(access_nfs) + request_data = jsonutils.loads(data) + success_data = """{"error":{"code":0}}""" + fail_data = """{"error":{"code":1077939723}}""" + ret = None - access_cifs = { - "NAME": "user_name", - "PARENTID": "2", - "PERMISSION": "0", - "DOMAINTYPE": "2", - } - access_cifs_ro_data = jsonutils.dumps(access_cifs) + if type == "NFS": + if request_data['ACCESSVAL'] == '0': + allow_ro_flag = True + ret = success_data + elif request_data['ACCESSVAL'] == '1': + allow_rw_flag = True + ret = success_data + elif type == "CIFS": + if request_data['PERMISSION'] == '0': + allow_ro_flag = True + ret = success_data + elif request_data['PERMISSION'] == '5': + allow_rw_flag = True + ret = success_data + # Group name should start with '@'. + if ('group' in request_data['NAME'] + and not request_data['NAME'].startswith('@')): + ret = fail_data - access_cifs["PERMISSION"] = "5" - access_cifs_rw_data = jsonutils.dumps(access_cifs) + if ret is None: + ret = fail_data + return (ret, allow_ro_flag, allow_rw_flag) - if method != "POST": - data = """{"error":{"code":31755596}}""" - return data - if ((data == access_nfs_ro_data and type == "NFS") - or (data == access_cifs_ro_data and type == "CIFS")): - allow_ro_flag = True - data = """{"error":{"code":0}}""" - elif ((data == access_nfs_rw_data and type == 'NFS') - or (data == access_cifs_rw_data and type == 'CIFS')): - allow_rw_flag = True - data = """{"error":{"code":0}}""" - else: - data = """{"error":{"code":31755596}}""" - return (data, allow_ro_flag, allow_rw_flag) +def dec_driver_handles_share_servers(func): + def wrapper(*args, **kw): + self = args[0] + self.configuration.driver_handles_share_servers = True + self.recreate_fake_conf_file(logical_port='CTE0.A.H0') + self.driver.plugin.configuration.manila_huawei_conf_file = ( + self.fake_conf_file) + self.driver.plugin.helper.login() + return func(*args, **kw) + return wrapper class FakeHuaweiNasHelper(helper.RestHelper): @@ -565,6 +564,75 @@ class FakeHuaweiNasHelper(helper.RestHelper): if url == "/smartPartition/removeFs": data = """{"error":{"code":0}}""" + + if url == "/ETH_PORT": + data = """{"error":{"code":0}, + "data":[{"ID": "4", + "LOCATION":"CTE0.A.H0", + "IPV4ADDR":"", + "BONDNAME":"", + "BONDID":"", + "RUNNINGSTATUS":"10"}, + {"ID": "6", + "LOCATION":"CTE0.A.H1", + "IPV4ADDR":"", + "BONDNAME":"fake_bond", + "BONDID":"5", + "RUNNINGSTATUS":"10"}]}""" + + if url == "/ETH_PORT/6": + data = """{"error":{"code":0}, + "data":{"ID": "6", + "LOCATION":"CTE0.A.H1", + "IPV4ADDR":"", + "BONDNAME":"fake_bond", + "BONDID":"5", + "RUNNINGSTATUS":"10"}}""" + + if url == "/BOND_PORT": + data = "{\"error\":{\"code\":0},\ + \"data\":[{\"ID\": \"5\",\ + \"NAME\":\"fake_bond\",\ + \"PORTIDLIST\": \"[\\\"6\\\"]\",\ + \"RUNNINGSTATUS\":\"10\"}]}" + + if url == "/vlan": + if method == "GET": + data = """{"error":{"code":0}}""" + else: + data = """{"error":{"code":0},"data":{ + "ID":"4"}}""" + + if url == "/LIF": + if method == "GET": + data = """{"error":{"code":0}}""" + else: + data = """{"error":{"code":0},"data":{ + "ID":"4"}}""" + + if url == "/DNS_Server": + if method == "GET": + data = "{\"error\":{\"code\":0},\"data\":{\ + \"ADDRESS\":\"[\\\"\\\"]\"}}" + else: + data = """{"error":{"code":0}}""" + + if url == "/AD_CONFIG": + if method == "GET": + data = """{"error":{"code":0},"data":{ + "DOMAINSTATUS":"1", + "FULLDOMAINNAME":"huawei.com"}}""" + else: + data = """{"error":{"code":0}}""" + + if url == "/LDAP_CONFIG": + if method == "GET": + data = """{"error":{"code":0},"data":{ + "BASEDN":"dc=huawei,dc=com", + "LDAPSERVER": "100.97.5.87"}}""" + else: + data = """{"error":{"code":0}}""" + else: data = '{"error":{"code":31755596}}' @@ -596,11 +664,6 @@ class HuaweiShareDriverTestCase(test.TestCase): def setUp(self): super(HuaweiShareDriverTestCase, self).setUp() self._context = context.get_admin_context() - self.tmp_dir = tempfile.mkdtemp() - self.fake_conf_file = self.tmp_dir + '/manila_huawei_conf.xml' - self.addCleanup(shutil.rmtree, self.tmp_dir) - self.create_fake_conf_file(self.fake_conf_file) - self.addCleanup(os.remove, self.fake_conf_file) def _safe_get(opt): return getattr(self.configuration, opt) @@ -611,9 +674,15 @@ class HuaweiShareDriverTestCase(test.TestCase): self.configuration.share_backend_name = 'fake_share_backend_name' self.configuration.huawei_share_backend = 'V3' self.configuration.max_over_subscription_ratio = 1 + self.configuration.driver_handles_share_servers = False + + self.tmp_dir = tempfile.mkdtemp() + self.fake_conf_file = self.tmp_dir + '/manila_huawei_conf.xml' + self.addCleanup(shutil.rmtree, self.tmp_dir) + self.create_fake_conf_file(self.fake_conf_file) + self.addCleanup(os.remove, self.fake_conf_file) self.configuration.manila_huawei_conf_file = self.fake_conf_file - self.configuration.driver_handles_share_servers = False self._helper_fake = mock.Mock() self.mock_object(huawei_nas.importutils, 'import_object', mock.Mock(return_value=self._helper_fake)) @@ -826,21 +895,50 @@ class HuaweiShareDriverTestCase(test.TestCase): 'access_level': 'rw', } + self.access_group = { + 'access_type': 'user', + 'access_to': 'group_name', + 'access_level': 'rw', + } + + self.access_cert = { + 'access_type': 'cert', + 'access_to': 'fake_cert', + 'access_level': 'rw', + } + self.driver_options = { 'volume_id': 'fake', } self.share_server = None self.driver._licenses = ['fake'] - self.network_info = { - 'server_id': 'fake_server_id', - 'cidr': '10.0.0.0/24', - 'security_services': ['fake_ldap', 'fake_kerberos', 'fake_ad', ], - 'segmentation_id': '1000', - 'network_allocations': [ - {'id': 'fake_na_id_1', 'ip_address': 'fake_ip_1', }, - {'id': 'fake_na_id_2', 'ip_address': 'fake_ip_2', }, - ], + self.fake_network_allocations = [{ + 'id': 'fake_network_allocation_id', + 'ip_address': '111.111.111.109', + }] + self.fake_network_info = { + 'server_id': '0', + 'segmentation_id': '2', + 'cidr': '111.111.111.0/24', + 'neutron_net_id': 'fake_neutron_net_id', + 'neutron_subnet_id': 'fake_neutron_subnet_id', + 'nova_net_id': '', + 'security_services': '', + 'network_allocations': self.fake_network_allocations, + 'network_type': 'vlan', + } + self.fake_active_directory = { + 'type': 'active_directory', + 'dns_ip': '100.97.5.5', + 'user': 'ad_user', + 'password': 'ad_password', + 'domain': 'huawei.com' + } + self.fake_ldap = { + 'type': 'ldap', + 'server': '100.97.5.87', + 'domain': 'dc=huawei,dc=com' } fake_share_type_id_not_extra = 'fake_id' @@ -1008,6 +1106,15 @@ class HuaweiShareDriverTestCase(test.TestCase): wait_interval = self.driver.plugin._get_wait_interval() self.assertEqual(3, wait_interval) + def test_conf_logical_ip_fail(self): + self.configuration.driver_handles_share_servers = True + self.recreate_fake_conf_file(logical_port="fake_port") + self.driver.plugin.configuration.manila_huawei_conf_file = ( + self.fake_conf_file) + self.configuration.driver_handles_share_servers = False + self.assertRaises(exception.InvalidInput, + self.driver.plugin.check_conf_file) + def test_get_backend_driver_fail(self): test_fake_conf_file = None self.driver.plugin.configuration.manila_huawei_conf_file = ( @@ -1373,7 +1480,13 @@ class HuaweiShareDriverTestCase(test.TestCase): self.share_server) self.assertTrue(self.driver.plugin.helper.delete_flag) - def test_get_network_allocations_number(self): + def test_get_network_allocations_number_dhss_true(self): + self.configuration.driver_handles_share_servers = True + number = self.driver.get_network_allocations_number() + self.assertEqual(1, number) + + def test_get_network_allocations_number_dhss_false(self): + self.configuration.driver_handles_share_servers = False number = self.driver.get_network_allocations_number() self.assertEqual(0, number) @@ -1399,7 +1512,7 @@ class HuaweiShareDriverTestCase(test.TestCase): expected["share_backend_name"] = "fake_share_backend_name" expected["driver_handles_share_servers"] = False expected["vendor_name"] = 'Huawei' - expected["driver_version"] = '1.1' + expected["driver_version"] = '1.2' expected["storage_protocol"] = 'NFS_CIFS' expected['reserved_percentage'] = 0 expected['total_capacity_gb'] = 0.0 @@ -1462,16 +1575,49 @@ class HuaweiShareDriverTestCase(test.TestCase): self.assertTrue(self.driver.plugin.helper.allow_flag) self.assertTrue(self.driver.plugin.helper.allow_ro_flag) - def test_allow_access_user_rw_success(self): + def test_allow_access_nfs_user_success(self): + self.driver.plugin.helper.login() + self.allow_flag = False + self.allow_rw_flag = False + self.driver.allow_access(self._context, + self.share_nfs, + self.access_user, + self.share_server) + self.assertTrue(self.driver.plugin.helper.allow_flag) + self.assertTrue(self.driver.plugin.helper.allow_rw_flag) + + @ddt.data( + { + 'access_type': 'user', + 'access_to': 'user_name', + 'access_level': 'rw', + }, + { + 'access_type': 'user', + 'access_to': 'group_name', + 'access_level': 'rw', + }, + { + 'access_type': 'user', + 'access_to': 'domain\\user_name', + 'access_level': 'rw', + }, + { + 'access_type': 'user', + 'access_to': 'domain\\group_name', + 'access_level': 'rw', + }, + ) + def test_allow_access_cifs_rw_success(self, access_user): self.driver.plugin.helper.login() self.allow_flag = False self.allow_rw_flag = False self.driver.allow_access(self._context, self.share_cifs, - self.access_user, self.share_server) + access_user, self.share_server) self.assertTrue(self.driver.plugin.helper.allow_flag) self.assertTrue(self.driver.plugin.helper.allow_rw_flag) - def test_allow_access_user_ro_success(self): + def test_allow_access_cifs_user_ro_success(self): access_ro = { 'access_type': 'user', 'access_to': 'user_name', @@ -1518,25 +1664,37 @@ class HuaweiShareDriverTestCase(test.TestCase): self.driver.plugin._get_location_path, share_name, share_proto) - def test_allow_access_ip_proto_fail(self): + def test_allow_access_nfs_fail(self): self.driver.plugin.helper.login() self.assertRaises(exception.InvalidShareAccess, self.driver.allow_access, self._context, - self.share_nfs, self.access_user, self.share_server) + self.share_nfs, self.access_cert, self.share_server) - def test_allow_access_user_proto_fail(self): + def test_allow_access_cifs_fail(self): self.driver.plugin.helper.login() self.assertRaises(exception.InvalidShareAccess, self.driver.allow_access, self._context, self.share_cifs, self.access_ip, self.share_server) - def test_deny_access_ip_proto_fail(self): + def test_deny_access_nfs_fail(self): self.driver.plugin.helper.login() result = self.driver.deny_access(self._context, self.share_nfs, - self.access_user, self.share_server) + self.access_cert, self.share_server) self.assertIsNone(result) - def test_deny_access_user_proto_fail(self): + def test_deny_access_not_exist_fail(self): + self.driver.plugin.helper.login() + access_ip_not_exist = { + 'access_type': 'ip', + 'access_to': '100.112.0.99', + 'access_level': 'rw', + } + result = self.driver.deny_access(self._context, self.share_nfs, + access_ip_not_exist, + self.share_server) + self.assertIsNone(result) + + def test_deny_access_cifs_fail(self): self.driver.plugin.helper.login() result = self.driver.deny_access(self._context, self.share_cifs, self.access_ip, self.share_server) @@ -1928,7 +2086,7 @@ class HuaweiShareDriverTestCase(test.TestCase): self.driver_options) def test_manage_logical_port_ip_fail(self): - self.recreate_fake_conf_file(logical_port_ip="") + self.recreate_fake_conf_file(logical_port="") self.driver.plugin.configuration.manila_huawei_conf_file = ( self.fake_conf_file) self.driver.plugin.helper.login() @@ -1978,13 +2136,574 @@ class HuaweiShareDriverTestCase(test.TestCase): self.share_nfs, self.share_server) + @dec_driver_handles_share_servers + def test_setup_server_success(self): + backend_details = self.driver.setup_server(self.fake_network_info) + fake_share_server = { + 'backend_details': backend_details + } + share_type = self.fake_type_not_extra['test_with_extra'] + self.mock_object(db, + 'share_type_get', + mock.Mock(return_value=share_type)) + location = self.driver.create_share(self._context, self.share_nfs, + fake_share_server) + self.assertTrue(db.share_type_get.called) + self.assertEqual((self.fake_network_allocations[0]['ip_address'] + + ":/share_fake_uuid"), location) + + @dec_driver_handles_share_servers + def test_setup_server_with_bond_port_success(self): + self.recreate_fake_conf_file(logical_port='fake_bond') + self.driver.plugin.configuration.manila_huawei_conf_file = ( + self.fake_conf_file) + backend_details = self.driver.setup_server(self.fake_network_info) + fake_share_server = { + 'backend_details': backend_details + } + share_type = self.fake_type_not_extra['test_with_extra'] + self.mock_object(db, + 'share_type_get', + mock.Mock(return_value=share_type)) + location = self.driver.create_share(self._context, self.share_nfs, + fake_share_server) + self.assertTrue(db.share_type_get.called) + self.assertEqual((self.fake_network_allocations[0]['ip_address'] + + ":/share_fake_uuid"), location) + + @dec_driver_handles_share_servers + def test_setup_server_logical_port_exist(self): + def call_logical_port_exist(*args, **kwargs): + url = args[0] + method = args[2] + if url == "/LIF" and method == "GET": + data = """{"error":{"code":0},"data":[{ + "ID":"4", + "HOMEPORTID":"4", + "IPV4ADDR":"111.111.111.109", + "IPV4MASK":"255.255.255.0", + "OPERATIONALSTATUS":"false"}]}""" + elif url == "/LIF/4" and method == "PUT": + data = """{"error":{"code":0}}""" + else: + return self.driver.plugin.helper.do_call(*args, **kwargs) + + res_json = jsonutils.loads(data) + return res_json + + self.mock_object(self.driver.plugin.helper, "create_logical_port") + with mock.patch.object(self.driver.plugin.helper, + 'call') as mock_call: + mock_call.side_effect = call_logical_port_exist + backend_details = self.driver.setup_server(self.fake_network_info) + self.assertEqual(backend_details['ip'], + self.fake_network_allocations[0]['ip_address']) + self.assertEqual( + 0, self.driver.plugin.helper.create_logical_port.call_count) + + @dec_driver_handles_share_servers + def test_setup_server_vlan_exist(self): + def call_vlan_exist(*args, **kwargs): + url = args[0] + method = args[2] + if url == "/vlan" and method == "GET": + data = """{"error":{"code":0},"data":[{ + "ID":"4", + "NAME":"fake_vlan", + "PORTID":"4", + "TAG":"2"}]}""" + else: + return self.driver.plugin.helper.do_call(*args, **kwargs) + + res_json = jsonutils.loads(data) + return res_json + + self.mock_object(self.driver.plugin.helper, "create_vlan") + with mock.patch.object(self.driver.plugin.helper, + 'call') as mock_call: + mock_call.side_effect = call_vlan_exist + backend_details = self.driver.setup_server(self.fake_network_info) + self.assertEqual(backend_details['ip'], + self.fake_network_allocations[0]['ip_address']) + self.assertEqual( + 0, self.driver.plugin.helper.create_vlan.call_count) + + def test_setup_server_invalid_ipv4(self): + netwot_info_invali_ipv4 = self.fake_network_info + netwot_info_invali_ipv4['network_allocations'][0]['ip_address'] =\ + "::1/128" + self.assertRaises(exception.InvalidInput, + self.driver._setup_server, + netwot_info_invali_ipv4) + + @dec_driver_handles_share_servers + def test_setup_server_network_type_error(self): + vxlan_netwotk_info = self.fake_network_info + vxlan_netwotk_info['network_type'] = 'vxlan' + self.assertRaises(exception.NetworkBadConfigurationException, + self.driver.setup_server, + vxlan_netwotk_info) + + @dec_driver_handles_share_servers + def test_setup_server_port_conf_miss(self): + self.recreate_fake_conf_file(logical_port='') + self.driver.plugin.configuration.manila_huawei_conf_file = ( + self.fake_conf_file) + backend_details = self.driver.setup_server(self.fake_network_info) + self.assertEqual(self.fake_network_allocations[0]['ip_address'], + backend_details['ip']) + + @dec_driver_handles_share_servers + def test_setup_server_port_offline_error(self): + self.mock_object(self.driver.plugin, + '_get_online_port', + mock.Mock(return_value=(None, None))) + self.assertRaises(exception.InvalidInput, + self.driver.setup_server, + self.fake_network_info) + self.assertTrue(self.driver.plugin._get_online_port.called) + + @dec_driver_handles_share_servers + def test_setup_server_port_not_exist(self): + self.mock_object(self.driver.plugin.helper, + 'get_port_id', + mock.Mock(return_value=None)) + self.assertRaises(exception.InvalidInput, + self.driver.setup_server, + self.fake_network_info) + self.assertTrue(self.driver.plugin.helper.get_port_id.called) + + @dec_driver_handles_share_servers + def test_setup_server_port_type_not_exist(self): + self.mock_object(self.driver.plugin, + '_get_optimal_port', + mock.Mock(return_value=('CTE0.A.H2', '8'))) + self.assertRaises(exception.InvalidInput, + self.driver.setup_server, + self.fake_network_info) + self.assertTrue(self.driver.plugin._get_optimal_port.called) + + @dec_driver_handles_share_servers + def test_setup_server_choose_eth_port(self): + self.recreate_fake_conf_file(logical_port='CTE0.A.H0;fake_bond') + self.driver.plugin.configuration.manila_huawei_conf_file = ( + self.fake_conf_file) + + self.mock_object(self.driver.plugin.helper, + 'get_all_vlan', + mock.Mock(return_value=[{'NAME': 'fake_bond.10'}])) + fake_network_info = self.fake_network_info + backend_details = self.driver.setup_server(fake_network_info) + self.assertTrue(self.driver.plugin.helper.get_all_vlan.called) + self.assertEqual(self.fake_network_allocations[0]['ip_address'], + backend_details['ip']) + + @dec_driver_handles_share_servers + def test_setup_server_choose_bond_port(self): + self.recreate_fake_conf_file(logical_port='CTE0.A.H0;fake_bond') + self.driver.plugin.configuration.manila_huawei_conf_file = ( + self.fake_conf_file) + + self.mock_object(self.driver.plugin.helper, + 'get_all_vlan', + mock.Mock(return_value=[{'NAME': 'CTE0.A.H0.10'}])) + fake_network_info = self.fake_network_info + backend_details = self.driver.setup_server(fake_network_info) + self.assertTrue(self.driver.plugin.helper.get_all_vlan.called) + self.assertEqual(self.fake_network_allocations[0]['ip_address'], + backend_details['ip']) + + @dec_driver_handles_share_servers + def test_setup_server_create_vlan_fail(self): + def call_create_vlan_fail(*args, **kwargs): + url = args[0] + method = args[2] + if url == "/vlan" and method == "POST": + data = """{"error":{"code":1}}""" + res_json = jsonutils.loads(data) + return res_json + else: + return self.driver.plugin.helper.do_call(*args, **kwargs) + + with mock.patch.object(self.driver.plugin.helper, + 'call') as mock_call: + mock_call.side_effect = call_create_vlan_fail + self.assertRaises(exception.InvalidShare, + self.driver.setup_server, + self.fake_network_info) + + @dec_driver_handles_share_servers + def test_setup_server_create_logical_port_fail(self): + def call_create_logical_port_fail(*args, **kwargs): + url = args[0] + method = args[2] + if url == "/LIF" and method == "POST": + data = """{"error":{"code":1}}""" + res_json = jsonutils.loads(data) + return res_json + else: + return self.driver.plugin.helper.do_call(*args, **kwargs) + + fake_network_info = self.fake_network_info + fake_network_info['security_services'] = [ + self.fake_active_directory, self.fake_ldap] + self.mock_object(self.driver.plugin.helper, "delete_vlan") + self.mock_object(self.driver.plugin.helper, "delete_AD_config") + self.mock_object(self.driver.plugin.helper, "delete_LDAP_config") + self.mock_object(self.driver.plugin.helper, + "get_AD_config", + mock.Mock(side_effect=[None, + {'DOMAINSTATUS': '1'}, + {'DOMAINSTATUS': '0'}])) + self.mock_object( + self.driver.plugin.helper, + "get_LDAP_config", + mock.Mock( + side_effect=[None, {'BASEDN': 'dc=huawei,dc=com'}])) + with mock.patch.object(self.driver.plugin.helper, + 'call') as mock_call: + mock_call.side_effect = call_create_logical_port_fail + self.assertRaises(exception.InvalidShare, + self.driver.setup_server, + fake_network_info) + self.assertTrue(self.driver.plugin.helper.get_AD_config.called) + self.assertTrue(self.driver.plugin.helper.get_LDAP_config.called) + self.assertEqual( + 1, self.driver.plugin.helper.delete_vlan.call_count) + self.assertEqual( + 1, self.driver.plugin.helper.delete_AD_config.call_count) + self.assertEqual( + 1, self.driver.plugin.helper.delete_LDAP_config.call_count) + + @dec_driver_handles_share_servers + def test_setup_server_with_ad_domain_success(self): + fake_network_info = self.fake_network_info + fake_network_info['security_services'] = [self.fake_active_directory] + self.mock_object(self.driver.plugin.helper, + "get_AD_config", + mock.Mock( + side_effect=[None, + {'DOMAINSTATUS': '0', + 'FULLDOMAINNAME': 'huawei.com'}, + {'DOMAINSTATUS': '1', + 'FULLDOMAINNAME': 'huawei.com'}])) + backend_details = self.driver.setup_server(fake_network_info) + self.assertEqual(self.fake_network_allocations[0]['ip_address'], + backend_details['ip']) + self.assertTrue(self.driver.plugin.helper.get_AD_config.called) + + @ddt.data( + "100.97.5.87", + "100.97.5.87,100.97.5.88", + "100.97.5.87,100.97.5.88,100.97.5.89" + ) + @dec_driver_handles_share_servers + def test_setup_server_with_ldap_domain_success(self, server_ips): + fake_network_info = self.fake_network_info + fake_network_info['security_services'] = [self.fake_ldap] + fake_network_info['security_services'][0]['server'] = server_ips + self.mock_object( + self.driver.plugin.helper, + "get_LDAP_config", + mock.Mock( + side_effect=[None, {'BASEDN': 'dc=huawei,dc=com'}])) + backend_details = self.driver.setup_server(fake_network_info) + self.assertEqual(self.fake_network_allocations[0]['ip_address'], + backend_details['ip']) + self.assertTrue(self.driver.plugin.helper.get_LDAP_config.called) + + @dec_driver_handles_share_servers + def test_setup_server_with_ldap_domain_fail(self): + server_ips = "100.97.5.87,100.97.5.88,100.97.5.89,100.97.5.86" + fake_network_info = self.fake_network_info + fake_network_info['security_services'] = [self.fake_ldap] + fake_network_info['security_services'][0]['server'] = server_ips + self.mock_object( + self.driver.plugin.helper, + "get_LDAP_config", + mock.Mock( + side_effect=[None, {'BASEDN': 'dc=huawei,dc=com'}])) + self.assertRaises(exception.InvalidInput, + self.driver.setup_server, + fake_network_info) + self.assertTrue(self.driver.plugin.helper.get_LDAP_config.called) + + @ddt.data( + {'type': 'fake_unsupport'}, + {'type': 'active_directory', + 'dns_ip': '', + 'user': '', + 'password': '', + 'domain': ''}, + {'type': 'ldap', + 'server': '', + 'domain': ''}, + ) + @dec_driver_handles_share_servers + def test_setup_server_with_security_service_invalid(self, data): + fake_network_info = self.fake_network_info + fake_network_info['security_services'] = [data] + self.assertRaises(exception.InvalidInput, + self.driver.setup_server, + fake_network_info) + + @dec_driver_handles_share_servers + def test_setup_server_with_security_service_number_invalid(self): + fake_network_info = self.fake_network_info + ss = [ + {'type': 'fake_unsupport'}, + {'type': 'active_directory', + 'dns_ip': '', + 'user': '', + 'password': '', + 'domain': ''}, + {'type': 'ldap', + 'server': '', + 'domain': ''}, + ] + fake_network_info['security_services'] = ss + self.assertRaises(exception.InvalidInput, + self.driver.setup_server, + fake_network_info) + + @dec_driver_handles_share_servers + def test_setup_server_dns_exist_error(self): + fake_network_info = self.fake_network_info + fake_network_info['security_services'] = [self.fake_active_directory] + self.mock_object(self.driver.plugin.helper, + "get_DNS_ip_address", + mock.Mock(return_value=['100.97.5.85'])) + self.assertRaises(exception.InvalidInput, + self.driver.setup_server, + fake_network_info) + self.assertTrue(self.driver.plugin.helper.get_DNS_ip_address.called) + + @dec_driver_handles_share_servers + def test_setup_server_ad_exist_error(self): + fake_network_info = self.fake_network_info + fake_network_info['security_services'] = [self.fake_active_directory] + self.mock_object(self.driver.plugin.helper, + "get_AD_config", + mock.Mock( + return_value={'DOMAINSTATUS': '1', + 'FULLDOMAINNAME': 'huawei.com'})) + self.assertRaises(exception.InvalidInput, + self.driver.setup_server, + fake_network_info) + self.assertTrue(self.driver.plugin.helper.get_AD_config.called) + + @dec_driver_handles_share_servers + def test_setup_server_ldap_exist_error(self): + fake_network_info = self.fake_network_info + fake_network_info['security_services'] = [self.fake_ldap] + self.mock_object(self.driver.plugin.helper, + "get_LDAP_config", + mock.Mock( + return_value={'LDAPSERVER': '100.97.5.87'})) + self.assertRaises(exception.InvalidInput, + self.driver.setup_server, + fake_network_info) + self.assertTrue(self.driver.plugin.helper.get_LDAP_config.called) + + @dec_driver_handles_share_servers + def test_setup_server_with_dns_fail(self): + fake_network_info = self.fake_network_info + fake_active_directory = self.fake_active_directory + ip_list = "100.97.5.5,100.97.5.6,100.97.5.7,100.97.5.8" + fake_active_directory['dns_ip'] = ip_list + fake_network_info['security_services'] = [fake_active_directory] + self.mock_object( + self.driver.plugin.helper, + "get_AD_config", + mock.Mock(side_effect=[None, {'DOMAINSTATUS': '1'}])) + self.assertRaises(exception.InvalidInput, + self.driver.setup_server, + fake_network_info) + self.assertTrue(self.driver.plugin.helper.get_AD_config.called) + + @dec_driver_handles_share_servers + def test_setup_server_with_ad_domain_fail(self): + fake_network_info = self.fake_network_info + fake_network_info['security_services'] = [self.fake_active_directory] + self.mock_object(self.driver.plugin, + '_get_wait_interval', + mock.Mock(return_value=1)) + self.mock_object(self.driver.plugin, + '_get_timeout', + mock.Mock(return_value=1)) + self.mock_object( + self.driver.plugin.helper, + "get_AD_config", + mock.Mock(side_effect=[None, + {'DOMAINSTATUS': '0', + 'FULLDOMAINNAME': 'huawei.com'}])) + self.mock_object(self.driver.plugin.helper, "set_DNS_ip_address") + self.assertRaises(exception.InvalidShare, + self.driver.setup_server, + fake_network_info) + self.assertTrue(self.driver.plugin.helper.get_AD_config.called) + self.assertTrue(self.driver.plugin._get_wait_interval.called) + self.assertTrue(self.driver.plugin._get_timeout.called) + self.assertEqual( + 2, self.driver.plugin.helper.set_DNS_ip_address.call_count) + + def test_teardown_server_success(self): + server_details = { + "logical_port_id": "1", + "vlan_id": "2", + "ad_created": "1", + "ldap_created": "1", + } + security_services = [ + self.fake_ldap, + self.fake_active_directory + ] + self.logical_port_deleted = False + self.vlan_deleted = False + self.ad_deleted = False + self.ldap_deleted = False + self.dns_deleted = False + + def fake_teardown_call(*args, **kwargs): + url = args[0] + method = args[2] + if url.startswith("/LIF"): + if method == "GET": + data = """{"error":{"code":0},"data":[{ + "ID":"1"}]}""" + elif method == "DELETE": + data = """{"error":{"code":0}}""" + self.logical_port_deleted = True + elif url.startswith("/vlan"): + if method == "GET": + data = """{"error":{"code":0},"data":[{ + "ID":"2"}]}""" + elif method == "DELETE": + data = """{"error":{"code":1073813505}}""" + self.vlan_deleted = True + elif url == "/AD_CONFIG": + if method == "PUT": + data = """{"error":{"code":0}}""" + self.ad_deleted = True + elif method == "GET": + if self.ad_deleted: + data = """{"error":{"code":0},"data":{ + "DOMAINSTATUS":"0"}}""" + else: + data = """{"error":{"code":0},"data":{ + "DOMAINSTATUS":"1", + "FULLDOMAINNAME":"huawei.com"}}""" + else: + data = """{"error":{"code":0}}""" + elif url == "/LDAP_CONFIG": + if method == "DELETE": + data = """{"error":{"code":0}}""" + self.ldap_deleted = True + elif method == "GET": + if self.ldap_deleted: + data = """{"error":{"code":0}}""" + else: + data = """{"error":{"code":0},"data":{ + "LDAPSERVER":"100.97.5.87", + "BASEDN":"dc=huawei,dc=com"}}""" + else: + data = """{"error":{"code":0}}""" + elif url == "/DNS_Server": + if method == "GET": + data = "{\"error\":{\"code\":0},\"data\":{\ + \"ADDRESS\":\"[\\\"100.97.5.5\\\",\\\"\\\"]\"}}" + elif method == "PUT": + data = """{"error":{"code":0}}""" + self.dns_deleted = True + else: + data = """{"error":{"code":0}}""" + else: + return self.driver.plugin.helper.do_call(*args, **kwargs) + + res_json = jsonutils.loads(data) + return res_json + + with mock.patch.object(self.driver.plugin.helper, + 'call') as mock_call: + mock_call.side_effect = fake_teardown_call + self.driver._teardown_server(server_details, security_services) + self.assertTrue(self.logical_port_deleted) + self.assertTrue(self.vlan_deleted) + self.assertTrue(self.ad_deleted) + self.assertTrue(self.ldap_deleted) + self.assertTrue(self.dns_deleted) + + def test_teardown_server_with_already_deleted(self): + server_details = { + "logical_port_id": "1", + "vlan_id": "2", + "ad_created": "1", + "ldap_created": "1", + } + security_services = [ + self.fake_ldap, + self.fake_active_directory + ] + + self.mock_object(self.driver.plugin.helper, + "check_logical_port_exists_by_id", + mock.Mock(return_value=False)) + self.mock_object(self.driver.plugin.helper, + "check_vlan_exists_by_id", + mock.Mock(return_value=False)) + self.mock_object(self.driver.plugin.helper, + "get_DNS_ip_address", + mock.Mock(return_value=None)) + self.mock_object(self.driver.plugin.helper, + "get_AD_domain_name", + mock.Mock(return_value=(False, None))) + self.mock_object(self.driver.plugin.helper, + "get_LDAP_domain_server", + mock.Mock(return_value=(False, None))) + + self.driver._teardown_server(server_details, security_services) + self.assertEqual(1, (self.driver.plugin.helper. + check_logical_port_exists_by_id.call_count)) + self.assertEqual(1, (self.driver.plugin.helper. + check_vlan_exists_by_id.call_count)) + self.assertEqual(1, (self.driver.plugin.helper. + get_DNS_ip_address.call_count)) + self.assertEqual(1, (self.driver.plugin.helper. + get_AD_domain_name.call_count)) + self.assertEqual(1, (self.driver.plugin.helper. + get_LDAP_domain_server.call_count)) + + def test_teardown_server_with_vlan_logical_port_deleted(self): + server_details = { + "logical_port_id": "1", + "vlan_id": "2", + } + + self.mock_object(self.driver.plugin.helper, + 'get_all_logical_port', + mock.Mock(return_value=[{'ID': '4'}])) + self.mock_object(self.driver.plugin.helper, + 'get_all_vlan', + mock.Mock(return_value=[{'ID': '4'}])) + self.driver._teardown_server(server_details, None) + self.assertEqual(1, (self.driver.plugin.helper. + get_all_logical_port.call_count)) + self.assertEqual(1, (self.driver.plugin.helper. + get_all_vlan.call_count)) + + def test_teardown_server_with_empty_detail(self): + server_details = {} + with mock.patch.object(connection.LOG, 'debug') as mock_debug: + self.driver._teardown_server(server_details, None) + mock_debug.assert_called_with('Server details are empty.') + def create_fake_conf_file(self, fake_conf_file, product_flag=True, username_flag=True, pool_node_flag=True, timeout_flag=True, wait_interval_flag=True, alloctype_value='Thick', multi_url=False, - logical_port_ip='100.115.10.68'): + logical_port='100.115.10.68'): doc = xml.dom.minidom.Document() config = doc.createElement('Config') doc.appendChild(config) @@ -1992,10 +2711,16 @@ class HuaweiShareDriverTestCase(test.TestCase): storage = doc.createElement('Storage') config.appendChild(storage) - controllerip0 = doc.createElement('LogicalPortIP') - controllerip0_text = doc.createTextNode(logical_port_ip) - controllerip0.appendChild(controllerip0_text) - storage.appendChild(controllerip0) + if self.configuration.driver_handles_share_servers: + port0 = doc.createElement('Port') + port0_text = doc.createTextNode(logical_port) + port0.appendChild(port0_text) + storage.appendChild(port0) + else: + controllerip0 = doc.createElement('LogicalPortIP') + controllerip0_text = doc.createTextNode(logical_port) + controllerip0.appendChild(controllerip0_text) + storage.appendChild(controllerip0) if product_flag: product_text = doc.createTextNode('V3') @@ -2044,14 +2769,14 @@ class HuaweiShareDriverTestCase(test.TestCase): timeout = doc.createElement('Timeout') if timeout_flag: - timeout_text = doc.createTextNode('0') + timeout_text = doc.createTextNode('60') else: timeout_text = doc.createTextNode('') timeout.appendChild(timeout_text) waitinterval = doc.createElement('WaitInterval') if wait_interval_flag: - waitinterval_text = doc.createTextNode('0') + waitinterval_text = doc.createTextNode('3') else: waitinterval_text = doc.createTextNode('') waitinterval.appendChild(waitinterval_text) @@ -2079,7 +2804,7 @@ class HuaweiShareDriverTestCase(test.TestCase): wait_interval_flag=True, alloctype_value='Thick', multi_url=False, - logical_port_ip='100.115.10.68'): + logical_port='100.115.10.68'): self.tmp_dir = tempfile.mkdtemp() self.fake_conf_file = self.tmp_dir + '/manila_huawei_conf.xml' self.addCleanup(shutil.rmtree, self.tmp_dir) @@ -2087,5 +2812,5 @@ class HuaweiShareDriverTestCase(test.TestCase): username_flag, pool_node_flag, timeout_flag, wait_interval_flag, alloctype_value, multi_url, - logical_port_ip) + logical_port) self.addCleanup(os.remove, self.fake_conf_file)