diff --git a/full_quobyte_patch/Ocata/full_quobyte_ocata_cinder.patch b/full_quobyte_patch/Ocata/full_quobyte_ocata_cinder.patch index 03622b8..a2d70c5 100644 --- a/full_quobyte_patch/Ocata/full_quobyte_ocata_cinder.patch +++ b/full_quobyte_patch/Ocata/full_quobyte_ocata_cinder.patch @@ -1,372 +1,3 @@ -diff --git cinder/tests/unit/volume/drivers/test_quobyte.py cinder/tests/unit/volume/drivers/test_quobyte.py -index de5169c..20a8973 100644 ---- cinder/tests/unit/volume/drivers/test_quobyte.py -+++ cinder/tests/unit/volume/drivers/test_quobyte.py -@@ -17,6 +17,7 @@ - - import errno - import os -+import psutil - import six - import traceback - -@@ -100,6 +101,12 @@ class QuobyteDriverTestCase(test.TestCase): - if not caught: - self.fail('Expected raised exception but nothing caught.') - -+ def get_mock_partitions(self): -+ mypart = mock.Mock() -+ mypart.device = "quobyte@" -+ mypart.mountpoint = self.TEST_MNT_POINT -+ return [mypart] -+ - def test_local_path(self): - """local_path common use case.""" - drv = self._driver -@@ -113,7 +120,9 @@ class QuobyteDriverTestCase(test.TestCase): - def test_mount_quobyte_should_mount_correctly(self): - with mock.patch.object(self._driver, '_execute') as mock_execute, \ - mock.patch('cinder.volume.drivers.quobyte.QuobyteDriver' -- '.read_proc_mount') as mock_open: -+ '.read_proc_mount') as mock_open, \ -+ mock.patch('cinder.volume.drivers.quobyte.QuobyteDriver' -+ '._validate_volume') as mock_validate: - # Content of /proc/mount (not mounted yet). - mock_open.return_value = six.StringIO( - "/dev/sda5 / ext4 rw,relatime,data=ordered 0 0") -@@ -127,17 +136,15 @@ class QuobyteDriverTestCase(test.TestCase): - 'mount.quobyte', self.TEST_QUOBYTE_VOLUME, - self.TEST_MNT_POINT, run_as_root=False) - -- getfattr_call = mock.call( -- 'getfattr', '-n', 'quobyte.info', self.TEST_MNT_POINT, -- run_as_root=False) -- - mock_execute.assert_has_calls( -- [mkdir_call, mount_call, getfattr_call], any_order=False) -+ [mkdir_call, mount_call], any_order=False) -+ mock_validate.called_once_with(self.TEST_MNT_POINT) - - def test_mount_quobyte_already_mounted_detected_seen_in_proc_mount(self): -- with mock.patch.object(self._driver, '_execute') as mock_execute, \ -+ with mock.patch('cinder.volume.drivers.quobyte.QuobyteDriver' -+ '.read_proc_mount') as mock_open, \ - mock.patch('cinder.volume.drivers.quobyte.QuobyteDriver' -- '.read_proc_mount') as mock_open: -+ '._validate_volume') as mock_validate: - # Content of /proc/mount (already mounted). - mock_open.return_value = six.StringIO( - "quobyte@%s %s fuse rw,nosuid,nodev,noatime,user_id=1000" -@@ -146,10 +153,7 @@ class QuobyteDriverTestCase(test.TestCase): - - self._driver._mount_quobyte(self.TEST_QUOBYTE_VOLUME, - self.TEST_MNT_POINT) -- -- mock_execute.assert_called_once_with( -- 'getfattr', '-n', 'quobyte.info', self.TEST_MNT_POINT, -- run_as_root=False) -+ mock_validate.assert_called_once_with(self.TEST_MNT_POINT) - - def test_mount_quobyte_should_suppress_and_log_already_mounted_error(self): - """test_mount_quobyte_should_suppress_and_log_already_mounted_error -@@ -604,13 +608,11 @@ class QuobyteDriverTestCase(test.TestCase): - - img_info = imageutils.QemuImgInfo(qemu_img_info_output) - -- drv.get_active_image_from_info = mock.Mock(return_value=volume['name']) - image_utils.qemu_img_info = mock.Mock(return_value=img_info) - image_utils.resize_image = mock.Mock() - - drv.extend_volume(volume, 3) - -- drv.get_active_image_from_info.assert_called_once_with(volume) - image_utils.qemu_img_info.assert_called_once_with(volume_path, - run_as_root=False) - image_utils.resize_image.assert_called_once_with(volume_path, 3) -@@ -928,3 +930,84 @@ class QuobyteDriverTestCase(test.TestCase): - self.assertEqual("true", - drv.configuration.nas_secure_file_permissions) - self.assertFalse(drv._execute_as_root) -+ -+ @mock.patch.object(psutil, "disk_partitions") -+ @mock.patch.object(os, "stat") -+ def test_validate_volume_all_good(self, stat_mock, part_mock): -+ part_mock.return_value = self.get_mock_partitions() -+ drv = self._driver -+ -+ def statMockCall(*args): -+ if args[0] == self.TEST_MNT_POINT: -+ stat_result = mock.Mock() -+ stat_result.st_size = 0 -+ return stat_result -+ return os.stat(args) -+ -+ stat_mock.side_effect = statMockCall -+ -+ drv._validate_volume(self.TEST_MNT_POINT) -+ -+ stat_mock.assert_called_once_with(self.TEST_MNT_POINT) -+ part_mock.assert_called_once_with(all=True) -+ -+ @mock.patch.object(psutil, "disk_partitions") -+ @mock.patch.object(os, "stat") -+ def test_validate_volume_mount_not_working(self, stat_mock, part_mock): -+ part_mock.return_value = self.get_mock_partitions() -+ drv = self._driver -+ -+ def statMockCall(*args): -+ if args[0] == self.TEST_MNT_POINT: -+ raise exception.VolumeDriverException() -+ -+ stat_mock.side_effect = [statMockCall, os.stat] -+ -+ self.assertRaises( -+ exception.VolumeDriverException, -+ drv._validate_volume, -+ self.TEST_MNT_POINT) -+ stat_mock.assert_called_once_with(self.TEST_MNT_POINT) -+ part_mock.assert_called_once_with(all=True) -+ -+ def test_validate_volume_no_mtab_entry(self): -+ msg = ("Volume driver reported an error: " -+ "No matching Quobyte mount entry for %(mpt)s" -+ " could be found for validation in partition list." -+ % {'mpt': self.TEST_MNT_POINT}) -+ -+ self.assertRaisesAndMessageMatches( -+ exception.VolumeDriverException, -+ msg, -+ self._driver._validate_volume, -+ self.TEST_MNT_POINT) -+ -+ @mock.patch.object(psutil, "disk_partitions") -+ def test_validate_volume_wrong_mount_type(self, part_mock): -+ mypart = mock.Mock() -+ mypart.device = "not-quobyte" -+ mypart.mountpoint = self.TEST_MNT_POINT -+ part_mock.return_value = [mypart] -+ msg = ("Volume driver reported an error: " -+ "The mount %(mpt)s is not a valid" -+ " Quobyte volume according to partition list." -+ % {'mpt': self.TEST_MNT_POINT}) -+ drv = self._driver -+ -+ self.assertRaisesAndMessageMatches( -+ exception.VolumeDriverException, -+ msg, -+ drv._validate_volume, -+ self.TEST_MNT_POINT) -+ part_mock.assert_called_once_with(all=True) -+ -+ @mock.patch.object(psutil, "disk_partitions") -+ def test_validate_volume_stale_mount(self, part_mock): -+ part_mock.return_value = self.get_mock_partitions() -+ drv = self._driver -+ -+ # As this uses a local fs dir size is >0, raising an exception -+ self.assertRaises( -+ exception.VolumeDriverException, -+ drv._validate_volume, -+ self.TEST_MNT_POINT) -diff --git cinder/tests/unit/volume/drivers/test_remotefs.py cinder/tests/unit/volume/drivers/test_remotefs.py -index 0cd2ad8..72d70f4 100644 ---- cinder/tests/unit/volume/drivers/test_remotefs.py -+++ cinder/tests/unit/volume/drivers/test_remotefs.py -@@ -52,9 +52,26 @@ class RemoteFsSnapDriverTestCase(test.TestCase): - self._fake_snapshot.id) - self._fake_snapshot.volume = self._fake_volume - -+ @ddt.data({'current_state': 'in-use', -+ 'acceptable_states': ['available', 'in-use']}, -+ {'current_state': 'in-use', -+ 'acceptable_states': ['available'], -+ 'expected_exception': exception.InvalidVolume}) -+ @ddt.unpack -+ def test_validate_state(self, current_state, acceptable_states, -+ expected_exception=None): -+ if expected_exception: -+ self.assertRaises(expected_exception, -+ self._driver._validate_state, -+ current_state, -+ acceptable_states) -+ else: -+ self._driver._validate_state(current_state, acceptable_states) -+ - def _test_delete_snapshot(self, volume_in_use=False, - stale_snapshot=False, -- is_active_image=True): -+ is_active_image=True, -+ is_tmp_snap=False): - # If the snapshot is not the active image, it is guaranteed that - # another snapshot exists having it as backing file. - -@@ -78,6 +95,7 @@ class RemoteFsSnapDriverTestCase(test.TestCase): - self._driver._local_volume_dir = mock.Mock( - return_value=self._FAKE_MNT_POINT) - -+ self._driver._validate_state = mock.Mock() - self._driver._read_info_file = mock.Mock() - self._driver._write_info_file = mock.Mock() - self._driver._img_commit = mock.Mock() -@@ -91,12 +109,18 @@ class RemoteFsSnapDriverTestCase(test.TestCase): - self._fake_snapshot.id: fake_snapshot_name - } - -+ exp_acceptable_states = ['available', 'in-use', 'backing-up', -+ 'deleting', 'downloading'] -+ - if volume_in_use: - self._fake_snapshot.volume.status = 'in-use' - - self._driver._read_info_file.return_value = fake_info - - self._driver._delete_snapshot(self._fake_snapshot) -+ self._driver._validate_state.assert_called_once_with( -+ self._fake_snapshot.volume.status, -+ exp_acceptable_states) - if stale_snapshot: - self._driver._delete_stale_snapshot.assert_called_once_with( - self._fake_snapshot) -@@ -228,7 +252,7 @@ class RemoteFsSnapDriverTestCase(test.TestCase): - mock.call(*command3, run_as_root=True)] - self._driver._execute.assert_has_calls(calls) - -- def _test_create_snapshot(self, volume_in_use=False): -+ def _test_create_snapshot(self, volume_in_use=False, tmp_snap=False): - fake_snapshot_info = {} - fake_snapshot_file_name = os.path.basename(self._fake_snapshot_path) - -@@ -243,11 +267,16 @@ class RemoteFsSnapDriverTestCase(test.TestCase): - return_value=self._fake_volume.name) - self._driver._get_new_snap_path = mock.Mock( - return_value=self._fake_snapshot_path) -+ self._driver._validate_state = mock.Mock() - - expected_snapshot_info = { - 'active': fake_snapshot_file_name, - self._fake_snapshot.id: fake_snapshot_file_name - } -+ exp_acceptable_states = ['available', 'in-use', 'backing-up'] -+ if tmp_snap: -+ exp_acceptable_states.append('downloading') -+ self._fake_snapshot.id = 'tmp-snap-%s' % self._fake_snapshot.id - - if volume_in_use: - self._fake_snapshot.volume.status = 'in-use' -@@ -258,6 +287,9 @@ class RemoteFsSnapDriverTestCase(test.TestCase): - - self._driver._create_snapshot(self._fake_snapshot) - -+ self._driver._validate_state.assert_called_once_with( -+ self._fake_snapshot.volume.status, -+ exp_acceptable_states) - fake_method = getattr(self._driver, expected_method_called) - fake_method.assert_called_with( - self._fake_snapshot, self._fake_volume.name, -@@ -428,52 +460,59 @@ class RemoteFsSnapDriverTestCase(test.TestCase): - basedir=basedir, - valid_backing_file=False) - -- def test_create_cloned_volume(self): -+ @mock.patch.object(remotefs.RemoteFSSnapDriver, -+ '_validate_state') -+ @mock.patch.object(remotefs.RemoteFSSnapDriver, '_create_snapshot') -+ @mock.patch.object(remotefs.RemoteFSSnapDriver, '_delete_snapshot') -+ @mock.patch.object(remotefs.RemoteFSSnapDriver, -+ '_copy_volume_from_snapshot') -+ def test_create_cloned_volume(self, mock_copy_volume_from_snapshot, -+ mock_delete_snapshot, -+ mock_create_snapshot, -+ mock_validate_state): - drv = self._driver - -- with mock.patch.object(drv, '_create_snapshot') as \ -- mock_create_snapshot,\ -- mock.patch.object(drv, '_delete_snapshot') as \ -- mock_delete_snapshot,\ -- mock.patch.object(drv, '_copy_volume_from_snapshot') as \ -- mock_copy_volume_from_snapshot: -- -- volume = fake_volume.fake_volume_obj(self.context) -- src_vref_id = '375e32b2-804a-49f2-b282-85d1d5a5b9e1' -- src_vref = fake_volume.fake_volume_obj( -- self.context, -- id=src_vref_id, -- name='volume-%s' % src_vref_id) -- -- vol_attrs = ['provider_location', 'size', 'id', 'name', 'status', -- 'volume_type', 'metadata'] -- Volume = collections.namedtuple('Volume', vol_attrs) -- -- snap_attrs = ['volume_name', 'volume_size', 'name', -- 'volume_id', 'id', 'volume'] -- Snapshot = collections.namedtuple('Snapshot', snap_attrs) -- -- volume_ref = Volume(id=volume.id, -- name=volume.name, -- status=volume.status, -- provider_location=volume.provider_location, -- size=volume.size, -- volume_type=volume.volume_type, -- metadata=volume.metadata) -- -- snap_ref = Snapshot(volume_name=volume.name, -- name='clone-snap-%s' % src_vref.id, -- volume_size=src_vref.size, -- volume_id=src_vref.id, -- id='tmp-snap-%s' % src_vref.id, -- volume=src_vref) -- -- drv.create_cloned_volume(volume, src_vref) -- -- mock_create_snapshot.assert_called_once_with(snap_ref) -- mock_copy_volume_from_snapshot.assert_called_once_with( -- snap_ref, volume_ref, volume['size']) -- self.assertTrue(mock_delete_snapshot.called) -+ volume = fake_volume.fake_volume_obj(self.context) -+ src_vref_id = '375e32b2-804a-49f2-b282-85d1d5a5b9e1' -+ src_vref = fake_volume.fake_volume_obj( -+ self.context, -+ id=src_vref_id, -+ name='volume-%s' % src_vref_id) -+ -+ vol_attrs = ['provider_location', 'size', 'id', 'name', 'status', -+ 'volume_type', 'metadata'] -+ Volume = collections.namedtuple('Volume', vol_attrs) -+ -+ snap_attrs = ['volume_name', 'volume_size', 'name', -+ 'volume_id', 'id', 'volume'] -+ Snapshot = collections.namedtuple('Snapshot', snap_attrs) -+ -+ volume_ref = Volume(id=volume.id, -+ name=volume.name, -+ status=volume.status, -+ provider_location=volume.provider_location, -+ size=volume.size, -+ volume_type=volume.volume_type, -+ metadata=volume.metadata) -+ -+ snap_ref = Snapshot(volume_name=volume.name, -+ name='clone-snap-%s' % src_vref.id, -+ volume_size=src_vref.size, -+ volume_id=src_vref.id, -+ id='tmp-snap-%s' % src_vref.id, -+ volume=src_vref) -+ -+ drv.create_cloned_volume(volume, src_vref) -+ -+ exp_acceptable_states = ['available', 'backing-up', 'downloading'] -+ mock_validate_state.assert_called_once_with( -+ src_vref.status, -+ exp_acceptable_states, -+ obj_description='source volume') -+ mock_create_snapshot.assert_called_once_with(snap_ref) -+ mock_copy_volume_from_snapshot.assert_called_once_with( -+ snap_ref, volume_ref, volume['size']) -+ self.assertTrue(mock_delete_snapshot.called) - - def test_create_regular_file(self): - self._driver._create_regular_file('/path', 1) diff --git cinder/volume/drivers/quobyte.py cinder/volume/drivers/quobyte.py index 8d77e65..32d4021 100644 --- cinder/volume/drivers/quobyte.py diff --git a/full_quobyte_patch/Ocata/full_quobyte_ocata_cinder_full_source_tree.patch b/full_quobyte_patch/Ocata/full_quobyte_ocata_cinder_full_source_tree.patch new file mode 100644 index 0000000..03622b8 --- /dev/null +++ b/full_quobyte_patch/Ocata/full_quobyte_ocata_cinder_full_source_tree.patch @@ -0,0 +1,583 @@ +diff --git cinder/tests/unit/volume/drivers/test_quobyte.py cinder/tests/unit/volume/drivers/test_quobyte.py +index de5169c..20a8973 100644 +--- cinder/tests/unit/volume/drivers/test_quobyte.py ++++ cinder/tests/unit/volume/drivers/test_quobyte.py +@@ -17,6 +17,7 @@ + + import errno + import os ++import psutil + import six + import traceback + +@@ -100,6 +101,12 @@ class QuobyteDriverTestCase(test.TestCase): + if not caught: + self.fail('Expected raised exception but nothing caught.') + ++ def get_mock_partitions(self): ++ mypart = mock.Mock() ++ mypart.device = "quobyte@" ++ mypart.mountpoint = self.TEST_MNT_POINT ++ return [mypart] ++ + def test_local_path(self): + """local_path common use case.""" + drv = self._driver +@@ -113,7 +120,9 @@ class QuobyteDriverTestCase(test.TestCase): + def test_mount_quobyte_should_mount_correctly(self): + with mock.patch.object(self._driver, '_execute') as mock_execute, \ + mock.patch('cinder.volume.drivers.quobyte.QuobyteDriver' +- '.read_proc_mount') as mock_open: ++ '.read_proc_mount') as mock_open, \ ++ mock.patch('cinder.volume.drivers.quobyte.QuobyteDriver' ++ '._validate_volume') as mock_validate: + # Content of /proc/mount (not mounted yet). + mock_open.return_value = six.StringIO( + "/dev/sda5 / ext4 rw,relatime,data=ordered 0 0") +@@ -127,17 +136,15 @@ class QuobyteDriverTestCase(test.TestCase): + 'mount.quobyte', self.TEST_QUOBYTE_VOLUME, + self.TEST_MNT_POINT, run_as_root=False) + +- getfattr_call = mock.call( +- 'getfattr', '-n', 'quobyte.info', self.TEST_MNT_POINT, +- run_as_root=False) +- + mock_execute.assert_has_calls( +- [mkdir_call, mount_call, getfattr_call], any_order=False) ++ [mkdir_call, mount_call], any_order=False) ++ mock_validate.called_once_with(self.TEST_MNT_POINT) + + def test_mount_quobyte_already_mounted_detected_seen_in_proc_mount(self): +- with mock.patch.object(self._driver, '_execute') as mock_execute, \ ++ with mock.patch('cinder.volume.drivers.quobyte.QuobyteDriver' ++ '.read_proc_mount') as mock_open, \ + mock.patch('cinder.volume.drivers.quobyte.QuobyteDriver' +- '.read_proc_mount') as mock_open: ++ '._validate_volume') as mock_validate: + # Content of /proc/mount (already mounted). + mock_open.return_value = six.StringIO( + "quobyte@%s %s fuse rw,nosuid,nodev,noatime,user_id=1000" +@@ -146,10 +153,7 @@ class QuobyteDriverTestCase(test.TestCase): + + self._driver._mount_quobyte(self.TEST_QUOBYTE_VOLUME, + self.TEST_MNT_POINT) +- +- mock_execute.assert_called_once_with( +- 'getfattr', '-n', 'quobyte.info', self.TEST_MNT_POINT, +- run_as_root=False) ++ mock_validate.assert_called_once_with(self.TEST_MNT_POINT) + + def test_mount_quobyte_should_suppress_and_log_already_mounted_error(self): + """test_mount_quobyte_should_suppress_and_log_already_mounted_error +@@ -604,13 +608,11 @@ class QuobyteDriverTestCase(test.TestCase): + + img_info = imageutils.QemuImgInfo(qemu_img_info_output) + +- drv.get_active_image_from_info = mock.Mock(return_value=volume['name']) + image_utils.qemu_img_info = mock.Mock(return_value=img_info) + image_utils.resize_image = mock.Mock() + + drv.extend_volume(volume, 3) + +- drv.get_active_image_from_info.assert_called_once_with(volume) + image_utils.qemu_img_info.assert_called_once_with(volume_path, + run_as_root=False) + image_utils.resize_image.assert_called_once_with(volume_path, 3) +@@ -928,3 +930,84 @@ class QuobyteDriverTestCase(test.TestCase): + self.assertEqual("true", + drv.configuration.nas_secure_file_permissions) + self.assertFalse(drv._execute_as_root) ++ ++ @mock.patch.object(psutil, "disk_partitions") ++ @mock.patch.object(os, "stat") ++ def test_validate_volume_all_good(self, stat_mock, part_mock): ++ part_mock.return_value = self.get_mock_partitions() ++ drv = self._driver ++ ++ def statMockCall(*args): ++ if args[0] == self.TEST_MNT_POINT: ++ stat_result = mock.Mock() ++ stat_result.st_size = 0 ++ return stat_result ++ return os.stat(args) ++ ++ stat_mock.side_effect = statMockCall ++ ++ drv._validate_volume(self.TEST_MNT_POINT) ++ ++ stat_mock.assert_called_once_with(self.TEST_MNT_POINT) ++ part_mock.assert_called_once_with(all=True) ++ ++ @mock.patch.object(psutil, "disk_partitions") ++ @mock.patch.object(os, "stat") ++ def test_validate_volume_mount_not_working(self, stat_mock, part_mock): ++ part_mock.return_value = self.get_mock_partitions() ++ drv = self._driver ++ ++ def statMockCall(*args): ++ if args[0] == self.TEST_MNT_POINT: ++ raise exception.VolumeDriverException() ++ ++ stat_mock.side_effect = [statMockCall, os.stat] ++ ++ self.assertRaises( ++ exception.VolumeDriverException, ++ drv._validate_volume, ++ self.TEST_MNT_POINT) ++ stat_mock.assert_called_once_with(self.TEST_MNT_POINT) ++ part_mock.assert_called_once_with(all=True) ++ ++ def test_validate_volume_no_mtab_entry(self): ++ msg = ("Volume driver reported an error: " ++ "No matching Quobyte mount entry for %(mpt)s" ++ " could be found for validation in partition list." ++ % {'mpt': self.TEST_MNT_POINT}) ++ ++ self.assertRaisesAndMessageMatches( ++ exception.VolumeDriverException, ++ msg, ++ self._driver._validate_volume, ++ self.TEST_MNT_POINT) ++ ++ @mock.patch.object(psutil, "disk_partitions") ++ def test_validate_volume_wrong_mount_type(self, part_mock): ++ mypart = mock.Mock() ++ mypart.device = "not-quobyte" ++ mypart.mountpoint = self.TEST_MNT_POINT ++ part_mock.return_value = [mypart] ++ msg = ("Volume driver reported an error: " ++ "The mount %(mpt)s is not a valid" ++ " Quobyte volume according to partition list." ++ % {'mpt': self.TEST_MNT_POINT}) ++ drv = self._driver ++ ++ self.assertRaisesAndMessageMatches( ++ exception.VolumeDriverException, ++ msg, ++ drv._validate_volume, ++ self.TEST_MNT_POINT) ++ part_mock.assert_called_once_with(all=True) ++ ++ @mock.patch.object(psutil, "disk_partitions") ++ def test_validate_volume_stale_mount(self, part_mock): ++ part_mock.return_value = self.get_mock_partitions() ++ drv = self._driver ++ ++ # As this uses a local fs dir size is >0, raising an exception ++ self.assertRaises( ++ exception.VolumeDriverException, ++ drv._validate_volume, ++ self.TEST_MNT_POINT) +diff --git cinder/tests/unit/volume/drivers/test_remotefs.py cinder/tests/unit/volume/drivers/test_remotefs.py +index 0cd2ad8..72d70f4 100644 +--- cinder/tests/unit/volume/drivers/test_remotefs.py ++++ cinder/tests/unit/volume/drivers/test_remotefs.py +@@ -52,9 +52,26 @@ class RemoteFsSnapDriverTestCase(test.TestCase): + self._fake_snapshot.id) + self._fake_snapshot.volume = self._fake_volume + ++ @ddt.data({'current_state': 'in-use', ++ 'acceptable_states': ['available', 'in-use']}, ++ {'current_state': 'in-use', ++ 'acceptable_states': ['available'], ++ 'expected_exception': exception.InvalidVolume}) ++ @ddt.unpack ++ def test_validate_state(self, current_state, acceptable_states, ++ expected_exception=None): ++ if expected_exception: ++ self.assertRaises(expected_exception, ++ self._driver._validate_state, ++ current_state, ++ acceptable_states) ++ else: ++ self._driver._validate_state(current_state, acceptable_states) ++ + def _test_delete_snapshot(self, volume_in_use=False, + stale_snapshot=False, +- is_active_image=True): ++ is_active_image=True, ++ is_tmp_snap=False): + # If the snapshot is not the active image, it is guaranteed that + # another snapshot exists having it as backing file. + +@@ -78,6 +95,7 @@ class RemoteFsSnapDriverTestCase(test.TestCase): + self._driver._local_volume_dir = mock.Mock( + return_value=self._FAKE_MNT_POINT) + ++ self._driver._validate_state = mock.Mock() + self._driver._read_info_file = mock.Mock() + self._driver._write_info_file = mock.Mock() + self._driver._img_commit = mock.Mock() +@@ -91,12 +109,18 @@ class RemoteFsSnapDriverTestCase(test.TestCase): + self._fake_snapshot.id: fake_snapshot_name + } + ++ exp_acceptable_states = ['available', 'in-use', 'backing-up', ++ 'deleting', 'downloading'] ++ + if volume_in_use: + self._fake_snapshot.volume.status = 'in-use' + + self._driver._read_info_file.return_value = fake_info + + self._driver._delete_snapshot(self._fake_snapshot) ++ self._driver._validate_state.assert_called_once_with( ++ self._fake_snapshot.volume.status, ++ exp_acceptable_states) + if stale_snapshot: + self._driver._delete_stale_snapshot.assert_called_once_with( + self._fake_snapshot) +@@ -228,7 +252,7 @@ class RemoteFsSnapDriverTestCase(test.TestCase): + mock.call(*command3, run_as_root=True)] + self._driver._execute.assert_has_calls(calls) + +- def _test_create_snapshot(self, volume_in_use=False): ++ def _test_create_snapshot(self, volume_in_use=False, tmp_snap=False): + fake_snapshot_info = {} + fake_snapshot_file_name = os.path.basename(self._fake_snapshot_path) + +@@ -243,11 +267,16 @@ class RemoteFsSnapDriverTestCase(test.TestCase): + return_value=self._fake_volume.name) + self._driver._get_new_snap_path = mock.Mock( + return_value=self._fake_snapshot_path) ++ self._driver._validate_state = mock.Mock() + + expected_snapshot_info = { + 'active': fake_snapshot_file_name, + self._fake_snapshot.id: fake_snapshot_file_name + } ++ exp_acceptable_states = ['available', 'in-use', 'backing-up'] ++ if tmp_snap: ++ exp_acceptable_states.append('downloading') ++ self._fake_snapshot.id = 'tmp-snap-%s' % self._fake_snapshot.id + + if volume_in_use: + self._fake_snapshot.volume.status = 'in-use' +@@ -258,6 +287,9 @@ class RemoteFsSnapDriverTestCase(test.TestCase): + + self._driver._create_snapshot(self._fake_snapshot) + ++ self._driver._validate_state.assert_called_once_with( ++ self._fake_snapshot.volume.status, ++ exp_acceptable_states) + fake_method = getattr(self._driver, expected_method_called) + fake_method.assert_called_with( + self._fake_snapshot, self._fake_volume.name, +@@ -428,52 +460,59 @@ class RemoteFsSnapDriverTestCase(test.TestCase): + basedir=basedir, + valid_backing_file=False) + +- def test_create_cloned_volume(self): ++ @mock.patch.object(remotefs.RemoteFSSnapDriver, ++ '_validate_state') ++ @mock.patch.object(remotefs.RemoteFSSnapDriver, '_create_snapshot') ++ @mock.patch.object(remotefs.RemoteFSSnapDriver, '_delete_snapshot') ++ @mock.patch.object(remotefs.RemoteFSSnapDriver, ++ '_copy_volume_from_snapshot') ++ def test_create_cloned_volume(self, mock_copy_volume_from_snapshot, ++ mock_delete_snapshot, ++ mock_create_snapshot, ++ mock_validate_state): + drv = self._driver + +- with mock.patch.object(drv, '_create_snapshot') as \ +- mock_create_snapshot,\ +- mock.patch.object(drv, '_delete_snapshot') as \ +- mock_delete_snapshot,\ +- mock.patch.object(drv, '_copy_volume_from_snapshot') as \ +- mock_copy_volume_from_snapshot: +- +- volume = fake_volume.fake_volume_obj(self.context) +- src_vref_id = '375e32b2-804a-49f2-b282-85d1d5a5b9e1' +- src_vref = fake_volume.fake_volume_obj( +- self.context, +- id=src_vref_id, +- name='volume-%s' % src_vref_id) +- +- vol_attrs = ['provider_location', 'size', 'id', 'name', 'status', +- 'volume_type', 'metadata'] +- Volume = collections.namedtuple('Volume', vol_attrs) +- +- snap_attrs = ['volume_name', 'volume_size', 'name', +- 'volume_id', 'id', 'volume'] +- Snapshot = collections.namedtuple('Snapshot', snap_attrs) +- +- volume_ref = Volume(id=volume.id, +- name=volume.name, +- status=volume.status, +- provider_location=volume.provider_location, +- size=volume.size, +- volume_type=volume.volume_type, +- metadata=volume.metadata) +- +- snap_ref = Snapshot(volume_name=volume.name, +- name='clone-snap-%s' % src_vref.id, +- volume_size=src_vref.size, +- volume_id=src_vref.id, +- id='tmp-snap-%s' % src_vref.id, +- volume=src_vref) +- +- drv.create_cloned_volume(volume, src_vref) +- +- mock_create_snapshot.assert_called_once_with(snap_ref) +- mock_copy_volume_from_snapshot.assert_called_once_with( +- snap_ref, volume_ref, volume['size']) +- self.assertTrue(mock_delete_snapshot.called) ++ volume = fake_volume.fake_volume_obj(self.context) ++ src_vref_id = '375e32b2-804a-49f2-b282-85d1d5a5b9e1' ++ src_vref = fake_volume.fake_volume_obj( ++ self.context, ++ id=src_vref_id, ++ name='volume-%s' % src_vref_id) ++ ++ vol_attrs = ['provider_location', 'size', 'id', 'name', 'status', ++ 'volume_type', 'metadata'] ++ Volume = collections.namedtuple('Volume', vol_attrs) ++ ++ snap_attrs = ['volume_name', 'volume_size', 'name', ++ 'volume_id', 'id', 'volume'] ++ Snapshot = collections.namedtuple('Snapshot', snap_attrs) ++ ++ volume_ref = Volume(id=volume.id, ++ name=volume.name, ++ status=volume.status, ++ provider_location=volume.provider_location, ++ size=volume.size, ++ volume_type=volume.volume_type, ++ metadata=volume.metadata) ++ ++ snap_ref = Snapshot(volume_name=volume.name, ++ name='clone-snap-%s' % src_vref.id, ++ volume_size=src_vref.size, ++ volume_id=src_vref.id, ++ id='tmp-snap-%s' % src_vref.id, ++ volume=src_vref) ++ ++ drv.create_cloned_volume(volume, src_vref) ++ ++ exp_acceptable_states = ['available', 'backing-up', 'downloading'] ++ mock_validate_state.assert_called_once_with( ++ src_vref.status, ++ exp_acceptable_states, ++ obj_description='source volume') ++ mock_create_snapshot.assert_called_once_with(snap_ref) ++ mock_copy_volume_from_snapshot.assert_called_once_with( ++ snap_ref, volume_ref, volume['size']) ++ self.assertTrue(mock_delete_snapshot.called) + + def test_create_regular_file(self): + self._driver._create_regular_file('/path', 1) +diff --git cinder/volume/drivers/quobyte.py cinder/volume/drivers/quobyte.py +index 8d77e65..32d4021 100644 +--- cinder/volume/drivers/quobyte.py ++++ cinder/volume/drivers/quobyte.py +@@ -16,6 +16,7 @@ + + import errno + import os ++import psutil + + from oslo_concurrency import processutils + from oslo_config import cfg +@@ -30,14 +31,14 @@ from cinder import interface + from cinder import utils + from cinder.volume.drivers import remotefs as remotefs_drv + +-VERSION = '1.1' ++VERSION = '1.1.5' + + LOG = logging.getLogger(__name__) + + volume_opts = [ +- cfg.URIOpt('quobyte_volume_url', +- help=('URL to the Quobyte volume e.g.,' +- ' quobyte:///')), ++ cfg.StrOpt('quobyte_volume_url', ++ help=('Quobyte URL to the Quobyte volume e.g.,' ++ ' quobyte://, /')), + cfg.StrOpt('quobyte_client_cfg', + help=('Path to a Quobyte Client configuration file.')), + cfg.BoolOpt('quobyte_sparsed_volumes', +@@ -79,6 +80,10 @@ class QuobyteDriver(remotefs_drv.RemoteFSSnapDriverDistributed): + Version history: + 1.0 - Initial driver. + 1.1 - Adds optional insecure NAS settings ++ 1.1.1 - Removes getfattr calls from driver ++ 1.1.2 - Fixes a bug in the creation of cloned volumes ++ 1.1.4 - Fixes capability to configure redundancy in quobyte_volume_url ++ 1.1.5 - Enables extension of volumes with snapshots + """ + + driver_volume_type = 'quobyte' +@@ -172,7 +177,7 @@ class QuobyteDriver(remotefs_drv.RemoteFSSnapDriverDistributed): + @utils.synchronized('quobyte', external=False) + def create_cloned_volume(self, volume, src_vref): + """Creates a clone of the specified volume.""" +- self._create_cloned_volume(volume, src_vref) ++ return self._create_cloned_volume(volume, src_vref) + + @utils.synchronized('quobyte', external=False) + def create_volume(self, volume): +@@ -295,14 +300,6 @@ class QuobyteDriver(remotefs_drv.RemoteFSSnapDriverDistributed): + @utils.synchronized('quobyte', external=False) + def extend_volume(self, volume, size_gb): + volume_path = self.local_path(volume) +- volume_filename = os.path.basename(volume_path) +- +- # Ensure no snapshots exist for the volume +- active_image = self.get_active_image_from_info(volume) +- if volume_filename != active_image: +- msg = _('Extend volume is only supported for this' +- ' driver when no snapshots exist.') +- raise exception.InvalidVolume(msg) + + info = self._qemu_img_info(volume_path, volume.name) + backing_fmt = info.file_format +@@ -312,7 +309,10 @@ class QuobyteDriver(remotefs_drv.RemoteFSSnapDriverDistributed): + raise exception.InvalidVolume(msg % backing_fmt) + + # qemu-img can resize both raw and qcow2 files +- image_utils.resize_image(volume_path, size_gb) ++ active_path = os.path.join( ++ self._get_mount_point_for_share(volume.provider_location), ++ self.get_active_image_from_info(volume)) ++ image_utils.resize_image(active_path, size_gb) + + def _do_create_volume(self, volume): + """Create a volume on given Quobyte volume. +@@ -465,16 +465,40 @@ class QuobyteDriver(remotefs_drv.RemoteFSSnapDriverDistributed): + self._validate_volume(mount_path) + + def _validate_volume(self, mount_path): +- """Wraps execute calls for checking validity of a Quobyte volume""" +- command = ['getfattr', "-n", "quobyte.info", mount_path] +- try: +- self._execute(*command, run_as_root=self._execute_as_root) +- except processutils.ProcessExecutionError as exc: +- msg = (_("The mount %(mount_path)s is not a valid" +- " Quobyte USP volume. Error: %(exc)s") +- % {'mount_path': mount_path, 'exc': exc}) +- raise exception.VolumeDriverException(msg) +- +- if not os.access(mount_path, os.W_OK | os.X_OK): +- LOG.warning(_LW("Volume is not writable. Please broaden the file" +- " permissions. Mount: %s"), mount_path) ++ """Runs a number of tests on the expect Quobyte mount""" ++ partitions = psutil.disk_partitions(all=True) ++ for p in partitions: ++ if mount_path == p.mountpoint: ++ if p.device.startswith("quobyte@"): ++ try: ++ statresult = os.stat(mount_path) ++ if statresult.st_size == 0: ++ # client looks healthy ++ if not os.access(mount_path, ++ os.W_OK | os.X_OK): ++ LOG.warning(_LW("Volume is not writable. " ++ "Please broaden the file" ++ " permissions." ++ " Mount: %s"), ++ mount_path) ++ return # we're happy here ++ else: ++ msg = (_("The mount %(mount_path)s is not a " ++ "valid Quobyte volume. Stale mount?") ++ % {'mount_path': mount_path}) ++ raise exception.VolumeDriverException(msg) ++ except Exception as exc: ++ msg = (_("The mount %(mount_path)s is not a valid" ++ " Quobyte volume. Error: %(exc)s . " ++ " Possibly a Quobyte client crash?") ++ % {'mount_path': mount_path, 'exc': exc}) ++ raise exception.VolumeDriverException(msg) ++ else: ++ msg = (_("The mount %(mount_path)s is not a valid" ++ " Quobyte volume according to partition list.") ++ % {'mount_path': mount_path}) ++ raise exception.VolumeDriverException(msg) ++ msg = (_("No matching Quobyte mount entry for %(mount_path)s" ++ " could be found for validation in partition list.") ++ % {'mount_path': mount_path}) ++ raise exception.VolumeDriverException(msg) +diff --git cinder/volume/drivers/remotefs.py cinder/volume/drivers/remotefs.py +index 6f07cb9..e76c945 100644 +--- cinder/volume/drivers/remotefs.py ++++ cinder/volume/drivers/remotefs.py +@@ -227,6 +227,23 @@ class RemoteFSDriver(driver.BaseVD): + " mount_point_base.") + return None + ++ @staticmethod ++ def _validate_state(current_state, ++ acceptable_states, ++ obj_description='volume', ++ invalid_exc=exception.InvalidVolume): ++ if current_state not in acceptable_states: ++ message = _('Invalid %(obj_description)s state. ' ++ 'Acceptable states for this operation: ' ++ '%(acceptable_states)s. ' ++ 'Current %(obj_description)s state: ' ++ '%(current_state)s.') ++ raise invalid_exc( ++ message=message % ++ dict(obj_description=obj_description, ++ acceptable_states=acceptable_states, ++ current_state=current_state)) ++ + @utils.trace + def create_volume(self, volume): + """Creates a volume. +@@ -937,12 +954,10 @@ class RemoteFSSnapDriverBase(RemoteFSDriver): + {'src': src_vref.id, + 'dst': volume.id}) + +- if src_vref.status not in ['available', 'backing-up']: +- msg = _("Source volume status must be 'available', or " +- "'backing-up' but is: " +- "%(status)s.") % {'status': src_vref.status} +- raise exception.InvalidVolume(msg) +- ++ acceptable_states = ['available', 'backing-up', 'downloading'] ++ self._validate_state(src_vref.status, ++ acceptable_states, ++ obj_description='source volume') + volume_name = CONF.volume_name_template % volume.id + + # Create fake volume and snapshot objects +@@ -1017,12 +1032,9 @@ class RemoteFSSnapDriverBase(RemoteFSDriver): + else 'offline')}) + + volume_status = snapshot.volume.status +- if volume_status not in ['available', 'in-use', 'backing-up']: +- msg = _("Volume status must be 'available', 'in-use' or " +- "'backing-up' but is: " +- "%(status)s.") % {'status': volume_status} +- +- raise exception.InvalidVolume(msg) ++ acceptable_states = ['available', 'in-use', 'backing-up', 'deleting', ++ 'downloading'] ++ self._validate_state(volume_status, acceptable_states) + + vol_path = self._local_volume_dir(snapshot.volume) + self._ensure_share_writable(vol_path) +@@ -1327,12 +1339,15 @@ class RemoteFSSnapDriverBase(RemoteFSDriver): + else 'offline')}) + + status = snapshot.volume.status +- if status not in ['available', 'in-use', 'backing-up']: +- msg = _("Volume status must be 'available', 'in-use' or " +- "'backing-up' but is: " +- "%(status)s.") % {'status': status} + +- raise exception.InvalidVolume(msg) ++ acceptable_states = ['available', 'in-use', 'backing-up'] ++ if snapshot.id.startswith('tmp-snap-'): ++ # This is an internal volume snapshot. In order to support ++ # image caching, we'll allow creating/deleting such snapshots ++ # while having volumes in 'downloading' state. ++ acceptable_states.append('downloading') ++ ++ self._validate_state(status, acceptable_states) + + info_path = self._local_path_volume_info(snapshot.volume) + snap_info = self._read_info_file(info_path, empty_if_missing=True) diff --git a/full_quobyte_patch/Ocata/full_quobyte_ocata_nova.patch b/full_quobyte_patch/Ocata/full_quobyte_ocata_nova.patch index 2f387a9..68c6032 100644 --- a/full_quobyte_patch/Ocata/full_quobyte_ocata_nova.patch +++ b/full_quobyte_patch/Ocata/full_quobyte_ocata_nova.patch @@ -1,295 +1,3 @@ -diff --git nova/tests/unit/virt/libvirt/test_imagebackend.py nova/tests/unit/virt/libvirt/test_imagebackend.py -index b3cc277..2c6dff9 100644 ---- nova/tests/unit/virt/libvirt/test_imagebackend.py -+++ nova/tests/unit/virt/libvirt/test_imagebackend.py -@@ -120,9 +120,8 @@ class _ImageTestCase(object): - image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE) - - self.assertEqual(fake_processutils.fake_execute_get_log(), -- ['fallocate -l 1 %s.fallocate_test' % self.PATH, -- 'fallocate -n -l %s %s' % (self.SIZE, self.PATH), -- 'fallocate -n -l %s %s' % (self.SIZE, self.PATH)]) -+ ['truncate -s %s %s' % (self.SIZE, self.PATH), -+ 'truncate -s %s %s' % (self.SIZE, self.PATH)]) - - def test_prealloc_image_without_write_access(self): - CONF.set_override('preallocate_images', 'space') -diff --git nova/tests/unit/virt/libvirt/volume/test_quobyte.py nova/tests/unit/virt/libvirt/volume/test_quobyte.py -index a43a79a..7f867fa 100644 ---- nova/tests/unit/virt/libvirt/volume/test_quobyte.py -+++ nova/tests/unit/virt/libvirt/volume/test_quobyte.py -@@ -15,12 +15,15 @@ - """Unit tests for the Quobyte volume driver module.""" - - import os -+import traceback - - import mock - from oslo_concurrency import processutils - from oslo_utils import fileutils -+import psutil -+import six - --from nova import exception -+from nova import exception as nova_exception - from nova import test - from nova.tests.unit.virt.libvirt.volume import test_volume - from nova import utils -@@ -31,9 +34,37 @@ from nova.virt.libvirt.volume import quobyte - class QuobyteTestCase(test.NoDBTestCase): - """Tests the nova.virt.libvirt.volume.quobyte module utilities.""" - -+ TEST_MNT_POINT = mock.sentinel.TEST_MNT_POINT -+ -+ def assertRaisesAndMessageMatches( -+ self, excClass, msg, callableObj, *args, **kwargs): -+ """Ensure that the specified exception was raised. """ -+ -+ caught = False -+ try: -+ callableObj(*args, **kwargs) -+ except Exception as exc: -+ caught = True -+ self.assertIsInstance(exc, excClass, -+ 'Wrong exception caught: %s Stacktrace: %s' % -+ (exc, traceback.format_exc())) -+ self.assertIn(msg, six.text_type(exc)) -+ -+ if not caught: -+ self.fail('Expected raised exception but nothing caught.') -+ -+ def get_mock_partitions(self): -+ mypart = mock.Mock() -+ mypart.device = "quobyte@" -+ mypart.mountpoint = self.TEST_MNT_POINT -+ return [mypart] -+ -+ @mock.patch.object(os.path, "exists", return_value=False) - @mock.patch.object(fileutils, "ensure_tree") - @mock.patch.object(utils, "execute") -- def test_quobyte_mount_volume(self, mock_execute, mock_ensure_tree): -+ def test_quobyte_mount_volume_not_systemd(self, mock_execute, -+ mock_ensure_tree, -+ mock_exists): - mnt_base = '/mnt' - quobyte_volume = '192.168.1.1/volume-00001' - export_mnt_base = os.path.join(mnt_base, -@@ -43,17 +74,45 @@ class QuobyteTestCase(test.NoDBTestCase): - - mock_ensure_tree.assert_called_once_with(export_mnt_base) - expected_commands = [mock.call('mount.quobyte', -+ '--disable-xattrs', - quobyte_volume, -- export_mnt_base, -- check_exit_code=[0, 4]) -+ export_mnt_base) -+ ] -+ mock_execute.assert_has_calls(expected_commands) -+ mock_exists.assert_called_once_with(" /run/systemd/system") -+ -+ @mock.patch.object(os.path, "exists", return_value=True) -+ @mock.patch.object(fileutils, "ensure_tree") -+ @mock.patch.object(utils, "execute") -+ def test_quobyte_mount_volume_systemd(self, mock_execute, -+ mock_ensure_tree, -+ mock_exists): -+ mnt_base = '/mnt' -+ quobyte_volume = '192.168.1.1/volume-00001' -+ export_mnt_base = os.path.join(mnt_base, -+ utils.get_hash_str(quobyte_volume)) -+ -+ quobyte.mount_volume(quobyte_volume, export_mnt_base) -+ -+ mock_ensure_tree.assert_called_once_with(export_mnt_base) -+ expected_commands = [mock.call('systemd-run', -+ '--scope', -+ '--user', -+ 'mount.quobyte', -+ '--disable-xattrs', -+ quobyte_volume, -+ export_mnt_base) - ] - mock_execute.assert_has_calls(expected_commands) -+ mock_exists.assert_called_once_with(" /run/systemd/system") - -+ @mock.patch.object(os.path, "exists", return_value=False) - @mock.patch.object(fileutils, "ensure_tree") - @mock.patch.object(utils, "execute") - def test_quobyte_mount_volume_with_config(self, - mock_execute, -- mock_ensure_tree): -+ mock_ensure_tree, -+ mock_exists): - mnt_base = '/mnt' - quobyte_volume = '192.168.1.1/volume-00001' - export_mnt_base = os.path.join(mnt_base, -@@ -66,13 +125,14 @@ class QuobyteTestCase(test.NoDBTestCase): - - mock_ensure_tree.assert_called_once_with(export_mnt_base) - expected_commands = [mock.call('mount.quobyte', -+ '--disable-xattrs', - quobyte_volume, - export_mnt_base, - '-c', -- config_file_dummy, -- check_exit_code=[0, 4]) -+ config_file_dummy) - ] - mock_execute.assert_has_calls(expected_commands) -+ mock_exists.assert_called_once_with(" /run/systemd/system") - - @mock.patch.object(fileutils, "ensure_tree") - @mock.patch.object(utils, "execute", -@@ -141,49 +201,91 @@ class QuobyteTestCase(test.NoDBTestCase): - "the Quobyte Volume at %s", - export_mnt_base)) - -- @mock.patch.object(os, "access", return_value=True) -- @mock.patch.object(utils, "execute") -- def test_quobyte_is_valid_volume(self, mock_execute, mock_access): -- mnt_base = '/mnt' -- quobyte_volume = '192.168.1.1/volume-00001' -- export_mnt_base = os.path.join(mnt_base, -- utils.get_hash_str(quobyte_volume)) -- -- quobyte.validate_volume(export_mnt_base) -- -- mock_execute.assert_called_once_with('getfattr', -- '-n', -- 'quobyte.info', -- export_mnt_base) -- -- @mock.patch.object(utils, "execute", -- side_effect=(processutils. -- ProcessExecutionError)) -- def test_quobyte_is_valid_volume_vol_not_valid_volume(self, mock_execute): -- mnt_base = '/mnt' -- quobyte_volume = '192.168.1.1/volume-00001' -- export_mnt_base = os.path.join(mnt_base, -- utils.get_hash_str(quobyte_volume)) -- -- self.assertRaises(exception.NovaException, -- quobyte.validate_volume, -- export_mnt_base) -- -- @mock.patch.object(os, "access", return_value=False) -- @mock.patch.object(utils, "execute", -- side_effect=(processutils. -- ProcessExecutionError)) -- def test_quobyte_is_valid_volume_vol_no_valid_access(self, -- mock_execute, -- mock_access): -- mnt_base = '/mnt' -- quobyte_volume = '192.168.1.1/volume-00001' -- export_mnt_base = os.path.join(mnt_base, -- utils.get_hash_str(quobyte_volume)) -- -- self.assertRaises(exception.NovaException, -- quobyte.validate_volume, -- export_mnt_base) -+ @mock.patch.object(psutil, "disk_partitions") -+ @mock.patch.object(os, "stat") -+ def test_validate_volume_all_good(self, stat_mock, part_mock): -+ part_mock.return_value = self.get_mock_partitions() -+ drv = quobyte -+ -+ def statMockCall(*args): -+ if args[0] == self.TEST_MNT_POINT: -+ stat_result = mock.Mock() -+ stat_result.st_size = 0 -+ return stat_result -+ return os.stat(args) -+ stat_mock.side_effect = statMockCall -+ -+ drv.validate_volume(self.TEST_MNT_POINT) -+ -+ stat_mock.assert_called_once_with(self.TEST_MNT_POINT) -+ part_mock.assert_called_once_with(all=True) -+ -+ @mock.patch.object(psutil, "disk_partitions") -+ @mock.patch.object(os, "stat") -+ def test_validate_volume_mount_not_working(self, stat_mock, part_mock): -+ part_mock.return_value = self.get_mock_partitions() -+ drv = quobyte -+ -+ def statMockCall(*args): -+ print (args) -+ if args[0] == self.TEST_MNT_POINT: -+ raise nova_exception.InvalidVolume() -+ stat_mock.side_effect = [os.stat, statMockCall] -+ -+ self.assertRaises( -+ excClass=nova_exception.InvalidVolume, -+ callableObj=drv.validate_volume, -+ mount_path=self.TEST_MNT_POINT) -+ stat_mock.assert_called_with(self.TEST_MNT_POINT) -+ part_mock.assert_called_once_with(all=True) -+ -+ def test_validate_volume_no_mtab_entry(self): -+ msg = ("No matching Quobyte mount entry for %(mpt)s" -+ " could be found for validation in partition list." -+ % {'mpt': self.TEST_MNT_POINT}) -+ -+ self.assertRaisesAndMessageMatches( -+ nova_exception.InvalidVolume, -+ msg, -+ quobyte.validate_volume, -+ self.TEST_MNT_POINT) -+ -+ @mock.patch.object(psutil, "disk_partitions") -+ def test_validate_volume_wrong_mount_type(self, part_mock): -+ mypart = mock.Mock() -+ mypart.device = "not-quobyte" -+ mypart.mountpoint = self.TEST_MNT_POINT -+ part_mock.return_value = [mypart] -+ msg = ("The mount %(mpt)s is not a valid" -+ " Quobyte volume according to partition list." -+ % {'mpt': self.TEST_MNT_POINT}) -+ -+ self.assertRaisesAndMessageMatches( -+ nova_exception.InvalidVolume, -+ msg, -+ quobyte.validate_volume, -+ self.TEST_MNT_POINT) -+ part_mock.assert_called_once_with(all=True) -+ -+ @mock.patch.object(os, "stat") -+ @mock.patch.object(psutil, "disk_partitions") -+ def test_validate_volume_stale_mount(self, part_mock, stat_mock): -+ part_mock.return_value = self.get_mock_partitions() -+ -+ def statMockCall(*args): -+ if args[0] == self.TEST_MNT_POINT: -+ stat_result = mock.Mock() -+ stat_result.st_size = 1 -+ return stat_result -+ return os.stat(args) -+ stat_mock.side_effect = statMockCall -+ -+ # As this uses a dir size >0, it raises an exception -+ self.assertRaises( -+ nova_exception.InvalidVolume, -+ quobyte.validate_volume, -+ self.TEST_MNT_POINT) -+ part_mock.assert_called_once_with(all=True) - - - class LibvirtQuobyteVolumeDriverTestCase( -@@ -332,12 +434,12 @@ class LibvirtQuobyteVolumeDriverTestCase( - - def exe_side_effect(*cmd, **kwargs): - if cmd == mock.ANY: -- raise exception.NovaException() -+ raise nova_exception.NovaException() - - with mock.patch.object(quobyte, - 'validate_volume') as mock_execute: - mock_execute.side_effect = exe_side_effect -- self.assertRaises(exception.NovaException, -+ self.assertRaises(nova_exception.NovaException, - libvirt_driver.connect_volume, - connection_info, - self.disk_info) diff --git nova/virt/libvirt/imagebackend.py nova/virt/libvirt/imagebackend.py index 8dd227e..eaf5c81 100644 --- nova/virt/libvirt/imagebackend.py @@ -419,16 +127,4 @@ index 284a084..f36aacb 100644 + @utils.synchronized('connect_qb_volume') def disconnect_volume(self, connection_info, disk_dev): """Disconnect the volume.""" - -diff --git releasenotes/notes/bug_1659328-73686be497f5f85a.yaml releasenotes/notes/bug_1659328-73686be497f5f85a.yaml -new file mode 100644 -index 0000000..e55248e ---- /dev/null -+++ releasenotes/notes/bug_1659328-73686be497f5f85a.yaml -@@ -0,0 +1,6 @@ -+--- -+ -+fixes: -+ - | -+ The i/o performance for Quobyte volumes has been increased significantly -+ by disabling xattrs. + \ No newline at end of file diff --git a/full_quobyte_patch/Ocata/full_quobyte_ocata_nova_full_source_tree.patch b/full_quobyte_patch/Ocata/full_quobyte_ocata_nova_full_source_tree.patch new file mode 100644 index 0000000..2f387a9 --- /dev/null +++ b/full_quobyte_patch/Ocata/full_quobyte_ocata_nova_full_source_tree.patch @@ -0,0 +1,434 @@ +diff --git nova/tests/unit/virt/libvirt/test_imagebackend.py nova/tests/unit/virt/libvirt/test_imagebackend.py +index b3cc277..2c6dff9 100644 +--- nova/tests/unit/virt/libvirt/test_imagebackend.py ++++ nova/tests/unit/virt/libvirt/test_imagebackend.py +@@ -120,9 +120,8 @@ class _ImageTestCase(object): + image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE) + + self.assertEqual(fake_processutils.fake_execute_get_log(), +- ['fallocate -l 1 %s.fallocate_test' % self.PATH, +- 'fallocate -n -l %s %s' % (self.SIZE, self.PATH), +- 'fallocate -n -l %s %s' % (self.SIZE, self.PATH)]) ++ ['truncate -s %s %s' % (self.SIZE, self.PATH), ++ 'truncate -s %s %s' % (self.SIZE, self.PATH)]) + + def test_prealloc_image_without_write_access(self): + CONF.set_override('preallocate_images', 'space') +diff --git nova/tests/unit/virt/libvirt/volume/test_quobyte.py nova/tests/unit/virt/libvirt/volume/test_quobyte.py +index a43a79a..7f867fa 100644 +--- nova/tests/unit/virt/libvirt/volume/test_quobyte.py ++++ nova/tests/unit/virt/libvirt/volume/test_quobyte.py +@@ -15,12 +15,15 @@ + """Unit tests for the Quobyte volume driver module.""" + + import os ++import traceback + + import mock + from oslo_concurrency import processutils + from oslo_utils import fileutils ++import psutil ++import six + +-from nova import exception ++from nova import exception as nova_exception + from nova import test + from nova.tests.unit.virt.libvirt.volume import test_volume + from nova import utils +@@ -31,9 +34,37 @@ from nova.virt.libvirt.volume import quobyte + class QuobyteTestCase(test.NoDBTestCase): + """Tests the nova.virt.libvirt.volume.quobyte module utilities.""" + ++ TEST_MNT_POINT = mock.sentinel.TEST_MNT_POINT ++ ++ def assertRaisesAndMessageMatches( ++ self, excClass, msg, callableObj, *args, **kwargs): ++ """Ensure that the specified exception was raised. """ ++ ++ caught = False ++ try: ++ callableObj(*args, **kwargs) ++ except Exception as exc: ++ caught = True ++ self.assertIsInstance(exc, excClass, ++ 'Wrong exception caught: %s Stacktrace: %s' % ++ (exc, traceback.format_exc())) ++ self.assertIn(msg, six.text_type(exc)) ++ ++ if not caught: ++ self.fail('Expected raised exception but nothing caught.') ++ ++ def get_mock_partitions(self): ++ mypart = mock.Mock() ++ mypart.device = "quobyte@" ++ mypart.mountpoint = self.TEST_MNT_POINT ++ return [mypart] ++ ++ @mock.patch.object(os.path, "exists", return_value=False) + @mock.patch.object(fileutils, "ensure_tree") + @mock.patch.object(utils, "execute") +- def test_quobyte_mount_volume(self, mock_execute, mock_ensure_tree): ++ def test_quobyte_mount_volume_not_systemd(self, mock_execute, ++ mock_ensure_tree, ++ mock_exists): + mnt_base = '/mnt' + quobyte_volume = '192.168.1.1/volume-00001' + export_mnt_base = os.path.join(mnt_base, +@@ -43,17 +74,45 @@ class QuobyteTestCase(test.NoDBTestCase): + + mock_ensure_tree.assert_called_once_with(export_mnt_base) + expected_commands = [mock.call('mount.quobyte', ++ '--disable-xattrs', + quobyte_volume, +- export_mnt_base, +- check_exit_code=[0, 4]) ++ export_mnt_base) ++ ] ++ mock_execute.assert_has_calls(expected_commands) ++ mock_exists.assert_called_once_with(" /run/systemd/system") ++ ++ @mock.patch.object(os.path, "exists", return_value=True) ++ @mock.patch.object(fileutils, "ensure_tree") ++ @mock.patch.object(utils, "execute") ++ def test_quobyte_mount_volume_systemd(self, mock_execute, ++ mock_ensure_tree, ++ mock_exists): ++ mnt_base = '/mnt' ++ quobyte_volume = '192.168.1.1/volume-00001' ++ export_mnt_base = os.path.join(mnt_base, ++ utils.get_hash_str(quobyte_volume)) ++ ++ quobyte.mount_volume(quobyte_volume, export_mnt_base) ++ ++ mock_ensure_tree.assert_called_once_with(export_mnt_base) ++ expected_commands = [mock.call('systemd-run', ++ '--scope', ++ '--user', ++ 'mount.quobyte', ++ '--disable-xattrs', ++ quobyte_volume, ++ export_mnt_base) + ] + mock_execute.assert_has_calls(expected_commands) ++ mock_exists.assert_called_once_with(" /run/systemd/system") + ++ @mock.patch.object(os.path, "exists", return_value=False) + @mock.patch.object(fileutils, "ensure_tree") + @mock.patch.object(utils, "execute") + def test_quobyte_mount_volume_with_config(self, + mock_execute, +- mock_ensure_tree): ++ mock_ensure_tree, ++ mock_exists): + mnt_base = '/mnt' + quobyte_volume = '192.168.1.1/volume-00001' + export_mnt_base = os.path.join(mnt_base, +@@ -66,13 +125,14 @@ class QuobyteTestCase(test.NoDBTestCase): + + mock_ensure_tree.assert_called_once_with(export_mnt_base) + expected_commands = [mock.call('mount.quobyte', ++ '--disable-xattrs', + quobyte_volume, + export_mnt_base, + '-c', +- config_file_dummy, +- check_exit_code=[0, 4]) ++ config_file_dummy) + ] + mock_execute.assert_has_calls(expected_commands) ++ mock_exists.assert_called_once_with(" /run/systemd/system") + + @mock.patch.object(fileutils, "ensure_tree") + @mock.patch.object(utils, "execute", +@@ -141,49 +201,91 @@ class QuobyteTestCase(test.NoDBTestCase): + "the Quobyte Volume at %s", + export_mnt_base)) + +- @mock.patch.object(os, "access", return_value=True) +- @mock.patch.object(utils, "execute") +- def test_quobyte_is_valid_volume(self, mock_execute, mock_access): +- mnt_base = '/mnt' +- quobyte_volume = '192.168.1.1/volume-00001' +- export_mnt_base = os.path.join(mnt_base, +- utils.get_hash_str(quobyte_volume)) +- +- quobyte.validate_volume(export_mnt_base) +- +- mock_execute.assert_called_once_with('getfattr', +- '-n', +- 'quobyte.info', +- export_mnt_base) +- +- @mock.patch.object(utils, "execute", +- side_effect=(processutils. +- ProcessExecutionError)) +- def test_quobyte_is_valid_volume_vol_not_valid_volume(self, mock_execute): +- mnt_base = '/mnt' +- quobyte_volume = '192.168.1.1/volume-00001' +- export_mnt_base = os.path.join(mnt_base, +- utils.get_hash_str(quobyte_volume)) +- +- self.assertRaises(exception.NovaException, +- quobyte.validate_volume, +- export_mnt_base) +- +- @mock.patch.object(os, "access", return_value=False) +- @mock.patch.object(utils, "execute", +- side_effect=(processutils. +- ProcessExecutionError)) +- def test_quobyte_is_valid_volume_vol_no_valid_access(self, +- mock_execute, +- mock_access): +- mnt_base = '/mnt' +- quobyte_volume = '192.168.1.1/volume-00001' +- export_mnt_base = os.path.join(mnt_base, +- utils.get_hash_str(quobyte_volume)) +- +- self.assertRaises(exception.NovaException, +- quobyte.validate_volume, +- export_mnt_base) ++ @mock.patch.object(psutil, "disk_partitions") ++ @mock.patch.object(os, "stat") ++ def test_validate_volume_all_good(self, stat_mock, part_mock): ++ part_mock.return_value = self.get_mock_partitions() ++ drv = quobyte ++ ++ def statMockCall(*args): ++ if args[0] == self.TEST_MNT_POINT: ++ stat_result = mock.Mock() ++ stat_result.st_size = 0 ++ return stat_result ++ return os.stat(args) ++ stat_mock.side_effect = statMockCall ++ ++ drv.validate_volume(self.TEST_MNT_POINT) ++ ++ stat_mock.assert_called_once_with(self.TEST_MNT_POINT) ++ part_mock.assert_called_once_with(all=True) ++ ++ @mock.patch.object(psutil, "disk_partitions") ++ @mock.patch.object(os, "stat") ++ def test_validate_volume_mount_not_working(self, stat_mock, part_mock): ++ part_mock.return_value = self.get_mock_partitions() ++ drv = quobyte ++ ++ def statMockCall(*args): ++ print (args) ++ if args[0] == self.TEST_MNT_POINT: ++ raise nova_exception.InvalidVolume() ++ stat_mock.side_effect = [os.stat, statMockCall] ++ ++ self.assertRaises( ++ excClass=nova_exception.InvalidVolume, ++ callableObj=drv.validate_volume, ++ mount_path=self.TEST_MNT_POINT) ++ stat_mock.assert_called_with(self.TEST_MNT_POINT) ++ part_mock.assert_called_once_with(all=True) ++ ++ def test_validate_volume_no_mtab_entry(self): ++ msg = ("No matching Quobyte mount entry for %(mpt)s" ++ " could be found for validation in partition list." ++ % {'mpt': self.TEST_MNT_POINT}) ++ ++ self.assertRaisesAndMessageMatches( ++ nova_exception.InvalidVolume, ++ msg, ++ quobyte.validate_volume, ++ self.TEST_MNT_POINT) ++ ++ @mock.patch.object(psutil, "disk_partitions") ++ def test_validate_volume_wrong_mount_type(self, part_mock): ++ mypart = mock.Mock() ++ mypart.device = "not-quobyte" ++ mypart.mountpoint = self.TEST_MNT_POINT ++ part_mock.return_value = [mypart] ++ msg = ("The mount %(mpt)s is not a valid" ++ " Quobyte volume according to partition list." ++ % {'mpt': self.TEST_MNT_POINT}) ++ ++ self.assertRaisesAndMessageMatches( ++ nova_exception.InvalidVolume, ++ msg, ++ quobyte.validate_volume, ++ self.TEST_MNT_POINT) ++ part_mock.assert_called_once_with(all=True) ++ ++ @mock.patch.object(os, "stat") ++ @mock.patch.object(psutil, "disk_partitions") ++ def test_validate_volume_stale_mount(self, part_mock, stat_mock): ++ part_mock.return_value = self.get_mock_partitions() ++ ++ def statMockCall(*args): ++ if args[0] == self.TEST_MNT_POINT: ++ stat_result = mock.Mock() ++ stat_result.st_size = 1 ++ return stat_result ++ return os.stat(args) ++ stat_mock.side_effect = statMockCall ++ ++ # As this uses a dir size >0, it raises an exception ++ self.assertRaises( ++ nova_exception.InvalidVolume, ++ quobyte.validate_volume, ++ self.TEST_MNT_POINT) ++ part_mock.assert_called_once_with(all=True) + + + class LibvirtQuobyteVolumeDriverTestCase( +@@ -332,12 +434,12 @@ class LibvirtQuobyteVolumeDriverTestCase( + + def exe_side_effect(*cmd, **kwargs): + if cmd == mock.ANY: +- raise exception.NovaException() ++ raise nova_exception.NovaException() + + with mock.patch.object(quobyte, + 'validate_volume') as mock_execute: + mock_execute.side_effect = exe_side_effect +- self.assertRaises(exception.NovaException, ++ self.assertRaises(nova_exception.NovaException, + libvirt_driver.connect_volume, + connection_info, + self.disk_info) +diff --git nova/virt/libvirt/imagebackend.py nova/virt/libvirt/imagebackend.py +index 8dd227e..eaf5c81 100644 +--- nova/virt/libvirt/imagebackend.py ++++ nova/virt/libvirt/imagebackend.py +@@ -232,9 +232,11 @@ class Image(object): + if os.path.exists(base) and size > self.get_disk_size(base): + self.resize_image(size) + +- if (self.preallocate and self._can_fallocate() and +- os.access(self.path, os.W_OK)): +- utils.execute('fallocate', '-n', '-l', size, self.path) ++ if (self.preallocate and os.access(self.path, os.W_OK)): ++ LOG.debug('Truncating new image at %(path)s to virtual size ' ++ '(patched for Quobyte).', ++ {'path': self.path}) ++ utils.execute('truncate', '-s', size, self.path) + + def _can_fallocate(self): + """Check once per class, whether fallocate(1) is available, +diff --git nova/virt/libvirt/volume/quobyte.py nova/virt/libvirt/volume/quobyte.py +index 284a084..f36aacb 100644 +--- nova/virt/libvirt/volume/quobyte.py ++++ nova/virt/libvirt/volume/quobyte.py +@@ -19,6 +19,7 @@ import os + from oslo_concurrency import processutils + from oslo_log import log as logging + from oslo_utils import fileutils ++import psutil + import six + + import nova.conf +@@ -44,15 +45,22 @@ def mount_volume(volume, mnt_base, configfile=None): + """Wraps execute calls for mounting a Quobyte volume""" + fileutils.ensure_tree(mnt_base) + +- command = ['mount.quobyte', volume, mnt_base] ++ # NOTE(kaisers): disable xattrs to speed up io as this omits ++ # additional metadata requests in the backend. xattrs can be ++ # enabled without issues but will reduce performance. ++ command = ['mount.quobyte', '--disable-xattrs', volume, mnt_base] ++ if os.path.exists(" /run/systemd/system"): ++ # Note(kaisers): with systemd this requires a separate CGROUP to ++ # prevent Nova service stop/restarts from killing the mount. ++ command = ['systemd-run', '--scope', '--user', 'mount.quobyte', ++ '--disable-xattrs', volume, mnt_base] + if configfile: + command.extend(['-c', configfile]) + + LOG.debug('Mounting volume %s at mount point %s ...', + volume, + mnt_base) +- # Run mount command but do not fail on already mounted exit code +- utils.execute(*command, check_exit_code=[0, 4]) ++ utils.execute(*command) + LOG.info(_LI('Mounted volume: %s'), volume) + + +@@ -69,21 +77,32 @@ def umount_volume(mnt_base): + mnt_base) + + +-def validate_volume(mnt_base): +- """Wraps execute calls for checking validity of a Quobyte volume""" +- command = ['getfattr', "-n", "quobyte.info", mnt_base] +- try: +- utils.execute(*command) +- except processutils.ProcessExecutionError as exc: +- msg = (_("The mount %(mount_path)s is not a valid" +- " Quobyte volume. Error: %(exc)s") +- % {'mount_path': mnt_base, 'exc': exc}) +- raise nova_exception.InternalError(msg) +- +- if not os.access(mnt_base, os.W_OK | os.X_OK): +- msg = (_LE("Volume is not writable. Please broaden the file" +- " permissions. Mount: %s") % mnt_base) +- raise nova_exception.InternalError(msg) ++def validate_volume(mount_path): ++ """Runs a number of tests to be sure this is a (working) Quobyte mount""" ++ partitions = psutil.disk_partitions(all=True) ++ for p in partitions: ++ if mount_path != p.mountpoint: ++ continue ++ if p.device.startswith("quobyte@"): ++ statresult = os.stat(mount_path) ++ # Note(kaisers): Quobyte always shows mount points with size 0 ++ if statresult.st_size == 0: ++ # client looks healthy ++ return # we're happy here ++ else: ++ msg = (_("The mount %(mount_path)s is not a " ++ "valid Quobyte volume. Stale mount?") ++ % {'mount_path': mount_path}) ++ raise nova_exception.InvalidVolume(msg) ++ else: ++ msg = (_("The mount %(mount_path)s is not a valid" ++ " Quobyte volume according to partition list.") ++ % {'mount_path': mount_path}) ++ raise nova_exception.InvalidVolume(msg) ++ msg = (_("No matching Quobyte mount entry for %(mount_path)s" ++ " could be found for validation in partition list.") ++ % {'mount_path': mount_path}) ++ raise nova_exception.InvalidVolume(msg) + + + class LibvirtQuobyteVolumeDriver(fs.LibvirtBaseFileSystemVolumeDriver): +@@ -106,7 +125,7 @@ class LibvirtQuobyteVolumeDriver(fs.LibvirtBaseFileSystemVolumeDriver): + + return conf + +- @utils.synchronized('connect_volume') ++ @utils.synchronized('connect_qb_volume') + def connect_volume(self, connection_info, disk_info): + """Connect the volume.""" + data = connection_info['data'] +@@ -127,12 +146,12 @@ class LibvirtQuobyteVolumeDriver(fs.LibvirtBaseFileSystemVolumeDriver): + + if not mounted: + mount_volume(quobyte_volume, +- mount_path, +- CONF.libvirt.quobyte_client_cfg) ++ mount_path, ++ CONF.libvirt.quobyte_client_cfg) + + validate_volume(mount_path) + +- @utils.synchronized('connect_volume') ++ @utils.synchronized('connect_qb_volume') + def disconnect_volume(self, connection_info, disk_dev): + """Disconnect the volume.""" + +diff --git releasenotes/notes/bug_1659328-73686be497f5f85a.yaml releasenotes/notes/bug_1659328-73686be497f5f85a.yaml +new file mode 100644 +index 0000000..e55248e +--- /dev/null ++++ releasenotes/notes/bug_1659328-73686be497f5f85a.yaml +@@ -0,0 +1,6 @@ ++--- ++ ++fixes: ++ - | ++ The i/o performance for Quobyte volumes has been increased significantly ++ by disabling xattrs. diff --git a/full_quobyte_patch/README.md b/full_quobyte_patch/README.md index 112584c..39f6cb7 100644 --- a/full_quobyte_patch/README.md +++ b/full_quobyte_patch/README.md @@ -17,9 +17,31 @@ When complete these patches contain all relevant fixes and changes for the given - Removes the requirement to support extended attributes from Nova mounts with patch [428646](https://review.openstack.org/#/c/428646/) for improved performance - Exchanges Nova fallocate on qcow2 ephemeral images with a truncate command for improved performance with Quobyte +The patches come in two versions, the pure driver and the full source patch. For Cinder these are: -### Usage +1. _full_quobyte_ocata_cinder.patch_ - The pure driver patch for patching package based installations that strip tests and other development elements +2. _full_quobyte_ocata_cinder_full_source_tree.patch_ - The full source patch including updates of unit tests and release notes files -These patches can be applied by navigating to the project to be patched root directory and running: +And for Nova: + +1. _full_quobyte_ocata_nova.patch_ - The pure driver patch for patching package based installations that strip tests and other development elements +2. _full_quobyte_ocata_nova_full_source_tree.patch_ - The full source patch including updates of unit tests and release notes files + +### Installation + +#### Usage for the pure driver patches + +These patches can be applied by navigating to the respective project package root directory and running: + + patch -p1 < /path/to/patchfile + +For __example__, to patch a cinder installation based on a stripped package run: + + cd /usr/lib/python2.7/site-packages/cinder/ + patch -p1 < /path/to/full_quobyte_ocata_cinder.patch + +#### Usage for the full source tree driver patches + +These patches can be applied by navigating to the respective project source root directory and running: patch -p0 < /path/to/patchfile diff --git a/overlay_volumes/README.md b/overlay_volumes/README.md index 3b5c001..84cf3ef 100644 --- a/overlay_volumes/README.md +++ b/overlay_volumes/README.md @@ -21,7 +21,7 @@ This patch can be applied by navigating to the Cinder project package root direc patch -p1 < /path/to/overlay_volumes_Cinder-Pike.patch -#### Usage for the full source driver patch +#### Usage for the full source tree driver patch This patch can be applied by navigating to the Cinder project source root directory and running: diff --git a/return_for_create_clone/README.md b/return_for_create_clone/README.md index 2edcfd9..fa68532 100644 --- a/return_for_create_clone/README.md +++ b/return_for_create_clone/README.md @@ -4,8 +4,14 @@ This patch fixes the Cinder bug [1674611](https://bugs.launchpad.net/cinder/+bug/1674611) with patch [447958](https://review.openstack.org/#/c/447958/), thus allowing e.g. cloned backups of Quobyte Cinder volumes. This patch is part of the upstream code fore releases Pike and newer. +This patch applies to stripped packaged installations as well as full source tree installations. Slightly different patch commands are used (see below). + ### Usage -This patch can be applied by navigating to the Nova project root directory and running: +This patch can be applied by navigating to the Cinder project root directory. For stripped packaged installations please run: + + patch -p1 < /path/to/patchfile + +For full source tree installations please run: patch -p0 < /path/to/patchfile diff --git a/systemd-cgroup_patch/Mitaka/systemd-run_Nova-Mitaka.patch b/systemd-cgroup_patch/Mitaka/systemd-run_Nova-Mitaka.patch new file mode 100644 index 0000000..98c4061 --- /dev/null +++ b/systemd-cgroup_patch/Mitaka/systemd-run_Nova-Mitaka.patch @@ -0,0 +1,34 @@ +diff --git nova/virt/libvirt/volume/quobyte.py nova/virt/libvirt/volume/quobyte.py +index 199439b..c9ea879 100644 +--- nova/virt/libvirt/volume/quobyte.py ++++ nova/virt/libvirt/volume/quobyte.py +@@ -56,13 +56,17 @@ def mount_volume(volume, mnt_base, configfile=None): + fileutils.ensure_tree(mnt_base) + + command = ['mount.quobyte', volume, mnt_base] ++ if os.path.exists(" /run/systemd/system"): ++ # Note(kaisers): with systemd this requires a separate CGROUP to ++ # prevent Nova service stop/restarts from killing the mount. ++ command = ['systemd-run', '--scope', '--user', 'mount.quobyte', volume, ++ mnt_base] + if configfile: + command.extend(['-c', configfile]) + + LOG.debug('Mounting volume %s at mount point %s ...', + volume, + mnt_base) +- # Run mount command but do not fail on already mounted exit code + utils.execute(*command, check_exit_code=[0, 4]) + LOG.info(_LI('Mounted volume: %s'), volume) + +@@ -138,8 +142,8 @@ class LibvirtQuobyteVolumeDriver(fs.LibvirtBaseFileSystemVolumeDriver): + + if not mounted: + mount_volume(quobyte_volume, +- mount_path, +- CONF.libvirt.quobyte_client_cfg) ++ mount_path, ++ CONF.libvirt.quobyte_client_cfg) + + validate_volume(mount_path) + \ No newline at end of file diff --git a/systemd-cgroup_patch/Mitaka/systemd-run_mitaka_plain-diff.patch b/systemd-cgroup_patch/Mitaka/systemd-run_Nova-Mitaka_full_source_tree.patch similarity index 100% rename from systemd-cgroup_patch/Mitaka/systemd-run_mitaka_plain-diff.patch rename to systemd-cgroup_patch/Mitaka/systemd-run_Nova-Mitaka_full_source_tree.patch diff --git a/systemd-cgroup_patch/README.md b/systemd-cgroup_patch/README.md index 5bea75d..70fc24d 100644 --- a/systemd-cgroup_patch/README.md +++ b/systemd-cgroup_patch/README.md @@ -3,19 +3,35 @@ Backport of a [Nova bugfix](https://review.openstack.org/#/c/432344/) for a [bug](https://bugs.launchpad.net/nova/+bug/1530860) that caused mounts to be removed when the Nova service was stopped or restarted. +This patch is part of the upstream code fore releases Pike and newer. + +The patch comes in two versions: + +1. _systemd-run_Nova-Mitaka.patch_ - The pure driver patch for patching package based installations that strip tests and other development elements +2. _systemd-run_Nova-Mitaka_full_source_tree.patch_ - The full source patch including updates of unit tests and release notes files + __Important__: Installations that applied the previous [Nova Mitaka external mount patch](https://github.com/quobyte/nova_mitaka_external-mount_patch) for this issue have to remove this previous patch before applying the systemd-cgroup_patch! Removal can easily be done via: patch -p1 -R < /path/to/patchfile -This patch is part of the upstream code fore releases Pike and newer. +### Installation + +The patch usage is slightly different for both versions (pure driver vs. full source patch). The difference resides in the -p parameter for the patch command and is applied as follows: + +#### Usage for the pure driver patch + +This patch can be applied by navigating to the Nova project package root directory and running: + + patch -p1 < /path/to/systemd-run_Nova-Mitaka.patch + -### Usage +#### Usage for the full source tree driver patch -These patches can be applied by navigating to the project to be patched root directory and running: +This patch can be applied by navigating to the Nova project source root directory and running: - patch -p0 < /path/to/patchfile + patch -p0 < /path/to/systemd-run_Nova-Mitaka_full_source_tree.patch ### Changelog diff --git a/truncate_ephemeral_patch/Mitaka/truncate-ephemeral_Nova-Mitaka.patch b/truncate_ephemeral_patch/Mitaka/truncate-ephemeral_Nova-Mitaka.patch new file mode 100644 index 0000000..2f6446c --- /dev/null +++ b/truncate_ephemeral_patch/Mitaka/truncate-ephemeral_Nova-Mitaka.patch @@ -0,0 +1,19 @@ +diff --git nova/virt/libvirt/imagebackend.py nova/virt/libvirt/imagebackend.py +index 06a9c8f..fd73b32 100644 +--- nova/virt/libvirt/imagebackend.py ++++ nova/virt/libvirt/imagebackend.py +@@ -256,9 +256,11 @@ class Image(object): + if os.path.exists(base) and size > self.get_disk_size(base): + self.resize_image(size) + +- if (self.preallocate and self._can_fallocate() and +- os.access(self.path, os.W_OK)): +- utils.execute('fallocate', '-n', '-l', size, self.path) ++ if (self.preallocate and os.access(self.path, os.W_OK)): ++ LOG.debug('Truncating new image at %(path)s to virtual size ' ++ '(patched for Quobyte).', ++ {'path': self.path}) ++ utils.execute('truncate', '-s', size, self.path) + + def _can_fallocate(self): + """Check once per class, whether fallocate(1) is available, diff --git a/truncate_ephemeral_patch/Mitaka/truncate-ephemeral_Mitaka.patch b/truncate_ephemeral_patch/Mitaka/truncate-ephemeral_Nova-Mitaka_full_source_tree.patch similarity index 100% rename from truncate_ephemeral_patch/Mitaka/truncate-ephemeral_Mitaka.patch rename to truncate_ephemeral_patch/Mitaka/truncate-ephemeral_Nova-Mitaka_full_source_tree.patch diff --git a/truncate_ephemeral_patch/Ocata/truncate-ephemeral_Nova-Ocata.patch b/truncate_ephemeral_patch/Ocata/truncate-ephemeral_Nova-Ocata.patch new file mode 100644 index 0000000..6b0ee5b --- /dev/null +++ b/truncate_ephemeral_patch/Ocata/truncate-ephemeral_Nova-Ocata.patch @@ -0,0 +1,19 @@ +diff --git nova/virt/libvirt/imagebackend.py nova/virt/libvirt/imagebackend.py +index 8dd227e..eaf5c81 100644 +--- nova/virt/libvirt/imagebackend.py ++++ nova/virt/libvirt/imagebackend.py +@@ -232,9 +232,11 @@ class Image(object): + if os.path.exists(base) and size > self.get_disk_size(base): + self.resize_image(size) + +- if (self.preallocate and self._can_fallocate() and +- os.access(self.path, os.W_OK)): +- utils.execute('fallocate', '-n', '-l', size, self.path) ++ if (self.preallocate and os.access(self.path, os.W_OK)): ++ LOG.debug('Truncating new image at %(path)s to virtual size ' ++ '(patched for Quobyte).', ++ {'path': self.path}) ++ utils.execute('truncate', '-s', size, self.path) + + def _can_fallocate(self): + """Check once per class, whether fallocate(1) is available, diff --git a/truncate_ephemeral_patch/Ocata/truncate-ephemeral_Ocata.patch b/truncate_ephemeral_patch/Ocata/truncate-ephemeral_Nova-Ocata_full_source_tree.patch similarity index 100% rename from truncate_ephemeral_patch/Ocata/truncate-ephemeral_Ocata.patch rename to truncate_ephemeral_patch/Ocata/truncate-ephemeral_Nova-Ocata_full_source_tree.patch diff --git a/truncate_ephemeral_patch/Pike/truncate-ephemeral_Nova-Pike.patch b/truncate_ephemeral_patch/Pike/truncate-ephemeral_Nova-Pike.patch new file mode 100644 index 0000000..baece2d --- /dev/null +++ b/truncate_ephemeral_patch/Pike/truncate-ephemeral_Nova-Pike.patch @@ -0,0 +1,19 @@ +diff --git nova/virt/libvirt/imagebackend.py nova/virt/libvirt/imagebackend.py +index bc7f05c..737a1a9 100644 +--- nova/virt/libvirt/imagebackend.py ++++ nova/virt/libvirt/imagebackend.py +@@ -246,9 +246,11 @@ class Image(object): + if os.path.exists(base) and size > self.get_disk_size(base): + self.resize_image(size) + +- if (self.preallocate and self._can_fallocate() and +- os.access(self.path, os.W_OK)): +- utils.execute('fallocate', '-n', '-l', size, self.path) ++ if (self.preallocate and os.access(self.path, os.W_OK)): ++ LOG.debug('Truncating new image at %(path)s to virtual size ' ++ '(patched for Quobyte).', ++ {'path': self.path}) ++ utils.execute('truncate', '-s', size, self.path) + + def _can_fallocate(self): + """Check once per class, whether fallocate(1) is available, diff --git a/truncate_ephemeral_patch/Pike/truncate-ephemeral_Pike.patch b/truncate_ephemeral_patch/Pike/truncate-ephemeral_Nova-Pike_full_source_tree.patch similarity index 100% rename from truncate_ephemeral_patch/Pike/truncate-ephemeral_Pike.patch rename to truncate_ephemeral_patch/Pike/truncate-ephemeral_Nova-Pike_full_source_tree.patch diff --git a/truncate_ephemeral_patch/README.md b/truncate_ephemeral_patch/README.md index 0d8ed2c..0b1d69d 100644 --- a/truncate_ephemeral_patch/README.md +++ b/truncate_ephemeral_patch/README.md @@ -4,8 +4,25 @@ This patch changes Nova ephemeral image prealloc behaviour by running truncate instead of fallocate. This improves performance with ephemeral images on Quobyte volumes. This change may have a possible performance impact on other backends. -### Usage +The patch comes in two versions: -This patch can be applied by navigating to the Nova project root directory and running: +1. _truncate-ephemeral_-Pike.patch_ - The pure driver patch for patching package based installations that strip tests and other development elements +2. _truncate-ephemeral_-Pike_full_source_tree.patch_ - The full source patch including updates of unit tests and release notes files + + +### Installation + +The patch usage is slightly different for both versions (pure driver vs. full source patch). The difference resides in the -p parameter for the patch command and is applied as follows: + +#### Usage for the pure driver patch + +This patch can be applied by navigating to the Cinder project package root directory and running: + + patch -p1 < /path/to/patchfile + + +#### Usage for the full source tree driver patch + +This patch can be applied by navigating to the Cinder project source root directory and running: patch -p0 < /path/to/patchfile diff --git a/xattr-removal_patch/Mitaka/xattr-removal_Nova-Mitaka.patch b/xattr-removal_patch/Mitaka/xattr-removal_Nova-Mitaka.patch new file mode 100644 index 0000000..6e2723e --- /dev/null +++ b/xattr-removal_patch/Mitaka/xattr-removal_Nova-Mitaka.patch @@ -0,0 +1,102 @@ +diff --git a/nova/virt/libvirt/volume/quobyte.py b/nova/virt/libvirt/volume/quobyte.py +index 284a084..97a36ea 100644 +--- a/nova/virt/libvirt/volume/quobyte.py ++++ b/nova/virt/libvirt/volume/quobyte.py +@@ -19,6 +19,7 @@ + from oslo_concurrency import processutils + from oslo_log import log as logging + from oslo_utils import fileutils ++import psutil + import six + + import nova.conf +@@ -26,6 +27,7 @@ + from nova.i18n import _ + from nova.i18n import _LE + from nova.i18n import _LI ++from nova.i18n import _LW + from nova import utils + from nova.virt.libvirt import utils as libvirt_utils + from nova.virt.libvirt.volume import fs +@@ -44,7 +46,7 @@ + """Wraps execute calls for mounting a Quobyte volume""" + fileutils.ensure_tree(mnt_base) + +- command = ['mount.quobyte', volume, mnt_base] ++ command = ['mount.quobyte', '--disable-xattrs', volume, mnt_base] + if configfile: + command.extend(['-c', configfile]) + +@@ -69,21 +71,46 @@ + mnt_base) + + +-def validate_volume(mnt_base): +- """Wraps execute calls for checking validity of a Quobyte volume""" +- command = ['getfattr', "-n", "quobyte.info", mnt_base] +- try: +- utils.execute(*command) +- except processutils.ProcessExecutionError as exc: +- msg = (_("The mount %(mount_path)s is not a valid" +- " Quobyte volume. Error: %(exc)s") +- % {'mount_path': mnt_base, 'exc': exc}) +- raise nova_exception.InternalError(msg) +- +- if not os.access(mnt_base, os.W_OK | os.X_OK): +- msg = (_LE("Volume is not writable. Please broaden the file" +- " permissions. Mount: %s") % mnt_base) +- raise nova_exception.InternalError(msg) ++def validate_volume(mount_path): ++ """Runs a number of tests on the expected Quobyte mount""" ++ partitions = psutil.disk_partitions(all=True) ++ for p in partitions: ++ if mount_path != p.mountpoint: ++ continue ++ if p.device.startswith("quobyte@"): ++ try: ++ statresult = os.stat(mount_path) ++ # Note(kaisers): Quobyte always shows mount points with size 0 ++ if statresult.st_size == 0: ++ # client looks healthy ++ if not os.access(mount_path, ++ os.W_OK | os.X_OK): ++ LOG.warning(_LW("Volume is not writable. " ++ "Please broaden the file" ++ " permissions." ++ " Mount: %s"), ++ mount_path) ++ return # we're happy here ++ else: ++ msg = (_("The mount %(mount_path)s is not a " ++ "valid Quobyte volume. Stale mount?") ++ % {'mount_path': mount_path}) ++ raise nova_exception.InvalidVolume(msg) ++ except Exception as exc: ++ msg = (_("The mount %(mount_path)s is not a valid" ++ " Quobyte volume. Error: %(exc)s . " ++ " Possibly a Quobyte client crash?") ++ % {'mount_path': mount_path, 'exc': exc}) ++ raise nova_exception.InvalidVolume(msg) ++ else: ++ msg = (_("The mount %(mount_path)s is not a valid" ++ " Quobyte volume according to partition list.") ++ % {'mount_path': mount_path}) ++ raise nova_exception.InvalidVolume(msg) ++ msg = (_("No matching Quobyte mount entry for %(mount_path)s" ++ " could be found for validation in partition list.") ++ % {'mount_path': mount_path}) ++ raise nova_exception.InvalidVolume(msg) + + + class LibvirtQuobyteVolumeDriver(fs.LibvirtBaseFileSystemVolumeDriver): +@@ -127,8 +154,8 @@ + + if not mounted: + mount_volume(quobyte_volume, +- mount_path, +- CONF.libvirt.quobyte_client_cfg) ++ mount_path, ++ CONF.libvirt.quobyte_client_cfg) + + validate_volume(mount_path) + \ No newline at end of file diff --git a/xattr-removal_patch/Mitaka/xattr-removal_mitaka-nova_plain-diff.patch b/xattr-removal_patch/Mitaka/xattr-removal_Nova-Mitaka_full_source_tree.patch similarity index 100% rename from xattr-removal_patch/Mitaka/xattr-removal_mitaka-nova_plain-diff.patch rename to xattr-removal_patch/Mitaka/xattr-removal_Nova-Mitaka_full_source_tree.patch diff --git a/xattr-removal_patch/Mitaka/xattr-removal_Nova-Mitaka_upon-systemd-run-patch.patch b/xattr-removal_patch/Mitaka/xattr-removal_Nova-Mitaka_upon-systemd-run-patch.patch new file mode 100644 index 0000000..c947e2c --- /dev/null +++ b/xattr-removal_patch/Mitaka/xattr-removal_Nova-Mitaka_upon-systemd-run-patch.patch @@ -0,0 +1,96 @@ +diff --git a/nova/virt/libvirt/volume/quobyte.py b/nova/virt/libvirt/volume/quobyte.py +index 05e2933..0ad8138 100644 +--- a/nova/virt/libvirt/volume/quobyte.py ++++ b/nova/virt/libvirt/volume/quobyte.py +@@ -20,12 +20,14 @@ from oslo_concurrency import processutils + from oslo_config import cfg + from oslo_log import log as logging + from oslo_utils import fileutils ++import psutil + import six + + from nova import exception as nova_exception + from nova.i18n import _ + from nova.i18n import _LE + from nova.i18n import _LI ++from nova.i18n import _LW + from nova import paths + from nova import utils + from nova.virt.libvirt import utils as libvirt_utils +@@ -55,11 +57,12 @@ def mount_volume(volume, mnt_base, configfile=None): + """Wraps execute calls for mounting a Quobyte volume""" + fileutils.ensure_tree(mnt_base) + +- command = ['mount.quobyte', volume, mnt_base] ++ command = ['mount.quobyte', '--disable-xattrs', volume, mnt_base] + if os.path.exists(" /run/systemd/system"): + # Note(kaisers): with systemd this requires a separate CGROUP to + # prevent Nova service stop/restarts from killing the mount. +- command = ['systemd-run', 'mount.quobyte', '-f', volume, mnt_base] ++ command = ['systemd-run', 'mount.quobyte', '-f', ++ '--disable-xattrs', volume, mnt_base] + if configfile: + command.extend(['-c', configfile]) + +@@ -84,21 +87,46 @@ def umount_volume(mnt_base): + mnt_base) + + +-def validate_volume(mnt_base): +- """Wraps execute calls for checking validity of a Quobyte volume""" +- command = ['getfattr', "-n", "quobyte.info", mnt_base] +- try: +- utils.execute(*command) +- except processutils.ProcessExecutionError as exc: +- msg = (_("The mount %(mount_path)s is not a valid" +- " Quobyte volume. Error: %(exc)s") +- % {'mount_path': mnt_base, 'exc': exc}) +- raise nova_exception.NovaException(msg) +- +- if not os.access(mnt_base, os.W_OK | os.X_OK): +- msg = (_LE("Volume is not writable. Please broaden the file" +- " permissions. Mount: %s") % mnt_base) +- raise nova_exception.NovaException(msg) ++def validate_volume(mount_path): ++ """Runs a number of tests on the expected Quobyte mount""" ++ partitions = psutil.disk_partitions(all=True) ++ for p in partitions: ++ if mount_path != p.mountpoint: ++ continue ++ if p.device.startswith("quobyte@"): ++ try: ++ statresult = os.stat(mount_path) ++ # Note(kaisers): Quobyte always shows mount points with size 0 ++ if statresult.st_size == 0: ++ # client looks healthy ++ if not os.access(mount_path, ++ os.W_OK | os.X_OK): ++ LOG.warning(_LW("Volume is not writable. " ++ "Please broaden the file" ++ " permissions." ++ " Mount: %s"), ++ mount_path) ++ return # we're happy here ++ else: ++ msg = (_("The mount %(mount_path)s is not a " ++ "valid Quobyte volume. Stale mount?") ++ % {'mount_path': mount_path}) ++ raise nova_exception.InvalidVolume(msg) ++ except Exception as exc: ++ msg = (_("The mount %(mount_path)s is not a valid" ++ " Quobyte volume. Error: %(exc)s . " ++ " Possibly a Quobyte client crash?") ++ % {'mount_path': mount_path, 'exc': exc}) ++ raise nova_exception.InvalidVolume(msg) ++ else: ++ msg = (_("The mount %(mount_path)s is not a valid" ++ " Quobyte volume according to partition list.") ++ % {'mount_path': mount_path}) ++ raise nova_exception.InvalidVolume(msg) ++ msg = (_("No matching Quobyte mount entry for %(mount_path)s" ++ " could be found for validation in partition list.") ++ % {'mount_path': mount_path}) ++ raise nova_exception.InvalidVolume(msg) + + + class LibvirtQuobyteVolumeDriver(fs.LibvirtBaseFileSystemVolumeDriver): diff --git a/xattr-removal_patch/Mitaka/xattr-removal_mitaka-nova_upon-systemd-run-patch.patch b/xattr-removal_patch/Mitaka/xattr-removal_Nova-Mitaka_upon-systemd-run-patch_full_source_tree.patch similarity index 100% rename from xattr-removal_patch/Mitaka/xattr-removal_mitaka-nova_upon-systemd-run-patch.patch rename to xattr-removal_patch/Mitaka/xattr-removal_Nova-Mitaka_upon-systemd-run-patch_full_source_tree.patch diff --git a/xattr-removal_patch/Ocata/xattr-removal_Nova-Ocata.patch b/xattr-removal_patch/Ocata/xattr-removal_Nova-Ocata.patch new file mode 100644 index 0000000..6e2723e --- /dev/null +++ b/xattr-removal_patch/Ocata/xattr-removal_Nova-Ocata.patch @@ -0,0 +1,102 @@ +diff --git a/nova/virt/libvirt/volume/quobyte.py b/nova/virt/libvirt/volume/quobyte.py +index 284a084..97a36ea 100644 +--- a/nova/virt/libvirt/volume/quobyte.py ++++ b/nova/virt/libvirt/volume/quobyte.py +@@ -19,6 +19,7 @@ + from oslo_concurrency import processutils + from oslo_log import log as logging + from oslo_utils import fileutils ++import psutil + import six + + import nova.conf +@@ -26,6 +27,7 @@ + from nova.i18n import _ + from nova.i18n import _LE + from nova.i18n import _LI ++from nova.i18n import _LW + from nova import utils + from nova.virt.libvirt import utils as libvirt_utils + from nova.virt.libvirt.volume import fs +@@ -44,7 +46,7 @@ + """Wraps execute calls for mounting a Quobyte volume""" + fileutils.ensure_tree(mnt_base) + +- command = ['mount.quobyte', volume, mnt_base] ++ command = ['mount.quobyte', '--disable-xattrs', volume, mnt_base] + if configfile: + command.extend(['-c', configfile]) + +@@ -69,21 +71,46 @@ + mnt_base) + + +-def validate_volume(mnt_base): +- """Wraps execute calls for checking validity of a Quobyte volume""" +- command = ['getfattr', "-n", "quobyte.info", mnt_base] +- try: +- utils.execute(*command) +- except processutils.ProcessExecutionError as exc: +- msg = (_("The mount %(mount_path)s is not a valid" +- " Quobyte volume. Error: %(exc)s") +- % {'mount_path': mnt_base, 'exc': exc}) +- raise nova_exception.InternalError(msg) +- +- if not os.access(mnt_base, os.W_OK | os.X_OK): +- msg = (_LE("Volume is not writable. Please broaden the file" +- " permissions. Mount: %s") % mnt_base) +- raise nova_exception.InternalError(msg) ++def validate_volume(mount_path): ++ """Runs a number of tests on the expected Quobyte mount""" ++ partitions = psutil.disk_partitions(all=True) ++ for p in partitions: ++ if mount_path != p.mountpoint: ++ continue ++ if p.device.startswith("quobyte@"): ++ try: ++ statresult = os.stat(mount_path) ++ # Note(kaisers): Quobyte always shows mount points with size 0 ++ if statresult.st_size == 0: ++ # client looks healthy ++ if not os.access(mount_path, ++ os.W_OK | os.X_OK): ++ LOG.warning(_LW("Volume is not writable. " ++ "Please broaden the file" ++ " permissions." ++ " Mount: %s"), ++ mount_path) ++ return # we're happy here ++ else: ++ msg = (_("The mount %(mount_path)s is not a " ++ "valid Quobyte volume. Stale mount?") ++ % {'mount_path': mount_path}) ++ raise nova_exception.InvalidVolume(msg) ++ except Exception as exc: ++ msg = (_("The mount %(mount_path)s is not a valid" ++ " Quobyte volume. Error: %(exc)s . " ++ " Possibly a Quobyte client crash?") ++ % {'mount_path': mount_path, 'exc': exc}) ++ raise nova_exception.InvalidVolume(msg) ++ else: ++ msg = (_("The mount %(mount_path)s is not a valid" ++ " Quobyte volume according to partition list.") ++ % {'mount_path': mount_path}) ++ raise nova_exception.InvalidVolume(msg) ++ msg = (_("No matching Quobyte mount entry for %(mount_path)s" ++ " could be found for validation in partition list.") ++ % {'mount_path': mount_path}) ++ raise nova_exception.InvalidVolume(msg) + + + class LibvirtQuobyteVolumeDriver(fs.LibvirtBaseFileSystemVolumeDriver): +@@ -127,8 +154,8 @@ + + if not mounted: + mount_volume(quobyte_volume, +- mount_path, +- CONF.libvirt.quobyte_client_cfg) ++ mount_path, ++ CONF.libvirt.quobyte_client_cfg) + + validate_volume(mount_path) + \ No newline at end of file diff --git a/xattr-removal_patch/Ocata/xattr-removal_ocata-nova_plain-diff.patch b/xattr-removal_patch/Ocata/xattr-removal_Nova-Ocata_full_source_tree.patch similarity index 100% rename from xattr-removal_patch/Ocata/xattr-removal_ocata-nova_plain-diff.patch rename to xattr-removal_patch/Ocata/xattr-removal_Nova-Ocata_full_source_tree.patch diff --git a/xattr-removal_patch/README.md b/xattr-removal_patch/README.md index 4b4ddb9..42abf68 100644 --- a/xattr-removal_patch/README.md +++ b/xattr-removal_patch/README.md @@ -1,13 +1,45 @@ ## xattr-removal_patch -Backports of performance optimizations that remove the usage of xattr from the Nova driver and mount Quobyte volumes without xattr support, in order to improve iops. This patch comes in two flavors: +Backports of performance optimizations that remove the usage of xattr from the Nova driver and mount Quobyte volumes without xattr support, in order to improve iops. This patch comes in two flavors, one for patching the plain vanilla release code and one for patching installations that have previously applied the systemd-cgroup patch from this repository. -* xattr-removal_mitaka-nova_plain-diff.patch: patches xattr out of the vanilla release code -* xattr-removal_mitaka-nova_upon-systemd-run-patch.patch: patches xattr out of code that has previously been patched with the systemd-cgroup patch from this repository +Both flavors come in two versions, the vanilla release : -### Usage +1. _xattr-removal_Nova-.patch_ - The pure driver patch for patching package based installations that strip tests and other development elements +2. _xattr-removal_-Mitaka_full_source_tree.patch_ - The full source patch including updates of unit tests and release notes files -These patches can be applied by navigating to the project to be patched root directory and running: +And the _upon-systemd-run-patch_ versions: + +1. _xattr-removal_Nova-_upon-systemd-run-patch.patch_ - The pure driver patch for patching package based installations that strip tests and other development elements +2. _xattr-removal_-Mitaka_upon-systemd-run-patch_full_source_tree.patch_ - The full source patch including updates of unit tests and release notes files + + +### Installation + +The patch usage is slightly different for both versions (pure driver vs. full source patch). The difference resides in the -p parameter for the patch command and is applied as follows: + +#### Usage for the pure driver patch + +This patch can be applied by navigating to the Nova project package root directory and running: + + patch -p2 < /path/to/patchfile + + +#### Usage for the full source tree driver patch + +This patch can be applied by navigating to the Nova project source root directory and running: patch -p1 < /path/to/patchfile + + +### Configuration + +Some older releases may have incomplete Nova rootwrap configurations. This can be checked by looking for the following entries in the Nova rootwrap configuration file _compute.filters_ : + + # nova/virt/libvirt/volume/quobyte.py + mount.quobyte: CommandFilter, mount.quobyte, root + umount.quobyte: CommandFilter, umount.quobyte, root + +If the entries are not present you can copy & paste from here. + +The exact location of the _compute.filters_ file is set in _/etc/nova/rootwrap.conf_ .