From 62d63b904e28823f99826e1d906ecdcb74ce70a8 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Mon, 17 Sep 2012 14:53:12 -0700 Subject: [PATCH] Move to a more canonicalized output from qemu-img info. Move to a form that is all lower cased, dashes->underscores, underscores instead of spaces which allows for better integration with python. Also make the parser more robust to failures when encountering new fields such as snapshot lists. Provide a new qemu img info object that can be used to do the parsing and access the underlying attributes. Change-Id: Ie098dbd9f06dd4ef966768e2caa128f1d09b019c --- nova/tests/test_image_utils.py | 100 +++++++++++++++++++++++++++------ nova/utils.py | 38 +++++++++++++ 2 files changed, 121 insertions(+), 17 deletions(-) diff --git a/nova/tests/test_image_utils.py b/nova/tests/test_image_utils.py index 711f1c202..fac0422bf 100644 --- a/nova/tests/test_image_utils.py +++ b/nova/tests/test_image_utils.py @@ -21,7 +21,7 @@ from nova.virt import images class ImageUtilsTestCase(test.TestCase): - def test_qemu_info(self): + def test_qemu_info_canon(self): path = "disk.config" example_output = """image: disk.config file format: raw @@ -35,14 +35,35 @@ blah BLAH: bb 'qemu-img', 'info', path).AndReturn((example_output, '')) self.mox.ReplayAll() image_info = images.qemu_img_info(path) - self.assertEquals('disk.config', image_info['image']) - self.assertEquals('raw', image_info['file format']) - self.assertEquals('64M (67108864 bytes)', image_info['virtual size']) - self.assertEquals('96K', image_info['disk size']) - self.assertEquals('bb', image_info['blah blah']) - self.assertEquals("65536", image_info['cluster_size']) + self.assertEquals('disk.config', image_info.image) + self.assertEquals('raw', image_info.file_format) + self.assertEquals(67108864, image_info.virtual_size) + self.assertEquals(98304, image_info.disk_size) + self.assertEquals(65536, image_info.cluster_size) - def test_qemu_info_snap(self): + def test_qemu_info_canon2(self): + path = "disk.config" + example_output = """image: disk.config +file format: QCOW2 +virtual size: 67108844 +cluster_size: 65536 +disk size: 963434 +backing file: /var/lib/nova/a328c7998805951a_2 +""" + self.mox.StubOutWithMock(utils, 'execute') + utils.execute('env', 'LC_ALL=C', 'LANG=C', + 'qemu-img', 'info', path).AndReturn((example_output, '')) + self.mox.ReplayAll() + image_info = images.qemu_img_info(path) + self.assertEquals('disk.config', image_info.image) + self.assertEquals('qcow2', image_info.file_format) + self.assertEquals(67108844, image_info.virtual_size) + self.assertEquals(963434, image_info.disk_size) + self.assertEquals(65536, image_info.cluster_size) + self.assertEquals('/var/lib/nova/a328c7998805951a_2', + image_info.backing_file) + + def test_qemu_backing_file_actual(self): path = "disk.config" example_output = """image: disk.config file format: raw @@ -52,18 +73,63 @@ disk size: 96K Snapshot list: ID TAG VM SIZE DATE VM CLOCK 1 d9a9784a500742a7bb95627bb3aace38 0 2012-08-20 10:52:46 00:00:00.000 +backing file: /var/lib/nova/a328c7998805951a_2 (actual path: /b/3a988059e51a_2) """ self.mox.StubOutWithMock(utils, 'execute') utils.execute('env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'info', path).AndReturn((example_output, '')) self.mox.ReplayAll() image_info = images.qemu_img_info(path) - self.assertEquals('disk.config', image_info['image']) - self.assertEquals('raw', image_info['file format']) - self.assertEquals('64M (67108864 bytes)', image_info['virtual size']) - self.assertEquals('96K', image_info['disk size']) - self.assertEquals("65536", image_info['cluster_size']) - # This would be triggered if the split encountered this section - self.assertNotIn('snapshot list', image_info) - bad_cap = '1 d9a9784a500742a7bb95627bb3aace38 0 2012-08-20 10' - self.assertNotIn(bad_cap, image_info) + self.assertEquals('disk.config', image_info.image) + self.assertEquals('raw', image_info.file_format) + self.assertEquals(67108864, image_info.virtual_size) + self.assertEquals(98304, image_info.disk_size) + self.assertEquals(1, len(image_info.snapshots)) + self.assertEquals('/b/3a988059e51a_2', + image_info.backing_file) + + def test_qemu_info_convert(self): + path = "disk.config" + example_output = """image: disk.config +file format: raw +virtual size: 64M +disk size: 96K +Snapshot list: +ID TAG VM SIZE DATE VM CLOCK +1 d9a9784a500742a7bb95627bb3aace38 0 2012-08-20 10:52:46 00:00:00.000 +3 d9a9784a500742a7bb95627bb3aace38 0 2012-08-20 10:52:46 00:00:00.000 +4 d9a9784a500742a7bb95627bb3aace38 0 2012-08-20 10:52:46 00:00:00.000 +junk stuff: bbb +""" + self.mox.StubOutWithMock(utils, 'execute') + utils.execute('env', 'LC_ALL=C', 'LANG=C', + 'qemu-img', 'info', path).AndReturn((example_output, '')) + self.mox.ReplayAll() + image_info = images.qemu_img_info(path) + self.assertEquals('disk.config', image_info.image) + self.assertEquals('raw', image_info.file_format) + self.assertEquals(67108864, image_info.virtual_size) + self.assertEquals(98304, image_info.disk_size) + + def test_qemu_info_snaps(self): + path = "disk.config" + example_output = """image: disk.config +file format: raw +virtual size: 64M (67108864 bytes) +disk size: 96K +Snapshot list: +ID TAG VM SIZE DATE VM CLOCK +1 d9a9784a500742a7bb95627bb3aace38 0 2012-08-20 10:52:46 00:00:00.000 +3 d9a9784a500742a7bb95627bb3aace38 0 2012-08-20 10:52:46 00:00:00.000 +4 d9a9784a500742a7bb95627bb3aace38 0 2012-08-20 10:52:46 00:00:00.000 +""" + self.mox.StubOutWithMock(utils, 'execute') + utils.execute('env', 'LC_ALL=C', 'LANG=C', + 'qemu-img', 'info', path).AndReturn((example_output, '')) + self.mox.ReplayAll() + image_info = images.qemu_img_info(path) + self.assertEquals('disk.config', image_info.image) + self.assertEquals('raw', image_info.file_format) + self.assertEquals(67108864, image_info.virtual_size) + self.assertEquals(98304, image_info.disk_size) + self.assertEquals(3, len(image_info.snapshots)) diff --git a/nova/utils.py b/nova/utils.py index 01df19b27..fe81ffd93 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -63,6 +63,16 @@ FLAGS.register_opt( cfg.BoolOpt('disable_process_locking', default=False, help='Whether to disable inter-process locks')) +# Used for looking up extensions of text +# to their 'multiplied' byte amount +BYTE_MULTIPLIERS = { + '': 1, + 't': 1024 ** 4, + 'g': 1024 ** 3, + 'm': 1024 ** 2, + 'k': 1024, +} + def vpn_ping(address, port, timeout=0.05, session_id=None): """Sends a vpn negotiation packet and returns the server session. @@ -574,6 +584,34 @@ def utf8(value): return value +def to_bytes(text, default=0): + """Try to turn a string into a number of bytes. Looks at the last + characters of the text to determine what conversion is needed to + turn the input text into a byte number. + + Supports: B/b, K/k, M/m, G/g, T/t (or the same with b/B on the end) + + """ + # Take off everything not number 'like' (which should leave + # only the byte 'identifier' left) + mult_key_org = text.lstrip('-1234567890') + mult_key = mult_key_org.lower() + mult_key_len = len(mult_key) + if mult_key.endswith("b"): + mult_key = mult_key[0:-1] + try: + multiplier = BYTE_MULTIPLIERS[mult_key] + if mult_key_len: + # Empty cases shouldn't cause text[0:-0] + text = text[0:-mult_key_len] + return int(text) * multiplier + except KeyError: + msg = _('Unknown byte multiplier: %s') % mult_key_org + raise TypeError(msg) + except ValueError: + return default + + def delete_if_exists(pathname): """delete a file, but ignore file not found error"""