diff options
| author | Jenkins <jenkins@review.openstack.org> | 2012-11-01 17:27:28 +0000 |
|---|---|---|
| committer | Gerrit Code Review <review@openstack.org> | 2012-11-01 17:27:28 +0000 |
| commit | 6ee9883b8cb1ef8e503a03229a100e50813abe5a (patch) | |
| tree | fd155c8bda8c3e9a8f9f7baefa1f3fbf78e1cb2a /nova/virt | |
| parent | c49d96e08121c89d42a3bcbcece63fd671f1a63d (diff) | |
| parent | b04213f5dc75b507ee5bc7fe16af7f7b8a3b0c3a (diff) | |
Merge "Move to a more canonicalized output from qemu-img info."
Diffstat (limited to 'nova/virt')
| -rw-r--r-- | nova/virt/disk/api.py | 4 | ||||
| -rw-r--r-- | nova/virt/images.py | 164 | ||||
| -rw-r--r-- | nova/virt/libvirt/utils.py | 18 |
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 |
