Merge "Huawei: Add share server support"
This commit is contained in:
commit
a22f4e94be
@ -40,7 +40,7 @@ The following operations is supported on V3 storage:
|
|||||||
- Delete CIFS/NFS Share
|
- Delete CIFS/NFS Share
|
||||||
- Allow CIFS/NFS Share access
|
- 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).
|
* Only USER access type is supported for CIFS(ro/rw).
|
||||||
- Deny CIFS/NFS Share access
|
- Deny CIFS/NFS Share access
|
||||||
- Create snapshot
|
- Create snapshot
|
||||||
@ -70,6 +70,7 @@ storage systems, the driver configuration file is as follows:
|
|||||||
<Storage>
|
<Storage>
|
||||||
<Product>V3</Product>
|
<Product>V3</Product>
|
||||||
<LogicalPortIP>x.x.x.x</LogicalPortIP>
|
<LogicalPortIP>x.x.x.x</LogicalPortIP>
|
||||||
|
<Port>abc;CTE0.A.H1</Port>
|
||||||
<RestURL>https://x.x.x.x:8088/deviceManager/rest/;
|
<RestURL>https://x.x.x.x:8088/deviceManager/rest/;
|
||||||
https://x.x.x.x:8088/deviceManager/rest/</RestURL>
|
https://x.x.x.x:8088/deviceManager/rest/</RestURL>
|
||||||
<UserName>xxxxxxxxx</UserName>
|
<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`.
|
- `Product` is a type of a storage product. Set it to `V3`.
|
||||||
- `LogicalPortIP` is an IP address of the logical port.
|
- `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
|
- `RestURL` is an access address of the REST interface. Multi RestURLs
|
||||||
can be configured in <RestURL>(separated by ";"). When one of the RestURL
|
can be configured in <RestURL>(separated by ";"). When one of the RestURL
|
||||||
failed to connect, driver will retry another automatically.
|
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
|
- `share_driver` = manila.share.drivers.huawei.huawei_nas.HuaweiNasDriver
|
||||||
- `manila_huawei_conf_file` = /etc/manila/manila_huawei_conf.xml
|
- `manila_huawei_conf_file` = /etc/manila/manila_huawei_conf.xml
|
||||||
- `driver_handles_share_servers` = False
|
- `driver_handles_share_servers` = True or False
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
As far as Manila requires `share type` for creation of shares, make sure that
|
- If `driver_handles_share_servers` is True, the driver will choose a port
|
||||||
used `share type` has extra spec `driver_handles_share_servers` set to `False`
|
in <Port> to create vlan and logical port for each tenant network.
|
||||||
otherwise Huawei backend will be filtered by `manila-scheduler`.
|
And the share type with the DHSS extra spec should be set to True when
|
||||||
If you do not provide `share type` with share creation request then default
|
creating shares.
|
||||||
`share type` and its extra specs will be used.
|
- 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
|
Restart of manila-share service is needed for the configuration changes to take
|
||||||
effect.
|
effect.
|
||||||
@ -195,10 +202,14 @@ Restrictions
|
|||||||
|
|
||||||
The Huawei driver has the following 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 USER access type is supported for CIFS.
|
||||||
|
|
||||||
|
- Only AD domain is supported for CIFS.
|
||||||
|
|
||||||
The :mod:`manila.share.drivers.huawei.huawei_nas` Module
|
The :mod:`manila.share.drivers.huawei.huawei_nas` Module
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ Mapping of share drivers and share features support
|
|||||||
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+
|
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+
|
||||||
| HPE 3PAR | DHSS = True (L) & False (K) | \- | \- | \- | K | K |
|
| 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 |
|
| 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
|
Mapping of share drivers and share access rules support
|
||||||
-------------------------------------------------------
|
-------------------------------------------------------
|
||||||
|
|
||||||
+----------------------------------------+----------------------------------------+----------------------------------------+
|
+----------------------------------------+--------------------------------------------+--------------------------------------------+
|
||||||
| | Read & Write | Read Only |
|
| | Read & Write | Read Only |
|
||||||
+ Driver name +--------------+------------+------------+--------------+------------+------------+
|
+ Driver name +--------------+----------------+------------+--------------+----------------+------------+
|
||||||
| | IP | USER | Cert | IP | USER | Cert |
|
| | IP | USER | Cert | IP | USER | Cert |
|
||||||
+========================================+==============+============+============+==============+============+============+
|
+========================================+==============+================+============+==============+================+============+
|
||||||
| Generic (Cinder as back-end) | NFS,CIFS (J) | \- | \- | NFS (K) | \- | \- |
|
| Generic (Cinder as back-end) | NFS,CIFS (J) | \- | \- | NFS (K) | \- | \- |
|
||||||
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
|
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||||
| NetApp Clustered Data ONTAP | NFS (J) | CIFS (J) | \- | NFS (K) | CIFS (M) | \- |
|
| NetApp Clustered Data ONTAP | NFS (J) | CIFS (J) | \- | NFS (K) | CIFS (M) | \- |
|
||||||
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
|
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||||
| EMC VNX | NFS (J) | CIFS (J) | \- | NFS (L) | CIFS (L) | \- |
|
| EMC VNX | NFS (J) | CIFS (J) | \- | NFS (L) | CIFS (L) | \- |
|
||||||
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
|
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||||
| EMC Isilon | NFS,CIFS (K) | \- | \- | \- | \- | \- |
|
| EMC Isilon | NFS,CIFS (K) | \- | \- | \- | \- | \- |
|
||||||
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
|
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||||
| Red Hat GlusterFS | NFS (J) | \- | \- | \- | \- | \- |
|
| Red Hat GlusterFS | NFS (J) | \- | \- | \- | \- | \- |
|
||||||
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
|
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||||
| Red Hat GlusterFS-Native | \- | \- | J | \- | \- | \- |
|
| Red Hat GlusterFS-Native | \- | \- | J | \- | \- | \- |
|
||||||
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
|
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||||
| HDFS | \- | HDFS(K) | \- | \- | HDFS(K) | \- |
|
| HDFS | \- | HDFS(K) | \- | \- | HDFS(K) | \- |
|
||||||
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
|
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||||
| Hitachi HNAS | NFS (L) | \- | \- | NFS (L) | \- | \- |
|
| Hitachi HNAS | NFS (L) | \- | \- | NFS (L) | \- | \- |
|
||||||
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
|
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||||
| HPE 3PAR | NFS,CIFS (K) | CIFS (K) | \- | \- | \- | \- |
|
| HPE 3PAR | NFS,CIFS (K) | CIFS (K) | \- | \- | \- | \- |
|
||||||
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
|
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||||
| Huawei | NFS (K) | CIFS (K) | \- | NFS (K) | CIFS (K) | \- |
|
| Huawei | NFS (K) |NFS (M),CIFS (K)| \- | NFS (K) |NFS (M),CIFS (K)| \- |
|
||||||
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
|
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||||
| Quobyte | NFS (K) | \- | \- | NFS (K) | \- | \- |
|
| Quobyte | NFS (K) | \- | \- | NFS (K) | \- | \- |
|
||||||
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
|
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||||
| Windows SMB | \- | CIFS (L) | \- | \- | CIFS (L) | \- |
|
| Windows SMB | \- | CIFS (L) | \- | \- | CIFS (L) | \- |
|
||||||
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
|
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||||
| IBM GPFS | NFS (K) | \- | \- | NFS (K) | \- | \- |
|
| IBM GPFS | NFS (K) | \- | \- | NFS (K) | \- | \- |
|
||||||
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
|
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||||
| ZFS | ? | ? | ? | ? | ? | ? |
|
| ZFS | ? | ? | ? | ? | ? | ? |
|
||||||
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
|
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
|
||||||
|
|
||||||
Mapping of share drivers and security services support
|
Mapping of share drivers and security services support
|
||||||
------------------------------------------------------
|
------------------------------------------------------
|
||||||
@ -127,7 +127,7 @@ Mapping of share drivers and security services support
|
|||||||
+----------------------------------------+------------------+-----------------+------------------+
|
+----------------------------------------+------------------+-----------------+------------------+
|
||||||
| HPE 3PAR | \- | \- | \- |
|
| HPE 3PAR | \- | \- | \- |
|
||||||
+----------------------------------------+------------------+-----------------+------------------+
|
+----------------------------------------+------------------+-----------------+------------------+
|
||||||
| Huawei | \- | \- | \- |
|
| Huawei | M | M | \- |
|
||||||
+----------------------------------------+------------------+-----------------+------------------+
|
+----------------------------------------+------------------+-----------------+------------------+
|
||||||
| Quobyte | \- | \- | \- |
|
| Quobyte | \- | \- | \- |
|
||||||
+----------------------------------------+------------------+-----------------+------------------+
|
+----------------------------------------+------------------+-----------------+------------------+
|
||||||
|
@ -73,3 +73,11 @@ class HuaweiBase(object):
|
|||||||
|
|
||||||
def update_share_stats(self, stats_dict):
|
def update_share_stats(self, stats_dict):
|
||||||
"""Retrieve stats info from share group."""
|
"""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."""
|
||||||
|
@ -12,16 +12,22 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
STATUS_ETH_RUNNING = "10"
|
||||||
STATUS_FS_HEALTH = "1"
|
STATUS_FS_HEALTH = "1"
|
||||||
STATUS_FS_RUNNING = "27"
|
STATUS_FS_RUNNING = "27"
|
||||||
|
STATUS_JOIN_DOMAIN = '1'
|
||||||
|
STATUS_EXIT_DOMAIN = '0'
|
||||||
STATUS_SERVICE_RUNNING = "2"
|
STATUS_SERVICE_RUNNING = "2"
|
||||||
DEFAULT_WAIT_INTERVAL = 3
|
DEFAULT_WAIT_INTERVAL = 3
|
||||||
DEFAULT_TIMEOUT = 60
|
DEFAULT_TIMEOUT = 60
|
||||||
|
|
||||||
MSG_SNAPSHOT_NOT_FOUND = 1073754118
|
MSG_SNAPSHOT_NOT_FOUND = 1073754118
|
||||||
IP_ALLOCATIONS = 0
|
IP_ALLOCATIONS_DHSS_FALSE = 0
|
||||||
|
IP_ALLOCATIONS_DHSS_TRUE = 1
|
||||||
SOCKET_TIMEOUT = 52
|
SOCKET_TIMEOUT = 52
|
||||||
LOGIN_SOCKET_TIMEOUT = 4
|
LOGIN_SOCKET_TIMEOUT = 4
|
||||||
|
SYSTEM_NAME_PREFIX = "Array-"
|
||||||
|
|
||||||
ACCESS_NFS_RW = "1"
|
ACCESS_NFS_RW = "1"
|
||||||
ACCESS_NFS_RO = "0"
|
ACCESS_NFS_RO = "0"
|
||||||
@ -30,6 +36,12 @@ ACCESS_CIFS_RO = "0"
|
|||||||
|
|
||||||
ERROR_CONNECT_TO_SERVER = -403
|
ERROR_CONNECT_TO_SERVER = -403
|
||||||
ERROR_UNAUTHORIZED_TO_SERVER = -401
|
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_THIN_FLAG = "1"
|
||||||
ALLOC_TYPE_THICK_FLAG = "0"
|
ALLOC_TYPE_THICK_FLAG = "0"
|
||||||
|
@ -52,12 +52,13 @@ class HuaweiNasDriver(driver.ShareDriver):
|
|||||||
Add share level(ro).
|
Add share level(ro).
|
||||||
Add smartx capabilities.
|
Add smartx capabilities.
|
||||||
Support multi pools in one backend.
|
Support multi pools in one backend.
|
||||||
|
1.2 - Add share server support.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Do initialization."""
|
"""Do initialization."""
|
||||||
LOG.debug("Enter into init function.")
|
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)
|
self.configuration = kwargs.get('configuration', None)
|
||||||
if self.configuration:
|
if self.configuration:
|
||||||
self.configuration.append_config_values(huawei_opts)
|
self.configuration.append_config_values(huawei_opts)
|
||||||
@ -169,10 +170,18 @@ class HuaweiNasDriver(driver.ShareDriver):
|
|||||||
data = dict(
|
data = dict(
|
||||||
share_backend_name=backend_name or 'HUAWEI_NAS_Driver',
|
share_backend_name=backend_name or 'HUAWEI_NAS_Driver',
|
||||||
vendor_name='Huawei',
|
vendor_name='Huawei',
|
||||||
driver_version='1.1',
|
driver_version='1.2',
|
||||||
storage_protocol='NFS_CIFS',
|
storage_protocol='NFS_CIFS',
|
||||||
total_capacity_gb=0.0,
|
total_capacity_gb=0.0,
|
||||||
free_capacity_gb=0.0)
|
free_capacity_gb=0.0)
|
||||||
|
|
||||||
self.plugin.update_share_stats(data)
|
self.plugin.update_share_stats(data)
|
||||||
super(HuaweiNasDriver, self)._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)
|
||||||
|
@ -13,9 +13,12 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import random
|
||||||
|
import string
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
from oslo_serialization import jsonutils
|
||||||
from oslo_utils import strutils
|
from oslo_utils import strutils
|
||||||
from oslo_utils import units
|
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.drivers.huawei.v3 import smartx
|
||||||
from manila.share import share_types
|
from manila.share import share_types
|
||||||
from manila.share import utils as share_utils
|
from manila.share import utils as share_utils
|
||||||
|
from manila import utils
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
@ -108,9 +111,20 @@ class V3StorageConnection(driver.HuaweiBase):
|
|||||||
reason=(_('Failed to create share %(name)s. Reason: %(err)s.')
|
reason=(_('Failed to create share %(name)s. Reason: %(err)s.')
|
||||||
% {'name': share_name, 'err': err}))
|
% {'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
|
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):
|
def extend_share(self, share, new_size, share_server):
|
||||||
share_proto = share['share_proto']
|
share_proto = share['share_proto']
|
||||||
share_name = share['name']
|
share_name = share['name']
|
||||||
@ -296,7 +310,10 @@ class V3StorageConnection(driver.HuaweiBase):
|
|||||||
|
|
||||||
def get_network_allocations_number(self):
|
def get_network_allocations_number(self):
|
||||||
"""Get number of network interfaces to be created."""
|
"""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):
|
def _get_capacity(self, pool_name, result):
|
||||||
"""Get free capacity and total capacity of the pools."""
|
"""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_url_type = self.helper._get_share_url_type(share_proto)
|
||||||
share_client_type = self.helper._get_share_client_type(share_proto)
|
share_client_type = self.helper._get_share_client_type(share_proto)
|
||||||
access_type = access['access_type']
|
access_type = access['access_type']
|
||||||
if share_proto == 'NFS' and access_type != 'ip':
|
if share_proto == 'NFS' and access_type not in ('ip', 'user'):
|
||||||
LOG.warning(_LW('Only IP access type is allowed for NFS shares.'))
|
LOG.warning(_LW('Only IP or USER access types are allowed for '
|
||||||
|
'NFS shares.'))
|
||||||
return
|
return
|
||||||
elif share_proto == 'CIFS' and access_type != 'user':
|
elif share_proto == 'CIFS' and access_type != 'user':
|
||||||
LOG.warning(_LW('Only USER access type is allowed for'
|
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)
|
share_url_type = self.helper._get_share_url_type(share_proto)
|
||||||
access_type = access['access_type']
|
access_type = access['access_type']
|
||||||
access_level = access['access_level']
|
access_level = access['access_level']
|
||||||
|
access_to = access['access_to']
|
||||||
|
|
||||||
if access_level not in common_constants.ACCESS_LEVELS:
|
if access_level not in common_constants.ACCESS_LEVELS:
|
||||||
raise exception.InvalidShareAccess(
|
raise exception.InvalidShareAccess(
|
||||||
@ -411,14 +430,18 @@ class V3StorageConnection(driver.HuaweiBase):
|
|||||||
access_level))
|
access_level))
|
||||||
|
|
||||||
if share_proto == 'NFS':
|
if share_proto == 'NFS':
|
||||||
if access_type == 'ip':
|
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:
|
if access_level == common_constants.ACCESS_LEVEL_RW:
|
||||||
access_level = constants.ACCESS_NFS_RW
|
access_level = constants.ACCESS_NFS_RW
|
||||||
else:
|
else:
|
||||||
access_level = constants.ACCESS_NFS_RO
|
access_level = constants.ACCESS_NFS_RO
|
||||||
else:
|
|
||||||
message = _('Only IP access type is allowed for NFS shares.')
|
|
||||||
raise exception.InvalidShareAccess(reason=message)
|
|
||||||
elif share_proto == 'CIFS':
|
elif share_proto == 'CIFS':
|
||||||
if access_type == 'user':
|
if access_type == 'user':
|
||||||
if access_level == common_constants.ACCESS_LEVEL_RW:
|
if access_level == common_constants.ACCESS_LEVEL_RW:
|
||||||
@ -438,7 +461,6 @@ class V3StorageConnection(driver.HuaweiBase):
|
|||||||
raise exception.InvalidShareAccess(reason=err_msg)
|
raise exception.InvalidShareAccess(reason=err_msg)
|
||||||
|
|
||||||
share_id = share['ID']
|
share_id = share['ID']
|
||||||
access_to = access['access_to']
|
|
||||||
self.helper._allow_access_rest(share_id, access_to,
|
self.helper._allow_access_rest(share_id, access_to,
|
||||||
share_proto, access_level)
|
share_proto, access_level)
|
||||||
|
|
||||||
@ -723,17 +745,15 @@ class V3StorageConnection(driver.HuaweiBase):
|
|||||||
"new_compression": new_compression})
|
"new_compression": new_compression})
|
||||||
LOG.info(msg)
|
LOG.info(msg)
|
||||||
|
|
||||||
def _get_location_path(self, share_name, share_proto):
|
def _get_location_path(self, share_name, share_proto, ip=None):
|
||||||
root = self.helper._read_xml()
|
|
||||||
target_ip = root.findtext('Storage/LogicalPortIP').strip()
|
|
||||||
|
|
||||||
location = None
|
location = None
|
||||||
|
if ip is None:
|
||||||
|
root = self.helper._read_xml()
|
||||||
|
ip = root.findtext('Storage/LogicalPortIP').strip()
|
||||||
if share_proto == 'NFS':
|
if share_proto == 'NFS':
|
||||||
location = '%s:/%s' % (target_ip,
|
location = '%s:/%s' % (ip, share_name.replace("-", "_"))
|
||||||
share_name.replace("-", "_"))
|
|
||||||
elif share_proto == 'CIFS':
|
elif share_proto == 'CIFS':
|
||||||
location = '\\\\%s\\%s' % (target_ip,
|
location = '\\\\%s\\%s' % (ip, share_name.replace("-", "_"))
|
||||||
share_name.replace("-", "_"))
|
|
||||||
else:
|
else:
|
||||||
raise exception.InvalidShareAccess(
|
raise exception.InvalidShareAccess(
|
||||||
reason=(_('Invalid NAS protocol supplied: %s.')
|
reason=(_('Invalid NAS protocol supplied: %s.')
|
||||||
@ -775,6 +795,7 @@ class V3StorageConnection(driver.HuaweiBase):
|
|||||||
pwd = root.findtext('Storage/UserPassword')
|
pwd = root.findtext('Storage/UserPassword')
|
||||||
product = root.findtext('Storage/Product')
|
product = root.findtext('Storage/Product')
|
||||||
pool_node = root.findtext('Filesystem/StoragePool')
|
pool_node = root.findtext('Filesystem/StoragePool')
|
||||||
|
logical_port_ip = root.findtext('Storage/LogicalPortIP')
|
||||||
|
|
||||||
if product != "V3":
|
if product != "V3":
|
||||||
err_msg = (_(
|
err_msg = (_(
|
||||||
@ -797,6 +818,14 @@ class V3StorageConnection(driver.HuaweiBase):
|
|||||||
LOG.error(err_msg)
|
LOG.error(err_msg)
|
||||||
raise exception.InvalidInput(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):
|
def check_service(self):
|
||||||
running_status = self.helper._get_cifs_service_status()
|
running_status = self.helper._get_cifs_service_status()
|
||||||
if running_status != constants.STATUS_SERVICE_RUNNING:
|
if running_status != constants.STATUS_SERVICE_RUNNING:
|
||||||
@ -807,3 +836,381 @@ class V3StorageConnection(driver.HuaweiBase):
|
|||||||
(service['SUPPORTV3'] == 'false') or
|
(service['SUPPORTV3'] == 'false') or
|
||||||
(service['SUPPORTV4'] == 'false')):
|
(service['SUPPORTV4'] == 'false')):
|
||||||
self.helper._start_nfs_service_status()
|
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()
|
@ -25,6 +25,7 @@ from six.moves.urllib import request as urlreq # pylint: disable=E0611
|
|||||||
from manila import exception
|
from manila import exception
|
||||||
from manila.i18n import _
|
from manila.i18n import _
|
||||||
from manila.i18n import _LE
|
from manila.i18n import _LE
|
||||||
|
from manila.i18n import _LW
|
||||||
from manila.share.drivers.huawei import constants
|
from manila.share.drivers.huawei import constants
|
||||||
from manila import utils
|
from manila import utils
|
||||||
|
|
||||||
@ -419,17 +420,23 @@ class RestHelper(object):
|
|||||||
self._assert_rest_result(result, 'Get access id by share error!')
|
self._assert_rest_result(result, 'Get access id by share error!')
|
||||||
|
|
||||||
for item in result.get('data', []):
|
for item in result.get('data', []):
|
||||||
if access_to == item['NAME']:
|
if item['NAME'] in (access_to, '@' + access_to):
|
||||||
return item['ID']
|
return item['ID']
|
||||||
|
|
||||||
def _allow_access_rest(self, share_id, access_to,
|
def _allow_access_rest(self, share_id, access_to,
|
||||||
share_proto, access_level):
|
share_proto, access_level):
|
||||||
"""Allow access to the share."""
|
"""Allow access to the share."""
|
||||||
access_type = self._get_share_client_type(share_proto)
|
if share_proto == 'NFS':
|
||||||
url = "/" + access_type
|
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 = {}
|
def _allow_nfs_access_rest(self, share_id, access_to, access_level):
|
||||||
if access_type == "NFS_SHARE_AUTH_CLIENT":
|
url = "/NFS_SHARE_AUTH_CLIENT"
|
||||||
access = {
|
access = {
|
||||||
"TYPE": "16409",
|
"TYPE": "16409",
|
||||||
"NAME": access_to,
|
"NAME": access_to,
|
||||||
@ -439,19 +446,67 @@ class RestHelper(object):
|
|||||||
"ALLSQUASH": "1",
|
"ALLSQUASH": "1",
|
||||||
"ROOTSQUASH": "0",
|
"ROOTSQUASH": "0",
|
||||||
}
|
}
|
||||||
elif access_type == "CIFS_SHARE_AUTH_CLIENT":
|
|
||||||
access = {
|
|
||||||
"NAME": access_to,
|
|
||||||
"PARENTID": share_id,
|
|
||||||
"PERMISSION": access_level,
|
|
||||||
"DOMAINTYPE": "2",
|
|
||||||
}
|
|
||||||
data = jsonutils.dumps(access)
|
data = jsonutils.dumps(access)
|
||||||
result = self.call(url, data, "POST")
|
result = self.call(url, data, "POST")
|
||||||
|
|
||||||
msg = 'Allow access error.'
|
msg = 'Allow access error.'
|
||||||
self._assert_rest_result(result, msg)
|
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):
|
def _get_share_client_type(self, share_proto):
|
||||||
share_client_type = None
|
share_client_type = None
|
||||||
if share_proto == 'NFS':
|
if share_proto == 'NFS':
|
||||||
@ -766,3 +821,276 @@ class RestHelper(object):
|
|||||||
|
|
||||||
self._assert_rest_result(result,
|
self._assert_rest_result(result,
|
||||||
_('Remove filesystem from cache error.'))
|
_('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
Loading…
Reference in New Issue
Block a user