ScaleIO Driver: Backup volume via snapshot

When a volume is in-use, the ScaleIO driver can achieve the backup of
that volume through creating a temp snapshot, and then backing-up the
snapshot.

Change-Id: I6330cb1b6644de5f470b198f035933ff676c1bce
This commit is contained in:
Eric Young 2017-10-13 12:49:58 -04:00
parent d6cfe5ed88
commit 7b5bbc951a
3 changed files with 178 additions and 18 deletions

View File

@ -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']))

View File

@ -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

View File

@ -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.