]>
git.immae.eu Git - perso/Immae/Config/Nix.git/blob - modules/private/buildbot/common/libvirt.py
85fd90815a69d3011827f2ef7a4b32b2572fa1c7
1 # This file was part of Buildbot. Buildbot is free software: you can
2 # redistribute it and/or modify it under the terms of the GNU General Public
3 # License as published by the Free Software Foundation, version 2.
5 # This program is distributed in the hope that it will be useful, but WITHOUT
6 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
7 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10 # You should have received a copy of the GNU General Public License along with
11 # this program; if not, write to the Free Software Foundation, Inc., 51
12 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
14 # Portions Copyright Buildbot Team Members
15 # Portions Copyright 2010 Isotoma Limited
20 from twisted
.internet
import defer
21 from twisted
.internet
import threads
22 from twisted
.internet
import utils
23 from twisted
.python
import failure
24 from twisted
.python
import log
26 from buildbot
import config
27 from buildbot
.util
.eventual
import eventually
28 from buildbot
.worker
import AbstractLatentWorker
38 def random_string_generator():
39 chars
= string
.ascii_letters
40 return ''.join(random
.choice(chars
) for x
in range(6))
45 I am a class that turns parallel access into serial access.
47 I exist because we want to run libvirt access in threads as we don't
48 trust calls not to block, but under load libvirt doesn't seem to like
49 this kind of threaded use.
56 log
.msg("Looking to start a piece of work now...")
58 # Is there anything to do?
60 log
.msg("_process called when there is no work")
63 # Peek at the top of the stack - get a function to call and
64 # a deferred to fire when its all over
65 d
, next_operation
, args
, kwargs
= self
.queue
[0]
67 # Start doing some work - expects a deferred
69 d2
= next_operation(*args
, **kwargs
)
73 # Whenever a piece of work is done, whether it worked or not
74 # call this to schedule the next piece of work
77 log
.msg("Completed a piece of work")
80 log
.msg("Preparing next piece of work")
81 eventually(self
._process
)
84 # When the work is done, trigger d
87 def execute(self
, cb
, *args
, **kwargs
):
88 kickstart_processing
= not self
.queue
90 self
.queue
.append((d
, cb
, args
, kwargs
))
91 if kickstart_processing
:
95 def executeInThread(self
, cb
, *args
, **kwargs
):
96 return self
.execute(threads
.deferToThread
, cb
, *args
, **kwargs
)
99 # A module is effectively a singleton class, so this is OK
106 I am a wrapper around a libvirt Domain object
109 def __init__(self
, connection
, domain
):
110 self
.connection
= connection
114 return queue
.executeInThread(self
.domain
.name
)
117 return queue
.executeInThread(self
.domain
.create
)
120 return queue
.executeInThread(self
.domain
.shutdown
)
123 return queue
.executeInThread(self
.domain
.destroy
)
126 def __init__(self
, connection
, volume
):
127 self
.connection
= connection
130 @defer.inlineCallbacks
132 yield queue
.executeInThread(self
.volume
.wipe
)
133 yield queue
.executeInThread(self
.volume
.delete
)
137 def __init__(self
, connection
, pool
):
138 self
.connection
= connection
141 @defer.inlineCallbacks
142 def create_volume(self
, xml
):
143 res
= yield queue
.executeInThread(self
.pool
.createXML
, xml
)
144 return self
.VolumeClass(self
.connection
, res
)
149 I am a wrapper around a libvirt Connection object.
155 def __init__(self
, uri
):
157 self
.connection
= libvirt
.open(uri
)
159 @defer.inlineCallbacks
160 def create(self
, xml
):
161 """ I take libvirt XML and start a new VM """
162 res
= yield queue
.executeInThread(self
.connection
.createXML
, xml
, 0)
163 return self
.DomainClass(self
, res
)
165 @defer.inlineCallbacks
166 def lookup_pool(self
, name
):
167 res
= yield queue
.executeInThread(self
.connection
.storagePoolLookupByName
, name
)
168 return self
.PoolClass(self
, res
)
170 class LibVirtWorker(AbstractLatentWorker
):
172 def __init__(self
, name
, password
, connection
, master_url
, base_image
=None, **kwargs
):
173 super().__init
__(name
, password
, **kwargs
)
176 "The python module 'libvirt' is needed to use a LibVirtWorker")
178 self
.master_url
= master_url
179 self
.random_name
= random_string_generator()
180 self
.connection
= connection
181 self
.base_image
= base_image
184 self
.domain_name
= "buildbot-" + self
.workername
+ "-" + self
.random_name
186 self
.volume_name
= "buildbot-" + self
.workername
+ "-" + self
.random_name
187 self
.pool_name
= "buildbot-disks"
189 def reconfigService(self
, *args
, **kwargs
):
190 if 'build_wait_timeout' not in kwargs
:
191 kwargs
['build_wait_timeout'] = 0
192 return super().reconfigService(*args
, **kwargs
)
194 def canStartBuild(self
):
195 if self
.domain
and not self
.isConnected():
197 "Not accepting builds as existing domain but worker not connected")
200 return super().canStartBuild()
202 @defer.inlineCallbacks
203 def _prepare_image(self
):
204 log
.msg("Creating temporary image {}".format(self
.volume_name
))
205 pool
= yield self
.connection
.lookup_pool(self
.pool_name
)
208 <name>{vol_name}</name>
209 <capacity unit='G'>10</capacity>
211 <format type='qcow2'/>
219 <path>/etc/libvirtd/base-images/buildbot.qcow2</path>
220 <format type='qcow2'/>
223 """.format(vol_name
= self
.volume_name
)
224 self
.volume
= yield pool
.create_volume(vol_xml
)
226 @defer.inlineCallbacks
227 def start_instance(self
, build
):
229 I start a new instance of a VM.
231 If a base_image is specified, I will make a clone of that otherwise i will
234 If i'm not given libvirt domain definition XML, I will look for my name
235 in the list of defined virtual machines and start that.
239 <name>{domain_name}</name>
240 <memory unit="GiB">2</memory>
242 <sysinfo type='smbios'>
244 <entry>buildbot_master_url={master_url}</entry>
245 <entry>buildbot_worker_name={worker_name}</entry>
249 <type arch="x86_64">hvm</type>
250 <smbios mode='sysinfo'/>
253 <emulator>/run/current-system/sw/bin/qemu-system-x86_64</emulator>
254 <disk type="volume" device="disk">
255 <driver name='qemu' type='qcow2' />
256 <source type="volume" pool="{pool_name}" volume="{volume_name}" />
257 <backingStore type='volume'>
258 <format type='qcow2'/>
259 <source type="volume" pool="niximages" volume="buildbot.qcow2" />
261 <target dev="vda" bus="virtio"/>
263 <input type="keyboard" bus="usb"/>
264 <graphics type="vnc" port="-1" autoport="yes"/>
265 <interface type="network">
266 <source network="immae" />
270 """.format(volume_name
= self
.volume_name
, master_url
= self
.master_url
, pool_name
=
271 self
.pool_name
, domain_name
= self
.domain_name
, worker_name
= self
.workername
)
273 yield self
._prepare
_image
()
276 self
.domain
= yield self
.connection
.create(domain_xml
)
278 log
.err(failure
.Failure(),
279 ("Cannot start a VM ({}), failing gracefully and triggering"
280 "a new build check").format(self
.workername
))
284 return [self
.domain_name
]
286 def stop_instance(self
, fast
=False):
288 I attempt to stop a running VM.
289 I make sure any connection to the worker is removed.
290 If the VM was using a cloned image, I remove the clone
291 When everything is tidied up, I ask that bbot looks for work to do
294 log
.msg("Attempting to stop '{}'".format(self
.workername
))
295 if self
.domain
is None:
296 log
.msg("I don't think that domain is even running, aborting")
297 return defer
.succeed(None)
303 if self
.volume
is not None:
304 self
.volume
.destroy()