diff --git a/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_initialize_connection_snapshot.py b/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_initialize_connection_snapshot.py new file mode 100644 index 00000000000..206823815f1 --- /dev/null +++ b/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_initialize_connection_snapshot.py @@ -0,0 +1,102 @@ +# Copyright (c) 2017 Dell Inc. or its subsidiaries. +# 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 mock + +from cinder import context +from cinder.tests.unit import fake_constants as fake +from cinder.tests.unit import fake_snapshot +from cinder.tests.unit import fake_volume +from cinder.tests.unit.volume.drivers.dell_emc import scaleio + + +class TestInitializeConnectionSnapshot(scaleio.TestScaleIODriver): + + def setUp(self): + super(TestInitializeConnectionSnapshot, self).setUp() + self.snapshot_id = 'SNAPID' + self.ctx = context.RequestContext('fake', 'fake', auth_token=True) + self.fake_path = '/fake/path/vol-xx' + self.volume = fake_volume.fake_volume_obj( + self.ctx, **{'provider_id': fake.PROVIDER_ID}) + self.connector = {} + + def test_backup_can_use_snapshots(self): + """Make sure the driver can use snapshots for backup.""" + use_snaps = self.driver.backup_use_temp_snapshot() + self.assertTrue(use_snaps) + + def test_initialize_connection_without_size(self): + """Test initializing when we do not know the snapshot size. + + ScaleIO can determine QOS specs based upon volume/snapshot size + The QOS keys should always be returned + """ + snapshot = fake_snapshot.fake_snapshot_obj( + self.ctx, **{'volume': self.volume, + 'provider_id': self.snapshot_id}) + props = self.driver.initialize_connection_snapshot( + snapshot, + self.connector) + # validate the volume type + self.assertEqual(props['driver_volume_type'], 'scaleio') + # make sure a volume name and id exist + self.assertIsNotNone(props['data']['scaleIO_volname']) + self.assertEqual(self.snapshot_id, + props['data']['scaleIO_volume_id']) + # make sure QOS properties are set + self.assertTrue('iopsLimit' in props['data']) + + def test_initialize_connection_with_size(self): + """Test initializing when we know the snapshot size. + + ScaleIO can determine QOS specs based upon volume/snapshot size + The QOS keys should always be returned + """ + snapshot = fake_snapshot.fake_snapshot_obj( + self.ctx, **{'volume': self.volume, + 'provider_id': self.snapshot_id, + 'volume_size': 8}) + props = self.driver.initialize_connection_snapshot( + snapshot, + self.connector) + # validate the volume type + self.assertEqual(props['driver_volume_type'], 'scaleio') + # make sure a volume name and id exist + self.assertIsNotNone(props['data']['scaleIO_volname']) + self.assertEqual(self.snapshot_id, + props['data']['scaleIO_volume_id']) + # make sure QOS properties are set + self.assertTrue('iopsLimit' in props['data']) + + def test_qos_specs(self): + """Ensure QOS specs are honored if present.""" + qos = {'maxIOPS': 1000, 'maxBWS': 2048} + snapshot = fake_snapshot.fake_snapshot_obj( + self.ctx, **{'volume': self.volume, + 'provider_id': self.snapshot_id, + 'volume_size': 8}) + extraspecs = {} + self.driver._get_volumetype_qos = mock.MagicMock() + self.driver._get_volumetype_qos.return_value = qos + self.driver._get_volumetype_extraspecs = mock.MagicMock() + self.driver._get_volumetype_extraspecs.return_value = extraspecs + + props = self.driver.initialize_connection_snapshot( + snapshot, + self.connector) + + self.assertEqual(1000, int(props['data']['iopsLimit'])) + self.assertEqual(2048, int(props['data']['bandwidthLimit'])) diff --git a/cinder/volume/drivers/dell_emc/scaleio/driver.py b/cinder/volume/drivers/dell_emc/scaleio/driver.py index f8b94703c57..2cd9a78d9d0 100644 --- a/cinder/volume/drivers/dell_emc/scaleio/driver.py +++ b/cinder/volume/drivers/dell_emc/scaleio/driver.py @@ -863,30 +863,44 @@ class ScaleIODriver(driver.VolumeDriver): return self._delete_volume(snap_id) def initialize_connection(self, volume, connector, **kwargs): - """Initializes the connection and returns connection info. + return self._initialize_connection(volume, connector, volume.size) + + def _initialize_connection(self, vol_or_snap, connector, vol_size): + """Initializes a connection and returns connection info. The scaleio driver returns a driver_volume_type of 'scaleio'. """ - LOG.debug("Connector is %s.", connector) + try: + ip = connector['ip'] + except Exception: + ip = 'unknown' + + LOG.debug("Initializing connection for %(vol)s, " + "to SDC at %(sdc)s", + {'vol': vol_or_snap.id, + 'sdc': ip}) + connection_properties = dict(self.connection_properties) - volname = self._id_to_base64(volume.id) + volname = self._id_to_base64(vol_or_snap.id) connection_properties['scaleIO_volname'] = volname - connection_properties['scaleIO_volume_id'] = volume.provider_id - extra_specs = self._get_volumetype_extraspecs(volume) - qos_specs = self._get_volumetype_qos(volume) - storage_type = extra_specs.copy() - storage_type.update(qos_specs) - LOG.info("Volume type is %s.", storage_type) - round_volume_size = self._round_to_num_gran(volume.size) - iops_limit = self._get_iops_limit(round_volume_size, storage_type) - bandwidth_limit = self._get_bandwidth_limit(round_volume_size, - storage_type) - LOG.info("iops limit is %s", iops_limit) - LOG.info("bandwidth limit is %s", bandwidth_limit) - connection_properties['iopsLimit'] = iops_limit - connection_properties['bandwidthLimit'] = bandwidth_limit + connection_properties['scaleIO_volume_id'] = vol_or_snap.provider_id + + if vol_size is not None: + extra_specs = self._get_volumetype_extraspecs(vol_or_snap) + qos_specs = self._get_volumetype_qos(vol_or_snap) + storage_type = extra_specs.copy() + storage_type.update(qos_specs) + round_volume_size = self._round_to_num_gran(vol_size) + iops_limit = self._get_iops_limit(round_volume_size, storage_type) + bandwidth_limit = self._get_bandwidth_limit(round_volume_size, + storage_type) + LOG.info("iops limit is %s", iops_limit) + LOG.info("bandwidth limit is %s", bandwidth_limit) + connection_properties['iopsLimit'] = iops_limit + connection_properties['bandwidthLimit'] = bandwidth_limit + return {'driver_volume_type': 'scaleio', 'data': connection_properties} @@ -940,7 +954,22 @@ class ScaleIODriver(driver.VolumeDriver): raise exception.InvalidInput(reason=msg) def terminate_connection(self, volume, connector, **kwargs): - LOG.debug("scaleio driver terminate connection.") + self._terminate_connection(volume, connector) + + def _terminate_connection(self, volume_or_snap, connector): + """Terminate connection to a volume or snapshot + + With ScaleIO, snaps and volumes are terminated identically + """ + try: + ip = connector['ip'] + except Exception: + ip = 'unknown' + + LOG.debug("Terminating connection for %(vol)s, " + "to SDC at %(sdc)s", + {'vol': volume_or_snap.id, + 'sdc': ip}) def _update_volume_stats(self): stats = {} @@ -1812,3 +1841,28 @@ class ScaleIODriver(driver.VolumeDriver): def check_for_export(self, context, volume_id): """Make sure volume is exported.""" pass + + def initialize_connection_snapshot(self, snapshot, connector, **kwargs): + # return self._initialize_connection(snapshot, connector) + """Initializes a connection and returns connection info.""" + try: + vol_size = snapshot['volume_size'] + except Exception: + vol_size = None + + return self._initialize_connection(snapshot, connector, vol_size) + + def terminate_connection_snapshot(self, snapshot, connector, **kwargs): + """Terminates a connection to a snapshot.""" + return self._terminate_connection(snapshot, connector) + + def create_export_snapshot(self, context, volume, connector): + """Driver entry point to get the export info for a snapshot.""" + pass + + def remove_export_snapshot(self, context, volume): + """Driver entry point to remove an export for a snapshot.""" + pass + + def backup_use_temp_snapshot(self): + return True diff --git a/releasenotes/notes/scaleio-backup-via-snapshot-8e75aa3f4570e17c.yaml b/releasenotes/notes/scaleio-backup-via-snapshot-8e75aa3f4570e17c.yaml new file mode 100644 index 00000000000..6fcfe0dade9 --- /dev/null +++ b/releasenotes/notes/scaleio-backup-via-snapshot-8e75aa3f4570e17c.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add support to backup volume using snapshot in the Unity driver, + which enables backing up of volumes that are in-use.