Merge lp:~soren/nova/virt-test-improvements into lp:~hudson-openstack/nova/trunk

Proposed by Soren Hansen
Status: Rejected
Rejected by: Soren Hansen
Proposed branch: lp:~soren/nova/virt-test-improvements
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 2428 lines (+1976/-107)
10 files modified
nova/image/fake.py (+9/-1)
nova/image/service.py (+200/-0)
nova/tests/fake_libvirt_utils.py (+94/-0)
nova/tests/fakelibvirt.py (+773/-0)
nova/tests/test_fakelibvirt.py (+391/-0)
nova/tests/test_image.py (+11/-0)
nova/tests/test_libvirt.py (+184/-2)
nova/tests/test_virt_drivers.py (+41/-4)
nova/virt/libvirt/connection.py (+44/-100)
nova/virt/libvirt/utils.py (+229/-0)
To merge this branch: bzr merge lp:~soren/nova/virt-test-improvements
Reviewer Review Type Date Requested Status
Rick Harris (community) Needs Fixing
Review via email: mp+73644@code.launchpad.net

Commit message

Extend test_virt_drivers to test the libvirt driver

Description of the change

Extend test_virt_driver to also test libvirt driver.

To support this, I've added a fake libvirt implementation. It's supposed
to expose an API and behaviour identical to that of libvirt itself
except without actually running any VM's or setting up any firewall or
anything, but still responding correctly when asked for a domain's XML,
a list of defined domains, running domains, etc.

I've also split out everything from libvirt.connection that is
potentially destructive or otherwise undesirable to run during testing,
and moved it to a new nova.virt.libvirt.utils. I added tests for those
things separately as well as stub version of it for testing. I hope
eventually to make it similar to fakelibvirt in style (e.g. keep track
of files created and deleted and attempts to open a file that it doesn't
know about, you'll get proper exceptions with proper errnos set and
whatnot).

To post a comment you must log in.
Revision history for this message
Soren Hansen (soren) wrote :

Whoa, hang on. Found a bug.

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

> Whoa, hang on. Found a bug.

...and fixed it, by the way. Reviews welcome.

Revision history for this message
Rick Harris (rconradharris) wrote :

Nice work here Soren.

Some notes:

Received this error when running the tests: http://paste.openstack.org/show/2357/

> 911 + if not auth == [[VIR_CRED_AUTHNAME, VIR_CRED_NOECHOPROMPT],
> 912 + 'root',
> 913 + None]:

Might be better as:

    if auth != [[VIR_CRED_AUTHNAME, VIR_CRED_NOECHOPROMPT],...

> 914 + raise Exception("Please extend fake libvirt module to support this "
> 915 + "auth method")

i18n.

> 1420 + try:
> 1421 + dst_fd, dst_path = tempfile.mkstemp()
> 1422 + os.close(dst_fd)
> 1423 +
> 1424 + src_fd, src_path = tempfile.mkstemp()
> 1425 + with os.fdopen(src_fd) as fp:
> 1426 + fp.write('canary')
> 1427 +
> 1428 + libvirt_utils.copy_image(src_path, dst_path)
> 1429 + with open(dst_path, 'r') as fp:
> 1430 + self.assertEquals(fp.read(), 'canary')
> 1431 + finally:
> 1432 + os.unlink(src_path)
> 1433 + os.unlink(dst_path)

Not likely to happen, but if an exception was is raised after the first
`mkstemp()` but before the second `mkstemp()`, the `finally` will attempt to
unlink `src_path` which hasn't been defined yet.

Also, in that first `mkstemp`, if that were to raise an exception, then
`dest_path` wouldn't have been bound as a local variable (since the RHS raised
before assigning to the LHS). To handle that, the `mkstemp` should go outside of the `try`.

This should solve both problems:

    dst_fd, dst_path = tempfile.mkstemp()
    try:
        os.close(dst_fd)

        src_fd, src_path = tempfile.mkstemp()
        try:
            with os.fdopen(src_fd) as fp:
                fp.write('canary')

            libvirt_utils.copy_image(src_path, dst_path)
            with open(dst_path, 'r') as fp:
                self.assertEquals(fp.read(), 'canary')
        finally:
            os.unlink(src_path)
    finally:
        os.unlink(dst_path)

> 1454 + try:
> 1455 + dst_fd, dst_path = tempfile.mkstemp()

Same deal here:

    dst_fd, dst_path = tempfile.mkstemp()
    try:
        ...

Few more places like this in the file.

> 1990 +# Copyright 2010 United States Government as represented by the
> 1991 +# Administrator of the National Aeronautics and Space Administration.
> 1992 +# All Rights Reserved.
> 1993 +# Copyright (c) 2010 Citrix Systems, Inc.
> 1994 +# Copyright (c) 2011 Piston Cloud Computing, Inc
> 1995 +# Copyright (c) 2011 OpenStack LLC

Given that this is a new file, should we just start it out with:

# Copyright (c) 2011 OpenStack LLC

No real opinion here, just tossing out the question.

> 2105 + if umask:
> 2106 + saved_umask = os.umask(umask)
> 2107 +
> 2108 + with open(path, 'w') as f:
> 2109 + f.write(contents)
> 2110 +
> 2111 + if umask:
> 2112 + os.umask(saved_umask)

Should this be inside a try/finally?

    if umask:
        saved_umask = os.umask(umask)
    try:
        with open(path, 'w') as f:
            f.write(contents)
    finally:
        if umask:
            os.umask(saved_umask)

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

> Nice work here Soren.
>
> Some notes:
>
>
> Received this error when running the tests:
> http://paste.openstack.org/show/2357/

Which OS are you running this on? Can you give me the outout of "uname -a" and see if its df(1) command can somehow be pursuaded to provide its output in bytes?

>> + raise Exception("Please extend fake libvirt module to support this "
>> + "auth method")
> i18n.

I added i18n here, but generally, I don't really think doing so in general for strings that only exist in the tests is super useful. Kind of like how we don't i18n enable our code comments.

> > 1420 + try:
> > 1421 + dst_fd, dst_path = tempfile.mkstemp()
> > 1422 + os.close(dst_fd)
> > 1423 +
> > 1424 + src_fd, src_path = tempfile.mkstemp()
> > 1425 + with os.fdopen(src_fd) as fp:
> > 1426 + fp.write('canary')
> > 1427 +
> > 1428 + libvirt_utils.copy_image(src_path, dst_path)
> > 1429 + with open(dst_path, 'r') as fp:
> > 1430 + self.assertEquals(fp.read(), 'canary')
> > 1431 + finally:
> > 1432 + os.unlink(src_path)
> > 1433 + os.unlink(dst_path)
> Not likely to happen, but if an exception was is raised after the first
> `mkstemp()` but before the second `mkstemp()`, the `finally` will attempt to
> unlink `src_path` which hasn't been defined yet.

Fixed.
> > 1990 +# Copyright 2010 United States Government as represented by the
> > 1991 +# Administrator of the National Aeronautics and Space
> Administration.
> > 1992 +# All Rights Reserved.
> > 1993 +# Copyright (c) 2010 Citrix Systems, Inc.
> > 1994 +# Copyright (c) 2011 Piston Cloud Computing, Inc
> > 1995 +# Copyright (c) 2011 OpenStack LLC
>
> Given that this is a new file, should we just start it out with:

Parts of its contents are based on an existing file, so the copyright carries over. This was intentional.

> > 2105 + if umask:
> > 2106 + saved_umask = os.umask(umask)
> > 2107 +
> > 2108 + with open(path, 'w') as f:
> > 2109 + f.write(contents)
> > 2110 +
> > 2111 + if umask:
> > 2112 + os.umask(saved_umask)
> Should this be inside a try/finally?

Good call. Fixed.

Thanks!

1394. By Soren Hansen

Move the calls to mkstemp outside the try block so that only files that have been created are cleaned up.

1395. By Soren Hansen

i18n for exception strings in tests.

1396. By Soren Hansen

Move resetting umask back to its original value into a finally block.

1397. By Soren Hansen

Merge trunk

1398. By Soren Hansen

Also fix call to reboot()

1399. By Soren Hansen

Merge trunk

1400. By Soren Hansen

Extend fakelibvirt to properly support snapshotCreateXML

Revision history for this message
Brian Lamar (blamar) wrote :

Overall I don't have a lot of code-specific comments. I will say that this does bring some much needed testing to the libvirt layer which historically has lacked some of the bigger-picture tests like the ones you've added.

However, I can't help but think about how overly complex this change is:

347 === added file 'nova/tests/fakelibvirt.py'
1112 === added file 'nova/tests/test_fakelibvirt.py'

I have always been told that if you're spending time writing tests for your tests then take a step back and think about why. In this case we're creating a 100% fake libvirt module which provides all of the functionality (we need/use) for our tests.

If a test fails, we have absolutely no way of knowing if it's an error in our driver or in our test code. Now we're debugging our test's tests which seems to be to be unneeded. Which version of the libvirt API does the fake libvirt module follow? I say leave the libvirt code up to the libvirt maintainers and the nova code, which interfaces with libvirt, to us.

We could use the libvirt 'test' driver, which provides basically what you said:

> to expose an API and behaviour identical to that of libvirt itself
> except without actually running any VM's or setting up any firewall or
> anything, but still responding correctly when asked for a domain's XML,
> a list of defined domains, running domains, etc.

However, one thing I can see is that the 'test' drive doesn't support a lot of nwfilter things that we run in our tests.

Example of 'test' driver, for anyone who hasn't used it: http://paste.openstack.org/show/2465/

So I guess long story short I don't want to hold this merge prop up if we think it's going to increase libvirt reliability, however for the future in my opinion we should shoot for:

Small Tests: Simple stubbing out of libvirt methods we call, or use of Mox to make sure we're
             passing expected arguments to libvirt. These tests are just meant to test OUR logic and
             not the interaction of our code with libvirt's code.

Medium Tests: Use the 'test' driver to test as much as it allows. Sometimes we might have to
              expect failures here when things like nwfilters are not supported by the 'test'
              driver. These tests are meant to test the interaction of our code with libvirt,
              to make sure that the functions we're calling are the correct ones and that the
              parameters passed make relative sense.

Large Tests: Have a libvirt process running on Jenkins or somewhere else that runs a full test
             suite of QEMU, LXC, and any other driver we support. These tests make sure that
             overall things are working as we intend them to work.

http://wiki.openstack.org/TestGuide

Also, perhaps this was discussed elsewhere and I missed up, but feel free to point me to doc/discussions on this topic.

Revision history for this message
Brian Lamar (blamar) wrote :

Two quick code comments:

43 === added file 'nova/image/service.py'

I think this is a remnant of the past, it was moved in trunk a bit ago. I think it might have been left after a trunk merge.

1841 + try:
1842 + virt_dom = self._lookup_by_name(instance['name'])
1843 + except exception.InstanceNotFound:
1844 + raise exception.InstanceNotRunning()

Why change the exception type here? Seems like an InstanceNotFound exception makes more sense to me.

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

Unmerged revisions

1400. By Soren Hansen

Extend fakelibvirt to properly support snapshotCreateXML

1399. By Soren Hansen

Merge trunk

1398. By Soren Hansen

Also fix call to reboot()

1397. By Soren Hansen

Merge trunk

1396. By Soren Hansen

Move resetting umask back to its original value into a finally block.

1395. By Soren Hansen

i18n for exception strings in tests.

1394. By Soren Hansen

Move the calls to mkstemp outside the try block so that only files that have been created are cleaned up.

1393. By Soren Hansen

Accidentally included a patch to enable profiling. Reverting.

1392. By Soren Hansen

Merge trunk

1391. By Soren Hansen

For some reason I thought qemu-img was being run as root.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/image/fake.py'
2--- nova/image/fake.py 2011-09-01 03:13:21 +0000
3+++ nova/image/fake.py 2011-09-16 15:21:29 +0000
4@@ -54,7 +54,7 @@
5 'ramdisk_id': FLAGS.null_kernel,
6 'architecture': 'x86_64'}}
7
8- image2 = {'id': 'fake',
9+ image2 = {'id': '5',
10 'name': 'fakeimage123456',
11 'created_at': timestamp,
12 'updated_at': timestamp,
13@@ -111,6 +111,7 @@
14 self.create(None, image3)
15 self.create(None, image4)
16 self.create(None, image5)
17+ self._imagedata = {}
18 super(_FakeImageService, self).__init__()
19
20 def index(self, context, filters=None, marker=None, limit=None):
21@@ -125,6 +126,11 @@
22 """Return list of detailed image information."""
23 return copy.deepcopy(self.images.values())
24
25+ def get(self, context, image_id, data):
26+ metadata = self.show(context, image_id)
27+ data.write(self._imagedata.get(image_id, ''))
28+ return metadata
29+
30 def show(self, context, image_id):
31 """Get data about specified image.
32
33@@ -167,6 +173,8 @@
34
35 metadata['id'] = image_id
36 self.images[image_id] = copy.deepcopy(metadata)
37+ if data:
38+ self._imagedata[image_id] = data.read()
39 return self.images[image_id]
40
41 def update(self, context, image_id, metadata, data=None):
42
43=== added file 'nova/image/service.py'
44--- nova/image/service.py 1970-01-01 00:00:00 +0000
45+++ nova/image/service.py 2011-09-16 15:21:29 +0000
46@@ -0,0 +1,200 @@
47+# vim: tabstop=4 shiftwidth=4 softtabstop=4
48+
49+# Copyright 2010 OpenStack LLC.
50+# All Rights Reserved.
51+#
52+# Licensed under the Apache License, Version 2.0 (the "License"); you may
53+# not use this file except in compliance with the License. You may obtain
54+# a copy of the License at
55+#
56+# http://www.apache.org/licenses/LICENSE-2.0
57+#
58+# Unless required by applicable law or agreed to in writing, software
59+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
60+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
61+# License for the specific language governing permissions and limitations
62+# under the License.
63+
64+
65+from nova import utils
66+
67+
68+class BaseImageService(object):
69+ """Base class for providing image search and retrieval services.
70+
71+ ImageService exposes two concepts of metadata:
72+
73+ 1. First-class attributes: This is metadata that is common to all
74+ ImageService subclasses and is shared across all hypervisors. These
75+ attributes are defined by IMAGE_ATTRS.
76+
77+ 2. Properties: This is metdata that is specific to an ImageService,
78+ and Image, or a particular hypervisor. Any attribute not present in
79+ BASE_IMAGE_ATTRS should be considered an image property.
80+
81+ This means that ImageServices will return BASE_IMAGE_ATTRS as keys in the
82+ metadata dict, all other attributes will be returned as keys in the nested
83+ 'properties' dict.
84+
85+ """
86+
87+ BASE_IMAGE_ATTRS = ['id', 'name', 'created_at', 'updated_at',
88+ 'deleted_at', 'deleted', 'status', 'is_public']
89+
90+ # NOTE(sirp): ImageService subclasses may override this to aid translation
91+ # between BaseImageService attributes and additional metadata stored by
92+ # the ImageService subclass
93+ SERVICE_IMAGE_ATTRS = []
94+
95+ def index(self, context, *args, **kwargs):
96+ """List images.
97+
98+ :returns: a sequence of mappings with the following signature
99+ {'id': opaque id of image, 'name': name of image}
100+
101+ """
102+ raise NotImplementedError
103+
104+ def detail(self, context, *args, **kwargs):
105+ """Detailed information about an images.
106+
107+ :returns: a sequence of mappings with the following signature
108+ {'id': opaque id of image,
109+ 'name': name of image,
110+ 'created_at': creation datetime object,
111+ 'updated_at': modification datetime object,
112+ 'deleted_at': deletion datetime object or None,
113+ 'deleted': boolean indicating if image has been deleted,
114+ 'status': string description of image status,
115+ 'is_public': boolean indicating if image is public
116+ }
117+
118+ If the service does not implement a method that provides a detailed
119+ set of information about images, then the method should raise
120+ NotImplementedError, in which case Nova will emulate this method
121+ with repeated calls to show() for each image received from the
122+ index() method.
123+
124+ """
125+ raise NotImplementedError
126+
127+ def show(self, context, image_id):
128+ """Detailed information about an image.
129+
130+ :returns: a mapping with the following signature:
131+ {'id': opaque id of image,
132+ 'name': name of image,
133+ 'created_at': creation datetime object,
134+ 'updated_at': modification datetime object,
135+ 'deleted_at': deletion datetime object or None,
136+ 'deleted': boolean indicating if image has been deleted,
137+ 'status': string description of image status,
138+ 'is_public': boolean indicating if image is public
139+ }, ...
140+
141+ :raises: NotFound if the image does not exist
142+
143+ """
144+ raise NotImplementedError
145+
146+ def get(self, context, image_id, data):
147+ """Get an image.
148+
149+ :param data: a file-like object to hold binary image data
150+ :returns: a dict containing image metadata, writes image data to data.
151+ :raises: NotFound if the image does not exist
152+
153+ """
154+ raise NotImplementedError
155+
156+ def create(self, context, metadata, data=None):
157+ """Store the image metadata and data.
158+
159+ :returns: the new image metadata.
160+ :raises: AlreadyExists if the image already exist.
161+
162+ """
163+ raise NotImplementedError
164+
165+ def update(self, context, image_id, metadata, data=None):
166+ """Update the given image metadata and data and return the metadata.
167+
168+ :raises: NotFound if the image does not exist.
169+
170+ """
171+ raise NotImplementedError
172+
173+ def delete(self, context, image_id):
174+ """Delete the given image.
175+
176+ :raises: NotFound if the image does not exist.
177+
178+ """
179+ raise NotImplementedError
180+
181+ @staticmethod
182+ def _is_image_available(context, image_meta):
183+ """Check image availability.
184+
185+ Images are always available if they are public or if the user is an
186+ admin.
187+
188+ Otherwise, we filter by project_id (if present) and then fall-back to
189+ images owned by user.
190+
191+ """
192+ # FIXME(sirp): We should be filtering by user_id on the Glance side
193+ # for security; however, we can't do that until we get authn/authz
194+ # sorted out. Until then, filtering in Nova.
195+ if image_meta['is_public'] or context.is_admin:
196+ return True
197+
198+ properties = image_meta['properties']
199+
200+ if context.project_id and ('project_id' in properties):
201+ return str(properties['project_id']) == str(context.project_id)
202+
203+ try:
204+ user_id = properties['user_id']
205+ except KeyError:
206+ return False
207+
208+ return str(user_id) == str(context.user_id)
209+
210+ @classmethod
211+ def _translate_to_base(cls, metadata):
212+ """Return a metadata dictionary that is BaseImageService compliant.
213+
214+ This is used by subclasses to expose only a metadata dictionary that
215+ is the same across ImageService implementations.
216+
217+ """
218+ return cls._propertify_metadata(metadata, cls.BASE_IMAGE_ATTRS)
219+
220+ @classmethod
221+ def _translate_to_service(cls, metadata):
222+ """Return a metadata dict that is usable by the ImageService subclass.
223+
224+ As an example, Glance has additional attributes (like 'location'); the
225+ BaseImageService considers these properties, but we need to translate
226+ these back to first-class attrs for sending to Glance. This method
227+ handles this by allowing you to specify the attributes an ImageService
228+ considers first-class.
229+
230+ """
231+ if not cls.SERVICE_IMAGE_ATTRS:
232+ raise NotImplementedError(_('Cannot use this without specifying '
233+ 'SERVICE_IMAGE_ATTRS for subclass'))
234+ return cls._propertify_metadata(metadata, cls.SERVICE_IMAGE_ATTRS)
235+
236+ @staticmethod
237+ def _propertify_metadata(metadata, keys):
238+ """Move unknown keys to a nested 'properties' dict.
239+
240+ :returns: a new dict with the keys moved.
241+
242+ """
243+ flattened = utils.flatten_dict(metadata)
244+ attributes, properties = utils.partition_dict(flattened, keys)
245+ attributes['properties'] = properties
246+ return attributes
247
248=== added file 'nova/tests/fake_libvirt_utils.py'
249--- nova/tests/fake_libvirt_utils.py 1970-01-01 00:00:00 +0000
250+++ nova/tests/fake_libvirt_utils.py 2011-09-16 15:21:29 +0000
251@@ -0,0 +1,94 @@
252+# vim: tabstop=4 shiftwidth=4 softtabstop=4
253+
254+# Copyright (c) 2011 OpenStack LLC
255+#
256+# Licensed under the Apache License, Version 2.0 (the "License"); you may
257+# not use this file except in compliance with the License. You may obtain
258+# a copy of the License at
259+#
260+# http://www.apache.org/licenses/LICENSE-2.0
261+#
262+# Unless required by applicable law or agreed to in writing, software
263+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
264+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
265+# License for the specific language governing permissions and limitations
266+# under the License.
267+
268+import StringIO
269+
270+files = {}
271+
272+
273+def create_image(disk_format, path, size):
274+ pass
275+
276+
277+def create_cow_image(backing_file, path):
278+ pass
279+
280+
281+def get_disk_size(path):
282+ pass
283+
284+
285+def copy_image(src, dest):
286+ pass
287+
288+
289+def mkfs(fs, path):
290+ pass
291+
292+
293+def ensure_tree(path):
294+ pass
295+
296+
297+def write_to_file(path, contents, umask=None):
298+ pass
299+
300+
301+def chown(path, owner):
302+ pass
303+
304+
305+def extract_snapshot(disk_path, snapshot_name, out_path):
306+ files[out_path] = ''
307+
308+
309+class File(object):
310+ def __init__(self, path, mode=None):
311+ self.fp = StringIO.StringIO(files[path])
312+
313+ def __enter__(self):
314+ return self.fp
315+
316+ def __exit__(self, *args):
317+ return
318+
319+
320+def file_open(path, mode=None):
321+ return File(path, mode)
322+
323+
324+def load_file(path):
325+ return ''
326+
327+
328+def get_open_port(start_port, end_port):
329+ # Return the port in the middle
330+ return int((start_port + end_port) / 2)
331+
332+
333+def run_ajaxterm(cmd, token, port):
334+ pass
335+
336+
337+def get_fs_info(path):
338+ return {'total': 128 * (1024 ** 3),
339+ 'used': 44 * (1024 ** 3),
340+ 'free': 84 * (1024 ** 3)}
341+
342+
343+def fetch_image(context, target, image_id, user_id, project_id,
344+ size=None):
345+ pass
346
347=== added file 'nova/tests/fakelibvirt.py'
348--- nova/tests/fakelibvirt.py 1970-01-01 00:00:00 +0000
349+++ nova/tests/fakelibvirt.py 2011-09-16 15:21:29 +0000
350@@ -0,0 +1,773 @@
351+# vim: tabstop=4 shiftwidth=4 softtabstop=4
352+#
353+# Copyright 2010 OpenStack LLC
354+#
355+# Licensed under the Apache License, Version 2.0 (the "License"); you may
356+# not use this file except in compliance with the License. You may obtain
357+# a copy of the License at
358+#
359+# http://www.apache.org/licenses/LICENSE-2.0
360+#
361+# Unless required by applicable law or agreed to in writing, software
362+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
363+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
364+# License for the specific language governing permissions and limitations
365+# under the License.
366+
367+from xml.etree.ElementTree import fromstring as xml_to_tree
368+from xml.etree.ElementTree import ParseError
369+import uuid
370+
371+# Allow passing None to the various connect methods
372+# (i.e. allow the client to rely on default URLs)
373+allow_default_uri_connection = True
374+
375+# string indicating the CPU arch
376+node_arch = 'x86_64' # or 'i686' (or whatever else uname -m might return)
377+
378+# memory size in kilobytes
379+node_kB_mem = 4096
380+
381+# the number of active CPUs
382+node_cpus = 2
383+
384+# expected CPU frequency
385+node_mhz = 800
386+
387+# the number of NUMA cell, 1 for unusual NUMA topologies or uniform
388+# memory access; check capabilities XML for the actual NUMA topology
389+node_nodes = 1 # NUMA nodes
390+
391+# number of CPU sockets per node if nodes > 1, total number of CPU
392+# sockets otherwise
393+node_sockets = 1
394+
395+# number of cores per socket
396+node_cores = 2
397+
398+# number of threads per core
399+node_threads = 1
400+
401+# CPU model
402+node_cpu_model = "Penryn"
403+
404+# CPU vendor
405+node_cpu_vendor = "Intel"
406+
407+
408+def _reset():
409+ global allow_default_uri_connection
410+ allow_default_uri_connection = True
411+
412+# virDomainState
413+VIR_DOMAIN_NOSTATE = 0
414+VIR_DOMAIN_RUNNING = 1
415+VIR_DOMAIN_BLOCKED = 2
416+VIR_DOMAIN_PAUSED = 3
417+VIR_DOMAIN_SHUTDOWN = 4
418+VIR_DOMAIN_SHUTOFF = 5
419+VIR_DOMAIN_CRASHED = 6
420+
421+VIR_CPU_COMPARE_ERROR = -1
422+VIR_CPU_COMPARE_INCOMPATIBLE = 0
423+VIR_CPU_COMPARE_IDENTICAL = 1
424+VIR_CPU_COMPARE_SUPERSET = 2
425+
426+VIR_CRED_AUTHNAME = 2
427+VIR_CRED_NOECHOPROMPT = 7
428+
429+# libvirtError enums
430+# (Intentionally different from what's in libvirt. We do this to check,
431+# that consumers of the library are using the symbolic names rather than
432+# hardcoding the numerical values)
433+VIR_FROM_QEMU = 100
434+VIR_FROM_DOMAIN = 200
435+VIR_FROM_NWFILTER = 330
436+VIR_ERR_XML_DETAIL = 350
437+VIR_ERR_NO_DOMAIN = 420
438+VIR_ERR_NO_NWFILTER = 620
439+
440+
441+def _parse_disk_info(element):
442+ disk_info = {}
443+ disk_info['type'] = element.get('type', 'file')
444+ disk_info['device'] = element.get('device', 'disk')
445+
446+ driver = element.find('./driver')
447+ if driver is not None:
448+ disk_info['driver_name'] = driver.get('name')
449+ disk_info['driver_type'] = driver.get('type')
450+
451+ source = element.find('./source')
452+ if source is not None:
453+ disk_info['source'] = source.get('file')
454+ if not disk_info['source']:
455+ disk_info['source'] = source.get('dev')
456+
457+ if not disk_info['source']:
458+ disk_info['source'] = source.get('path')
459+
460+ target = element.find('./target')
461+ if target is not None:
462+ disk_info['target_dev'] = target.get('dev')
463+ disk_info['target_bus'] = target.get('bus')
464+
465+ return disk_info
466+
467+
468+class libvirtError(Exception):
469+ def __init__(self, error_code, error_domain, msg):
470+ self.error_code = error_code
471+ self.error_domain = error_domain
472+ Exception(self, msg)
473+
474+ def get_error_code(self):
475+ return self.error_code
476+
477+ def get_error_domain(self):
478+ return self.error_domain
479+
480+
481+class NWFilter(object):
482+ def __init__(self, connection, xml):
483+ self._connection = connection
484+
485+ self._xml = xml
486+ self._parse_xml(xml)
487+
488+ def _parse_xml(self, xml):
489+ tree = xml_to_tree(xml)
490+ root = tree.find('.')
491+ self._name = root.get('name')
492+
493+ def undefine(self):
494+ self._connection._remove_filter(self)
495+
496+
497+class Domain(object):
498+ def __init__(self, connection, xml, running=False, transient=False):
499+ self._connection = connection
500+ if running:
501+ connection._mark_running(self)
502+
503+ self._state = running and VIR_DOMAIN_RUNNING or VIR_DOMAIN_SHUTOFF
504+ self._transient = transient
505+ self._def = self._parse_definition(xml)
506+ self._has_saved_state = False
507+ self._snapshots = {}
508+
509+ def _parse_definition(self, xml):
510+ try:
511+ tree = xml_to_tree(xml)
512+ except ParseError:
513+ raise libvirtError(VIR_ERR_XML_DETAIL, VIR_FROM_DOMAIN,
514+ "Invalid XML.")
515+
516+ definition = {}
517+
518+ name = tree.find('./name')
519+ if name is not None:
520+ definition['name'] = name.text
521+
522+ uuid_elem = tree.find('./uuid')
523+ if uuid_elem is not None:
524+ definition['uuid'] = uuid_elem.text
525+ else:
526+ definition['uuid'] = str(uuid.uuid4())
527+
528+ vcpu = tree.find('./vcpu')
529+ if vcpu is not None:
530+ definition['vcpu'] = int(vcpu.text)
531+
532+ memory = tree.find('./memory')
533+ if memory is not None:
534+ definition['memory'] = int(memory.text)
535+
536+ os = {}
537+ os_type = tree.find('./os/type')
538+ if os_type is not None:
539+ os['type'] = os_type.text
540+ os['arch'] = os_type.get('arch', node_arch)
541+
542+ os_kernel = tree.find('./os/kernel')
543+ if os_kernel is not None:
544+ os['kernel'] = os_kernel.text
545+
546+ os_initrd = tree.find('./os/initrd')
547+ if os_initrd is not None:
548+ os['initrd'] = os_initrd.text
549+
550+ os_cmdline = tree.find('./os/cmdline')
551+ if os_cmdline is not None:
552+ os['cmdline'] = os_cmdline.text
553+
554+ os_boot = tree.find('./os/boot')
555+ if os_boot is not None:
556+ os['boot_dev'] = os_boot.get('dev')
557+
558+ definition['os'] = os
559+
560+ features = {}
561+
562+ acpi = tree.find('./features/acpi')
563+ if acpi is not None:
564+ features['acpi'] = True
565+
566+ definition['features'] = features
567+
568+ devices = {}
569+
570+ device_nodes = tree.find('./devices')
571+ if device_nodes is not None:
572+ disks_info = []
573+ disks = device_nodes.findall('./disk')
574+ for disk in disks:
575+ disks_info += [_parse_disk_info(disk)]
576+ devices['disks'] = disks_info
577+
578+ nics_info = []
579+ nics = device_nodes.findall('./interface')
580+ for nic in nics:
581+ nic_info = {}
582+ nic_info['type'] = nic.get('type')
583+
584+ mac = nic.find('./mac')
585+ if mac is not None:
586+ nic_info['mac'] = mac.get('address')
587+
588+ source = nic.find('./source')
589+ if source is not None:
590+ if nic_info['type'] == 'network':
591+ nic_info['source'] = source.get('network')
592+ elif nic_info['type'] == 'bridge':
593+ nic_info['source'] = source.get('bridge')
594+
595+ nics_info += [nic_info]
596+
597+ devices['nics'] = nics_info
598+
599+ definition['devices'] = devices
600+
601+ return definition
602+
603+ def create(self):
604+ self.createWithFlags(0)
605+
606+ def createWithFlags(self, flags):
607+ # FIXME: Not handling flags at the moment
608+ self._state = VIR_DOMAIN_RUNNING
609+ self._connection._mark_running(self)
610+ self._has_saved_state = False
611+
612+ def isActive(self):
613+ return int(self._state == VIR_DOMAIN_RUNNING)
614+
615+ def undefine(self):
616+ self._connection._undefine(self)
617+
618+ def destroy(self):
619+ self._state = VIR_DOMAIN_SHUTOFF
620+ self._connection._mark_not_running(self)
621+
622+ def name(self):
623+ return self._def['name']
624+
625+ def UUIDString(self):
626+ return self._def['uuid']
627+
628+ def interfaceStats(self, device):
629+ return [10000242400, 1234, 0, 2, 213412343233, 34214234, 23, 3]
630+
631+ def blockStats(self, device):
632+ return [2, 10000242400, 234, 2343424234, 34]
633+
634+ def suspend(self):
635+ self._state = VIR_DOMAIN_PAUSED
636+
637+ def info(self):
638+ return [VIR_DOMAIN_RUNNING,
639+ long(self._def['memory']),
640+ long(self._def['memory']),
641+ self._def['vcpu'],
642+ 123456789L]
643+
644+ def attachDevice(self, xml):
645+ disk_info = _parse_disk_info(xml_to_tree(xml))
646+ disk_info['_attached'] = True
647+ self._def['devices']['disks'] += [disk_info]
648+ return True
649+
650+ def detachDevice(self, xml):
651+ disk_info = _parse_disk_info(xml_to_tree(xml))
652+ disk_info['_attached'] = True
653+ return disk_info in self._def['devices']['disks']
654+
655+ def XMLDesc(self, flags):
656+ disks = ''
657+ for disk in self._def['devices']['disks']:
658+ disks += '''<disk type='%(type)s' device='%(device)s'>
659+ <driver name='%(driver_name)s' type='%(driver_type)s'/>
660+ <source file='%(source)s'/>
661+ <target dev='%(target_dev)s' bus='%(target_bus)s'/>
662+ <address type='drive' controller='0' bus='0' unit='0'/>
663+ </disk>''' % disk
664+
665+ nics = ''
666+ for nic in self._def['devices']['nics']:
667+ nics += '''<interface type='%(type)s'>
668+ <mac address='%(mac)s'/>
669+ <source %(type)s='%(source)s'/>
670+ <address type='pci' domain='0x0000' bus='0x00' slot='0x03'
671+ function='0x0'/>
672+ </interface>''' % nic
673+
674+ return '''<domain type='kvm'>
675+ <name>%(name)s</name>
676+ <uuid>%(uuid)s</uuid>
677+ <memory>%(memory)s</memory>
678+ <currentMemory>%(memory)s</currentMemory>
679+ <vcpu>%(vcpu)s</vcpu>
680+ <os>
681+ <type arch='%(arch)s' machine='pc-0.12'>hvm</type>
682+ <boot dev='hd'/>
683+ </os>
684+ <features>
685+ <acpi/>
686+ <apic/>
687+ <pae/>
688+ </features>
689+ <clock offset='localtime'/>
690+ <on_poweroff>destroy</on_poweroff>
691+ <on_reboot>restart</on_reboot>
692+ <on_crash>restart</on_crash>
693+ <devices>
694+ <emulator>/usr/bin/kvm</emulator>
695+ %(disks)s
696+ <controller type='ide' index='0'>
697+ <address type='pci' domain='0x0000' bus='0x00' slot='0x01'
698+ function='0x1'/>
699+ </controller>
700+ %(nics)s
701+ <serial type='pty'>
702+ <source pty='/dev/pts/27'/>
703+ <target port='0'/>
704+ </serial>
705+ <console type='pty'>
706+ <target type='serial' port='0'/>
707+ </console>
708+ <input type='tablet' bus='usb'/>
709+ <input type='mouse' bus='ps2'/>
710+ <graphics type='vnc' port='-1' autoport='yes'/>
711+ <video>
712+ <model type='cirrus' vram='9216' heads='1'/>
713+ <address type='pci' domain='0x0000' bus='0x00' slot='0x02'
714+ function='0x0'/>
715+ </video>
716+ <memballoon model='virtio'>
717+ <address type='pci' domain='0x0000' bus='0x00' slot='0x04'
718+ function='0x0'/>
719+ </memballoon>
720+ </devices>
721+</domain>''' % {'name': self._def['name'],
722+ 'uuid': self._def['uuid'],
723+ 'memory': self._def['memory'],
724+ 'vcpu': self._def['vcpu'],
725+ 'arch': self._def['os']['arch'],
726+ 'disks': disks,
727+ 'nics': nics}
728+
729+ def managedSave(self, flags):
730+ self._connection._mark_not_running(self)
731+ self._has_saved_state = True
732+
733+ def resume(self):
734+ self._state = VIR_DOMAIN_RUNNING
735+
736+ def snapshotCreateXML(self, xml, flags):
737+ tree = xml_to_tree(xml)
738+ name = tree.find('./name').text
739+ snapshot = DomainSnapshot(name, self)
740+ self._snapshots[name] = snapshot
741+ return snapshot
742+
743+
744+class DomainSnapshot(object):
745+ def __init__(self, name, domain):
746+ self._name = name
747+ self._domain = domain
748+
749+ def delete(self, flags):
750+ del self._domain._snapshots[self._name]
751+
752+
753+class Connection(object):
754+ def __init__(self, uri, readonly):
755+ if not uri:
756+ if allow_default_uri_connection:
757+ uri = 'qemu:///session'
758+ else:
759+ raise Exception("URI was None, but fake libvirt is configured"
760+ " to not accept this.")
761+
762+ uri_whitelist = ['qemu:///system',
763+ 'qemu:///session',
764+ 'xen:///system',
765+ 'uml:///system']
766+
767+ if uri not in uri_whitelist:
768+ raise libvirtError(5, 0,
769+ "libvir: error : no connection driver "
770+ "available for No connection for URI %s" % uri)
771+
772+ self.readonly = readonly
773+ self._uri = uri
774+ self._vms = {}
775+ self._running_vms = {}
776+ self._id_counter = 0
777+ self._nwfilters = {}
778+
779+ def _add_filter(self, nwfilter):
780+ self._nwfilters[nwfilter._name] = nwfilter
781+
782+ def _remove_filter(self, nwfilter):
783+ del self._nwfilters[nwfilter._name]
784+
785+ def _mark_running(self, dom):
786+ self._running_vms[self._id_counter] = dom
787+ self._id_counter += 1
788+
789+ def _mark_not_running(self, dom):
790+ if dom._transient:
791+ self._undefine(dom)
792+
793+ for (k, v) in self._running_vms.iteritems():
794+ if v == dom:
795+ del self._running_vms[k]
796+ return
797+
798+ def _undefine(self, dom):
799+ del self._vms[dom.name()]
800+
801+ def getInfo(self):
802+ return [node_arch,
803+ node_kB_mem,
804+ node_cpus,
805+ node_mhz,
806+ node_nodes,
807+ node_sockets,
808+ node_cores,
809+ node_threads]
810+
811+ def listDomainsID(self):
812+ return self._running_vms.keys()
813+
814+ def lookupByID(self, id):
815+ if id in self._running_vms:
816+ return self._running_vms[id]
817+ raise libvirtError(VIR_ERR_NO_DOMAIN, VIR_FROM_QEMU,
818+ 'Domain not found: no domain with matching '
819+ 'id %d' % id)
820+
821+ def lookupByName(self, name):
822+ if name in self._vms:
823+ return self._vms[name]
824+ raise libvirtError(VIR_ERR_NO_DOMAIN, VIR_FROM_QEMU,
825+ 'Domain not found: no domain with matching '
826+ 'name "%s"' % name)
827+
828+ def defineXML(self, xml):
829+ dom = Domain(connection=self, running=False, transient=False, xml=xml)
830+ self._vms[dom.name()] = dom
831+ return dom
832+
833+ def createXML(self, xml, flags):
834+ dom = Domain(connection=self, running=True, transient=True, xml=xml)
835+ self._vms[dom.name()] = dom
836+ return dom
837+
838+ def getType(self):
839+ if self._uri == 'qemu:///system':
840+ return 'QEMU'
841+
842+ def getVersion(self):
843+ return 14000
844+
845+ def getCapabilities(self):
846+ return '''<capabilities>
847+ <host>
848+ <uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
849+ <cpu>
850+ <arch>x86_64</arch>
851+ <model>Penryn</model>
852+ <vendor>Intel</vendor>
853+ <topology sockets='1' cores='2' threads='1'/>
854+ <feature name='xtpr'/>
855+ <feature name='tm2'/>
856+ <feature name='est'/>
857+ <feature name='vmx'/>
858+ <feature name='ds_cpl'/>
859+ <feature name='monitor'/>
860+ <feature name='pbe'/>
861+ <feature name='tm'/>
862+ <feature name='ht'/>
863+ <feature name='ss'/>
864+ <feature name='acpi'/>
865+ <feature name='ds'/>
866+ <feature name='vme'/>
867+ </cpu>
868+ <migration_features>
869+ <live/>
870+ <uri_transports>
871+ <uri_transport>tcp</uri_transport>
872+ </uri_transports>
873+ </migration_features>
874+ <secmodel>
875+ <model>apparmor</model>
876+ <doi>0</doi>
877+ </secmodel>
878+ </host>
879+
880+ <guest>
881+ <os_type>hvm</os_type>
882+ <arch name='i686'>
883+ <wordsize>32</wordsize>
884+ <emulator>/usr/bin/qemu</emulator>
885+ <machine>pc-0.14</machine>
886+ <machine canonical='pc-0.14'>pc</machine>
887+ <machine>pc-0.13</machine>
888+ <machine>pc-0.12</machine>
889+ <machine>pc-0.11</machine>
890+ <machine>pc-0.10</machine>
891+ <machine>isapc</machine>
892+ <domain type='qemu'>
893+ </domain>
894+ <domain type='kvm'>
895+ <emulator>/usr/bin/kvm</emulator>
896+ <machine>pc-0.14</machine>
897+ <machine canonical='pc-0.14'>pc</machine>
898+ <machine>pc-0.13</machine>
899+ <machine>pc-0.12</machine>
900+ <machine>pc-0.11</machine>
901+ <machine>pc-0.10</machine>
902+ <machine>isapc</machine>
903+ </domain>
904+ </arch>
905+ <features>
906+ <cpuselection/>
907+ <deviceboot/>
908+ <pae/>
909+ <nonpae/>
910+ <acpi default='on' toggle='yes'/>
911+ <apic default='on' toggle='no'/>
912+ </features>
913+ </guest>
914+
915+ <guest>
916+ <os_type>hvm</os_type>
917+ <arch name='x86_64'>
918+ <wordsize>64</wordsize>
919+ <emulator>/usr/bin/qemu-system-x86_64</emulator>
920+ <machine>pc-0.14</machine>
921+ <machine canonical='pc-0.14'>pc</machine>
922+ <machine>pc-0.13</machine>
923+ <machine>pc-0.12</machine>
924+ <machine>pc-0.11</machine>
925+ <machine>pc-0.10</machine>
926+ <machine>isapc</machine>
927+ <domain type='qemu'>
928+ </domain>
929+ <domain type='kvm'>
930+ <emulator>/usr/bin/kvm</emulator>
931+ <machine>pc-0.14</machine>
932+ <machine canonical='pc-0.14'>pc</machine>
933+ <machine>pc-0.13</machine>
934+ <machine>pc-0.12</machine>
935+ <machine>pc-0.11</machine>
936+ <machine>pc-0.10</machine>
937+ <machine>isapc</machine>
938+ </domain>
939+ </arch>
940+ <features>
941+ <cpuselection/>
942+ <deviceboot/>
943+ <acpi default='on' toggle='yes'/>
944+ <apic default='on' toggle='no'/>
945+ </features>
946+ </guest>
947+
948+ <guest>
949+ <os_type>hvm</os_type>
950+ <arch name='arm'>
951+ <wordsize>32</wordsize>
952+ <emulator>/usr/bin/qemu-system-arm</emulator>
953+ <machine>integratorcp</machine>
954+ <machine>vexpress-a9</machine>
955+ <machine>syborg</machine>
956+ <machine>musicpal</machine>
957+ <machine>mainstone</machine>
958+ <machine>n800</machine>
959+ <machine>n810</machine>
960+ <machine>n900</machine>
961+ <machine>cheetah</machine>
962+ <machine>sx1</machine>
963+ <machine>sx1-v1</machine>
964+ <machine>beagle</machine>
965+ <machine>beaglexm</machine>
966+ <machine>tosa</machine>
967+ <machine>akita</machine>
968+ <machine>spitz</machine>
969+ <machine>borzoi</machine>
970+ <machine>terrier</machine>
971+ <machine>connex</machine>
972+ <machine>verdex</machine>
973+ <machine>lm3s811evb</machine>
974+ <machine>lm3s6965evb</machine>
975+ <machine>realview-eb</machine>
976+ <machine>realview-eb-mpcore</machine>
977+ <machine>realview-pb-a8</machine>
978+ <machine>realview-pbx-a9</machine>
979+ <machine>versatilepb</machine>
980+ <machine>versatileab</machine>
981+ <domain type='qemu'>
982+ </domain>
983+ </arch>
984+ <features>
985+ <deviceboot/>
986+ </features>
987+ </guest>
988+
989+ <guest>
990+ <os_type>hvm</os_type>
991+ <arch name='mips'>
992+ <wordsize>32</wordsize>
993+ <emulator>/usr/bin/qemu-system-mips</emulator>
994+ <machine>malta</machine>
995+ <machine>mipssim</machine>
996+ <machine>magnum</machine>
997+ <machine>pica61</machine>
998+ <machine>mips</machine>
999+ <domain type='qemu'>
1000+ </domain>
1001+ </arch>
1002+ <features>
1003+ <deviceboot/>
1004+ </features>
1005+ </guest>
1006+
1007+ <guest>
1008+ <os_type>hvm</os_type>
1009+ <arch name='mipsel'>
1010+ <wordsize>32</wordsize>
1011+ <emulator>/usr/bin/qemu-system-mipsel</emulator>
1012+ <machine>malta</machine>
1013+ <machine>mipssim</machine>
1014+ <machine>magnum</machine>
1015+ <machine>pica61</machine>
1016+ <machine>mips</machine>
1017+ <domain type='qemu'>
1018+ </domain>
1019+ </arch>
1020+ <features>
1021+ <deviceboot/>
1022+ </features>
1023+ </guest>
1024+
1025+ <guest>
1026+ <os_type>hvm</os_type>
1027+ <arch name='sparc'>
1028+ <wordsize>32</wordsize>
1029+ <emulator>/usr/bin/qemu-system-sparc</emulator>
1030+ <machine>SS-5</machine>
1031+ <machine>leon3_generic</machine>
1032+ <machine>SS-10</machine>
1033+ <machine>SS-600MP</machine>
1034+ <machine>SS-20</machine>
1035+ <machine>Voyager</machine>
1036+ <machine>LX</machine>
1037+ <machine>SS-4</machine>
1038+ <machine>SPARCClassic</machine>
1039+ <machine>SPARCbook</machine>
1040+ <machine>SS-1000</machine>
1041+ <machine>SS-2000</machine>
1042+ <machine>SS-2</machine>
1043+ <domain type='qemu'>
1044+ </domain>
1045+ </arch>
1046+ </guest>
1047+
1048+ <guest>
1049+ <os_type>hvm</os_type>
1050+ <arch name='ppc'>
1051+ <wordsize>32</wordsize>
1052+ <emulator>/usr/bin/qemu-system-ppc</emulator>
1053+ <machine>g3beige</machine>
1054+ <machine>virtex-ml507</machine>
1055+ <machine>mpc8544ds</machine>
1056+ <machine canonical='bamboo-0.13'>bamboo</machine>
1057+ <machine>bamboo-0.13</machine>
1058+ <machine>bamboo-0.12</machine>
1059+ <machine>ref405ep</machine>
1060+ <machine>taihu</machine>
1061+ <machine>mac99</machine>
1062+ <machine>prep</machine>
1063+ <domain type='qemu'>
1064+ </domain>
1065+ </arch>
1066+ <features>
1067+ <deviceboot/>
1068+ </features>
1069+ </guest>
1070+
1071+</capabilities>'''
1072+
1073+ def compareCPU(self, xml, flags):
1074+ tree = xml_to_tree(xml)
1075+
1076+ arch_node = tree.find('./arch')
1077+ if arch_node is not None:
1078+ if arch_node.text not in ['x86_64', 'i686']:
1079+ return VIR_CPU_COMPARE_INCOMPATIBLE
1080+
1081+ model_node = tree.find('./model')
1082+ if model_node is not None:
1083+ if model_node.text != node_cpu_model:
1084+ return VIR_CPU_COMPARE_INCOMPATIBLE
1085+
1086+ vendor_node = tree.find('./vendor')
1087+ if vendor_node is not None:
1088+ if vendor_node.text != node_cpu_vendor:
1089+ return VIR_CPU_COMPARE_INCOMPATIBLE
1090+
1091+ # The rest of the stuff libvirt implements is rather complicated
1092+ # and I don't think it adds much value to replicate it here.
1093+
1094+ return VIR_CPU_COMPARE_IDENTICAL
1095+
1096+ def nwfilterLookupByName(self, name):
1097+ try:
1098+ return self._nwfilters[name]
1099+ except KeyError:
1100+ raise libvirtError(VIR_ERR_NO_NWFILTER, VIR_FROM_NWFILTER,
1101+ "no nwfilter with matching name %s" % name)
1102+
1103+ def nwfilterDefineXML(self, xml):
1104+ nwfilter = NWFilter(self, xml)
1105+ self._add_filter(nwfilter)
1106+
1107+
1108+def openReadOnly(uri):
1109+ return Connection(uri, readonly=True)
1110+
1111+
1112+def openAuth(uri, auth, flags):
1113+ if flags != 0:
1114+ raise Exception(_("Please extend mock libvirt module to support "
1115+ "flags"))
1116+
1117+ if auth != [[VIR_CRED_AUTHNAME, VIR_CRED_NOECHOPROMPT],
1118+ 'root',
1119+ None]:
1120+ raise Exception(_("Please extend fake libvirt module to support "
1121+ "this auth method"))
1122+
1123+ return Connection(uri, readonly=False)
1124
1125=== added file 'nova/tests/test_fakelibvirt.py'
1126--- nova/tests/test_fakelibvirt.py 1970-01-01 00:00:00 +0000
1127+++ nova/tests/test_fakelibvirt.py 2011-09-16 15:21:29 +0000
1128@@ -0,0 +1,391 @@
1129+# vim: tabstop=4 shiftwidth=4 softtabstop=4
1130+#
1131+# Copyright 2010 OpenStack LLC
1132+#
1133+# Licensed under the Apache License, Version 2.0 (the "License"); you may
1134+# not use this file except in compliance with the License. You may obtain
1135+# a copy of the License at
1136+#
1137+# http://www.apache.org/licenses/LICENSE-2.0
1138+#
1139+# Unless required by applicable law or agreed to in writing, software
1140+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1141+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1142+# License for the specific language governing permissions and limitations
1143+# under the License.
1144+
1145+from nova import test
1146+
1147+from xml.etree.ElementTree import fromstring as xml_to_tree
1148+
1149+import fakelibvirt as libvirt
1150+
1151+
1152+def get_vm_xml(name="testname", uuid=None, source_type='file',
1153+ interface_type='bridge'):
1154+ uuid_tag = ''
1155+ if uuid:
1156+ uuid_tag = '<uuid>%s</uuid>' % (uuid,)
1157+
1158+ return '''<domain type='kvm'>
1159+ <name>%(name)s</name>
1160+%(uuid_tag)s
1161+ <memory>128000</memory>
1162+ <vcpu>1</vcpu>
1163+ <os>
1164+ <type>hvm</type>
1165+ <kernel>/somekernel</kernel>
1166+ <cmdline>root=/dev/sda</cmdline>
1167+ <boot dev='hd'/>
1168+ </os>
1169+ <features>
1170+ <acpi/>
1171+ </features>
1172+ <devices>
1173+ <disk type='file' device='disk'>
1174+ <driver name='qemu' type='qcow2'/>
1175+ <source %(source_type)s='/somefile'/>
1176+ <target dev='vda' bus='virtio'/>
1177+ </disk>
1178+ <interface type='%(interface_type)s'>
1179+ <mac address='05:26:3e:31:28:1f'/>
1180+ <source %(interface_type)s='br100'/>
1181+ </interface>
1182+ <input type='mouse' bus='ps2'/>
1183+ <graphics type='vnc' port='5901' autoport='yes' keymap='en-us'/>
1184+ </devices>
1185+</domain>''' % {'name': name,
1186+ 'uuid_tag': uuid_tag,
1187+ 'source_type': source_type,
1188+ 'interface_type': interface_type}
1189+
1190+
1191+class FakeLibvirtTests(test.TestCase):
1192+ def setUp(self):
1193+ super(FakeLibvirtTests, self).setUp()
1194+ libvirt._reset()
1195+
1196+ def get_openReadOnly_curry_func(self):
1197+ return lambda uri: libvirt.openReadOnly(uri)
1198+
1199+ def get_openAuth_curry_func(self):
1200+ return lambda uri: libvirt.openAuth(uri,
1201+ [[libvirt.VIR_CRED_AUTHNAME,
1202+ libvirt.VIR_CRED_NOECHOPROMPT],
1203+ 'root',
1204+ None], 0)
1205+
1206+ def _test_connect_method_accepts_None_uri_by_default(self, conn_method):
1207+ conn = conn_method(None)
1208+ self.assertNotEqual(conn, None, "Connecting to fake libvirt failed")
1209+
1210+ def test_openReadOnly_accepts_None_uri_by_default(self):
1211+ conn_method = self.get_openReadOnly_curry_func()
1212+ self._test_connect_method_accepts_None_uri_by_default(conn_method)
1213+
1214+ def test_openAuth_accepts_None_uri_by_default(self):
1215+ conn_method = self.get_openAuth_curry_func()
1216+ self._test_connect_method_accepts_None_uri_by_default(conn_method)
1217+
1218+ def _test_connect_method_can_refuse_None_uri(self, conn_method):
1219+ libvirt.allow_default_uri_connection = False
1220+ self.assertRaises(Exception, conn_method, None)
1221+
1222+ def test_openReadOnly_can_refuse_None_uri(self):
1223+ conn_method = self.get_openReadOnly_curry_func()
1224+ self._test_connect_method_can_refuse_None_uri(conn_method)
1225+
1226+ def test_openAuth_can_refuse_None_uri(self):
1227+ conn_method = self.get_openAuth_curry_func()
1228+ self._test_connect_method_can_refuse_None_uri(conn_method)
1229+
1230+ def _test_connect_method_refuses_invalid_URI(self, conn_method):
1231+ self.assertRaises(libvirt.libvirtError, conn_method, 'blah')
1232+
1233+ def test_openReadOnly_refuses_invalid_URI(self):
1234+ conn_method = self.get_openReadOnly_curry_func()
1235+ self._test_connect_method_refuses_invalid_URI(conn_method)
1236+
1237+ def test_openAuth_refuses_invalid_URI(self):
1238+ conn_method = self.get_openAuth_curry_func()
1239+ self._test_connect_method_refuses_invalid_URI(conn_method)
1240+
1241+ def test_getInfo(self):
1242+ conn = libvirt.openReadOnly(None)
1243+ res = conn.getInfo()
1244+ self.assertIn(res[0], ('i686', 'x86_64'))
1245+ self.assertTrue(1024 <= res[1] <= 16384,
1246+ "Memory unusually high or low.")
1247+ self.assertTrue(1 <= res[2] <= 32,
1248+ "Active CPU count unusually high or low.")
1249+ self.assertTrue(800 <= res[3] <= 4500,
1250+ "CPU speed unusually high or low.")
1251+ self.assertTrue(res[2] <= (res[5] * res[6]),
1252+ "More active CPUs than num_sockets*cores_per_socket")
1253+
1254+ def test_createXML_detects_invalid_xml(self):
1255+ self._test_XML_func_detects_invalid_xml('createXML', [0])
1256+
1257+ def test_defineXML_detects_invalid_xml(self):
1258+ self._test_XML_func_detects_invalid_xml('defineXML', [])
1259+
1260+ def _test_XML_func_detects_invalid_xml(self, xmlfunc_name, args):
1261+ conn = self.get_openAuth_curry_func()('qemu:///system')
1262+ try:
1263+ getattr(conn, xmlfunc_name)("this is not valid </xml>", *args)
1264+ except libvirt.libvirtError, e:
1265+ self.assertEqual(e.get_error_code(), libvirt.VIR_ERR_XML_DETAIL)
1266+ self.assertEqual(e.get_error_domain(), libvirt.VIR_FROM_DOMAIN)
1267+ return
1268+ raise self.failureException("Invalid XML didn't raise libvirtError")
1269+
1270+ def test_defineXML_defines_domain(self):
1271+ conn = self.get_openAuth_curry_func()('qemu:///system')
1272+ conn.defineXML(get_vm_xml())
1273+ dom = conn.lookupByName('testname')
1274+ self.assertEqual('testname', dom.name())
1275+ self.assertEqual(0, dom.isActive())
1276+ dom.undefine()
1277+ self.assertRaises(libvirt.libvirtError,
1278+ conn.lookupByName,
1279+ 'testname')
1280+
1281+ def test_blockStats(self):
1282+ conn = self.get_openAuth_curry_func()('qemu:///system')
1283+ conn.createXML(get_vm_xml(), 0)
1284+ dom = conn.lookupByName('testname')
1285+ blockstats = dom.blockStats('vda')
1286+ self.assertEqual(len(blockstats), 5)
1287+ for x in blockstats:
1288+ self.assertTrue(type(x) in [int, long])
1289+
1290+ def test_attach_detach(self):
1291+ conn = self.get_openAuth_curry_func()('qemu:///system')
1292+ conn.createXML(get_vm_xml(), 0)
1293+ dom = conn.lookupByName('testname')
1294+ xml = '''<disk type='block'>
1295+ <driver name='qemu' type='raw'/>
1296+ <source dev='/dev/nbd0'/>
1297+ <target dev='/dev/vdc' bus='virtio'/>
1298+ </disk>'''
1299+ self.assertTrue(dom.attachDevice(xml))
1300+ self.assertTrue(dom.detachDevice(xml))
1301+
1302+ def test_info(self):
1303+ conn = self.get_openAuth_curry_func()('qemu:///system')
1304+ conn.createXML(get_vm_xml(), 0)
1305+ dom = conn.lookupByName('testname')
1306+ info = dom.info()
1307+ self.assertEqual(info[0], libvirt.VIR_DOMAIN_RUNNING)
1308+ self.assertEqual(info[1], 128000)
1309+ self.assertTrue(info[2] <= 128000)
1310+ self.assertEqual(info[3], 1)
1311+ self.assertTrue(type(info[4]) in [int, long])
1312+
1313+ def test_createXML_runs_domain(self):
1314+ conn = self.get_openAuth_curry_func()('qemu:///system')
1315+ conn.createXML(get_vm_xml(), 0)
1316+ dom = conn.lookupByName('testname')
1317+ self.assertEqual('testname', dom.name())
1318+ self.assertEqual(1, dom.isActive())
1319+ dom.destroy()
1320+ try:
1321+ dom = conn.lookupByName('testname')
1322+ except libvirt.libvirtError as e:
1323+ self.assertEqual(e.get_error_code(), libvirt.VIR_ERR_NO_DOMAIN)
1324+ self.assertEqual(e.get_error_domain(), libvirt.VIR_FROM_QEMU)
1325+ return
1326+ self.fail("lookupByName succeeded for destroyed non-defined VM")
1327+
1328+ def test_defineXML_remembers_uuid(self):
1329+ conn = self.get_openAuth_curry_func()('qemu:///system')
1330+ uuid = 'b21f957d-a72f-4b93-b5a5-45b1161abb02'
1331+ conn.defineXML(get_vm_xml(uuid=uuid))
1332+ dom = conn.lookupByName('testname')
1333+ self.assertEquals(dom.UUIDString(), uuid)
1334+
1335+ def test_createWithFlags(self):
1336+ conn = self.get_openAuth_curry_func()('qemu:///system')
1337+ conn.defineXML(get_vm_xml())
1338+ dom = conn.lookupByName('testname')
1339+ self.assertFalse(dom.isActive(), 'Defined domain was running.')
1340+ dom.createWithFlags(0)
1341+ self.assertTrue(dom.isActive(),
1342+ 'Domain wasn\'t running after createWithFlags')
1343+
1344+ def test_listDomainsId_and_lookupById(self):
1345+ conn = self.get_openAuth_curry_func()('qemu:///system')
1346+ self.assertEquals(conn.listDomainsID(), [])
1347+ conn.defineXML(get_vm_xml())
1348+ dom = conn.lookupByName('testname')
1349+ dom.createWithFlags(0)
1350+ self.assertEquals(len(conn.listDomainsID()), 1)
1351+
1352+ dom_id = conn.listDomainsID()[0]
1353+ self.assertEquals(conn.lookupByID(dom_id), dom)
1354+
1355+ dom_id = conn.listDomainsID()[0]
1356+ try:
1357+ conn.lookupByID(dom_id + 1)
1358+ except libvirt.libvirtError, e:
1359+ self.assertEqual(e.get_error_code(), libvirt.VIR_ERR_NO_DOMAIN)
1360+ self.assertEqual(e.get_error_domain(), libvirt.VIR_FROM_QEMU)
1361+ return
1362+ raise self.failureException("Looking up an invalid domain ID didn't "
1363+ "raise libvirtError")
1364+
1365+ def test_define_and_retrieve(self):
1366+ conn = self.get_openAuth_curry_func()('qemu:///system')
1367+ self.assertEquals(conn.listDomainsID(), [])
1368+ conn.defineXML(get_vm_xml())
1369+ dom = conn.lookupByName('testname')
1370+ xml = dom.XMLDesc(0)
1371+ xml_to_tree(xml)
1372+
1373+ def _test_accepts_source_type(self, source_type):
1374+ conn = self.get_openAuth_curry_func()('qemu:///system')
1375+ self.assertEquals(conn.listDomainsID(), [])
1376+ conn.defineXML(get_vm_xml(source_type=source_type))
1377+ dom = conn.lookupByName('testname')
1378+ xml = dom.XMLDesc(0)
1379+ tree = xml_to_tree(xml)
1380+ elem = tree.find('./devices/disk/source')
1381+ self.assertEquals(elem.get('file'), '/somefile')
1382+
1383+ def test_accepts_source_dev(self):
1384+ self._test_accepts_source_type('dev')
1385+
1386+ def test_accepts_source_path(self):
1387+ self._test_accepts_source_type('path')
1388+
1389+ def test_network_type_bridge_sticks(self):
1390+ self._test_network_type_sticks('bridge')
1391+
1392+ def test_network_type_network_sticks(self):
1393+ self._test_network_type_sticks('network')
1394+
1395+ def _test_network_type_sticks(self, network_type):
1396+ conn = self.get_openAuth_curry_func()('qemu:///system')
1397+ self.assertEquals(conn.listDomainsID(), [])
1398+ conn.defineXML(get_vm_xml(interface_type=network_type))
1399+ dom = conn.lookupByName('testname')
1400+ xml = dom.XMLDesc(0)
1401+ tree = xml_to_tree(xml)
1402+ elem = tree.find('./devices/interface')
1403+ self.assertEquals(elem.get('type'), network_type)
1404+ elem = elem.find('./source')
1405+ self.assertEquals(elem.get(network_type), 'br100')
1406+
1407+ def test_getType(self):
1408+ conn = self.get_openAuth_curry_func()('qemu:///system')
1409+ self.assertEquals(conn.getType(), 'QEMU')
1410+
1411+ def test_getVersion(self):
1412+ conn = self.get_openAuth_curry_func()('qemu:///system')
1413+ self.assertTrue(type(conn.getVersion()) is int)
1414+
1415+ def test_getCapabilities(self):
1416+ conn = self.get_openAuth_curry_func()('qemu:///system')
1417+ xml_to_tree(conn.getCapabilities())
1418+
1419+ def test_nwfilter_define_undefine(self):
1420+ conn = self.get_openAuth_curry_func()('qemu:///system')
1421+ # Will raise an exception if it's not valid XML
1422+ xml = '''<filter name='nova-instance-instance-789' chain='root'>
1423+ <uuid>946878c6-3ad3-82b2-87f3-c709f3807f58</uuid>
1424+ </filter>'''
1425+
1426+ conn.nwfilterDefineXML(xml)
1427+ nwfilter = conn.nwfilterLookupByName('nova-instance-instance-789')
1428+ nwfilter.undefine()
1429+ try:
1430+ conn.nwfilterLookupByName('nova-instance-instance-789320334')
1431+ except libvirt.libvirtError, e:
1432+ self.assertEqual(e.get_error_code(), libvirt.VIR_ERR_NO_NWFILTER)
1433+ self.assertEqual(e.get_error_domain(), libvirt.VIR_FROM_NWFILTER)
1434+ return
1435+ raise self.failureException("Invalid NWFilter name didn't"
1436+ " raise libvirtError")
1437+
1438+ def test_compareCPU_compatible(self):
1439+ conn = self.get_openAuth_curry_func()('qemu:///system')
1440+
1441+ xml = '''<cpu>
1442+ <arch>%s</arch>
1443+ <model>%s</model>
1444+ <vendor>%s</vendor>
1445+ <topology sockets="%d" cores="%d" threads="%d"/>
1446+ </cpu>''' % (libvirt.node_arch,
1447+ libvirt.node_cpu_model,
1448+ libvirt.node_cpu_vendor,
1449+ libvirt.node_sockets,
1450+ libvirt.node_cores,
1451+ libvirt.node_threads)
1452+ self.assertEqual(conn.compareCPU(xml, 0),
1453+ libvirt.VIR_CPU_COMPARE_IDENTICAL)
1454+
1455+ def test_compareCPU_incompatible_vendor(self):
1456+ conn = self.get_openAuth_curry_func()('qemu:///system')
1457+
1458+ xml = '''<cpu>
1459+ <arch>%s</arch>
1460+ <model>%s</model>
1461+ <vendor>%s</vendor>
1462+ <topology sockets="%d" cores="%d" threads="%d"/>
1463+ </cpu>''' % (libvirt.node_arch,
1464+ libvirt.node_cpu_model,
1465+ "AnotherVendor",
1466+ libvirt.node_sockets,
1467+ libvirt.node_cores,
1468+ libvirt.node_threads)
1469+ self.assertEqual(conn.compareCPU(xml, 0),
1470+ libvirt.VIR_CPU_COMPARE_INCOMPATIBLE)
1471+
1472+ def test_compareCPU_incompatible_arch(self):
1473+ conn = self.get_openAuth_curry_func()('qemu:///system')
1474+
1475+ xml = '''<cpu>
1476+ <arch>%s</arch>
1477+ <model>%s</model>
1478+ <vendor>%s</vendor>
1479+ <topology sockets="%d" cores="%d" threads="%d"/>
1480+ </cpu>''' % ('not-a-valid-arch',
1481+ libvirt.node_cpu_model,
1482+ libvirt.node_cpu_vendor,
1483+ libvirt.node_sockets,
1484+ libvirt.node_cores,
1485+ libvirt.node_threads)
1486+ self.assertEqual(conn.compareCPU(xml, 0),
1487+ libvirt.VIR_CPU_COMPARE_INCOMPATIBLE)
1488+
1489+ def test_compareCPU_incompatible_model(self):
1490+ conn = self.get_openAuth_curry_func()('qemu:///system')
1491+
1492+ xml = '''<cpu>
1493+ <arch>%s</arch>
1494+ <model>%s</model>
1495+ <vendor>%s</vendor>
1496+ <topology sockets="%d" cores="%d" threads="%d"/>
1497+ </cpu>''' % (libvirt.node_arch,
1498+ "AnotherModel",
1499+ libvirt.node_cpu_vendor,
1500+ libvirt.node_sockets,
1501+ libvirt.node_cores,
1502+ libvirt.node_threads)
1503+ self.assertEqual(conn.compareCPU(xml, 0),
1504+ libvirt.VIR_CPU_COMPARE_INCOMPATIBLE)
1505+
1506+ def test_compareCPU_compatible_unspecified_model(self):
1507+ conn = self.get_openAuth_curry_func()('qemu:///system')
1508+
1509+ xml = '''<cpu>
1510+ <arch>%s</arch>
1511+ <vendor>%s</vendor>
1512+ <topology sockets="%d" cores="%d" threads="%d"/>
1513+ </cpu>''' % (libvirt.node_arch,
1514+ libvirt.node_cpu_vendor,
1515+ libvirt.node_sockets,
1516+ libvirt.node_cores,
1517+ libvirt.node_threads)
1518+ self.assertEqual(conn.compareCPU(xml, 0),
1519+ libvirt.VIR_CPU_COMPARE_IDENTICAL)
1520
1521=== modified file 'nova/tests/test_image.py'
1522--- nova/tests/test_image.py 2011-08-05 19:28:22 +0000
1523+++ nova/tests/test_image.py 2011-09-16 15:21:29 +0000
1524@@ -16,6 +16,7 @@
1525 # under the License.
1526
1527 import datetime
1528+import StringIO
1529
1530 from nova import context
1531 from nova import exception
1532@@ -127,6 +128,16 @@
1533 index = self.image_service.index(self.context)
1534 self.assertEquals(len(index), 0)
1535
1536+ def test_create_then_get(self):
1537+ blob = 'some data'
1538+ s1 = StringIO.StringIO(blob)
1539+ self.image_service.create(self.context,
1540+ {'id': '32', 'foo': 'bar'},
1541+ data=s1)
1542+ s2 = StringIO.StringIO()
1543+ self.image_service.get(self.context, '32', data=s2)
1544+ self.assertEquals(s2.getvalue(), blob, 'Did not get blob back intact')
1545+
1546
1547 class FakeImageTestCase(_ImageTestCase):
1548 def setUp(self):
1549
1550=== modified file 'nova/tests/test_libvirt.py'
1551--- nova/tests/test_libvirt.py 2011-09-14 16:15:09 +0000
1552+++ nova/tests/test_libvirt.py 2011-09-16 15:21:29 +0000
1553@@ -35,9 +35,12 @@
1554 from nova.api.ec2 import cloud
1555 from nova.compute import power_state
1556 from nova.compute import vm_states
1557+from nova.virt import disk
1558+from nova.virt import images
1559 from nova.virt import driver
1560 from nova.virt.libvirt import connection
1561 from nova.virt.libvirt import firewall
1562+from nova.virt.libvirt import utils as libvirt_utils
1563 from nova.tests import fake_network
1564
1565 libvirt = None
1566@@ -733,8 +736,8 @@
1567 os.path.getsize("/test/disk").AndReturn(10 * 1024 * 1024 * 1024)
1568 # another is qcow image, so qemu-img should be mocked.
1569 self.mox.StubOutWithMock(utils, "execute")
1570- utils.execute('qemu-img', 'info', '/test/disk.local').\
1571- AndReturn((ret, ''))
1572+ utils.execute('qemu-img', 'info',
1573+ '/test/disk.local').AndReturn((ret, ''))
1574
1575 self.mox.ReplayAll()
1576 conn = connection.LibvirtConnection(False)
1577@@ -1423,3 +1426,182 @@
1578 self.assertEqual(original_filter_count - len(fakefilter.filters), 2)
1579
1580 db.instance_destroy(admin_ctxt, instance_ref['id'])
1581+
1582+
1583+class LibvirtUtilsTestCase(test.TestCase):
1584+ def test_create_image(self):
1585+ self.mox.StubOutWithMock(utils, 'execute')
1586+ utils.execute('qemu-img', 'create', '-f', 'raw',
1587+ '/some/path', '10G')
1588+ utils.execute('qemu-img', 'create', '-f', 'qcow2',
1589+ '/some/stuff', '1234567891234')
1590+ # Start test
1591+ self.mox.ReplayAll()
1592+ libvirt_utils.create_image('raw', '/some/path', '10G')
1593+ libvirt_utils.create_image('qcow2', '/some/stuff', '1234567891234')
1594+
1595+ def test_create_cow_image(self):
1596+ self.mox.StubOutWithMock(utils, 'execute')
1597+ utils.execute('qemu-img', 'create', '-f', 'qcow2',
1598+ '-o', 'cluster_size=2M,backing_file=/some/path',
1599+ '/the/new/cow')
1600+ # Start test
1601+ self.mox.ReplayAll()
1602+ libvirt_utils.create_cow_image('/some/path', '/the/new/cow')
1603+
1604+ def test_get_disk_size(self):
1605+ self.mox.StubOutWithMock(utils, 'execute')
1606+ utils.execute('qemu-img',
1607+ 'info',
1608+ '/some/path').AndReturn(('''image: 00000001
1609+file format: raw
1610+virtual size: 4.4M (4592640 bytes)
1611+disk size: 4.4M''', ''))
1612+
1613+ # Start test
1614+ self.mox.ReplayAll()
1615+ self.assertEquals(libvirt_utils.get_disk_size('/some/path'), 4592640)
1616+
1617+ def test_copy_image(self):
1618+ dst_fd, dst_path = tempfile.mkstemp()
1619+ try:
1620+ os.close(dst_fd)
1621+
1622+ src_fd, src_path = tempfile.mkstemp()
1623+ try:
1624+ with os.fdopen(src_fd) as fp:
1625+ fp.write('canary')
1626+
1627+ libvirt_utils.copy_image(src_path, dst_path)
1628+ with open(dst_path, 'r') as fp:
1629+ self.assertEquals(fp.read(), 'canary')
1630+ finally:
1631+ os.unlink(src_path)
1632+ finally:
1633+ os.unlink(dst_path)
1634+
1635+ def test_mkfs(self):
1636+ self.mox.StubOutWithMock(utils, 'execute')
1637+ utils.execute('mkfs', '-t', 'ext4', '/my/block/dev')
1638+ utils.execute('mkswap', '/my/swap/block/dev')
1639+ self.mox.ReplayAll()
1640+
1641+ libvirt_utils.mkfs('ext4', '/my/block/dev')
1642+ libvirt_utils.mkfs('swap', '/my/swap/block/dev')
1643+
1644+ def test_ensure_tree(self):
1645+ tmpdir = tempfile.mkdtemp()
1646+ try:
1647+ testdir = '%s/foo/bar/baz' % (tmpdir,)
1648+ libvirt_utils.ensure_tree(testdir)
1649+ self.assertTrue(os.path.isdir(testdir))
1650+ finally:
1651+ shutil.rmtree(tmpdir)
1652+
1653+ def test_write_to_file(self):
1654+ dst_fd, dst_path = tempfile.mkstemp()
1655+ try:
1656+ os.close(dst_fd)
1657+
1658+ libvirt_utils.write_to_file(dst_path, 'hello')
1659+ with open(dst_path, 'r') as fp:
1660+ self.assertEquals(fp.read(), 'hello')
1661+ finally:
1662+ os.unlink(dst_path)
1663+
1664+ def test_write_to_file_with_umask(self):
1665+ dst_fd, dst_path = tempfile.mkstemp()
1666+ try:
1667+ os.close(dst_fd)
1668+ os.unlink(dst_path)
1669+
1670+ libvirt_utils.write_to_file(dst_path, 'hello', umask=0277)
1671+ with open(dst_path, 'r') as fp:
1672+ self.assertEquals(fp.read(), 'hello')
1673+ mode = os.stat(dst_path).st_mode
1674+ self.assertEquals(mode & 0277, 0)
1675+ finally:
1676+ os.unlink(dst_path)
1677+
1678+ def test_chown(self):
1679+ self.mox.StubOutWithMock(utils, 'execute')
1680+ utils.execute('chown', 'soren', '/some/path', run_as_root=True)
1681+ self.mox.ReplayAll()
1682+ libvirt_utils.chown('/some/path', 'soren')
1683+
1684+ def test_extract_snapshot(self):
1685+ self.mox.StubOutWithMock(utils, 'execute')
1686+ utils.execute('qemu-img', 'convert', '-f', 'qcow2', '-O', 'raw',
1687+ '-s', 'snap1', '/path/to/disk/image', '/extracted/snap')
1688+
1689+ # Start test
1690+ self.mox.ReplayAll()
1691+ libvirt_utils.extract_snapshot('/path/to/disk/image',
1692+ 'snap1', '/extracted/snap')
1693+
1694+ def test_load_file(self):
1695+ dst_fd, dst_path = tempfile.mkstemp()
1696+ try:
1697+ os.close(dst_fd)
1698+
1699+ # We have a test for write_to_file. If that is sound, this suffices
1700+ libvirt_utils.write_to_file(dst_path, 'hello')
1701+ self.assertEquals(libvirt_utils.load_file(dst_path), 'hello')
1702+ finally:
1703+ os.unlink(dst_path)
1704+
1705+ def test_file_open(self):
1706+ dst_fd, dst_path = tempfile.mkstemp()
1707+ try:
1708+ os.close(dst_fd)
1709+
1710+ # We have a test for write_to_file. If that is sound, this suffices
1711+ libvirt_utils.write_to_file(dst_path, 'hello')
1712+ with libvirt_utils.file_open(dst_path, 'r') as fp:
1713+ self.assertEquals(fp.read(), 'hello')
1714+ finally:
1715+ os.unlink(dst_path)
1716+
1717+ def test_run_ajaxterm(self):
1718+ self.mox.StubOutWithMock(utils, 'execute')
1719+ token = 's3cr3tt0ken'
1720+ shell_cmd = 'shell-cmd.py'
1721+ port = 2048
1722+ utils.execute(mox.IgnoreArg(),
1723+ '--command', shell_cmd,
1724+ '-t', token,
1725+ '-p', port)
1726+
1727+ # Start test
1728+ self.mox.ReplayAll()
1729+ libvirt_utils.run_ajaxterm(shell_cmd, token, port)
1730+
1731+ def test_get_fs_info(self):
1732+ stdout, stderr = utils.execute('df', '-B1', '/tmp')
1733+ info_line = stdout.split('\n')[1]
1734+ _dev, total, used, free, _percentage, _mntpnt = re.split('\s+',
1735+ info_line)
1736+
1737+ fs_info = libvirt_utils.get_fs_info('/tmp')
1738+ self.assertEquals(int(total), fs_info['total'])
1739+ self.assertEquals(int(free), fs_info['free'])
1740+ self.assertEquals(int(used), fs_info['used'])
1741+
1742+ def test_fetch_image(self):
1743+ self.mox.StubOutWithMock(images, 'fetch')
1744+ self.mox.StubOutWithMock(disk, 'extend')
1745+
1746+ context = 'opaque context'
1747+ target = '/tmp/targetfile'
1748+ image_id = '4'
1749+ user_id = 'fake'
1750+ project_id = 'fake'
1751+ images.fetch(context, image_id, target, user_id, project_id)
1752+ images.fetch(context, image_id, target, user_id, project_id)
1753+ disk.extend(target, '10G')
1754+
1755+ self.mox.ReplayAll()
1756+ libvirt_utils.fetch_image(context, target, image_id,
1757+ user_id, project_id)
1758+ libvirt_utils.fetch_image(context, target, image_id,
1759+ user_id, project_id, size='10G')
1760
1761=== modified file 'nova/tests/test_virt_drivers.py'
1762--- nova/tests/test_virt_drivers.py 2011-09-08 21:10:03 +0000
1763+++ nova/tests/test_virt_drivers.py 2011-09-16 15:21:29 +0000
1764@@ -16,6 +16,7 @@
1765
1766 import base64
1767 import netaddr
1768+import os
1769 import sys
1770 import traceback
1771
1772@@ -24,6 +25,7 @@
1773 from nova import image
1774 from nova import log as logging
1775 from nova import test
1776+from nova import utils
1777 from nova.tests import utils as test_utils
1778
1779 libvirt = None
1780@@ -484,7 +486,42 @@
1781 self.driver_module = nova.virt.fake
1782 super(FakeConnectionTestCase, self).setUp()
1783
1784-# Before long, we'll add the real hypervisor drivers here as well
1785-# with whatever instrumentation they need to work independently of
1786-# their hypervisor. This way, we can verify that they all act the
1787-# same.
1788+
1789+class LibvirtConnTestCase(_VirtDriverTestCase):
1790+ def setUp(self):
1791+ # Put fakelibvirt in place
1792+ if 'libvirt' in sys.modules:
1793+ self.saved_libvirt = sys.modules['libvirt']
1794+ else:
1795+ self.saved_libvirt = None
1796+
1797+ import fakelibvirt
1798+ import fake_libvirt_utils
1799+
1800+ sys.modules['libvirt'] = fakelibvirt
1801+
1802+ import nova.virt.libvirt.connection
1803+ import nova.virt.libvirt.firewall
1804+
1805+ nova.virt.libvirt.connection.libvirt = fakelibvirt
1806+ nova.virt.libvirt.connection.libvirt_utils = fake_libvirt_utils
1807+ nova.virt.libvirt.firewall.libvirt = fakelibvirt
1808+
1809+ # Point _VirtDriverTestCase at the right module
1810+ self.driver_module = nova.virt.libvirt.connection
1811+ super(LibvirtConnTestCase, self).setUp()
1812+ FLAGS.rescue_image_id = "2"
1813+ FLAGS.rescue_kernel_id = "3"
1814+ FLAGS.rescue_ramdisk_id = None
1815+
1816+ def tearDown(self):
1817+ super(LibvirtConnTestCase, self).setUp()
1818+
1819+ # Restore libvirt
1820+ import nova.virt.libvirt.connection
1821+ import nova.virt.libvirt.firewall
1822+ if self.saved_libvirt:
1823+ sys.modules['libvirt'] = self.saved_libvirt
1824+ nova.virt.libvirt.connection.libvirt = self.saved_libvirt
1825+ nova.virt.libvirt.connection.libvirt_utils = self.saved_libvirt
1826+ nova.virt.libvirt.firewall.libvirt = self.saved_libvirt
1827
1828=== modified file 'nova/virt/libvirt/connection.py'
1829--- nova/virt/libvirt/connection.py 2011-09-16 01:57:03 +0000
1830+++ nova/virt/libvirt/connection.py 2011-09-16 15:21:29 +0000
1831@@ -70,7 +70,7 @@
1832 from nova.virt import disk
1833 from nova.virt import driver
1834 from nova.virt import images
1835-from nova.virt.libvirt import netutils
1836+from nova.virt.libvirt import utils as libvirt_utils
1837
1838
1839 libvirt = None
1840@@ -125,8 +125,6 @@
1841 'Define block migration behavior.')
1842 flags.DEFINE_integer('live_migration_bandwidth', 0,
1843 'Define live migration behavior')
1844-flags.DEFINE_string('qemu_img', 'qemu-img',
1845- 'binary to use for qemu-img commands')
1846 flags.DEFINE_string('libvirt_vif_type', 'bridge',
1847 'Type of VIF to create.')
1848 flags.DEFINE_string('libvirt_vif_driver',
1849@@ -396,7 +394,10 @@
1850 to support this command.
1851
1852 """
1853- virt_dom = self._lookup_by_name(instance['name'])
1854+ try:
1855+ virt_dom = self._lookup_by_name(instance['name'])
1856+ except exception.InstanceNotFound:
1857+ raise exception.InstanceNotRunning()
1858
1859 (image_service, image_id) = nova.image.get_image_service(
1860 context, instance['image_ref'])
1861@@ -444,20 +445,10 @@
1862 # Export the snapshot to a raw image
1863 temp_dir = tempfile.mkdtemp()
1864 out_path = os.path.join(temp_dir, snapshot_name)
1865- qemu_img_cmd = (FLAGS.qemu_img,
1866- 'convert',
1867- '-f',
1868- 'qcow2',
1869- '-O',
1870- 'raw',
1871- '-s',
1872- snapshot_name,
1873- disk_path,
1874- out_path)
1875- utils.execute(*qemu_img_cmd)
1876+ libvirt_utils.extract_snapshot(disk_path, snapshot_name, out_path)
1877
1878 # Upload that image to the image service
1879- with open(out_path) as image_file:
1880+ with libvirt_utils.file_open(out_path) as image_file:
1881 image_service.update(context,
1882 image_href,
1883 metadata,
1884@@ -549,7 +540,8 @@
1885 rescue_images = {'image_id': FLAGS.rescue_image_id,
1886 'kernel_id': FLAGS.rescue_kernel_id,
1887 'ramdisk_id': FLAGS.rescue_ramdisk_id}
1888- self._create_image(context, instance, xml, '.rescue', rescue_images)
1889+ self._create_image(context, instance, xml, '.rescue', rescue_images,
1890+ network_info=network_info)
1891 self._create_new_domain(xml)
1892
1893 def _wait_for_rescue():
1894@@ -572,14 +564,14 @@
1895 return timer.start(interval=0.5, now=True)
1896
1897 @exception.wrap_exception()
1898- def unrescue(self, instance, network_info):
1899+ def unrescue(self, instance, callback, network_info):
1900 """Reboot the VM which is being rescued back into primary images.
1901
1902 Because reboot destroys and re-creates instances, unresue should
1903 simply call reboot.
1904
1905 """
1906- self.reboot(instance, network_info)
1907+ self.reboot(instance, network_info, 'SOFT')
1908
1909 @exception.wrap_exception()
1910 def poll_rescued_instances(self, timeout):
1911@@ -641,18 +633,12 @@
1912 fp.write(data)
1913 return fpath
1914
1915- def _dump_file(self, fpath):
1916- fp = open(fpath, 'r+')
1917- contents = fp.read()
1918- LOG.info(_('Contents of file %(fpath)s: %(contents)r') % locals())
1919- return contents
1920-
1921 @exception.wrap_exception()
1922 def get_console_output(self, instance):
1923 console_log = os.path.join(FLAGS.instances_path, instance['name'],
1924 'console.log')
1925
1926- utils.execute('chown', os.getuid(), console_log, run_as_root=True)
1927+ libvirt_utils.chown(console_log, os.getuid())
1928
1929 if FLAGS.libvirt_type == 'xen':
1930 # Xen is special
1931@@ -666,23 +652,10 @@
1932 else:
1933 fpath = console_log
1934
1935- return self._dump_file(fpath)
1936+ return libvirt_utils.load_file(fpath)
1937
1938 @exception.wrap_exception()
1939 def get_ajax_console(self, instance):
1940- def get_open_port():
1941- start_port, end_port = FLAGS.ajaxterm_portrange.split("-")
1942- for i in xrange(0, 100): # don't loop forever
1943- port = random.randint(int(start_port), int(end_port))
1944- # netcat will exit with 0 only if the port is in use,
1945- # so a nonzero return value implies it is unused
1946- cmd = 'netcat', '0.0.0.0', port, '-w', '1'
1947- try:
1948- stdout, stderr = utils.execute(*cmd, process_input='')
1949- except exception.ProcessExecutionError:
1950- return port
1951- raise Exception(_('Unable to find an open port'))
1952-
1953 def get_pty_for_instance(instance_name):
1954 virt_dom = self._lookup_by_name(instance_name)
1955 xml = virt_dom.XMLDesc(0)
1956@@ -693,17 +666,15 @@
1957 source = serial.getElementsByTagName('source')[0]
1958 return source.getAttribute('path')
1959
1960- port = get_open_port()
1961+ start_port, end_port = FLAGS.ajaxterm_portrange.split("-")
1962+ port = libvirt_utils.get_open_port(int(start_port), int(end_port))
1963 token = str(uuid.uuid4())
1964 host = instance['host']
1965
1966 ajaxterm_cmd = 'sudo socat - %s' \
1967 % get_pty_for_instance(instance['name'])
1968
1969- cmd = ['%s/tools/ajaxterm/ajaxterm.py' % utils.novadir(),
1970- '--command', ajaxterm_cmd, '-t', token, '-p', port]
1971-
1972- utils.execute(cmd)
1973+ libvirt_utils.run_ajaxterm(ajaxterm_cmd, token, port)
1974 return {'token': token, 'host': host, 'port': port}
1975
1976 def get_host_ip_addr(self):
1977@@ -746,7 +717,7 @@
1978 if not os.path.exists(target):
1979 base_dir = os.path.join(FLAGS.instances_path, '_base')
1980 if not os.path.exists(base_dir):
1981- os.mkdir(base_dir)
1982+ libvirt_utils.ensure_tree(base_dir)
1983 base = os.path.join(base_dir, fname)
1984
1985 @utils.synchronized(fname)
1986@@ -757,18 +728,9 @@
1987 call_if_not_exists(base, fn, *args, **kwargs)
1988
1989 if cow:
1990- utils.execute('qemu-img', 'create', '-f', 'qcow2', '-o',
1991- 'cluster_size=2M,backing_file=%s' % base,
1992- target)
1993+ libvirt_utils.create_cow_image(base, target)
1994 else:
1995- utils.execute('cp', base, target)
1996-
1997- def _fetch_image(self, context, target, image_id, user_id, project_id,
1998- size=None):
1999- """Grab image and optionally attempt to resize it"""
2000- images.fetch(context, image_id, target, user_id, project_id)
2001- if size:
2002- disk.extend(target, size)
2003+ libvirt_utils.copy_image(base, target)
2004
2005 def _create_local(self, target, local_size, prefix='G', fs_format=None):
2006 """Create a blank image of specified size"""
2007@@ -776,9 +738,10 @@
2008 if not fs_format:
2009 fs_format = FLAGS.default_local_format
2010
2011- utils.execute('truncate', target, '-s', "%d%c" % (local_size, prefix))
2012+ libvirt_utils.create_image('raw', target,
2013+ '%d%c' % (local_size, prefix))
2014 if fs_format:
2015- utils.execute('mkfs', '-t', fs_format, target)
2016+ libvirt_utils.mkfs(fs_format, target)
2017
2018 def _create_ephemeral(self, target, local_size, fs_label, os_type):
2019 self._create_local(target, local_size)
2020@@ -786,8 +749,8 @@
2021
2022 def _create_swap(self, target, swap_gb):
2023 """Create a swap file of specified size"""
2024- self._create_local(target, swap_gb)
2025- utils.execute('mkswap', target)
2026+ libvirt_utils.create_image('raw', target, '%dG' % swap_gb)
2027+ libvirt_utils.mkfs(target, 'swap')
2028
2029 def _create_image(self, context, inst, libvirt_xml, suffix='',
2030 disk_images=None, network_info=None,
2031@@ -802,20 +765,17 @@
2032 fname + suffix)
2033
2034 # ensure directories exist and are writable
2035- utils.execute('mkdir', '-p', basepath(suffix=''))
2036+ libvirt_utils.ensure_tree(basepath(suffix=''))
2037
2038 LOG.info(_('instance %s: Creating image'), inst['name'])
2039- f = open(basepath('libvirt.xml'), 'w')
2040- f.write(libvirt_xml)
2041- f.close()
2042+ libvirt_utils.write_to_file(basepath('libvirt.xml'), libvirt_xml)
2043
2044 if FLAGS.libvirt_type == 'lxc':
2045 container_dir = '%s/rootfs' % basepath(suffix='')
2046- utils.execute('mkdir', '-p', container_dir)
2047+ libvirt_utils.ensure_tree(container_dir)
2048
2049 # NOTE(vish): No need add the suffix to console.log
2050- os.close(os.open(basepath('console.log', ''),
2051- os.O_CREAT | os.O_WRONLY, 0660))
2052+ libvirt_utils.write_to_file(basepath('console.log', ''), '', 007)
2053
2054 if not disk_images:
2055 disk_images = {'image_id': inst['image_ref'],
2056@@ -824,7 +784,7 @@
2057
2058 if disk_images['kernel_id']:
2059 fname = '%08x' % int(disk_images['kernel_id'])
2060- self._cache_image(fn=self._fetch_image,
2061+ self._cache_image(fn=libvirt_utils.fetch_image,
2062 context=context,
2063 target=basepath('kernel'),
2064 fname=fname,
2065@@ -833,7 +793,7 @@
2066 project_id=inst['project_id'])
2067 if disk_images['ramdisk_id']:
2068 fname = '%08x' % int(disk_images['ramdisk_id'])
2069- self._cache_image(fn=self._fetch_image,
2070+ self._cache_image(fn=libvirt_utils.fetch_image,
2071 context=context,
2072 target=basepath('ramdisk'),
2073 fname=fname,
2074@@ -852,7 +812,7 @@
2075
2076 if not self._volume_in_mapping(self.default_root_device,
2077 block_device_info):
2078- self._cache_image(fn=self._fetch_image,
2079+ self._cache_image(fn=libvirt_utils.fetch_image,
2080 context=context,
2081 target=basepath('disk'),
2082 fname=root_fname,
2083@@ -914,7 +874,7 @@
2084
2085 if config_drive_id:
2086 fname = '%08x' % int(config_drive_id)
2087- self._cache_image(fn=self._fetch_image,
2088+ self._cache_image(fn=libvirt_utils.fetch_image,
2089 target=basepath('disk.config'),
2090 fname=fname,
2091 image_id=config_drive_id,
2092@@ -1001,7 +961,7 @@
2093 ' data into image %(img_id)s (%(e)s)') % locals())
2094
2095 if FLAGS.libvirt_type == 'uml':
2096- utils.execute('chown', 'root', basepath('disk'), run_as_root=True)
2097+ libvirt_utils.chown(basepath('disk'), 'root')
2098
2099 if FLAGS.libvirt_type == 'uml':
2100 _disk_prefix = 'ubd'
2101@@ -1208,10 +1168,6 @@
2102
2103 return domain
2104
2105- def get_diagnostics(self, instance_name):
2106- raise exception.ApiError(_("diagnostics are not supported "
2107- "for libvirt"))
2108-
2109 def get_disks(self, instance_name):
2110 """
2111 Note that this function takes an instance name.
2112@@ -1335,8 +1291,8 @@
2113
2114 """
2115
2116- hddinfo = os.statvfs(FLAGS.instances_path)
2117- return hddinfo.f_frsize * hddinfo.f_blocks / 1024 / 1024 / 1024
2118+ stats = libvirt_utils.get_fs_info(FLAGS.instances_path)
2119+ return stats['total'] / (1024 ** 3)
2120
2121 def get_vcpu_used(self):
2122 """ Get vcpu usage number of physical computer.
2123@@ -1378,9 +1334,8 @@
2124
2125 """
2126
2127- hddinfo = os.statvfs(FLAGS.instances_path)
2128- avail = hddinfo.f_frsize * hddinfo.f_bavail / 1024 / 1024 / 1024
2129- return self.get_local_gb_total() - avail
2130+ stats = libvirt_utils.get_fs_info(FLAGS.instances_path)
2131+ return stats['used'] / (1024 ** 3)
2132
2133 def get_hypervisor_type(self):
2134 """Get hypervisor type.
2135@@ -1714,8 +1669,8 @@
2136 base = os.path.basename(info['path'])
2137 # Get image type and create empty disk image.
2138 instance_disk = os.path.join(instance_dir, base)
2139- utils.execute('qemu-img', 'create', '-f', info['type'],
2140- instance_disk, info['local_gb'])
2141+ libvirt_utils.create_image(info['type'], instance_disk,
2142+ info['local_gb'])
2143
2144 # if image has kernel and ramdisk, just download
2145 # following normal way.
2146@@ -1723,13 +1678,13 @@
2147 user = manager.AuthManager().get_user(instance_ref['user_id'])
2148 project = manager.AuthManager().get_project(
2149 instance_ref['project_id'])
2150- self._fetch_image(nova_context.get_admin_context(),
2151+ libvirt_utils.fetch_image(nova_context.get_admin_context(),
2152 os.path.join(instance_dir, 'kernel'),
2153 instance_ref['kernel_id'],
2154 user,
2155 project)
2156 if instance_ref['ramdisk_id']:
2157- self._fetch_image(nova_context.get_admin_context(),
2158+ libvirt_utils.fetch_image(nova_context.get_admin_context(),
2159 os.path.join(instance_dir, 'ramdisk'),
2160 instance_ref['ramdisk_id'],
2161 user,
2162@@ -1806,10 +1761,7 @@
2163 if disk_type == 'raw':
2164 size = int(os.path.getsize(path))
2165 else:
2166- out, err = utils.execute('qemu-img', 'info', path)
2167- size = [i.split('(')[1].split()[0] for i in out.split('\n')
2168- if i.strip().find('virtual size') >= 0]
2169- size = int(size[0])
2170+ size = libvirt_utils.get_disk_size(path)
2171
2172 # block migration needs same/larger size of empty image on the
2173 # destination host. since qemu-img creates bit smaller size image
2174@@ -1835,18 +1787,10 @@
2175 self.firewall_driver.unfilter_instance(instance_ref,
2176 network_info=network_info)
2177
2178- def update_host_status(self):
2179- """See xenapi_conn.py implementation."""
2180- pass
2181-
2182- def get_host_stats(self, refresh=False):
2183- """See xenapi_conn.py implementation."""
2184- pass
2185-
2186 def host_power_action(self, host, action):
2187 """Reboots, shuts down or powers up the host."""
2188- pass
2189+ raise NotImplementedError()
2190
2191 def set_host_enabled(self, host, enabled):
2192 """Sets the specified host's ability to accept new instances."""
2193- pass
2194+ raise NotImplementedError()
2195
2196=== added file 'nova/virt/libvirt/utils.py'
2197--- nova/virt/libvirt/utils.py 1970-01-01 00:00:00 +0000
2198+++ nova/virt/libvirt/utils.py 2011-09-16 15:21:29 +0000
2199@@ -0,0 +1,229 @@
2200+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2201+
2202+# Copyright 2010 United States Government as represented by the
2203+# Administrator of the National Aeronautics and Space Administration.
2204+# All Rights Reserved.
2205+# Copyright (c) 2010 Citrix Systems, Inc.
2206+# Copyright (c) 2011 Piston Cloud Computing, Inc
2207+# Copyright (c) 2011 OpenStack LLC
2208+#
2209+# Licensed under the Apache License, Version 2.0 (the "License"); you may
2210+# not use this file except in compliance with the License. You may obtain
2211+# a copy of the License at
2212+#
2213+# http://www.apache.org/licenses/LICENSE-2.0
2214+#
2215+# Unless required by applicable law or agreed to in writing, software
2216+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
2217+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2218+# License for the specific language governing permissions and limitations
2219+# under the License.
2220+
2221+import os
2222+import random
2223+
2224+from nova import exception
2225+from nova import flags
2226+from nova import utils
2227+from nova.virt import disk
2228+from nova.virt import images
2229+
2230+FLAGS = flags.FLAGS
2231+
2232+flags.DEFINE_string('qemu_img', 'qemu-img',
2233+ 'binary to use for qemu-img commands')
2234+
2235+
2236+def execute(*args, **kwargs):
2237+ return utils.execute(*args, **kwargs)
2238+
2239+
2240+def create_image(disk_format, path, size):
2241+ """Create a disk image
2242+
2243+ :param disk_format: Disk image format (as known by qemu-img)
2244+ :param path: Desired location of the disk image
2245+ :param size: Desired size of disk image. May be given as an int or
2246+ a string. If given as an int, it will be interpreted
2247+ as bytes. If it's a string, it should consist of a number
2248+ followed by an optional prefix ('k' for kilobytes, 'm'
2249+ for megabytes, 'g' for gigabytes, 't' for terabytes). If no
2250+ prefix is given, it will be interpreted as bytes.
2251+ """
2252+ execute(FLAGS.qemu_img, 'create', '-f', disk_format, path, size)
2253+
2254+
2255+def create_cow_image(backing_file, path):
2256+ """Create COW image
2257+
2258+ Creates a COW image with the given backing file
2259+
2260+ :param backing_file: Existing image on which to base the COW image
2261+ :param path: Desired location of the COW image
2262+ """
2263+ execute(FLAGS.qemu_img, 'create', '-f', 'qcow2', '-o',
2264+ 'cluster_size=2M,backing_file=%s' % backing_file, path)
2265+
2266+
2267+def get_disk_size(path):
2268+ """Get the (virtual) size of a disk image
2269+
2270+ :param path: Path to the disk image
2271+ :returns: Size (in bytes) of the given disk image as it would be seen
2272+ by a virtual machine.
2273+ """
2274+ out, err = execute(FLAGS.qemu_img, 'info', path)
2275+ size = [i.split('(')[1].split()[0] for i in out.split('\n')
2276+ if i.strip().find('virtual size') >= 0]
2277+ return int(size[0])
2278+
2279+
2280+def copy_image(src, dest):
2281+ """Copy a disk image
2282+
2283+ :param src: Source image
2284+ :param dest: Destination path
2285+ """
2286+ execute('cp', src, dest)
2287+
2288+
2289+def mkfs(fs, path):
2290+ """Format a file or block device
2291+
2292+ :param fs: Filesystem type (examples include 'swap', 'ext3', 'ext4'
2293+ 'btrfs', etc.)
2294+ :param path: Path to file or block device to format
2295+ """
2296+ if fs == 'swap':
2297+ execute('mkswap', path)
2298+ else:
2299+ execute('mkfs', '-t', fs, path)
2300+
2301+
2302+def ensure_tree(path):
2303+ """Create a directory (and any ancestor directories required)
2304+
2305+ :param path: Directory to create
2306+ """
2307+ execute('mkdir', '-p', path)
2308+
2309+
2310+def write_to_file(path, contents, umask=None):
2311+ """Write the given contents to a file
2312+
2313+ :param path: Destination file
2314+ :param contents: Desired contents of the file
2315+ :param umask: Umask to set when creating this file (will be reset)
2316+ """
2317+ if umask:
2318+ saved_umask = os.umask(umask)
2319+
2320+ try:
2321+ with open(path, 'w') as f:
2322+ f.write(contents)
2323+ finally:
2324+ if umask:
2325+ os.umask(saved_umask)
2326+
2327+
2328+def chown(path, owner):
2329+ """Change ownership of file or directory
2330+
2331+ :param path: File or directory whose ownership to change
2332+ :param owner: Desired new owner (given as uid or username)
2333+ """
2334+ utils.execute('chown', owner, path, run_as_root=True)
2335+
2336+
2337+def extract_snapshot(disk_path, snapshot_name, out_path):
2338+ """Extract a named snapshot from a disk image
2339+
2340+ :param disk_path: Path to disk image
2341+ :param snapshot_name: Name of snapshot in disk image
2342+ :param out_path: Desired path of extracted snapshot
2343+ """
2344+ qemu_img_cmd = (FLAGS.qemu_img,
2345+ 'convert',
2346+ '-f',
2347+ 'qcow2',
2348+ '-O',
2349+ 'raw',
2350+ '-s',
2351+ snapshot_name,
2352+ disk_path,
2353+ out_path)
2354+ execute(*qemu_img_cmd)
2355+
2356+
2357+def load_file(path):
2358+ """Read contents of file
2359+
2360+ :param path: File to read
2361+ """
2362+ with open(path, 'r+') as fp:
2363+ return fp.read()
2364+
2365+
2366+def file_open(*args, **kwargs):
2367+ """Open file
2368+
2369+ see built-in file() documentation for more details
2370+ """
2371+ return file(*args, **kwargs)
2372+
2373+
2374+def get_open_port(start_port, end_port):
2375+ """Find an available port
2376+
2377+ :param start_port: Start of acceptable port range
2378+ :param end_port: End of acceptable port range
2379+ """
2380+ for i in xrange(0, 100): # don't loop forever
2381+ port = random.randint(start_port, end_port)
2382+ # netcat will exit with 0 only if the port is in use,
2383+ # so a nonzero return value implies it is unused
2384+ cmd = 'netcat', '0.0.0.0', port, '-w', '1'
2385+ try:
2386+ stdout, stderr = execute(*cmd, process_input='')
2387+ except exception.ProcessExecutionError:
2388+ return port
2389+ raise Exception(_('Unable to find an open port'))
2390+
2391+
2392+def run_ajaxterm(cmd, token, port):
2393+ """Run ajaxterm
2394+
2395+ :param cmd: Command to connect to
2396+ :param token: Token to require for authentication
2397+ :param port: Port to run on
2398+ """
2399+ cmd = ['%s/tools/ajaxterm/ajaxterm.py' % utils.novadir(),
2400+ '--command', cmd, '-t', token, '-p', port]
2401+ execute(*cmd)
2402+
2403+
2404+def get_fs_info(path):
2405+ """Get free/used/total space info for a filesystem
2406+
2407+ :param path: Any dirent on the filesystem
2408+ :returns: A dict containing:
2409+
2410+ :free: How much space is free (in bytes)
2411+ :used: How much space is used (in bytes)
2412+ :total: How big the filesystem is (in bytes)
2413+ """
2414+ hddinfo = os.statvfs(path)
2415+ total = hddinfo.f_frsize * hddinfo.f_blocks
2416+ free = hddinfo.f_frsize * hddinfo.f_bavail
2417+ used = hddinfo.f_frsize * (hddinfo.f_blocks - hddinfo.f_bfree)
2418+ return {'total': total,
2419+ 'free': free,
2420+ 'used': used}
2421+
2422+
2423+def fetch_image(context, target, image_id, user_id, project_id,
2424+ size=None):
2425+ """Grab image and optionally attempt to resize it"""
2426+ images.fetch(context, image_id, target, user_id, project_id)
2427+ if size:
2428+ disk.extend(target, size)