From 7ec7bd06756690dad79507e66d2d406bbc07cf55 Mon Sep 17 00:00:00 2001 From: Nilesh Bhosale Date: Thu, 25 Dec 2014 19:22:58 +0530 Subject: [PATCH] IBM GPFS Consistency Group Implementation Adding support for consistency groups in IBM GPFS driver. Change-Id: I308d224982301eb42e75e68b4e3c689ee568fcd6 Implements: blueprint ibm-gpfs-consistency-group --- cinder/tests/test_gpfs.py | 346 +++++++++++++++++++++------ cinder/volume/drivers/ibm/gpfs.py | 172 ++++++++++++- etc/cinder/rootwrap.d/volume.filters | 7 + 3 files changed, 443 insertions(+), 82 deletions(-) diff --git a/cinder/tests/test_gpfs.py b/cinder/tests/test_gpfs.py index cea131316..f88cefc56 100644 --- a/cinder/tests/test_gpfs.py +++ b/cinder/tests/test_gpfs.py @@ -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) diff --git a/cinder/volume/drivers/ibm/gpfs.py b/cinder/volume/drivers/ibm/gpfs.py index 7d8de1e00..b4e6941df 100644 --- a/cinder/volume/drivers/ibm/gpfs.py +++ b/cinder/volume/drivers/ibm/gpfs.py @@ -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 diff --git a/etc/cinder/rootwrap.d/volume.filters b/etc/cinder/rootwrap.d/volume.filters index 05c8d23aa..36a037bb9 100644 --- a/etc/cinder/rootwrap.d/volume.filters +++ b/etc/cinder/rootwrap.d/volume.filters @@ -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 -- 2.45.2