Shared filesystem management project for OpenStack.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

927 lines
40 KiB

# Copyright (c) 2016 QNAP 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.
"""
Share driver for QNAP Storage.
This driver supports QNAP Storage for NFS.
"""
import datetime
import math
import re
import time
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import timeutils
from oslo_utils import units
from manila.common import constants
from manila import exception
from manila.i18n import _
from manila import share
from manila.share import driver
from manila.share.drivers.qnap import api
from manila.share import share_types
from manila import utils
LOG = logging.getLogger(__name__)
qnap_manila_opts = [
cfg.StrOpt('qnap_management_url',
required=True,
help='The URL to manage QNAP Storage.'),
cfg.HostAddressOpt('qnap_share_ip',
required=True,
help='NAS share IP for mounting shares.'),
cfg.StrOpt('qnap_nas_login',
required=True,
help='Username for QNAP storage.'),
cfg.StrOpt('qnap_nas_password',
required=True,
secret=True,
help='Password for QNAP storage.'),
cfg.StrOpt('qnap_poolname',
required=True,
help='Pool within which QNAP shares must be created.'),
]
CONF = cfg.CONF
CONF.register_opts(qnap_manila_opts)
class QnapShareDriver(driver.ShareDriver):
"""OpenStack driver to enable QNAP Storage.
Version history:
1.0.0 - Initial driver (Only NFS)
1.0.1 - Add support for QES fw 1.1.4.
1.0.2 - Fix bug #1736370, QNAP Manila driver: Access rule setting is
override by the another access rule.
1.0.3 - Add supports for Thin Provisioning, SSD Cache, Deduplication
and Compression.
1.0.4 - Add support for QES fw 2.0.0.
1.0.5 - Fix bug #1773761, when user tries to manage share, the size
of managed share should not be changed.
1.0.6 - Add support for QES fw 2.1.0.
1.0.7 - Add support for QES fw on TDS series NAS model.
1.0.8 - Fix bug, driver should not manage snapshot which does not
exist in NAS.
Fix bug, driver should create share from snapshot with
specified size.
"""
DRIVER_VERSION = '1.0.8'
def __init__(self, *args, **kwargs):
"""Initialize QnapShareDriver."""
super(QnapShareDriver, self).__init__(False, *args, **kwargs)
self.private_storage = kwargs.get('private_storage')
self.api_executor = None
self.group_stats = {}
self.configuration.append_config_values(qnap_manila_opts)
self.share_api = share.API()
def do_setup(self, context):
"""Setup the QNAP Manila share driver."""
self.ctxt = context
LOG.debug('context: %s', context)
# Setup API Executor
try:
self.api_executor = self._create_api_executor()
except Exception:
LOG.exception('Failed to create HTTP client. Check IP '
'address, port, username, password and make '
'sure the array version is compatible.')
raise
def check_for_setup_error(self):
"""Check the status of setup."""
if self.api_executor is None:
msg = _("Failed to instantiate API client to communicate with "
"QNAP storage systems.")
raise exception.ShareBackendException(msg=msg)
def _create_api_executor(self):
"""Create API executor by NAS model."""
"""LOG.debug('CONF.qnap_nas_login=%(conf)s',
{'conf': CONF.qnap_nas_login})
LOG.debug('self.configuration.qnap_nas_login=%(conf)s',
{'conf': self.configuration.qnap_nas_login})"""
self.api_executor = api.QnapAPIExecutor(
username=self.configuration.qnap_nas_login,
password=self.configuration.qnap_nas_password,
management_url=self.configuration.qnap_management_url)
display_model_name, internal_model_name, fw_version = (
self.api_executor.get_basic_info(
self.configuration.qnap_management_url))
pattern = re.compile(r"^([A-Z]+)-?[A-Z]{0,2}(\d+)\d{2}(U|[a-z]*)")
matches = pattern.match(display_model_name)
if not matches:
return None
model_type = matches.group(1)
ts_model_types = (
"TS", "SS", "IS", "TVS", "TBS"
)
tes_model_types = (
"TES", "TDS"
)
es_model_types = (
"ES",
)
if model_type in ts_model_types:
if (fw_version.startswith("4.2") or fw_version.startswith("4.3")):
LOG.debug('Create TS API Executor')
# modify the pool name to pool index
self.configuration.qnap_poolname = (
self._get_ts_model_pool_id(
self.configuration.qnap_poolname))
return api.QnapAPIExecutorTS(
username=self.configuration.qnap_nas_login,
password=self.configuration.qnap_nas_password,
management_url=self.configuration.qnap_management_url)
elif model_type in tes_model_types:
if 'TS' in internal_model_name:
if (fw_version.startswith("4.2") or
fw_version.startswith("4.3")):
LOG.debug('Create TS API Executor')
# modify the pool name to pool index
self.configuration.qnap_poolname = (
self._get_ts_model_pool_id(
self.configuration.qnap_poolname))
return api.QnapAPIExecutorTS(
username=self.configuration.qnap_nas_login,
password=self.configuration.qnap_nas_password,
management_url=self.configuration.qnap_management_url)
elif "1.1.2" <= fw_version <= "2.1.9999":
LOG.debug('Create ES API Executor')
return api.QnapAPIExecutor(
username=self.configuration.qnap_nas_login,
password=self.configuration.qnap_nas_password,
management_url=self.configuration.qnap_management_url)
elif model_type in es_model_types:
if "1.1.2" <= fw_version <= "2.1.9999":
LOG.debug('Create ES API Executor')
return api.QnapAPIExecutor(
username=self.configuration.qnap_nas_login,
password=self.configuration.qnap_nas_password,
management_url=self.configuration.qnap_management_url)
msg = _('QNAP Storage model is not supported by this driver.')
raise exception.ShareBackendException(msg=msg)
def _get_ts_model_pool_id(self, pool_name):
"""Modify the pool name to pool index."""
pattern = re.compile(r"^(\d+)+|^Storage Pool (\d+)+")
matches = pattern.match(pool_name)
if matches.group(1):
return matches.group(1)
else:
return matches.group(2)
@utils.synchronized('qnap-gen_name')
def _gen_random_name(self, type):
if type == 'share':
infix = "shr-"
elif type == 'snapshot':
infix = "snp-"
elif type == 'host':
infix = "hst-"
else:
infix = ""
return ("manila-%(ifx)s%(time)s" %
{'ifx': infix,
'time': timeutils.utcnow().strftime('%Y%m%d%H%M%S%f')})
def _gen_host_name(self, vol_name_timestamp, access_level):
# host_name will be manila-{vol_name_timestamp}-ro or
# manila-{vol_name_timestamp}-rw
return 'manila-{}-{}'.format(vol_name_timestamp, access_level)
def _get_timestamp_from_vol_name(self, vol_name):
vol_name_split = vol_name.split('-')
dt = datetime.datetime.strptime(vol_name_split[2], '%Y%m%d%H%M%S%f')
return int(time.mktime(dt.timetuple()))
def _get_location_path(self, share_name, share_proto, ip, vol_id):
if share_proto == 'NFS':
vol = self.api_executor.get_specific_volinfo(vol_id)
vol_mount_path = vol.find('vol_mount_path').text
location = '%s:%s' % (ip, vol_mount_path)
else:
msg = _('Invalid NAS protocol: %s') % share_proto
raise exception.InvalidInput(reason=msg)
export_location = {
'path': location,
'is_admin_only': False,
}
return export_location
def _update_share_stats(self):
"""Get latest share stats."""
backend_name = (self.configuration.safe_get(
'share_backend_name') or
self.__class__.__name__)
LOG.debug('backend_name=%(backend_name)s',
{'backend_name': backend_name})
selected_pool = self.api_executor.get_specific_poolinfo(
self.configuration.qnap_poolname)
total_capacity_gb = (int(selected_pool.find('capacity_bytes').text) /
units.Gi)
LOG.debug('total_capacity_gb: %s GB', total_capacity_gb)
free_capacity_gb = (int(selected_pool.find('freesize_bytes').text) /
units.Gi)
LOG.debug('free_capacity_gb: %s GB', free_capacity_gb)
alloc_capacity_gb = (int(selected_pool.find('allocated_bytes').text) /
units.Gi)
LOG.debug('allocated_capacity_gb: %s GB', alloc_capacity_gb)
reserved_percentage = self.configuration.safe_get(
'reserved_share_percentage')
# single pool now, need support multiple pools in the future
single_pool = {
"pool_name": self.configuration.qnap_poolname,
"total_capacity_gb": total_capacity_gb,
"free_capacity_gb": free_capacity_gb,
"allocated_capacity_gb": alloc_capacity_gb,
"reserved_percentage": reserved_percentage,
"qos": False,
"dedupe": [True, False],
"compression": [True, False],
"thin_provisioning": [True, False],
"qnap_ssd_cache": [True, False]
}
data = {
"share_backend_name": backend_name,
"vendor_name": "QNAP",
"driver_version": self.DRIVER_VERSION,
"storage_protocol": "NFS",
"snapshot_support": True,
"create_share_from_snapshot_support": True,
"driver_handles_share_servers": self.configuration.safe_get(
'driver_handles_share_servers'),
'pools': [single_pool],
}
super(QnapShareDriver, self)._update_share_stats(data)
@utils.retry(exception=exception.ShareBackendException,
interval=3,
retries=5)
@utils.synchronized('qnap-create_share')
def create_share(self, context, share, share_server=None):
"""Create a new share."""
LOG.debug('share: %s', share.__dict__)
extra_specs = share_types.get_extra_specs_from_share(share)
LOG.debug('extra_specs: %s', extra_specs)
qnap_thin_provision = share_types.parse_boolean_extra_spec(
'thin_provisioning', extra_specs.get("thin_provisioning") or
extra_specs.get('capabilities:thin_provisioning') or 'true')
qnap_compression = share_types.parse_boolean_extra_spec(
'compression', extra_specs.get("compression") or
extra_specs.get('capabilities:compression') or 'true')
qnap_deduplication = share_types.parse_boolean_extra_spec(
'dedupe', extra_specs.get("dedupe") or
extra_specs.get('capabilities:dedupe') or 'false')
qnap_ssd_cache = share_types.parse_boolean_extra_spec(
'qnap_ssd_cache', extra_specs.get("qnap_ssd_cache") or
extra_specs.get("capabilities:qnap_ssd_cache") or 'false')
LOG.debug('qnap_thin_provision: %(qnap_thin_provision)s '
'qnap_compression: %(qnap_compression)s '
'qnap_deduplication: %(qnap_deduplication)s '
'qnap_ssd_cache: %(qnap_ssd_cache)s',
{'qnap_thin_provision': qnap_thin_provision,
'qnap_compression': qnap_compression,
'qnap_deduplication': qnap_deduplication,
'qnap_ssd_cache': qnap_ssd_cache})
share_proto = share['share_proto']
# User could create two shares with the same name on horizon.
# Therefore, we should not use displayname to create shares on NAS.
create_share_name = self._gen_random_name("share")
# If share name exists, need to change to another name.
created_share = self.api_executor.get_share_info(
self.configuration.qnap_poolname,
vol_label=create_share_name)
LOG.debug('created_share: %s', created_share)
if created_share is not None:
msg = (_("The share name %s is used by other share on NAS.") %
create_share_name)
LOG.error(msg)
raise exception.ShareBackendException(msg=msg)
if (qnap_deduplication and not qnap_thin_provision):
msg = _("Dedupe cannot be enabled without thin_provisioning.")
LOG.debug('Dedupe cannot be enabled without thin_provisioning.')
raise exception.InvalidExtraSpec(reason=msg)
self.api_executor.create_share(
share,
self.configuration.qnap_poolname,
create_share_name,
share_proto,
qnap_thin_provision=qnap_thin_provision,
qnap_compression=qnap_compression,
qnap_deduplication=qnap_deduplication,
qnap_ssd_cache=qnap_ssd_cache)
created_share = self._get_share_info(create_share_name)
volID = created_share.find('vol_no').text
# Use private_storage to record volume ID and Name created in the NAS.
LOG.debug('volID: %(volID)s '
'volName: %(create_share_name)s',
{'volID': volID,
'create_share_name': create_share_name})
_metadata = {'volID': volID,
'volName': create_share_name,
'thin_provision': qnap_thin_provision,
'compression': qnap_compression,
'deduplication': qnap_deduplication,
'ssd_cache': qnap_ssd_cache}
self.private_storage.update(share['id'], _metadata)
return self._get_location_path(create_share_name,
share['share_proto'],
self.configuration.qnap_share_ip,
volID)
@utils.retry(exception=exception.ShareBackendException,
interval=5, retries=5, backoff_rate=1)
def _get_share_info(self, share_name):
share = self.api_executor.get_share_info(
self.configuration.qnap_poolname,
vol_label=share_name)
if share is None:
msg = _("Fail to get share info of %s on NAS.") % share_name
LOG.error(msg)
raise exception.ShareBackendException(msg=msg)
else:
return share
@utils.synchronized('qnap-delete_share')
def delete_share(self, context, share, share_server=None):
"""Delete the specified share."""
# Use private_storage to retrieve volume ID created in the NAS.
volID = self.private_storage.get(share['id'], 'volID')
if not volID:
LOG.warning('volID for Share %s does not exist', share['id'])
return
LOG.debug('volID: %s', volID)
del_share = self.api_executor.get_share_info(
self.configuration.qnap_poolname,
vol_no=volID)
if del_share is None:
LOG.warning('Share %s does not exist', share['id'])
return
vol_no = del_share.find('vol_no').text
self.api_executor.delete_share(vol_no)
self.private_storage.delete(share['id'])
@utils.synchronized('qnap-extend_share')
def extend_share(self, share, new_size, share_server=None):
"""Extend an existing share."""
LOG.debug('Entering extend_share share_name=%(share_name)s '
'share_id=%(share_id)s '
'new_size=%(size)s',
{'share_name': share['display_name'],
'share_id': share['id'],
'size': new_size})
# Use private_storage to retrieve volume Name created in the NAS.
volName = self.private_storage.get(share['id'], 'volName')
if not volName:
LOG.debug('Share %s does not exist', share['id'])
raise exception.ShareResourceNotFound(share_id=share['id'])
LOG.debug('volName: %s', volName)
thin_provision = self.private_storage.get(
share['id'], 'thin_provision')
compression = self.private_storage.get(share['id'], 'compression')
deduplication = self.private_storage.get(share['id'], 'deduplication')
ssd_cache = self.private_storage.get(share['id'], 'ssd_cache')
LOG.debug('thin_provision: %(thin_provision)s '
'compression: %(compression)s '
'deduplication: %(deduplication)s '
'ssd_cache: %(ssd_cache)s',
{'thin_provision': thin_provision,
'compression': compression,
'deduplication': deduplication,
'ssd_cache': ssd_cache})
share_dict = {
'sharename': volName,
'old_sharename': volName,
'new_size': new_size,
'thin_provision': thin_provision == 'True',
'compression': compression == 'True',
'deduplication': deduplication == 'True',
'ssd_cache': ssd_cache == 'True',
'share_proto': share['share_proto']
}
self.api_executor.edit_share(share_dict)
@utils.retry(exception=exception.ShareBackendException,
interval=3,
retries=5)
@utils.synchronized('qnap-create_snapshot')
def create_snapshot(self, context, snapshot, share_server=None):
"""Create a snapshot."""
LOG.debug('snapshot[share][share_id]: %s',
snapshot['share']['share_id'])
LOG.debug('snapshot id: %s', snapshot['id'])
# Use private_storage to retrieve volume ID created in the NAS.
volID = self.private_storage.get(snapshot['share']['id'], 'volID')
if not volID:
LOG.warning(
'volID for Share %s does not exist',
snapshot['share']['id'])
raise exception.ShareResourceNotFound(
share_id=snapshot['share']['id'])
LOG.debug('volID: %s', volID)
# User could create two snapshot with the same name on horizon.
# Therefore, we should not use displayname to create snapshot on NAS.
# if snapshot exist, need to change another
create_snapshot_name = self._gen_random_name("snapshot")
LOG.debug('create_snapshot_name: %s', create_snapshot_name)
check_snapshot = self.api_executor.get_snapshot_info(
volID=volID, snapshot_name=create_snapshot_name)
if check_snapshot is not None:
msg = _("Failed to create an unused snapshot name.")
raise exception.ShareBackendException(msg=msg)
LOG.debug('create_snapshot_name: %s', create_snapshot_name)
self.api_executor.create_snapshot_api(volID, create_snapshot_name)
snapshot_id = ""
created_snapshot = self.api_executor.get_snapshot_info(
volID=volID, snapshot_name=create_snapshot_name)
if created_snapshot is not None:
snapshot_id = created_snapshot.find('snapshot_id').text
else:
msg = _("Failed to get snapshot information.")
raise exception.ShareBackendException(msg=msg)
LOG.debug('created_snapshot: %s', created_snapshot)
LOG.debug('snapshot_id: %s', snapshot_id)
# Use private_storage to record data instead of metadata.
_metadata = {'snapshot_id': snapshot_id}
self.private_storage.update(snapshot['id'], _metadata)
# Test to get value from private_storage.
snapshot_id = self.private_storage.get(snapshot['id'], 'snapshot_id')
LOG.debug('snapshot_id: %s', snapshot_id)
return {'provider_location': snapshot_id}
@utils.synchronized('qnap-delete_snapshot')
def delete_snapshot(self, context, snapshot, share_server=None):
"""Delete a snapshot."""
LOG.debug('Entering delete_snapshot. The deleted snapshot=%(snap)s',
{'snap': snapshot['id']})
snapshot_id = (snapshot.get('provider_location') or
self.private_storage.get(snapshot['id'], 'snapshot_id'))
if not snapshot_id:
LOG.warning('Snapshot %s does not exist', snapshot['id'])
return
LOG.debug('snapshot_id: %s', snapshot_id)
self.api_executor.delete_snapshot_api(snapshot_id)
self.private_storage.delete(snapshot['id'])
@utils.retry(exception=exception.ShareBackendException,
interval=3,
retries=5)
@utils.synchronized('qnap-create_share_from_snapshot')
def create_share_from_snapshot(self, context, share, snapshot,
share_server=None, parent_share=None):
"""Create a share from a snapshot."""
LOG.debug('Entering create_share_from_snapshot. The source '
'snapshot=%(snap)s. The created share=%(share)s',
{'snap': snapshot['id'], 'share': share['id']})
snapshot_id = (snapshot.get('provider_location') or
self.private_storage.get(snapshot['id'], 'snapshot_id'))
if not snapshot_id:
LOG.warning('Snapshot %s does not exist', snapshot['id'])
raise exception.SnapshotResourceNotFound(name=snapshot['id'])
LOG.debug('snapshot_id: %s', snapshot_id)
create_share_name = self._gen_random_name("share")
# if sharename exist, need to change another
created_share = self.api_executor.get_share_info(
self.configuration.qnap_poolname,
vol_label=create_share_name)
if created_share is not None:
msg = _("Failed to create an unused share name.")
raise exception.ShareBackendException(msg=msg)
self.api_executor.clone_snapshot(snapshot_id,
create_share_name, share['size'])
create_volID = ""
created_share = self.api_executor.get_share_info(
self.configuration.qnap_poolname,
vol_label=create_share_name)
if created_share is not None:
create_volID = created_share.find('vol_no').text
LOG.debug('create_volID: %s', create_volID)
else:
msg = _("Failed to clone a snapshot in time.")
raise exception.ShareBackendException(msg=msg)
thin_provision = self.private_storage.get(
snapshot['share_instance_id'], 'thin_provision')
compression = self.private_storage.get(
snapshot['share_instance_id'], 'compression')
deduplication = self.private_storage.get(
snapshot['share_instance_id'], 'deduplication')
ssd_cache = self.private_storage.get(
snapshot['share_instance_id'], 'ssd_cache')
LOG.debug('thin_provision: %(thin_provision)s '
'compression: %(compression)s '
'deduplication: %(deduplication)s '
'ssd_cache: %(ssd_cache)s',
{'thin_provision': thin_provision,
'compression': compression,
'deduplication': deduplication,
'ssd_cache': ssd_cache})
# Use private_storage to record volume ID and Name created in the NAS.
_metadata = {
'volID': create_volID,
'volName': create_share_name,
'thin_provision': thin_provision,
'compression': compression,
'deduplication': deduplication,
'ssd_cache': ssd_cache
}
self.private_storage.update(share['id'], _metadata)
# Test to get value from private_storage.
volName = self.private_storage.get(share['id'], 'volName')
LOG.debug('volName: %s', volName)
return self._get_location_path(create_share_name,
share['share_proto'],
self.configuration.qnap_share_ip,
create_volID)
def _get_vol_host(self, host_list, vol_name_timestamp):
vol_host_list = []
if host_list is None:
return vol_host_list
for host in host_list:
# Check host alias name with prefix "manila-{vol_name_timestamp}"
# to find the host of this manila share.
LOG.debug('_get_vol_host name:%s', host.find('name').text)
# Because driver supports only IPv4 now, check "netaddrs"
# have "ipv4" tag to get address.
if re.match("^manila-{}".format(vol_name_timestamp),
host.find('name').text):
host_dict = {
'index': host.find('index').text,
'hostid': host.find('hostid').text,
'name': host.find('name').text,
'ipv4': [],
}
for ipv4 in host.findall('netaddrs/ipv4'):
host_dict['ipv4'].append(ipv4.text)
vol_host_list.append(host_dict)
LOG.debug('_get_vol_host vol_host_list:%s', vol_host_list)
return vol_host_list
@utils.synchronized('qnap-update_access')
def update_access(self, context, share, access_rules, add_rules,
delete_rules, share_server=None):
if not (add_rules or delete_rules):
volName = self.private_storage.get(share['id'], 'volName')
LOG.debug('volName: %s', volName)
if volName is None:
LOG.debug('Share %s does not exist', share['id'])
raise exception.ShareResourceNotFound(share_id=share['id'])
# Clear all current ACLs
self.api_executor.set_nfs_access(volName, 2, "all")
vol_name_timestamp = self._get_timestamp_from_vol_name(volName)
host_list = self.api_executor.get_host_list()
LOG.debug('host_list:%s', host_list)
vol_host_list = self._get_vol_host(host_list, vol_name_timestamp)
# If host already exist, delete the host
if len(vol_host_list) > 0:
for vol_host in vol_host_list:
self.api_executor.delete_host(vol_host['name'])
# Add each one through all rules.
for access in access_rules:
self._allow_access(context, share, access, share_server)
else:
# Adding/Deleting specific rules
for access in delete_rules:
self._deny_access(context, share, access, share_server)
for access in add_rules:
self._allow_access(context, share, access, share_server)
def _allow_access(self, context, share, access, share_server=None):
"""Allow access to the share."""
share_proto = share['share_proto']
access_type = access['access_type']
access_level = access['access_level']
access_to = access['access_to']
LOG.debug('share_proto: %(share_proto)s '
'access_type: %(access_type)s '
'access_level: %(access_level)s '
'access_to: %(access_to)s',
{'share_proto': share_proto,
'access_type': access_type,
'access_level': access_level,
'access_to': access_to})
self._check_share_access(share_proto, access_type)
vol_name = self.private_storage.get(share['id'], 'volName')
vol_name_timestamp = self._get_timestamp_from_vol_name(vol_name)
host_name = self._gen_host_name(vol_name_timestamp, access_level)
host_list = self.api_executor.get_host_list()
LOG.debug('vol_name: %(vol_name)s '
'access_level: %(access_level)s '
'host_name: %(host_name)s '
'host_list: %(host_list)s ',
{'vol_name': vol_name,
'access_level': access_level,
'host_name': host_name,
'host_list': host_list})
filter_host_list = self._get_vol_host(host_list, vol_name_timestamp)
if len(filter_host_list) == 0:
# if host does not exist, create a host for the share
self.api_executor.add_host(host_name, access_to)
elif (len(filter_host_list) == 1 and
filter_host_list[0]['name'] == host_name):
# if the host exist, and this host is for the same access right,
# add ip to the host.
ipv4_list = filter_host_list[0]['ipv4']
if access_to not in ipv4_list:
ipv4_list.append(access_to)
LOG.debug('vol_host["ipv4"]: %s', filter_host_list[0]['ipv4'])
LOG.debug('ipv4_list: %s', ipv4_list)
self.api_executor.edit_host(host_name, ipv4_list)
else:
# Until now, share of QNAP NAS can only apply one access level for
# all ips. "rw" for some ips and "ro" for else is not allowed.
support_level = (constants.ACCESS_LEVEL_RW if
access_level == constants.ACCESS_LEVEL_RO
else constants.ACCESS_LEVEL_RO)
reason = _('Share only supports one access '
'level: %s') % support_level
LOG.error(reason)
raise exception.InvalidShareAccess(reason=reason)
access = 1 if access_level == constants.ACCESS_LEVEL_RO else 0
self.api_executor.set_nfs_access(vol_name, access, host_name)
def _deny_access(self, context, share, access, share_server=None):
"""Deny access to the share."""
share_proto = share['share_proto']
access_type = access['access_type']
access_level = access['access_level']
access_to = access['access_to']
LOG.debug('share_proto: %(share_proto)s '
'access_type: %(access_type)s '
'access_level: %(access_level)s '
'access_to: %(access_to)s',
{'share_proto': share_proto,
'access_type': access_type,
'access_level': access_level,
'access_to': access_to})
try:
self._check_share_access(share_proto, access_type)
except exception.InvalidShareAccess:
LOG.warning('The denied rule is invalid and does not exist.')
return
vol_name = self.private_storage.get(share['id'], 'volName')
vol_name_timestamp = self._get_timestamp_from_vol_name(vol_name)
host_name = self._gen_host_name(vol_name_timestamp, access_level)
host_list = self.api_executor.get_host_list()
LOG.debug('vol_name: %(vol_name)s '
'access_level: %(access_level)s '
'host_name: %(host_name)s '
'host_list: %(host_list)s ',
{'vol_name': vol_name,
'access_level': access_level,
'host_name': host_name,
'host_list': host_list})
filter_host_list = self._get_vol_host(host_list, vol_name_timestamp)
# if share already have host, remove ip from host
for vol_host in filter_host_list:
if vol_host['name'] == host_name:
ipv4_list = vol_host['ipv4']
if access_to in ipv4_list:
ipv4_list.remove(access_to)
LOG.debug('vol_host["ipv4"]: %s', vol_host['ipv4'])
LOG.debug('ipv4_list: %s', ipv4_list)
if len(ipv4_list) == 0: # if list empty, remove the host
self.api_executor.set_nfs_access(
vol_name, 2, host_name)
self.api_executor.delete_host(host_name)
else:
self.api_executor.edit_host(host_name, ipv4_list)
break
def _check_share_access(self, share_proto, access_type):
if share_proto == 'NFS' and access_type != 'ip':
reason = _('Only "ip" access type is allowed for '
'NFS shares.')
LOG.warning(reason)
raise exception.InvalidShareAccess(reason=reason)
elif share_proto != 'NFS':
reason = _('Invalid NAS protocol: %s') % share_proto
raise exception.InvalidShareAccess(reason=reason)
def manage_existing(self, share, driver_options):
"""Manages a share that exists on backend."""
if share['share_proto'].lower() == 'nfs':
# 10.0.0.1:/share/example
LOG.info("Share %(shr_path)s will be managed with ID "
"%(shr_id)s.",
{'shr_path': share['export_locations'][0]['path'],
'shr_id': share['id']})
old_path_info = share['export_locations'][0]['path'].split(
':/share/')
if len(old_path_info) == 2:
ip = old_path_info[0]
share_name = old_path_info[1]
else:
msg = _("Incorrect path. It should have the following format: "
"IP:/share/share_name.")
raise exception.ShareBackendException(msg=msg)
else:
msg = _('Invalid NAS protocol: %s') % share['share_proto']
raise exception.InvalidInput(reason=msg)
if ip != self.configuration.qnap_share_ip:
msg = _("The NAS IP %(ip)s is not configured.") % {'ip': ip}
raise exception.ShareBackendException(msg=msg)
existing_share = self.api_executor.get_share_info(
self.configuration.qnap_poolname,
vol_label=share_name)
if existing_share is None:
msg = _("The share %s trying to be managed was not found on "
"backend.") % share['id']
raise exception.ManageInvalidShare(reason=msg)
extra_specs = share_types.get_extra_specs_from_share(share)
qnap_thin_provision = share_types.parse_boolean_extra_spec(
'thin_provisioning', extra_specs.get("thin_provisioning") or
extra_specs.get('capabilities:thin_provisioning') or 'true')
qnap_compression = share_types.parse_boolean_extra_spec(
'compression', extra_specs.get("compression") or
extra_specs.get('capabilities:compression') or 'true')
qnap_deduplication = share_types.parse_boolean_extra_spec(
'dedupe', extra_specs.get("dedupe") or
extra_specs.get('capabilities:dedupe') or 'false')
qnap_ssd_cache = share_types.parse_boolean_extra_spec(
'qnap_ssd_cache', extra_specs.get("qnap_ssd_cache") or
extra_specs.get("capabilities:qnap_ssd_cache") or 'false')
LOG.debug('qnap_thin_provision: %(qnap_thin_provision)s '
'qnap_compression: %(qnap_compression)s '
'qnap_deduplication: %(qnap_deduplication)s '
'qnap_ssd_cache: %(qnap_ssd_cache)s',
{'qnap_thin_provision': qnap_thin_provision,
'qnap_compression': qnap_compression,
'qnap_deduplication': qnap_deduplication,
'qnap_ssd_cache': qnap_ssd_cache})
if (qnap_deduplication and not qnap_thin_provision):
msg = _("Dedupe cannot be enabled without thin_provisioning.")
LOG.debug('Dedupe cannot be enabled without thin_provisioning.')
raise exception.InvalidExtraSpec(reason=msg)
vol_no = existing_share.find('vol_no').text
vol = self.api_executor.get_specific_volinfo(vol_no)
vol_size_gb = math.ceil(float(vol.find('size').text) / units.Gi)
share_dict = {
'sharename': share_name,
'old_sharename': share_name,
'thin_provision': qnap_thin_provision,
'compression': qnap_compression,
'deduplication': qnap_deduplication,
'ssd_cache': qnap_ssd_cache,
'share_proto': share['share_proto']
}
self.api_executor.edit_share(share_dict)
_metadata = {}
_metadata['volID'] = vol_no
_metadata['volName'] = share_name
_metadata['thin_provision'] = qnap_thin_provision
_metadata['compression'] = qnap_compression
_metadata['deduplication'] = qnap_deduplication
_metadata['ssd_cache'] = qnap_ssd_cache
self.private_storage.update(share['id'], _metadata)
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 = self._get_location_path(
share_name,
share['share_proto'],
self.configuration.qnap_share_ip,
vol_no)
return {'size': vol_size_gb, 'export_locations': export_locations}
def unmanage(self, share):
"""Remove the specified share from Manila management."""
self.private_storage.delete(share['id'])
def manage_existing_snapshot(self, snapshot, driver_options):
"""Manage existing share snapshot with manila."""
volID = self.private_storage.get(snapshot['share']['id'], 'volID')
LOG.debug('volID: %s', volID)
existing_share = self.api_executor.get_share_info(
self.configuration.qnap_poolname,
vol_no=volID)
if existing_share is None:
msg = _("The share id %s was not found on backend.") % volID
LOG.error(msg)
raise exception.ShareNotFound(msg)
snapshot_id = snapshot.get('provider_location')
snapshot_id_info = snapshot_id.split('@')
if len(snapshot_id_info) == 2:
share_name = snapshot_id_info[0]
snapshot_name = snapshot_id_info[1]
else:
msg = _("Incorrect provider_location format. It should have the "
"following format: share_name@snapshot_name.")
LOG.error(msg)
raise exception.InvalidParameterValue(msg)
if share_name != existing_share.find('vol_label').text:
msg = (_("The assigned share %(share_name)s was not matched "
"%(vol_label)s on backend.") %
{'share_name': share_name,
'vol_label': existing_share.find('vol_label').text})
LOG.error(msg)
raise exception.ShareNotFound(msg)
check_snapshot = self.api_executor.get_snapshot_info(
volID=volID, snapshot_name=snapshot_name)
if check_snapshot is None:
msg = (_("The snapshot %(snapshot_name)s was not "
"found on backend.") %
{'snapshot_name': snapshot_name})
LOG.error(msg)
raise exception.InvalidParameterValue(err=msg)
_metadata = {
'snapshot_id': snapshot_id,
}
self.private_storage.update(snapshot['id'], _metadata)
parent_size = check_snapshot.find('parent_size')
snap_size_gb = None
if parent_size is not None:
snap_size_gb = math.ceil(float(parent_size.text) / units.Gi)
return {'size': snap_size_gb}
def unmanage_snapshot(self, snapshot):
"""Remove the specified snapshot from Manila management."""
self.private_storage.delete(snapshot['id'])