From 12548cc311d43d704d25c25a31478941ede27a6b Mon Sep 17 00:00:00 2001 From: Tomoki Sekiyama Date: Fri, 13 Jun 2014 18:49:24 -0400 Subject: [PATCH] I/O rate limit for volume copy with qemu-img convert Currently, volume copy operations consumes disk I/O bandwidth heavily and may slow down the other guest instances. This patch limits bandwidth for volume copy to mitigate interference to other instance performance. In this implementation, 'qemu-img' is put in a blkio cgroup for throttling, when CONF.volume_copy_bps_limit is set to non-zero. Change-Id: I8403d4bcafc4279654034da97aa9b6c097f9e649 Signed-off-by: Tomoki Sekiyama Implements: blueprint limit-volume-copy-bandwidth --- cinder/image/image_utils.py | 12 ++++-- cinder/tests/test_image_utils.py | 66 ++++++++++++++++++++++++++++---- 2 files changed, 68 insertions(+), 10 deletions(-) diff --git a/cinder/image/image_utils.py b/cinder/image/image_utils.py index 883f73635..2ffe80354 100644 --- a/cinder/image/image_utils.py +++ b/cinder/image/image_utils.py @@ -59,9 +59,13 @@ def qemu_img_info(path): return imageutils.QemuImgInfo(out) -def convert_image(source, dest, out_format): +def convert_image(source, dest, out_format, bps_limit=None): """Convert image to other format.""" cmd = ('qemu-img', 'convert', '-O', out_format, source, dest) + cgcmd = volume_utils.setup_blkio_cgroup(source, dest, bps_limit) + if cgcmd: + cmd = tuple(cgcmd) + cmd + cmd += ('-t', 'none') # required to enable ratelimit by blkio cgroup utils.execute(*cmd, run_as_root=True) @@ -215,7 +219,8 @@ def fetch_to_volume_format(context, image_service, # malicious. LOG.debug("%s was %s, converting to %s " % (image_id, fmt, volume_format)) - convert_image(tmp, dest, volume_format) + convert_image(tmp, dest, volume_format, + bps_limit=CONF.volume_copy_bps_limit) data = qemu_img_info(dest) if data.file_format != volume_format: @@ -251,7 +256,8 @@ def upload_volume(context, image_service, image_meta, volume_path, with fileutils.remove_path_on_error(tmp): LOG.debug("%s was %s, converting to %s" % (image_id, volume_format, image_meta['disk_format'])) - convert_image(volume_path, tmp, image_meta['disk_format']) + convert_image(volume_path, tmp, image_meta['disk_format'], + bps_limit=CONF.volume_copy_bps_limit) data = qemu_img_info(tmp) if data.file_format != image_meta['disk_format']: diff --git a/cinder/tests/test_image_utils.py b/cinder/tests/test_image_utils.py index 03c4dcb40..39666bd30 100644 --- a/cinder/tests/test_image_utils.py +++ b/cinder/tests/test_image_utils.py @@ -19,6 +19,8 @@ import contextlib import mox import tempfile +from oslo.config import cfg + from cinder import context from cinder import exception from cinder.image import image_utils @@ -26,6 +28,9 @@ from cinder.openstack.common import processutils from cinder import test from cinder import units from cinder import utils +from cinder.volume import utils as volume_utils + +CONF = cfg.CONF class FakeImageService: @@ -191,17 +196,21 @@ class TestUtils(test.TestCase): self.assertEqual(str(inf), TEST_STR) - def _test_fetch_to_raw(self, has_qemu=True, src_inf=None, dest_inf=None): + def _test_fetch_to_raw(self, has_qemu=True, src_inf=None, dest_inf=None, + bps_limit=0): mox = self._mox mox.StubOutWithMock(image_utils, 'create_temporary_file') mox.StubOutWithMock(utils, 'execute') mox.StubOutWithMock(image_utils, 'fetch') + mox.StubOutWithMock(volume_utils, 'setup_blkio_cgroup') TEST_INFO = ("image: qemu.qcow2\n" "file format: raw\n" "virtual size: 0 (0 bytes)\n" "disk size: 0") + CONF.set_override('volume_copy_bps_limit', bps_limit) + image_utils.create_temporary_file().AndReturn(self.TEST_DEV_PATH) test_qemu_img = utils.execute( @@ -223,9 +232,19 @@ class TestUtils(test.TestCase): ) if has_qemu and dest_inf: - utils.execute( - 'qemu-img', 'convert', '-O', 'raw', - self.TEST_DEV_PATH, self.TEST_DEV_PATH, run_as_root=True) + if bps_limit: + prefix = ('cgexec', '-g', 'blkio:test') + postfix = ('-t', 'none') + else: + prefix = postfix = () + cmd = prefix + ('qemu-img', 'convert', '-O', 'raw', + self.TEST_DEV_PATH, self.TEST_DEV_PATH) + postfix + + volume_utils.setup_blkio_cgroup( + self.TEST_DEV_PATH, self.TEST_DEV_PATH, + bps_limit).AndReturn(prefix) + + utils.execute(*cmd, run_as_root=True) utils.execute( 'env', 'LC_ALL=C', 'qemu-img', 'info', @@ -254,6 +273,26 @@ class TestUtils(test.TestCase): mox.IgnoreArg()) self._mox.VerifyAll() + def test_fetch_to_raw_with_bps_limit(self): + SRC_INFO = ("image: qemu.qcow2\n" + "file_format: qcow2 \n" + "virtual_size: 50M (52428800 bytes)\n" + "cluster_size: 65536\n" + "disk_size: 196K (200704 bytes)") + DST_INFO = ("image: qemu.raw\n" + "file_format: raw\n" + "virtual_size: 50M (52428800 bytes)\n" + "cluster_size: 65536\n" + "disk_size: 196K (200704 bytes)\n") + + self._test_fetch_to_raw(src_inf=SRC_INFO, dest_inf=DST_INFO, + bps_limit=1048576) + + image_utils.fetch_to_raw(context, self._image_service, + self.TEST_IMAGE_ID, self.TEST_DEV_PATH, + mox.IgnoreArg()) + self._mox.VerifyAll() + def test_fetch_to_raw_no_qemu_img(self): self._test_fetch_to_raw(has_qemu=False) @@ -385,19 +424,29 @@ class TestUtils(test.TestCase): self._test_fetch_verify_image(TEST_RETURN) - def test_upload_volume(self): + def test_upload_volume(self, bps_limit=0): image_meta = {'id': 1, 'disk_format': 'qcow2'} TEST_RET = "image: qemu.qcow2\n"\ "file_format: qcow2 \n"\ "virtual_size: 50M (52428800 bytes)\n"\ "cluster_size: 65536\n"\ "disk_size: 196K (200704 bytes)" + if bps_limit: + CONF.set_override('volume_copy_bps_limit', bps_limit) + prefix = ('cgexec', '-g', 'blkio:test') + postfix = ('-t', 'none') + else: + prefix = postfix = () + cmd = prefix + ('qemu-img', 'convert', '-O', 'qcow2', + mox.IgnoreArg(), mox.IgnoreArg()) + postfix m = self._mox m.StubOutWithMock(utils, 'execute') + m.StubOutWithMock(volume_utils, 'setup_blkio_cgroup') - utils.execute('qemu-img', 'convert', '-O', 'qcow2', - mox.IgnoreArg(), mox.IgnoreArg(), run_as_root=True) + volume_utils.setup_blkio_cgroup(mox.IgnoreArg(), mox.IgnoreArg(), + bps_limit).AndReturn(prefix) + utils.execute(*cmd, run_as_root=True) utils.execute( 'env', 'LC_ALL=C', 'qemu-img', 'info', mox.IgnoreArg(), run_as_root=True).AndReturn( @@ -410,6 +459,9 @@ class TestUtils(test.TestCase): image_meta, '/dev/loop1') m.VerifyAll() + def test_upload_volume_with_bps_limit(self): + self.test_upload_volume(bps_limit=1048576) + def test_upload_volume_with_raw_image(self): image_meta = {'id': 1, 'disk_format': 'raw'} mox = self._mox -- 2.45.2