~adiroiban/+junk/deps

« back to all changes in this revision

Viewing changes to buildbot-0.8.5/buildbot/libvirtbuildslave.py

  • Committer: Adi Roiban
  • Date: 2012-01-17 06:17:46 UTC
  • Revision ID: adi@roiban.ro-20120117061746-b1gwbsd97ukx9lv0
Upgrade to buildbot 0.8.5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.
 
4
#
 
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
 
8
# details.
 
9
#
 
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.
 
13
#
 
14
# Portions Copyright Buildbot Team Members
 
15
# Portions Copyright 2010 Isotoma Limited
 
16
 
 
17
import os
 
18
 
 
19
from twisted.internet import defer, utils, reactor, threads
 
20
from twisted.python import log
 
21
from buildbot.buildslave import AbstractBuildSlave, AbstractLatentBuildSlave
 
22
 
 
23
import libvirt
 
24
 
 
25
 
 
26
class WorkQueue(object):
 
27
    """
 
28
    I am a class that turns parallel access into serial access.
 
29
 
 
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.
 
33
    """
 
34
 
 
35
    def __init__(self):
 
36
        self.queue = []
 
37
 
 
38
    def _process(self):
 
39
        log.msg("Looking to start a piece of work now...")
 
40
 
 
41
        # Is there anything to do?
 
42
        if not self.queue:
 
43
            log.msg("_process called when there is no work")
 
44
            return
 
45
 
 
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]
 
49
 
 
50
        # Start doing some work - expects a deferred
 
51
        try:
 
52
            d2 = next_operation(*args, **kwargs)
 
53
        except:
 
54
            d2 = defer.fail()
 
55
 
 
56
        # Whenever a piece of work is done, whether it worked or not 
 
57
        # call this to schedule the next piece of work
 
58
        def _work_done(res):
 
59
            log.msg("Completed a piece of work")
 
60
            self.queue.pop(0)
 
61
            if self.queue:
 
62
                log.msg("Preparing next piece of work")
 
63
                reactor.callLater(0, self._process)
 
64
            return res
 
65
        d2.addBoth(_work_done)
 
66
 
 
67
        # When the work is done, trigger d
 
68
        d2.chainDeferred(d)
 
69
 
 
70
    def execute(self, cb, *args, **kwargs):
 
71
        kickstart_processing = not self.queue
 
72
        d = defer.Deferred()
 
73
        self.queue.append((d, cb, args, kwargs))
 
74
        if kickstart_processing:
 
75
            self._process()
 
76
        return d
 
77
 
 
78
    def executeInThread(self, cb, *args, **kwargs):
 
79
        return self.execute(threads.deferToThread, cb, *args, **kwargs)
 
80
 
 
81
 
 
82
# A module is effectively a singleton class, so this is OK
 
83
queue = WorkQueue()
 
84
 
 
85
 
 
86
class Domain(object):
 
87
 
 
88
    """
 
89
    I am a wrapper around a libvirt Domain object
 
90
    """
 
91
 
 
92
    def __init__(self, connection, domain):
 
93
        self.connection = connection
 
94
        self.domain = domain
 
95
 
 
96
    def create(self):
 
97
        return queue.executeInThread(self.domain.create)
 
98
 
 
99
    def shutdown(self):
 
100
        return queue.executeInThread(self.domain.shutdown)
 
101
 
 
102
    def destroy(self):
 
103
        return queue.executeInThread(self.domain.destroy)
 
104
 
 
105
 
 
106
class Connection(object):
 
107
 
 
108
    """
 
109
    I am a wrapper around a libvirt Connection object.
 
110
    """
 
111
 
 
112
    def __init__(self, uri):
 
113
        self.uri = uri
 
114
        self.connection = libvirt.open(uri)
 
115
 
 
116
    def lookupByName(self, name):
 
117
        """ I lookup an existing prefined domain """
 
118
        d = queue.executeInThread(self.connection.lookupByName, name)
 
119
        def _(res):
 
120
            return Domain(self, res)
 
121
        d.addCallback(_)
 
122
        return d
 
123
 
 
124
    def create(self, xml):
 
125
        """ I take libvirt XML and start a new VM """
 
126
        d = queue.executeInThread(self.connection.createXML, xml, 0)
 
127
        def _(res):
 
128
            return Domain(self, res)
 
129
        d.addCallback(_)
 
130
        return d
 
131
 
 
132
 
 
133
class LibVirtSlave(AbstractLatentBuildSlave):
 
134
 
 
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)
 
139
        self.name = name
 
140
        self.connection = connection
 
141
        self.image = hd_image
 
142
        self.base_image = base_image
 
143
        self.xml = xml
 
144
 
 
145
        self.insubstantiate_after_build = True
 
146
        self.cheap_copy = True
 
147
        self.graceful_shutdown = False
 
148
 
 
149
        self.domain = None
 
150
 
 
151
    def _prepare_base_image(self):
 
152
        """
 
153
        I am a private method for creating (possibly cheap) copies of a
 
154
        base_image for start_instance to boot.
 
155
        """
 
156
        if not self.base_image:
 
157
            return defer.succeed(True)
 
158
 
 
159
        if self.cheap_copy:
 
160
            clone_cmd = "qemu-img"
 
161
            clone_args = "create -b %(base)s -f qcow2 %(image)s"
 
162
        else:
 
163
            clone_cmd = "cp"
 
164
            clone_args = "%(base)s %(image)s"
 
165
 
 
166
        clone_args = clone_args % {
 
167
                "base": self.base_image,
 
168
                "image": self.image,
 
169
                }
 
170
 
 
171
        log.msg("Cloning base image: %s %s'" % (clone_cmd, clone_args))
 
172
 
 
173
        def _log_result(res):
 
174
            log.msg("Cloning exit code was: %d" % res)
 
175
            return res
 
176
 
 
177
        d = utils.getProcessValue(clone_cmd, clone_args.split())
 
178
        d.addBoth(_log_result)
 
179
        return d
 
180
 
 
181
    def start_instance(self, build):
 
182
        """
 
183
        I start a new instance of a VM.
 
184
 
 
185
        If a base_image is specified, I will make a clone of that otherwise i will
 
186
        use image directly.
 
187
 
 
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.
 
190
        """
 
191
        if self.domain is not None:
 
192
             raise ValueError('domain active')
 
193
 
 
194
        d = self._prepare_base_image()
 
195
 
 
196
        def _start(res):
 
197
            if self.xml:
 
198
                return self.connection.create(self.xml)
 
199
            d = self.connection.lookupByName(self.name)
 
200
            def _really_start(res):
 
201
                self.domain = res
 
202
                return self.domain.create()
 
203
            d.addCallback(_really_start)
 
204
            return d
 
205
        d.addCallback(_start)
 
206
 
 
207
        def _started(res):
 
208
            return True
 
209
        d.addCallback(_started)
 
210
 
 
211
        def _start_failed(failure):
 
212
            log.msg("Cannot start a VM (%s), failing gracefully and triggering a new build check" % self.name)
 
213
            log.err(failure)
 
214
            self.domain = None
 
215
            return False
 
216
        d.addErrback(_start_failed)
 
217
 
 
218
        return d
 
219
 
 
220
    def stop_instance(self, fast=False):
 
221
        """
 
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
 
226
        """
 
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)
 
231
 
 
232
        domain = self.domain
 
233
        self.domain = None
 
234
 
 
235
        if self.graceful_shutdown and not fast:
 
236
            log.msg("Graceful shutdown chosen for %s" % self.name)
 
237
            d = domain.shutdown()
 
238
        else:
 
239
            d = domain.destroy()
 
240
 
 
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)
 
245
 
 
246
        def _disconnected(res):
 
247
            log.msg("We forced disconnection (%s), cleaning up and triggering new build" % self.name)
 
248
            if self.base_image:
 
249
                os.remove(self.image)
 
250
            self.botmaster.maybeStartBuildsForSlave(self.name)
 
251
            return res
 
252
        d.addBoth(_disconnected)
 
253
 
 
254
        return d
 
255
 
 
256
    def buildFinished(self, *args, **kwargs):
 
257
        """
 
258
        I insubstantiate a slave after it has done a build, if that is
 
259
        desired behaviour.
 
260
        """
 
261
        AbstractLatentBuildSlave.buildFinished(self, *args, **kwargs)
 
262
        if self.insubstantiate_after_build:
 
263
            log.msg("Got buildFinished notification - attempting to insubstantiate")
 
264
            self.insubstantiate()
 
265
 
 
266