]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Implement Clone CG in Pure Volume Drivers
authorPatrick East <patrick.east@purestorage.com>
Tue, 11 Aug 2015 21:08:29 +0000 (14:08 -0700)
committerPatrick East <patrick.east@purestorage.com>
Fri, 14 Aug 2015 23:12:42 +0000 (16:12 -0700)
This adds support for the consisgroup-create-from-src method specifying
a source consistency group.

The driver will create a new Purity Protection Group, take a temporary
snapshot of the source Protection Group, create new volumes from the
consistent snapshot, and then add these new volumes into the newly
created protection group.

Implements: blueprint pure-clone-cg
Change-Id: I3446c4a86027f1f50ada673ddb1f3b13b5e8f6cc

cinder/tests/unit/test_pure.py
cinder/volume/drivers/pure.py

index ded9990dd6519245cbb8368f318ec467e70c666f..166e1e8015db3b3ab0b88405a554fb7dc123b7f9 100644 (file)
@@ -286,8 +286,7 @@ class PureBaseVolumeDriverTestCase(PureDriverTestCase):
     @mock.patch(BASE_DRIVER_OBJ + "._add_volume_to_consistency_group",
                 autospec=True)
     @mock.patch(BASE_DRIVER_OBJ + "._extend_if_needed", autospec=True)
-    @mock.patch(BASE_DRIVER_OBJ + "._get_pgroup_vol_snap_name",
-                spec=pure.PureBaseVolumeDriver._get_pgroup_vol_snap_name)
+    @mock.patch(BASE_DRIVER_OBJ + "._get_pgroup_snap_name_from_snapshot")
     def test_create_volume_from_cgsnapshot(self, mock_get_snap_name,
                                            mock_extend_if_needed,
                                            mock_add_to_cgroup):
@@ -634,7 +633,8 @@ class PureBaseVolumeDriverTestCase(PureDriverTestCase):
                             "vol": volume_name,
                         }
 
-        actual_name = self.driver._get_pgroup_vol_snap_name(mock_snap)
+        actual_name = self.driver._get_pgroup_snap_name_from_snapshot(
+            mock_snap)
 
         self.assertEqual(expected_name, actual_name)
 
@@ -654,8 +654,8 @@ class PureBaseVolumeDriverTestCase(PureDriverTestCase):
 
     @mock.patch(BASE_DRIVER_OBJ + ".create_volume_from_snapshot")
     @mock.patch(BASE_DRIVER_OBJ + ".create_consistencygroup")
-    def test_create_consistencygroup_from_src(self, mock_create_cg,
-                                              mock_create_vol):
+    def test_create_consistencygroup_from_cgsnapshot(self, mock_create_cg,
+                                                     mock_create_vol):
         mock_context = mock.Mock()
         mock_group = mock.Mock()
         mock_cgsnapshot = mock.Mock()
@@ -688,13 +688,52 @@ class PureBaseVolumeDriverTestCase(PureDriverTestCase):
             source_vols=None
         )
 
-    def test_create_consistencygroup_from_src_no_snap(self):
-        # Expect an error when no cgsnapshot or snapshots are provided
-        self.assertRaises(exception.InvalidInput,
-                          self.driver.create_consistencygroup_from_src,
-                          mock.Mock(),  # context
-                          mock.Mock(),  # group
-                          [mock.Mock()])  # volumes
+    @mock.patch(BASE_DRIVER_OBJ + ".create_consistencygroup")
+    def test_create_consistencygroup_from_cg(self, mock_create_cg):
+        num_volumes = 5
+        mock_context = mock.MagicMock()
+        mock_group = mock.MagicMock()
+        mock_source_cg = mock.MagicMock()
+        mock_volumes = [mock.MagicMock() for i in range(num_volumes)]
+        mock_source_vols = [mock.MagicMock() for i in range(num_volumes)]
+        self.driver.create_consistencygroup_from_src(
+            mock_context,
+            mock_group,
+            mock_volumes,
+            source_cg=mock_source_cg,
+            source_vols=mock_source_vols
+        )
+        mock_create_cg.assert_called_with(mock_context, mock_group)
+        self.assertTrue(self.array.create_pgroup_snapshot.called)
+        self.assertEqual(num_volumes, self.array.copy_volume.call_count)
+        self.assertEqual(num_volumes, self.array.set_pgroup.call_count)
+        self.assertTrue(self.array.destroy_pgroup.called)
+
+    @mock.patch(BASE_DRIVER_OBJ + ".create_consistencygroup")
+    def test_create_consistencygroup_from_cg_with_error(self, mock_create_cg):
+        num_volumes = 5
+        mock_context = mock.MagicMock()
+        mock_group = mock.MagicMock()
+        mock_source_cg = mock.MagicMock()
+        mock_volumes = [mock.MagicMock() for i in range(num_volumes)]
+        mock_source_vols = [mock.MagicMock() for i in range(num_volumes)]
+
+        self.array.copy_volume.side_effect = FakePureStorageHTTPError()
+
+        self.assertRaises(
+            FakePureStorageHTTPError,
+            self.driver.create_consistencygroup_from_src,
+            mock_context,
+            mock_group,
+            mock_volumes,
+            source_cg=mock_source_cg,
+            source_vols=mock_source_vols
+        )
+        mock_create_cg.assert_called_with(mock_context, mock_group)
+        self.assertTrue(self.array.create_pgroup_snapshot.called)
+        # Make sure that the temp snapshot is cleaned up even when copying
+        # the volume fails!
+        self.assertTrue(self.array.destroy_pgroup.called)
 
     @mock.patch(BASE_DRIVER_OBJ + ".delete_volume", autospec=True)
     def test_delete_consistencygroup(self, mock_delete_volume):
index 10053ac626322b9d54596d61dd23afbc8bfe352d..7be2c8f6693b3529ad476b6f120c677fb94ececb 100644 (file)
@@ -130,7 +130,7 @@ class PureBaseVolumeDriver(san.SanDriver):
         """Creates a volume from a snapshot."""
         vol_name = self._get_vol_name(volume)
         if snapshot['cgsnapshot_id']:
-            snap_name = self._get_pgroup_vol_snap_name(snapshot)
+            snap_name = self._get_pgroup_snap_name_from_snapshot(snapshot)
         else:
             snap_name = self._get_snap_name(snapshot)
 
@@ -336,19 +336,57 @@ class PureBaseVolumeDriver(san.SanDriver):
         model_update = {'status': 'available'}
         return model_update
 
+    def _create_cg_from_cgsnap(self, volumes, snapshots):
+        """Creates a new consistency group from a cgsnapshot.
+
+        The new volumes will be consistent with the snapshot.
+        """
+        for volume, snapshot in zip(volumes, snapshots):
+            self.create_volume_from_snapshot(volume, snapshot)
+
+    def _create_cg_from_cg(self, group, source_group, volumes, source_vols):
+        """Creates a new consistency group from an existing cg.
+
+        The new volumes will be in a consistent state, but this requires
+        taking a new temporary group snapshot and cloning from that.
+        """
+        pgroup_name = self._get_pgroup_name_from_id(source_group.id)
+        tmp_suffix = '%s-tmp' % uuid.uuid4()
+        tmp_pgsnap_name = '%(pgroup_name)s.%(pgsnap_suffix)s' % {
+            'pgroup_name': pgroup_name,
+            'pgsnap_suffix': tmp_suffix,
+        }
+        LOG.debug('Creating temporary Protection Group snapshot %(snap_name)s '
+                  'while cloning Consistency Group %(source_group)s.',
+                  {'snap_name': tmp_pgsnap_name,
+                   'source_group': source_group.id})
+        self._array.create_pgroup_snapshot(pgroup_name, suffix=tmp_suffix)
+        try:
+            for source_vol, cloned_vol in zip(source_vols, volumes):
+                source_snap_name = self._get_pgroup_vol_snap_name(
+                    pgroup_name,
+                    tmp_suffix,
+                    self._get_vol_name(source_vol)
+                )
+                cloned_vol_name = self._get_vol_name(cloned_vol)
+                self._array.copy_volume(source_snap_name, cloned_vol_name)
+                self._add_volume_to_consistency_group(
+                    group.id,
+                    cloned_vol_name
+                )
+        finally:
+            self._delete_pgsnapshot(tmp_pgsnap_name)
+
     @log_debug_trace
     def create_consistencygroup_from_src(self, context, group, volumes,
                                          cgsnapshot=None, snapshots=None,
                                          source_cg=None, source_vols=None):
-
+        self.create_consistencygroup(context, group)
         if cgsnapshot and snapshots:
-            self.create_consistencygroup(context, group)
-            for volume, snapshot in zip(volumes, snapshots):
-                self.create_volume_from_snapshot(volume, snapshot)
-        else:
-            msg = _("create_consistencygroup_from_src only supports a"
-                    " cgsnapshot source, other sources cannot be used.")
-            raise exception.InvalidInput(reason=msg)
+            self._create_cg_from_cgsnap(volumes,
+                                        snapshots)
+        elif source_cg:
+            self._create_cg_from_cg(group, source_cg, volumes, source_vols)
 
         return None, None
 
@@ -418,12 +456,7 @@ class PureBaseVolumeDriver(san.SanDriver):
 
         return model_update, snapshots
 
-    @log_debug_trace
-    def delete_cgsnapshot(self, context, cgsnapshot):
-        """Deletes a cgsnapshot."""
-
-        pgsnap_name = self._get_pgroup_snap_name(cgsnapshot)
-
+    def _delete_pgsnapshot(self, pgsnap_name):
         try:
             # FlashArray.destroy_pgroup is also used for deleting
             # pgroup snapshots. The underlying REST API is identical.
@@ -439,6 +472,13 @@ class PureBaseVolumeDriver(san.SanDriver):
                     LOG.warning(_LW("Unable to delete Protection Group "
                                     "Snapshot: %s"), err.text)
 
+    @log_debug_trace
+    def delete_cgsnapshot(self, context, cgsnapshot):
+        """Deletes a cgsnapshot."""
+
+        pgsnap_name = self._get_pgroup_snap_name(cgsnapshot)
+        self._delete_pgsnapshot(pgsnap_name)
+
         snapshots = objects.SnapshotList.get_all_for_cgsnapshot(
             context, cgsnapshot.id)
 
@@ -564,12 +604,19 @@ class PureBaseVolumeDriver(san.SanDriver):
         return "%s.%s" % (cls._get_pgroup_name_from_id(cg_id),
                           cls._get_pgroup_snap_suffix(cgsnapshot))
 
-    @classmethod
-    def _get_pgroup_vol_snap_name(cls, snapshot):
+    @staticmethod
+    def _get_pgroup_vol_snap_name(pg_name, pgsnap_suffix, volume_name):
+        return "%(pgroup_name)s.%(pgsnap_suffix)s.%(volume_name)s" % {
+            'pgroup_name': pg_name,
+            'pgsnap_suffix': pgsnap_suffix,
+            'volume_name': volume_name,
+        }
+
+    def _get_pgroup_snap_name_from_snapshot(self, snapshot):
         """Return the name of the snapshot that Purity will use."""
         cg_id = snapshot.cgsnapshot.consistencygroup_id
-        cg_name = cls._get_pgroup_name_from_id(cg_id)
-        cgsnapshot_id = cls._get_pgroup_snap_suffix(snapshot.cgsnapshot)
+        cg_name = self._get_pgroup_name_from_id(cg_id)
+        cgsnapshot_id = self._get_pgroup_snap_suffix(snapshot.cgsnapshot)
         volume_name = snapshot.volume_name
         return "%s.%s.%s-cinder" % (cg_name, cgsnapshot_id, volume_name)