blob: 3c72a8d569a4a7cfccf010c847715a68081b2a00 [file] [log] [blame]
Andy Bavierae1e4932016-02-22 12:07:49 -05001# Copyright (C) 2013 Canonical Ltd.
2# Author: Robie Basak <robie.basak@canonical.com>
3#
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.
8#
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.
13#
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/>.
16
17from __future__ import absolute_import
18from __future__ import print_function
19from __future__ import unicode_literals
20
21import codecs
22import contextlib
23import itertools
24import os
25import shutil
26import subprocess
27import tempfile
28
29import libvirt
30from lxml import etree
31from lxml.builder import E
32
33LIBVIRT_DNSMASQ_LEASE_FILE = '/var/lib/libvirt/dnsmasq/default.leases'
34
35
36def get_libvirt_pool_object(libvirt_conn, pool_name):
37 try:
38 pool = libvirt_conn.storagePoolLookupByName(pool_name)
39 except libvirt.libvirtError:
40 raise RuntimeError("Cannot find pool %s." % repr(pool_name))
41 return pool
42
43
44def create_volume_from_fobj(new_volume_name, fobj, image_type='raw',
45 pool_name='default'):
46 """Create a new libvirt volume and populate it from a file-like object."""
47
48 compressed_fobj = tempfile.NamedTemporaryFile()
49 decompressed_fobj = tempfile.NamedTemporaryFile()
50 with contextlib.closing(compressed_fobj):
51 with contextlib.closing(decompressed_fobj):
52 shutil.copyfileobj(fobj, compressed_fobj)
53 compressed_fobj.flush()
54 compressed_fobj.seek(0) # is this necessary?
55 subprocess.check_call(
56 [
57 'qemu-img', 'convert', '-f', image_type, '-O', image_type,
58 compressed_fobj.name, decompressed_fobj.name
59 ],
60 shell=False, close_fds=False)
61 decompressed_fobj.seek(0) # is this necessary?
62 return _create_volume_from_fobj_with_size(
63 new_volume_name=new_volume_name,
64 fobj=decompressed_fobj,
65 fobj_size=os.fstat(decompressed_fobj.fileno()).st_size,
66 image_type=image_type,
67 pool_name=pool_name
68 )
69
70
71def _create_volume_from_fobj_with_size(new_volume_name, fobj, fobj_size,
72 image_type, pool_name):
73 conn = libvirt.open('qemu:///system')
74 pool = get_libvirt_pool_object(conn, pool_name)
75
76 if image_type == 'raw':
77 extra = [E.allocation(str(fobj_size)), E.capacity(str(fobj_size))]
78 elif image_type == 'qcow2':
79 extra = [E.capacity('0')]
80 else:
81 raise NotImplementedError("Unknown image type %r." % image_type)
82
83 new_vol = E.volume(
84 E.name(new_volume_name),
85 E.target(E.format(type=image_type)),
86 *extra
87 )
88 vol = pool.createXML(etree.tostring(new_vol), 0)
89
90 try:
91 stream = conn.newStream(0)
92 vol.upload(stream, 0, fobj_size, 0)
93
94 def handler(stream_ignored, size, opaque_ignored):
95 return fobj.read(size)
96
97 try:
98 stream.sendAll(handler, None)
99 except Exception as e:
100 try:
101 # This unexpectedly raises an exception even on a normal call,
102 # so ignore it.
103 stream.abort()
104 except:
105 pass
106 raise e
107 stream.finish()
108 except:
109 vol.delete(flags=0)
110 raise
111
112 return vol
113
114
115def volume_names_in_pool(pool_name='default'):
116 conn = libvirt.open('qemu:///system')
117 pool = get_libvirt_pool_object(conn, pool_name)
118 return pool.listVolumes()
119
120
121def delete_volume_by_name(volume_name, pool_name='default'):
122 conn = libvirt.open('qemu:///system')
123 pool = get_libvirt_pool_object(conn, pool_name)
124 volume = pool.storageVolLookupByName(volume_name)
125 volume.delete(flags=0)
126
127
128def have_volume_by_name(volume_name, pool_name='default'):
129 conn = libvirt.open('qemu:///system')
130 pool = get_libvirt_pool_object(conn, pool_name)
131 try:
132 volume = pool.storageVolLookupByName(volume_name)
133 except libvirt.libvirtError:
134 return False
135 else:
136 return True
137
138
139def _get_all_domains(conn=None):
140 if conn is None:
141 conn = libvirt.open('qemu:///system')
142
143 # libvirt in Precise doesn't seem to have a binding for
144 # virConnectListAllDomains, and it seems that we must enumerate
145 # defined-by-not-running and running instances separately and in different
146 # ways.
147
148 for domain_id in conn.listDomainsID():
149 yield conn.lookupByID(domain_id)
150
151 for domain_name in conn.listDefinedDomains():
152 yield conn.lookupByName(domain_name)
153
154
155def _domain_element_to_volume_paths(element):
156 assert element.tag == 'domain'
157 return (
158 source.get('file')
159 for source in element.xpath(
160 "/domain/devices/disk[@type='file']/source[@file]"
161 )
162 )
163
164
165def _domain_volume_paths(domain):
166 volume_paths = set()
167
168 for flags in [0, libvirt.VIR_DOMAIN_XML_INACTIVE]:
169 element = etree.fromstring(domain.XMLDesc(flags))
170 volume_paths.update(_domain_element_to_volume_paths(element))
171
172 return frozenset(volume_paths)
173
174
175def _volume_element_to_volume_paths(element):
176 assert element.tag == 'volume'
177 return itertools.chain(
178 (path.text for path in element.xpath('/volume/target/path')),
179 (path.text for path in element.xpath('/volume/backingStore/path')),
180 )
181
182
183def _volume_volume_paths(volume):
184 # Volumes can depend on other volumes ("backing stores"), so return all
185 # paths a volume needs to function, including the top level one.
186 volume_paths = set()
187
188 element = etree.fromstring(volume.XMLDesc(0))
189 volume_paths.update(_volume_element_to_volume_paths(element))
190
191 return frozenset(volume_paths)
192
193
194def _get_all_domain_volume_paths(conn=None):
195 if conn is None:
196 conn = libvirt.open('qemu:///system')
197
198 all_volume_paths = set()
199 for domain in _get_all_domains(conn):
200 for path in _domain_volume_paths(domain):
201 try:
202 volume = conn.storageVolLookupByKey(path)
203 except libvirt.libvirtError:
204 # ignore a lookup failure, since if a volume doesn't exist,
205 # it isn't reasonable to consider what backing volumes it may
206 # have
207 continue
208 all_volume_paths.update(_volume_volume_paths(volume))
209
210 return frozenset(all_volume_paths)
211
212
213def get_all_domain_volume_names(conn=None, filter_by_dir=None):
214 # Limitation: filter_by_dir must currently end in a '/' and be the
215 # canonical path as libvirt returns it. Ideally I'd filter by pool instead,
216 # but the libvirt API appears to not provide any method to find what pool a
217 # volume is in when looked up by key.
218 if conn is None:
219 conn = libvirt.open('qemu:///system')
220
221 for path in _get_all_domain_volume_paths(conn=conn):
222 volume = conn.storageVolLookupByKey(path)
223 if filter_by_dir and not volume.path().startswith(filter_by_dir):
224 continue
225 yield volume.name()
226
227
228def get_domain_macs(domain_name, conn=None):
229 if conn is None:
230 conn = libvirt.open('qemu:///system')
231
232 domain = conn.lookupByName(domain_name)
233 xml = etree.fromstring(domain.XMLDesc(0))
234 for mac in xml.xpath(
235 "/domain/devices/interface[@type='network' or @type='bridge']/mac[@address]"):
236 yield mac.get('address')
237
238
239def mac_to_ip(mac):
240 canonical_mac = mac.lower()
241 with codecs.open(LIBVIRT_DNSMASQ_LEASE_FILE, 'r') as f:
242 for line in f:
243 fields = line.split()
244 if len(fields) > 1 and fields[1].lower() == canonical_mac:
245 return fields[2]
246 return None