manila/manila/share/drivers/zfssa/zfssashare.py

520 lines
20 KiB
Python

# Copyright (c) 2014, Oracle and/or its affiliates. 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.
"""
ZFS Storage Appliance Manila Share Driver
"""
import base64
import math
from oslo_config import cfg
from oslo_log import log
from oslo_utils import units
import six
from manila import exception
from manila.i18n import _
from manila.share import driver
from manila.share.drivers.zfssa import zfssarest
ZFSSA_OPTS = [
cfg.HostAddressOpt('zfssa_host',
help='ZFSSA management IP address.'),
cfg.HostAddressOpt('zfssa_data_ip',
help='IP address for data.'),
cfg.StrOpt('zfssa_auth_user',
help='ZFSSA management authorized username.'),
cfg.StrOpt('zfssa_auth_password',
help='ZFSSA management authorized user\'s password.'),
cfg.StrOpt('zfssa_pool',
help='ZFSSA storage pool name.'),
cfg.StrOpt('zfssa_project',
help='ZFSSA project name.'),
cfg.StrOpt('zfssa_nas_checksum', default='fletcher4',
help='Controls checksum used for data blocks.'),
cfg.StrOpt('zfssa_nas_compression', default='off',
help='Data compression-off, lzjb, gzip-2, gzip, gzip-9.'),
cfg.StrOpt('zfssa_nas_logbias', default='latency',
help='Controls behavior when servicing synchronous writes.'),
cfg.StrOpt('zfssa_nas_mountpoint', default='',
help='Location of project in ZFS/SA.'),
cfg.StrOpt('zfssa_nas_quota_snap', default='true',
help='Controls whether a share quota includes snapshot.'),
cfg.StrOpt('zfssa_nas_rstchown', default='true',
help='Controls whether file ownership can be changed.'),
cfg.StrOpt('zfssa_nas_vscan', default='false',
help='Controls whether the share is scanned for viruses.'),
cfg.StrOpt('zfssa_rest_timeout',
help='REST connection timeout (in seconds).'),
cfg.StrOpt('zfssa_manage_policy', default='loose',
choices=['loose', 'strict'],
help='Driver policy for share manage. A strict policy checks '
'for a schema named manila_managed, and makes sure its '
'value is true. A loose policy does not check for the '
'schema.')
]
cfg.CONF.register_opts(ZFSSA_OPTS)
LOG = log.getLogger(__name__)
def factory_zfssa():
return zfssarest.ZFSSAApi()
class ZFSSAShareDriver(driver.ShareDriver):
"""ZFSSA share driver: Supports NFS and CIFS protocols.
Uses ZFSSA RESTful API to create shares and snapshots on backend.
API version history:
1.0 - Initial version.
1.0.1 - Add share shrink/extend feature.
1.0.2 - Add share manage/unmanage feature.
"""
VERSION = '1.0.2'
PROTOCOL = 'NFS_CIFS'
def __init__(self, *args, **kwargs):
super(ZFSSAShareDriver, self).__init__(False, *args, **kwargs)
self.configuration.append_config_values(ZFSSA_OPTS)
self.zfssa = None
self._stats = None
self.mountpoint = '/export/'
lcfg = self.configuration
required = [
'zfssa_host',
'zfssa_data_ip',
'zfssa_auth_user',
'zfssa_auth_password',
'zfssa_pool',
'zfssa_project'
]
for prop in required:
if not getattr(lcfg, prop, None):
exception_msg = _('%s is required in manila.conf') % prop
LOG.error(exception_msg)
raise exception.InvalidParameterValue(exception_msg)
self.default_args = {
'compression': lcfg.zfssa_nas_compression,
'logbias': lcfg.zfssa_nas_logbias,
'checksum': lcfg.zfssa_nas_checksum,
'vscan': lcfg.zfssa_nas_vscan,
'rstchown': lcfg.zfssa_nas_rstchown,
}
self.share_args = {
'sharedav': 'off',
'shareftp': 'off',
'sharesftp': 'off',
'sharetftp': 'off',
'root_permissions': '777',
'sharenfs': 'sec=sys',
'sharesmb': 'off',
'quota_snap': self.configuration.zfssa_nas_quota_snap,
'reservation_snap': self.configuration.zfssa_nas_quota_snap,
'custom:manila_managed': True,
}
def do_setup(self, context):
"""Login, create project, no sharing option enabled."""
lcfg = self.configuration
LOG.debug("Connecting to host: %s.", lcfg.zfssa_host)
self.zfssa = factory_zfssa()
self.zfssa.set_host(lcfg.zfssa_host, timeout=lcfg.zfssa_rest_timeout)
creds = '%s:%s' % (lcfg.zfssa_auth_user, lcfg.zfssa_auth_password)
auth_str = base64.encodestring(six.b(creds))[:-1]
self.zfssa.login(auth_str)
if lcfg.zfssa_nas_mountpoint == '':
self.mountpoint += lcfg.zfssa_project
else:
self.mountpoint += lcfg.zfssa_nas_mountpoint
arg = {
'name': lcfg.zfssa_project,
'sharesmb': 'off',
'sharenfs': 'off',
'mountpoint': self.mountpoint,
}
arg.update(self.default_args)
self.zfssa.create_project(lcfg.zfssa_pool, lcfg.zfssa_project, arg)
self.zfssa.enable_service('nfs')
self.zfssa.enable_service('smb')
schema = {
'property': 'manila_managed',
'description': 'Managed by Manila',
'type': 'Boolean',
}
self.zfssa.create_schema(schema)
def check_for_setup_error(self):
"""Check for properly configured pool, project."""
lcfg = self.configuration
LOG.debug("Verifying pool %s.", lcfg.zfssa_pool)
self.zfssa.verify_pool(lcfg.zfssa_pool)
LOG.debug("Verifying project %s.", lcfg.zfssa_project)
self.zfssa.verify_project(lcfg.zfssa_pool, lcfg.zfssa_project)
def _export_location(self, share):
"""Export share's location based on protocol used."""
lcfg = self.configuration
arg = {
'host': lcfg.zfssa_data_ip,
'mountpoint': self.mountpoint,
'name': share['id'],
}
location = ''
proto = share['share_proto']
if proto == 'NFS':
location = ("%(host)s:%(mountpoint)s/%(name)s" % arg)
elif proto == 'CIFS':
location = ("\\\\%(host)s\\%(name)s" % arg)
else:
exception_msg = _('Protocol %s is not supported.') % proto
LOG.error(exception_msg)
raise exception.InvalidParameterValue(exception_msg)
LOG.debug("Export location: %s.", location)
return location
def create_arg(self, size):
size = units.Gi * int(size)
arg = {
'quota': size,
'reservation': size,
}
arg.update(self.share_args)
return arg
def create_share(self, context, share, share_server=None):
"""Create a share and export it based on protocol used.
The created share inherits properties from its project.
"""
lcfg = self.configuration
arg = self.create_arg(share['size'])
arg.update(self.default_args)
arg.update({'name': share['id']})
if share['share_proto'] == 'CIFS':
arg.update({'sharesmb': 'on'})
LOG.debug("ZFSSAShareDriver.create_share: id=%(name)s, size=%(quota)s",
{'name': arg['name'],
'quota': arg['quota']})
self.zfssa.create_share(lcfg.zfssa_pool, lcfg.zfssa_project, arg)
return self._export_location(share)
def delete_share(self, context, share, share_server=None):
"""Delete a share.
Shares with existing snapshots can't be deleted.
"""
LOG.debug("ZFSSAShareDriver.delete_share: id=%s", share['id'])
lcfg = self.configuration
self.zfssa.delete_share(lcfg.zfssa_pool,
lcfg.zfssa_project,
share['id'])
def create_snapshot(self, context, snapshot, share_server=None):
"""Creates a snapshot of the snapshot['share_id']."""
LOG.debug("ZFSSAShareDriver.create_snapshot: "
"id=%(snap)s share=%(share)s",
{'snap': snapshot['id'],
'share': snapshot['share_id']})
lcfg = self.configuration
self.zfssa.create_snapshot(lcfg.zfssa_pool,
lcfg.zfssa_project,
snapshot['share_id'],
snapshot['id'])
def create_share_from_snapshot(self, context, share, snapshot,
share_server=None, parent_share=None):
"""Create a share from a snapshot - clone a snapshot."""
lcfg = self.configuration
LOG.debug("ZFSSAShareDriver.create_share_from_snapshot: clone=%s",
share['id'])
LOG.debug("ZFSSAShareDriver.create_share_from_snapshot: snapshot=%s",
snapshot['id'])
arg = self.create_arg(share['size'])
details = {
'share': share['id'],
'project': lcfg.zfssa_project,
}
arg.update(details)
if share['share_proto'] == 'CIFS':
arg.update({'sharesmb': 'on'})
self.zfssa.clone_snapshot(lcfg.zfssa_pool,
lcfg.zfssa_project,
snapshot,
share,
arg)
return self._export_location(share)
def delete_snapshot(self, context, snapshot, share_server=None):
"""Delete a snapshot.
Snapshots with existing clones cannot be deleted.
"""
LOG.debug("ZFSSAShareDriver.delete_snapshot: id=%s", snapshot['id'])
lcfg = self.configuration
has_clones = self.zfssa.has_clones(lcfg.zfssa_pool,
lcfg.zfssa_project,
snapshot['share_id'],
snapshot['id'])
if has_clones:
LOG.error("snapshot %s: has clones", snapshot['id'])
raise exception.ShareSnapshotIsBusy(snapshot_name=snapshot['id'])
self.zfssa.delete_snapshot(lcfg.zfssa_pool,
lcfg.zfssa_project,
snapshot['share_id'],
snapshot['id'])
def manage_existing(self, share, driver_options):
"""Manage an existing ZFSSA share.
This feature requires an option 'zfssa_name', which specifies the
name of the share as appeared in ZFSSA.
The driver automatically retrieves information from the ZFSSA backend
and returns the correct share size and export location.
"""
if 'zfssa_name' not in driver_options:
msg = _('Name of the share in ZFSSA share has to be '
'specified in option zfssa_name.')
LOG.error(msg)
raise exception.ShareBackendException(msg=msg)
name = driver_options['zfssa_name']
try:
details = self._get_share_details(name)
except Exception:
LOG.error('Cannot manage share %s', name)
raise
lcfg = self.configuration
input_export_loc = share['export_locations'][0]['path']
proto = share['share_proto']
self._verify_share_to_manage(name, details)
# Get and verify share size:
size_byte = details['quota']
size_gb = int(math.ceil(size_byte / float(units.Gi)))
if size_byte % units.Gi != 0:
# Round up the size:
new_size_byte = size_gb * units.Gi
free_space = self.zfssa.get_project_stats(lcfg.zfssa_pool,
lcfg.zfssa_project)
diff_space = int(new_size_byte - size_byte)
if diff_space > free_space:
msg = (_('Quota and reservation of share %(name)s need to be '
'rounded up to %(size)d. But there is not enough '
'space in the backend.') % {'name': name,
'size': size_gb})
LOG.error(msg)
raise exception.ManageInvalidShare(reason=msg)
size_byte = new_size_byte
# Get and verify share export location, also update share properties.
arg = {
'host': lcfg.zfssa_data_ip,
'mountpoint': input_export_loc,
'name': share['id'],
}
manage_args = self.default_args.copy()
manage_args.update(self.share_args)
# The ZFSSA share name has to be updated, as Manila generates a new
# share id for each share to be managed.
manage_args.update({'name': share['id'],
'quota': size_byte,
'reservation': size_byte})
if proto == 'NFS':
export_loc = ("%(host)s:%(mountpoint)s/%(name)s" % arg)
manage_args.update({'sharenfs': 'sec=sys',
'sharesmb': 'off'})
elif proto == 'CIFS':
export_loc = ("\\\\%(host)s\\%(name)s" % arg)
manage_args.update({'sharesmb': 'on',
'sharenfs': 'off'})
else:
msg = _('Protocol %s is not supported.') % proto
LOG.error(msg)
raise exception.ManageInvalidShare(reason=msg)
self.zfssa.modify_share(lcfg.zfssa_pool, lcfg.zfssa_project,
name, manage_args)
return {'size': size_gb, 'export_locations': export_loc}
def _verify_share_to_manage(self, name, details):
lcfg = self.configuration
if lcfg.zfssa_manage_policy == 'loose':
return
if 'custom:manila_managed' not in details:
msg = (_("Unknown if the share: %s to be managed is "
"already being managed by Manila. Aborting manage "
"share. Please add 'manila_managed' custom schema "
"property to the share and set its value to False."
"Alternatively, set Manila config property "
"'zfssa_manage_policy' to 'loose' to remove this "
"restriction.") % name)
LOG.error(msg)
raise exception.ManageInvalidShare(reason=msg)
if details['custom:manila_managed'] is True:
msg = (_("Share %s is already being managed by Manila.") % name)
LOG.error(msg)
raise exception.ManageInvalidShare(reason=msg)
def unmanage(self, share):
"""Removes the specified share from Manila management.
This task involves only changing the custom:manila_managed
property to False. Current accesses to the share will be removed in
ZFSSA, as these accesses are removed in Manila.
"""
name = share['id']
lcfg = self.configuration
managed = 'custom:manila_managed'
details = self._get_share_details(name)
if (managed not in details) or (details[managed] is not True):
msg = (_("Share %s is not being managed by the current Manila "
"instance.") % name)
LOG.error(msg)
raise exception.UnmanageInvalidShare(reason=msg)
arg = {'custom:manila_managed': False}
if share['share_proto'] == 'NFS':
arg.update({'sharenfs': 'off'})
elif share['share_proto'] == 'CIFS':
arg.update({'sharesmb': 'off'})
else:
msg = (_("ZFSSA does not support %s protocol.") %
share['share_proto'])
LOG.error(msg)
raise exception.UnmanageInvalidShare(reason=msg)
self.zfssa.modify_share(lcfg.zfssa_pool, lcfg.zfssa_project, name, arg)
def _get_share_details(self, name):
lcfg = self.configuration
details = self.zfssa.get_share(lcfg.zfssa_pool,
lcfg.zfssa_project,
name)
if not details:
msg = (_("Share %s doesn't exist in ZFSSA.") % name)
LOG.error(msg)
raise exception.ShareResourceNotFound(share_id=name)
return details
def ensure_share(self, context, share, share_server=None):
self._get_share_details(share['id'])
def shrink_share(self, share, new_size, share_server=None):
"""Shrink a share to new_size."""
lcfg = self.configuration
details = self.zfssa.get_share(lcfg.zfssa_pool,
lcfg.zfssa_project,
share['id'])
used_space = details['space_data']
new_size_byte = int(new_size) * units.Gi
if used_space > new_size_byte:
LOG.error('%(used).1fGB of share %(id)s is already used. '
'Cannot shrink to %(newsize)dGB.',
{'used': float(used_space) / units.Gi,
'id': share['id'],
'newsize': new_size})
raise exception.ShareShrinkingPossibleDataLoss(
share_id=share['id'])
arg = self.create_arg(new_size)
self.zfssa.modify_share(lcfg.zfssa_pool, lcfg.zfssa_project,
share['id'], arg)
def extend_share(self, share, new_size, share_server=None):
"""Extend a share to new_size."""
lcfg = self.configuration
free_space = self.zfssa.get_project_stats(lcfg.zfssa_pool,
lcfg.zfssa_project)
diff_space = int(new_size - share['size']) * units.Gi
if diff_space > free_space:
msg = (_('There is not enough free space in project %s')
% (lcfg.zfssa_project))
LOG.error(msg)
raise exception.ShareExtendingError(share_id=share['id'],
reason=msg)
arg = self.create_arg(new_size)
self.zfssa.modify_share(lcfg.zfssa_pool, lcfg.zfssa_project,
share['id'], arg)
def allow_access(self, context, share, access, share_server=None):
"""Allows access to an NFS share for the specified IP."""
LOG.debug("ZFSSAShareDriver.allow_access: share=%s", share['id'])
lcfg = self.configuration
if share['share_proto'] == 'NFS':
self.zfssa.allow_access_nfs(lcfg.zfssa_pool,
lcfg.zfssa_project,
share['id'],
access)
def deny_access(self, context, share, access, share_server=None):
"""Deny access to an NFS share for the specified IP."""
LOG.debug("ZFSSAShareDriver.deny_access: share=%s", share['id'])
lcfg = self.configuration
if share['share_proto'] == 'NFS':
self.zfssa.deny_access_nfs(lcfg.zfssa_pool,
lcfg.zfssa_project,
share['id'],
access)
elif share['share_proto'] == 'CIFS':
return
def _update_share_stats(self):
"""Retrieve stats info from a share."""
backend_name = self.configuration.safe_get('share_backend_name')
data = dict(
share_backend_name=backend_name or self.__class__.__name__,
vendor_name='Oracle',
driver_version=self.VERSION,
storage_protocol=self.PROTOCOL)
lcfg = self.configuration
(avail, used) = self.zfssa.get_pool_stats(lcfg.zfssa_pool)
if avail:
data['free_capacity_gb'] = int(avail) / units.Gi
if used:
total = int(avail) + int(used)
data['total_capacity_gb'] = total / units.Gi
else:
data['total_capacity_gb'] = 0
else:
data['free_capacity_gb'] = 0
data['total_capacity_gb'] = 0
super(ZFSSAShareDriver, self)._update_share_stats(data)
def get_network_allocations_number(self):
"""Returns number of network allocations for creating VIFs."""
return 0