Repository URL to install this package:
Version:
3.3.29 ▾
|
python3-tvault-contego
/
usr
/
lib
/
python3
/
dist-packages
/
contego
/
nova
/
extension
/
driver
/
qemuimages.py
|
---|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2013 TrilioData, Inc.
# All Rights Reserved.
"""
Handling of VM disk images.
"""
import re
import time
import os
try:
from oslo_log import log as logging
except ImportError:
from nova.openstack.common import log as logging
try:
from oslo_config import cfg
except ImportError:
from oslo.config import cfg
from nova import utils
LOG = logging.getLogger(__name__)
contego_qemuimage_opts = [
cfg.BoolOpt('force_raw_images',
default=True,
help='Force backing images to raw format'),
]
CONF = cfg.CONF
if 'force_raw_images' not in list(CONF.keys()):
CONF.register_opts(contego_qemuimage_opts)
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)
"""
BYTE_MULTIPLIERS = {
'': 1,
't': 1024 ** 4,
'g': 1024 ** 3,
'm': 1024 ** 2,
'k': 1024,
}
# 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(float(text) * multiplier)
except KeyError:
msg = ('Unknown byte multiplier: %s') % mult_key_org
raise TypeError(msg)
except ValueError:
return default
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=None):
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 = 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 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 copy_to_temp(src, dst):
out, err = utils.execute('env', 'LC_ALL=C', 'LANG=C',
'cp', src, dst, run_as_root=False)
if err == '':
return True
return False
def qemu_img_info(path):
"""Return an object containing the parsed output from qemu-img info."""
try:
out, err = utils.execute('env', 'LC_ALL=C', 'LANG=C',
'qemu-img', 'info', '--image-opts',
'file.locking=off,file.filename=%s'%path, run_as_root=False)
if "--force-share" in err:
out, err = utils.execute('env', 'LC_ALL=C', 'LANG=C',
'qemu-img', 'info', path, run_as_root=False)
return QemuImgInfo(out)
except Exception as ex:
LOG.exception(ex)
# Sometimes it fails to get "consistent read" lock.
time.sleep(30)
out, err = utils.execute('env', 'LC_ALL=C', 'LANG=C',
'qemu-img', 'info', '--image-opts',
'file.locking=off,file.filename=%s'%path, run_as_root=False)
if "--force-share" in err:
out, err = utils.execute('env', 'LC_ALL=C', 'LANG=C',
'qemu-img', 'info', path, run_as_root=False)
return QemuImgInfo(out)
def qemu_check_n_resize(path, vsize):
"""Return an object containing the parsed output from qemu-img info."""
try:
out, err = utils.execute('env', 'LC_ALL=C', 'LANG=C',
'qemu-img', 'check', '-r', 'all', path,
run_as_root=False)
except Exception as ex:
LOG.exception(ex)
# Sometimes it fails to get "consistent read" lock.
time.sleep(30)
out, err = utils.execute('env', 'LC_ALL=C', 'LANG=C',
'qemu-img', 'check', '-r', 'all', path,
run_as_root=False)
resize_image(path, vsize)
def convert_image(source, dest, out_format, run_as_root=False):
"""Convert image to other format."""
cmd = ('env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'convert') + \
('-O', out_format, source, dest)
utils.execute(*cmd, run_as_root=run_as_root)
def rebase_qcow2(backing_file_base, backing_file_top, run_as_root=False):
"""rebase the backing_file_top to backing_file_base using unsafe mode
:param backing_file_base: backing file to rebase to
:param backing_file_top: top file to rebase
"""
try:
utils.execute('qemu-img', 'rebase', '-u', '-b',
backing_file_base, backing_file_top,
run_as_root=run_as_root)
except Exception as ex:
LOG.exception("qemu-img Errored. Retrying: %s", ex)
time.sleep(30)
utils.execute('qemu-img', 'rebase', '-u', '-b',
backing_file_base, backing_file_top,
run_as_root=run_as_root)
def commit_qcow2(backing_file_top, run_as_root=False):
"""rebase the backing_file_top to backing_file_base
:param backing_file_top: top file to commit from to its base
"""
try:
utils.execute('qemu-img', 'commit', backing_file_top,
run_as_root=run_as_root)
except Exception as ex:
if int(ex.exit_code) in [1, 13]:
try:
user_id = str(os.getuid())
utils.execute('chown', user_id + ':' + user_id,
backing_file_top, run_as_root=False)
qemuinfo = qemu_img_info(backing_file_top)
utils.execute('chown', user_id + ':' + user_id,
qemuinfo.backing_file, run_as_root=False)
except Exception as ex:
LOG.exception(ex)
try:
user_id = str(os.getuid())
utils.execute('chown', user_id + ':' + user_id,
backing_file_top, run_as_root=True)
qemuinfo = qemu_img_info(backing_file_top)
utils.execute('chown', user_id + ':' + user_id,
qemuinfo.backing_file, run_as_root=True)
except Exception as ex:
LOG.exception(ex)
utils.execute('qemu-img', 'commit', backing_file_top,
run_as_root=run_as_root)
else:
raise
def resize_image(path, new_size, run_as_root=False):
"""rebase the backing_file_top to backing_file_base
:param backing_file_top: top file to commit from to its base
"""
utils.execute(
'qemu-img',
'resize',
path,
new_size,
run_as_root=run_as_root)
def create_cow_image(backing_file, path, size=None):
"""Create COW image
Creates a COW image with the given backing file
:param backing_file: Existing image on which to base the COW image
:param path: Desired location of the COW image
"""
base_cmd = ['qemu-img', 'create', '-f', 'qcow2']
cow_opts = []
if backing_file:
cow_opts += ['backing_file=%s' % backing_file]
base_details = qemu_img_info(backing_file)
else:
base_details = None
# Explicitly inherit the value of 'cluster_size' property of a qcow2
# overlay image from its backing file. This can be useful in cases
# when people create a base image with a non-default 'cluster_size'
# value or cases when images were created with very old QEMU
# versions which had a different default 'cluster_size'.
if base_details and base_details.cluster_size is not None:
cow_opts += ['cluster_size=%s' % base_details.cluster_size]
if size is not None:
cow_opts += ['size=%s' % size]
if cow_opts:
# Format as a comma separated list
csv_opts = ",".join(cow_opts)
cow_opts = ['-o', csv_opts]
cmd = base_cmd + cow_opts + [path]
utils.execute(*cmd)
def get_disk_backing_file(path, basename=True, format=None):
"""Get the backing file of a disk image
:param path: Path to the disk image
:returns: a path to the image's backing store
"""
backing_file = qemu_img_info(path).backing_file
if backing_file and basename:
backing_file = os.path.basename(backing_file)
return backing_file