Skip to content

Commit

Permalink
Move to a more canonicalized output from qemu-img info.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Joshua Harlow committed Nov 1, 2012
1 parent 33825be commit b04213f
Show file tree
Hide file tree
Showing 6 changed files with 302 additions and 57 deletions.
100 changes: 83 additions & 17 deletions nova/tests/test_image_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@


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
Expand All @@ -35,14 +35,35 @@ def test_qemu_info(self):
'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
Expand All @@ -52,18 +73,63 @@ def test_qemu_info_snap(self):
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(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('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(3, len(image_info.snapshots))
35 changes: 35 additions & 0 deletions nova/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,41 @@
FLAGS = flags.FLAGS


class ByteConversionTest(test.TestCase):
def test_string_conversions(self):
working_examples = {
'1024KB': 1048576,
'1024TB': 1125899906842624,
'1024K': 1048576,
'1024T': 1125899906842624,
'1TB': 1099511627776,
'1T': 1099511627776,
'1KB': 1024,
'1K': 1024,
'1B': 1,
'1B': 1,
'1': 1,
'1MB': 1048576,
'7MB': 7340032,
'0MB': 0,
'0KB': 0,
'0TB': 0,
'': 0,
}
for (in_value, expected_value) in working_examples.items():
b_value = utils.to_bytes(in_value)
self.assertEquals(expected_value, b_value)
if len(in_value):
in_value = "-" + in_value
b_value = utils.to_bytes(in_value)
self.assertEquals(expected_value * -1, b_value)
breaking_examples = [
'junk1KB', '1023BBBB',
]
for v in breaking_examples:
self.assertRaises(TypeError, utils.to_bytes, v)


class ExecuteTestCase(test.TestCase):
def test_retry_on_failure(self):
fd, tmpfilename = tempfile.mkstemp()
Expand Down
38 changes: 38 additions & 0 deletions nova/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@
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.
Expand Down Expand Up @@ -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"""

Expand Down
4 changes: 1 addition & 3 deletions nova/virt/disk/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,7 @@ def get_disk_size(path):
:returns: Size (in bytes) of the given disk image as it would be seen
by a virtual machine.
"""
size = images.qemu_img_info(path)['virtual size']
size = size.split('(')[1].split()[0]
return int(size)
return images.qemu_img_info(path).virtual_size


def extend(image, size):
Expand Down

0 comments on commit b04213f

Please sign in to comment.