class ViolinBackendErrNotFound(CinderException):
message = _("Backend reports: item not found")
+
+
+# ZFSSA NFS driver exception.
+class WebDAVClientError(CinderException):
+ message = _("The WebDAV request failed. Reason: %(msg)s, "
+ "Return code/reason: %(code)s, Source Volume: %(src)s, "
+ "Destination Volume: %(dst)s, Method: %(method)s.")
from cinder.volume import configuration as conf
from cinder.volume.drivers.zfssa import restclient as client
from cinder.volume.drivers.zfssa import zfssaiscsi as iscsi
+from cinder.volume.drivers.zfssa import zfssanfs
from cinder.volume.drivers.zfssa import zfssarest as rest
LOG = logging.getLogger(__name__)
+nfs_logbias = 'latency'
+nfs_compression = 'off'
+
class FakeZFSSA(object):
"""Fake ZFS SA"""
return ret
+class FakeNFSZFSSA(FakeZFSSA):
+ """Fake ZFS SA for the NFS Driver
+ """
+ def set_webdav(self, https_path, auth_str):
+ self.webdavclient = https_path
+
+ def create_share(self, pool, project, share, args):
+ out = {}
+ if not self.host and not self.user:
+ return out
+
+ out = {"logbias": nfs_logbias,
+ "compression": nfs_compression,
+ "status": "online",
+ "pool": pool,
+ "name": share,
+ "project": project,
+ "mountpoint": '/export/nfs_share'}
+
+ return out
+
+ def get_share(self, pool, project, share):
+ out = {}
+ if not self.host and not self.user:
+ return out
+
+ out = {"logbias": nfs_logbias,
+ "compression": nfs_compression,
+ "encryption": "off",
+ "status": "online",
+ "pool": pool,
+ "name": share,
+ "project": project,
+ "mountpoint": '/export/nfs_share'}
+
+ return out
+
+ def create_snapshot_of_volume_file(self, src_file="", dst_file=""):
+ out = {}
+ if not self.host and not self.user:
+ return out
+ out = {"status": 201}
+
+ return out
+
+ def delete_snapshot_of_volume_file(self, src_file=""):
+ out = {}
+ if not self.host and not self.user:
+ return out
+ out = {"status": 204}
+
+ return out
+
+ def create_volume_from_snapshot_file(self, src_file="", dst_file="",
+ method='COPY'):
+ out = {}
+ if not self.host and not self.user:
+ return out
+ out = {"status": 202}
+
+ return out
+
+ def modify_service(self, service, args):
+ out = {}
+ if not self.host and not self.user:
+ return out
+ out = {"service": {"<status>": "online"}}
+ return out
+
+ def enable_service(self, service):
+ out = {}
+ if not self.host and not self.user:
+ return out
+ out = {"service": {"<status>": "online"}}
+ return out
+
+
class TestZFSSAISCSIDriver(test.TestCase):
test_vol = {
result = client.RestResult()
result.status = client.Status.CREATED
return result
+
+
+class TestZFSSANFSDriver(test.TestCase):
+
+ test_vol = {
+ 'name': 'test-vol',
+ 'size': 1
+ }
+
+ test_snap = {
+ 'name': 'cindersnap',
+ 'volume_name': test_vol['name'],
+ 'volume_size': test_vol['size']
+ }
+
+ test_vol_snap = {
+ 'name': 'cindersnapvol',
+ 'size': test_vol['size']
+ }
+
+ def __init__(self, method):
+ super(TestZFSSANFSDriver, self).__init__(method)
+
+ @mock.patch.object(zfssanfs, 'factory_zfssa')
+ def setUp(self, _factory_zfssa):
+ super(TestZFSSANFSDriver, self).setUp()
+ self._create_fake_config()
+ _factory_zfssa.return_value = FakeNFSZFSSA()
+ self.drv = zfssanfs.ZFSSANFSDriver(configuration=self.configuration)
+ self.drv.do_setup({})
+
+ def _create_fake_config(self):
+ self.configuration = mock.Mock(spec=conf.Configuration)
+ self.configuration.san_ip = '1.1.1.1'
+ self.configuration.san_login = 'user'
+ self.configuration.san_password = 'passwd'
+ self.configuration.zfssa_data_ip = '2.2.2.2'
+ self.configuration.zfssa_https_port = '443'
+ self.configuration.zfssa_nfs_pool = 'pool'
+ self.configuration.zfssa_nfs_project = 'nfs_project'
+ self.configuration.zfssa_nfs_share = 'nfs_share'
+ self.configuration.zfssa_nfs_share_logbias = nfs_logbias
+ self.configuration.zfssa_nfs_share_compression = nfs_compression
+ self.configuration.zfssa_nfs_mount_options = ''
+ self.configuration.zfssa_rest_timeout = '30'
+ self.configuration.nfs_oversub_ratio = 1
+ self.configuration.nfs_used_ratio = 1
+
+ def test_create_delete_snapshot(self):
+ self.drv.create_snapshot(self.test_snap)
+ self.drv.delete_snapshot(self.test_snap)
+
+ def test_create_volume_from_snapshot(self):
+ self.drv.create_snapshot(self.test_snap)
+ with mock.patch.object(self.drv, '_ensure_shares_mounted'):
+ prov_loc = self.drv.create_volume_from_snapshot(self.test_vol_snap,
+ self.test_snap,
+ method='COPY')
+ self.assertEqual('2.2.2.2:/export/nfs_share',
+ prov_loc['provider_location'])
+
+ def test_get_volume_stats(self):
+ self.drv._mounted_shares = ['nfs_share']
+ with mock.patch.object(self.drv, '_ensure_shares_mounted'):
+ with mock.patch.object(self.drv, '_get_share_capacity_info') as \
+ mock_get_share_capacity_info:
+ mock_get_share_capacity_info.return_value = (1073741824,
+ 9663676416)
+ self.drv.get_volume_stats(refresh=True)
+
+ def tearDown(self):
+ super(TestZFSSANFSDriver, self).tearDown()
--- /dev/null
+# 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 WebDAV Client
+"""
+
+import httplib
+import time
+import urllib2
+
+from cinder import exception
+from cinder.i18n import _, _LE
+from cinder.openstack.common import log
+
+LOG = log.getLogger(__name__)
+
+bad_gateway_err = _('Check the state of the http service. Also ensure that '
+ 'the https port number is the same as the one specified '
+ 'in cinder.conf.')
+
+WebDAVHTTPErrors = {
+ httplib.UNAUTHORIZED: _('User not authorized to perform WebDAV '
+ 'operations.'),
+ httplib.BAD_GATEWAY: bad_gateway_err,
+ httplib.FORBIDDEN: _('Check access permissions for the ZFS share assigned '
+ 'to this driver.'),
+ httplib.NOT_FOUND: _('The source volume for this WebDAV operation not '
+ 'found.'),
+ httplib.INSUFFICIENT_STORAGE: _('Not enough storage space in the ZFS '
+ 'share to perform this operation.')
+}
+
+WebDAVErrors = {
+ 'BadStatusLine': _('http service may have been abruptly disabled or put '
+ 'to maintenance state in the middle of this '
+ 'operation.'),
+ 'Bad_Gateway': bad_gateway_err
+}
+
+
+class ZFSSAWebDAVClient(object):
+ def __init__(self, url, auth_str, **kwargs):
+ """Initialize WebDAV Client"""
+ self.https_path = url
+ self.auth_str = auth_str
+
+ def _lookup_error(self, error):
+ msg = ''
+ if error in httplib.responses:
+ msg = httplib.responses[error]
+
+ if error in WebDAVHTTPErrors:
+ msg = WebDAVHTTPErrors[error]
+ elif error in WebDAVErrors:
+ msg = WebDAVErrors[error]
+
+ return msg
+
+ def request(self, src_file="", dst_file="", method="", maxretries=10):
+ retry = 0
+ src_url = self.https_path + "/" + src_file
+ dst_url = self.https_path + "/" + dst_file
+ request = urllib2.Request(src_url)
+
+ if dst_file != "":
+ request.add_header('Destination', dst_url)
+
+ request.add_header("Authorization", "Basic %s" % self.auth_str)
+
+ request.get_method = lambda: method
+
+ LOG.debug('Sending WebDAV request:%s %s %s' % (method, src_url,
+ dst_url))
+
+ while retry < maxretries:
+ try:
+ response = urllib2.urlopen(request, timeout=None)
+ except urllib2.HTTPError as err:
+ LOG.error(_LE('WebDAV returned with %(code)s error during '
+ '%(method)s call.')
+ % {'code': err.code,
+ 'method': method})
+
+ if err.code == httplib.INTERNAL_SERVER_ERROR:
+ exception_msg = (_('WebDAV operation failed with '
+ 'error code: %(code)s '
+ 'reason: %(reason)s '
+ 'Retry attempt %(retry)s in progress.')
+ % {'code': err.code,
+ 'reason': err.reason,
+ 'retry': retry})
+ LOG.error(exception_msg)
+ if retry < maxretries:
+ retry += 1
+ time.sleep(1)
+ continue
+
+ msg = self._lookup_error(err.code)
+ raise exception.WebDAVClientError(msg=msg, code=err.code,
+ src=src_file, dst=dst_file,
+ method=method)
+
+ except httplib.BadStatusLine as err:
+ msg = self._lookup_error('BadStatusLine')
+ raise exception.WebDAVClientError(msg=msg,
+ code='httplib.BadStatusLine',
+ src=src_file, dst=dst_file,
+ method=method)
+
+ except urllib2.URLError as err:
+ reason = ''
+ if getattr(err, 'reason'):
+ reason = err.reason
+
+ msg = self._lookup_error('Bad_Gateway')
+ raise exception.WebDAVClientError(msg=msg,
+ code=reason, src=src_file,
+ dst=dst_file, method=method)
+
+ break
+ return response
--- /dev/null
+# 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 NFS Cinder Volume Driver
+"""
+import base64
+from datetime import datetime
+import errno
+
+from oslo.config import cfg
+from oslo.utils import excutils
+from oslo.utils import units
+
+from cinder import exception
+from cinder.i18n import _, _LE, _LI
+from cinder.openstack.common import log
+from cinder.volume.drivers import nfs
+from cinder.volume.drivers.san import san
+from cinder.volume.drivers.zfssa import zfssarest
+
+
+ZFSSA_OPTS = [
+ cfg.StrOpt('zfssa_data_ip',
+ help='Data path IP address'),
+ cfg.StrOpt('zfssa_https_port', default='443',
+ help='HTTPS port number'),
+ cfg.StrOpt('zfssa_nfs_mount_options', default='',
+ help='Options to be passed while mounting share over nfs'),
+ cfg.StrOpt('zfssa_nfs_pool', default='',
+ help='Storage pool name.'),
+ cfg.StrOpt('zfssa_nfs_project', default='NFSProject',
+ help='Project name.'),
+ cfg.StrOpt('zfssa_nfs_share', default='nfs_share',
+ help='Share name.'),
+ cfg.StrOpt('zfssa_nfs_share_compression', default='off',
+ help='Data compression-off, lzjb, gzip-2, gzip, gzip-9.'),
+ cfg.StrOpt('zfssa_nfs_share_logbias', default='latency',
+ help='Synchronous write bias-latency, throughput.'),
+ cfg.IntOpt('zfssa_rest_timeout',
+ help='REST connection timeout. (seconds)')
+]
+
+LOG = log.getLogger(__name__)
+
+CONF = cfg.CONF
+CONF.register_opts(ZFSSA_OPTS)
+
+
+def factory_zfssa():
+ return zfssarest.ZFSSANfsApi()
+
+
+class ZFSSANFSDriver(nfs.NfsDriver):
+ VERSION = '1.0.0'
+ volume_backend_name = 'ZFSSA_NFS'
+ protocol = driver_prefix = driver_volume_type = 'nfs'
+
+ def __init__(self, *args, **kwargs):
+ super(ZFSSANFSDriver, self).__init__(*args, **kwargs)
+ self.configuration.append_config_values(ZFSSA_OPTS)
+ self.configuration.append_config_values(san.san_opts)
+ self.zfssa = None
+ self._stats = None
+
+ def do_setup(self, context):
+ if not self.configuration.nfs_oversub_ratio > 0:
+ msg = _("NFS config 'nfs_oversub_ratio' invalid. Must be > 0: "
+ "%s") % self.configuration.nfs_oversub_ratio
+ LOG.error(msg)
+ raise exception.NfsException(msg)
+
+ if ((not self.configuration.nfs_used_ratio > 0) and
+ (self.configuration.nfs_used_ratio <= 1)):
+ msg = _("NFS config 'nfs_used_ratio' invalid. Must be > 0 "
+ "and <= 1.0: %s") % self.configuration.nfs_used_ratio
+ LOG.error(msg)
+ raise exception.NfsException(msg)
+
+ package = 'mount.nfs'
+ try:
+ self._execute(package, check_exit_code=False, run_as_root=True)
+ except OSError as exc:
+ if exc.errno == errno.ENOENT:
+ msg = _('%s is not installed') % package
+ raise exception.NfsException(msg)
+ else:
+ raise exc
+
+ lcfg = self.configuration
+ LOG.info(_LI('Connecting to host: %s.'), lcfg.san_ip)
+
+ host = lcfg.san_ip
+ user = lcfg.san_login
+ password = lcfg.san_password
+ https_port = lcfg.zfssa_https_port
+
+ credentials = ['san_ip', 'san_login', 'san_password', 'zfssa_data_ip']
+
+ for cred in credentials:
+ if not getattr(lcfg, cred, None):
+ exception_msg = _('%s not set in cinder.conf') % cred
+ LOG.error(exception_msg)
+ raise exception.CinderException(exception_msg)
+
+ self.zfssa = factory_zfssa()
+ self.zfssa.set_host(host, timeout=lcfg.zfssa_rest_timeout)
+
+ auth_str = base64.encodestring('%s:%s' % (user, password))[:-1]
+ self.zfssa.login(auth_str)
+
+ self.zfssa.create_project(lcfg.zfssa_nfs_pool, lcfg.zfssa_nfs_project,
+ compression=lcfg.zfssa_nfs_share_compression,
+ logbias=lcfg.zfssa_nfs_share_logbias)
+
+ share_args = {
+ 'sharedav': 'rw',
+ 'sharenfs': 'rw',
+ 'root_permissions': '777',
+ 'compression': lcfg.zfssa_nfs_share_compression,
+ 'logbias': lcfg.zfssa_nfs_share_logbias
+ }
+
+ self.zfssa.create_share(lcfg.zfssa_nfs_pool, lcfg.zfssa_nfs_project,
+ lcfg.zfssa_nfs_share, share_args)
+
+ share_details = self.zfssa.get_share(lcfg.zfssa_nfs_pool,
+ lcfg.zfssa_nfs_project,
+ lcfg.zfssa_nfs_share)
+
+ mountpoint = share_details['mountpoint']
+
+ self.mount_path = lcfg.zfssa_data_ip + ':' + mountpoint
+ https_path = 'https://' + lcfg.zfssa_data_ip + ':' + https_port + \
+ '/shares' + mountpoint
+
+ LOG.debug('NFS mount path: %s' % self.mount_path)
+ LOG.debug('WebDAV path to the share: %s' % https_path)
+
+ self.shares = {}
+ mnt_opts = self.configuration.zfssa_nfs_mount_options
+ self.shares[self.mount_path] = mnt_opts if len(mnt_opts) > 1 else None
+
+ # Initialize the WebDAV client
+ self.zfssa.set_webdav(https_path, auth_str)
+
+ # Edit http service so that WebDAV requests are always authenticated
+ args = {'https_port': https_port,
+ 'require_login': True}
+
+ self.zfssa.modify_service('http', args)
+ self.zfssa.enable_service('http')
+
+ def _ensure_shares_mounted(self):
+ try:
+ self._ensure_share_mounted(self.mount_path)
+ except Exception as exc:
+ LOG.error(_LE('Exception during mounting %s.') % exc)
+
+ self._mounted_shares = [self.mount_path]
+ LOG.debug('Available shares %s' % self._mounted_shares)
+
+ def check_for_setup_error(self):
+ """Check that driver can login.
+
+ Check also for properly configured pool, project and share
+ Check that the http and nfs services are enabled
+ """
+ lcfg = self.configuration
+
+ self.zfssa.verify_pool(lcfg.zfssa_nfs_pool)
+ self.zfssa.verify_project(lcfg.zfssa_nfs_pool, lcfg.zfssa_nfs_project)
+ self.zfssa.verify_share(lcfg.zfssa_nfs_pool, lcfg.zfssa_nfs_project,
+ lcfg.zfssa_nfs_share)
+ self.zfssa.verify_service('http')
+ self.zfssa.verify_service('nfs')
+
+ def create_snapshot(self, snapshot):
+ """Creates a snapshot of a volume."""
+ LOG.info(_LI('Creating snapshot: %s'), snapshot['name'])
+ lcfg = self.configuration
+ snap_name = self._create_snapshot_name()
+ self.zfssa.create_snapshot(lcfg.zfssa_nfs_pool, lcfg.zfssa_nfs_project,
+ lcfg.zfssa_nfs_share, snap_name)
+
+ src_file = snap_name + '/' + snapshot['volume_name']
+
+ try:
+ self.zfssa.create_snapshot_of_volume_file(src_file=src_file,
+ dst_file=
+ snapshot['name'])
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ LOG.debug('Error thrown during snapshot: %s creation' %
+ snapshot['name'])
+ finally:
+ self.zfssa.delete_snapshot(lcfg.zfssa_nfs_pool,
+ lcfg.zfssa_nfs_project,
+ lcfg.zfssa_nfs_share, snap_name)
+
+ def delete_snapshot(self, snapshot):
+ """Deletes a snapshot."""
+ LOG.info(_LI('Deleting snapshot: %s'), snapshot['name'])
+ self.zfssa.delete_snapshot_of_volume_file(src_file=snapshot['name'])
+
+ def create_volume_from_snapshot(self, volume, snapshot, method='COPY'):
+ LOG.info(_LI('Creatng volume from snapshot. volume: %s'),
+ volume['name'])
+ LOG.info(_LI('Source Snapshot: %s'), snapshot['name'])
+
+ self._ensure_shares_mounted()
+ self.zfssa.create_volume_from_snapshot_file(src_file=snapshot['name'],
+ dst_file=volume['name'],
+ method=method)
+
+ volume['provider_location'] = self.mount_path
+
+ if volume['size'] != snapshot['volume_size']:
+ try:
+ self.extend_volume(volume, volume['size'])
+ except Exception:
+ vol_path = self.local_path(volume)
+ exception_msg = (_('Error in extending volume size: '
+ 'Volume: %(volume)s '
+ 'Vol_Size: %(vol_size)d with '
+ 'Snapshot: %(snapshot)s '
+ 'Snap_Size: %(snap_size)d')
+ % {'volume': volume['name'],
+ 'vol_size': volume['size'],
+ 'snapshot': snapshot['name'],
+ 'snap_size': snapshot['volume_size']})
+ with excutils.save_and_reraise_exception():
+ LOG.error(exception_msg)
+ self._execute('rm', '-f', vol_path, run_as_root=True)
+
+ return {'provider_location': volume['provider_location']}
+
+ def create_cloned_volume(self, volume, src_vref):
+ """Creates a snapshot and then clones the snapshot into a volume."""
+ LOG.info(_LI('new cloned volume: %s'), volume['name'])
+ LOG.info(_LI('source volume for cloning: %s'), src_vref['name'])
+
+ snapshot = {'volume_name': src_vref['name'],
+ 'volume_id': src_vref['id'],
+ 'volume_size': src_vref['size'],
+ 'name': self._create_snapshot_name()}
+
+ self.create_snapshot(snapshot)
+ return self.create_volume_from_snapshot(volume, snapshot,
+ method='MOVE')
+
+ def _create_snapshot_name(self):
+ """Creates a snapshot name from the date and time."""
+ return 'cinder-zfssa-nfs-snapshot-%s' % datetime.now().isoformat()
+
+ def _get_share_capacity_info(self):
+ """Get available and used capacity info for the NFS share."""
+ lcfg = self.configuration
+ share_details = self.zfssa.get_share(lcfg.zfssa_nfs_pool,
+ lcfg.zfssa_nfs_project,
+ lcfg.zfssa_nfs_share)
+
+ free = share_details['space_available']
+ used = share_details['space_total']
+ return free, used
+
+ def _update_volume_stats(self):
+ """Get volume stats from zfssa"""
+ self._ensure_shares_mounted()
+ data = {}
+ backend_name = self.configuration.safe_get('volume_backend_name')
+ data['volume_backend_name'] = backend_name or self.__class__.__name__
+ data['vendor_name'] = 'Oracle'
+ data['driver_version'] = self.VERSION
+ data['storage_protocol'] = self.protocol
+
+ free, used = self._get_share_capacity_info()
+ capacity = float(free) + float(used)
+ ratio_used = used / capacity
+
+ data['QoS_support'] = False
+ data['reserved_percentage'] = 0
+
+ if ratio_used > self.configuration.nfs_used_ratio or \
+ ratio_used >= self.configuration.nfs_oversub_ratio:
+ data['reserved_percentage'] = 100
+
+ data['total_capacity_gb'] = float(capacity) / units.Gi
+ data['free_capacity_gb'] = float(free) / units.Gi
+
+ self._stats = data
from cinder.i18n import _, _LE
from cinder.openstack.common import log
from cinder.volume.drivers.zfssa import restclient
+from cinder.volume.drivers.zfssa import webdavclient
LOG = log.getLogger(__name__)
"default initiator group.")
groups.append('default')
return groups
+
+
+class ZFSSANfsApi(ZFSSAApi):
+ """ZFSSA API proxy class for NFS driver"""
+ projects_path = '/api/storage/v1/pools/%s/projects'
+ project_path = projects_path + '/%s'
+
+ shares_path = project_path + '/filesystems'
+ share_path = shares_path + '/%s'
+ share_snapshots_path = share_path + '/snapshots'
+ share_snapshot_path = share_snapshots_path + '/%s'
+
+ services_path = '/api/service/v1/services/'
+
+ def __init__(self, *args, **kwargs):
+ super(ZFSSANfsApi, self).__init__(*args, **kwargs)
+ self.webdavclient = None
+
+ def set_webdav(self, https_path, auth_str):
+ self.webdavclient = webdavclient.ZFSSAWebDAVClient(https_path,
+ auth_str)
+
+ def verify_share(self, pool, project, share):
+ """Checks whether the share exists"""
+ svc = self.share_path % (pool, project, share)
+ ret = self.rclient.get(svc)
+ if ret.status != restclient.Status.OK:
+ exception_msg = (_('Error Verifying '
+ 'share: %(share)s on '
+ 'Project: %(project)s and '
+ 'Pool: %(pool)s '
+ 'Return code: %(ret.status)d '
+ 'Message: %(ret.data)s.')
+ % {'share': share,
+ 'project': project,
+ 'pool': pool,
+ 'ret.status': ret.status,
+ 'ret.data': ret.data})
+ LOG.error(exception_msg)
+ raise exception.VolumeBackendAPIException(data=exception_msg)
+
+ def create_snapshot(self, pool, project, share, snapshot):
+ """create snapshot of a share"""
+ svc = self.share_snapshots_path % (pool, project, share)
+
+ arg = {
+ 'name': snapshot
+ }
+
+ ret = self.rclient.post(svc, arg)
+ if ret.status != restclient.Status.CREATED:
+ exception_msg = (_('Error Creating '
+ 'Snapshot: %(snapshot)s on'
+ 'share: %(share)s to '
+ 'Pool: %(pool)s '
+ 'Project: %(project)s '
+ 'Return code: %(ret.status)d '
+ 'Message: %(ret.data)s.')
+ % {'snapshot': snapshot,
+ 'share': share,
+ 'pool': pool,
+ 'project': project,
+ 'ret.status': ret.status,
+ 'ret.data': ret.data})
+ LOG.error(exception_msg)
+ raise exception.VolumeBackendAPIException(data=exception_msg)
+
+ def delete_snapshot(self, pool, project, share, snapshot):
+ """delete snapshot of a share"""
+ svc = self.share_snapshot_path % (pool, project, share, snapshot)
+
+ ret = self.rclient.delete(svc)
+ if ret.status != restclient.Status.NO_CONTENT:
+ exception_msg = (_('Error Deleting '
+ 'Snapshot: %(snapshot)s on '
+ 'Share: %(share)s to '
+ 'Pool: %(pool)s '
+ 'Project: %(project)s '
+ 'Return code: %(ret.status)d '
+ 'Message: %(ret.data)s.')
+ % {'snapshot': snapshot,
+ 'share': share,
+ 'pool': pool,
+ 'project': project,
+ 'ret.status': ret.status,
+ 'ret.data': ret.data})
+ LOG.error(exception_msg)
+ raise exception.VolumeBackendAPIException(data=exception_msg)
+
+ def create_snapshot_of_volume_file(self, src_file="", dst_file=""):
+ src_file = '.zfs/snapshot/' + src_file
+ return self.webdavclient.request(src_file=src_file, dst_file=dst_file,
+ method='COPY')
+
+ def delete_snapshot_of_volume_file(self, src_file=""):
+ return self.webdavclient.request(src_file=src_file, method='DELETE')
+
+ def create_volume_from_snapshot_file(self, src_file="", dst_file="",
+ method='COPY'):
+ return self.webdavclient.request(src_file=src_file, dst_file=dst_file,
+ method=method)
+
+ def _change_service_state(self, service, state=''):
+ svc = self.services_path + service + '/' + state
+ ret = self.rclient.put(svc)
+ if ret.status != restclient.Status.ACCEPTED:
+ exception_msg = (_('Error Verifying '
+ 'Service: %(service)s '
+ 'Return code: %(ret.status)d '
+ 'Message: %(ret.data)s.')
+ % {'service': service,
+ 'ret.status': ret.status,
+ 'ret.data': ret.data})
+
+ LOG.error(exception_msg)
+ raise exception.VolumeBackendAPIException(data=exception_msg)
+ data = json.loads(ret.data)['service']
+ LOG.debug('%s service state: %s' % (service, data))
+
+ status = 'online' if state == 'enable' else 'disabled'
+
+ if data['<status>'] != status:
+ exception_msg = (_('%(service)s Service is not %(status)s '
+ 'on storage appliance: %(host)s')
+ % {'service': service,
+ 'status': status,
+ 'host': self.host})
+ LOG.error(exception_msg)
+ raise exception.VolumeBackendAPIException(data=exception_msg)
+
+ def enable_service(self, service):
+ self._change_service_state(service, state='enable')
+ self.verify_service(service)
+
+ def disable_service(self, service):
+ self._change_service_state(service, state='disable')
+ self.verify_service(service, status='offline')
+
+ def verify_service(self, service, status='online'):
+ """Checks whether a service is online or not"""
+ svc = self.services_path + service
+ ret = self.rclient.get(svc)
+
+ if ret.status != restclient.Status.OK:
+ exception_msg = (_('Error Verifying '
+ 'Service: %(service)s '
+ 'Return code: %(ret.status)d '
+ 'Message: %(ret.data)s.')
+ % {'service': service,
+ 'ret.status': ret.status,
+ 'ret.data': ret.data})
+
+ LOG.error(exception_msg)
+ raise exception.VolumeBackendAPIException(data=exception_msg)
+
+ data = json.loads(ret.data)['service']
+
+ if data['<status>'] != status:
+ exception_msg = (_('%(service)s Service is not %(status)s '
+ 'on storage appliance: %(host)s')
+ % {'service': service,
+ 'status': status,
+ 'host': self.host})
+ LOG.error(exception_msg)
+ raise exception.VolumeBackendAPIException(data=exception_msg)
+
+ def modify_service(self, service, edit_args=None):
+ """Edit service properties"""
+ if edit_args is None:
+ edit_args = {}
+
+ svc = self.services_path + service
+
+ ret = self.rclient.put(svc, edit_args)
+
+ if ret.status != restclient.Status.ACCEPTED:
+ exception_msg = (_('Error modifying '
+ 'Service: %(service)s '
+ 'Return code: %(ret.status)d '
+ 'Message: %(ret.data)s.')
+ % {'service': service,
+ 'ret.status': ret.status,
+ 'ret.data': ret.data})
+
+ LOG.error(exception_msg)
+ raise exception.VolumeBackendAPIException(data=exception_msg)
+ data = json.loads(ret.data)['service']
+ LOG.debug('Modify %(service)s service '
+ 'return data: %(data)s'
+ % {'service': service,
+ 'data': data})
+
+ def create_share(self, pool, project, share, args):
+ """Create a share in the specified pool and project"""
+ svc = self.share_path % (pool, project, share)
+ ret = self.rclient.get(svc)
+ if ret.status != restclient.Status.OK:
+ svc = self.shares_path % (pool, project)
+ args.update({'name': share})
+ ret = self.rclient.post(svc, args)
+ if ret.status != restclient.Status.CREATED:
+ exception_msg = (_('Error Creating '
+ 'Share: %(name)s '
+ 'Return code: %(ret.status)d '
+ 'Message: %(ret.data)s.')
+ % {'name': share,
+ 'ret.status': ret.status,
+ 'ret.data': ret.data})
+ LOG.error(exception_msg)
+ raise exception.VolumeBackendAPIException(data=exception_msg)
+ else:
+ LOG.debug('Editing properties of a pre-existing share')
+ ret = self.rclient.put(svc, args)
+ if ret.status != restclient.Status.ACCEPTED:
+ exception_msg = (_('Error editing share: '
+ '%(share)s on '
+ 'Pool: %(pool)s '
+ 'Return code: %(ret.status)d '
+ 'Message: %(ret.data)s .')
+ % {'share': share,
+ 'pool': pool,
+ 'ret.status': ret.status,
+ 'ret.data': ret.data})
+ LOG.error(exception_msg)
+ raise exception.VolumeBackendAPIException(data=exception_msg)
+
+ def get_share(self, pool, project, share):
+ """return share properties"""
+ svc = self.share_path % (pool, project, share)
+ ret = self.rclient.get(svc)
+ if ret.status != restclient.Status.OK:
+ exception_msg = (_('Error Getting '
+ 'Share: %(share)s on '
+ 'Pool: %(pool)s '
+ 'Project: %(project)s '
+ 'Return code: %(ret.status)d '
+ 'Message: %(ret.data)s.')
+ % {'share': share,
+ 'pool': pool,
+ 'project': project,
+ 'ret.status': ret.status,
+ 'ret.data': ret.data})
+ LOG.error(exception_msg)
+ raise exception.VolumeBackendAPIException(data=exception_msg)
+
+ val = json.loads(ret.data)
+ return val['filesystem']