1
# This file is 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
19
from twisted.internet import defer, utils, reactor, threads
20
from twisted.python import log
21
from buildbot.buildslave import AbstractBuildSlave, AbstractLatentBuildSlave
26
class WorkQueue(object):
28
I am a class that turns parallel access into serial access.
30
I exist because we want to run libvirt access in threads as we don't
31
trust calls not to block, but under load libvirt doesnt seem to like
32
this kind of threaded use.
39
log.msg("Looking to start a piece of work now...")
41
# Is there anything to do?
43
log.msg("_process called when there is no work")
46
# Peek at the top of the stack - get a function to call and
47
# a deferred to fire when its all over
48
d, next_operation, args, kwargs = self.queue[0]
50
# Start doing some work - expects a deferred
52
d2 = next_operation(*args, **kwargs)
56
# Whenever a piece of work is done, whether it worked or not
57
# call this to schedule the next piece of work
59
log.msg("Completed a piece of work")
62
log.msg("Preparing next piece of work")
63
reactor.callLater(0, self._process)
65
d2.addBoth(_work_done)
67
# When the work is done, trigger d
70
def execute(self, cb, *args, **kwargs):
71
kickstart_processing = not self.queue
73
self.queue.append((d, cb, args, kwargs))
74
if kickstart_processing:
78
def executeInThread(self, cb, *args, **kwargs):
79
return self.execute(threads.deferToThread, cb, *args, **kwargs)
82
# A module is effectively a singleton class, so this is OK
89
I am a wrapper around a libvirt Domain object
92
def __init__(self, connection, domain):
93
self.connection = connection
97
return queue.executeInThread(self.domain.create)
100
return queue.executeInThread(self.domain.shutdown)
103
return queue.executeInThread(self.domain.destroy)
106
class Connection(object):
109
I am a wrapper around a libvirt Connection object.
112
def __init__(self, uri):
114
self.connection = libvirt.open(uri)
116
def lookupByName(self, name):
117
""" I lookup an existing prefined domain """
118
d = queue.executeInThread(self.connection.lookupByName, name)
120
return Domain(self, res)
124
def create(self, xml):
125
""" I take libvirt XML and start a new VM """
126
d = queue.executeInThread(self.connection.createXML, xml, 0)
128
return Domain(self, res)
133
class LibVirtSlave(AbstractLatentBuildSlave):
135
def __init__(self, name, password, connection, hd_image, base_image = None, xml=None, max_builds=None, notify_on_missing=[],
136
missing_timeout=60*20, build_wait_timeout=60*10, properties={}, locks=None):
137
AbstractLatentBuildSlave.__init__(self, name, password, max_builds, notify_on_missing,
138
missing_timeout, build_wait_timeout, properties, locks)
140
self.connection = connection
141
self.image = hd_image
142
self.base_image = base_image
145
self.insubstantiate_after_build = True
146
self.cheap_copy = True
147
self.graceful_shutdown = False
151
def _prepare_base_image(self):
153
I am a private method for creating (possibly cheap) copies of a
154
base_image for start_instance to boot.
156
if not self.base_image:
157
return defer.succeed(True)
160
clone_cmd = "qemu-img"
161
clone_args = "create -b %(base)s -f qcow2 %(image)s"
164
clone_args = "%(base)s %(image)s"
166
clone_args = clone_args % {
167
"base": self.base_image,
171
log.msg("Cloning base image: %s %s'" % (clone_cmd, clone_args))
173
def _log_result(res):
174
log.msg("Cloning exit code was: %d" % res)
177
d = utils.getProcessValue(clone_cmd, clone_args.split())
178
d.addBoth(_log_result)
181
def start_instance(self, build):
183
I start a new instance of a VM.
185
If a base_image is specified, I will make a clone of that otherwise i will
188
If i'm not given libvirt domain definition XML, I will look for my name
189
in the list of defined virtual machines and start that.
191
if self.domain is not None:
192
raise ValueError('domain active')
194
d = self._prepare_base_image()
198
return self.connection.create(self.xml)
199
d = self.connection.lookupByName(self.name)
200
def _really_start(res):
202
return self.domain.create()
203
d.addCallback(_really_start)
205
d.addCallback(_start)
209
d.addCallback(_started)
211
def _start_failed(failure):
212
log.msg("Cannot start a VM (%s), failing gracefully and triggering a new build check" % self.name)
216
d.addErrback(_start_failed)
220
def stop_instance(self, fast=False):
222
I attempt to stop a running VM.
223
I make sure any connection to the slave is removed.
224
If the VM was using a cloned image, I remove the clone
225
When everything is tidied up, I ask that bbot looks for work to do
227
log.msg("Attempting to stop '%s'" % self.name)
228
if self.domain is None:
229
log.msg("I don't think that domain is evening running, aborting")
230
return defer.succeed(None)
235
if self.graceful_shutdown and not fast:
236
log.msg("Graceful shutdown chosen for %s" % self.name)
237
d = domain.shutdown()
241
def _disconnect(res):
242
log.msg("VM destroyed (%s): Forcing its connection closed." % self.name)
243
return AbstractBuildSlave.disconnect(self)
244
d.addCallback(_disconnect)
246
def _disconnected(res):
247
log.msg("We forced disconnection (%s), cleaning up and triggering new build" % self.name)
249
os.remove(self.image)
250
self.botmaster.maybeStartBuildsForSlave(self.name)
252
d.addBoth(_disconnected)
256
def buildFinished(self, *args, **kwargs):
258
I insubstantiate a slave after it has done a build, if that is
261
AbstractLatentBuildSlave.buildFinished(self, *args, **kwargs)
262
if self.insubstantiate_after_build:
263
log.msg("Got buildFinished notification - attempting to insubstantiate")
264
self.insubstantiate()