manila/manila/share/drivers/hitachi/hsp/driver.py

367 lines
14 KiB
Python

# Copyright (c) 2016 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
from oslo_utils import excutils
from oslo_utils import units
from manila.common import constants
from manila import exception
from manila.i18n import _
from manila.share import driver
from manila.share.drivers.hitachi.hsp import rest
LOG = log.getLogger(__name__)
hitachi_hsp_opts = [
cfg.HostAddressOpt('hitachi_hsp_host',
required=True,
help="HSP management host for communication between "
"Manila controller and HSP."),
cfg.StrOpt('hitachi_hsp_username',
required=True,
help="HSP username to perform tasks such as create filesystems"
" and shares."),
cfg.StrOpt('hitachi_hsp_password',
required=True,
secret=True,
help="HSP password for the username provided."),
]
class HitachiHSPDriver(driver.ShareDriver):
"""Manila HSP Driver implementation.
1.0.0 - Initial Version.
"""
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(
[False], *args, config_opts=[hitachi_hsp_opts], **kwargs)
self.private_storage = kwargs.get('private_storage')
self.backend_name = self.configuration.safe_get('share_backend_name')
self.hsp_host = self.configuration.safe_get('hitachi_hsp_host')
self.hsp = rest.HSPRestBackend(
self.hsp_host,
self.configuration.safe_get('hitachi_hsp_username'),
self.configuration.safe_get('hitachi_hsp_password')
)
def _update_share_stats(self, data=None):
LOG.debug("Updating Backend Capability Information - Hitachi HSP.")
reserved = self.configuration.safe_get('reserved_share_percentage')
max_over_subscription_ratio = self.configuration.safe_get(
'max_over_subscription_ratio')
hsp_cluster = self.hsp.get_cluster()
total_space = hsp_cluster['properties']['total-storage-capacity']
free_space = hsp_cluster['properties']['total-storage-available']
data = {
'share_backend_name': self.backend_name,
'vendor_name': 'Hitachi',
'driver_version': '1.0.0',
'storage_protocol': 'NFS',
'pools': [{
'reserved_percentage': reserved,
'pool_name': 'HSP',
'thin_provisioning': True,
'total_capacity_gb': total_space / units.Gi,
'free_capacity_gb': free_space / units.Gi,
'max_over_subscription_ratio': max_over_subscription_ratio,
'qos': False,
'dedupe': False,
'compression': False,
}],
}
LOG.info("Hitachi HSP Capabilities: %(data)s.",
{'data': data})
super(HitachiHSPDriver, self)._update_share_stats(data)
def create_share(self, context, share, share_server=None):
LOG.debug("Creating share in HSP: %(shr)s", {'shr': share['id']})
if share['share_proto'].lower() != 'nfs':
msg = _("Only NFS protocol is currently supported.")
raise exception.InvalidShare(reason=msg)
self.hsp.add_file_system(share['id'], share['size'] * units.Gi)
filesystem_id = self.hsp.get_file_system(share['id'])['id']
try:
self.hsp.add_share(share['id'], filesystem_id)
except exception.HSPBackendException:
with excutils.save_and_reraise_exception():
self.hsp.delete_file_system(filesystem_id)
msg = ("Could not create share %s on HSP.")
LOG.exception(msg, share['id'])
uri = self.hsp_host + ':/' + share['id']
LOG.debug("Share created successfully on path: %(uri)s.",
{'uri': uri})
return [{
"path": uri,
"metadata": {},
"is_admin_only": False,
}]
def delete_share(self, context, share, share_server=None):
LOG.debug("Deleting share in HSP: %(shr)s.", {'shr': share['id']})
filesystem_id = hsp_share_id = None
try:
filesystem_id = self.hsp.get_file_system(share['id'])['id']
hsp_share_id = self.hsp.get_share(filesystem_id)['id']
except exception.HSPItemNotFoundException:
LOG.info("Share %(shr)s already removed from backend.",
{'shr': share['id']})
if hsp_share_id:
# Clean all rules from share before deleting it
current_rules = self.hsp.get_access_rules(hsp_share_id)
for rule in current_rules:
try:
self.hsp.delete_access_rule(hsp_share_id,
rule['name'])
except exception.HSPBackendException as e:
if 'No matching access rule found.' in e.msg:
LOG.debug("Rule %(rule)s already deleted in "
"backend.", {'rule': rule['name']})
else:
raise
self.hsp.delete_share(hsp_share_id)
if filesystem_id:
self.hsp.delete_file_system(filesystem_id)
LOG.debug("Export and share successfully deleted: %(shr)s.",
{'shr': share['id']})
def update_access(self, context, share, access_rules, add_rules,
delete_rules, share_server=None):
LOG.debug("Updating access rules for share: %(shr)s.",
{'shr': share['id']})
try:
filesystem_id = self.hsp.get_file_system(share['id'])['id']
hsp_share_id = self.hsp.get_share(filesystem_id)['id']
except exception.HSPItemNotFoundException:
raise exception.ShareResourceNotFound(share_id=share['id'])
if not (add_rules or delete_rules):
# Recovery mode
current_rules = self.hsp.get_access_rules(hsp_share_id)
# Indexing the rules for faster searching
hsp_rules_dict = {
rule['host-specification']: rule['read-write']
for rule in current_rules
}
manila_rules_dict = {}
for rule in access_rules:
if rule['access_type'].lower() != 'ip':
msg = _("Only IP access type currently supported.")
raise exception.InvalidShareAccess(reason=msg)
access_to = rule['access_to']
is_rw = rule['access_level'] == constants.ACCESS_LEVEL_RW
manila_rules_dict[access_to] = is_rw
# Remove the rules that exist on HSP but not on manila
remove_rules = self._get_complement(hsp_rules_dict,
manila_rules_dict)
# Add the rules that exist on manila but not on HSP
add_rules = self._get_complement(manila_rules_dict, hsp_rules_dict)
for rule in remove_rules:
rule_name = self._get_hsp_rule_name(hsp_share_id, rule[0])
self.hsp.delete_access_rule(hsp_share_id, rule_name)
for rule in add_rules:
self.hsp.add_access_rule(hsp_share_id, rule[0], rule[1])
else:
for rule in delete_rules:
if rule['access_type'].lower() != 'ip':
continue
# get the real rule name in HSP
rule_name = self._get_hsp_rule_name(hsp_share_id,
rule['access_to'])
try:
self.hsp.delete_access_rule(hsp_share_id,
rule_name)
except exception.HSPBackendException as e:
if 'No matching access rule found.' in e.msg:
LOG.debug("Rule %(rule)s already deleted in "
"backend.", {'rule': rule['access_to']})
else:
raise
for rule in add_rules:
if rule['access_type'].lower() != 'ip':
msg = _("Only IP access type currently supported.")
raise exception.InvalidShareAccess(reason=msg)
try:
self.hsp.add_access_rule(
hsp_share_id, rule['access_to'],
(rule['access_level'] == constants.ACCESS_LEVEL_RW))
except exception.HSPBackendException as e:
if 'Duplicate NFS access rule exists' in e.msg:
LOG.debug("Rule %(rule)s already exists in "
"backend.", {'rule': rule['access_to']})
else:
raise
LOG.debug("Successfully updated share %(shr)s rules.",
{'shr': share['id']})
def _get_hsp_rule_name(self, share_id, host_to):
rule_name = share_id + host_to
all_rules = self.hsp.get_access_rules(share_id)
for rule in all_rules:
# check if this rule has other name in HSP
if rule['host-specification'] == host_to:
rule_name = rule['name']
break
return rule_name
def _get_complement(self, rules_a, rules_b):
"""Returns the rules of list A that are not on list B"""
complement = []
for rule, is_rw in rules_a.items():
if rule not in rules_b or rules_b[rule] != is_rw:
complement.append((rule, is_rw))
return complement
def extend_share(self, share, new_size, share_server=None):
LOG.debug("Extending share in HSP: %(shr_id)s.",
{'shr_id': share['id']})
old_size = share['size']
hsp_cluster = self.hsp.get_cluster()
free_space = hsp_cluster['properties']['total-storage-available']
free_space = free_space / units.Gi
if (new_size - old_size) < free_space:
filesystem_id = self.hsp.get_file_system(share['id'])['id']
self.hsp.resize_file_system(filesystem_id, new_size * units.Gi)
else:
msg = (_("Share %s cannot be extended due to insufficient space.")
% share['id'])
raise exception.HSPBackendException(msg=msg)
LOG.info("Share %(shr_id)s successfully extended to "
"%(shr_size)sG.",
{'shr_id': share['id'],
'shr_size': new_size})
def shrink_share(self, share, new_size, share_server=None):
LOG.debug("Shrinking share in HSP: %(shr_id)s.",
{'shr_id': share['id']})
file_system = self.hsp.get_file_system(share['id'])
usage = file_system['properties']['used-capacity'] / units.Gi
LOG.debug("Usage for share %(shr_id)s in HSP: %(usage)sG.",
{'shr_id': share['id'], 'usage': usage})
if new_size > usage:
self.hsp.resize_file_system(file_system['id'], new_size * units.Gi)
else:
raise exception.ShareShrinkingPossibleDataLoss(
share_id=share['id'])
LOG.info("Share %(shr_id)s successfully shrunk to "
"%(shr_size)sG.",
{'shr_id': share['id'],
'shr_size': new_size})
def manage_existing(self, share, driver_options):
LOG.debug("Managing share in HSP: %(shr_id)s.",
{'shr_id': share['id']})
ip, share_name = share['export_locations'][0]['path'].split(':')
try:
hsp_share = self.hsp.get_share(name=share_name.strip('/'))
except exception.HSPItemNotFoundException:
msg = _("The share %s trying to be managed was not found on "
"backend.") % share['id']
raise exception.ManageInvalidShare(reason=msg)
self.hsp.rename_file_system(hsp_share['properties']['file-system-id'],
share['id'])
original_name = hsp_share['properties']['file-system-name']
private_storage_content = {
'old_name': original_name,
'new_name': share['id'],
}
self.private_storage.update(share['id'], private_storage_content)
LOG.debug("Filesystem %(original_name)s was renamed to %(name)s.",
{'original_name': original_name,
'name': share['id']})
file_system = self.hsp.get_file_system(share['id'])
LOG.info("Share %(shr_path)s was successfully managed with ID "
"%(shr_id)s.",
{'shr_path': share['export_locations'][0]['path'],
'shr_id': share['id']})
export_locations = [{
"path": share['export_locations'][0]['path'],
"metadata": {},
"is_admin_only": False,
}]
return {'size': file_system['properties']['quota'] / units.Gi,
'export_locations': export_locations}
def unmanage(self, share):
original_name = self.private_storage.get(share['id'], 'old_name')
LOG.debug("Filesystem %(name)s that was originally named "
"%(original_name)s will no longer be managed.",
{'original_name': original_name,
'name': share['id']})
self.private_storage.delete(share['id'])
LOG.info("The share with current path %(shr_path)s and ID "
"%(shr_id)s is no longer being managed.",
{'shr_path': share['export_locations'][0]['path'],
'shr_id': share['id']})
def get_default_filter_function(self):
return "share.size >= 128"