423 lines
17 KiB
Python
423 lines
17 KiB
Python
# Copyright (c) 2015 Hitachi Data Systems, 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.
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log
|
|
import six
|
|
|
|
from manila import exception
|
|
from manila.i18n import _
|
|
from manila.i18n import _LI
|
|
from manila.share import driver
|
|
from manila.share.drivers.hitachi import ssh
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
hds_hnas_opts = [
|
|
cfg.StrOpt('hds_hnas_ip',
|
|
help="HNAS management interface IP for communication "
|
|
"between Manila controller and HNAS."),
|
|
cfg.StrOpt('hds_hnas_user',
|
|
help="HNAS username Base64 String in order to perform tasks "
|
|
"such as create file-systems and network interfaces."),
|
|
cfg.StrOpt('hds_hnas_password',
|
|
secret=True,
|
|
help="HNAS user password. Required only if private key is not "
|
|
"provided."),
|
|
cfg.StrOpt('hds_hnas_evs_id',
|
|
help="Specify which EVS this backend is assigned to."),
|
|
cfg.StrOpt('hds_hnas_evs_ip',
|
|
help="Specify IP for mounting shares."),
|
|
cfg.StrOpt('hds_hnas_file_system_name',
|
|
help="Specify file-system name for creating shares."),
|
|
cfg.StrOpt('hds_hnas_ssh_private_key',
|
|
secret=True,
|
|
help="RSA/DSA private key value used to connect into HNAS. "
|
|
"Required only if password is not provided."),
|
|
cfg.StrOpt('hds_hnas_cluster_admin_ip0',
|
|
help="The IP of the clusters admin node. Only set in HNAS "
|
|
"multinode clusters."),
|
|
cfg.IntOpt('hds_hnas_stalled_job_timeout',
|
|
default=30,
|
|
help="The time (in seconds) to wait for stalled HNAS jobs "
|
|
"before aborting."),
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(hds_hnas_opts)
|
|
|
|
|
|
class HDSHNASDriver(driver.ShareDriver):
|
|
"""Manila HNAS Driver implementation.
|
|
|
|
1.0 - Initial Version
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""Do initialization."""
|
|
|
|
LOG.debug("Invoking base constructor for Manila HDS HNAS Driver.")
|
|
super(HDSHNASDriver, self).__init__(False, *args, **kwargs)
|
|
|
|
LOG.debug("Setting up attributes for Manila HDS HNAS Driver.")
|
|
self.configuration.append_config_values(hds_hnas_opts)
|
|
|
|
LOG.debug("Reading config parameters for Manila HDS HNAS Driver.")
|
|
self.backend_name = self.configuration.safe_get('share_backend_name')
|
|
hnas_ip = self.configuration.safe_get('hds_hnas_ip')
|
|
hnas_username = self.configuration.safe_get('hds_hnas_user')
|
|
hnas_password = self.configuration.safe_get('hds_hnas_password')
|
|
hnas_evs_id = self.configuration.safe_get('hds_hnas_evs_id')
|
|
self.hnas_evs_ip = self.configuration.safe_get('hds_hnas_evs_ip')
|
|
fs_name = self.configuration.safe_get('hds_hnas_file_system_name')
|
|
ssh_private_key = self.configuration.safe_get(
|
|
'hds_hnas_ssh_private_key')
|
|
cluster_admin_ip0 = self.configuration.safe_get(
|
|
'hds_hnas_cluster_admin_ip0')
|
|
self.private_storage = kwargs.get('private_storage')
|
|
job_timeout = self.configuration.safe_get(
|
|
'hds_hnas_stalled_job_timeout')
|
|
|
|
if hnas_evs_id is None:
|
|
msg = _("The config parameter hds_hnas_evs_id is not set.")
|
|
raise exception.InvalidParameterValue(err=msg)
|
|
|
|
if self.hnas_evs_ip is None:
|
|
msg = _("The config parameter hds_hnas_evs_ip is not set.")
|
|
raise exception.InvalidParameterValue(err=msg)
|
|
|
|
if hnas_ip is None:
|
|
msg = _("The config parameter hds_hnas_ip is not set.")
|
|
raise exception.InvalidParameterValue(err=msg)
|
|
|
|
if hnas_username is None:
|
|
msg = _("The config parameter hds_hnas_user is not set.")
|
|
raise exception.InvalidParameterValue(err=msg)
|
|
|
|
if hnas_password is None and ssh_private_key is None:
|
|
msg = _("Credentials configuration parameters missing: "
|
|
"you need to set hds_hnas_password or "
|
|
"hds_hnas_ssh_private_key.")
|
|
raise exception.InvalidParameterValue(err=msg)
|
|
|
|
LOG.debug("Initializing HNAS Layer.")
|
|
|
|
self.hnas = ssh.HNASSSHBackend(hnas_ip, hnas_username, hnas_password,
|
|
ssh_private_key, cluster_admin_ip0,
|
|
hnas_evs_id, self.hnas_evs_ip, fs_name,
|
|
job_timeout)
|
|
|
|
def allow_access(self, context, share, access, share_server=None):
|
|
"""Allow access to a share.
|
|
|
|
:param context: The `context.RequestContext` object for the request
|
|
:param share: Share to which access will be allowed.
|
|
:param access: Information about the access that will be allowed, e.g.
|
|
host allowed, type of access granted.
|
|
:param share_server: Data structure with share server information.
|
|
Not used by this driver.
|
|
"""
|
|
if ('nfs', 'ip') != (share['share_proto'].lower(),
|
|
access['access_type'].lower()):
|
|
msg = _("Only NFS protocol and IP access type currently "
|
|
"supported.")
|
|
raise exception.InvalidShareAccess(reason=msg)
|
|
|
|
LOG.debug("Sending HNAS Request to allow access to share: "
|
|
"%(shr)s.", {'shr': (share['id'])})
|
|
|
|
share_id = self._get_hnas_share_id(share['id'])
|
|
|
|
self.hnas.allow_access(share_id, access['access_to'],
|
|
share['share_proto'],
|
|
access['access_level'])
|
|
|
|
LOG.info(_LI("Access allowed successfully to share: %(shr)s."),
|
|
{'shr': six.text_type(share['id'])})
|
|
|
|
def deny_access(self, context, share, access, share_server=None):
|
|
"""Deny access to a share.
|
|
|
|
:param context: The `context.RequestContext` object for the request
|
|
:param share: Share to which access will be denied.
|
|
:param access: Information about the access that will be denied, e.g.
|
|
host and type of access denied.
|
|
:param share_server: Data structure with share server information.
|
|
Not used by this driver.
|
|
"""
|
|
if ('nfs', 'ip') != (share['share_proto'].lower(),
|
|
access['access_type'].lower()):
|
|
msg = _("Only NFS protocol and IP access type currently "
|
|
"supported.")
|
|
raise exception.InvalidShareAccess(reason=msg)
|
|
|
|
LOG.debug("Sending HNAS request to deny access to share:"
|
|
" %(shr_id)s.",
|
|
{'shr_id': six.text_type(share['id'])})
|
|
|
|
share_id = self._get_hnas_share_id(share['id'])
|
|
|
|
self.hnas.deny_access(share_id, access['access_to'],
|
|
share['share_proto'], access['access_level'])
|
|
|
|
LOG.info(_LI("Access denied successfully to share: %(shr)s."),
|
|
{'shr': six.text_type(share['id'])})
|
|
|
|
def create_share(self, context, share, share_server=None):
|
|
"""Creates share.
|
|
|
|
:param context: The `context.RequestContext` object for the request
|
|
:param share: Share that will be created.
|
|
:param share_server: Data structure with share server information.
|
|
Not used by this driver.
|
|
:returns: Returns a path of EVS IP concatenate with the path
|
|
of share in the filesystem (e.g. ['172.24.44.10:/shares/id']).
|
|
"""
|
|
LOG.debug("Creating share in HNAS: %(shr)s.",
|
|
{'shr': six.text_type(share['id'])})
|
|
|
|
if share['share_proto'].lower() != 'nfs':
|
|
msg = _("Only NFS protocol is currently supported.")
|
|
raise exception.ShareBackendException(msg=msg)
|
|
|
|
ip = self.hnas_evs_ip
|
|
|
|
path = self.hnas.create_share(share['id'], share['size'],
|
|
share['share_proto'])
|
|
|
|
LOG.debug("Share created successfully on path: %(ip)s:%(path)s.",
|
|
{'ip': ip, 'path': path})
|
|
return ip + ":" + path
|
|
|
|
def delete_share(self, context, share, share_server=None):
|
|
"""Deletes share.
|
|
|
|
:param context: The `context.RequestContext` object for the request
|
|
:param share: Share that will be deleted.
|
|
:param share_server: Data structure with share server information.
|
|
Not used by this driver.
|
|
"""
|
|
share_id = self._get_hnas_share_id(share['id'])
|
|
|
|
LOG.debug("Deleting share in HNAS: %(shr)s.",
|
|
{'shr': six.text_type(share['id'])})
|
|
|
|
self.hnas.delete_share(share_id, share['share_proto'])
|
|
|
|
def create_snapshot(self, context, snapshot, share_server=None):
|
|
"""Creates snapshot.
|
|
|
|
:param context: The `context.RequestContext` object for the request
|
|
:param snapshot: Snapshot that will be created.
|
|
:param share_server: Data structure with share server information.
|
|
Not used by this driver.
|
|
"""
|
|
share_id = self._get_hnas_share_id(snapshot['share_id'])
|
|
|
|
LOG.debug("The snapshot of share %(ss_sid)s will be created with "
|
|
"id %(ss_id)s.", {'ss_sid': snapshot['share_id'],
|
|
'ss_id': snapshot['id']})
|
|
|
|
self.hnas.create_snapshot(share_id, snapshot['id'])
|
|
LOG.info(_LI("Snapshot %(id)s successfully created."),
|
|
{'id': snapshot['id']})
|
|
|
|
def delete_snapshot(self, context, snapshot, share_server=None):
|
|
"""Deletes snapshot.
|
|
|
|
:param context: The `context.RequestContext` object for the request
|
|
:param snapshot: Snapshot that will be deleted.
|
|
:param share_server:Data structure with share server information.
|
|
Not used by this driver.
|
|
"""
|
|
share_id = self._get_hnas_share_id(snapshot['share_id'])
|
|
|
|
LOG.debug("The snapshot %(ss_sid)s will be deleted. The related "
|
|
"share ID is %(ss_id)s.",
|
|
{'ss_sid': snapshot['share_id'], 'ss_id': snapshot['id']})
|
|
|
|
self.hnas.delete_snapshot(share_id, snapshot['id'])
|
|
LOG.info(_LI("Snapshot %(id)s successfully deleted."),
|
|
{'id': snapshot['id']})
|
|
|
|
def create_share_from_snapshot(self, context, share, snapshot,
|
|
share_server=None):
|
|
"""Creates a new share from snapshot.
|
|
|
|
:param context: The `context.RequestContext` object for the request
|
|
:param share: Information about the new share.
|
|
:param snapshot: Information about the snapshot that will be copied
|
|
to new share.
|
|
:param share_server: Data structure with share server information.
|
|
Not used by this driver.
|
|
:returns: Returns a path of EVS IP concatenate with the path
|
|
of new share in the filesystem (e.g. ['172.24.44.10:/shares/id']).
|
|
"""
|
|
LOG.debug("Creating a new share from snapshot: %(ss_id)s.",
|
|
{'ss_id': six.text_type(snapshot['id'])})
|
|
|
|
ip = self.hnas_evs_ip
|
|
path = self.hnas.create_share_from_snapshot(share, snapshot)
|
|
|
|
LOG.debug("Share created successfully on path: %(ip)s:%(path)s.",
|
|
{'ip': ip, 'path': path})
|
|
return ip + ":" + path
|
|
|
|
def ensure_share(self, context, share, share_server=None):
|
|
"""Ensure that share is exported.
|
|
|
|
:param context: The `context.RequestContext` object for the request
|
|
:param share: Share that will be checked.
|
|
:param share_server: Data structure with share server information.
|
|
Not used by this driver.
|
|
:returns: Returns a list of EVS IP concatenated with the path
|
|
of share in the filesystem (e.g. ['172.24.44.10:/shares/id']).
|
|
"""
|
|
LOG.debug("Ensuring share in HNAS: %(shr)s.",
|
|
{'shr': six.text_type(share['id'])})
|
|
|
|
if share['share_proto'].lower() != 'nfs':
|
|
msg = _("Only NFS protocol is currently supported.")
|
|
raise exception.ShareBackendException(msg=msg)
|
|
|
|
path = self.hnas.ensure_share(share['id'], share['share_proto'])
|
|
|
|
export = self.hnas_evs_ip + ":" + path
|
|
export_list = [export]
|
|
|
|
LOG.debug("Share ensured in HNAS: %(shr)s.",
|
|
{'shr': six.text_type(share['id'])})
|
|
return export_list
|
|
|
|
def extend_share(self, share, new_size, share_server=None):
|
|
"""Extends a share to new size.
|
|
|
|
:param share: Share that will be extended.
|
|
:param new_size: New size of share.
|
|
:param share_server: Data structure with share server information.
|
|
Not used by this driver.
|
|
"""
|
|
share_id = self._get_hnas_share_id(share['id'])
|
|
|
|
LOG.debug("Expanding share in HNAS: %(shr_id)s.",
|
|
{'shr_id': six.text_type(share['id'])})
|
|
|
|
if share['share_proto'].lower() != 'nfs':
|
|
msg = _("Only NFS protocol is currently supported.")
|
|
raise exception.ShareBackendException(msg=msg)
|
|
|
|
self.hnas.extend_share(share_id, new_size, share['share_proto'])
|
|
LOG.info(_LI("Share %(shr_id)s successfully extended to "
|
|
"%(shr_size)s."),
|
|
{'shr_id': six.text_type(share['id']),
|
|
'shr_size': six.text_type(new_size)})
|
|
|
|
# TODO(alyson): Implement in DHSS = true mode
|
|
def get_network_allocations_number(self):
|
|
"""Track allocations_number in DHSS = true.
|
|
|
|
When using the setting driver_handles_share_server = false
|
|
does not require to track allocations_number because we do not handle
|
|
network stuff.
|
|
"""
|
|
return 0
|
|
|
|
def _update_share_stats(self):
|
|
"""Updates the Capability of Backend."""
|
|
LOG.debug("Updating Backend Capability Information - HDS HNAS.")
|
|
|
|
total_space, free_space = self.hnas.get_stats()
|
|
|
|
reserved = self.configuration.safe_get('reserved_share_percentage')
|
|
|
|
data = {
|
|
'share_backend_name': self.backend_name,
|
|
'driver_handles_share_servers': self.driver_handles_share_servers,
|
|
'vendor_name': 'HDS',
|
|
'driver_version': '1.0',
|
|
'storage_protocol': 'NFS',
|
|
'total_capacity_gb': total_space,
|
|
'free_capacity_gb': free_space,
|
|
'reserved_percentage': reserved,
|
|
'QoS_support': False,
|
|
}
|
|
|
|
LOG.info(_LI("HNAS Capabilities: %(data)s."),
|
|
{'data': six.text_type(data)})
|
|
|
|
super(HDSHNASDriver, self)._update_share_stats(data)
|
|
|
|
def manage_existing(self, share, driver_options):
|
|
"""Manages a share that exists on backend.
|
|
|
|
:param share: Share that will be managed.
|
|
:param driver_options: Empty dict or dict with 'volume_id' option.
|
|
:returns: Returns a dict with size of share managed
|
|
and its location (your path in file-system).
|
|
"""
|
|
share_id = self._get_hnas_share_id(share['id'])
|
|
|
|
LOG.info(_LI("Share %(shr_path)s will be managed with ID %(shr_id)s."),
|
|
{'shr_path': six.text_type(
|
|
share['export_locations'][0]['path']),
|
|
'shr_id': six.text_type(share_id)})
|
|
|
|
old_path_info = share['export_locations'][0]['path'].split(':')
|
|
old_path = old_path_info[1].split('/')
|
|
|
|
if len(old_path) == 3:
|
|
evs_ip = old_path_info[0]
|
|
share_id = old_path[2]
|
|
else:
|
|
msg = _("Incorrect path. It should have the following format: "
|
|
"IP:/shares/share_id.")
|
|
raise exception.ShareBackendException(msg=msg)
|
|
|
|
if evs_ip != self.hnas_evs_ip:
|
|
msg = _("The EVS IP %(evs)s is not "
|
|
"configured.") % {'evs': six.text_type(evs_ip)}
|
|
raise exception.ShareBackendException(msg=msg)
|
|
|
|
if six.text_type(self.backend_name) not in share['host']:
|
|
msg = _("The backend passed in the host parameter (%(shr)s) is "
|
|
"not configured.") % {'shr': share['host']}
|
|
raise exception.ShareBackendException(msg=msg)
|
|
|
|
output = self.hnas.manage_existing(share, share_id)
|
|
self.private_storage.update(
|
|
share['id'], {'hnas_id': share_id})
|
|
|
|
return output
|
|
|
|
def unmanage(self, share):
|
|
"""Unmanages a share.
|
|
|
|
:param share: Share that will be unmanaged.
|
|
"""
|
|
self.private_storage.delete(share['id'])
|
|
|
|
LOG.info(_LI("The share with current path %(shr_path)s and ID "
|
|
"%(shr_id)s is no longer being managed."),
|
|
{'shr_path': six.text_type(
|
|
share['export_locations'][0]['path']),
|
|
'shr_id': six.text_type(share['id'])})
|
|
|
|
def _get_hnas_share_id(self, share_id):
|
|
hnas_id = self.private_storage.get(share_id, 'hnas_id')
|
|
|
|
if hnas_id is None:
|
|
hnas_id = share_id
|
|
return hnas_id
|