+++ /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.
-"""
-Unit tests for Oracle's ZFSSA Cinder volume driver
-"""
-
-import json
-
-import mock
-from oslo_log import log as logging
-from oslo_utils import units
-
-from cinder import test
-from cinder.tests import fake_utils
-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"""
- def __init__(self):
- self.user = None
- self.host = None
-
- def login(self, user):
- self.user = user
-
- def set_host(self, host, timeout=None):
- self.host = host
-
- def create_project(self, pool, project, compression, logbias):
- out = {}
- if not self.host or not self.user:
- return out
-
- out = {"status": "online",
- "name": "pool",
- "usage": {"available": 10,
- "total": 10,
- "dedupratio": 100,
- "used": 1},
- "peer": "00000000-0000-0000-0000-000000000000",
- "owner": "host",
- "asn": "11111111-2222-3333-4444-555555555555"}
- return out
-
- def create_initiator(self, init, initgrp, chapuser, chapsecret):
- out = {}
- if not self.host or not self.user:
- return out
- out = {"href": "fake_href",
- "alias": "fake_alias",
- "initiator": "fake_iqn.1993-08.org.fake:01:000000000000",
- "chapuser": "",
- "chapsecret": ""
- }
-
- return out
-
- def add_to_initiatorgroup(self, init, initgrp):
- r = rest.ZFSSAApi()
- type(r).rclient = mock.PropertyMock(return_value=FakeAddIni2InitGrp())
- r.add_to_initiatorgroup(init, initgrp)
-
- def create_target(self, tgtalias, inter, tchapuser, tchapsecret):
- out = {}
- if not self.host or not self.user:
- return out
- out = {"href": "fake_href",
- "alias": "fake_tgtgrp",
- "iqn": "iqn.1986-03.com.sun:02:00000-aaaa-bbbb-cccc-ddddd",
- "auth": "none",
- "targetchapuser": "",
- "targetchapsecret": "",
- "interfaces": ["eth0"]
- }
-
- return out
-
- def add_to_targetgroup(self, iqn, tgtgrp):
- out = {}
- if not self.host or not self.user:
- return {}
- out = {"href": "fake_href",
- "name": "fake_tgtgrp",
- "targets": ["iqn.1986-03.com.sun:02:00000-aaaa-bbbb-cccc-ddddd"]
- }
- return out
-
- def get_lun(self, pool, project, lun):
- ret = {
- 'guid': '600144F0F8FBD5BD000053CE53AB0001',
- 'number': 0,
- 'initiatorgroup': 'fake_initgrp',
- 'size': 1 * units.Gi
- }
- return ret
-
- def get_target(self, target):
- return 'iqn.1986-03.com.sun:02:00000-aaaa-bbbb-cccc-ddddd'
-
- def create_lun(self, pool, project, lun, volsize, targetgroup, specs):
- out = {}
- if not self.host and not self.user:
- return out
-
- out = {"status": "online",
- "lunguid": "600144F0F8FBD5BD000053CE53AB0001",
- "initiatorgroup": ["fake_initgrp"],
- "volsize": volsize,
- "pool": pool,
- "name": lun,
- "project": project,
- "targetgroup": targetgroup}
- if specs:
- out.update(specs)
-
- return out
-
- def delete_lun(self, pool, project, lun):
- out = {}
- if not self.host and not self.user:
- return out
- out = {"pool": pool,
- "project": project,
- "name": lun}
-
- return out
-
- def create_snapshot(self, pool, project, vol, snap):
- out = {}
- if not self.host and not self.user:
- return {}
- out = {"name": snap,
- "numclones": 0,
- "share": vol,
- "project": project,
- "pool": pool}
-
- return out
-
- def delete_snapshot(self, pool, project, vol, snap):
- out = {}
- if not self.host and not self.user:
- return {}
- out = {"name": snap,
- "share": vol,
- "project": project,
- "pool": pool}
-
- return out
-
- def clone_snapshot(self, pool, project, pvol, snap, vol):
- out = {}
- if not self.host and not self.user:
- return out
- out = {"origin": {"project": project,
- "snapshot": snap,
- "share": pvol,
- "pool": pool},
- "logbias": "latency",
- "assignednumber": 1,
- "status": "online",
- "lunguid": "600144F0F8FBD5BD000053CE67A50002",
- "volsize": 1,
- "pool": pool,
- "name": vol,
- "project": project}
-
- return out
-
- def set_lun_initiatorgroup(self, pool, project, vol, initgrp):
- out = {}
- if not self.host and not self.user:
- return out
- out = {"lunguid": "600144F0F8FBD5BD000053CE67A50002",
- "pool": pool,
- "name": vol,
- "project": project,
- "initiatorgroup": ["fake_initgrp"]}
-
- return out
-
- def has_clones(self, pool, project, vol, snapshot):
- return False
-
- def set_lun_props(self, pool, project, vol, **kargs):
- out = {}
- if not self.host and not self.user:
- return out
- out = {"pool": pool,
- "name": vol,
- "project": project,
- "volsize": kargs['volsize']}
-
- return out
-
- def get_initiator_initiatorgroup(self, initiator):
- ret = ['test-init-grp1']
- 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 = {
- 'name': 'cindervol',
- 'size': 1,
- 'id': 1
- }
-
- test_snap = {
- 'name': 'cindersnap',
- 'volume_name': test_vol['name']
- }
-
- test_vol_snap = {
- 'name': 'cindersnapvol',
- 'size': test_vol['size']
- }
-
- def __init__(self, method):
- super(TestZFSSAISCSIDriver, self).__init__(method)
-
- @mock.patch.object(iscsi, 'factory_zfssa')
- def setUp(self, _factory_zfssa):
- super(TestZFSSAISCSIDriver, self).setUp()
- self._create_fake_config()
- _factory_zfssa.return_value = FakeZFSSA()
- iscsi.ZFSSAISCSIDriver._execute = fake_utils.fake_execute
- self.drv = iscsi.ZFSSAISCSIDriver(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_pool = 'pool'
- self.configuration.zfssa_project = 'project'
- self.configuration.zfssa_lun_volblocksize = '8k'
- self.configuration.zfssa_lun_sparse = 'false'
- self.configuration.zfssa_lun_logbias = 'latency'
- self.configuration.zfssa_lun_compression = 'off'
- self.configuration.zfssa_initiator_group = 'test-init-grp1'
- self.configuration.zfssa_initiator = \
- 'iqn.1-0.org.deb:01:d7, iqn.1-0.org.deb:01:d9'
- self.configuration.zfssa_initiator_user = ''
- self.configuration.zfssa_initiator_password = ''
- self.configuration.zfssa_initiator_config = "{'test-init-grp1':[{'iqn':\
- 'iqn.1-0.org.deb:01:d7','user':'','password':''}],'test-init-grp\
- 2':[{'iqn':'iqn.1-0.org.deb:01:d9','user':'','password':''}]}"
- self.configuration.zfssa_target_group = 'test-target-grp1'
- self.configuration.zfssa_target_user = ''
- self.configuration.zfssa_target_password = ''
- self.configuration.zfssa_target_portal = '1.1.1.1:3260'
- self.configuration.zfssa_target_interfaces = 'e1000g0'
- self.configuration.zfssa_rest_timeout = 60
- self.configuration.volume_backend_name = 'fake_zfssa'
- self.configuration.safe_get = self.fake_safe_get
-
- def test_create_delete_volume(self):
- self.drv.create_volume(self.test_vol)
- self.drv.delete_volume(self.test_vol)
-
- def test_create_delete_snapshot(self):
- self.drv.create_volume(self.test_vol)
- self.drv.create_snapshot(self.test_snap)
- self.drv.delete_snapshot(self.test_snap)
- self.drv.delete_volume(self.test_vol)
-
- def test_create_volume_from_snapshot(self):
- self.drv.create_volume(self.test_vol)
- self.drv.create_snapshot(self.test_snap)
- self.drv.create_volume_from_snapshot(self.test_vol_snap,
- self.test_snap)
- self.drv.delete_volume(self.test_vol)
-
- def test_remove_export(self):
- self.drv.create_volume(self.test_vol)
- self.drv.terminate_connection(self.test_vol, '')
- self.drv.delete_volume(self.test_vol)
-
- def test_volume_attach_detach(self):
- self.drv.create_volume(self.test_vol)
-
- connector = dict(initiator='iqn.1-0.org.deb:01:d7')
- props = self.drv.initialize_connection(self.test_vol, connector)
- self.assertEqual('iscsi', props['driver_volume_type'])
- self.assertEqual(self.test_vol['id'], props['data']['volume_id'])
-
- self.drv.terminate_connection(self.test_vol, '')
- self.drv.delete_volume(self.test_vol)
-
- def test_get_volume_stats(self):
- self.drv.get_volume_stats(refresh=False)
-
- def test_extend_volume(self):
- self.drv.create_volume(self.test_vol)
- self.drv.extend_volume(self.test_vol, 3)
- self.drv.delete_volume(self.test_vol)
-
- @mock.patch('cinder.volume.volume_types.get_volume_type_extra_specs')
- def test_get_voltype_specs(self, get_volume_type_extra_specs):
- volume_type_id = mock.sentinel.volume_type_id
- volume = {'volume_type_id': volume_type_id}
- get_volume_type_extra_specs.return_value = {
- 'zfssa:volblocksize': '128k',
- 'zfssa:compression': 'gzip'
- }
- ret = self.drv._get_voltype_specs(volume)
- self.assertEqual(ret.get('volblocksize'), '128k')
- self.assertEqual(ret.get('sparse'),
- self.configuration.zfssa_lun_sparse)
- self.assertEqual(ret.get('compression'), 'gzip')
- self.assertEqual(ret.get('logbias'),
- self.configuration.zfssa_lun_logbias)
-
- def tearDown(self):
- super(TestZFSSAISCSIDriver, self).tearDown()
-
- def fake_safe_get(self, value):
- try:
- val = getattr(self.configuration, value)
- except AttributeError:
- val = None
- return val
-
-
-class FakeAddIni2InitGrp(object):
-
- def get(self, path, **kwargs):
- result = client.RestResult()
- result.status = client.Status.OK
- result.data = json.JSONEncoder().encode({'group':
- {'initiators':
- ['iqn.1-0.org.deb:01:d7']}})
- return result
-
- def put(self, path, body="", **kwargs):
- result = client.RestResult()
- result.status = client.Status.ACCEPTED
- return result
-
- def post(self, path, body="", **kwargs):
- 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._execute = fake_utils.fake_execute
- 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 REST API Client Programmatic Interface
-"""
-
-import httplib
-import json
-import StringIO
-import time
-import urllib2
-
-from oslo_log import log
-
-from cinder.i18n import _LE, _LI
-
-LOG = log.getLogger(__name__)
-
-
-class Status(object):
- """Result HTTP Status"""
-
- def __init__(self):
- pass
-
- #: Request return OK
- OK = httplib.OK
-
- #: New resource created successfully
- CREATED = httplib.CREATED
-
- #: Command accepted
- ACCEPTED = httplib.ACCEPTED
-
- #: Command returned OK but no data will be returned
- NO_CONTENT = httplib.NO_CONTENT
-
- #: Bad Request
- BAD_REQUEST = httplib.BAD_REQUEST
-
- #: User is not authorized
- UNAUTHORIZED = httplib.UNAUTHORIZED
-
- #: The request is not allowed
- FORBIDDEN = httplib.FORBIDDEN
-
- #: The requested resource was not found
- NOT_FOUND = httplib.NOT_FOUND
-
- #: The request is not allowed
- NOT_ALLOWED = httplib.METHOD_NOT_ALLOWED
-
- #: Request timed out
- TIMEOUT = httplib.REQUEST_TIMEOUT
-
- #: Invalid request
- CONFLICT = httplib.CONFLICT
-
- #: Service Unavailable
- BUSY = httplib.SERVICE_UNAVAILABLE
-
-
-class RestResult(object):
- """Result from a REST API operation"""
- def __init__(self, response=None, err=None):
- """Initialize a RestResult containing the results from a REST call
- :param response: HTTP response
- """
- self.response = response
- self.error = err
- self.data = ""
- self.status = 0
- if self.response:
- self.status = self.response.getcode()
- result = self.response.read()
- while result:
- self.data += result
- result = self.response.read()
-
- if self.error:
- self.status = self.error.code
- self.data = httplib.responses[self.status]
-
- LOG.debug('Response code: %s' % self.status)
- LOG.debug('Response data: %s' % self.data)
-
- def get_header(self, name):
- """Get an HTTP header with the given name from the results
-
- :param name: HTTP header name
- :return: The header value or None if no value is found
- """
- if self.response is None:
- return None
- info = self.response.info()
- return info.getheader(name)
-
-
-class RestClientError(Exception):
- """Exception for ZFS REST API client errors"""
- def __init__(self, status, name="ERR_INTERNAL", message=None):
-
- """Create a REST Response exception
-
- :param status: HTTP response status
- :param name: The name of the REST API error type
- :param message: Descriptive error message returned from REST call
- """
- super(RestClientError, self).__init__(message)
- self.code = status
- self.name = name
- self.msg = message
- if status in httplib.responses:
- self.msg = httplib.responses[status]
-
- def __str__(self):
- return "%d %s %s" % (self.code, self.name, self.msg)
-
-
-class RestClientURL(object):
- """ZFSSA urllib2 client"""
- def __init__(self, url, **kwargs):
- """Initialize a REST client.
-
- :param url: The ZFSSA REST API URL
- :key session: HTTP Cookie value of x-auth-session obtained from a
- normal BUI login.
- :key timeout: Time in seconds to wait for command to complete.
- (Default is 60 seconds)
- """
- self.url = url
- self.local = kwargs.get("local", False)
- self.base_path = kwargs.get("base_path", "/api")
- self.timeout = kwargs.get("timeout", 60)
- self.headers = None
- if kwargs.get('session'):
- self.headers['x-auth-session'] = kwargs.get('session')
-
- self.headers = {"content-type": "application/json"}
- self.do_logout = False
- self.auth_str = None
-
- def _path(self, path, base_path=None):
- """build rest url path"""
- if path.startswith("http://") or path.startswith("https://"):
- return path
- if base_path is None:
- base_path = self.base_path
- if not path.startswith(base_path) and not (
- self.local and ("/api" + path).startswith(base_path)):
- path = "%s%s" % (base_path, path)
- if self.local and path.startswith("/api"):
- path = path[4:]
- return self.url + path
-
- def _authorize(self):
- """Performs authorization setting x-auth-session"""
- self.headers['authorization'] = 'Basic %s' % self.auth_str
- if 'x-auth-session' in self.headers:
- del self.headers['x-auth-session']
-
- try:
- result = self.post("/access/v1")
- del self.headers['authorization']
- if result.status == httplib.CREATED:
- self.headers['x-auth-session'] = \
- result.get_header('x-auth-session')
- self.do_logout = True
- LOG.info(_LI('ZFSSA version: %s') %
- result.get_header('x-zfssa-version'))
-
- elif result.status == httplib.NOT_FOUND:
- raise RestClientError(result.status, name="ERR_RESTError",
- message="REST Not Available: \
- Please Upgrade")
-
- except RestClientError as err:
- del self.headers['authorization']
- raise err
-
- def login(self, auth_str):
- """Login to an appliance using a user name and password.
-
- Start a session like what is done logging into the BUI. This is not a
- requirement to run REST commands, since the protocol is stateless.
- What is does is set up a cookie session so that some server side
- caching can be done. If login is used remember to call logout when
- finished.
-
- :param auth_str: Authorization string (base64)
- """
- self.auth_str = auth_str
- self._authorize()
-
- def logout(self):
- """Logout of an appliance"""
- result = None
- try:
- result = self.delete("/access/v1", base_path="/api")
- except RestClientError:
- pass
-
- self.headers.clear()
- self.do_logout = False
- return result
-
- def islogin(self):
- """return if client is login"""
- return self.do_logout
-
- @staticmethod
- def mkpath(*args, **kwargs):
- """Make a path?query string for making a REST request
-
- :cmd_params args: The path part
- :cmd_params kwargs: The query part
- """
- buf = StringIO.StringIO()
- query = "?"
- for arg in args:
- buf.write("/")
- buf.write(arg)
- for k in kwargs:
- buf.write(query)
- if query == "?":
- query = "&"
- buf.write(k)
- buf.write("=")
- buf.write(kwargs[k])
- return buf.getvalue()
-
- def request(self, path, request, body=None, **kwargs):
- """Make an HTTP request and return the results
-
- :param path: Path used with the initialized URL to make a request
- :param request: HTTP request type (GET, POST, PUT, DELETE)
- :param body: HTTP body of request
- :key accept: Set HTTP 'Accept' header with this value
- :key base_path: Override the base_path for this request
- :key content: Set HTTP 'Content-Type' header with this value
- """
- out_hdrs = dict.copy(self.headers)
- if kwargs.get("accept"):
- out_hdrs['accept'] = kwargs.get("accept")
-
- if body:
- if isinstance(body, dict):
- body = str(json.dumps(body))
-
- if body and len(body):
- out_hdrs['content-length'] = len(body)
-
- zfssaurl = self._path(path, kwargs.get("base_path"))
- req = urllib2.Request(zfssaurl, body, out_hdrs)
- req.get_method = lambda: request
- maxreqretries = kwargs.get("maxreqretries", 10)
- retry = 0
- response = None
-
- LOG.debug('Request: %s %s' % (request, zfssaurl))
- LOG.debug('Out headers: %s' % out_hdrs)
- if body and body != '':
- LOG.debug('Body: %s' % body)
-
- while retry < maxreqretries:
- try:
- response = urllib2.urlopen(req, timeout=self.timeout)
- except urllib2.HTTPError as err:
- if err.code == httplib.NOT_FOUND:
- LOG.debug('REST Not Found: %s' % err.code)
- else:
- LOG.error(_LE('REST Not Available: %s') % err.code)
-
- if err.code == httplib.SERVICE_UNAVAILABLE and \
- retry < maxreqretries:
- retry += 1
- time.sleep(1)
- LOG.error(_LE('Server Busy retry request: %s') % retry)
- continue
- if (err.code == httplib.UNAUTHORIZED or
- err.code == httplib.INTERNAL_SERVER_ERROR) and \
- '/access/v1' not in zfssaurl:
- try:
- LOG.error(_LE('Authorizing request: '
- '%(zfssaurl)s'
- 'retry: %(retry)d .')
- % {'zfssaurl': zfssaurl,
- 'retry': retry})
- self._authorize()
- req.add_header('x-auth-session',
- self.headers['x-auth-session'])
- except RestClientError:
- pass
- retry += 1
- time.sleep(1)
- continue
-
- return RestResult(err=err)
-
- except urllib2.URLError as err:
- LOG.error(_LE('URLError: %s') % err.reason)
- raise RestClientError(-1, name="ERR_URLError",
- message=err.reason)
-
- break
-
- if response and response.getcode() == httplib.SERVICE_UNAVAILABLE and \
- retry >= maxreqretries:
- raise RestClientError(response.getcode(), name="ERR_HTTPError",
- message="REST Not Available: Disabled")
-
- return RestResult(response=response)
-
- def get(self, path, **kwargs):
- """Make an HTTP GET request
-
- :param path: Path to resource.
- """
- return self.request(path, "GET", **kwargs)
-
- def post(self, path, body="", **kwargs):
- """Make an HTTP POST request
-
- :param path: Path to resource.
- :param body: Post data content
- """
- return self.request(path, "POST", body, **kwargs)
-
- def put(self, path, body="", **kwargs):
- """Make an HTTP PUT request
-
- :param path: Path to resource.
- :param body: Put data content
- """
- return self.request(path, "PUT", body, **kwargs)
-
- def delete(self, path, **kwargs):
- """Make an HTTP DELETE request
-
- :param path: Path to resource that will be deleted.
- """
- return self.request(path, "DELETE", **kwargs)
-
- def head(self, path, **kwargs):
- """Make an HTTP HEAD request
-
- :param path: Path to resource.
- """
- return self.request(path, "HEAD", **kwargs)
+++ /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 oslo_log import log
-
-from cinder import exception
-from cinder.i18n import _, _LE
-
-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 Cinder Volume Driver
-"""
-import ast
-import base64
-
-from oslo_config import cfg
-from oslo_log import log
-from oslo_utils import units
-
-from cinder import exception
-from cinder.i18n import _, _LE, _LW
-from cinder.volume import driver
-from cinder.volume.drivers.san import san
-from cinder.volume.drivers.zfssa import zfssarest
-from cinder.volume import volume_types
-
-CONF = cfg.CONF
-LOG = log.getLogger(__name__)
-
-ZFSSA_OPTS = [
- cfg.StrOpt('zfssa_pool',
- help='Storage pool name.'),
- cfg.StrOpt('zfssa_project',
- help='Project name.'),
- cfg.StrOpt('zfssa_lun_volblocksize', default='8k',
- choices=['512', '1k', '2k', '4k', '8k', '16k', '32k', '64k',
- '128k'],
- help='Block size.'),
- cfg.BoolOpt('zfssa_lun_sparse', default=False,
- help='Flag to enable sparse (thin-provisioned): True, False.'),
- cfg.StrOpt('zfssa_lun_compression', default='off',
- choices=['off', 'lzjb', 'gzip-2', 'gzip', 'gzip-9'],
- help='Data compression.'),
- cfg.StrOpt('zfssa_lun_logbias', default='latency',
- choices=['latency', 'throughput'],
- help='Synchronous write bias.'),
- cfg.StrOpt('zfssa_initiator_group', default='',
- help='iSCSI initiator group.'),
- cfg.StrOpt('zfssa_initiator', default='',
- help='iSCSI initiator IQNs. (comma separated)'),
- cfg.StrOpt('zfssa_initiator_user', default='',
- help='iSCSI initiator CHAP user.'),
- cfg.StrOpt('zfssa_initiator_password', default='',
- help='iSCSI initiator CHAP password.', secret=True),
- cfg.StrOpt('zfssa_initiator_config', default='',
- help='iSCSI initiators configuration.'),
- cfg.StrOpt('zfssa_target_group', default='tgt-grp',
- help='iSCSI target group name.'),
- cfg.StrOpt('zfssa_target_user', default='',
- help='iSCSI target CHAP user.'),
- cfg.StrOpt('zfssa_target_password', default='',
- help='iSCSI target CHAP password.', secret=True),
- cfg.StrOpt('zfssa_target_portal',
- help='iSCSI target portal (Data-IP:Port, w.x.y.z:3260).'),
- cfg.StrOpt('zfssa_target_interfaces',
- help='Network interfaces of iSCSI targets. (comma separated)'),
- cfg.IntOpt('zfssa_rest_timeout',
- help='REST connection timeout. (seconds)')
-
-]
-
-CONF.register_opts(ZFSSA_OPTS)
-
-ZFSSA_LUN_SPECS = {'zfssa:volblocksize',
- 'zfssa:sparse',
- 'zfssa:compression',
- 'zfssa:logbias'}
-
-
-def factory_zfssa():
- return zfssarest.ZFSSAApi()
-
-
-class ZFSSAISCSIDriver(driver.ISCSIDriver):
- """ZFSSA Cinder volume driver"""
-
- VERSION = '1.0.0'
- protocol = 'iSCSI'
-
- def __init__(self, *args, **kwargs):
- super(ZFSSAISCSIDriver, 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 _get_target_alias(self):
- """return target alias"""
- return self.configuration.zfssa_target_group
-
- def do_setup(self, context):
- """Setup - create multiple elements.
-
- Project, initiators, initiatorgroup, target and targetgroup.
- """
- lcfg = self.configuration
- msg = (_('Connecting to host: %s.') % lcfg.san_ip)
- LOG.info(msg)
- self.zfssa = factory_zfssa()
- self.zfssa.set_host(lcfg.san_ip, timeout=lcfg.zfssa_rest_timeout)
- auth_str = base64.encodestring('%s:%s' %
- (lcfg.san_login,
- lcfg.san_password))[:-1]
- self.zfssa.login(auth_str)
- self.zfssa.create_project(lcfg.zfssa_pool, lcfg.zfssa_project,
- compression=lcfg.zfssa_lun_compression,
- logbias=lcfg.zfssa_lun_logbias)
-
- if (lcfg.zfssa_initiator_config != ''):
- initiator_config = ast.literal_eval(lcfg.zfssa_initiator_config)
- for initiator_group in initiator_config:
- zfssa_initiator_group = initiator_group
- for zfssa_initiator in initiator_config[zfssa_initiator_group]:
- self.zfssa.create_initiator(zfssa_initiator['iqn'],
- zfssa_initiator_group + '-' +
- zfssa_initiator['iqn'],
- chapuser=
- zfssa_initiator['user'],
- chapsecret=
- zfssa_initiator['password'])
- if (zfssa_initiator_group != 'default'):
- self.zfssa.add_to_initiatorgroup(
- zfssa_initiator['iqn'],
- zfssa_initiator_group)
- else:
- LOG.warning(_LW('zfssa_initiator_config not found. '
- 'Using deprecated configuration options.'))
- if (lcfg.zfssa_initiator != '' and
- (lcfg.zfssa_initiator_group == '' or
- lcfg.zfssa_initiator_group == 'default')):
- LOG.warning(_LW('zfssa_initiator: %(ini)s'
- ' wont be used on '
- 'zfssa_initiator_group= %(inigrp)s.')
- % {'ini': lcfg.zfssa_initiator,
- 'inigrp': lcfg.zfssa_initiator_group})
-
- # Setup initiator and initiator group
- if (lcfg.zfssa_initiator != '' and
- lcfg.zfssa_initiator_group != '' and
- lcfg.zfssa_initiator_group != 'default'):
- for initiator in lcfg.zfssa_initiator.split(','):
- self.zfssa.create_initiator(
- initiator, lcfg.zfssa_initiator_group + '-' +
- initiator, chapuser=lcfg.zfssa_initiator_user,
- chapsecret=lcfg.zfssa_initiator_password)
- self.zfssa.add_to_initiatorgroup(
- initiator, lcfg.zfssa_initiator_group)
-
- # Parse interfaces
- interfaces = []
- for interface in lcfg.zfssa_target_interfaces.split(','):
- if interface == '':
- continue
- interfaces.append(interface)
-
- # Setup target and target group
- iqn = self.zfssa.create_target(
- self._get_target_alias(),
- interfaces,
- tchapuser=lcfg.zfssa_target_user,
- tchapsecret=lcfg.zfssa_target_password)
-
- self.zfssa.add_to_targetgroup(iqn, lcfg.zfssa_target_group)
-
- def check_for_setup_error(self):
- """Check that driver can login.
-
- Check also pool, project, initiators, initiatorgroup, target and
- targetgroup.
- """
- lcfg = self.configuration
-
- self.zfssa.verify_pool(lcfg.zfssa_pool)
- self.zfssa.verify_project(lcfg.zfssa_pool, lcfg.zfssa_project)
-
- if (lcfg.zfssa_initiator_config != ''):
- initiator_config = ast.literal_eval(lcfg.zfssa_initiator_config)
- for initiator_group in initiator_config:
- zfssa_initiator_group = initiator_group
- for zfssa_initiator in initiator_config[zfssa_initiator_group]:
- self.zfssa.verify_initiator(zfssa_initiator['iqn'])
- else:
- if (lcfg.zfssa_initiator != '' and
- lcfg.zfssa_initiator_group != '' and
- lcfg.zfssa_initiator_group != 'default'):
- for initiator in lcfg.zfssa_initiator.split(','):
- self.zfssa.verify_initiator(initiator)
-
- self.zfssa.verify_target(self._get_target_alias())
-
- def _get_provider_info(self, volume):
- """return provider information"""
- lcfg = self.configuration
- lun = self.zfssa.get_lun(lcfg.zfssa_pool,
- lcfg.zfssa_project, volume['name'])
- iqn = self.zfssa.get_target(self._get_target_alias())
- loc = "%s %s %s" % (lcfg.zfssa_target_portal, iqn, lun['number'])
- LOG.debug('_get_provider_info: provider_location: %s' % loc)
- provider = {'provider_location': loc}
- if lcfg.zfssa_target_user != '' and lcfg.zfssa_target_password != '':
- provider['provider_auth'] = ('CHAP %s %s' %
- lcfg.zfssa_target_user,
- lcfg.zfssa_target_password)
-
- return provider
-
- def create_volume(self, volume):
- """Create a volume on ZFSSA"""
- LOG.debug('zfssa.create_volume: volume=' + volume['name'])
- lcfg = self.configuration
- volsize = str(volume['size']) + 'g'
- specs = self._get_voltype_specs(volume)
- self.zfssa.create_lun(lcfg.zfssa_pool,
- lcfg.zfssa_project,
- volume['name'],
- volsize,
- lcfg.zfssa_target_group,
- specs)
-
- def delete_volume(self, volume):
- """Deletes a volume with the given volume['name']."""
- LOG.debug('zfssa.delete_volume: name=' + volume['name'])
- lcfg = self.configuration
- lun2del = self.zfssa.get_lun(lcfg.zfssa_pool,
- lcfg.zfssa_project,
- volume['name'])
- # Delete clone temp snapshot. see create_cloned_volume()
- if 'origin' in lun2del and 'id' in volume:
- if lun2del['nodestroy']:
- self.zfssa.set_lun_props(lcfg.zfssa_pool,
- lcfg.zfssa_project,
- volume['name'],
- nodestroy=False)
-
- tmpsnap = 'tmp-snapshot-%s' % volume['id']
- if lun2del['origin']['snapshot'] == tmpsnap:
- self.zfssa.delete_snapshot(lcfg.zfssa_pool,
- lcfg.zfssa_project,
- lun2del['origin']['share'],
- lun2del['origin']['snapshot'])
- return
-
- self.zfssa.delete_lun(pool=lcfg.zfssa_pool,
- project=lcfg.zfssa_project,
- lun=volume['name'])
-
- def create_snapshot(self, snapshot):
- """Creates a snapshot with the given snapshot['name'] of the
- snapshot['volume_name']
- """
- LOG.debug('zfssa.create_snapshot: snapshot=' + snapshot['name'])
- lcfg = self.configuration
- self.zfssa.create_snapshot(lcfg.zfssa_pool,
- lcfg.zfssa_project,
- snapshot['volume_name'],
- snapshot['name'])
-
- def delete_snapshot(self, snapshot):
- """Deletes a snapshot."""
- LOG.debug('zfssa.delete_snapshot: snapshot=' + snapshot['name'])
- lcfg = self.configuration
- has_clones = self.zfssa.has_clones(lcfg.zfssa_pool,
- lcfg.zfssa_project,
- snapshot['volume_name'],
- snapshot['name'])
- if has_clones:
- LOG.error(_LE('Snapshot %s: has clones') % snapshot['name'])
- raise exception.SnapshotIsBusy(snapshot_name=snapshot['name'])
-
- self.zfssa.delete_snapshot(lcfg.zfssa_pool,
- lcfg.zfssa_project,
- snapshot['volume_name'],
- snapshot['name'])
-
- def create_volume_from_snapshot(self, volume, snapshot):
- """Creates a volume from a snapshot - clone a snapshot"""
- LOG.debug('zfssa.create_volume_from_snapshot: volume=' +
- volume['name'])
- LOG.debug('zfssa.create_volume_from_snapshot: snapshot=' +
- snapshot['name'])
- if not self._verify_clone_size(snapshot, volume['size'] * units.Gi):
- exception_msg = (_('Error verifying clone size on '
- 'Volume clone: %(clone)s '
- 'Size: %(size)d on'
- 'Snapshot: %(snapshot)s')
- % {'clone': volume['name'],
- 'size': volume['size'],
- 'snapshot': snapshot['name']})
- LOG.error(exception_msg)
- raise exception.InvalidInput(reason=exception_msg)
-
- lcfg = self.configuration
- self.zfssa.clone_snapshot(lcfg.zfssa_pool,
- lcfg.zfssa_project,
- snapshot['volume_name'],
- snapshot['name'],
- volume['name'])
-
- def _update_volume_status(self):
- """Retrieve status info from volume group."""
- LOG.debug("Updating volume status")
- self._stats = None
- 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
-
- lcfg = self.configuration
- (avail, total) = self.zfssa.get_pool_stats(lcfg.zfssa_pool)
- if avail is None or total is None:
- return
-
- data['total_capacity_gb'] = int(total) / units.Gi
- data['free_capacity_gb'] = int(avail) / units.Gi
- data['reserved_percentage'] = 0
- data['QoS_support'] = False
- self._stats = data
-
- def get_volume_stats(self, refresh=False):
- """Get volume status.
- If 'refresh' is True, run update the stats first.
- """
- if refresh:
- self._update_volume_status()
- return self._stats
-
- def create_export(self, context, volume):
- pass
-
- def remove_export(self, context, volume):
- pass
-
- def ensure_export(self, context, volume):
- pass
-
- def copy_image_to_volume(self, context, volume, image_service, image_id):
- self.ensure_export(context, volume)
- super(ZFSSAISCSIDriver, self).copy_image_to_volume(
- context, volume, image_service, image_id)
-
- def extend_volume(self, volume, new_size):
- """Driver entry point to extent volume size."""
- LOG.debug('extend_volume: volume name: %s' % volume['name'])
- lcfg = self.configuration
- self.zfssa.set_lun_props(lcfg.zfssa_pool,
- lcfg.zfssa_project,
- volume['name'],
- volsize=new_size * units.Gi)
-
- def create_cloned_volume(self, volume, src_vref):
- """Create a clone of the specified volume."""
- zfssa_snapshot = {'volume_name': src_vref['name'],
- 'name': 'tmp-snapshot-%s' % volume['id']}
- self.create_snapshot(zfssa_snapshot)
- try:
- self.create_volume_from_snapshot(volume, zfssa_snapshot)
- except exception.VolumeBackendAPIException:
- LOG.error(_LE('Clone Volume:'
- '%(volume)s failed from source volume:'
- '%(src_vref)s')
- % {'volume': volume['name'],
- 'src_vref': src_vref['name']})
- # Cleanup snapshot
- self.delete_snapshot(zfssa_snapshot)
-
- def local_path(self, volume):
- """Not implemented"""
- pass
-
- def backup_volume(self, context, backup, backup_service):
- """Not implemented"""
- pass
-
- def restore_backup(self, context, backup, volume, backup_service):
- """Not implemented"""
- pass
-
- def _verify_clone_size(self, snapshot, size):
- """Check whether the clone size is the same as the parent volume"""
- lcfg = self.configuration
- lun = self.zfssa.get_lun(lcfg.zfssa_pool,
- lcfg.zfssa_project,
- snapshot['volume_name'])
- return lun['size'] == size
-
- def initialize_connection(self, volume, connector):
- lcfg = self.configuration
- init_groups = self.zfssa.get_initiator_initiatorgroup(
- connector['initiator'])
- for initiator_group in init_groups:
- self.zfssa.set_lun_initiatorgroup(lcfg.zfssa_pool,
- lcfg.zfssa_project,
- volume['name'],
- initiator_group)
- iscsi_properties = {}
- provider = self._get_provider_info(volume)
- (target_portal, iqn, lun) = provider['provider_location'].split()
- iscsi_properties['target_discovered'] = False
- iscsi_properties['target_portal'] = target_portal
- iscsi_properties['target_iqn'] = iqn
- iscsi_properties['target_lun'] = lun
- iscsi_properties['volume_id'] = volume['id']
-
- if 'provider_auth' in provider:
- (auth_method, auth_username, auth_password) = provider[
- 'provider_auth'].split()
- iscsi_properties['auth_method'] = auth_method
- iscsi_properties['auth_username'] = auth_username
- iscsi_properties['auth_password'] = auth_password
-
- return {
- 'driver_volume_type': 'iscsi',
- 'data': iscsi_properties
- }
-
- def terminate_connection(self, volume, connector, **kwargs):
- """Driver entry point to terminate a connection for a volume."""
- LOG.debug('terminate_connection: volume name: %s.' % volume['name'])
- lcfg = self.configuration
- self.zfssa.set_lun_initiatorgroup(lcfg.zfssa_pool,
- lcfg.zfssa_project,
- volume['name'],
- '')
-
- def _get_voltype_specs(self, volume):
- """Get specs suitable for volume creation."""
- vtype = volume.get('volume_type_id', None)
- extra_specs = None
- if vtype:
- extra_specs = volume_types.get_volume_type_extra_specs(vtype)
-
- return self._get_specs(extra_specs)
-
- def _get_specs(self, xspecs):
- """Return a dict with extra specs and/or config values."""
- result = {}
- for spc in ZFSSA_LUN_SPECS:
- val = None
- prop = spc.split(':')[1]
- cfg = 'zfssa_lun_' + prop
- if xspecs:
- val = xspecs.pop(spc, None)
-
- if val is None:
- val = self.configuration.safe_get(cfg)
-
- if val is not None and val != '':
- result.update({prop: val})
-
- return result
+++ /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
-import datetime as dt
-import errno
-
-from oslo_config import cfg
-from oslo_log import log
-from oslo_utils import excutils
-from oslo_utils import units
-
-from cinder import exception
-from cinder.i18n import _, _LE, _LI
-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',
- choices=['off', 'lzjb', 'gzip-2', 'gzip', 'gzip-9'],
- help='Data compression.'),
- cfg.StrOpt('zfssa_nfs_share_logbias', default='latency',
- choices=['latency', 'throughput'],
- 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' %
- dt.datetime.utcnow().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
+++ /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 Proxy
-"""
-import json
-
-from oslo_log import log
-
-from cinder import exception
-from cinder.i18n import _, _LE
-from cinder.volume.drivers.zfssa import restclient
-from cinder.volume.drivers.zfssa import webdavclient
-
-LOG = log.getLogger(__name__)
-
-
-class ZFSSAApi(object):
- """ZFSSA API proxy class"""
-
- def __init__(self):
- self.host = None
- self.url = None
- self.rclient = None
-
- def __del__(self):
- if self.rclient and self.rclient.islogin():
- self.rclient.logout()
-
- def _is_pool_owned(self, pdata):
- """returns True if the pool's owner is the
- same as the host.
- """
- svc = '/api/system/v1/version'
- ret = self.rclient.get(svc)
- if ret.status != restclient.Status.OK:
- exception_msg = (_('Error getting version: '
- 'svc: %(svc)s.'
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s.')
- % {'svc': svc,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
- raise exception.VolumeBackendAPIException(data=exception_msg)
-
- vdata = json.loads(ret.data)
- return vdata['version']['asn'] == pdata['pool']['asn'] and \
- vdata['version']['nodename'] == pdata['pool']['owner']
-
- def set_host(self, host, timeout=None):
- self.host = host
- self.url = "https://" + self.host + ":215"
- self.rclient = restclient.RestClientURL(self.url, timeout=timeout)
-
- def login(self, auth_str):
- """Login to the appliance"""
- if self.rclient and not self.rclient.islogin():
- self.rclient.login(auth_str)
-
- def get_pool_stats(self, pool):
- """Get space available and total properties of a pool
- returns (avail, total).
- """
- svc = '/api/storage/v1/pools/' + pool
- ret = self.rclient.get(svc)
- if ret.status != restclient.Status.OK:
- exception_msg = (_('Error Getting Pool Stats: '
- 'Pool: %(pool)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s.')
- % {'pool': pool,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
- raise exception.InvalidVolume(reason=exception_msg)
-
- val = json.loads(ret.data)
-
- if not self._is_pool_owned(val):
- exception_msg = (_('Error Pool ownership: '
- 'Pool %(pool)s is not owned '
- 'by %(host)s.')
- % {'pool': pool,
- 'host': self.host})
- LOG.error(exception_msg)
- raise exception.InvalidInput(reason=pool)
-
- avail = val['pool']['usage']['available']
- total = val['pool']['usage']['total']
-
- return avail, total
-
- def create_project(self, pool, project, compression=None, logbias=None):
- """Create a project on a pool
- Check first whether the pool exists.
- """
- self.verify_pool(pool)
- svc = '/api/storage/v1/pools/' + pool + '/projects/' + project
- ret = self.rclient.get(svc)
- if ret.status != restclient.Status.OK:
- svc = '/api/storage/v1/pools/' + pool + '/projects'
- arg = {
- 'name': project
- }
- if compression and compression != '':
- arg.update({'compression': compression})
- if logbias and logbias != '':
- arg.update({'logbias': logbias})
-
- ret = self.rclient.post(svc, arg)
- if ret.status != restclient.Status.CREATED:
- exception_msg = (_('Error Creating Project: '
- '%(project)s on '
- 'Pool: %(pool)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s .')
- % {'project': project,
- 'pool': pool,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
- raise exception.VolumeBackendAPIException(data=exception_msg)
-
- def create_initiator(self, initiator, alias, chapuser=None,
- chapsecret=None):
- """Create an iSCSI initiator."""
-
- svc = '/api/san/v1/iscsi/initiators/alias=' + alias
- ret = self.rclient.get(svc)
- if ret.status != restclient.Status.OK:
- svc = '/api/san/v1/iscsi/initiators'
- arg = {
- 'initiator': initiator,
- 'alias': alias
- }
- if chapuser and chapuser != '' and chapsecret and chapsecret != '':
- arg.update({'chapuser': chapuser,
- 'chapsecret': chapsecret})
-
- ret = self.rclient.post(svc, arg)
- if ret.status != restclient.Status.CREATED:
- exception_msg = (_('Error Creating Initiator: '
- '%(initiator)s on '
- 'Alias: %(alias)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s .')
- % {'initiator': initiator,
- 'alias': alias,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
- raise exception.VolumeBackendAPIException(data=exception_msg)
-
- def add_to_initiatorgroup(self, initiator, initiatorgroup):
- """Add an iSCSI initiator to initiatorgroup"""
- svc = '/api/san/v1/iscsi/initiator-groups/' + initiatorgroup
- ret = self.rclient.get(svc)
- if ret.status != restclient.Status.OK:
- svc = '/api/san/v1/iscsi/initiator-groups'
- arg = {
- 'name': initiatorgroup,
- 'initiators': [initiator]
- }
- ret = self.rclient.post(svc, arg)
- if ret.status != restclient.Status.CREATED:
- exception_msg = (_('Error Adding Initiator: '
- '%(initiator)s on group'
- 'InitiatorGroup: %(initiatorgroup)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s .')
- % {'initiator': initiator,
- 'initiatorgroup': initiatorgroup,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
- raise exception.VolumeBackendAPIException(data=exception_msg)
- else:
- val = json.loads(ret.data)
- inits = val['group']['initiators']
- if inits is None:
- exception_msg = (_('Error Getting Initiators: '
- 'InitiatorGroup: %(initiatorgroup)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s .')
- % {'initiatorgroup': initiatorgroup,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
- raise exception.VolumeBackendAPIException(data=exception_msg)
-
- if initiator in inits:
- return
-
- inits.append(initiator)
- svc = '/api/san/v1/iscsi/initiator-groups/' + initiatorgroup
- arg = {
- 'initiators': inits
- }
- ret = self.rclient.put(svc, arg)
- if ret.status != restclient.Status.ACCEPTED:
- exception_msg = (_('Error Adding Initiator: '
- '%(initiator)s on group'
- 'InitiatorGroup: %(initiatorgroup)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s .')
- % {'initiator': initiator,
- 'initiatorgroup': initiatorgroup,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
- raise exception.VolumeBackendAPIException(data=exception_msg)
-
- def create_target(self, alias, interfaces=None, tchapuser=None,
- tchapsecret=None):
- """Create an iSCSI target.
- interfaces: an array with network interfaces
- tchapuser, tchapsecret: target's chapuser and chapsecret
- returns target iqn
- """
- svc = '/api/san/v1/iscsi/targets/alias=' + alias
- ret = self.rclient.get(svc)
- if ret.status != restclient.Status.OK:
- svc = '/api/san/v1/iscsi/targets'
- arg = {
- 'alias': alias
- }
-
- if tchapuser and tchapuser != '' and tchapsecret and \
- tchapsecret != '':
- arg.update({'targetchapuser': tchapuser,
- 'targetchapsecret': tchapsecret,
- 'auth': 'chap'})
-
- if interfaces is not None and len(interfaces) > 0:
- arg.update({'interfaces': interfaces})
-
- ret = self.rclient.post(svc, arg)
- if ret.status != restclient.Status.CREATED:
- exception_msg = (_('Error Creating Target: '
- '%(alias)s'
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s .')
- % {'alias': alias,
- '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['target']['iqn']
-
- def get_target(self, alias):
- """Get an iSCSI target iqn."""
- svc = '/api/san/v1/iscsi/targets/alias=' + alias
- ret = self.rclient.get(svc)
- if ret.status != restclient.Status.OK:
- exception_msg = (_('Error Getting Target: '
- '%(alias)s'
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s .')
- % {'alias': alias,
- '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['target']['iqn']
-
- def add_to_targetgroup(self, iqn, targetgroup):
- """Add an iSCSI target to targetgroup."""
- svc = '/api/san/v1/iscsi/target-groups/' + targetgroup
- ret = self.rclient.get(svc)
- if ret.status != restclient.Status.OK:
- svccrt = '/api/san/v1/iscsi/target-groups'
- arg = {
- 'name': targetgroup,
- 'targets': [iqn]
- }
-
- ret = self.rclient.post(svccrt, arg)
- if ret.status != restclient.Status.CREATED:
- exception_msg = (_('Error Creating TargetGroup: '
- '%(targetgroup)s with'
- 'IQN: %(iqn)s'
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s .')
- % {'targetgroup': targetgroup,
- 'iqn': iqn,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
- raise exception.VolumeBackendAPIException(data=exception_msg)
-
- return
-
- arg = {
- 'targets': [iqn]
- }
-
- ret = self.rclient.put(svc, arg)
- if ret.status != restclient.Status.ACCEPTED:
- exception_msg = (_('Error Adding to TargetGroup: '
- '%(targetgroup)s with'
- 'IQN: %(iqn)s'
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s.')
- % {'targetgroup': targetgroup,
- 'iqn': iqn,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
- raise exception.VolumeBackendAPIException(data=exception_msg)
-
- def verify_pool(self, pool):
- """Checks whether pool exists."""
- svc = '/api/storage/v1/pools/' + pool
- ret = self.rclient.get(svc)
- if ret.status != restclient.Status.OK:
- exception_msg = (_('Error Verifying Pool: '
- '%(pool)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s.')
- % {'pool': pool,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
- raise exception.VolumeBackendAPIException(data=exception_msg)
-
- def verify_project(self, pool, project):
- """Checks whether project exists."""
- svc = '/api/storage/v1/pools/' + pool + '/projects/' + project
- ret = self.rclient.get(svc)
- if ret.status != restclient.Status.OK:
- exception_msg = (_('Error Verifying '
- 'Project: %(project)s on '
- 'Pool: %(pool)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s.')
- % {'project': project,
- 'pool': pool,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
- raise exception.VolumeBackendAPIException(data=exception_msg)
-
- def verify_initiator(self, iqn):
- """Check whether initiator iqn exists."""
- svc = '/api/san/v1/iscsi/initiators/' + iqn
- ret = self.rclient.get(svc)
- if ret.status != restclient.Status.OK:
- exception_msg = (_('Error Verifying '
- 'Initiator: %(iqn)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s.')
- % {'initiator': iqn,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
- raise exception.VolumeBackendAPIException(data=exception_msg)
-
- def verify_target(self, alias):
- """Check whether target alias exists."""
- svc = '/api/san/v1/iscsi/targets/alias=' + alias
- ret = self.rclient.get(svc)
- if ret.status != restclient.Status.OK:
- exception_msg = (_('Error Verifying '
- 'Target: %(alias)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s.')
- % {'alias': alias,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
- raise exception.VolumeBackendAPIException(data=exception_msg)
-
- def create_lun(self, pool, project, lun, volsize, targetgroup, specs):
-
- """Create a LUN.
- specs - contains volume properties (e.g blocksize, compression).
- """
- svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
- project + '/luns'
- arg = {
- 'name': lun,
- 'volsize': volsize,
- 'targetgroup': targetgroup,
- 'initiatorgroup': 'com.sun.ms.vss.hg.maskAll'
- }
- if specs:
- arg.update(specs)
-
- ret = self.rclient.post(svc, arg)
- if ret.status != restclient.Status.CREATED:
- exception_msg = (_('Error Creating '
- 'Volume: %(lun)s '
- 'Size: %(size)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s.')
- % {'lun': lun,
- 'size': volsize,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
- raise exception.VolumeBackendAPIException(data=exception_msg)
-
- def get_lun(self, pool, project, lun):
- """return iscsi lun properties."""
- svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
- project + "/luns/" + lun
- ret = self.rclient.get(svc)
- if ret.status != restclient.Status.OK:
- exception_msg = (_('Error Getting '
- 'Volume: %(lun)s on '
- 'Pool: %(pool)s '
- 'Project: %(project)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s.')
- % {'lun': lun,
- '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)
- ret = {
- 'guid': val['lun']['lunguid'],
- 'number': val['lun']['assignednumber'],
- 'initiatorgroup': val['lun']['initiatorgroup'],
- 'size': val['lun']['volsize'],
- 'nodestroy': val['lun']['nodestroy']
- }
- if 'origin' in val['lun']:
- ret.update({'origin': val['lun']['origin']})
- if isinstance(ret['number'], list):
- ret['number'] = ret['number'][0]
-
- return ret
-
- def set_lun_initiatorgroup(self, pool, project, lun, initiatorgroup):
- """Set the initiatorgroup property of a LUN."""
- if initiatorgroup == '':
- initiatorgroup = 'com.sun.ms.vss.hg.maskAll'
-
- svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
- project + '/luns/' + lun
- arg = {
- 'initiatorgroup': initiatorgroup
- }
-
- ret = self.rclient.put(svc, arg)
- if ret.status != restclient.Status.ACCEPTED:
- exception_msg = (_('Error Setting '
- 'Volume: %(lun)s to '
- 'InitiatorGroup: %(initiatorgroup)s '
- 'Pool: %(pool)s '
- 'Project: %(project)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s.')
- % {'lun': lun,
- 'initiatorgroup': initiatorgroup,
- 'pool': pool,
- 'project': project,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
-
- def delete_lun(self, pool, project, lun):
- """delete iscsi lun."""
- svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
- project + '/luns/' + lun
-
- ret = self.rclient.delete(svc)
- if ret.status != restclient.Status.NO_CONTENT:
- exception_msg = (_('Error Deleting '
- 'Volume: %(lun)s to '
- 'Pool: %(pool)s '
- 'Project: %(project)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s.')
- % {'lun': lun,
- 'pool': pool,
- 'project': project,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
-
- def create_snapshot(self, pool, project, lun, snapshot):
- """create snapshot."""
- svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
- project + '/luns/' + lun + '/snapshots'
- arg = {
- 'name': snapshot
- }
-
- ret = self.rclient.post(svc, arg)
- if ret.status != restclient.Status.CREATED:
- exception_msg = (_('Error Creating '
- 'Snapshot: %(snapshot)s on'
- 'Volume: %(lun)s to '
- 'Pool: %(pool)s '
- 'Project: %(project)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s.')
- % {'snapshot': snapshot,
- 'lun': lun,
- '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, lun, snapshot):
- """delete snapshot."""
- svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
- project + '/luns/' + lun + '/snapshots/' + snapshot
-
- ret = self.rclient.delete(svc)
- if ret.status != restclient.Status.NO_CONTENT:
- exception_msg = (_('Error Deleting '
- 'Snapshot: %(snapshot)s on '
- 'Volume: %(lun)s to '
- 'Pool: %(pool)s '
- 'Project: %(project)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s.')
- % {'snapshot': snapshot,
- 'lun': lun,
- 'pool': pool,
- 'project': project,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
- raise exception.VolumeBackendAPIException(data=exception_msg)
-
- def clone_snapshot(self, pool, project, lun, snapshot, clone):
- """clone snapshot."""
- svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
- project + '/luns/' + lun + '/snapshots/' + snapshot + '/clone'
- arg = {
- 'project': project,
- 'share': clone,
- 'nodestroy': True
- }
-
- ret = self.rclient.put(svc, arg)
- if ret.status != restclient.Status.CREATED:
- exception_msg = (_('Error Cloning '
- 'Snapshot: %(snapshot)s on '
- 'Volume: %(lun)s of '
- 'Pool: %(pool)s '
- 'Project: %(project)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s.')
- % {'snapshot': snapshot,
- 'lun': lun,
- 'pool': pool,
- 'project': project,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
- raise exception.VolumeBackendAPIException(data=exception_msg)
-
- def set_lun_props(self, pool, project, lun, **kargs):
- """set lun properties."""
- svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
- project + '/luns/' + lun
- if kargs is None:
- return
-
- ret = self.rclient.put(svc, kargs)
- if ret.status != restclient.Status.ACCEPTED:
- exception_msg = (_('Error Setting props '
- 'Props: %(props)s on '
- 'Volume: %(lun)s of '
- 'Pool: %(pool)s '
- 'Project: %(project)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s.')
- % {'props': kargs,
- 'lun': lun,
- 'pool': pool,
- 'project': project,
- 'ret.status': ret.status,
- 'ret.data': ret.data})
- LOG.error(exception_msg)
- raise exception.VolumeBackendAPIException(data=exception_msg)
-
- def has_clones(self, pool, project, lun, snapshot):
- """Checks whether snapshot has clones or not."""
- svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
- project + '/luns/' + lun + '/snapshots/' + snapshot
-
- ret = self.rclient.get(svc)
- if ret.status != restclient.Status.OK:
- exception_msg = (_('Error Getting '
- 'Snapshot: %(snapshot)s on '
- 'Volume: %(lun)s to '
- 'Pool: %(pool)s '
- 'Project: %(project)s '
- 'Return code: %(ret.status)d '
- 'Message: %(ret.data)s.')
- % {'snapshot': snapshot,
- 'lun': lun,
- '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['snapshot']['numclones'] != 0
-
- def get_initiator_initiatorgroup(self, initiator):
- """Returns the initiator group of the initiator."""
- groups = []
- svc = "/api/san/v1/iscsi/initiator-groups"
- ret = self.rclient.get(svc)
- if ret.status != restclient.Status.OK:
- LOG.error(_LE('Error getting initiator groups.'))
- exception_msg = (_('Error getting initiator groups.'))
- raise exception.VolumeBackendAPIException(data=exception_msg)
- val = json.loads(ret.data)
- for initiator_group in val['groups']:
- if initiator in initiator_group['initiators']:
- groups.append(initiator_group["name"])
- if len(groups) == 0:
- LOG.debug("Initiator group not found. Attaching volume to "
- "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']