@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):
"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)
@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()
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):
"""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)
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
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.
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)
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)