From: Mike Perez Date: Mon, 10 Aug 2015 00:40:25 +0000 (-0700) Subject: Remove StorPool Driver X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=bd80cbe553b4d53d50b3b5315fa743629f906715;p=openstack-build%2Fcinder-build.git Remove StorPool Driver CI has been disabled since July 31st due to false failure reports, and no sign from the maintainers to fix things. Change-Id: Ife07a5f5933274a04f88967abfc02e57ebea576c --- diff --git a/cinder/exception.py b/cinder/exception.py index 6cfa77c30..8cfc6faec 100644 --- a/cinder/exception.py +++ b/cinder/exception.py @@ -941,17 +941,6 @@ class XtremIOArrayBusy(CinderException): message = _("System is busy, retry operation.") -# StorPool driver -class StorPoolConfigurationMissing(CinderException): - message = _("Missing parameter %(param)s in the %(section)s section " - "of the /etc/storpool.conf file") - - -class StorPoolConfigurationInvalid(CinderException): - message = _("Invalid parameter %(param)s in the %(section)s section " - "of the /etc/storpool.conf file: %(error)s") - - # Infortrend EonStor DS Driver class InfortrendCliException(CinderException): message = _("Infortrend CLI exception: %(err)s Param: %(param)s " diff --git a/cinder/tests/unit/test_storpool.py b/cinder/tests/unit/test_storpool.py deleted file mode 100644 index 71f8f36fb..000000000 --- a/cinder/tests/unit/test_storpool.py +++ /dev/null @@ -1,487 +0,0 @@ -# Copyright 2014, 2015 StorPool -# 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. - - -import re -import sys - -import mock -from oslo_utils import units - - -fakeStorPool = mock.Mock() -fakeStorPool.spopenstack = mock.Mock() -fakeStorPool.spapi = mock.Mock() -fakeStorPool.spconfig = mock.Mock() -fakeStorPool.sptypes = mock.Mock() -sys.modules['storpool'] = fakeStorPool - - -from cinder import exception -from cinder import test -from cinder.volume import configuration as conf -from cinder.volume.drivers import storpool as driver - - -volume_types = { - 1: {}, - 2: {'storpool_template': 'ssd'}, - 3: {'storpool_template': 'hdd'} -} -volumes = {} -snapshots = {} - - -def MockExtraSpecs(vtype): - return volume_types[vtype] - - -def mock_volume_types(f): - def _types_inner_inner1(inst, *args, **kwargs): - @mock.patch('cinder.volume.volume_types.get_volume_type_extra_specs', - new=MockExtraSpecs) - def _types_inner_inner2(): - return f(inst, *args, **kwargs) - - return _types_inner_inner2() - - return _types_inner_inner1 - - -def volumeName(vid): - return 'os--volume--{id}'.format(id=vid) - - -def snapshotName(vtype, vid): - return 'os--snap--{t}--{id}'.format(t=vtype, id=vid) - - -class MockDisk(object): - def __init__(self, diskId): - self.id = diskId - self.generationLeft = -1 - self.agCount = 13 - self.agFree = 12 - self.agAllocated = 1 - - -class MockVolume(object): - def __init__(self, v): - self.name = v['name'] - - -class MockTemplate(object): - def __init__(self, name): - self.name = name - - -class MockApiError(Exception): - def __init__(self, msg): - super(MockApiError, self).__init__(msg) - - -class MockAPI(object): - def __init__(self): - self._disks = {diskId: MockDisk(diskId) for diskId in (1, 2, 3, 4)} - self._disks[3].generationLeft = 42 - - self._templates = [MockTemplate(name) for name in ('ssd', 'hdd')] - - def setlog(self, log): - self._log = log - - def disksList(self): - return self._disks - - def snapshotCreate(self, vname, snap): - snapshots[snap['name']] = dict(volumes[vname]) - - def snapshotDelete(self, name): - del snapshots[name] - - def volumeCreate(self, v): - if v['name'] in volumes: - raise MockApiError('volume already exists') - volumes[v['name']] = v - - def volumeDelete(self, name): - del volumes[name] - - def volumesList(self): - return [MockVolume(v[1]) for v in volumes.items()] - - def volumeTemplatesList(self): - return self._templates - - def volumesReassign(self, json): - pass - - def volumeUpdate(self, name, data): - if 'size' in data: - volumes[name]['size'] = data['size'] - - if 'rename' in data and data['rename'] != name: - volumes[data['rename']] = volumes[name] - del volumes[name] - - -class MockAttachDB(object): - def __init__(self, log): - self._api = MockAPI() - - def api(self): - return self._api - - def volumeName(self, vid): - return volumeName(vid) - - def snapshotName(self, vtype, vid): - return snapshotName(vtype, vid) - - -def MockVolumeUpdateDesc(size): - return {'size': size} - - -def MockSPConfig(section = 's01'): - res = {} - m = re.match('^s0*([A-Za-z0-9]+)$', section) - if m: - res['SP_OURID'] = m.group(1) - return res - - -fakeStorPool.spapi.ApiError = MockApiError -fakeStorPool.spconfig.SPConfig = MockSPConfig -fakeStorPool.spopenstack.AttachDB = MockAttachDB -fakeStorPool.sptypes.VolumeUpdateDesc = MockVolumeUpdateDesc - - -class StorPoolTestCase(test.TestCase): - - def setUp(self): - super(StorPoolTestCase, self).setUp() - - self.cfg = mock.Mock(spec=conf.Configuration) - self.cfg.volume_backend_name = 'storpool_test' - self.cfg.storpool_template = None - self.cfg.storpool_replication = 3 - - mock_exec = mock.Mock() - mock_exec.return_value = ('', '') - - self.driver = driver.StorPoolDriver(execute=mock_exec, - configuration=self.cfg) - self.driver.check_for_setup_error() - - def test_initialized(self): - self.assertRaises(TypeError, - self.driver.validate_connector, - 5) - self.assertRaises(KeyError, - self.driver.validate_connector, - {'no-host': None}) - self.assertRaises(exception.StorPoolConfigurationMissing, - self.driver.validate_connector, - {'host': 'none'}) - self.assertRaises(exception.StorPoolConfigurationInvalid, - self.driver.validate_connector, - {'host': 'sbad'}) - self.assertTrue(self.driver.validate_connector({'host': 's01'})) - - self.assertRaises(TypeError, - self.driver.initialize_connection, - None, 5) - self.assertRaises(KeyError, - self.driver.initialize_connection, - None, {'no-host': None}) - self.assertRaises(exception.StorPoolConfigurationMissing, - self.driver.initialize_connection, - None, {'host': 'none'}) - self.assertRaises(exception.StorPoolConfigurationInvalid, - self.driver.initialize_connection, - None, {'host': 'sbad'}) - - c = self.driver.initialize_connection({'id': '42'}, {'host': 's01'}) - self.assertEqual('storpool', c['driver_volume_type']) - self.assertDictEqual({'client_id': 1, 'volume': '42'}, c['data']) - c = self.driver.initialize_connection({'id': '616'}, {'host': 's02'}) - self.assertEqual('storpool', c['driver_volume_type']) - self.assertDictEqual({'client_id': 2, 'volume': '616'}, c['data']) - - self.driver.terminate_connection(None, None) - self.driver.create_export(None, None, {}) - self.driver.remove_export(None, None) - - def test_stats(self): - stats = self.driver.get_volume_stats(refresh=True) - self.assertEqual('StorPool', stats['vendor_name']) - self.assertEqual('storpool', stats['storage_protocol']) - self.assertListEqual(['default', 'template_hdd', 'template_ssd'], - sorted([p['pool_name'] for p in stats['pools']])) - r = re.compile('^template_([A-Za-z0-9_]+)$') - for pool in stats['pools']: - self.assertEqual(19, pool['total_capacity_gb']) - self.assertEqual(5, pool['free_capacity_gb']) - if pool['pool_name'] != 'default': - m = r.match(pool['pool_name']) - self.assertIsNotNone(m) - self.assertIsNotNone(m.group(1)) - self.assertEqual(m.group(1), pool['storpool_template']) - - def assertVolumeNames(self, names): - self.assertListEqual(sorted([volumeName(n) for n in names]), - sorted(volumes.keys())) - - @mock_volume_types - def test_create_delete_volume(self): - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - - self.driver.create_volume({'id': '1', 'name': 'v1', 'size': 1, - 'volume_type': None}) - self.assertListEqual([volumeName('1')], volumes.keys()) - self.assertVolumeNames(('1',)) - v = volumes[volumeName('1')] - self.assertEqual(1 * units.Gi, v['size']) - self.assertNotIn('template', v.keys()) - self.assertEqual(3, v['replication']) - - caught = False - try: - self.driver.create_volume({'id': '1', 'name': 'v1', 'size': 0, - 'volume_type': None}) - except exception.VolumeBackendAPIException: - caught = True - self.assertTrue(caught) - - self.driver.delete_volume({'id': '1'}) - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) - - self.driver.create_volume({'id': '1', 'name': 'v1', 'size': 2, - 'volume_type': None}) - self.assertVolumeNames(('1',)) - v = volumes[volumeName('1')] - self.assertEqual(2 * units.Gi, v['size']) - self.assertNotIn('template', v.keys()) - self.assertEqual(3, v['replication']) - - self.driver.create_volume({'id': '2', 'name': 'v2', 'size': 3, - 'volume_type': {'id': 1}}) - self.assertVolumeNames(('1', '2')) - v = volumes[volumeName('2')] - self.assertEqual(3 * units.Gi, v['size']) - self.assertNotIn('template', v.keys()) - self.assertEqual(3, v['replication']) - - self.driver.create_volume({'id': '3', 'name': 'v2', 'size': 4, - 'volume_type': {'id': 2}}) - self.assertVolumeNames(('1', '2', '3')) - v = volumes[volumeName('3')] - self.assertEqual(4 * units.Gi, v['size']) - self.assertEqual('ssd', v['template']) - self.assertNotIn('replication', v.keys()) - - self.driver.create_volume({'id': '4', 'name': 'v2', 'size': 5, - 'volume_type': {'id': 3}}) - self.assertVolumeNames(('1', '2', '3', '4')) - v = volumes[volumeName('4')] - self.assertEqual(5 * units.Gi, v['size']) - self.assertEqual('hdd', v['template']) - self.assertNotIn('replication', v.keys()) - - # Make sure the dictionary is not corrupted somehow... - v = volumes[volumeName('1')] - self.assertEqual(2 * units.Gi, v['size']) - self.assertNotIn('template', v.keys()) - self.assertEqual(3, v['replication']) - - for vid in ('1', '2', '3', '4'): - self.driver.delete_volume({'id': vid}) - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - - @mock_volume_types - def test_update_migrated_volume(self): - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - - # Create two volumes - self.driver.create_volume({'id': '1', 'name': 'v1', 'size': 1, - 'volume_type': None}) - self.driver.create_volume({'id': '2', 'name': 'v2', 'size': 1, - 'volume_type': None}) - self.assertListEqual([volumeName('1'), volumeName('2')], - volumes.keys()) - self.assertVolumeNames(('1', '2',)) - - # Failure: the "migrated" volume does not even exist - res = self.driver.update_migrated_volume(None, {'id': '1'}, - {'id': '3', '_name_id': '1'}, - 'available') - self.assertDictEqual({'_name_id': '1'}, res) - - # Failure: a volume with the original volume's name already exists - res = self.driver.update_migrated_volume(None, {'id': '1'}, - {'id': '2', '_name_id': '1'}, - 'available') - self.assertDictEqual({'_name_id': '1'}, res) - - # Success: rename the migrated volume to match the original - res = self.driver.update_migrated_volume(None, {'id': '3'}, - {'id': '2', '_name_id': '3'}, - 'available') - self.assertDictEqual({'_name_id': None}, res) - self.assertListEqual([volumeName('1'), volumeName('3')], - volumes.keys()) - self.assertVolumeNames(('1', '3',)) - - for vid in ('1', '3'): - self.driver.delete_volume({'id': vid}) - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - - def test_clone_extend_volume(self): - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - - self.driver.create_volume({'id': '1', 'name': 'v1', 'size': 1, - 'volume_type': None}) - self.assertVolumeNames(('1',)) - self.driver.extend_volume({'id': '1'}, 2) - self.assertEqual(2 * units.Gi, volumes[volumeName('1')]['size']) - - self.driver.create_cloned_volume({'id': '2', 'name': 'clo', 'size': 3}, - {'id': 1}) - self.assertVolumeNames(('1', '2')) - self.assertDictEqual({}, snapshots) - # Note: this would not be true in a real environment (the snapshot will - # have been deleted, the volume would have no parent), but with this - # fake implementation it helps us make sure that the second volume was - # created with the proper options. - self.assertEqual(volumes[volumeName('2')]['parent'], - snapshotName('clone', '2')) - - self.driver.delete_volume({'id': 1}) - self.driver.delete_volume({'id': 2}) - - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - - @mock_volume_types - def test_config_replication(self): - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - - save_repl = self.driver.configuration.storpool_replication - - self.driver.configuration.storpool_replication = 3 - stats = self.driver.get_volume_stats(refresh=True) - pool = stats['pools'][0] - self.assertEqual(19, pool['total_capacity_gb']) - self.assertEqual(5, pool['free_capacity_gb']) - - self.driver.create_volume({'id': 'cfgrepl1', 'name': 'v1', 'size': 1, - 'volume_type': None}) - self.assertVolumeNames(('cfgrepl1',)) - v = volumes[volumeName('cfgrepl1')] - self.assertEqual(3, v['replication']) - self.assertNotIn('template', v) - self.driver.delete_volume({'id': 'cfgrepl1'}) - - self.driver.configuration.storpool_replication = 2 - stats = self.driver.get_volume_stats(refresh=True) - pool = stats['pools'][0] - self.assertEqual(19, pool['total_capacity_gb']) - self.assertEqual(8, pool['free_capacity_gb']) - - self.driver.create_volume({'id': 'cfgrepl2', 'name': 'v1', 'size': 1, - 'volume_type': None}) - self.assertVolumeNames(('cfgrepl2',)) - v = volumes[volumeName('cfgrepl2')] - self.assertEqual(2, v['replication']) - self.assertNotIn('template', v) - self.driver.delete_volume({'id': 'cfgrepl2'}) - - self.driver.create_volume({'id': 'cfgrepl3', 'name': 'v1', 'size': 1, - 'volume_type': {'id': 2}}) - self.assertVolumeNames(('cfgrepl3',)) - v = volumes[volumeName('cfgrepl3')] - self.assertNotIn('replication', v) - self.assertEqual('ssd', v['template']) - self.driver.delete_volume({'id': 'cfgrepl3'}) - - self.driver.configuration.storpool_replication = save_repl - - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - - @mock_volume_types - def test_config_template(self): - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - - save_template = self.driver.configuration.storpool_template - - self.driver.configuration.storpool_template = None - - self.driver.create_volume({'id': 'cfgtempl1', 'name': 'v1', 'size': 1, - 'volume_type': None}) - self.assertVolumeNames(('cfgtempl1',)) - v = volumes[volumeName('cfgtempl1')] - self.assertEqual(3, v['replication']) - self.assertNotIn('template', v) - self.driver.delete_volume({'id': 'cfgtempl1'}) - - self.driver.create_volume({'id': 'cfgtempl2', 'name': 'v1', 'size': 1, - 'volume_type': {'id': 2}}) - self.assertVolumeNames(('cfgtempl2',)) - v = volumes[volumeName('cfgtempl2')] - self.assertNotIn('replication', v) - self.assertEqual('ssd', v['template']) - self.driver.delete_volume({'id': 'cfgtempl2'}) - - self.driver.configuration.storpool_template = 'hdd' - - self.driver.create_volume({'id': 'cfgtempl3', 'name': 'v1', 'size': 1, - 'volume_type': None}) - self.assertVolumeNames(('cfgtempl3',)) - v = volumes[volumeName('cfgtempl3')] - self.assertNotIn('replication', v) - self.assertEqual('hdd', v['template']) - self.driver.delete_volume({'id': 'cfgtempl3'}) - - self.driver.create_volume({'id': 'cfgtempl4', 'name': 'v1', 'size': 1, - 'volume_type': {'id': 2}}) - self.assertVolumeNames(('cfgtempl4',)) - v = volumes[volumeName('cfgtempl4')] - self.assertNotIn('replication', v) - self.assertEqual('ssd', v['template']) - self.driver.delete_volume({'id': 'cfgtempl4'}) - - self.driver.configuration.storpool_template = save_template - - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py deleted file mode 100644 index c152fa2a7..000000000 --- a/cinder/volume/drivers/storpool.py +++ /dev/null @@ -1,469 +0,0 @@ -# Copyright (c) 2014, 2015 StorPool -# 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. - -"""StorPool block device driver""" - -from __future__ import absolute_import - -from oslo_config import cfg -from oslo_log import log as logging -from oslo_utils import importutils -from oslo_utils import units -import six - -from cinder import exception -from cinder.i18n import _, _LE -from cinder.volume import driver -from cinder.volume import volume_types - -LOG = logging.getLogger(__name__) - -storpool = importutils.try_import('storpool') -if storpool: - from storpool import spapi - from storpool import spconfig - from storpool import spopenstack - from storpool import sptypes - - -storpool_opts = [ - cfg.StrOpt('storpool_template', - default=None, - help='The StorPool template for volumes with no type.'), - cfg.IntOpt('storpool_replication', - default=3, - help='The default StorPool chain replication value. ' - 'Used when creating a volume with no specified type if ' - 'storpool_template is not set. Also used for calculating ' - 'the apparent free space reported in the stats.'), -] - -CONF = cfg.CONF -CONF.register_opts(storpool_opts) - - -class StorPoolDriver(driver.TransferVD, driver.ExtendVD, driver.CloneableVD, - driver.SnapshotVD, driver.RetypeVD, driver.BaseVD): - """The StorPool block device driver. - - Version history: - 0.1.0 - Initial driver - 0.2.0 - Bring the driver up to date with Kilo and Liberty: - - implement volume retyping and migrations - - use the driver.*VD ABC metaclasses - - bugfix: fall back to the configured StorPool template - 1.0.0 - Imported into OpenStack Liberty with minor fixes - """ - - VERSION = '1.0.0' - - def __init__(self, *args, **kwargs): - super(StorPoolDriver, self).__init__(*args, **kwargs) - self.configuration.append_config_values(storpool_opts) - self._sp_config = None - self._ourId = None - self._ourIdInt = None - self._attach = None - - def _backendException(self, e): - return exception.VolumeBackendAPIException(data=six.text_type(e)) - - def _template_from_volume_type(self, vtype): - specs = volume_types.get_volume_type_extra_specs(vtype['id']) - if specs is None: - return None - return specs.get('storpool_template', None) - - def create_volume(self, volume): - size = int(volume['size']) * units.Gi - name = self._attach.volumeName(volume['id']) - template = None - if volume['volume_type'] is not None: - template = self._template_from_volume_type(volume['volume_type']) - if template is None: - template = self.configuration.storpool_template - try: - if template is None: - self._attach.api().volumeCreate({ - 'name': name, - 'size': size, - 'replication': self.configuration.storpool_replication - }) - else: - self._attach.api().volumeCreate({ - 'name': name, - 'size': size, - 'template': template - }) - except spapi.ApiError as e: - raise self._backendException(e) - - def _storpool_client_id(self, connector): - hostname = connector['host'] - try: - cfg = spconfig.SPConfig(section=hostname) - return int(cfg['SP_OURID']) - except KeyError: - raise exception.StorPoolConfigurationMissing( - section=hostname, param='SP_OURID') - except Exception as e: - raise exception.StorPoolConfigurationInvalid( - section=hostname, param='SP_OURID', error=e) - - def validate_connector(self, connector): - return self._storpool_client_id(connector) >= 0 - - def initialize_connection(self, volume, connector): - return {'driver_volume_type': 'storpool', - 'data': { - 'client_id': self._storpool_client_id(connector), - 'volume': volume['id'], - }} - - def terminate_connection(self, volume, connector, **kwargs): - pass - - def create_snapshot(self, snapshot): - volname = self._attach.volumeName(snapshot['volume_id']) - name = self._attach.snapshotName('snap', snapshot['id']) - try: - self._attach.api().snapshotCreate(volname, {'name': name}) - except spapi.ApiError as e: - raise self._backendException(e) - - def create_volume_from_snapshot(self, volume, snapshot): - size = int(volume['size']) * units.Gi - volname = self._attach.volumeName(volume['id']) - name = self._attach.snapshotName('snap', snapshot['id']) - try: - self._attach.api().volumeCreate({ - 'name': volname, - 'size': size, - 'parent': name - }) - except spapi.ApiError as e: - raise self._backendException(e) - - def create_cloned_volume(self, volume, src_vref): - refname = self._attach.volumeName(src_vref['id']) - snapname = self._attach.snapshotName('clone', volume['id']) - try: - self._attach.api().snapshotCreate(refname, {'name': snapname}) - except spapi.ApiError as e: - raise self._backendException(e) - - size = int(volume['size']) * units.Gi - volname = self._attach.volumeName(volume['id']) - try: - self._attach.api().volumeCreate({ - 'name': volname, - 'size': size, - 'parent': snapname - }) - except spapi.ApiError as e: - raise self._backendException(e) - finally: - try: - self._attach.api().snapshotDelete(snapname) - except spapi.ApiError as e: - # ARGH! - LOG.error(_LE("Could not delete the temp snapshot %(name)s: " - "%(msg)s"), - {'name': snapname, 'msg': e}) - - def create_export(self, context, volume, connector): - pass - - def remove_export(self, context, volume): - pass - - def delete_volume(self, volume): - name = self._attach.volumeName(volume['id']) - try: - self._attach.api().volumesReassign( - json=[{"volume": name, "detach": "all"}]) - self._attach.api().volumeDelete(name) - except spapi.ApiError as e: - if e.name == 'objectDoesNotExist': - pass - else: - raise self._backendException(e) - - def delete_snapshot(self, snapshot): - name = self._attach.snapshotName('snap', snapshot['id']) - try: - self._attach.api().volumesReassign( - json=[{"snapshot": name, "detach": "all"}]) - self._attach.api().snapshotDelete(name) - except spapi.ApiError as e: - if e.name == 'objectDoesNotExist': - pass - else: - raise self._backendException(e) - - def check_for_setup_error(self): - if storpool is None: - msg = _('storpool libraries not found') - raise exception.VolumeBackendAPIException(data=msg) - - self._attach = spopenstack.AttachDB(log=LOG) - try: - self._attach.api() - except Exception as e: - LOG.error(_LE("StorPoolDriver API initialization failed: %s"), e) - raise - - def get_volume_stats(self, refresh=False): - if refresh: - self._update_volume_stats() - - return self._stats - - def _update_volume_stats(self): - try: - dl = self._attach.api().disksList() - templates = self._attach.api().volumeTemplatesList() - except spapi.ApiError as e: - raise self._backendException(e) - total = 0 - used = 0 - free = 0 - agSize = 512 * units.Mi - for (id, desc) in dl.items(): - if desc.generationLeft != -1: - continue - total += desc.agCount * agSize - used += desc.agAllocated * agSize - free += desc.agFree * agSize * 4096 / (4096 + 128) - - # Report the free space as if all new volumes will be created - # with StorPool replication 3; anything else is rare. - free /= self.configuration.storpool_replication - - space = { - 'total_capacity_gb': total / units.Gi, - 'free_capacity_gb': free / units.Gi, - 'reserved_percentage': 0, - 'QoS_support': False, - } - - pools = [dict(space, pool_name='default')] - - pools += [dict(space, - pool_name='template_' + t.name, - storpool_template=t.name - ) for t in templates] - - self._stats = { - 'volume_backend_name': self.configuration.safe_get( - 'volume_backend_name') or 'storpool', - 'vendor_name': 'StorPool', - 'driver_version': self.VERSION, - 'storage_protocol': 'storpool', - - 'pools': pools - } - - def _attach_volume(self, context, volume, properties, remote=False): - if remote: - return super(StorPoolDriver, self)._attach_volume( - context, volume, properties, remote=remote) - req_id = context.request_id - req = self._attach.get().get(req_id, None) - if req is None: - req = { - 'volume': self._attach.volumeName(volume['id']), - 'type': 'cinder-attach', - 'id': context.request_id, - 'rights': 2, - 'volsnap': False, - 'remove_on_detach': True - } - self._attach.add(req_id, req) - name = req['volume'] - self._attach.sync(req_id, None) - return {'device': {'path': '/dev/storpool/' + name, - 'storpool_attach_req': req_id}}, volume - - def _detach_volume(self, context, attach_info, volume, properties, - force=False, remote=False): - if remote: - return super(StorPoolDriver, self)._detach_volume( - context, attach_info, volume, properties, - force=force, remote=remote) - req_id = attach_info.get('device', {}).get( - 'storpool_attach_req', context.request_id) - req = self._attach.get()[req_id] - name = req['volume'] - self._attach.sync(req_id, name) - if req.get('remove_on_detach', False): - self._attach.remove(req_id) - - def backup_volume(self, context, backup, backup_service): - volume = self.db.volume_get(context, backup['volume_id']) - req_id = context.request_id - volname = self._attach.volumeName(volume['id']) - name = self._attach.volsnapName(volume['id'], req_id) - try: - self._attach.api().snapshotCreate(volname, {'name': name}) - except spapi.ApiError as e: - raise self._backendException(e) - self._attach.add(req_id, { - 'volume': name, - 'type': 'backup', - 'id': req_id, - 'rights': 1, - 'volsnap': True - }) - try: - return super(StorPoolDriver, self).backup_volume( - context, backup, backup_service) - finally: - self._attach.remove(req_id) - try: - self._attach.api().snapshotDelete(name) - except spapi.ApiError as e: - LOG.error( - _LE('Could not remove the temp snapshot %(name)s for ' - '%(vol)s: %(err)s'), - {'name': name, 'vol': volname, 'err': e}) - - def copy_volume_to_image(self, context, volume, image_service, image_meta): - req_id = context.request_id - volname = self._attach.volumeName(volume['id']) - name = self._attach.volsnapName(volume['id'], req_id) - try: - self._attach.api().snapshotCreate(volname, {'name': name}) - except spapi.ApiError as e: - raise self._backendException(e) - self._attach.add(req_id, { - 'volume': name, - 'type': 'copy-from', - 'id': req_id, - 'rights': 1, - 'volsnap': True - }) - try: - return super(StorPoolDriver, self).copy_volume_to_image( - context, volume, image_service, image_meta) - finally: - self._attach.remove(req_id) - try: - self._attach.api().snapshotDelete(name) - except spapi.ApiError as e: - LOG.error( - _LE('Could not remove the temp snapshot %(name)s for ' - '%(vol)s: %(err)s'), - {'name': name, 'vol': volname, 'err': e}) - - def copy_image_to_volume(self, context, volume, image_service, image_id): - req_id = context.request_id - name = self._attach.volumeName(volume['id']) - self._attach.add(req_id, { - 'volume': name, - 'type': 'copy-to', - 'id': req_id, - 'rights': 2 - }) - try: - return super(StorPoolDriver, self).copy_image_to_volume( - context, volume, image_service, image_id) - finally: - self._attach.remove(req_id) - - def extend_volume(self, volume, new_size): - size = int(new_size) * units.Gi - name = self._attach.volumeName(volume['id']) - try: - upd = sptypes.VolumeUpdateDesc(size=size) - self._attach.api().volumeUpdate(name, upd) - except spapi.ApiError as e: - raise self._backendException(e) - - def ensure_export(self, context, volume): - # Already handled by Nova's AttachDB, we hope. - # Maybe it should move here, but oh well. - pass - - def retype(self, context, volume, new_type, diff, host): - update = {} - - if diff['encryption']: - LOG.error(_LE('Retype of encryption type not supported.')) - return False - - templ = self.configuration.storpool_template - repl = self.configuration.storpool_replication - if diff['extra_specs']: - for (k, v) in diff['extra_specs'].items(): - if k == 'volume_backend_name': - if v[0] != v[1]: - # Retype of a volume backend not supported yet, - # the volume needs to be migrated. - return False - elif k == 'storpool_template': - if v[0] != v[1]: - if v[1] is not None: - update['template'] = v[1] - elif templ is not None: - update['template'] = templ - else: - update['replication'] = repl - elif v[0] != v[1]: - LOG.error(_LE('Retype of extra_specs "%s" not ' - 'supported yet.'), k) - return False - - if update: - name = self._attach.volumeName(volume['id']) - try: - upd = sptypes.VolumeUpdateDesc(**update) - self._attach.api().volumeUpdate(name, upd) - except spapi.ApiError as e: - raise self._backendException(e) - - return True - - def update_migrated_volume(self, context, volume, new_volume, - original_volume_status): - orig_id = volume['id'] - orig_name = self._attach.volumeName(orig_id) - temp_id = new_volume['id'] - temp_name = self._attach.volumeName(temp_id) - vols = {v.name: True for v in self._attach.api().volumesList()} - if temp_name not in vols: - LOG.error(_LE('StorPool update_migrated_volume(): it seems ' - 'that the StorPool volume "%(tid)s" was not ' - 'created as part of the migration from ' - '"%(oid)s"'), {'tid': temp_id, 'oid': orig_id}) - return {'_name_id': new_volume['_name_id'] or new_volume['id']} - elif orig_name in vols: - LOG.error(_LE('StorPool update_migrated_volume(): both ' - 'the original volume "%(oid)s" and the migrated ' - 'StorPool volume "%(tid)s" seem to exist on ' - 'the StorPool cluster'), - {'oid': orig_id, 'tid': temp_id}) - return {'_name_id': new_volume['_name_id'] or new_volume['id']} - else: - try: - self._attach.api().volumeUpdate(temp_name, - {'rename': orig_name}) - return {'_name_id': None} - except spapi.ApiError as e: - LOG.error(_LE('StorPool update_migrated_volume(): ' - 'could not rename %(tname)s to %(oname)s: ' - '%(err)s'), - {'tname': temp_name, 'oname': orig_name, 'err': e}) - return {'_name_id': new_volume['_name_id'] or new_volume['id']}