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:
parent
d6cfe5ed88
commit
7b5bbc951a
@ -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']))
|
@ -863,30 +863,44 @@ class ScaleIODriver(driver.VolumeDriver):
|
|||||||
return self._delete_volume(snap_id)
|
return self._delete_volume(snap_id)
|
||||||
|
|
||||||
def initialize_connection(self, volume, connector, **kwargs):
|
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'.
|
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)
|
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_volname'] = volname
|
||||||
connection_properties['scaleIO_volume_id'] = volume.provider_id
|
connection_properties['scaleIO_volume_id'] = vol_or_snap.provider_id
|
||||||
extra_specs = self._get_volumetype_extraspecs(volume)
|
|
||||||
qos_specs = self._get_volumetype_qos(volume)
|
if vol_size is not None:
|
||||||
storage_type = extra_specs.copy()
|
extra_specs = self._get_volumetype_extraspecs(vol_or_snap)
|
||||||
storage_type.update(qos_specs)
|
qos_specs = self._get_volumetype_qos(vol_or_snap)
|
||||||
LOG.info("Volume type is %s.", storage_type)
|
storage_type = extra_specs.copy()
|
||||||
round_volume_size = self._round_to_num_gran(volume.size)
|
storage_type.update(qos_specs)
|
||||||
iops_limit = self._get_iops_limit(round_volume_size, storage_type)
|
round_volume_size = self._round_to_num_gran(vol_size)
|
||||||
bandwidth_limit = self._get_bandwidth_limit(round_volume_size,
|
iops_limit = self._get_iops_limit(round_volume_size, storage_type)
|
||||||
storage_type)
|
bandwidth_limit = self._get_bandwidth_limit(round_volume_size,
|
||||||
LOG.info("iops limit is %s", iops_limit)
|
storage_type)
|
||||||
LOG.info("bandwidth limit is %s", bandwidth_limit)
|
LOG.info("iops limit is %s", iops_limit)
|
||||||
connection_properties['iopsLimit'] = iops_limit
|
LOG.info("bandwidth limit is %s", bandwidth_limit)
|
||||||
connection_properties['bandwidthLimit'] = bandwidth_limit
|
connection_properties['iopsLimit'] = iops_limit
|
||||||
|
connection_properties['bandwidthLimit'] = bandwidth_limit
|
||||||
|
|
||||||
return {'driver_volume_type': 'scaleio',
|
return {'driver_volume_type': 'scaleio',
|
||||||
'data': connection_properties}
|
'data': connection_properties}
|
||||||
|
|
||||||
@ -940,7 +954,22 @@ class ScaleIODriver(driver.VolumeDriver):
|
|||||||
raise exception.InvalidInput(reason=msg)
|
raise exception.InvalidInput(reason=msg)
|
||||||
|
|
||||||
def terminate_connection(self, volume, connector, **kwargs):
|
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):
|
def _update_volume_stats(self):
|
||||||
stats = {}
|
stats = {}
|
||||||
@ -1812,3 +1841,28 @@ class ScaleIODriver(driver.VolumeDriver):
|
|||||||
def check_for_export(self, context, volume_id):
|
def check_for_export(self, context, volume_id):
|
||||||
"""Make sure volume is exported."""
|
"""Make sure volume is exported."""
|
||||||
pass
|
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
|
||||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user