Storwize: add backup snapshots support

This patch adds backup snapshots support for Storwize/SVC driver.
The change implements attach and detach snapshot:
initialize_connection_snapshot
terminate_connection_snapshot

Implements: blueprint storwize-backup-snapshots

Change-Id: I65393a6a5f995d609152d4b8546c055d7bf0b253
This commit is contained in:
yixuanzhang 2017-10-17 17:15:46 +08:00
parent 6af48d7bd9
commit f68847353e
5 changed files with 222 additions and 18 deletions

View File

@ -2758,6 +2758,12 @@ class StorwizeSVCISCSIDriverTestCase(test.TestCase):
vol = testutils.create_volume(self.ctxt, **prop)
return vol
def _generate_snap_info(self, vol_id, size=10):
prop = {'volume_id': vol_id,
'volume_size': size}
snap = testutils.create_snapshot(self.ctxt, **prop)
return snap
def _assert_vol_exists(self, name, exists):
is_vol_defined = self.iscsi_driver._helpers.is_vdisk_defined(name)
self.assertEqual(exists, is_vol_defined)
@ -2797,6 +2803,43 @@ class StorwizeSVCISCSIDriverTestCase(test.TestCase):
self.iscsi_driver.initialize_connection(volume_iSCSI, connector)
self.iscsi_driver.terminate_connection(volume_iSCSI, connector)
def test_storwize_iscsi_connection_snapshot(self):
# create a iSCSI volume
volume_iSCSI = self._create_volume()
snapshot = self._generate_snap_info(volume_iSCSI.id)
self.iscsi_driver.create_snapshot(snapshot)
connector = {'host': 'storwize-svc-host',
'wwnns': ['20000090fa17311e', '20000090fa17311f'],
'wwpns': ['ff00000000000000', 'ff00000000000001'],
'initiator': 'iqn.1993-08.org.debian:01:eac5ccc1aaa'}
self.iscsi_driver.initialize_connection_snapshot(snapshot, connector)
self.iscsi_driver.terminate_connection_snapshot(snapshot, connector)
def test_storwize_replication_failover_iscsi_connection_snapshot(self):
volume_iSCSI = self._create_volume()
snapshot = self._generate_snap_info(volume_iSCSI.id)
self.iscsi_driver.create_snapshot(snapshot)
connector = {'host': 'storwize-svc-host',
'wwnns': ['20000090fa17311e', '20000090fa17311f'],
'wwpns': ['ff00000000000000', 'ff00000000000001'],
'initiator': 'iqn.1993-08.org.debian:01:eac5ccc1aaa'}
# a snapshot of a replication failover volume. attach will be failed
with mock.patch.object(storwize_svc_common.StorwizeSVCCommonDriver,
'_get_volume_replicated_type') as rep_type:
rep_type.return_value = True
with mock.patch.object(storwize_svc_common.StorwizeSVCCommonDriver,
'_get_vol_sys_info') as sys_info:
sys_info.return_value = {'volume_name': 'voliscsi',
'backend_helper':
'self._aux_backend_helpers',
'node_state': 'self._state'}
self.assertRaises(exception.VolumeDriverException,
self.iscsi_driver.
initialize_connection_snapshot,
snapshot,
connector)
@mock.patch.object(storwize_svc_iscsi.StorwizeSVCISCSIDriver,
'_do_terminate_connection')
@mock.patch.object(storwize_svc_iscsi.StorwizeSVCISCSIDriver,
@ -3298,6 +3341,12 @@ class StorwizeSVCFcDriverTestCase(test.TestCase):
vol = testutils.create_volume(self.ctxt, **prop)
return vol
def _generate_snap_info(self, vol_id, size=10):
prop = {'volume_id': vol_id,
'volume_size': size}
snap = testutils.create_snapshot(self.ctxt, **prop)
return snap
def _assert_vol_exists(self, name, exists):
is_vol_defined = self.fc_driver._helpers.is_vdisk_defined(name)
self.assertEqual(exists, is_vol_defined)
@ -3315,6 +3364,44 @@ class StorwizeSVCFcDriverTestCase(test.TestCase):
self.assertIsNotNone(host_name)
def test_storwize_fc_connection_snapshot(self):
# create a iSCSI volume
volume_fc = self._create_volume()
snapshot = self._generate_snap_info(volume_fc.id)
self.fc_driver.create_snapshot(snapshot)
connector = {'host': 'storwize-svc-host',
'wwnns': ['20000090fa17311e', '20000090fa17311f'],
'wwpns': ['ff00000000000000', 'ff00000000000001'],
'initiator': 'iqn.1993-08.org.debian:01:eac5ccc1aaa'}
self.fc_driver.initialize_connection_snapshot(snapshot, connector)
self.fc_driver.terminate_connection_snapshot(snapshot, connector)
def test_storwize_replication_failover_fc_connection_snapshot(self):
volume_fc = self._create_volume()
volume_fc['replication_status'] = fields.ReplicationStatus.FAILED_OVER
snapshot = self._generate_snap_info(volume_fc.id)
self.fc_driver.create_snapshot(snapshot)
connector = {'host': 'storwize-svc-host',
'wwnns': ['20000090fa17311e', '20000090fa17311f'],
'wwpns': ['ff00000000000000', 'ff00000000000001'],
'initiator': 'iqn.1993-08.org.debian:01:eac5ccc1aaa'}
# a snapshot of a replication failover volume. attach will be failed
with mock.patch.object(storwize_svc_common.StorwizeSVCCommonDriver,
'_get_volume_replicated_type') as rep_type:
rep_type.return_value = True
with mock.patch.object(storwize_svc_common.StorwizeSVCCommonDriver,
'_get_vol_sys_info') as sys_info:
sys_info.return_value = {'volume_name': 'volfc',
'backend_helper':
'self._aux_backend_helpers',
'node_state': 'self._state'}
self.assertRaises(exception.VolumeDriverException,
self.fc_driver.
initialize_connection_snapshot,
snapshot,
connector)
def test_storwize_get_host_with_fc_connection_with_volume(self):
# create a FC volume
volume_fc = self._generate_vol_info(None, None)

View File

@ -2578,6 +2578,13 @@ class StorwizeSVCCommonDriver(san.SanDriver,
def remove_export(self, ctxt, volume):
pass
def create_export_snapshot(self, ctxt, snapshot, connector):
model_update = None
return model_update
def remove_export_snapshot(self, ctxt, snapshot):
pass
def _get_vdisk_params(self, type_id, volume_type=None,
volume_metadata=None):
return self._helpers.get_vdisk_params(self.configuration,
@ -3860,6 +3867,21 @@ class StorwizeSVCCommonDriver(san.SanDriver,
return tgt_vol, backend_helper, node_state
def _check_snapshot_replica_volume_status(self, snapshot):
ctxt = context.get_admin_context()
if self._get_volume_replicated_type(ctxt, None,
snapshot.volume_type_id):
LOG.debug('It is a replication volume snapshot for backup.')
rep_volume = objects.Volume.get_by_id(ctxt, snapshot.volume_id)
volume_name, backend_helper, node_state = self._get_vol_sys_info(
rep_volume)
if backend_helper != self._helpers or self._active_backend_id:
msg = (_('The snapshot of the replication volume %s has '
'failed over to the aux backend. It can not attach'
' to the aux backend.') % volume_name)
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
def migrate_volume(self, ctxt, volume, host):
"""Migrate directly if source and dest are managed by same storage.

View File

@ -33,6 +33,7 @@ localized format.
are of different sizes, is not supported.
"""
import collections
from oslo_config import cfg
from oslo_log import log as logging
@ -93,6 +94,7 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
2.2.1 - Add vdisk mirror/stretch cluster support
2.2.2 - Add npiv support
2.2.3 - Add replication group support
2.2.4 - Add backup snapshots support
"""
VERSION = "2.2.3"
@ -114,6 +116,21 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
raise exception.InvalidConnectorException(
missing='wwpns')
def initialize_connection_snapshot(self, snapshot, connector):
"""Perform attach snapshot for backup snapshots."""
# If the snapshot's source volume is a replication volume and the
# replication volume has failed over to aux_backend,
# attach the snapshot will be failed.
self._check_snapshot_replica_volume_status(snapshot)
vol_attrs = ['id', 'name', 'display_name']
Volume = collections.namedtuple('Volume', vol_attrs)
volume = Volume(id=snapshot.id,
name=snapshot.name,
display_name='backup-snapshot')
return self.initialize_connection(volume, connector)
@fczm_utils.add_fc_zone
def initialize_connection(self, volume, connector):
"""Perform necessary work to make a FC connection."""
@ -136,9 +153,16 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
"""
LOG.debug('enter: initialize_connection: volume %(vol)s with connector'
' %(conn)s', {'vol': volume['id'], 'conn': connector})
volume_name, backend_helper, node_state = self._get_vol_sys_info(
volume)
' %(conn)s', {'vol': volume.id, 'conn': connector})
if volume.display_name == 'backup-snapshot':
LOG.debug('It is a virtual volume %(vol)s for attach snapshot.',
{'vol': volume.id})
volume_name = volume.name
backend_helper = self._helpers
node_state = self._state
else:
volume_name, backend_helper, node_state = self._get_vol_sys_info(
volume)
# Check if a host object is defined for this host name
host_name = backend_helper.get_host_from_connector(connector)
@ -192,7 +216,7 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
properties = {}
properties['target_discovered'] = False
properties['target_lun'] = lun_id
properties['volume_id'] = volume['id']
properties['volume_id'] = volume.id
conn_wwpns = backend_helper.get_conn_fc_wwpns(host_name)
@ -234,7 +258,7 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
LOG.debug('leave: initialize_connection:\n volume: %(vol)s\n '
'connector %(conn)s\n properties: %(prop)s',
{'vol': volume['id'], 'conn': connector,
{'vol': volume.id, 'conn': connector,
'prop': properties})
return {'driver_volume_type': 'fibre_channel', 'data': properties, }
@ -249,6 +273,16 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
return i_t_map
def terminate_connection_snapshot(self, snapshot, connector, **kwargs):
"""Perform detach snapshot for backup snapshots."""
vol_attrs = ['id', 'name', 'display_name']
Volume = collections.namedtuple('Volume', vol_attrs)
volume = Volume(id=snapshot.id,
name=snapshot.name,
display_name='backup-snapshot')
return self.terminate_connection(volume, connector, **kwargs)
@fczm_utils.remove_fc_zone
def terminate_connection(self, volume, connector, **kwargs):
"""Cleanup after an FC connection has been terminated."""
@ -276,8 +310,16 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
automatically by this driver when mappings are created)
"""
LOG.debug('enter: terminate_connection: volume %(vol)s with connector'
' %(conn)s', {'vol': volume['id'], 'conn': connector})
vol_name, backend_helper, node_state = self._get_vol_sys_info(volume)
' %(conn)s', {'vol': volume.id, 'conn': connector})
if volume.display_name == 'backup-snapshot':
LOG.debug('It is a virtual volume %(vol)s for detach snapshot.',
{'vol': volume.id})
vol_name = volume.name
backend_helper = self._helpers
node_state = self._state
else:
vol_name, backend_helper, node_state = self._get_vol_sys_info(
volume)
info = {}
if 'host' in connector:
@ -324,6 +366,6 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
backend_helper.delete_host(host_name)
LOG.debug('leave: terminate_connection: volume %(vol)s with '
'connector %(conn)s', {'vol': volume['id'],
'connector %(conn)s', {'vol': volume.id,
'conn': connector})
return info

View File

@ -33,6 +33,7 @@ localized format.
are of different sizes, is not supported.
"""
import collections
from oslo_config import cfg
from oslo_log import log as logging
@ -92,6 +93,7 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
2.2 - Add CG capability to generic volume groups
2.2.1 - Add vdisk mirror/stretch cluster support
2.2.2 - Add replication group support
2.2.3 - Add backup snapshots support
"""
VERSION = "2.2.2"
@ -113,6 +115,21 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
raise exception.InvalidConnectorException(
missing='initiator')
def initialize_connection_snapshot(self, snapshot, connector):
"""Perform attach snapshot for backup snapshots."""
# If the snapshot's source volume is a replication volume and the
# replication volume has failed over to aux_backend,
# attach the snapshot will be failed.
self._check_snapshot_replica_volume_status(snapshot)
vol_attrs = ['id', 'name', 'display_name']
Volume = collections.namedtuple('Volume', vol_attrs)
volume = Volume(id=snapshot.id,
name=snapshot.name,
display_name='backup-snapshot')
return self.initialize_connection(volume, connector)
def initialize_connection(self, volume, connector):
"""Perform necessary work to make an iSCSI connection."""
@utils.synchronized('storwize-host' + self._state['system_id'] +
@ -133,9 +150,16 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
proper I/O group)
"""
LOG.debug('enter: initialize_connection: volume %(vol)s with connector'
' %(conn)s', {'vol': volume['id'], 'conn': connector})
volume_name, backend_helper, node_state = self._get_vol_sys_info(
volume)
' %(conn)s', {'vol': volume.id, 'conn': connector})
if volume.display_name == 'backup-snapshot':
LOG.debug('It is a virtual volume %(vol)s for attach snapshot.',
{'vol': volume.id})
volume_name = volume.name
backend_helper = self._helpers
node_state = self._state
else:
volume_name, backend_helper, node_state = self._get_vol_sys_info(
volume)
# Check if a host object is defined for this host name
host_name = backend_helper.get_host_from_connector(connector,
@ -178,7 +202,7 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
LOG.debug('leave: initialize_connection:\n volume: %(vol)s\n '
'connector: %(conn)s\n properties: %(prop)s',
{'vol': volume['id'], 'conn': connector,
{'vol': volume.id, 'conn': connector,
'prop': properties})
return {'driver_volume_type': 'iscsi', 'data': properties, }
@ -186,11 +210,19 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
def _get_single_iscsi_data(self, volume, connector, lun_id, chap_secret):
LOG.debug('enter: _get_single_iscsi_data: volume %(vol)s with '
'connector %(conn)s lun_id %(lun_id)s',
{'vol': volume['id'], 'conn': connector,
{'vol': volume.id, 'conn': connector,
'lun_id': lun_id})
volume_name, backend_helper, node_state = self._get_vol_sys_info(
volume)
if volume.display_name == 'backup-snapshot':
LOG.debug('It is a virtual volume %(vol)s for attach snapshot',
{'vol': volume.name})
volume_name = volume.name
backend_helper = self._helpers
node_state = self._state
else:
volume_name, backend_helper, node_state = self._get_vol_sys_info(
volume)
volume_attributes = backend_helper.get_vdisk_attributes(volume_name)
if volume_attributes is None:
msg = (_('_get_single_iscsi_data: Failed to get attributes'
@ -311,6 +343,16 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
return properties
def terminate_connection_snapshot(self, snapshot, connector, **kwargs):
"""Perform detach snapshot for backup snapshots."""
vol_attrs = ['id', 'name', 'display_name']
Volume = collections.namedtuple('Volume', vol_attrs)
volume = Volume(id=snapshot.id,
name=snapshot.name,
display_name='backup-snapshot')
return self.terminate_connection(volume, connector, **kwargs)
def terminate_connection(self, volume, connector, **kwargs):
"""Cleanup after an iSCSI connection has been terminated."""
# If a fake connector is generated by nova when the host
@ -337,8 +379,16 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
automatically by this driver when mappings are created)
"""
LOG.debug('enter: terminate_connection: volume %(vol)s with connector'
' %(conn)s', {'vol': volume['id'], 'conn': connector})
vol_name, backend_helper, node_state = self._get_vol_sys_info(volume)
' %(conn)s', {'vol': volume.id, 'conn': connector})
if volume.display_name == 'backup-snapshot':
LOG.debug('It is a virtual volume %(vol)s for detach snapshot.',
{'vol': volume.id})
vol_name = volume.name
backend_helper = self._helpers
node_state = self._state
else:
vol_name, backend_helper, node_state = self._get_vol_sys_info(
volume)
info = {}
if 'host' in connector:
@ -366,6 +416,6 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
backend_helper.delete_host(host_name)
LOG.debug('leave: terminate_connection: volume %(vol)s with '
'connector %(conn)s', {'vol': volume['id'],
'connector %(conn)s', {'vol': volume.id,
'conn': connector})
return info

View File

@ -0,0 +1,3 @@
---
features:
- Add backup snapshots support for Storwize/SVC driver.