Merge "Adding Zadara Manila driver"
This commit is contained in:
commit
8523d323e7
@ -97,3 +97,4 @@ each back end.
|
|||||||
tegile_driver
|
tegile_driver
|
||||||
nexentastor5_driver
|
nexentastor5_driver
|
||||||
../configuration/shared-file-systems/drivers/windows-smb-driver
|
../configuration/shared-file-systems/drivers/windows-smb-driver
|
||||||
|
zadara_driver
|
||||||
|
132
doc/source/admin/zadara_driver.rst
Normal file
132
doc/source/admin/zadara_driver.rst
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
..
|
||||||
|
Copyright (c) 2021 Zadara Inc.
|
||||||
|
All Rights Reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
not use this file except in compliance with the License. You may obtain
|
||||||
|
a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
License for the specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
|
||||||
|
=======================================
|
||||||
|
Zadara VPSA Driver for OpenStack Manila
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
`Zadara’s <https://www.zadara.com>`__ Virtual Private Storage Array (VPSA)
|
||||||
|
is the first software defined, Enterprise-Storage-as-a-Service. It is an
|
||||||
|
elastic and private block and file storage system which provides
|
||||||
|
enterprise-grade data protection and data management storage services.
|
||||||
|
|
||||||
|
Manila VPSA driver provides a seamless management capabilities for VPSA
|
||||||
|
volumes, in this case, NFS & SMB volumes without losing the added value
|
||||||
|
provided by the VPSA Storage Array/Flash-Array.
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
|
||||||
|
- VPSA Storage Array/Flash-Array running version 20.12 or higher.
|
||||||
|
|
||||||
|
- Networking preparation - the Zadara VPSA driver for Manila support DHSS=False
|
||||||
|
(driver_handles_share_servers), the driver does not handle
|
||||||
|
the network configuration, it is up to the administrator to ensure
|
||||||
|
connectivity from a manila-share node and the Openstack cloud to the
|
||||||
|
VPSA Front-End network (such as neutron flat/VLAN network).
|
||||||
|
|
||||||
|
Supported shared filesystems and operations
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
Share file system supported
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- SMB (CIFS)
|
||||||
|
- NFS
|
||||||
|
|
||||||
|
Supported operations
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The following operations are supported:
|
||||||
|
|
||||||
|
- Create a share.
|
||||||
|
- Delete a share.
|
||||||
|
- Extend a share.
|
||||||
|
- Create a snapshot.
|
||||||
|
- Delete a snapshot.
|
||||||
|
- Create a share from snapshot.
|
||||||
|
- Allow share access.
|
||||||
|
- Manage a share.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
- Only IP access type is supported
|
||||||
|
- Both RW and RO access levels supported
|
||||||
|
|
||||||
|
|
||||||
|
Backend Configuration
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The following parameters need to be configured in the [DEFAULT] section of
|
||||||
|
manila configuration (/etc/manila/manila.conf):
|
||||||
|
|
||||||
|
- `enabled_share_backends` = Name of the section on manila.conf used to specify
|
||||||
|
a backend i.e. *enabled_share_backends = zadaravpsa*
|
||||||
|
|
||||||
|
- `enabled_share_protocols` - Specify a list of protocols to be allowed for
|
||||||
|
share creation. The VPSA driver support the following options: *NFS* or
|
||||||
|
*CIFS* or *NFS, CIFS*
|
||||||
|
|
||||||
|
The following parameters need to be configured in the [backend] section of
|
||||||
|
manila configuration (/etc/manila/manila.conf):
|
||||||
|
|
||||||
|
Driver options
|
||||||
|
--------------
|
||||||
|
|
||||||
|
- `zadara_vpsa_host` = <VPSA - Management Host name or IP address>
|
||||||
|
- `zadara_vpsa_port` = <VPSA - Port number>
|
||||||
|
- `zadara_vpsa_use_ssl` = <VPSA - Use SSL connection (default=False)
|
||||||
|
- `zadara_ssl_cert_verify` = <If set to True the http client will validate
|
||||||
|
the SSL certificate of the VPSA endpoint (default=True)>
|
||||||
|
- `zadara_driver_ssl_cert_path` = <Can be used to specify a non default path
|
||||||
|
to a CA_BUNDLE file or directory with certificates of trusted CAs
|
||||||
|
(default=None)
|
||||||
|
- `zadara_access_key` - <VPSA access key>
|
||||||
|
- `zadara_vpsa_poolname` - <VPSA - Storage Pool assigned for volumes>
|
||||||
|
- `zadara_vol_encrypt` = <VPSA - Default encryption policy for volumes
|
||||||
|
(default = True)
|
||||||
|
- `zadara_gen3_vol_dedupe` = <VPSA - Default encryption policy for volumes
|
||||||
|
(default = True)>
|
||||||
|
- `zadara_gen3_vol_compress` = <VPSA - Enable compression for volumes
|
||||||
|
(default=False)>
|
||||||
|
- `zadara_share_name_template` = <VPSA - Default template for VPSA share names
|
||||||
|
(default=‘OS_share-%s’>
|
||||||
|
- `zadara_share_snap_name_template` = <VPSA - Default template for VPSA share
|
||||||
|
snapshot names (default=‘OS_share-snapshot-%s’)
|
||||||
|
- `zadara_default_snap_policy` = <VPSA - Attach snapshot policy for volumes
|
||||||
|
(default=False)>
|
||||||
|
- `driver_handles_share_servers` = <DHSS, driver working mode (must be set
|
||||||
|
to False)>
|
||||||
|
- `share_driver` = manila.share.drivers.zadara.zadara.ZadaraVPSAShareDriver
|
||||||
|
|
||||||
|
Back-end configuration example
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[DEFAULT]
|
||||||
|
enabled_share_backends = zadaravpsa
|
||||||
|
enabled_share_protocols = NFS,CIFS
|
||||||
|
|
||||||
|
[zadaravpsa]
|
||||||
|
driver_handles_share_servers = False
|
||||||
|
zadara_vpsa_host = vsa-00000010-mycloud.zadaravpsa.com
|
||||||
|
zadara_vpsa_port = 443
|
||||||
|
zadara_access_key = MYSUPERSECRETACCESSKEY
|
||||||
|
zadara_vpsa_poolname = pool-00010001
|
||||||
|
share_backend_name = zadaravpsa
|
||||||
|
zadara_vpsa_use_ssl = true
|
||||||
|
share_driver = manila.share.drivers.zadara.zadara.ZadaraVPSAShareDriver
|
@ -1013,3 +1013,72 @@ class InfortrendCLIException(ShareBackendException):
|
|||||||
|
|
||||||
class InfortrendNASException(ShareBackendException):
|
class InfortrendNASException(ShareBackendException):
|
||||||
message = _("Infortrend NAS exception: %(err)s")
|
message = _("Infortrend NAS exception: %(err)s")
|
||||||
|
|
||||||
|
|
||||||
|
# Zadara storage driver
|
||||||
|
class ZadaraUnknownCmd(ShareBackendException):
|
||||||
|
message = _("Unknown or unsupported command %(cmd)s")
|
||||||
|
|
||||||
|
|
||||||
|
class ZadaraSessionRequestException(ShareBackendException):
|
||||||
|
message = _("%(msg)s")
|
||||||
|
|
||||||
|
|
||||||
|
class ZadaraBadHTTPResponseStatus(ShareBackendException):
|
||||||
|
message = _("Bad HTTP response status %(status)s")
|
||||||
|
|
||||||
|
|
||||||
|
class ZadaraFailedCmdWithDump(ShareBackendException):
|
||||||
|
message = _("Operation failed with status=%(status)s. Full dump: %(data)s")
|
||||||
|
|
||||||
|
|
||||||
|
class ZadaraVPSANoActiveController(ShareBackendException):
|
||||||
|
message = _("Unable to find any active VPSA controller")
|
||||||
|
|
||||||
|
|
||||||
|
class ZadaraServerCreateFailure(ShareBackendException):
|
||||||
|
message = _("Unable to create server object for initiator %(name)s")
|
||||||
|
|
||||||
|
|
||||||
|
class ZadaraAttachmentsNotFound(ShareBackendException):
|
||||||
|
message = _("Failed to retrieve attachments for volume %(name)s")
|
||||||
|
|
||||||
|
|
||||||
|
class ZadaraManilaInvalidAccessKey(ShareBackendException):
|
||||||
|
message = _("Invalid VPSA access key")
|
||||||
|
|
||||||
|
|
||||||
|
class ZadaraVPSAVolumeShareFailed(ShareBackendException):
|
||||||
|
message = _("Failed to create VPSA backend share. Error: %(error)s")
|
||||||
|
|
||||||
|
|
||||||
|
class ZadaraInvalidShareAccessType(ShareBackendException):
|
||||||
|
message = _("Only ip access type allowed for the Zadara manila share.")
|
||||||
|
|
||||||
|
|
||||||
|
class ZadaraShareNotFound(ShareBackendException):
|
||||||
|
message = _("Share %(name)s could not be found.")
|
||||||
|
|
||||||
|
|
||||||
|
class ZadaraExtendShareFailed(ShareBackendException):
|
||||||
|
message = _("Failed to extend VPSA backend share. Error: %(error)s")
|
||||||
|
|
||||||
|
|
||||||
|
class ZadaraInvalidProtocol(ShareBackendException):
|
||||||
|
message = _("The type of protocol %(protocol_type)s for Zadara "
|
||||||
|
"manila driver is not supported. Only NFS or CIFS "
|
||||||
|
"protocol is supported.")
|
||||||
|
|
||||||
|
|
||||||
|
class ZadaraShareNotValid(ShareBackendException):
|
||||||
|
message = _("Share %(name)s is not valid.")
|
||||||
|
|
||||||
|
|
||||||
|
class ZadaraVPSASnapshotCreateFailed(ShareBackendException):
|
||||||
|
message = _("Failed to create VPSA share %(name)s snapshot. "
|
||||||
|
"Error: %(error)s")
|
||||||
|
|
||||||
|
|
||||||
|
class ZadaraVPSASnapshotManageFailed(ShareBackendException):
|
||||||
|
message = _("Failed to manage VPSA share snapshot with id %(snap_id)s. "
|
||||||
|
"Error: %(error)s")
|
||||||
|
0
manila/share/drivers/zadara/__init__.py
Normal file
0
manila/share/drivers/zadara/__init__.py
Normal file
496
manila/share/drivers/zadara/common.py
Normal file
496
manila/share/drivers/zadara/common.py
Normal file
@ -0,0 +1,496 @@
|
|||||||
|
# Copyright (c) 2020 Zadara Storage, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
import requests
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Number of seconds the repsonse for the request sent to
|
||||||
|
# vpsa is expected. Else the request will be timed out.
|
||||||
|
# Setting it to 300 seconds initially.
|
||||||
|
vpsa_timeout = 300
|
||||||
|
|
||||||
|
|
||||||
|
# Common exception class for all the exceptions that
|
||||||
|
# are used to redirect to the driver specific exceptions.
|
||||||
|
class CommonException(Exception):
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class UnknownCmd(Exception):
|
||||||
|
def __init__(self, cmd):
|
||||||
|
self.cmd = cmd
|
||||||
|
|
||||||
|
class BadHTTPResponseStatus(Exception):
|
||||||
|
def __init__(self, status):
|
||||||
|
self.status = status
|
||||||
|
|
||||||
|
class FailedCmdWithDump(Exception):
|
||||||
|
def __init__(self, status, data):
|
||||||
|
self.status = status
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
class SessionRequestException(Exception):
|
||||||
|
def __init__(self, msg):
|
||||||
|
self.msg = msg
|
||||||
|
|
||||||
|
class ZadaraInvalidAccessKey(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
exception = CommonException()
|
||||||
|
|
||||||
|
|
||||||
|
zadara_opts = [
|
||||||
|
cfg.HostAddressOpt('zadara_vpsa_host',
|
||||||
|
default=None,
|
||||||
|
help='VPSA - Management Host name or IP address'),
|
||||||
|
cfg.PortOpt('zadara_vpsa_port',
|
||||||
|
default=None,
|
||||||
|
help='VPSA - Port number'),
|
||||||
|
cfg.BoolOpt('zadara_vpsa_use_ssl',
|
||||||
|
default=False,
|
||||||
|
help='VPSA - Use SSL connection'),
|
||||||
|
cfg.BoolOpt('zadara_ssl_cert_verify',
|
||||||
|
default=True,
|
||||||
|
help='If set to True the http client will validate the SSL '
|
||||||
|
'certificate of the VPSA endpoint.'),
|
||||||
|
cfg.StrOpt('zadara_access_key',
|
||||||
|
default=None,
|
||||||
|
help='VPSA access key',
|
||||||
|
secret=True),
|
||||||
|
cfg.StrOpt('zadara_vpsa_poolname',
|
||||||
|
default=None,
|
||||||
|
help='VPSA - Storage Pool assigned for volumes'),
|
||||||
|
cfg.BoolOpt('zadara_vol_encrypt',
|
||||||
|
default=False,
|
||||||
|
help='VPSA - Default encryption policy for volumes. '
|
||||||
|
'If the option is neither configured nor provided '
|
||||||
|
'as metadata, the VPSA will inherit the default value.'),
|
||||||
|
cfg.BoolOpt('zadara_gen3_vol_dedupe',
|
||||||
|
default=False,
|
||||||
|
help='VPSA - Enable deduplication for volumes. '
|
||||||
|
'If the option is neither configured nor provided '
|
||||||
|
'as metadata, the VPSA will inherit the default value.'),
|
||||||
|
cfg.BoolOpt('zadara_gen3_vol_compress',
|
||||||
|
default=False,
|
||||||
|
help='VPSA - Enable compression for volumes. '
|
||||||
|
'If the option is neither configured nor provided '
|
||||||
|
'as metadata, the VPSA will inherit the default value.'),
|
||||||
|
cfg.BoolOpt('zadara_default_snap_policy',
|
||||||
|
default=False,
|
||||||
|
help="VPSA - Attach snapshot policy for volumes. "
|
||||||
|
"If the option is neither configured nor provided "
|
||||||
|
"as metadata, the VPSA will inherit the default value.")]
|
||||||
|
|
||||||
|
|
||||||
|
# Class used to connect and execute the commands on
|
||||||
|
# Zadara Virtual Private Storage Array (VPSA).
|
||||||
|
class ZadaraVPSAConnection(object):
|
||||||
|
"""Executes driver commands on VPSA."""
|
||||||
|
|
||||||
|
def __init__(self, conf, driver_ssl_cert_path, block):
|
||||||
|
self.conf = conf
|
||||||
|
self.access_key = conf.zadara_access_key
|
||||||
|
if not self.access_key:
|
||||||
|
raise exception.ZadaraInvalidAccessKey()
|
||||||
|
self.driver_ssl_cert_path = driver_ssl_cert_path
|
||||||
|
# Choose the volume type of either block or file-type
|
||||||
|
# that will help to filter volumes.
|
||||||
|
self.vol_type_str = 'showonlyblock' if block else 'showonlyfile'
|
||||||
|
|
||||||
|
def _generate_vpsa_cmd(self, cmd, **kwargs):
|
||||||
|
"""Generate command to be sent to VPSA."""
|
||||||
|
|
||||||
|
# Dictionary of applicable VPSA commands in the following format:
|
||||||
|
# 'command': (method, API_URL, {optional parameters})
|
||||||
|
vpsa_commands = {
|
||||||
|
# Volume operations
|
||||||
|
'create_volume': ('POST',
|
||||||
|
'/api/volumes.json',
|
||||||
|
{'name': kwargs.get('name'),
|
||||||
|
'capacity': kwargs.get('size'),
|
||||||
|
'pool': self.conf.zadara_vpsa_poolname,
|
||||||
|
'block': 'YES'
|
||||||
|
if self.vol_type_str == 'showonlyblock'
|
||||||
|
else 'NO',
|
||||||
|
'thin': 'YES',
|
||||||
|
'crypt': 'YES'
|
||||||
|
if self.conf.zadara_vol_encrypt else 'NO',
|
||||||
|
'compress': 'YES'
|
||||||
|
if self.conf.zadara_gen3_vol_compress else 'NO',
|
||||||
|
'dedupe': 'YES'
|
||||||
|
if self.conf.zadara_gen3_vol_dedupe else 'NO',
|
||||||
|
'attachpolicies': 'NO'
|
||||||
|
if not self.conf.zadara_default_snap_policy
|
||||||
|
else 'YES'}),
|
||||||
|
'delete_volume': ('DELETE',
|
||||||
|
'/api/volumes/%s.json' % kwargs.get('vpsa_vol'),
|
||||||
|
{'force': 'YES'}),
|
||||||
|
'expand_volume': ('POST',
|
||||||
|
'/api/volumes/%s/expand.json'
|
||||||
|
% kwargs.get('vpsa_vol'),
|
||||||
|
{'capacity': kwargs.get('size')}),
|
||||||
|
'rename_volume': ('POST',
|
||||||
|
'/api/volumes/%s/rename.json'
|
||||||
|
% kwargs.get('vpsa_vol'),
|
||||||
|
{'new_name': kwargs.get('new_name')}),
|
||||||
|
# Snapshot operations
|
||||||
|
# Snapshot request is triggered for a single volume though the
|
||||||
|
# API call implies that snapshot is triggered for CG (legacy API).
|
||||||
|
'create_snapshot': ('POST',
|
||||||
|
'/api/consistency_groups/%s/snapshots.json'
|
||||||
|
% kwargs.get('cg_name'),
|
||||||
|
{'display_name': kwargs.get('snap_name')}),
|
||||||
|
'delete_snapshot': ('DELETE',
|
||||||
|
'/api/snapshots/%s.json'
|
||||||
|
% kwargs.get('snap_id'),
|
||||||
|
{}),
|
||||||
|
'rename_snapshot': ('POST',
|
||||||
|
'/api/snapshots/%s/rename.json'
|
||||||
|
% kwargs.get('snap_id'),
|
||||||
|
{'newname': kwargs.get('new_name')}),
|
||||||
|
'create_clone_from_snap': ('POST',
|
||||||
|
'/api/consistency_groups/%s/clone.json'
|
||||||
|
% kwargs.get('cg_name'),
|
||||||
|
{'name': kwargs.get('name'),
|
||||||
|
'snapshot': kwargs.get('snap_id')}),
|
||||||
|
'create_clone': ('POST',
|
||||||
|
'/api/consistency_groups/%s/clone.json'
|
||||||
|
% kwargs.get('cg_name'),
|
||||||
|
{'name': kwargs.get('name')}),
|
||||||
|
# Server operations
|
||||||
|
'create_server': ('POST',
|
||||||
|
'/api/servers.json',
|
||||||
|
{'iqn': kwargs.get('iqn'),
|
||||||
|
'iscsi': kwargs.get('iscsi_ip'),
|
||||||
|
'display_name': kwargs.get('iqn')
|
||||||
|
if kwargs.get('iqn')
|
||||||
|
else kwargs.get('iscsi_ip')}),
|
||||||
|
# Attach/Detach operations
|
||||||
|
'attach_volume': ('POST',
|
||||||
|
'/api/servers/%s/volumes.json'
|
||||||
|
% kwargs.get('vpsa_srv'),
|
||||||
|
{'volume_name[]': kwargs.get('vpsa_vol'),
|
||||||
|
'access_type': kwargs.get('share_proto'),
|
||||||
|
'readonly': kwargs.get('read_only'),
|
||||||
|
'force': 'YES'}),
|
||||||
|
'detach_volume': ('POST',
|
||||||
|
'/api/volumes/%s/detach.json'
|
||||||
|
% kwargs.get('vpsa_vol'),
|
||||||
|
{'server_name[]': kwargs.get('vpsa_srv'),
|
||||||
|
'force': 'YES'}),
|
||||||
|
# Update volume comment
|
||||||
|
'update_volume': ('POST',
|
||||||
|
'/api/volumes/%s/update_comment.json'
|
||||||
|
% kwargs.get('vpsa_vol'),
|
||||||
|
{'new_comment': kwargs.get('new_comment')}),
|
||||||
|
|
||||||
|
# Get operations
|
||||||
|
'list_volumes': ('GET',
|
||||||
|
'/api/volumes.json?%s=YES' % self.vol_type_str,
|
||||||
|
{}),
|
||||||
|
'get_volume': ('GET',
|
||||||
|
'/api/volumes/%s.json' % kwargs.get('vpsa_vol'),
|
||||||
|
{}),
|
||||||
|
'get_volume_by_name': ('GET',
|
||||||
|
'/api/volumes.json?display_name=%s'
|
||||||
|
% kwargs.get('display_name'),
|
||||||
|
{}),
|
||||||
|
'get_pool': ('GET',
|
||||||
|
'/api/pools/%s.json' % kwargs.get('pool_name'),
|
||||||
|
{}),
|
||||||
|
'list_controllers': ('GET',
|
||||||
|
'/api/vcontrollers.json',
|
||||||
|
{}),
|
||||||
|
'list_servers': ('GET',
|
||||||
|
'/api/servers.json',
|
||||||
|
{}),
|
||||||
|
'list_vol_snapshots': ('GET',
|
||||||
|
'/api/consistency_groups/%s/snapshots.json'
|
||||||
|
% kwargs.get('cg_name'),
|
||||||
|
{}),
|
||||||
|
'list_vol_attachments': ('GET',
|
||||||
|
'/api/volumes/%s/servers.json'
|
||||||
|
% kwargs.get('vpsa_vol'),
|
||||||
|
{}),
|
||||||
|
'list_snapshots': ('GET',
|
||||||
|
'/api/snapshots.json',
|
||||||
|
{}),
|
||||||
|
# Put operations
|
||||||
|
'change_export_name': ('PUT',
|
||||||
|
'/api/volumes/%s/export_name.json'
|
||||||
|
% kwargs.get('vpsa_vol'),
|
||||||
|
{'exportname': kwargs.get('exportname')})}
|
||||||
|
try:
|
||||||
|
method, url, params = vpsa_commands[cmd]
|
||||||
|
# Populate the metadata for the volume creation
|
||||||
|
metadata = kwargs.get('metadata')
|
||||||
|
if metadata:
|
||||||
|
for key, value in metadata.items():
|
||||||
|
params[key] = value
|
||||||
|
except KeyError:
|
||||||
|
raise exception.UnknownCmd(cmd=cmd)
|
||||||
|
|
||||||
|
if method == 'GET':
|
||||||
|
params = dict(page=1, start=0, limit=0)
|
||||||
|
body = None
|
||||||
|
|
||||||
|
elif method in ['DELETE', 'POST', 'PUT']:
|
||||||
|
body = params
|
||||||
|
params = None
|
||||||
|
|
||||||
|
else:
|
||||||
|
msg = ('Method %(method)s is not defined' % {'method': method})
|
||||||
|
LOG.error(msg)
|
||||||
|
raise AssertionError(msg)
|
||||||
|
|
||||||
|
# 'access_key' was generated using username and password
|
||||||
|
# or it was taken from the input file
|
||||||
|
headers = {'X-Access-Key': self.access_key}
|
||||||
|
|
||||||
|
return method, url, params, body, headers
|
||||||
|
|
||||||
|
def send_cmd(self, cmd, **kwargs):
|
||||||
|
"""Send command to VPSA Controller."""
|
||||||
|
|
||||||
|
if not self.access_key:
|
||||||
|
raise exception.ZadaraInvalidAccessKey()
|
||||||
|
|
||||||
|
method, url, params, body, headers = self._generate_vpsa_cmd(cmd,
|
||||||
|
**kwargs)
|
||||||
|
LOG.debug('Invoking %(cmd)s using %(method)s request.',
|
||||||
|
{'cmd': cmd, 'method': method})
|
||||||
|
|
||||||
|
host = self._get_target_host(self.conf.zadara_vpsa_host)
|
||||||
|
port = int(self.conf.zadara_vpsa_port)
|
||||||
|
|
||||||
|
protocol = "https" if self.conf.zadara_vpsa_use_ssl else "http"
|
||||||
|
if protocol == "https":
|
||||||
|
if not self.conf.zadara_ssl_cert_verify:
|
||||||
|
verify = False
|
||||||
|
else:
|
||||||
|
verify = (self.driver_ssl_cert_path
|
||||||
|
if self.driver_ssl_cert_path else True)
|
||||||
|
else:
|
||||||
|
verify = False
|
||||||
|
|
||||||
|
if port:
|
||||||
|
api_url = "%s://%s:%d%s" % (protocol, host, port, url)
|
||||||
|
else:
|
||||||
|
api_url = "%s://%s%s" % (protocol, host, url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with requests.Session() as session:
|
||||||
|
session.headers.update(headers)
|
||||||
|
response = session.request(method, api_url, params=params,
|
||||||
|
data=body, headers=headers,
|
||||||
|
verify=verify, timeout=vpsa_timeout)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
msg = ('Exception: %s') % e
|
||||||
|
raise exception.SessionRequestException(msg=msg)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise exception.BadHTTPResponseStatus(
|
||||||
|
status=response.status_code)
|
||||||
|
|
||||||
|
data = response.content
|
||||||
|
json_data = json.loads(data)
|
||||||
|
response = json_data['response']
|
||||||
|
status = int(response['status'])
|
||||||
|
if status == 5:
|
||||||
|
# Invalid Credentials
|
||||||
|
raise exception.ZadaraInvalidAccessKey()
|
||||||
|
|
||||||
|
if status != 0:
|
||||||
|
raise exception.FailedCmdWithDump(status=status, data=data)
|
||||||
|
|
||||||
|
if method in ['POST', 'DELETE']:
|
||||||
|
LOG.debug('Operation completed with status code %(status)s',
|
||||||
|
{'status': status})
|
||||||
|
return response
|
||||||
|
|
||||||
|
def _get_target_host(self, vpsa_host):
|
||||||
|
"""Helper for target host formatting."""
|
||||||
|
ipv6_without_brackets = ':' in vpsa_host and vpsa_host[-1] != ']'
|
||||||
|
if ipv6_without_brackets:
|
||||||
|
return ('[%s]' % vpsa_host)
|
||||||
|
return ('%s' % vpsa_host)
|
||||||
|
|
||||||
|
def _get_active_controller_details(self):
|
||||||
|
"""Return details of VPSA's active controller."""
|
||||||
|
data = self.send_cmd('list_controllers')
|
||||||
|
ctrl = None
|
||||||
|
vcontrollers = data.get('vcontrollers', [])
|
||||||
|
for controller in vcontrollers:
|
||||||
|
if controller['state'] == 'active':
|
||||||
|
ctrl = controller
|
||||||
|
break
|
||||||
|
|
||||||
|
if ctrl is not None:
|
||||||
|
target_ip = (ctrl['iscsi_ipv6'] if
|
||||||
|
ctrl['iscsi_ipv6'] else
|
||||||
|
ctrl['iscsi_ip'])
|
||||||
|
return dict(target=ctrl['target'],
|
||||||
|
ip=target_ip,
|
||||||
|
chap_user=ctrl['vpsa_chap_user'],
|
||||||
|
chap_passwd=ctrl['vpsa_chap_secret'])
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _check_access_key_validity(self):
|
||||||
|
"""Check VPSA access key"""
|
||||||
|
if not self.access_key:
|
||||||
|
raise exception.ZadaraInvalidAccessKey()
|
||||||
|
active_ctrl = self._get_active_controller_details()
|
||||||
|
if active_ctrl is None:
|
||||||
|
raise exception.ZadaraInvalidAccessKey()
|
||||||
|
|
||||||
|
def _get_vpsa_volume(self, name):
|
||||||
|
"""Returns a single vpsa volume based on the display name"""
|
||||||
|
volume = None
|
||||||
|
display_name = name
|
||||||
|
if re.search(r"\s", name):
|
||||||
|
display_name = re.split(r"\s", name)[0]
|
||||||
|
data = self.send_cmd('get_volume_by_name',
|
||||||
|
display_name=display_name)
|
||||||
|
if data['status'] != 0:
|
||||||
|
return None
|
||||||
|
volumes = data['volumes']
|
||||||
|
|
||||||
|
for vol in volumes:
|
||||||
|
if vol['display_name'] == name:
|
||||||
|
volume = vol
|
||||||
|
break
|
||||||
|
return volume
|
||||||
|
|
||||||
|
def _get_vpsa_volume_by_id(self, vpsa_vol):
|
||||||
|
"""Returns a single vpsa volume based on name"""
|
||||||
|
data = self.send_cmd('get_volume', vpsa_vol=vpsa_vol)
|
||||||
|
return data['volume']
|
||||||
|
|
||||||
|
def _get_volume_cg_name(self, name):
|
||||||
|
"""Return name of the consistency group for the volume.
|
||||||
|
|
||||||
|
cg-name is a volume uniqe identifier (legacy attribute)
|
||||||
|
and not consistency group as it may imply.
|
||||||
|
"""
|
||||||
|
volume = self._get_vpsa_volume(name)
|
||||||
|
if volume is not None:
|
||||||
|
return volume['cg_name']
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_all_vpsa_snapshots(self):
|
||||||
|
"""Returns snapshots from all vpsa volumes"""
|
||||||
|
data = self.send_cmd('list_snapshots')
|
||||||
|
return data['snapshots']
|
||||||
|
|
||||||
|
def _get_all_vpsa_volumes(self):
|
||||||
|
"""Returns all vpsa block volumes from the configured pool"""
|
||||||
|
data = self.send_cmd('list_volumes')
|
||||||
|
# FIXME: Work around to filter volumes belonging to given pool
|
||||||
|
# Remove this when we have the API fixed to filter based
|
||||||
|
# on pools. This API today does not have virtual_capacity field
|
||||||
|
volumes = []
|
||||||
|
|
||||||
|
for volume in data['volumes']:
|
||||||
|
if volume['pool_name'] == self.conf.zadara_vpsa_poolname:
|
||||||
|
volumes.append(volume)
|
||||||
|
|
||||||
|
return volumes
|
||||||
|
|
||||||
|
def _get_server_name(self, initiator, share):
|
||||||
|
"""Return VPSA's name for server object.
|
||||||
|
|
||||||
|
'share' will be true to search for filesystem volumes
|
||||||
|
"""
|
||||||
|
data = self.send_cmd('list_servers')
|
||||||
|
servers = data.get('servers', [])
|
||||||
|
for server in servers:
|
||||||
|
if share:
|
||||||
|
if server['iscsi_ip'] == initiator:
|
||||||
|
return server['name']
|
||||||
|
else:
|
||||||
|
if server['iqn'] == initiator:
|
||||||
|
return server['name']
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _create_vpsa_server(self, iqn=None, iscsi_ip=None):
|
||||||
|
"""Create server object within VPSA (if doesn't exist)."""
|
||||||
|
initiator = iscsi_ip if iscsi_ip else iqn
|
||||||
|
share = True if iscsi_ip else False
|
||||||
|
vpsa_srv = self._get_server_name(initiator, share)
|
||||||
|
if not vpsa_srv:
|
||||||
|
data = self.send_cmd('create_server', iqn=iqn, iscsi_ip=iscsi_ip)
|
||||||
|
if data['status'] != 0:
|
||||||
|
return None
|
||||||
|
vpsa_srv = data['server_name']
|
||||||
|
return vpsa_srv
|
||||||
|
|
||||||
|
def _get_servers_attached_to_volume(self, vpsa_vol):
|
||||||
|
"""Return all servers attached to volume."""
|
||||||
|
servers = vpsa_vol.get('server_ext_names')
|
||||||
|
list_servers = []
|
||||||
|
if servers:
|
||||||
|
list_servers = servers.split(',')
|
||||||
|
return list_servers
|
||||||
|
|
||||||
|
def _detach_vpsa_volume(self, vpsa_vol, vpsa_srv=None):
|
||||||
|
"""Detach volume from all attached servers."""
|
||||||
|
if vpsa_srv:
|
||||||
|
list_servers_ids = [vpsa_srv]
|
||||||
|
else:
|
||||||
|
list_servers_ids = self._get_servers_attached_to_volume(vpsa_vol)
|
||||||
|
|
||||||
|
for server_id in list_servers_ids:
|
||||||
|
# Detach volume from server
|
||||||
|
self.send_cmd('detach_volume', vpsa_srv=server_id,
|
||||||
|
vpsa_vol=vpsa_vol['name'])
|
||||||
|
|
||||||
|
def _get_volume_snapshots(self, cg_name):
|
||||||
|
"""Get snapshots in the consistency group"""
|
||||||
|
data = self.send_cmd('list_vol_snapshots', cg_name=cg_name)
|
||||||
|
snapshots = data.get('snapshots', [])
|
||||||
|
return snapshots
|
||||||
|
|
||||||
|
def _get_snap_id(self, cg_name, snap_name):
|
||||||
|
"""Return snapshot ID for particular volume."""
|
||||||
|
snapshots = self._get_volume_snapshots(cg_name)
|
||||||
|
for snap_vol in snapshots:
|
||||||
|
if snap_vol['display_name'] == snap_name:
|
||||||
|
return snap_vol['name']
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_pool_capacity(self, pool_name):
|
||||||
|
"""Return pool's total and available capacities."""
|
||||||
|
data = self.send_cmd('get_pool', pool_name=pool_name)
|
||||||
|
pool = data.get('pool')
|
||||||
|
if pool is not None:
|
||||||
|
total = int(pool['capacity'])
|
||||||
|
free = int(pool['available_capacity'])
|
||||||
|
provisioned = int(pool['provisioned_capacity'])
|
||||||
|
LOG.debug('Pool %(name)s: %(total)sGB total, %(free)sGB free, '
|
||||||
|
'%(provisioned)sGB provisioned',
|
||||||
|
{'name': pool_name, 'total': total,
|
||||||
|
'free': free, 'provisioned': provisioned})
|
||||||
|
return total, free, provisioned
|
||||||
|
|
||||||
|
return 'unknown', 'unknown', 'unknown'
|
748
manila/share/drivers/zadara/zadara.py
Normal file
748
manila/share/drivers/zadara/zadara.py
Normal file
@ -0,0 +1,748 @@
|
|||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Shared File system services driver for Zadara
|
||||||
|
Virtual Private Storage Array (VPSA).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import strutils
|
||||||
|
|
||||||
|
from manila import exception as manila_exception
|
||||||
|
from manila.i18n import _
|
||||||
|
from manila.share import api
|
||||||
|
from manila.share import driver
|
||||||
|
from manila.share.drivers.zadara import common
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.register_opts(common.zadara_opts)
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
manila_opts = [
|
||||||
|
cfg.StrOpt('zadara_share_name_template',
|
||||||
|
default='OS_share-%s',
|
||||||
|
help='VPSA - Default template for VPSA share names'),
|
||||||
|
cfg.StrOpt('zadara_share_snap_name_template',
|
||||||
|
default='OS_share-snapshot-%s',
|
||||||
|
help='VPSA - Default template for VPSA share names'),
|
||||||
|
cfg.StrOpt('zadara_driver_ssl_cert_path',
|
||||||
|
default=None,
|
||||||
|
help='Can be used to specify a non default path to a '
|
||||||
|
'CA_BUNDLE file or directory with certificates '
|
||||||
|
'of trusted CAs, which will be used to validate '
|
||||||
|
'the backend')]
|
||||||
|
|
||||||
|
|
||||||
|
class ZadaraVPSAShareDriver(driver.ShareDriver):
|
||||||
|
"""Zadara VPSA Share driver.
|
||||||
|
|
||||||
|
Version history::
|
||||||
|
|
||||||
|
20.12-01 - Driver changes intended and aligned with
|
||||||
|
openstack latest release.
|
||||||
|
20.12-02 - Fixed #18723 - Manila: Parsing the export location in a
|
||||||
|
more generic way while managing the vpsa share
|
||||||
|
20.12-03 - Adding the metadata support while creating share to
|
||||||
|
configure vpsa.
|
||||||
|
20.12-20 - IPv6 connectivity support for Manila driver
|
||||||
|
20.12-21 - Adding unit tests and fixing review comments from the
|
||||||
|
openstack community.
|
||||||
|
20.12-22 - Addressing review comments from the manila community.
|
||||||
|
20.12-23 - Addressing review comments from the manila community.
|
||||||
|
20.12-24 - Addressing review comments from the manila community.
|
||||||
|
"""
|
||||||
|
|
||||||
|
VERSION = '20.12-24'
|
||||||
|
|
||||||
|
# ThirdPartySystems wiki page
|
||||||
|
CI_WIKI_NAME = "ZadaraStorage_VPSA_CI"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""Do initialization."""
|
||||||
|
super(ZadaraVPSAShareDriver, self).__init__(False, *args, **kwargs)
|
||||||
|
self.vpsa = None
|
||||||
|
self.configuration.append_config_values(common.zadara_opts)
|
||||||
|
self.configuration.append_config_values(manila_opts)
|
||||||
|
self.api = api.API()
|
||||||
|
# The valid list of share options that can be specified
|
||||||
|
# as the metadata while creating manila share
|
||||||
|
self.share_options = ['smbguest', 'smbonly', 'smbwindowsacl',
|
||||||
|
'smbfilecreatemask', 'smbbrowseable',
|
||||||
|
'smbhiddenfiles', 'smbhideunreadable',
|
||||||
|
'smbhideunwriteable', 'smbhidedotfiles',
|
||||||
|
'smbstoredosattributes', 'smbdircreatemask',
|
||||||
|
'smbmaparchive', 'smbencryptionmode',
|
||||||
|
'smbenableoplocks', 'smbaiosize',
|
||||||
|
'nfsrootsquash', 'nfsallsquash',
|
||||||
|
'nfsanongid', 'nfsanonuid',
|
||||||
|
'atimeupdate', 'readaheadkb', 'crypt',
|
||||||
|
'compress', 'dedupe', 'attachpolicies']
|
||||||
|
|
||||||
|
def _check_access_key_validity(self):
|
||||||
|
try:
|
||||||
|
self.vpsa._check_access_key_validity()
|
||||||
|
except common.exception.ZadaraInvalidAccessKey:
|
||||||
|
raise manila_exception.ZadaraManilaInvalidAccessKey()
|
||||||
|
|
||||||
|
def do_setup(self, context):
|
||||||
|
"""Any initialization the share driver does while starting.
|
||||||
|
|
||||||
|
Establishes initial connection with VPSA and retrieves access_key.
|
||||||
|
Need to pass driver_ssl_cert_path here (and not fetch it from the
|
||||||
|
config opts directly in common code), because this config option is
|
||||||
|
different for different drivers and so cannot be figured in the
|
||||||
|
common code.
|
||||||
|
"""
|
||||||
|
driver_ssl_cert_path = self.configuration.zadara_driver_ssl_cert_path
|
||||||
|
self.vpsa = common.ZadaraVPSAConnection(self.configuration,
|
||||||
|
driver_ssl_cert_path, False)
|
||||||
|
|
||||||
|
def check_for_setup_error(self):
|
||||||
|
"""Returns an error (exception) if prerequisites aren't met."""
|
||||||
|
self._check_access_key_validity()
|
||||||
|
|
||||||
|
def vpsa_send_cmd(self, cmd, **kwargs):
|
||||||
|
try:
|
||||||
|
response = self.vpsa.send_cmd(cmd, **kwargs)
|
||||||
|
except common.exception.UnknownCmd as e:
|
||||||
|
raise manila_exception.ZadaraUnknownCmd(cmd=e.cmd)
|
||||||
|
except common.exception.SessionRequestException as e:
|
||||||
|
raise manila_exception.ZadaraSessionRequestException(msg=e.msg)
|
||||||
|
except common.exception.BadHTTPResponseStatus as e:
|
||||||
|
raise manila_exception.ZadaraBadHTTPResponseStatus(status=e.status)
|
||||||
|
except common.exception.FailedCmdWithDump as e:
|
||||||
|
raise manila_exception.ZadaraFailedCmdWithDump(status=e.status,
|
||||||
|
data=e.data)
|
||||||
|
except common.exception.ZadaraInvalidAccessKey:
|
||||||
|
raise manila_exception.ZadaraManilaInvalidAccessKey()
|
||||||
|
return response
|
||||||
|
|
||||||
|
def _get_zadara_share_template_name(self, share_id):
|
||||||
|
return self.configuration.zadara_share_name_template % share_id
|
||||||
|
|
||||||
|
def _get_share_export_location(self, share):
|
||||||
|
export_location = ''
|
||||||
|
share_proto = share['share_proto'].upper()
|
||||||
|
|
||||||
|
share_name = self._get_zadara_share_template_name(share['id'])
|
||||||
|
vpsa_volume = self.vpsa._get_vpsa_volume(share_name)
|
||||||
|
if not vpsa_volume:
|
||||||
|
msg = (_('VPSA volume for share %s '
|
||||||
|
'could not be found.') % share['id'])
|
||||||
|
LOG.error(msg)
|
||||||
|
raise manila_exception.ZadaraShareNotFound(name=share['id'])
|
||||||
|
|
||||||
|
if share_proto == 'NFS':
|
||||||
|
export_location = vpsa_volume['nfs_export_path']
|
||||||
|
if share_proto == 'CIFS':
|
||||||
|
export_location = vpsa_volume['smb_export_path']
|
||||||
|
return export_location
|
||||||
|
|
||||||
|
def _check_share_protocol(self, share):
|
||||||
|
share_proto = share['share_proto'].upper()
|
||||||
|
if share_proto not in ('NFS', 'CIFS'):
|
||||||
|
msg = _("Only NFS or CIFS protocol are currently supported. "
|
||||||
|
"Share provided %(share)s with protocol "
|
||||||
|
"%(proto)s.") % {'share': share['id'],
|
||||||
|
'proto': share['share_proto']}
|
||||||
|
LOG.error(msg)
|
||||||
|
raise manila_exception.ZadaraInvalidProtocol(
|
||||||
|
protocol_type=share_proto)
|
||||||
|
|
||||||
|
def is_valid_metadata(self, metadata):
|
||||||
|
LOG.debug('Metadata while creating share: %(metadata)s',
|
||||||
|
{'metadata': metadata})
|
||||||
|
for key, value in metadata.items():
|
||||||
|
if key in self.share_options:
|
||||||
|
# Check for the values allowed with provided metadata
|
||||||
|
if key in ['smbguest', 'smbonly', 'smbwindowsacl',
|
||||||
|
'smbbrowseable', 'smbhideunreadable',
|
||||||
|
'smbhideunwriteable', 'smbhidedotfiles',
|
||||||
|
'smbstoredosattributes', 'smbmaparchive',
|
||||||
|
'smbenableoplocks', 'nfsrootsquash',
|
||||||
|
'nfsallsquash', 'atimeupdate', 'crypt',
|
||||||
|
'compress', 'dedupe', 'attachpolicies']:
|
||||||
|
if value in ['YES', 'NO']:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
if key in ['smbfilecreatemask', 'smbdircreatemask']:
|
||||||
|
if value.isdigit():
|
||||||
|
# The valid permissions should be for user,group,other
|
||||||
|
# with another special digit for attributes. Ex:0755
|
||||||
|
if len(value) != 4:
|
||||||
|
return False
|
||||||
|
# No special permission bits for suid,sgid,
|
||||||
|
# stickybit are allowed for vpsa share.
|
||||||
|
if int(value[0]) != 0:
|
||||||
|
return False
|
||||||
|
# The permissions are always specified in octal
|
||||||
|
for i in range(1, len(value)):
|
||||||
|
if int(value[i]) > 7:
|
||||||
|
return False
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
if key == 'smbaiosize':
|
||||||
|
if value.isdigit() and value in ['16384', '1']:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
if key == 'smbencryptionmode':
|
||||||
|
if value in ['off', 'desired', 'required']:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
if key in ['nfsanongid', 'nfsanonuid']:
|
||||||
|
if value.isdigit() and int(value) != 0:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
if key == 'readaheadkb':
|
||||||
|
if value in ['16', '64', '128', '256', '512']:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def create_share(self, context, share, share_server=None):
|
||||||
|
"""Create a Zadara share and export it.
|
||||||
|
|
||||||
|
:param context: A RequestContext.
|
||||||
|
:param share: A Share.
|
||||||
|
:param share_server: Not used currently
|
||||||
|
:return: The export locations dictionary.
|
||||||
|
"""
|
||||||
|
# Check share's protocol.
|
||||||
|
# Throw an exception immediately if it is an invalid protocol.
|
||||||
|
self._check_share_protocol(share)
|
||||||
|
share_name = self._get_zadara_share_template_name(share['id'])
|
||||||
|
|
||||||
|
# Collect the share metadata provided and validate it
|
||||||
|
metadata = self.api.get_share_metadata(context,
|
||||||
|
{'id': share['share_id']})
|
||||||
|
if not self.is_valid_metadata(metadata):
|
||||||
|
raise manila_exception.ManilaException(_(
|
||||||
|
"Not a valid metadata provided for the share %s")
|
||||||
|
% share['id'])
|
||||||
|
|
||||||
|
data = self.vpsa_send_cmd('create_volume',
|
||||||
|
name=share_name,
|
||||||
|
size=share['size'],
|
||||||
|
metadata=metadata)
|
||||||
|
if data['status'] != 0:
|
||||||
|
raise manila_exception.ZadaraVPSAVolumeShareFailed(
|
||||||
|
error=data['status'])
|
||||||
|
|
||||||
|
export_location = self._get_share_export_location(share)
|
||||||
|
return {'path': export_location}
|
||||||
|
|
||||||
|
def _allow_access(self, context, share, access):
|
||||||
|
"""Allow access to the share."""
|
||||||
|
access_type = access['access_type']
|
||||||
|
share_proto = share['share_proto'].upper()
|
||||||
|
if share_proto == 'CIFS':
|
||||||
|
share_proto = 'SMB'
|
||||||
|
|
||||||
|
if access_type != 'ip':
|
||||||
|
raise manila_exception.ZadaraInvalidShareAccessType()
|
||||||
|
access_ip = access['access_to']
|
||||||
|
access_level = 'YES'
|
||||||
|
if access['access_level'] == 'rw':
|
||||||
|
access_level = 'NO'
|
||||||
|
|
||||||
|
# First: Check Active controller: if not valid, raise exception
|
||||||
|
ctrl = self.vpsa._get_active_controller_details()
|
||||||
|
if not ctrl:
|
||||||
|
raise manila_exception.ZadaraVPSANoActiveController()
|
||||||
|
|
||||||
|
# Get volume name
|
||||||
|
vol_name = self._get_zadara_share_template_name(share['id'])
|
||||||
|
vpsa_volume = self.vpsa._get_vpsa_volume(vol_name)
|
||||||
|
|
||||||
|
if not vpsa_volume:
|
||||||
|
msg = (_('VPSA volume for share %s '
|
||||||
|
'could not be found.') % share['id'])
|
||||||
|
LOG.error(msg)
|
||||||
|
raise manila_exception.ZadaraShareNotFound(name=share['id'])
|
||||||
|
|
||||||
|
# Get/Create server name for given IP
|
||||||
|
vpsa_srv = self.vpsa._create_vpsa_server(iscsi_ip=access_ip)
|
||||||
|
if not vpsa_srv:
|
||||||
|
raise manila_exception.ZadaraServerCreateFailure(name=access_ip)
|
||||||
|
|
||||||
|
servers = self.vpsa._get_servers_attached_to_volume(vpsa_volume)
|
||||||
|
attach = None
|
||||||
|
for server in servers:
|
||||||
|
if server == vpsa_srv:
|
||||||
|
attach = server
|
||||||
|
break
|
||||||
|
# Attach volume to server
|
||||||
|
if attach is None:
|
||||||
|
self.vpsa_send_cmd('attach_volume',
|
||||||
|
vpsa_srv=vpsa_srv,
|
||||||
|
vpsa_vol=vpsa_volume['name'],
|
||||||
|
share_proto=share_proto,
|
||||||
|
read_only=access_level)
|
||||||
|
|
||||||
|
data = self.vpsa_send_cmd('list_vol_attachments',
|
||||||
|
vpsa_vol=vpsa_volume['name'])
|
||||||
|
server = None
|
||||||
|
servers = data.get('servers', [])
|
||||||
|
for srv in servers:
|
||||||
|
if srv['iscsi_ip'] == access_ip:
|
||||||
|
server = srv
|
||||||
|
break
|
||||||
|
|
||||||
|
if server is None:
|
||||||
|
raise manila_exception.ZadaraAttachmentsNotFound(
|
||||||
|
name=vpsa_volume['name'])
|
||||||
|
|
||||||
|
ctrl_ip = self.vpsa._get_target_host(ctrl['ip'])
|
||||||
|
properties = {'target_discovered': False,
|
||||||
|
'target_portal': (('%s:%s') % (ctrl_ip, '3260')),
|
||||||
|
'target_ip': server['iscsi_ip'],
|
||||||
|
'id': share['id'],
|
||||||
|
'auth_method': 'CHAP',
|
||||||
|
'auth_username': ctrl['chap_user'],
|
||||||
|
'auth_password': ctrl['chap_passwd']}
|
||||||
|
|
||||||
|
LOG.debug('Attach properties: %(properties)s',
|
||||||
|
{'properties': strutils.mask_password(properties)})
|
||||||
|
return {'driver_volume_type': share['share_proto'], 'data': properties}
|
||||||
|
|
||||||
|
def delete_share(self, context, share, share_server=None):
|
||||||
|
"""Delete share. Auto detach from all servers.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Get share name
|
||||||
|
share_name = self._get_zadara_share_template_name(share['id'])
|
||||||
|
volume = self.vpsa._get_vpsa_volume(share_name)
|
||||||
|
if not volume:
|
||||||
|
LOG.warning('Volume %s could not be found. '
|
||||||
|
'It might be already deleted', share['id'])
|
||||||
|
return
|
||||||
|
|
||||||
|
self.vpsa._detach_vpsa_volume(vpsa_vol=volume)
|
||||||
|
|
||||||
|
# Delete volume associate with the share
|
||||||
|
self.vpsa_send_cmd('delete_volume', vpsa_vol=volume['name'])
|
||||||
|
|
||||||
|
def _deny_access(self, context, share, access, share_server=None):
|
||||||
|
"""Deny access to the share from the host.
|
||||||
|
|
||||||
|
Auto detach from all servers.
|
||||||
|
"""
|
||||||
|
# First: Check Active controller: if not valid, raise exception
|
||||||
|
ctrl = self.vpsa._get_active_controller_details()
|
||||||
|
if not ctrl:
|
||||||
|
raise manila_exception.ZadaraVPSANoActiveController()
|
||||||
|
|
||||||
|
# Get share name
|
||||||
|
share_name = self._get_zadara_share_template_name(share['id'])
|
||||||
|
volume = self.vpsa._get_vpsa_volume(share_name)
|
||||||
|
if not volume:
|
||||||
|
LOG.error('Volume %s could not be found.'
|
||||||
|
'It might be already deleted', share['id'])
|
||||||
|
return
|
||||||
|
|
||||||
|
self.vpsa._detach_vpsa_volume(vpsa_vol=volume)
|
||||||
|
|
||||||
|
def update_access(self, context, share, access_rules, add_rules,
|
||||||
|
delete_rules, share_server=None):
|
||||||
|
access_updates = {}
|
||||||
|
if add_rules:
|
||||||
|
# Add rules for accessing share
|
||||||
|
for access_rule in add_rules:
|
||||||
|
try:
|
||||||
|
self._allow_access(context, share, access_rule)
|
||||||
|
except manila_exception.ZadaraInvalidShareAccessType:
|
||||||
|
LOG.error("Only ip access type allowed for Zadara share. "
|
||||||
|
"Failed to allow %(access_level)s access to "
|
||||||
|
"%(access_to)s for rule %(id)s. Setting rule "
|
||||||
|
"to 'error' state.",
|
||||||
|
{'access_level': access_rule['access_level'],
|
||||||
|
'access_to': access_rule['access_to'],
|
||||||
|
'id': access_rule['access_id']})
|
||||||
|
access_updates.update(
|
||||||
|
{access_rule['access_id']: {'state': 'error'}})
|
||||||
|
if delete_rules:
|
||||||
|
# Delete access rules for provided share
|
||||||
|
for access_rule in delete_rules:
|
||||||
|
self._deny_access(context, share, access_rule)
|
||||||
|
return access_updates
|
||||||
|
|
||||||
|
def extend_share(self, share, new_size, share_server=None):
|
||||||
|
"""Extend an existing share.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Get the backend volume name for the share
|
||||||
|
share_name = self._get_zadara_share_template_name(share['id'])
|
||||||
|
vpsa_volume = self.vpsa._get_vpsa_volume(share_name)
|
||||||
|
if not vpsa_volume:
|
||||||
|
msg = (_('VPSA volume for share %s '
|
||||||
|
'could not be found.') % share['id'])
|
||||||
|
LOG.error(msg)
|
||||||
|
raise manila_exception.ZadaraShareNotFound(name=share['id'])
|
||||||
|
|
||||||
|
size = vpsa_volume['virtual_capacity']
|
||||||
|
expand_size = new_size - size
|
||||||
|
data = self.vpsa_send_cmd('expand_volume',
|
||||||
|
vpsa_vol=vpsa_volume['name'],
|
||||||
|
size=expand_size)
|
||||||
|
if data['status'] != 0:
|
||||||
|
raise manila_exception.ZadaraExtendShareFailed(
|
||||||
|
error=data['status'])
|
||||||
|
|
||||||
|
def _ensure_share(self, context, share, share_server=None):
|
||||||
|
"""Ensure that the share has a backend volume and it is exported.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Get the backend volume name for the share
|
||||||
|
share_name = self._get_zadara_share_template_name(share['id'])
|
||||||
|
vpsa_volume = self.vpsa._get_vpsa_volume(share_name)
|
||||||
|
if not vpsa_volume:
|
||||||
|
msg = (_('VPSA volume for share %s '
|
||||||
|
'could not be found.') % share['id'])
|
||||||
|
LOG.error(msg)
|
||||||
|
raise manila_exception.ZadaraShareNotFound(name=share['id'])
|
||||||
|
|
||||||
|
export_locations = share['export_locations']
|
||||||
|
if export_locations:
|
||||||
|
return export_locations
|
||||||
|
else:
|
||||||
|
servers_list = (self.vpsa._get_servers_attached_to_volume(
|
||||||
|
vpsa_volume))
|
||||||
|
if len(servers_list) != 0:
|
||||||
|
msg = (_('Servers attached to the VPSA volume %s without '
|
||||||
|
'any locations exported.') % vpsa_volume['name'])
|
||||||
|
LOG.error(msg)
|
||||||
|
raise manila_exception.ZadaraShareNotValid(
|
||||||
|
name=share['id'])
|
||||||
|
|
||||||
|
def _update_share_stats(self):
|
||||||
|
|
||||||
|
backend_name = self.configuration.share_backend_name
|
||||||
|
dhss = self.configuration.driver_handles_share_servers
|
||||||
|
vpsa_poolname = self.configuration.zadara_vpsa_poolname
|
||||||
|
(total, free, provisioned) = (
|
||||||
|
self.vpsa._get_pool_capacity(vpsa_poolname))
|
||||||
|
ctrl = self.vpsa._get_active_controller_details()
|
||||||
|
if not ctrl:
|
||||||
|
raise manila_exception.ZadaraVPSANoActiveController()
|
||||||
|
ipv4_support = False if ':' in ctrl['ip'] else True
|
||||||
|
|
||||||
|
# VPSA backend pool
|
||||||
|
single_pool = dict(
|
||||||
|
pool_name=vpsa_poolname,
|
||||||
|
total_capacity_gb=total,
|
||||||
|
free_capacity_gb=free,
|
||||||
|
allocated_capacity_gb=(total - free),
|
||||||
|
provisioned_capacity_gb=provisioned,
|
||||||
|
reserved_percentage=self.configuration.reserved_share_percentage,
|
||||||
|
compression=[True, False],
|
||||||
|
dedupe=[True, False],
|
||||||
|
thin_provisioning=True
|
||||||
|
)
|
||||||
|
|
||||||
|
data = dict(
|
||||||
|
share_backend_name=backend_name,
|
||||||
|
driver_handles_share_servers=dhss,
|
||||||
|
vendor_name='Zadara Storage',
|
||||||
|
driver_version=self.VERSION,
|
||||||
|
storage_protocol='NFS_CIFS',
|
||||||
|
pools=[single_pool],
|
||||||
|
snapshot_support=True,
|
||||||
|
create_share_from_snapshot_support=True,
|
||||||
|
revert_to_snapshot_support=False,
|
||||||
|
mount_snapshot_support=False,
|
||||||
|
ipv4_support=ipv4_support,
|
||||||
|
ipv6_support=not ipv4_support
|
||||||
|
)
|
||||||
|
super(ZadaraVPSAShareDriver, self)._update_share_stats(data)
|
||||||
|
|
||||||
|
def create_snapshot(self, context, snapshot, share_server=None):
|
||||||
|
"""Creates a snapshot."""
|
||||||
|
LOG.debug('Create snapshot: %s', snapshot['id'])
|
||||||
|
|
||||||
|
# Retrieve the CG name for the base volume
|
||||||
|
share = snapshot['share']
|
||||||
|
volume_name = self._get_zadara_share_template_name(share['id'])
|
||||||
|
cg_name = self.vpsa._get_volume_cg_name(volume_name)
|
||||||
|
if not cg_name:
|
||||||
|
msg = (_('VPSA volume for share %s '
|
||||||
|
'could not be found.') % share['id'])
|
||||||
|
LOG.error(msg)
|
||||||
|
raise manila_exception.ZadaraShareNotFound(name=share['id'])
|
||||||
|
|
||||||
|
snap_name = (self.configuration.zadara_share_snap_name_template
|
||||||
|
% snapshot['id'])
|
||||||
|
data = self.vpsa_send_cmd('create_snapshot',
|
||||||
|
cg_name=cg_name,
|
||||||
|
snap_name=snap_name)
|
||||||
|
if data['status'] != 0:
|
||||||
|
raise manila_exception.ZadaraVPSASnapshotCreateFailed(
|
||||||
|
name=share['id'], error=data['status'])
|
||||||
|
|
||||||
|
return {'provider_location': data['snapshot_name']}
|
||||||
|
|
||||||
|
def delete_snapshot(self, context, snapshot, share_server=None):
|
||||||
|
"""Deletes a snapshot."""
|
||||||
|
LOG.debug('Delete snapshot: %s', snapshot['id'])
|
||||||
|
|
||||||
|
# Retrieve the CG name for the base volume
|
||||||
|
share = snapshot['share']
|
||||||
|
volume_name = self._get_zadara_share_template_name(share['id'])
|
||||||
|
cg_name = self.vpsa._get_volume_cg_name(volume_name)
|
||||||
|
if not cg_name:
|
||||||
|
# If the volume isn't present, then don't attempt to delete
|
||||||
|
LOG.warning('snapshot: original volume %s not found, '
|
||||||
|
'skipping delete operation',
|
||||||
|
volume_name)
|
||||||
|
return
|
||||||
|
|
||||||
|
snap_name = (self.configuration.zadara_share_snap_name_template
|
||||||
|
% snapshot['id'])
|
||||||
|
snap_id = self.vpsa._get_snap_id(cg_name, snap_name)
|
||||||
|
if not snap_id:
|
||||||
|
# If the snapshot isn't present, then don't attempt to delete
|
||||||
|
LOG.warning('snapshot: snapshot %s not found, '
|
||||||
|
'skipping delete operation', snap_name)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.vpsa_send_cmd('delete_snapshot',
|
||||||
|
snap_id=snap_id)
|
||||||
|
|
||||||
|
def create_share_from_snapshot(self, context, share, snapshot,
|
||||||
|
share_server=None, parent_share=None):
|
||||||
|
"""Creates a share from a snapshot.
|
||||||
|
|
||||||
|
"""
|
||||||
|
LOG.debug('Creating share from snapshot: %s', snapshot['id'])
|
||||||
|
|
||||||
|
# Retrieve the CG name for the base volume
|
||||||
|
volume_name = (self._get_zadara_share_template_name(
|
||||||
|
snapshot['share_instance_id']))
|
||||||
|
cg_name = self.vpsa._get_volume_cg_name(volume_name)
|
||||||
|
if not cg_name:
|
||||||
|
msg = (_('VPSA volume for share %s '
|
||||||
|
'could not be found.') % share['id'])
|
||||||
|
LOG.error(msg)
|
||||||
|
raise manila_exception.ZadaraShareNotFound(name=share['id'])
|
||||||
|
|
||||||
|
snap_name = (self.configuration.zadara_share_snap_name_template
|
||||||
|
% snapshot['id'])
|
||||||
|
snap_id = self.vpsa._get_snap_id(cg_name, snap_name)
|
||||||
|
if not snap_id:
|
||||||
|
msg = _('Snapshot %(name)s not found') % {'name': snap_name}
|
||||||
|
LOG.error(msg)
|
||||||
|
raise manila_exception.ShareSnapshotNotFound(
|
||||||
|
snapshot_id=snap_name)
|
||||||
|
|
||||||
|
self._check_share_protocol(share)
|
||||||
|
|
||||||
|
share_name = self._get_zadara_share_template_name(share['id'])
|
||||||
|
self.vpsa_send_cmd('create_clone_from_snap',
|
||||||
|
cg_name=cg_name,
|
||||||
|
name=share_name,
|
||||||
|
snap_id=snap_id)
|
||||||
|
|
||||||
|
if share['size'] > snapshot['size']:
|
||||||
|
self.extend_share(share, share['size'])
|
||||||
|
|
||||||
|
export_location = self._get_share_export_location(share)
|
||||||
|
return [{'path': export_location}]
|
||||||
|
|
||||||
|
def _get_export_name_from_export_path(self, proto, export_path):
|
||||||
|
if proto == 'nfs' and '\\' in export_path:
|
||||||
|
return None
|
||||||
|
if proto == 'cifs' and '/' in export_path:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Extract the export name from the provided export path
|
||||||
|
if proto == 'nfs':
|
||||||
|
separator = '/'
|
||||||
|
export_location = export_path.strip(separator)
|
||||||
|
export_name = export_location.split(separator)[-1]
|
||||||
|
else:
|
||||||
|
separator = '\\'
|
||||||
|
export_location = export_path.strip(separator)
|
||||||
|
export_name = export_location.split(separator)[-1]
|
||||||
|
return export_name
|
||||||
|
|
||||||
|
def _extract_vpsa_volume_from_share(self, share):
|
||||||
|
"""Returns a vpsa volume based on the export location"""
|
||||||
|
if not share['export_locations'][0]['path']:
|
||||||
|
return None
|
||||||
|
|
||||||
|
share_proto = share['share_proto'].lower()
|
||||||
|
export_path = share['export_locations'][0]['path']
|
||||||
|
export_name = self._get_export_name_from_export_path(share_proto,
|
||||||
|
export_path)
|
||||||
|
if export_name is None:
|
||||||
|
msg = (_('Please verify the specifed protocol and export path.'))
|
||||||
|
LOG.error(msg)
|
||||||
|
raise manila_exception.ManilaException(msg)
|
||||||
|
|
||||||
|
volume = None
|
||||||
|
volumes = self.vpsa._get_all_vpsa_volumes()
|
||||||
|
# Find the volume with the corresponding export name
|
||||||
|
for vol in volumes:
|
||||||
|
if share_proto == 'nfs':
|
||||||
|
vol_export_path = vol.get('nfs_export_path', None)
|
||||||
|
else:
|
||||||
|
vol_export_path = vol.get('smb_export_path', None)
|
||||||
|
|
||||||
|
vol_export_name = self._get_export_name_from_export_path(
|
||||||
|
share_proto, vol_export_path)
|
||||||
|
if export_name == vol_export_name:
|
||||||
|
volume = vol
|
||||||
|
break
|
||||||
|
|
||||||
|
# Check the additional smb export paths of the volume
|
||||||
|
if (share_proto == 'cifs' and
|
||||||
|
vol['additional_smb_export_paths_count'] > 0):
|
||||||
|
for additional_path in vol['additional_smb_export_paths']:
|
||||||
|
vol_export_name = self._get_export_name_from_export_path(
|
||||||
|
share_proto, additional_path)
|
||||||
|
if export_name == vol_export_name:
|
||||||
|
volume = vol
|
||||||
|
break
|
||||||
|
if volume:
|
||||||
|
return volume
|
||||||
|
else:
|
||||||
|
msg = (_('Manage backend share could not be found. It might be '
|
||||||
|
'deleted or please verify the specifed protocol and '
|
||||||
|
'export path.'))
|
||||||
|
LOG.error(msg)
|
||||||
|
raise manila_exception.ManilaException(msg)
|
||||||
|
|
||||||
|
def manage_existing(self, share, driver_options):
|
||||||
|
# Check whether the specified protocol is supported or not.
|
||||||
|
self._check_share_protocol(share)
|
||||||
|
|
||||||
|
LOG.info("Share %(shr_path)s will be managed with share %(shr_name)s.",
|
||||||
|
{'shr_path': share['export_locations'][0]['path'],
|
||||||
|
'shr_name': share['id']})
|
||||||
|
|
||||||
|
# Find the backend vpsa volume for the provided export location
|
||||||
|
vpsa_volume = self._extract_vpsa_volume_from_share(share)
|
||||||
|
|
||||||
|
# Check if the volume is available
|
||||||
|
if vpsa_volume['status'] != 'Available':
|
||||||
|
msg = (_('Existing share %(name)s is not available')
|
||||||
|
% {'name': vpsa_volume['name']})
|
||||||
|
LOG.error(msg)
|
||||||
|
raise manila_exception.ManilaException(msg)
|
||||||
|
|
||||||
|
new_share_name = self._get_zadara_share_template_name(share['id'])
|
||||||
|
new_vpsa_share = self.vpsa._get_vpsa_volume(new_share_name)
|
||||||
|
if new_vpsa_share:
|
||||||
|
msg = (_('Share %(new_name)s already exists')
|
||||||
|
% {'new_name': new_share_name})
|
||||||
|
LOG.error(msg)
|
||||||
|
raise manila_exception.ManilaException(msg)
|
||||||
|
|
||||||
|
# Rename the volume to the manila share specified name
|
||||||
|
data = self.vpsa_send_cmd('rename_volume',
|
||||||
|
vpsa_vol=vpsa_volume['name'],
|
||||||
|
new_name=new_share_name)
|
||||||
|
if data['status'] != 0:
|
||||||
|
msg = (_('Renaming volume %(old_name)s to %(new_name)s '
|
||||||
|
'has failed.') % {'old_name': vpsa_volume['name'],
|
||||||
|
'new_name': new_share_name})
|
||||||
|
LOG.error(msg)
|
||||||
|
raise manila_exception.ManilaException(msg)
|
||||||
|
|
||||||
|
return {'size': vpsa_volume['provisioned_capacity'],
|
||||||
|
'export_locations': share['export_locations'][0]['path']}
|
||||||
|
|
||||||
|
def unmanage(self, share):
|
||||||
|
"""Removes the specified volume from Manila management"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def manage_existing_snapshot(self, snapshot, driver_options):
|
||||||
|
share = snapshot['share']
|
||||||
|
share_name = self._get_zadara_share_template_name(share['id'])
|
||||||
|
|
||||||
|
vpsa_volume = self.vpsa._get_vpsa_volume(share_name)
|
||||||
|
if not vpsa_volume:
|
||||||
|
msg = (_('Volume %(name)s could not be found. '
|
||||||
|
'It might be already deleted') % {'name': share_name})
|
||||||
|
LOG.error(msg)
|
||||||
|
raise manila_exception.ZadaraShareNotFound(name=share['id'])
|
||||||
|
|
||||||
|
# Check if the provider_location is specified
|
||||||
|
if not snapshot['provider_location']:
|
||||||
|
msg = (_('Provider location as snap id of the VPSA backend '
|
||||||
|
'should be provided'))
|
||||||
|
LOG.error(msg)
|
||||||
|
raise manila_exception.ManilaException(msg)
|
||||||
|
|
||||||
|
new_name = (self.configuration.zadara_share_snap_name_template
|
||||||
|
% snapshot['id'])
|
||||||
|
new_snap_id = self.vpsa._get_snap_id(vpsa_volume['cg_name'],
|
||||||
|
new_name)
|
||||||
|
if new_snap_id:
|
||||||
|
msg = (_('Snapshot with name %s already exists') % new_name)
|
||||||
|
LOG.debug(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
data = self.vpsa_send_cmd('rename_snapshot',
|
||||||
|
snap_id=snapshot['provider_location'],
|
||||||
|
new_name=new_name)
|
||||||
|
if data['status'] != 0:
|
||||||
|
raise manila_exception.ZadaraVPSASnapshotManageFailed(
|
||||||
|
snap_id=snapshot['provider_location'],
|
||||||
|
error=data['status'])
|
||||||
|
|
||||||
|
def unmanage_snapshot(self, snapshot):
|
||||||
|
"""Removes the specified snapshot from Manila management"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_configured_ip_versions(self):
|
||||||
|
""""Get allowed IP versions.
|
||||||
|
|
||||||
|
The shares created should have export location as per the
|
||||||
|
IP version. Currently, zadara backend doesn't support both
|
||||||
|
ipv4 and ipv6. Collect the supported IP version from the
|
||||||
|
vpsa's active controller
|
||||||
|
"""
|
||||||
|
ctrl = self.vpsa._get_active_controller_details()
|
||||||
|
if not ctrl:
|
||||||
|
raise manila_exception.ZadaraVPSANoActiveController()
|
||||||
|
|
||||||
|
if ':' in ctrl['ip']:
|
||||||
|
return [6]
|
||||||
|
else:
|
||||||
|
return [4]
|
||||||
|
|
||||||
|
def get_backend_info(self, context):
|
||||||
|
return {
|
||||||
|
'version': self.VERSION,
|
||||||
|
'vsa_feip': socket.gethostbyname(self.vpsa.conf.zadara_vpsa_host),
|
||||||
|
'vsa_port': self.vpsa.conf.zadara_vpsa_port
|
||||||
|
}
|
||||||
|
|
||||||
|
def ensure_shares(self, context, shares):
|
||||||
|
updates = {}
|
||||||
|
for share in shares:
|
||||||
|
updates[share['id']] = {
|
||||||
|
'export_locations': self._ensure_share(context, share)}
|
||||||
|
return updates
|
0
manila/tests/share/drivers/zadara/__init__.py
Normal file
0
manila/tests/share/drivers/zadara/__init__.py
Normal file
1052
manila/tests/share/drivers/zadara/test_zadara.py
Normal file
1052
manila/tests/share/drivers/zadara/test_zadara.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Added Manila driver for Zadara VPSA Storage Array/Flash-Array.
|
Loading…
x
Reference in New Issue
Block a user