]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
IBM GPFS Consistency Group Implementation
authorNilesh Bhosale <nilesh.bhosale@in.ibm.com>
Thu, 25 Dec 2014 13:52:58 +0000 (19:22 +0530)
committerNilesh Bhosale <nilesh.bhosale@in.ibm.com>
Fri, 20 Feb 2015 17:47:52 +0000 (23:17 +0530)
Adding support for consistency groups in IBM GPFS driver.

Change-Id: I308d224982301eb42e75e68b4e3c689ee568fcd6
Implements: blueprint ibm-gpfs-consistency-group

cinder/tests/test_gpfs.py
cinder/volume/drivers/ibm/gpfs.py
etc/cinder/rootwrap.d/volume.filters

index cea131316dca60d0c0334c6e7021c2068eae4d53..f88cefc56ced281fd8ab47ba43f848c9a16d72a8 100644 (file)
@@ -698,8 +698,7 @@ class GPFSDriverTestCase(test.TestCase):
                            mock_allocate_file_blocks,
                            mock_exec):
         mock_local_path.return_value = 'test'
-        volume = {}
-        volume['size'] = 1000
+        volume = self._fake_volume()
         value = {}
         value['value'] = 'test'
 
@@ -725,8 +724,7 @@ class GPFSDriverTestCase(test.TestCase):
                                             mock_allocate_file_blocks,
                                             mock_exec):
         mock_local_path.return_value = 'test'
-        volume = {}
-        volume['size'] = 1000
+        volume = self._fake_volume()
         value = {}
         value['value'] = 'test'
 
@@ -752,8 +750,7 @@ class GPFSDriverTestCase(test.TestCase):
                                          mock_allocate_file_blocks,
                                          mock_exec):
         mock_local_path.return_value = 'test'
-        volume = {}
-        volume['size'] = 1000
+        volume = self._fake_volume()
         value = {}
         value['value'] = 'test'
         mock_set_volume_attributes.return_value = True
@@ -772,18 +769,27 @@ class GPFSDriverTestCase(test.TestCase):
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._gpfs_redirect')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._set_rw_permission')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._create_gpfs_copy')
+    @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._gpfs_full_copy')
+    @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._get_snapshot_path')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.local_path')
     def test_create_volume_from_snapshot(self,
                                          mock_local_path,
+                                         mock_snapshot_path,
+                                         mock_gpfs_full_copy,
                                          mock_create_gpfs_copy,
                                          mock_rw_permission,
                                          mock_gpfs_redirect,
                                          mock_set_volume_attributes,
                                          mock_resize_volume_file):
         mock_resize_volume_file.return_value = 5 * units.Gi
-        volume = {}
-        volume['size'] = 1000
-        self.assertEqual(self.driver.create_volume_from_snapshot(volume, ''),
+        volume = self._fake_volume()
+        volume['consistencygroup_id'] = None
+        snapshot = self._fake_snapshot()
+        mock_snapshot_path.return_value = "/tmp/fakepath"
+        self.assertEqual(self.driver.create_volume_from_snapshot(
+                         volume,
+                         snapshot
+                         ),
                          {'size': 5.0})
 
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._resize_volume_file')
@@ -791,62 +797,73 @@ class GPFSDriverTestCase(test.TestCase):
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._gpfs_redirect')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._set_rw_permission')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._create_gpfs_copy')
+    @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._gpfs_full_copy')
+    @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._get_snapshot_path')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.local_path')
     def test_create_volume_from_snapshot_metadata(self,
                                                   mock_local_path,
+                                                  mock_snapshot_path,
+                                                  mock_gpfs_full_copy,
                                                   mock_create_gpfs_copy,
                                                   mock_rw_permission,
                                                   mock_gpfs_redirect,
                                                   mock_set_volume_attributes,
                                                   mock_resize_volume_file):
         mock_resize_volume_file.return_value = 5 * units.Gi
-        volume = {}
-        volume['size'] = 1000
+        volume = self._fake_volume()
+        volume['consistencygroup_id'] = None
+        snapshot = self._fake_snapshot()
+        mock_snapshot_path.return_value = "/tmp/fakepath"
         mock_set_volume_attributes.return_value = True
         metadata = [{'key': 'fake_key', 'value': 'fake_value'}]
 
         self.assertEqual(True, self.driver._set_volume_attributes(volume,
                          'test', metadata))
-        self.assertEqual(self.driver.create_volume_from_snapshot(volume, ''),
+        self.assertEqual(self.driver.create_volume_from_snapshot(volume,
+                                                                 snapshot),
                          {'size': 5.0})
 
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._resize_volume_file')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._set_volume_attributes')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._set_rw_permission')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._create_gpfs_clone')
+    @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._gpfs_full_copy')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.local_path')
     def test_create_cloned_volume(self,
                                   mock_local_path,
+                                  mock_gpfs_full_copy,
                                   mock_create_gpfs_clone,
                                   mock_rw_permission,
                                   mock_set_volume_attributes,
                                   mock_resize_volume_file):
         mock_resize_volume_file.return_value = 5 * units.Gi
-        volume = {}
-        volume['size'] = 1000
-        self.assertEqual(self.driver.create_cloned_volume(volume, ''),
+        volume = self._fake_volume()
+        src_volume = self._fake_volume()
+        self.assertEqual(self.driver.create_cloned_volume(volume, src_volume),
                          {'size': 5.0})
 
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._resize_volume_file')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._set_volume_attributes')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._set_rw_permission')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._create_gpfs_clone')
+    @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._gpfs_full_copy')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.local_path')
     def test_create_cloned_volume_with_metadata(self,
                                                 mock_local_path,
+                                                mock_gpfs_full_copy,
                                                 mock_create_gpfs_clone,
                                                 mock_rw_permission,
                                                 mock_set_volume_attributes,
                                                 mock_resize_volume_file):
         mock_resize_volume_file.return_value = 5 * units.Gi
-        volume = {}
-        volume['size'] = 1000
+        volume = self._fake_volume()
+        src_volume = self._fake_volume()
         mock_set_volume_attributes.return_value = True
         metadata = [{'key': 'fake_key', 'value': 'fake_value'}]
 
         self.assertEqual(True, self.driver._set_volume_attributes(volume,
                          'test', metadata))
-        self.assertEqual(self.driver.create_cloned_volume(volume, ''),
+        self.assertEqual(self.driver.create_cloned_volume(volume, src_volume),
                          {'size': 5.0})
 
     @patch('cinder.utils.execute')
@@ -990,12 +1007,15 @@ class GPFSDriverTestCase(test.TestCase):
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._set_rw_permission')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._create_gpfs_snap')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.local_path')
+    @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._get_snapshot_path')
     def test_create_snapshot(self,
+                             mock_get_snapshot_path,
                              mock_local_path,
                              mock_create_gpfs_snap,
                              mock_set_rw_permission,
                              mock_gpfs_redirect):
         org_value = self.driver.configuration.gpfs_mount_point_base
+        mock_get_snapshot_path.return_value = "/tmp/fakepath"
         self.flags(volume_driver=self.driver_name,
                    gpfs_mount_point_base=self.volumes_path)
         snapshot = {}
@@ -1005,12 +1025,19 @@ class GPFSDriverTestCase(test.TestCase):
                    gpfs_mount_point_base=org_value)
 
     @patch('cinder.utils.execute')
-    @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.local_path')
+    @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._get_snapshot_path')
     def test_delete_snapshot(self,
-                             mock_local_path,
+                             mock_snapshot_path,
                              mock_exec):
-        snapshot = {}
+        snapshot = self._fake_snapshot()
+        snapshot_path = "/tmp/fakepath"
+        mock_snapshot_path.return_value = snapshot_path
+        snapshot_ts_path = '%s.ts' % snapshot_path
         self.driver.delete_snapshot(snapshot)
+        mock_exec.assert_any_call('mv', snapshot_path,
+                                  snapshot_ts_path)
+        mock_exec.assert_any_call('rm', '-f', snapshot_ts_path,
+                                  check_exit_code=False)
 
     def test_ensure_export(self):
         self.assertEqual(None, self.driver.ensure_export('', ''))
@@ -1021,13 +1048,13 @@ class GPFSDriverTestCase(test.TestCase):
     def test_remove_export(self):
         self.assertEqual(None, self.driver.remove_export('', ''))
 
-    def test_initialize_connection(self):
-        volume = {}
-        volume['name'] = 'test'
+    @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.local_path')
+    def test_initialize_connection(self, mock_local_path):
+        volume = self._fake_volume()
+        mock_local_path.return_value = "/tmp/fakepath"
         data = self.driver.initialize_connection(volume, '')
         self.assertEqual(data['data']['name'], 'test')
-        self.assertEqual(data['data']['device_path'], os.path.join(
-            self.driver.configuration.gpfs_mount_point_base, 'test'))
+        self.assertEqual(data['data']['device_path'], "/tmp/fakepath")
         self.assertEqual(data['driver_volume_type'], 'gpfs')
 
     def test_terminate_connection(self):
@@ -1112,9 +1139,7 @@ class GPFSDriverTestCase(test.TestCase):
         mock_is_cloneable.return_value = (True, 'test', self.images_dir)
         mock_is_gpfs_parent_file.return_value = False
         mock_qemu_img_info.return_value = self._fake_qemu_qcow2_image_info('')
-        volume = {}
-        volume['id'] = 'test'
-        volume['size'] = 1000
+        volume = self._fake_volume()
         self.assertEqual(({'provider_location': None}, True),
                          self.driver._clone_image(volume, '', 1))
 
@@ -1124,9 +1149,7 @@ class GPFSDriverTestCase(test.TestCase):
                                        mock_verify_gpfs_path_state,
                                        mock_is_cloneable):
         mock_is_cloneable.return_value = (False, 'test', self.images_dir)
-        volume = {}
-        volume['id'] = 'test'
-        volume['size'] = 1000
+        volume = self._fake_volume()
         self.assertEqual((None, False),
                          self.driver._clone_image(volume, '', 1))
 
@@ -1153,9 +1176,7 @@ class GPFSDriverTestCase(test.TestCase):
         mock_local_path.return_value = self.volumes_path
         mock_is_gpfs_parent_file.return_value = False
         mock_qemu_img_info.return_value = self._fake_qemu_raw_image_info('')
-        volume = {}
-        volume['id'] = 'test'
-        volume['size'] = 1000
+        volume = self._fake_volume()
         org_value = self.driver.configuration.gpfs_images_share_mode
         self.flags(volume_driver=self.driver_name,
                    gpfs_images_share_mode='copy_on_write')
@@ -1186,9 +1207,7 @@ class GPFSDriverTestCase(test.TestCase):
         mock_is_cloneable.return_value = (True, 'test', self.images_dir)
         mock_local_path.return_value = self.volumes_path
         mock_qemu_img_info.return_value = self._fake_qemu_raw_image_info('')
-        volume = {}
-        volume['id'] = 'test'
-        volume['size'] = 1000
+        volume = self._fake_volume()
         org_value = self.driver.configuration.gpfs_images_share_mode
 
         self.flags(volume_driver=self.driver_name,
@@ -1219,9 +1238,7 @@ class GPFSDriverTestCase(test.TestCase):
         mock_is_cloneable.return_value = (True, 'test', self.images_dir)
         mock_local_path.return_value = self.volumes_path
         mock_qemu_img_info.return_value = self._fake_qemu_qcow2_image_info('')
-        volume = {}
-        volume['id'] = 'test'
-        volume['size'] = 1000
+        volume = self._fake_volume()
         self.assertEqual(({'provider_location': None}, True),
                          self.driver._clone_image(volume, '', 1))
         image_utils.convert_image.assert_called_once_with(self.images_dir,
@@ -1237,9 +1254,7 @@ class GPFSDriverTestCase(test.TestCase):
                                   mock_fetch_to_raw,
                                   mock_local_path,
                                   mock_resize_volume_file):
-        volume = {}
-        volume['id'] = 'test'
-        volume['size'] = 1000
+        volume = self._fake_volume()
         self.driver.copy_image_to_volume('', volume, '', 1)
 
     @patch('cinder.image.image_utils.qemu_img_info')
@@ -1249,8 +1264,7 @@ class GPFSDriverTestCase(test.TestCase):
                                    mock_local_path,
                                    mock_resize_image,
                                    mock_qemu_img_info):
-        volume = {}
-        volume['id'] = 'test'
+        volume = self._fake_volume()
         mock_qemu_img_info.return_value = self._fake_qemu_qcow2_image_info('')
         self.assertEqual(self._fake_qemu_qcow2_image_info('').virtual_size,
                          self.driver._resize_volume_file(volume, 2000))
@@ -1262,8 +1276,7 @@ class GPFSDriverTestCase(test.TestCase):
                                      mock_local_path,
                                      mock_resize_image,
                                      mock_qemu_img_info):
-        volume = {}
-        volume['id'] = 'test'
+        volume = self._fake_volume()
         mock_resize_image.side_effect = (
             processutils.ProcessExecutionError(stdout='test', stderr='test'))
         mock_qemu_img_info.return_value = self._fake_qemu_qcow2_image_info('')
@@ -1272,15 +1285,13 @@ class GPFSDriverTestCase(test.TestCase):
 
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._resize_volume_file')
     def test_extend_volume(self, mock_resize_volume_file):
-        volume = {}
-        volume['id'] = 'test'
+        volume = self._fake_volume()
         self.driver.extend_volume(volume, 2000)
 
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.local_path')
     @patch('cinder.image.image_utils.upload_volume')
     def test_copy_volume_to_image(self, mock_upload_volume, mock_local_path):
-        volume = {}
-        volume['id'] = 'test'
+        volume = self._fake_volume()
         self.driver.copy_volume_to_image('', volume, '', '')
 
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._delete_gpfs_file')
@@ -1296,8 +1307,7 @@ class GPFSDriverTestCase(test.TestCase):
                            mock_temp_chown,
                            mock_file_open,
                            mock_delete_gpfs_file):
-        volume = {}
-        volume['name'] = 'test'
+        volume = self._fake_volume()
         self.driver.db = mock.Mock()
         self.driver.db.volume_get = mock.Mock()
         self.driver.db.volume_get.return_value = volume
@@ -1315,8 +1325,7 @@ class GPFSDriverTestCase(test.TestCase):
                             mock_local_path,
                             mock_temp_chown,
                             mock_file_open):
-        volume = {}
-        volume['id'] = '123456'
+        volume = self._fake_volume()
         backup = {}
         backup['id'] = '123456'
         backup_service = mock.Mock()
@@ -1326,8 +1335,7 @@ class GPFSDriverTestCase(test.TestCase):
     @patch('cinder.utils.execute')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._can_migrate_locally')
     def test_migrate_volume_ok(self, mock_local, mock_exec):
-        volume = {}
-        volume['name'] = 'test'
+        volume = self._fake_volume()
         host = {}
         host = {'host': 'foo', 'capabilities': {}}
         mock_local.return_value = (self.driver.configuration.
@@ -1338,8 +1346,7 @@ class GPFSDriverTestCase(test.TestCase):
     @patch('cinder.utils.execute')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._can_migrate_locally')
     def test_migrate_volume_fail_dest_path(self, mock_local, mock_exec):
-        volume = {}
-        volume['name'] = 'test'
+        volume = self._fake_volume()
         host = {}
         host = {'host': 'foo', 'capabilities': {}}
         mock_local.return_value = None
@@ -1349,8 +1356,7 @@ class GPFSDriverTestCase(test.TestCase):
     @patch('cinder.utils.execute')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._can_migrate_locally')
     def test_migrate_volume_fail_mpb(self, mock_local, mock_exec):
-        volume = {}
-        volume['name'] = 'test'
+        volume = self._fake_volume()
         host = {}
         host = {'host': 'foo', 'capabilities': {}}
         mock_local.return_value = (self.driver.configuration.
@@ -1363,8 +1369,7 @@ class GPFSDriverTestCase(test.TestCase):
     @patch('cinder.utils.execute')
     @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._can_migrate_locally')
     def test_migrate_volume_fail_mv(self, mock_local, mock_exec):
-        volume = {}
-        volume['name'] = 'test'
+        volume = self._fake_volume()
         host = {}
         host = {'host': 'foo', 'capabilities': {}}
         mock_local.return_value = (
@@ -1461,8 +1466,7 @@ class GPFSDriverTestCase(test.TestCase):
 
     @patch('cinder.utils.execute')
     def test_mkfs_ok(self, mock_exec):
-        volume = {}
-        volume['name'] = 'test'
+        volume = self._fake_volume()
         self.driver._mkfs(volume, 'swap')
         self.driver._mkfs(volume, 'swap', 'test')
         self.driver._mkfs(volume, 'ext3', 'test')
@@ -1470,8 +1474,7 @@ class GPFSDriverTestCase(test.TestCase):
 
     @patch('cinder.utils.execute')
     def test_mkfs_fail_mk(self, mock_exec):
-        volume = {}
-        volume['name'] = 'test'
+        volume = self._fake_volume()
         mock_exec.side_effect = (
             processutils.ProcessExecutionError(stdout='test', stderr='test'))
         self.assertRaises(exception.VolumeBackendAPIException,
@@ -1510,6 +1513,209 @@ class GPFSDriverTestCase(test.TestCase):
         self.assertRaises(exception.VolumeBackendAPIException,
                           self.driver._verify_gpfs_path_state, self.images_dir)
 
+    @patch('cinder.utils.execute')
+    def test_create_consistencygroup(self, mock_exec):
+        ctxt = self.context
+        group = self._fake_group()
+        self.driver.create_consistencygroup(ctxt, group)
+        fsdev = self.driver._gpfs_device
+        cgname = "consisgroup-%s" % group['id']
+        cgpath = os.path.join(self.driver.configuration.gpfs_mount_point_base,
+                              cgname)
+        cmd = ['mmcrfileset', fsdev, cgname, '--inode-space', 'new']
+        mock_exec.assert_any_call(*cmd)
+        cmd = ['mmlinkfileset', fsdev, cgname, '-J', cgpath]
+        mock_exec.assert_any_call(*cmd)
+        cmd = ['chmod', '770', cgpath]
+        mock_exec.assert_any_call(*cmd)
+
+    @patch('cinder.utils.execute')
+    def test_create_consistencygroup_fail(self, mock_exec):
+        ctxt = self.context
+        group = self._fake_group()
+        mock_exec.side_effect = (
+            processutils.ProcessExecutionError(stdout='test', stderr='test'))
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.create_consistencygroup, ctxt, group)
+
+    @patch('cinder.utils.execute')
+    def test_delete_consistencygroup(self, mock_exec):
+        ctxt = self.context
+        group = self._fake_group()
+        group['status'] = 'available'
+        volume = self._fake_volume()
+        volume['status'] = 'available'
+        volumes = []
+        volumes.append(volume)
+        self.driver.db = mock.Mock()
+        self.driver.db.volume_get_all_by_group = mock.Mock()
+        self.driver.db.volume_get_all_by_group.return_value = volumes
+
+        self.driver.delete_consistencygroup(ctxt, group)
+        fsdev = self.driver._gpfs_device
+        cgname = "consisgroup-%s" % group['id']
+        cmd = ['mmunlinkfileset', fsdev, cgname, '-f']
+        mock_exec.assert_any_call(*cmd)
+        cmd = ['mmdelfileset', fsdev, cgname, '-f']
+        mock_exec.assert_any_call(*cmd)
+
+    @patch('cinder.utils.execute')
+    def test_delete_consistencygroup_fail(self, mock_exec):
+        ctxt = self.context
+        group = self._fake_group()
+        group['status'] = 'available'
+        self.driver.db = mock.Mock()
+        self.driver.db.volume_get_all_by_group = mock.Mock()
+        self.driver.db.volume_get_all_by_group.return_value = []
+
+        mock_exec.side_effect = (
+            processutils.ProcessExecutionError(stdout='test', stderr='test'))
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.delete_consistencygroup, ctxt, group)
+
+    @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.create_snapshot')
+    def test_create_cgsnapshot(self, mock_create_snap):
+        ctxt = self.context
+        cgsnap = self._fake_cgsnapshot()
+        self.driver.db = mock.Mock()
+        self.driver.db.snapshot_get_all_for_cgsnapshot = mock.Mock()
+        snapshot1 = self._fake_snapshot()
+        snapshots = [snapshot1]
+        self.driver.db.snapshot_get_all_for_cgsnapshot.return_value = snapshots
+        model_update, snapshots = self.driver.create_cgsnapshot(ctxt, cgsnap)
+        self.driver.create_snapshot.assert_called_once_with(snapshot1)
+        self.assertEqual({'status': cgsnap['status']}, model_update)
+        self.assertEqual(snapshot1['status'], 'available')
+        self.driver.db.snapshot_get_all_for_cgsnapshot.\
+            assert_called_once_with(ctxt, cgsnap['id'])
+
+    @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.create_snapshot')
+    def test_create_cgsnapshot_empty(self, mock_create_snap):
+        ctxt = self.context
+        cgsnap = self._fake_cgsnapshot()
+        self.driver.db = mock.Mock()
+        self.driver.db.snapshot_get_all_for_cgsnapshot = mock.Mock()
+        snapshots = []
+        self.driver.db.snapshot_get_all_for_cgsnapshot.return_value = snapshots
+        model_update, snapshots = self.driver.create_cgsnapshot(ctxt, cgsnap)
+        self.assertFalse(self.driver.create_snapshot.called)
+        self.assertEqual({'status': cgsnap['status']}, model_update)
+        self.driver.db.snapshot_get_all_for_cgsnapshot.\
+            assert_called_once_with(ctxt, cgsnap['id'])
+
+    @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.delete_snapshot')
+    def test_delete_cgsnapshot(self, mock_delete_snap):
+        ctxt = self.context
+        cgsnap = self._fake_cgsnapshot()
+        self.driver.db = mock.Mock()
+        self.driver.db.snapshot_get_all_for_cgsnapshot = mock.Mock()
+        snapshot1 = self._fake_snapshot()
+        snapshots = [snapshot1]
+        self.driver.db.snapshot_get_all_for_cgsnapshot.return_value = snapshots
+        model_update, snapshots = self.driver.delete_cgsnapshot(ctxt, cgsnap)
+        self.driver.delete_snapshot.assert_called_once_with(snapshot1)
+        self.assertEqual({'status': cgsnap['status']}, model_update)
+        self.assertEqual(snapshot1['status'], 'deleted')
+        self.driver.db.snapshot_get_all_for_cgsnapshot.\
+            assert_called_once_with(ctxt, cgsnap['id'])
+
+    @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.delete_snapshot')
+    def test_delete_cgsnapshot_empty(self, mock_delete_snap):
+        ctxt = self.context
+        cgsnap = self._fake_cgsnapshot()
+        self.driver.db = mock.Mock()
+        self.driver.db.snapshot_get_all_for_cgsnapshot = mock.Mock()
+        snapshots = []
+        self.driver.db.snapshot_get_all_for_cgsnapshot.return_value = snapshots
+        model_update, snapshots = self.driver.delete_cgsnapshot(ctxt, cgsnap)
+        self.assertFalse(self.driver.delete_snapshot.called)
+        self.assertEqual({'status': cgsnap['status']}, model_update)
+        self.driver.db.snapshot_get_all_for_cgsnapshot.\
+            assert_called_once_with(ctxt, cgsnap['id'])
+
+    def test_local_path_volume_not_in_cg(self):
+        volume = self._fake_volume()
+        volume['consistencygroup_id'] = None
+        volume_path = os.path.join(
+            self.driver.configuration.gpfs_mount_point_base,
+            volume['name']
+        )
+        ret = self.driver.local_path(volume)
+        self.assertEqual(ret, volume_path)
+
+    def test_local_path_volume_in_cg(self):
+        volume = self._fake_volume()
+        cgname = "consisgroup-%s" % volume['consistencygroup_id']
+        volume_path = os.path.join(
+            self.driver.configuration.gpfs_mount_point_base,
+            cgname,
+            volume['name']
+        )
+        ret = self.driver.local_path(volume)
+        self.assertEqual(ret, volume_path)
+
+    @patch('cinder.context.get_admin_context')
+    @patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.local_path')
+    def test_get_snapshot_path(self, mock_local_path, mock_admin_context):
+        volume = self._fake_volume()
+        self.driver.db = mock.Mock()
+        self.driver.db.volume_get = mock.Mock()
+        self.driver.db.volume_get.return_value = volume
+        volume_path = self.volumes_path
+        mock_local_path.return_value = volume_path
+        snapshot = self._fake_snapshot()
+        ret = self.driver._get_snapshot_path(snapshot)
+        self.assertEqual(
+            ret, os.path.join(os.path.dirname(volume_path), snapshot['name'])
+        )
+
+    @patch('cinder.utils.execute')
+    def test_gpfs_full_copy(self, mock_exec):
+        src = "/tmp/vol1"
+        dest = "/tmp/vol2"
+        self.driver._gpfs_full_copy(src, dest)
+        mock_exec.assert_called_once_with('cp', src, dest,
+                                          check_exit_code=True)
+
+    def _fake_volume(self):
+        volume = {}
+        volume['id'] = '123456'
+        volume['name'] = 'test'
+        volume['size'] = 1000
+        volume['consistencygroup_id'] = 'cg-1234'
+        return volume
+
+    def _fake_snapshot(self):
+        snapshot = {}
+        snapshot['id'] = '12345'
+        snapshot['name'] = 'test-snap'
+        snapshot['size'] = 1000
+        snapshot['volume_id'] = '123456'
+        snapshot['status'] = 'available'
+        return snapshot
+
+    def _fake_volume_in_cg(self):
+        volume = {}
+        volume['id'] = '123456'
+        volume['name'] = 'test'
+        volume['size'] = 1000
+        volume['consistencygroup_id'] = 'fakecg'
+        return volume
+
+    def _fake_group(self):
+        group = {}
+        group['name'] = 'test_group'
+        group['id'] = '123456'
+        return group
+
+    def _fake_cgsnapshot(self):
+        cgsnap = {}
+        cgsnap['id'] = '123456'
+        cgsnap['name'] = 'testsnap'
+        cgsnap['consistencygroup_id'] = '123456'
+        cgsnap['status'] = 'available'
+        return cgsnap
+
     def _fake_qemu_qcow2_image_info(self, path):
         data = FakeQemuImgInfo()
         data.file_format = 'qcow2'
@@ -1543,9 +1749,7 @@ class GPFSDriverTestCase(test.TestCase):
                                                       old_type_ref['id'],
                                                       new_type_ref['id'])
 
-        volume = {}
-        volume['name'] = 'test'
+        volume = self._fake_volume()
         volume['host'] = host
-        volume['id'] = '123456'
 
         return (volume, new_type, diff, host)
index 7d8de1e0074b8ced6d0c2a75e2b8851458c32933..b4e6941dfe4c81e1cc6a8ce73583d9a98c71d6f9 100644 (file)
@@ -24,7 +24,9 @@ import shutil
 from oslo_concurrency import processutils
 from oslo_config import cfg
 from oslo_utils import units
+import six
 
+from cinder import context
 from cinder import exception
 from cinder.i18n import _, _LE, _LI
 from cinder.image import image_utils
@@ -106,9 +108,10 @@ class GPFSDriver(driver.VolumeDriver):
     Version history:
     1.0.0 - Initial driver
     1.1.0 - Add volume retype, refactor volume migration
+    1.2.0 - Add consistency group support
     """
 
-    VERSION = "1.1.0"
+    VERSION = "1.2.0"
 
     def __init__(self, *args, **kwargs):
         super(GPFSDriver, self).__init__(*args, **kwargs)
@@ -507,11 +510,25 @@ class GPFSDriver(driver.VolumeDriver):
     def create_volume_from_snapshot(self, volume, snapshot):
         """Creates a GPFS volume from a snapshot."""
 
+        snapshot_path = self._get_snapshot_path(snapshot)
+        # check if the snapshot lies in the same CG as the volume to be created
+        # if yes, clone the volume from the snapshot, else perform full copy
+        clone = False
+        if volume['consistencygroup_id'] is not None:
+            ctxt = context.get_admin_context()
+            snap_parent_vol = self.db.volume_get(ctxt, snapshot['volume_id'])
+            if (volume['consistencygroup_id'] ==
+                    snap_parent_vol['consistencygroup_id']):
+                clone = True
+
         volume_path = self.local_path(volume)
-        snapshot_path = self.local_path(snapshot)
-        self._create_gpfs_copy(src=snapshot_path, dest=volume_path)
+        if clone:
+            self._create_gpfs_copy(src=snapshot_path, dest=volume_path)
+            self._gpfs_redirect(volume_path)
+        else:
+            self._gpfs_full_copy(snapshot_path, volume_path)
+
         self._set_rw_permission(volume_path)
-        self._gpfs_redirect(volume_path)
         v_metadata = volume.get('volume_metadata')
         self._set_volume_attributes(volume, volume_path, v_metadata)
         virt_size = self._resize_volume_file(volume, volume['size'])
@@ -522,7 +539,11 @@ class GPFSDriver(driver.VolumeDriver):
 
         src = self.local_path(src_vref)
         dest = self.local_path(volume)
-        self._create_gpfs_clone(src, dest)
+        if (volume['consistencygroup_id'] == src_vref['consistencygroup_id']):
+            self._create_gpfs_clone(src, dest)
+        else:
+            self._gpfs_full_copy(src, dest)
+
         self._set_rw_permission(dest)
         v_metadata = volume.get('volume_metadata')
         self._set_volume_attributes(volume, dest, v_metadata)
@@ -602,6 +623,11 @@ class GPFSDriver(driver.VolumeDriver):
         """Create a GPFS file clone copy for the specified file."""
         self._execute('mmclone', 'copy', src, dest, run_as_root=True)
 
+    def _gpfs_full_copy(self, src, dest):
+        """Create a full copy from src to dest."""
+        self._execute('cp', src, dest,
+                      check_exit_code=True, run_as_root=True)
+
     def _create_gpfs_snap(self, src, dest=None):
         """Create a GPFS file clone snapshot for the specified file."""
         if dest is None:
@@ -618,8 +644,8 @@ class GPFSDriver(driver.VolumeDriver):
 
     def create_snapshot(self, snapshot):
         """Creates a GPFS snapshot."""
-        snapshot_path = self.local_path(snapshot)
-        volume_path = os.path.join(self.configuration.gpfs_mount_point_base,
+        snapshot_path = self._get_snapshot_path(snapshot)
+        volume_path = os.path.join(os.path.dirname(snapshot_path),
                                    snapshot['volume_name'])
         self._create_gpfs_snap(src=volume_path, dest=snapshot_path)
         self._set_rw_permission(snapshot_path, modebits='640')
@@ -632,16 +658,37 @@ class GPFSDriver(driver.VolumeDriver):
         # clone children, the delete will fail silently. When volumes that
         # are clone children are deleted in the future, the remaining ts
         # snapshots will also be deleted.
-        snapshot_path = self.local_path(snapshot)
+        snapshot_path = self._get_snapshot_path(snapshot)
         snapshot_ts_path = '%s.ts' % snapshot_path
         self._execute('mv', snapshot_path, snapshot_ts_path, run_as_root=True)
         self._execute('rm', '-f', snapshot_ts_path,
                       check_exit_code=False, run_as_root=True)
 
+    def _get_snapshot_path(self, snapshot):
+        ctxt = context.get_admin_context()
+        snap_parent_vol = self.db.volume_get(ctxt, snapshot['volume_id'])
+        snap_parent_vol_path = self.local_path(snap_parent_vol)
+        snapshot_path = os.path.join(os.path.dirname(snap_parent_vol_path),
+                                     snapshot['name'])
+        return snapshot_path
+
     def local_path(self, volume):
         """Return the local path for the specified volume."""
-        return os.path.join(self.configuration.gpfs_mount_point_base,
-                            volume['name'])
+        # Check if the volume is part of a consistency group and return
+        # the local_path accordingly.
+        if volume['consistencygroup_id'] is not None:
+            cgname = "consisgroup-%s" % volume['consistencygroup_id']
+            volume_path = os.path.join(
+                self.configuration.gpfs_mount_point_base,
+                cgname,
+                volume['name']
+            )
+        else:
+            volume_path = os.path.join(
+                self.configuration.gpfs_mount_point_base,
+                volume['name']
+            )
+        return volume_path
 
     def ensure_export(self, context, volume):
         """Synchronously recreates an export for a logical volume."""
@@ -700,7 +747,7 @@ class GPFSDriver(driver.VolumeDriver):
                                  {'cluster_id': self._cluster_id,
                                   'root_path': gpfs_base})
 
-        data['reserved_percentage'] = 0
+        data['consistencygroup_support'] = 'True'
         self._stats = data
 
     def clone_image(self, context, volume,
@@ -989,3 +1036,106 @@ class GPFSDriver(driver.VolumeDriver):
                      'file system is mounted.') % path)
             LOG.error(msg)
             raise exception.VolumeBackendAPIException(data=msg)
+
+    def create_consistencygroup(self, context, group):
+        """Create consistency group of GPFS volumes."""
+        cgname = "consisgroup-%s" % group['id']
+        fsdev = self._gpfs_device
+        cgpath = os.path.join(self.configuration.gpfs_mount_point_base,
+                              cgname)
+        try:
+            self._execute('mmcrfileset', fsdev, cgname,
+                          '--inode-space', 'new', run_as_root=True)
+        except processutils.ProcessExecutionError as e:
+            msg = (_('Failed to create consistency group: %(cgid)s. '
+                     'Error: %(excmsg)s.') %
+                   {'cgid': group['id'], 'excmsg': six.text_type(e)})
+            LOG.error(msg)
+            raise exception.VolumeBackendAPIException(data=msg)
+
+        try:
+            self._execute('mmlinkfileset', fsdev, cgname,
+                          '-J', cgpath, run_as_root=True)
+        except processutils.ProcessExecutionError as e:
+            msg = (_('Failed to link fileset for the share %(cgname)s. '
+                     'Error: %(excmsg)s.') %
+                   {'cgname': cgname, 'excmsg': six.text_type(e)})
+            LOG.error(msg)
+            raise exception.VolumeBackendAPIException(data=msg)
+
+        try:
+            self._execute('chmod', '770', cgpath, run_as_root=True)
+        except processutils.ProcessExecutionError as e:
+            msg = (_('Failed to set permissions for the consistency group '
+                     '%(cgname)s. '
+                     'Error: %(excmsg)s.') %
+                   {'cgname': cgname, 'excmsg': six.text_type(e)})
+            LOG.error(msg)
+            raise exception.VolumeBackendAPIException(data=msg)
+
+        model_update = {'status': 'available'}
+        return model_update
+
+    def delete_consistencygroup(self, context, group):
+        """Delete consistency group of GPFS volumes."""
+        cgname = "consisgroup-%s" % group['id']
+        fsdev = self._gpfs_device
+
+        model_update = {}
+        model_update['status'] = group['status']
+        volumes = self.db.volume_get_all_by_group(context, group['id'])
+
+        # Unlink and delete the fileset associated with the consistency group.
+        # All of the volumes and volume snapshot data will also be deleted.
+        try:
+            self._execute('mmunlinkfileset', fsdev, cgname, '-f',
+                          run_as_root=True)
+        except processutils.ProcessExecutionError as e:
+            msg = (_('Failed to unlink fileset for consistency group '
+                     '%(cgname)s. Error: %(excmsg)s.') %
+                   {'cgname': cgname, 'excmsg': six.text_type(e)})
+            LOG.error(msg)
+            raise exception.VolumeBackendAPIException(data=msg)
+
+        try:
+            self._execute('mmdelfileset', fsdev, cgname, '-f',
+                          run_as_root=True)
+        except processutils.ProcessExecutionError as e:
+            msg = (_('Failed to delete fileset for consistency group '
+                     '%(cgname)s. Error: %(excmsg)s.') %
+                   {'cgname': cgname, 'excmsg': six.text_type(e)})
+            LOG.error(msg)
+            raise exception.VolumeBackendAPIException(data=msg)
+
+        for volume_ref in volumes:
+            volume_ref['status'] = 'deleted'
+
+        model_update = {'status': group['status']}
+
+        return model_update, volumes
+
+    def create_cgsnapshot(self, context, cgsnapshot):
+        """Create snapshot of a consistency group of GPFS volumes."""
+        snapshots = self.db.snapshot_get_all_for_cgsnapshot(
+            context, cgsnapshot['id'])
+
+        for snapshot in snapshots:
+            self.create_snapshot(snapshot)
+            snapshot['status'] = 'available'
+
+        model_update = {'status': 'available'}
+
+        return model_update, snapshots
+
+    def delete_cgsnapshot(self, context, cgsnapshot):
+        """Delete snapshot of a consistency group of GPFS volumes."""
+        snapshots = self.db.snapshot_get_all_for_cgsnapshot(
+            context, cgsnapshot['id'])
+
+        for snapshot in snapshots:
+            self.delete_snapshot(snapshot)
+            snapshot['status'] = 'deleted'
+
+        model_update = {'status': cgsnapshot['status']}
+
+        return model_update, snapshots
index 05c8d23aa4a431729d5f96680b8fda9371894a6e..36a037bb97b578898f8d97f92f07c381e9ef7a41 100644 (file)
@@ -122,6 +122,7 @@ systool: CommandFilter, systool, root
 blockdev: CommandFilter, blockdev, root
 
 # cinder/volume/drivers/ibm/gpfs.py
+cp: CommandFilter, cp, root
 mv: CommandFilter, mv, root
 mmgetstate: CommandFilter, /usr/lpp/mmfs/bin/mmgetstate, root
 mmclone: CommandFilter, /usr/lpp/mmfs/bin/mmclone, root
@@ -131,6 +132,12 @@ mmlsconfig: CommandFilter, /usr/lpp/mmfs/bin/mmlsconfig, root
 mmlsfs: CommandFilter, /usr/lpp/mmfs/bin/mmlsfs, root
 mmlspool: CommandFilter, /usr/lpp/mmfs/bin/mmlspool, root
 mkfs: CommandFilter, mkfs, root
+mmcrfileset: CommandFilter, /usr/lpp/mmfs/bin/mmcrfileset, root
+mmlinkfileset: CommandFilter, /usr/lpp/mmfs/bin/mmlinkfileset, root
+mmunlinkfileset: CommandFilter, /usr/lpp/mmfs/bin/mmunlinkfileset, root
+mmdelfileset: CommandFilter, /usr/lpp/mmfs/bin/mmdelfileset, root
+mmcrsnapshot: CommandFilter, /usr/lpp/mmfs/bin/mmcrsnapshot, root
+mmdelsnapshot: CommandFilter, /usr/lpp/mmfs/bin/mmdelsnapshot, root
 
 # cinder/volume/drivers/ibm/gpfs.py
 # cinder/volume/drivers/ibm/ibmnas.py