Merge lp:~smoser/nova/lp853330 into lp:~hudson-openstack/nova/trunk

Proposed by Scott Moser
Status: Merged
Approved by: Soren Hansen
Approved revision: 1601
Merged at revision: 1604
Proposed branch: lp:~smoser/nova/lp853330
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 85 lines (+60/-1)
2 files modified
nova/virt/images.py (+59/-0)
nova/virt/libvirt/connection.py (+1/-1)
To merge this branch: bzr merge lp:~smoser/nova/lp853330
Reviewer Review Type Date Requested Status
Soren Hansen (community) Approve
Vish Ishaya (community) Approve
Review via email: mp+76164@code.launchpad.net

Commit message

convert images that are not 'raw' to 'raw' during caching to node

Description of the change

convert images that are not 'raw' to 'raw' during caching to node

This uses 'qemu-img' to convert images that are not 'raw' to be 'raw'.
By doing so, it
 a.) refuses to run uploaded images that have a backing image reference
     (LP: #853330, CVE-2011-3147)
 b.) ensures that when FLAGS.use_cow_images is False, and the libvirt
     xml written specifies 'driver_type="raw"' that the disk referenced
     is also raw format. (LP: #837102)
 c.) removes compression that might be present to avoid cpu bottlenecks
     (LP: #837100)

It does have the negative side affect of using more space in the case where
the user uploaded a qcow2 (or other advanced image format) that could have
been used directly by the hypervisor. That could, later, be remedied by
another 'qemu-img convert' being done to the "preferred" format of the
hypervisor.

To post a comment you must log in.
Revision history for this message
Vish Ishaya (vishvananda) wrote :

lgtm

review: Approve
Revision history for this message
Soren Hansen (soren) wrote :

Thanks so much for highlighting and addressing this bug.

A couple of issues:

 * qemu-img's output may be translated. We need to make sure LANG and LC_* either are set to "C" or not set at all, otherwise parsing its output might fail.

 * (as you pointed out yourself to me on IRC:) You don't clean up after yourself if you raise the ImageUnacceptable exception.

 * Calling the final two arguments to fetch_to_raw "_user_id" and "_project_id" suggests they're not used, while in fact they're passed on to fetch (which may or may not ignore them).

review: Needs Fixing
lp:~smoser/nova/lp853330 updated
1601. By Scott Moser

Address Soren's comments:
 * clean up temp files if an ImageUnacceptable is going to be raised
   Note, a qemu-img execution error will not clean up the image, but I
   think thats reasonable. We leave the image on disk so the user can
   easily investigate.
 * Change final 2 arguments to fetch_to_raw to not start with an _
 * use 'env' utility to change environment variables LC_ALL and LANG so
   that qemu-img output parsing is not locale dependent.
   Note, I considered the following, but found using 'env' more readable
     out, err = utils.execute('sh', '-c', 'export LC_ALL=C LANG=C && exec "$@"',
         'qemu-img', 'info', path)

Revision history for this message
Soren Hansen (soren) wrote :

Thanks.

I'd prefer extending nova.utils.execute() to accept an environment dict, but this is fine for now. Thanks again!

review: Approve
Revision history for this message
Scott Moser (smoser) wrote :

On Tue, 20 Sep 2011, Soren Hansen wrote:

> I'd prefer extending nova.utils.execute() to accept an environment dict,
> but this is fine for now. Thanks again!

I agree with that. but did not want to do it for milestone.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/virt/images.py'
2--- nova/virt/images.py 2011-09-10 17:56:54 +0000
3+++ nova/virt/images.py 2011-09-20 09:48:25 +0000
4@@ -21,6 +21,9 @@
5 Handling of VM disk images.
6 """
7
8+import os
9+
10+from nova import exception
11 from nova import flags
12 from nova.image import glance as glance_image_service
13 import nova.image
14@@ -42,3 +45,59 @@
15 with open(path, "wb") as image_file:
16 metadata = image_service.get(context, image_id, image_file)
17 return metadata
18+
19+
20+def fetch_to_raw(context, image_href, path, user_id, project_id):
21+ path_tmp = "%s.part" % path
22+ metadata = fetch(context, image_href, path_tmp, user_id, project_id)
23+
24+ def _qemu_img_info(path):
25+
26+ out, err = utils.execute('env', 'LC_ALL=C', 'LANG=C',
27+ 'qemu-img', 'info', path)
28+
29+ # output of qemu-img is 'field: value'
30+ # the fields of interest are 'file format' and 'backing file'
31+ data = {}
32+ for line in out.splitlines():
33+ (field, val) = line.split(':', 1)
34+ if val[0] == " ":
35+ val = val[1:]
36+ data[field] = val
37+
38+ return(data)
39+
40+ data = _qemu_img_info(path_tmp)
41+
42+ fmt = data.get("file format", None)
43+ if fmt == None:
44+ os.unlink(path_tmp)
45+ raise exception.ImageUnacceptable(
46+ reason=_("'qemu-img info' parsing failed."), image_id=image_href)
47+
48+ if fmt != "raw":
49+ staged = "%s.converted" % path
50+ if "backing file" in data:
51+ backing_file = data['backing file']
52+ os.unlink(path_tmp)
53+ raise exception.ImageUnacceptable(image_id=image_href,
54+ reason=_("fmt=%(fmt)s backed by: %(backing_file)s") % locals())
55+
56+ LOG.debug("%s was %s, converting to raw" % (image_href, fmt))
57+ out, err = utils.execute('qemu-img', 'convert', '-O', 'raw',
58+ path_tmp, staged)
59+ os.unlink(path_tmp)
60+
61+ data = _qemu_img_info(staged)
62+ if data.get('file format', None) != "raw":
63+ os.unlink(staged)
64+ raise exception.ImageUnacceptable(image_id=image_href,
65+ reason=_("Converted to raw, but format is now %s") %
66+ data.get('file format', None))
67+
68+ os.rename(staged, path)
69+
70+ else:
71+ os.rename(path_tmp, path)
72+
73+ return metadata
74
75=== modified file 'nova/virt/libvirt/connection.py'
76--- nova/virt/libvirt/connection.py 2011-09-20 08:41:51 +0000
77+++ nova/virt/libvirt/connection.py 2011-09-20 09:48:25 +0000
78@@ -773,7 +773,7 @@
79 def _fetch_image(self, context, target, image_id, user_id, project_id,
80 size=None):
81 """Grab image and optionally attempt to resize it"""
82- images.fetch(context, image_id, target, user_id, project_id)
83+ images.fetch_to_raw(context, image_id, target, user_id, project_id)
84 if size:
85 disk.extend(target, size)
86