]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Support efficient non-disruptive volume backup in VNX
authorpeter_wang <peter.wang13@emc.com>
Tue, 11 Aug 2015 08:36:07 +0000 (04:36 -0400)
committerpeter_wang <peter.wang13@emc.com>
Fri, 21 Aug 2015 02:30:30 +0000 (22:30 -0400)
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
cinder/volume/drivers/emc/emc_cli_fc.py
cinder/volume/drivers/emc/emc_cli_iscsi.py
cinder/volume/drivers/emc/emc_vnx_cli.py

index 55a51dab3424dacdbb9a8a9f71d5254cf20b29fb..159b9480d06898a2248f930076188fee4ff718fc 100644 (file)
@@ -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)
index 9736b2b329444b1dec1602b659fdd450a2c15097..8dcd142c884c6d51e1f35a22d47775d05f15b7dd 100644 (file)
@@ -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
index 34a313b489e4b451f8dbb7e798d40df07b6a9c82..332fc8fae556e78997a1388d92dc2a5d5445c46d 100644 (file)
@@ -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
index 9e16a30315ef0f6c95eee5fee562a2728d2beff2..4f33b22f2e9f774bcb3a3a55aa398c88a94d1c6f 100644 (file)
@@ -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