From 99822262b094a3d167988af0446ac8ff043376c6 Mon Sep 17 00:00:00 2001 From: Abhilash Divakaran Date: Mon, 8 Aug 2016 11:03:21 -0700 Subject: [PATCH] Revert "Remove Tegile volume driver" This reverts commit 4679d1fae04ef94e5126c3bca17d45ad6ef2deb9. Tegile Volume driver was removed because CI was not running. CI was down due to hardware inavailability. The CI systems are up and running now. Change-Id: Ie098a1b5330921426227543ad52b5ffed031fa85 Implements: blueprint tegile-volume-driver --- cinder/exception.py | 5 + cinder/opts.py | 2 + .../tests/unit/volume/drivers/test_tegile.py | 408 +++++++++++ cinder/volume/drivers/tegile.py | 662 ++++++++++++++++++ ...remove-tegile-driver-b6f33ff704f4d433.yaml | 4 - 5 files changed, 1077 insertions(+), 4 deletions(-) create mode 100644 cinder/tests/unit/volume/drivers/test_tegile.py create mode 100644 cinder/volume/drivers/tegile.py delete mode 100644 releasenotes/notes/remove-tegile-driver-b6f33ff704f4d433.yaml diff --git a/cinder/exception.py b/cinder/exception.py index be8099705ae..054fe3524f5 100644 --- a/cinder/exception.py +++ b/cinder/exception.py @@ -1247,6 +1247,11 @@ class CohoException(VolumeDriverException): message = _("Coho Data Cinder driver failure: %(message)s") +# Tegile Storage drivers +class TegileAPIException(VolumeBackendAPIException): + message = _("Unexpected response from Tegile IntelliFlash API") + + # NexentaStor driver exception class NexentaException(VolumeDriverException): message = _("%(message)s") diff --git a/cinder/opts.py b/cinder/opts.py index 54c1015c38a..4c004b03355 100644 --- a/cinder/opts.py +++ b/cinder/opts.py @@ -160,6 +160,7 @@ from cinder.volume.drivers import smbfs as cinder_volume_drivers_smbfs from cinder.volume.drivers import solidfire as cinder_volume_drivers_solidfire from cinder.volume.drivers.synology import synology_common as \ cinder_volume_drivers_synology_synologycommon +from cinder.volume.drivers import tegile as cinder_volume_drivers_tegile from cinder.volume.drivers import tintri as cinder_volume_drivers_tintri from cinder.volume.drivers.violin import v7000_common as \ cinder_volume_drivers_violin_v7000common @@ -347,6 +348,7 @@ def list_opts(): cinder_volume_manager.volume_manager_opts, cinder_volume_drivers_ibm_flashsystemiscsi. flashsystem_iscsi_opts, + cinder_volume_drivers_tegile.tegile_opts, cinder_volume_drivers_ibm_flashsystemcommon.flashsystem_opts, [cinder_volume_api.allow_force_upload_opt], [cinder_volume_api.volume_host_opt], diff --git a/cinder/tests/unit/volume/drivers/test_tegile.py b/cinder/tests/unit/volume/drivers/test_tegile.py new file mode 100644 index 00000000000..f8ad18ac743 --- /dev/null +++ b/cinder/tests/unit/volume/drivers/test_tegile.py @@ -0,0 +1,408 @@ +# Copyright (c) 2015 by Tegile Systems, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Volume driver Test for Tegile storage. +""" + +import mock + +from cinder import context +from cinder.exception import TegileAPIException +from cinder import test +from cinder.volume.drivers import tegile + +BASE_DRIVER = tegile.TegileIntelliFlashVolumeDriver +ISCSI_DRIVER = tegile.TegileISCSIDriver +FC_DRIVER = tegile.TegileFCDriver + +test_config = mock.Mock() +test_config.san_ip = 'some-ip' +test_config.san_login = 'some-user' +test_config.san_password = 'some-password' +test_config.san_is_local = True +test_config.tegile_default_pool = 'random-pool' +test_config.tegile_default_project = 'random-project' +test_config.volume_backend_name = "unittest" + +test_volume = {'host': 'node#testPool', + 'name': 'testvol', + 'id': 'a24c2ee8-525a-4406-8ccd-8d38688f8e9e', + '_name_id': 'testvol', + 'metadata': {'project': 'testProj'}, + 'provider_location': None, + 'size': 10} + +test_snapshot = {'name': 'testSnap', + 'id': '07ae9978-5445-405e-8881-28f2adfee732', + 'volume': {'host': 'node#testPool', + 'size': 1, + '_name_id': 'testvol' + } + } + +array_stats = {'total_capacity_gb': 4569.199686084874, + 'free_capacity_gb': 4565.381390112452, + 'pools': [{'total_capacity_gb': 913.5, + 'QoS_support': False, + 'free_capacity_gb': 911.812650680542, + 'reserved_percentage': 0, + 'pool_name': 'pyramid' + }, + {'total_capacity_gb': 2742.1996604874, + 'QoS_support': False, + 'free_capacity_gb': 2740.148867149747, + 'reserved_percentage': 0, + 'pool_name': 'cobalt' + }, + {'total_capacity_gb': 913.5, + 'QoS_support': False, + 'free_capacity_gb': 913.4198722839355, + 'reserved_percentage': 0, + 'pool_name': 'test' + }] + } + + +class FakeTegileService(object): + @staticmethod + def send_api_request(method, params=None, + request_type='post', + api_service='v2', + fine_logging=False): + if method is 'createVolume': + return '' + elif method is 'deleteVolume': + return '' + elif method is 'createVolumeSnapshot': + return '' + elif method is 'deleteVolumeSnapshot': + return '' + elif method is 'cloneVolumeSnapshot': + return '' + elif method is 'listPools': + return '' + elif method is 'resizeVolume': + return '' + elif method is 'getVolumeSizeinGB': + return 25 + elif method is 'getISCSIMappingForVolume': + return {'target_lun': '27', + 'target_iqn': 'iqn.2012-02.com.tegile:openstack-cobalt', + 'target_portal': '10.68.103.106:3260' + } + elif method is 'getFCPortsForVolume': + return {'target_lun': '12', + 'initiator_target_map': + '{"21000024ff59bb6e":["21000024ff578701",],' + '"21000024ff59bb6f":["21000024ff578700",],}', + 'target_wwn': '["21000024ff578700","21000024ff578701",]'} + elif method is 'getArrayStats': + return array_stats + + +fake_tegile_backend = FakeTegileService() + + +class FakeTegileServiceFail(object): + @staticmethod + def send_api_request(method, params=None, + request_type='post', + api_service='v2', + fine_logging=False): + raise TegileAPIException + + +fake_tegile_backend_fail = FakeTegileServiceFail() + + +class TegileIntelliFlashVolumeDriverTestCase(test.TestCase): + def setUp(self): + self.ctxt = context.get_admin_context() + self.configuration = test_config + super(TegileIntelliFlashVolumeDriverTestCase, self).setUp() + + def test_create_volume(self): + tegile_driver = self.get_object(self.configuration) + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend): + self.assertEqual({ + 'metadata': {'pool': 'testPool', + 'project': test_config.tegile_default_project + } + }, tegile_driver.create_volume(test_volume)) + + def test_create_volume_fail(self): + tegile_driver = self.get_object(self.configuration) + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend_fail): + self.assertRaises(TegileAPIException, + tegile_driver.create_volume, + test_volume) + + def test_delete_volume(self): + tegile_driver = self.get_object(self.configuration) + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend): + tegile_driver.delete_volume(test_volume) + + def test_delete_volume_fail(self): + tegile_driver = self.get_object(self.configuration) + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend_fail): + self.assertRaises(TegileAPIException, + tegile_driver.delete_volume, + test_volume) + + def test_create_snapshot(self): + tegile_driver = self.get_object(self.configuration) + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend): + tegile_driver.create_snapshot(test_snapshot) + + def test_create_snapshot_fail(self): + tegile_driver = self.get_object(self.configuration) + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend_fail): + self.assertRaises(TegileAPIException, + tegile_driver.create_snapshot, + test_snapshot) + + def test_delete_snapshot(self): + tegile_driver = self.get_object(self.configuration) + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend): + tegile_driver.delete_snapshot(test_snapshot) + + def test_delete_snapshot_fail(self): + tegile_driver = self.get_object(self.configuration) + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend_fail): + self.assertRaises(TegileAPIException, + tegile_driver.delete_snapshot, + test_snapshot) + + def test_create_volume_from_snapshot(self): + tegile_driver = self.get_object(self.configuration) + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend): + self.assertEqual({ + 'metadata': {'pool': 'testPool', + 'project': test_config.tegile_default_project + } + }, tegile_driver.create_volume_from_snapshot(test_volume, + test_snapshot)) + + def test_create_volume_from_snapshot_fail(self): + tegile_driver = self.get_object(self.configuration) + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend_fail): + self.assertRaises(TegileAPIException, + tegile_driver.create_volume_from_snapshot, + test_volume, test_snapshot) + + def test_create_cloned_volume(self): + tegile_driver = self.get_object(self.configuration) + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend): + self.assertEqual({'metadata': {'project': 'testProj', + 'pool': 'testPool'}}, + tegile_driver.create_cloned_volume(test_volume, + test_volume)) + + def test_create_cloned_volume_fail(self): + tegile_driver = self.get_object(self.configuration) + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend_fail): + self.assertRaises(TegileAPIException, + tegile_driver.create_cloned_volume, + test_volume, test_volume) + + def test_get_volume_stats(self): + tegile_driver = self.get_object(self.configuration) + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend): + self.assertEqual({'driver_version': '1.0.0', + 'free_capacity_gb': 4565.381390112452, + 'pools': [{'QoS_support': False, + 'allocated_capacity_gb': 0.0, + 'free_capacity_gb': 911.812650680542, + 'pool_name': 'pyramid', + 'reserved_percentage': 0, + 'total_capacity_gb': 913.5}, + {'QoS_support': False, + 'allocated_capacity_gb': 0.0, + 'free_capacity_gb': 2740.148867149747, + 'pool_name': 'cobalt', + 'reserved_percentage': 0, + 'total_capacity_gb': 2742.1996604874}, + {'QoS_support': False, + 'allocated_capacity_gb': 0.0, + 'free_capacity_gb': 913.4198722839355, + 'pool_name': 'test', + 'reserved_percentage': 0, + 'total_capacity_gb': 913.5}], + 'storage_protocol': 'iSCSI', + 'total_capacity_gb': 4569.199686084874, + 'vendor_name': 'Tegile Systems Inc.', + 'volume_backend_name': 'unittest'}, + tegile_driver.get_volume_stats(True)) + + def test_get_pool(self): + tegile_driver = self.get_object(self.configuration) + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend): + self.assertEqual('testPool', tegile_driver.get_pool(test_volume)) + + def test_extend_volume(self): + tegile_driver = self.get_object(self.configuration) + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend): + tegile_driver.extend_volume(test_volume, 12) + + def test_extend_volume_fail(self): + tegile_driver = self.get_object(self.configuration) + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend_fail): + self.assertRaises(TegileAPIException, + tegile_driver.extend_volume, + test_volume, 30) + + def test_manage_existing(self): + tegile_driver = self.get_object(self.configuration) + existing_ref = {'name': 'existingvol'} + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend): + self.assertEqual({'metadata': {'pool': 'testPool', + 'project': 'testProj' + }, + '_name_id': ('existingvol',) + }, tegile_driver.manage_existing(test_volume, + existing_ref)) + + def test_manage_existing_get_size(self): + tegile_driver = self.get_object(self.configuration) + existing_ref = {'name': 'existingvol'} + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend): + self.assertEqual(25, + tegile_driver.manage_existing_get_size( + test_volume, + existing_ref)) + + def test_manage_existing_get_size_fail(self): + tegile_driver = self.get_object(self.configuration) + existing_ref = {'name': 'existingvol'} + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend_fail): + self.assertRaises(TegileAPIException, + tegile_driver.manage_existing_get_size, + test_volume, existing_ref) + + def get_object(self, configuration): + class TegileBaseDriver(BASE_DRIVER): + def initialize_connection(self, volume, connector, **kwargs): + pass + + def terminate_connection(self, volume, connector, + force=False, **kwargs): + pass + + return TegileBaseDriver(configuration=self.configuration) + + +class TegileISCSIDriverTestCase(test.TestCase): + def setUp(self): + super(TegileISCSIDriverTestCase, self).setUp() + self.ctxt = context.get_admin_context() + self.configuration = test_config + self.configuration.chap_username = 'fake' + self.configuration.chap_password = "test" + + def test_initialize_connection(self): + tegile_driver = self.get_object(self.configuration) + connector = {'initiator': 'iqn.1993-08.org.debian:01:d0bb9a834f8'} + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend): + self.assertEqual( + {'data': {'auth_method': 'CHAP', + 'discard': False, + 'target_discovered': (False,), + 'auth_password': 'test', + 'auth_username': 'fake', + 'target_iqn': 'iqn.2012-02.' + 'com.tegile:openstack-cobalt', + 'target_lun': '27', + 'target_portal': '10.68.103.106:3260', + 'volume_id': ( + 'a24c2ee8-525a-4406-8ccd-8d38688f8e9e',)}, + 'driver_volume_type': 'iscsi'}, + tegile_driver.initialize_connection(test_volume, + connector)) + + def get_object(self, configuration): + return ISCSI_DRIVER(configuration=configuration) + + +class TegileFCDriverTestCase(test.TestCase): + def setUp(self): + super(TegileFCDriverTestCase, self).setUp() + self.ctxt = context.get_admin_context() + self.configuration = test_config + + def test_initialize_connection(self): + tegile_driver = self.get_object(self.configuration) + connector = {'wwpns': ['500110a0001a3990']} + with mock.patch.object(tegile_driver, + '_api_executor', + fake_tegile_backend): + self.assertEqual({'data': {'encrypted': False, + 'initiator_target_map': { + '21000024ff59bb6e': + ['21000024ff578701'], + '21000024ff59bb6f': + ['21000024ff578700'] + }, + 'target_discovered': False, + 'target_lun': '12', + 'target_wwn': + ['21000024ff578700', + '21000024ff578701']}, + 'driver_volume_type': 'fibre_channel'}, + tegile_driver.initialize_connection( + test_volume, + connector)) + + def get_object(self, configuration): + return FC_DRIVER(configuration=configuration) diff --git a/cinder/volume/drivers/tegile.py b/cinder/volume/drivers/tegile.py new file mode 100644 index 00000000000..5b84fc79ecf --- /dev/null +++ b/cinder/volume/drivers/tegile.py @@ -0,0 +1,662 @@ +# Copyright (c) 2015 by Tegile Systems, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Volume driver for Tegile storage. +""" + +import ast +import json +import requests + +from oslo_config import cfg +from oslo_log import log as logging +from oslo_utils import units +import six + +from cinder import exception +from cinder import utils +from cinder.i18n import _, _LI, _LW +from cinder import interface +from cinder.volume import driver +from cinder.volume.drivers.san import san +from cinder.volume import utils as volume_utils +from cinder.zonemanager import utils as fczm_utils + +LOG = logging.getLogger(__name__) +default_api_service = 'openstack' +TEGILE_API_PATH = 'zebi/api' +TEGILE_DEFAULT_BLOCK_SIZE = '32KB' +TEGILE_LOCAL_CONTAINER_NAME = 'Local' +DEBUG_LOGGING = False + +tegile_opts = [ + cfg.StrOpt('tegile_default_pool', + help='Create volumes in this pool'), + cfg.StrOpt('tegile_default_project', + help='Create volumes in this project')] + +CONF = cfg.CONF +CONF.register_opts(tegile_opts) + + +def debugger(func): + """Returns a wrapper that wraps func. + + The wrapper will log the entry and exit points of the function + """ + + def wrapper(*args, **kwds): + if DEBUG_LOGGING: + LOG.debug('Entering %(classname)s.%(funcname)s', + {'classname': args[0].__class__.__name__, + 'funcname': func.__name__}) + LOG.debug('Arguments: %(args)s, %(kwds)s', + {'args': args[1:], + 'kwds': kwds}) + f_result = func(*args, **kwds) + if DEBUG_LOGGING: + LOG.debug('Exiting %(classname)s.%(funcname)s', + {'classname': args[0].__class__.__name__, + 'funcname': func.__name__}) + LOG.debug('Results: %(result)s', + {'result': f_result}) + return f_result + + return wrapper + + +class TegileAPIExecutor(object): + def __init__(self, classname, hostname, username, password): + self._classname = classname + self._hostname = hostname + self._username = username + self._password = password + + @debugger + @utils.retry(exceptions=(requests.ConnectionError, requests.Timeout)) + def send_api_request(self, method, params=None, + request_type='post', + api_service=default_api_service, + fine_logging=DEBUG_LOGGING): + if params is not None: + params = json.dumps(params) + + url = 'https://%s/%s/%s/%s' % (self._hostname, + TEGILE_API_PATH, + api_service, + method) + if fine_logging: + LOG.debug('TegileAPIExecutor(%(classname)s) method: %(method)s, ' + 'url: %(url)s', {'classname': self._classname, + 'method': method, + 'url': url}) + if request_type == 'post': + if fine_logging: + LOG.debug('TegileAPIExecutor(%(classname)s) ' + 'method: %(method)s, payload: %(payload)s', + {'classname': self._classname, + 'method': method, + 'payload': params}) + req = requests.post(url, + data=params, + auth=(self._username, self._password), + verify=False) + else: + req = requests.get(url, + auth=(self._username, self._password), + verify=False) + + if fine_logging: + LOG.debug('TegileAPIExecutor(%(classname)s) method: %(method)s, ' + 'return code: %(retcode)s', + {'classname': self._classname, + 'method': method, + 'retcode': req}) + try: + response = req.json() + if fine_logging: + LOG.debug('TegileAPIExecutor(%(classname)s) ' + 'method: %(method)s, response: %(response)s', + {'classname': self._classname, + 'method': method, + 'response': response}) + except ValueError: + response = '' + req.close() + + if req.status_code != 200: + msg = _('API response: %(response)s') % {'response': response} + raise exception.TegileAPIException(msg) + + return response + + +class TegileIntelliFlashVolumeDriver(san.SanDriver): + """Tegile IntelliFlash Volume Driver.""" + + VENDOR = 'Tegile Systems Inc.' + VERSION = '1.0.0' + REQUIRED_OPTIONS = ['san_ip', 'san_login', + 'san_password', 'tegile_default_pool'] + SNAPSHOT_PREFIX = 'Manual-V-' + + _api_executor = None + + def __init__(self, *args, **kwargs): + self._context = None + super(TegileIntelliFlashVolumeDriver, self).__init__(*args, **kwargs) + self.configuration.append_config_values(tegile_opts) + self._protocol = 'iSCSI' # defaults to iscsi + hostname = getattr(self.configuration, 'san_ip') + username = getattr(self.configuration, 'san_login') + password = getattr(self.configuration, 'san_password') + self._default_pool = getattr(self.configuration, 'tegile_default_pool') + self._default_project = ( + getattr(self.configuration, 'tegile_default_project') or + 'openstack') + self._api_executor = TegileAPIExecutor(self.__class__.__name__, + hostname, + username, + password) + + @debugger + def do_setup(self, context): + super(TegileIntelliFlashVolumeDriver, self).do_setup(context) + self._context = context + self._check_ops(self.REQUIRED_OPTIONS, self.configuration) + + @debugger + def create_volume(self, volume): + pool = volume_utils.extract_host(volume['host'], level='pool', + default_pool_name=self._default_pool) + tegile_volume = {'blockSize': TEGILE_DEFAULT_BLOCK_SIZE, + 'datasetPath': '%s/%s/%s' % + (pool, + TEGILE_LOCAL_CONTAINER_NAME, + self._default_project), + 'local': 'true', + 'name': volume['name'], + 'poolName': '%s' % pool, + 'projectName': '%s' % self._default_project, + 'protocol': self._protocol, + 'thinProvision': 'true', + 'volSize': volume['size'] * units.Gi} + params = list() + params.append(tegile_volume) + params.append(True) + + self._api_executor.send_api_request(method='createVolume', + params=params) + + LOG.info(_LI("Created volume %(volname)s, volume id %(volid)s."), + {'volname': volume['name'], 'volid': volume['id']}) + + return self.get_additional_info(volume, pool, self._default_project) + + @debugger + def delete_volume(self, volume): + """Deletes a snapshot.""" + params = list() + pool, project, volume_name = self._get_pool_project_volume_name(volume) + params.append('%s/%s/%s/%s' % (pool, + TEGILE_LOCAL_CONTAINER_NAME, + project, + volume_name)) + params.append(True) + params.append(False) + + self._api_executor.send_api_request('deleteVolume', params) + + @debugger + def create_snapshot(self, snapshot): + """Creates a snapshot.""" + snap_name = snapshot['name'] + display_list = [getattr(snapshot, 'display_name', ''), + getattr(snapshot, 'display_description', '')] + snap_description = ':'.join(filter(None, display_list)) + # Limit to 254 characters + snap_description = snap_description[:254] + + pool, project, volume_name = self._get_pool_project_volume_name( + snapshot['volume']) + + volume = {'blockSize': TEGILE_DEFAULT_BLOCK_SIZE, + 'datasetPath': '%s/%s/%s' % + (pool, + TEGILE_LOCAL_CONTAINER_NAME, + project), + 'local': 'true', + 'name': volume_name, + 'poolName': '%s' % pool, + 'projectName': '%s' % project, + 'protocol': self._protocol, + 'thinProvision': 'true', + 'volSize': snapshot['volume']['size'] * units.Gi} + params = list() + params.append(volume) + params.append(snap_name) + params.append(False) + + LOG.info(_LI('Creating snapshot for volume_name=%(vol)s' + ' snap_name=%(name)s snap_description=%(desc)s'), + {'vol': volume_name, + 'name': snap_name, + 'desc': snap_description}) + + self._api_executor.send_api_request('createVolumeSnapshot', params) + + @debugger + def delete_snapshot(self, snapshot): + """Deletes a snapshot.""" + params = list() + pool, project, volume_name = self._get_pool_project_volume_name( + snapshot['volume']) + params.append('%s/%s/%s/%s@%s%s' % (pool, + TEGILE_LOCAL_CONTAINER_NAME, + project, + volume_name, + self.SNAPSHOT_PREFIX, + snapshot['name'])) + params.append(False) + + self._api_executor.send_api_request('deleteVolumeSnapshot', params) + + @debugger + def create_volume_from_snapshot(self, volume, snapshot): + """Creates a volume from snapshot.""" + params = list() + pool, project, volume_name = self._get_pool_project_volume_name( + snapshot['volume']) + + params.append('%s/%s/%s/%s@%s%s' % (pool, + TEGILE_LOCAL_CONTAINER_NAME, + project, + volume_name, + self.SNAPSHOT_PREFIX, + snapshot['name'])) + params.append(volume['name']) + params.append(True) + params.append(True) + + self._api_executor.send_api_request('cloneVolumeSnapshot', params) + return self.get_additional_info(volume, pool, project) + + @debugger + def create_cloned_volume(self, volume, src_vref): + """Creates a clone of the specified volume.""" + pool, project, volume_name = self._get_pool_project_volume_name( + src_vref) + data_set_path = '%s/%s/%s' % (pool, + TEGILE_LOCAL_CONTAINER_NAME, + project) + source_volume = {'blockSize': TEGILE_DEFAULT_BLOCK_SIZE, + 'datasetPath': data_set_path, + 'local': 'true', + 'name': volume_name, + 'poolName': '%s' % pool, + 'projectName': '%s' % project, + 'protocol': self._protocol, + 'thinProvision': 'true', + 'volSize': src_vref['size'] * units.Gi} + + dest_volume = {'blockSize': TEGILE_DEFAULT_BLOCK_SIZE, + 'datasetPath': data_set_path, + # clone can reside only in the source project + 'local': 'true', + 'name': volume['name'], + 'poolName': '%s' % pool, + 'projectName': '%s' % project, + 'protocol': self._protocol, + 'thinProvision': 'true', + 'volSize': volume['size'] * units.Gi} + + params = list() + params.append(source_volume) + params.append(dest_volume) + + self._api_executor.send_api_request(method='createClonedVolume', + params=params) + return self.get_additional_info(volume, pool, project) + + @debugger + def get_volume_stats(self, refresh=False): + """Get volume status. + + If 'refresh' is True, run update first. + The name is a bit misleading as + the majority of the data here is cluster + data + """ + if refresh: + try: + self._update_volume_stats() + except Exception: + pass + + return self._stats + + @debugger + def _update_volume_stats(self): + """Retrieves stats info from volume group.""" + + try: + data = self._api_executor.send_api_request(method='getArrayStats', + request_type='get', + fine_logging=False) + # fixing values coming back here as String to float + data['total_capacity_gb'] = float(data.get('total_capacity_gb', 0)) + data['free_capacity_gb'] = float(data.get('free_capacity_gb', 0)) + for pool in data.get('pools', []): + pool['total_capacity_gb'] = float( + pool.get('total_capacity_gb', 0)) + pool['free_capacity_gb'] = float( + pool.get('free_capacity_gb', 0)) + pool['allocated_capacity_gb'] = float( + pool.get('allocated_capacity_gb', 0)) + + data['volume_backend_name'] = getattr(self.configuration, + 'volume_backend_name') + data['vendor_name'] = self.VENDOR + data['driver_version'] = self.VERSION + data['storage_protocol'] = self._protocol + + self._stats = data + except Exception as e: + LOG.warning(_LW('TegileIntelliFlashVolumeDriver(%(clsname)s) ' + '_update_volume_stats failed: %(error)s'), + {'clsname': self.__class__.__name__, + 'error': e}) + + @debugger + def get_pool(self, volume): + """Returns pool name where volume resides. + + :param volume: The volume hosted by the driver. + :return: Name of the pool where given volume is hosted. + """ + pool = volume_utils.extract_host(volume['host'], level='pool', + default_pool_name=self._default_pool) + return pool + + @debugger + def extend_volume(self, volume, new_size): + params = list() + pool, project, volume_name = self._get_pool_project_volume_name(volume) + params.append('%s/%s/%s/%s' % (pool, + TEGILE_LOCAL_CONTAINER_NAME, + project, + volume_name)) + vol_size = six.text_type(new_size) + params.append(vol_size) + params.append('GB') + self._api_executor.send_api_request(method='resizeVolume', + params=params) + + @debugger + def manage_existing(self, volume, existing_ref): + volume['name_id'] = existing_ref['name'] + pool, project, volume_name = self._get_pool_project_volume_name(volume) + additional_info = self.get_additional_info(volume, pool, project) + additional_info['_name_id'] = existing_ref['name'], + return additional_info + + @debugger + def manage_existing_get_size(self, volume, existing_ref): + params = list() + pool, project, volume_name = self._get_pool_project_volume_name(volume) + params.append('%s/%s/%s/%s' % (pool, + TEGILE_LOCAL_CONTAINER_NAME, + project, + existing_ref['name'])) + volume_size = self._api_executor.send_api_request( + method='getVolumeSizeinGB', + params=params) + + return volume_size + + @debugger + def _get_pool_project_volume_name(self, volume): + pool = volume_utils.extract_host(volume['host'], level='pool', + default_pool_name=self._default_pool) + try: + project = volume['metadata']['project'] + except (AttributeError, TypeError, KeyError): + project = self._default_project + + if volume['_name_id'] is not None: + volume_name = volume['_name_id'] + else: + volume_name = volume['name'] + + return pool, project, volume_name + + @debugger + def get_additional_info(self, volume, pool, project): + try: + metadata = self._get_volume_metadata(volume) + except Exception: + metadata = dict() + metadata['pool'] = pool + metadata['project'] = project + return {'metadata': metadata} + + @debugger + def _get_volume_metadata(self, volume): + volume_metadata = {} + if 'volume_metadata' in volume: + for metadata in volume['volume_metadata']: + volume_metadata[metadata['key']] = metadata['value'] + if 'metadata' in volume: + metadata = volume['metadata'] + for key in metadata: + volume_metadata[key] = metadata[key] + return volume_metadata + + @debugger + def _check_ops(self, required_ops, configuration): + """Ensures that the options we care about are set.""" + for attr in required_ops: + if not getattr(configuration, attr, None): + raise exception.InvalidInput(reason=_('%(attr)s is not ' + 'set.') % {'attr': attr}) + + +@interface.volumedriver +class TegileISCSIDriver(TegileIntelliFlashVolumeDriver, san.SanISCSIDriver): + """Tegile ISCSI Driver.""" + + def __init__(self, *args, **kwargs): + super(TegileISCSIDriver, self).__init__(*args, **kwargs) + self._protocol = 'iSCSI' + + @debugger + def do_setup(self, context): + super(TegileISCSIDriver, self).do_setup(context) + + @debugger + def initialize_connection(self, volume, connector): + """Driver entry point to attach a volume to an instance.""" + + if getattr(self.configuration, 'use_chap_auth', False): + chap_username = getattr(self.configuration, + 'chap_username', + '') + chap_password = getattr(self.configuration, + 'chap_password', + '') + else: + chap_username = '' + chap_password = '' + + if volume['provider_location'] is None: + params = list() + pool, project, volume_name = ( + self._get_pool_project_volume_name(volume)) + params.append('%s/%s/%s/%s' % (pool, + TEGILE_LOCAL_CONTAINER_NAME, + project, + volume_name)) + initiator_info = { + 'initiatorName': connector['initiator'], + 'chapUserName': chap_username, + 'chapSecret': chap_password + } + params.append(initiator_info) + mapping_info = self._api_executor.send_api_request( + method='getISCSIMappingForVolume', + params=params) + target_portal = mapping_info['target_portal'] + target_iqn = mapping_info['target_iqn'] + target_lun = mapping_info['target_lun'] + else: + (target_portal, target_iqn, target_lun) = ( + volume['provider_location'].split()) + + connection_data = dict() + connection_data['target_portal'] = target_portal + connection_data['target_iqn'] = target_iqn + connection_data['target_lun'] = target_lun + connection_data['target_discovered'] = False, + connection_data['volume_id'] = volume['id'], + connection_data['discard'] = False + if getattr(self.configuration, 'use_chap_auth', False): + connection_data['auth_method'] = 'CHAP' + connection_data['auth_username'] = chap_username + connection_data['auth_password'] = chap_password + return { + 'driver_volume_type': 'iscsi', + 'data': connection_data + } + + @debugger + def terminate_connection(self, volume, connector, **kwargs): + pass + + @debugger + def create_export(self, context, volume, connector): + """Driver entry point to get the export info for a new volume.""" + params = list() + pool, project, volume_name = self._get_pool_project_volume_name(volume) + params.append('%s/%s/%s/%s' % (pool, + TEGILE_LOCAL_CONTAINER_NAME, + project, + volume_name)) + if getattr(self.configuration, 'use_chap_auth', False): + chap_username = getattr(self.configuration, 'chap_username', '') + chap_password = getattr(self.configuration, 'chap_password', '') + else: + chap_username = '' + chap_password = '' + + initiator_info = { + 'initiatorName': connector['initiator'], + 'chapUserName': chap_username, + 'chapSecret': chap_password + } + params.append(initiator_info) + mapping_info = self._api_executor.send_api_request( + method='getISCSIMappingForVolume', + params=params) + target_portal = mapping_info['target_portal'] + target_iqn = mapping_info['target_iqn'] + target_lun = mapping_info['target_lun'] + + provider_location = '%s %s %s' % (target_portal, + target_iqn, + target_lun) + if getattr(self.configuration, 'use_chap_auth', False): + provider_auth = ('CHAP %s %s' % (chap_username, + chap_password)) + else: + provider_auth = None + return ( + {'provider_location': provider_location, + 'provider_auth': provider_auth}) + + +@interface.volumedriver +class TegileFCDriver(TegileIntelliFlashVolumeDriver, + driver.FibreChannelDriver): + """Tegile FC driver.""" + + def __init__(self, *args, **kwargs): + super(TegileFCDriver, self).__init__(*args, **kwargs) + self._protocol = 'FC' + + @debugger + def do_setup(self, context): + super(TegileFCDriver, self).do_setup(context) + + @fczm_utils.AddFCZone + @debugger + def initialize_connection(self, volume, connector): + """Initializes the connection and returns connection info.""" + + params = list() + pool, project, volume_name = self._get_pool_project_volume_name(volume) + params.append('%s/%s/%s/%s' % (pool, + TEGILE_LOCAL_CONTAINER_NAME, + project, + volume_name)) + wwpns = connector['wwpns'] + + connectors = ','.join(wwpns) + + params.append(connectors) + target_info = self._api_executor.send_api_request( + method='getFCPortsForVolume', + params=params) + initiator_target_map = target_info['initiator_target_map'] + connection_data = { + 'driver_volume_type': 'fibre_channel', + 'data': { + 'encrypted': False, + 'target_discovered': False, + 'target_lun': target_info['target_lun'], + 'target_wwn': ast.literal_eval(target_info['target_wwn']), + 'initiator_target_map': ast.literal_eval(initiator_target_map) + } + } + + return connection_data + + @fczm_utils.RemoveFCZone + @debugger + def terminate_connection(self, volume, connector, force=False, **kwargs): + + params = list() + pool, project, volume_name = self._get_pool_project_volume_name(volume) + params.append('%s/%s/%s/%s' % (pool, + TEGILE_LOCAL_CONTAINER_NAME, + project, + volume_name)) + wwpns = connector['wwpns'] + + connectors = ','.join(wwpns) + + params.append(connectors) + target_info = self._api_executor.send_api_request( + method='getFCPortsForVolume', + params=params) + initiator_target_map = target_info['initiator_target_map'] + + connection_data = { + 'data': { + 'target_wwn': ast.literal_eval(target_info['target_wwn']), + 'initiator_target_map': ast.literal_eval(initiator_target_map) + } + } + + return connection_data diff --git a/releasenotes/notes/remove-tegile-driver-b6f33ff704f4d433.yaml b/releasenotes/notes/remove-tegile-driver-b6f33ff704f4d433.yaml deleted file mode 100644 index ae285098150..00000000000 --- a/releasenotes/notes/remove-tegile-driver-b6f33ff704f4d433.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -upgrade: - - Backend driver for Tegile has been removed. -