From 08e1fbf8aff2ff69a8bf267f34ce9382b0911a6e Mon Sep 17 00:00:00 2001 From: peter_wang Date: Tue, 11 Aug 2015 04:36:07 -0400 Subject: [PATCH] Support efficient non-disruptive volume backup in VNX Currently, VNX only supports creating backup for in-use volume via cloned volume. Due to the slow lun migration in creating cloned volume, A better solution is proposed below: https://review.openstack.org/#/c/201249/ This patch is to implement above feature in the VNX cinder driver. Change-Id: Ic0765081a88f28d3114f28b50c4f0a1b6d7ef9dd Implements: blueprint non-disruptive-backup-in-vnx --- cinder/tests/unit/test_emc_vnxdirect.py | 68 +++++++++++++++++++++- cinder/volume/drivers/emc/emc_cli_fc.py | 24 ++++++++ cinder/volume/drivers/emc/emc_cli_iscsi.py | 24 ++++++++ cinder/volume/drivers/emc/emc_vnx_cli.py | 57 ++++++++++++++---- 4 files changed, 160 insertions(+), 13 deletions(-) diff --git a/cinder/tests/unit/test_emc_vnxdirect.py b/cinder/tests/unit/test_emc_vnxdirect.py index 55a51dab3..159b9480d 100644 --- a/cinder/tests/unit/test_emc_vnxdirect.py +++ b/cinder/tests/unit/test_emc_vnxdirect.py @@ -19,9 +19,11 @@ import mock from oslo_concurrency import processutils import six +from cinder import context from cinder import exception from cinder import test from cinder.tests.unit import fake_snapshot +from cinder.tests.unit import fake_volume from cinder.tests.unit import utils from cinder.volume import configuration as conf from cinder.volume.drivers.emc import emc_cli_fc @@ -29,6 +31,8 @@ from cinder.volume.drivers.emc import emc_cli_iscsi from cinder.volume.drivers.emc import emc_vnx_cli from cinder.zonemanager import fc_san_lookup_service as fc_service +from mock import patch + SUCCEED = ("", 0) FAKE_ERROR_RETURN = ("FAKE ERROR", 255) @@ -38,6 +42,7 @@ VERSION = emc_vnx_cli.EMCVnxCliBase.VERSION class EMCVNXCLIDriverTestData(object): test_volume = { + 'status': 'creating', 'name': 'vol1', 'size': 1, 'volume_name': 'vol1', @@ -4884,7 +4889,68 @@ Message : Error occurred because of time out""" mock_utils.assert_has_calls(expected) -class EMCVNXCLIDMultiPoolsTestCase(DriverTestCaseBase): +class EMCVNXCLIBackupTestCase(DriverTestCaseBase): + """Provides cli-level and client-level mock test.""" + + def driverSetup(self): + self.context = context.get_admin_context() + self.driver = self.generate_driver(self.configuration) + self.driver.cli._client = mock.Mock() + self.snapshot = fake_snapshot.fake_snapshot_obj( + self.context, **self.testData.test_snapshot) + volume = fake_volume.fake_volume_obj(self.context) + self.snapshot.volume = volume + return self.driver.cli._client + + def generate_driver(self, conf): + driver = emc_cli_iscsi.EMCCLIISCSIDriver(configuration=conf) + return driver + + @patch.object(emc_vnx_cli.EMCVnxCliBase, 'terminate_connection') + def test_terminate_connection_snapshot(self, terminate_connection): + fake_client = self.driverSetup() + connector = self.testData.connector + smp_name = 'tmp-smp-' + self.snapshot['id'] + volume = {'name': smp_name} + self.driver.terminate_connection_snapshot( + self.snapshot, connector) + terminate_connection.assert_called_once_with( + volume, connector) + fake_client.detach_mount_point.assert_called_once_with( + smp_name) + + @patch.object(emc_vnx_cli.EMCVnxCliBase, 'initialize_connection') + def test_initialize_connection_snapshot(self, initialize_connection): + fake_client = self.driverSetup() + connector = self.testData.connector + smp_name = 'tmp-smp-' + self.snapshot['id'] + self.driver.initialize_connection_snapshot( + self.snapshot, connector) + fake_client.attach_mount_point.assert_called_once_with( + smp_name, self.snapshot['name']) + volume = {'name': smp_name, 'id': self.snapshot['id']} + initialize_connection.assert_called_once_with( + volume, connector) + + def test_create_export_snapshot(self): + fake_client = self.driverSetup() + connector = self.testData.connector + smp_name = 'tmp-smp-' + self.snapshot['id'] + self.driver.create_export_snapshot( + None, self.snapshot, connector) + fake_client.create_mount_point.assert_called_once_with( + self.snapshot['volume_name'], smp_name) + + @patch.object(emc_vnx_cli.EMCVnxCliBase, 'delete_volume') + def test_remove_export_snapshot(self, delete_volume): + self.driverSetup() + smp_name = 'tmp-smp-' + self.snapshot['id'] + self.driver.remove_export_snapshot(None, self.snapshot) + volume = {'name': smp_name, 'provider_location': None} + delete_volume.assert_called_once_with(volume, True) + + +class EMCVNXCLIMultiPoolsTestCase(DriverTestCaseBase): def generate_driver(self, conf): driver = emc_cli_iscsi.EMCCLIISCSIDriver(configuration=conf) diff --git a/cinder/volume/drivers/emc/emc_cli_fc.py b/cinder/volume/drivers/emc/emc_cli_fc.py index 9736b2b32..8dcd142c8 100644 --- a/cinder/volume/drivers/emc/emc_cli_fc.py +++ b/cinder/volume/drivers/emc/emc_cli_fc.py @@ -57,6 +57,7 @@ class EMCCLIFCDriver(driver.FibreChannelDriver): Manage/unmanage volume revise White list target ports support Snap copy support + Support efficient non-disruptive backup """ def __init__(self, *args, **kwargs): @@ -271,3 +272,26 @@ class EMCCLIFCDriver(driver.FibreChannelDriver): def update_migrated_volume(self, context, volume, new_volume): """Returns model update for migrated volume.""" return self.cli.update_migrated_volume(context, volume, new_volume) + + def create_export_snapshot(self, context, snapshot, connector): + """Creates a snapshot mount point for snapshot.""" + return self.cli.create_export_snapshot(context, snapshot, connector) + + def remove_export_snapshot(self, context, snapshot): + """Removes snapshot mount point for snapshot.""" + return self.cli.remove_export_snapshot(context, snapshot) + + def initialize_connection_snapshot(self, snapshot, connector, **kwargs): + """Allows connection to snapshot.""" + return self.cli.initialize_connection_snapshot(snapshot, + connector, + **kwargs) + + def terminate_connection_snapshot(self, snapshot, connector, **kwargs): + """Disallows connection to snapshot.""" + return self.cli.terminate_connection_snapshot(snapshot, + connector, + **kwargs) + + def backup_use_temp_snapshot(self): + return True diff --git a/cinder/volume/drivers/emc/emc_cli_iscsi.py b/cinder/volume/drivers/emc/emc_cli_iscsi.py index 34a313b48..332fc8fae 100644 --- a/cinder/volume/drivers/emc/emc_cli_iscsi.py +++ b/cinder/volume/drivers/emc/emc_cli_iscsi.py @@ -55,6 +55,7 @@ class EMCCLIISCSIDriver(driver.ISCSIDriver): Manage/unmanage volume revise White list target ports support Snap copy support + Support efficient non-disruptive backup """ def __init__(self, *args, **kwargs): @@ -250,3 +251,26 @@ class EMCCLIISCSIDriver(driver.ISCSIDriver): def update_migrated_volume(self, context, volume, new_volume): """Returns model update for migrated volume.""" return self.cli.update_migrated_volume(context, volume, new_volume) + + def create_export_snapshot(self, context, snapshot, connector): + """Creates a snapshot mount point for snapshot.""" + return self.cli.create_export_snapshot(context, snapshot, connector) + + def remove_export_snapshot(self, context, snapshot): + """Removes snapshot mount point for snapshot.""" + return self.cli.remove_export_snapshot(context, snapshot) + + def initialize_connection_snapshot(self, snapshot, connector, **kwargs): + """Allows connection to snapshot.""" + return self.cli.initialize_connection_snapshot(snapshot, + connector, + **kwargs) + + def terminate_connection_snapshot(self, snapshot, connector, **kwargs): + """Disallows connection to snapshot.""" + return self.cli.terminate_connection_snapshot(snapshot, + connector, + **kwargs) + + def backup_use_temp_snapshot(self): + return True diff --git a/cinder/volume/drivers/emc/emc_vnx_cli.py b/cinder/volume/drivers/emc/emc_vnx_cli.py index 9e16a3031..4f33b22f2 100644 --- a/cinder/volume/drivers/emc/emc_vnx_cli.py +++ b/cinder/volume/drivers/emc/emc_vnx_cli.py @@ -536,7 +536,7 @@ class CommandLineHelper(object): return lun def delete_lun(self, name): - + """Deletes a LUN or mount point.""" command_delete_lun = ['lun', '-destroy', '-name', name, '-forceDetach', @@ -1781,6 +1781,7 @@ class EMCVnxCliBase(object): tmp_snap_prefix = 'tmp-snap-' snap_as_vol_prefix = 'snap-as-vol-' tmp_cgsnap_prefix = 'tmp-cgsnapshot-' + tmp_smp_for_backup_prefix = 'tmp-smp-' def __init__(self, prtcl, configuration=None): self.protocol = prtcl @@ -1958,6 +1959,9 @@ class EMCVnxCliBase(object): def _construct_tmp_snap_name(self, volume): return self.tmp_snap_prefix + volume['id'] + def _construct_tmp_smp_name(self, snapshot): + return self.tmp_smp_for_backup_prefix + snapshot.id + def create_volume(self, volume): """Creates a EMC volume.""" volume_size = volume['size'] @@ -1993,7 +1997,7 @@ class EMCVnxCliBase(object): provisioning, tiering, volume['consistencygroup_id'], ignore_thresholds=self.ignore_pool_full_threshold, poll=False) - pl = self._build_provider_location_for_lun(data['lun_id']) + pl = self._build_provider_location(data['lun_id']) volume_metadata['lun_type'] = 'lun' model_update = {'provider_location': pl, 'metadata': volume_metadata} @@ -2097,13 +2101,13 @@ class EMCVnxCliBase(object): raise exception.VolumeBackendAPIException(data=msg) return - def delete_volume(self, volume): + def delete_volume(self, volume, force_delete=False): """Deletes an EMC volume.""" try: self._client.delete_lun(volume['name']) except exception.EMCVnxCLICmdError as ex: orig_out = "\n".join(ex.kwargs["out"]) - if (self.force_delete_lun_in_sg and + if ((force_delete or self.force_delete_lun_in_sg) and VNXError.has_error(orig_out, VNXError.LUN_IN_SG)): LOG.warning(_LW('LUN corresponding to %s is still ' 'in some Storage Groups.' @@ -2251,7 +2255,7 @@ class EMCVnxCliBase(object): self._client.delete_snapshot( self._construct_snap_as_vol_name(volume)) - pl = self._build_provider_location_for_lun(src_id, 'lun') + pl = self._build_provider_location(src_id, 'lun') volume_metadata = self._get_volume_metadata(volume) volume_metadata['lun_type'] = 'lun' model_update = {'provider_location': pl, @@ -2516,7 +2520,7 @@ class EMCVnxCliBase(object): store=store_spec) flow_engine.run() new_lun_id = flow_engine.storage.fetch('new_lun_id') - pl = self._build_provider_location_for_lun(new_lun_id, 'lun') + pl = self._build_provider_location(new_lun_id, 'lun') volume_metadata['lun_type'] = 'lun' else: work_flow.add(CopySnapshotTask(), @@ -2527,7 +2531,7 @@ class EMCVnxCliBase(object): store=store_spec) flow_engine.run() new_lun_id = flow_engine.storage.fetch('new_smp_id') - pl = self._build_provider_location_for_lun(new_lun_id, 'smp') + pl = self._build_provider_location(new_lun_id, 'smp') volume_metadata['lun_type'] = 'smp' model_update = {'provider_location': pl, 'metadata': volume_metadata} @@ -2591,7 +2595,7 @@ class EMCVnxCliBase(object): self._client.delete_cgsnapshot(snapshot['id']) else: self.delete_snapshot(snapshot) - pl = self._build_provider_location_for_lun(new_lun_id, 'lun') + pl = self._build_provider_location(new_lun_id, 'lun') volume_metadata['lun_type'] = 'lun' else: work_flow.add(CreateSnapshotTask(), @@ -2601,7 +2605,7 @@ class EMCVnxCliBase(object): store=store_spec) flow_engine.run() new_lun_id = flow_engine.storage.fetch('new_smp_id') - pl = self._build_provider_location_for_lun(new_lun_id, 'smp') + pl = self._build_provider_location(new_lun_id, 'smp') volume_metadata['lun_type'] = 'smp' model_update = {'provider_location': pl, @@ -2624,7 +2628,8 @@ class EMCVnxCliBase(object): def dumps_provider_location(self, pl_dict): return '|'.join([k + '^' + pl_dict[k] for k in pl_dict]) - def _build_provider_location_for_lun(self, lun_id, type='lun'): + def _build_provider_location(self, lun_id, type='lun'): + """Builds provider_location for volume or snapshot.""" pl_dict = {'system': self.get_array_serial(), 'type': type, 'id': six.text_type(lun_id), @@ -3331,6 +3336,34 @@ class EMCVnxCliBase(object): return conn_info return do_terminate_connection() + def initialize_connection_snapshot(self, snapshot, connector, **kwargs): + """Initializes connection for mount point.""" + smp_name = self._construct_tmp_smp_name(snapshot) + self._client.attach_mount_point(smp_name, snapshot.name) + volume = {'name': smp_name, 'id': snapshot.id} + return self.initialize_connection(volume, connector) + + def terminate_connection_snapshot(self, snapshot, connector, **kwargs): + """Disallows connection for mount point.""" + smp_name = self._construct_tmp_smp_name(snapshot) + volume = {'name': smp_name} + conn_info = self.terminate_connection(volume, connector) + self._client.detach_mount_point(smp_name) + return conn_info + + def create_export_snapshot(self, context, snapshot, connector): + """Creates mount point for a snapshot.""" + smp_name = self._construct_tmp_smp_name(snapshot) + primary_lun_name = snapshot.volume_name + self._client.create_mount_point(primary_lun_name, smp_name) + return None + + def remove_export_snapshot(self, context, snapshot): + """Removes mount point for a snapshot.""" + smp_name = self._construct_tmp_smp_name(snapshot) + volume = {'name': smp_name, 'provider_location': None} + self.delete_volume(volume, True) + def manage_existing_get_size(self, volume, existing_ref): """Returns size of volume to be managed by manage_existing.""" if 'source-id' in existing_ref: @@ -3378,7 +3411,7 @@ class EMCVnxCliBase(object): existing_ref=manage_existing_ref, reason=reason) self._client.rename_lun(lun_id, volume['name']) model_update = {'provider_location': - self._build_provider_location_for_lun(lun_id)} + self._build_provider_location(lun_id)} return model_update def get_login_ports(self, connector, io_ports=None): @@ -3515,7 +3548,7 @@ class EMCVnxCliBase(object): for i, update in enumerate(volume_model_updates): new_lun_id = flow_engine.storage.fetch(lun_id_key_template % i) update['provider_location'] = ( - self._build_provider_location_for_lun(new_lun_id)) + self._build_provider_location(new_lun_id)) return None, volume_model_updates -- 2.45.2