Merge "Huawei: Add share server support"

This commit is contained in:
Jenkins 2016-01-18 00:04:12 +00:00 committed by Gerrit Code Review
commit a22f4e94be
8 changed files with 1660 additions and 160 deletions

View File

@ -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:
<Storage>
<Product>V3</Product>
<LogicalPortIP>x.x.x.x</LogicalPortIP>
<Port>abc;CTE0.A.H1</Port>
<RestURL>https://x.x.x.x:8088/deviceManager/rest/;
https://x.x.x.x:8088/deviceManager/rest/</RestURL>
<UserName>xxxxxxxxx</UserName>
@ -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
<Port>(separated by ";"). If <Port> 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 <RestURL>(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 <Port> 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
<LogicalPortIP>. 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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -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 | \- | \- | \- |
+----------------------------------------+------------------+-----------------+------------------+

View File

@ -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."""

View File

@ -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"

View File

@ -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)

View File

@ -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()

View File

@ -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

File diff suppressed because it is too large Load Diff