1
# Copyright (C) 2013 Canonical Ltd.
2
# Author: Robie Basak <robie.basak@canonical.com>
4
# This program is free software: you can redistribute it and/or modify
5
# it under the terms of the GNU Affero General Public License as published by
6
# the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU Affero General Public License for more details.
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
from __future__ import absolute_import
18
from __future__ import print_function
19
from __future__ import unicode_literals
29
from lxml import etree
30
from lxml.builder import E
33
def get_libvirt_pool_object(libvirt_conn, pool_name):
35
pool = libvirt_conn.storagePoolLookupByName(pool_name)
36
except libvirt.libvirtError:
37
raise RuntimeError("Cannot find pool %s." % repr(pool_name))
41
def create_volume_from_fobj(new_volume_name, fobj, image_type='raw',
43
"""Create a new libvirt volume and populate it from a file-like object."""
45
compressed_fobj = tempfile.NamedTemporaryFile()
46
decompressed_fobj = tempfile.NamedTemporaryFile()
47
with contextlib.closing(compressed_fobj):
48
with contextlib.closing(decompressed_fobj):
49
shutil.copyfileobj(fobj, compressed_fobj)
50
compressed_fobj.flush()
51
compressed_fobj.seek(0) # is this necessary?
52
subprocess.check_call(
54
'qemu-img', 'convert', '-f', image_type, '-O', image_type,
55
compressed_fobj.name, decompressed_fobj.name
57
shell=False, close_fds=False)
58
decompressed_fobj.seek(0) # is this necessary?
59
return _create_volume_from_fobj_with_size(
60
new_volume_name=new_volume_name,
61
fobj=decompressed_fobj,
62
fobj_size=os.fstat(decompressed_fobj.fileno()).st_size,
63
image_type=image_type,
68
def _create_volume_from_fobj_with_size(new_volume_name, fobj, fobj_size,
69
image_type, pool_name):
70
conn = libvirt.open('qemu:///system')
71
pool = get_libvirt_pool_object(conn, pool_name)
73
if image_type == 'raw':
74
extra = [E.allocation(str(fobj_size)), E.capacity(str(fobj_size))]
75
elif image_type == 'qcow2':
76
extra = [E.capacity('0')]
78
raise NotImplementedError("Unknown image type %r." % image_type)
81
E.name(new_volume_name),
82
E.target(E.format(type=image_type)),
85
vol = pool.createXML(etree.tostring(new_vol), 0)
87
stream = conn.newStream(0)
88
vol.upload(stream, 0, fobj_size, 0)
90
def handler(stream_ignored, size, opaque_ignored):
91
return fobj.read(size)
93
stream.sendAll(handler, None)
99
def volume_names_in_pool(pool_name='default'):
100
conn = libvirt.open('qemu:///system')
101
pool = get_libvirt_pool_object(conn, pool_name)
102
return pool.listVolumes()
105
def delete_volume_by_name(volume_name, pool_name='default'):
106
conn = libvirt.open('qemu:///system')
107
pool = get_libvirt_pool_object(conn, pool_name)
108
volume = pool.storageVolLookupByName(volume_name)
109
volume.delete(flags=0)
112
def have_volume_by_name(volume_name, pool_name='default'):
113
conn = libvirt.open('qemu:///system')
114
pool = get_libvirt_pool_object(conn, pool_name)
116
volume = pool.storageVolLookupByName(volume_name)
117
except libvirt.libvirtError:
123
def _get_all_domains(conn=None):
125
conn = libvirt.open('qemu:///system')
127
# libvirt in Precise doesn't seem to have a binding for
128
# virConnectListAllDomains, and it seems that we must enumerate
129
# defined-by-not-running and running instances separately and in different
132
for domain_id in conn.listDomainsID():
133
yield conn.lookupByID(domain_id)
135
for domain_name in conn.listDefinedDomains():
136
yield conn.lookupByName(domain_name)
139
def _domain_element_to_volume_paths(element):
140
assert element.tag == 'domain'
143
for source in element.xpath(
144
"/domain/devices/disk[@type='file']/source[@file]"
149
def _domain_volume_paths(domain):
152
for flags in [0, libvirt.VIR_DOMAIN_XML_INACTIVE]:
153
element = etree.fromstring(domain.XMLDesc(flags))
154
volume_paths.update(_domain_element_to_volume_paths(element))
156
return frozenset(volume_paths)
159
def _volume_element_to_volume_paths(element):
160
assert element.tag == 'volume'
161
return itertools.chain(
162
(path.text for path in element.xpath('/volume/target/path')),
163
(path.text for path in element.xpath('/volume/backingStore/path')),
167
def _volume_volume_paths(volume):
168
# Volumes can depend on other volumes ("backing stores"), so return all
169
# paths a volume needs to function, including the top level one.
172
element = etree.fromstring(volume.XMLDesc(0))
173
volume_paths.update(_volume_element_to_volume_paths(element))
175
return frozenset(volume_paths)
178
def _get_all_domain_volume_paths(conn=None):
180
conn = libvirt.open('qemu:///system')
182
all_volume_paths = set()
183
for domain in _get_all_domains(conn):
184
for path in _domain_volume_paths(domain):
186
volume = conn.storageVolLookupByKey(path)
187
except libvirt.libvirtError:
188
# ignore a lookup failure, since if a volume doesn't exist,
189
# it isn't reasonable to consider what backing volumes it may
192
all_volume_paths.update(_volume_volume_paths(volume))
194
return frozenset(all_volume_paths)
197
def get_all_domain_volume_names(conn=None, filter_by_dir=None):
198
# Limitation: filter_by_dir must currently end in a '/' and be the
199
# canonical path as libvirt returns it. Ideally I'd filter by pool instead,
200
# but the libvirt API appears to not provide any method to find what pool a
201
# volume is in when looked up by key.
203
conn = libvirt.open('qemu:///system')
205
for path in _get_all_domain_volume_paths(conn=conn):
206
volume = conn.storageVolLookupByKey(path)
207
if filter_by_dir and not volume.path().startswith(filter_by_dir):