summaryrefslogtreecommitdiffstats
path: root/nova/virt
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2012-11-01 17:27:28 +0000
committerGerrit Code Review <review@openstack.org>2012-11-01 17:27:28 +0000
commit6ee9883b8cb1ef8e503a03229a100e50813abe5a (patch)
treefd155c8bda8c3e9a8f9f7baefa1f3fbf78e1cb2a /nova/virt
parentc49d96e08121c89d42a3bcbcece63fd671f1a63d (diff)
parentb04213f5dc75b507ee5bc7fe16af7f7b8a3b0c3a (diff)
Merge "Move to a more canonicalized output from qemu-img info."
Diffstat (limited to 'nova/virt')
-rw-r--r--nova/virt/disk/api.py4
-rw-r--r--nova/virt/images.py164
-rw-r--r--nova/virt/libvirt/utils.py18
3 files changed, 146 insertions, 40 deletions
diff --git a/nova/virt/disk/api.py b/nova/virt/disk/api.py
index 0cddcfa69..e113391a5 100644
--- a/nova/virt/disk/api.py
+++ b/nova/virt/disk/api.py
@@ -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):
diff --git a/nova/virt/images.py b/nova/virt/images.py
index 133f5e25b..5b631a0da 100644
--- a/nova/virt/images.py
+++ b/nova/virt/images.py
@@ -22,6 +22,7 @@ Handling of VM disk images.
"""
import os
+import re
from nova import exception
from nova import flags
@@ -43,31 +44,142 @@ FLAGS = flags.FLAGS
FLAGS.register_opts(image_opts)
-def qemu_img_info(path):
- """Return a dict containing the parsed output from qemu-img info."""
+class QemuImgInfo(object):
+ BACKING_FILE_RE = re.compile((r"^(.*?)\s*\(actual\s+path\s*:"
+ r"\s+(.*?)\)\s*$"), re.I)
+ TOP_LEVEL_RE = re.compile(r"^([\w\d\s\_\-]+):(.*)$")
+ SIZE_RE = re.compile(r"\(\s*(\d+)\s+bytes\s*\)", re.I)
+
+ def __init__(self, cmd_output):
+ details = self._parse(cmd_output)
+ self.image = details.get('image')
+ self.backing_file = details.get('backing_file')
+ self.file_format = details.get('file_format')
+ self.virtual_size = details.get('virtual_size')
+ self.cluster_size = details.get('cluster_size')
+ self.disk_size = details.get('disk_size')
+ self.snapshots = details.get('snapshot_list', [])
+ self.encryption = details.get('encryption')
+
+ def __str__(self):
+ lines = [
+ 'image: %s' % self.image,
+ 'file_format: %s' % self.file_format,
+ 'virtual_size: %s' % self.virtual_size,
+ 'disk_size: %s' % self.disk_size,
+ 'cluster_size: %s' % self.cluster_size,
+ 'backing_file: %s' % self.backing_file,
+ ]
+ if self.snapshots:
+ lines.append("snapshots: %s" % self.snapshots)
+ return "\n".join(lines)
+
+ def _canonicalize(self, field):
+ # Standardize on underscores/lc/no dash and no spaces
+ # since qemu seems to have mixed outputs here... and
+ # this format allows for better integration with python
+ # - ie for usage in kwargs and such...
+ field = field.lower().strip()
+ for c in (" ", "-"):
+ field = field.replace(c, '_')
+ return field
+
+ def _extract_bytes(self, details):
+ # Replace it with the byte amount
+ real_size = self.SIZE_RE.search(details)
+ if real_size:
+ details = real_size.group(1)
+ try:
+ details = utils.to_bytes(details)
+ except (TypeError, ValueError):
+ pass
+ return details
+
+ def _extract_details(self, root_cmd, root_details, lines_after):
+ consumed_lines = 0
+ real_details = root_details
+ if root_cmd == 'backing_file':
+ # Replace it with the real backing file
+ backing_match = self.BACKING_FILE_RE.match(root_details)
+ if backing_match:
+ real_details = backing_match.group(2).strip()
+ elif root_cmd in ['virtual_size', 'cluster_size', 'disk_size']:
+ # Replace it with the byte amount (if we can convert it)
+ real_details = self._extract_bytes(root_details)
+ elif root_cmd == 'file_format':
+ real_details = real_details.strip().lower()
+ elif root_cmd == 'snapshot_list':
+ # Next line should be a header, starting with 'ID'
+ if not lines_after or not lines_after[0].startswith("ID"):
+ msg = _("Snapshot list encountered but no header found!")
+ raise ValueError(msg)
+ consumed_lines += 1
+ possible_contents = lines_after[1:]
+ real_details = []
+ # This is the sprintf pattern we will try to match
+ # "%-10s%-20s%7s%20s%15s"
+ # ID TAG VM SIZE DATE VM CLOCK (current header)
+ for line in possible_contents:
+ line_pieces = line.split(None)
+ if len(line_pieces) != 6:
+ break
+ else:
+ # Check against this pattern occuring in the final position
+ # "%02d:%02d:%02d.%03d"
+ date_pieces = line_pieces[5].split(":")
+ if len(date_pieces) != 3:
+ break
+ real_details.append({
+ 'id': line_pieces[0],
+ 'tag': line_pieces[1],
+ 'vm_size': line_pieces[2],
+ 'date': line_pieces[3],
+ 'vm_clock': line_pieces[4] + " " + line_pieces[5],
+ })
+ consumed_lines += 1
+ return (real_details, consumed_lines)
+
+ def _parse(self, cmd_output):
+ # Analysis done of qemu-img.c to figure out what is going on here
+ # Find all points start with some chars and then a ':' then a newline
+ # and then handle the results of those 'top level' items in a separate
+ # function.
+ #
+ # TODO(harlowja): newer versions might have a json output format
+ # we should switch to that whenever possible.
+ # see: http://bit.ly/XLJXDX
+ if not cmd_output:
+ cmd_output = ''
+ contents = {}
+ lines = cmd_output.splitlines()
+ i = 0
+ line_am = len(lines)
+ while i < line_am:
+ line = lines[i]
+ if not line.strip():
+ i += 1
+ continue
+ consumed_lines = 0
+ top_level = self.TOP_LEVEL_RE.match(line)
+ if top_level:
+ root = self._canonicalize(top_level.group(1))
+ if not root:
+ i += 1
+ continue
+ root_details = top_level.group(2).strip()
+ details, consumed_lines = self._extract_details(root,
+ root_details,
+ lines[i + 1:])
+ contents[root] = details
+ i += consumed_lines + 1
+ return contents
+
+def qemu_img_info(path):
+ """Return a object containing the parsed output from qemu-img info."""
out, err = utils.execute('env', 'LC_ALL=C', 'LANG=C',
'qemu-img', 'info', path)
-
- # output of qemu-img is 'field: value'
- # except when its in the snapshot listing mode
- data = {}
- for line in out.splitlines():
- pieces = line.split(':', 1)
- if len(pieces) != 2:
- continue
- (field, val) = pieces
- field = field.strip().lower()
- val = val.strip()
- if field == 'snapshot list':
- # Skip everything after the snapshot list
- # which is safe to do since the code prints
- # these out at the end and nobody currently
- # uses this information in openstack as-is.
- break
- data[field] = val
-
- return data
+ return QemuImgInfo(out)
def convert_image(source, dest, out_format):
@@ -95,13 +207,13 @@ def fetch_to_raw(context, image_href, path, user_id, project_id):
with utils.remove_path_on_error(path_tmp):
data = qemu_img_info(path_tmp)
- fmt = data.get('file format')
+ fmt = data.file_format
if fmt is None:
raise exception.ImageUnacceptable(
reason=_("'qemu-img info' parsing failed."),
image_id=image_href)
- backing_file = data.get('backing file')
+ backing_file = data.backing_file
if backing_file is not None:
raise exception.ImageUnacceptable(image_id=image_href,
reason=_("fmt=%(fmt)s backed by: %(backing_file)s") % locals())
@@ -113,10 +225,10 @@ def fetch_to_raw(context, image_href, path, user_id, project_id):
convert_image(path_tmp, staged, 'raw')
data = qemu_img_info(staged)
- if data.get('file format') != "raw":
+ if data.file_format != "raw":
raise exception.ImageUnacceptable(image_id=image_href,
reason=_("Converted to raw, but format is now %s") %
- data.get('file format'))
+ data.file_format)
os.rename(staged, path)
diff --git a/nova/virt/libvirt/utils.py b/nova/virt/libvirt/utils.py
index fe54cacec..5da0aa6fb 100644
--- a/nova/virt/libvirt/utils.py
+++ b/nova/virt/libvirt/utils.py
@@ -94,18 +94,18 @@ def create_cow_image(backing_file, path):
cow_opts += ['backing_file=%s' % backing_file]
base_details = images.qemu_img_info(backing_file)
else:
- base_details = {}
+ base_details = None
# This doesn't seem to get inherited so force it to...
# http://paste.ubuntu.com/1213295/
# TODO(harlowja) probably file a bug against qemu-img/qemu
- if 'cluster_size' in base_details:
- cow_opts += ['cluster_size=%s' % base_details['cluster_size']]
+ if base_details and base_details.cluster_size is not None:
+ cow_opts += ['cluster_size=%s' % base_details.cluster_size]
# For now don't inherit this due the following discussion...
# See: http://www.gossamer-threads.com/lists/openstack/dev/10592
# if 'preallocation' in base_details:
# cow_opts += ['preallocation=%s' % base_details['preallocation']]
- if 'encryption' in base_details:
- cow_opts += ['encryption=%s' % base_details['encryption']]
+ if base_details and base_details.encryption:
+ cow_opts += ['encryption=%s' % base_details.encryption]
if cow_opts:
# Format as a comma separated list
csv_opts = ",".join(cow_opts)
@@ -228,8 +228,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]
+ size = images.qemu_img_info(path).virtual_size
return int(size)
@@ -239,11 +238,8 @@ def get_disk_backing_file(path):
:param path: Path to the disk image
:returns: a path to the image's backing store
"""
- backing_file = images.qemu_img_info(path).get('backing file')
-
+ backing_file = images.qemu_img_info(path).backing_file
if backing_file:
- if 'actual path: ' in backing_file:
- backing_file = backing_file.split('actual path: ')[1][:-1]
backing_file = os.path.basename(backing_file)
return backing_file