1
2
3 import os
4
5 from twisted.internet import defer, utils, reactor, threads
6 from twisted.python import log
7 from buildbot.buildslave import AbstractBuildSlave, AbstractLatentBuildSlave
8
9 import libvirt
10
11
13 """
14 I am a class that turns parallel access into serial access.
15
16 I exist because we want to run libvirt access in threads as we don't
17 trust calls not to block, but under load libvirt doesnt seem to like
18 this kind of threaded use.
19 """
20
23
25 log.msg("Looking to start a piece of work now...")
26
27
28 if not self.queue:
29 log.msg("_process called when there is no work")
30 return
31
32
33
34 d, next_operation, args, kwargs = self.queue[0]
35
36
37 try:
38 d2 = next_operation(*args, **kwargs)
39 except:
40 d2 = defer.fail()
41
42
43
44 def _work_done(res):
45 log.msg("Completed a piece of work")
46 self.queue.pop(0)
47 if self.queue:
48 log.msg("Preparing next piece of work")
49 reactor.callLater(0, self._process)
50 return res
51 d2.addBoth(_work_done)
52
53
54 d2.chainDeferred(d)
55
56 - def execute(self, cb, *args, **kwargs):
57 kickstart_processing = not self.queue
58 d = defer.Deferred()
59 self.queue.append((d, cb, args, kwargs))
60 if kickstart_processing:
61 self._process()
62 return d
63
65 return self.execute(threads.deferToThread, cb, *args, **kwargs)
66
67
68
69 queue = WorkQueue()
70
71
72 -class Domain(object):
73
74 """
75 I am a wrapper around a libvirt Domain object
76 """
77
78 - def __init__(self, connection, domain):
79 self.connection = connection
80 self.domain = domain
81
84
87
90
91
93
94 """
95 I am a wrapper around a libvirt Connection object.
96 """
97
99 self.uri = uri
100 self.connection = libvirt.open(uri)
101
107 d.addCallback(_)
108 return d
109
111 """ I take libvirt XML and start a new VM """
112 d = queue.executeInThread(self.connection.createXML, xml, 0)
113 def _(res):
114 return Domain(self, res)
115 d.addCallback(_)
116 return d
117
118
120
121 - def __init__(self, name, password, connection, hd_image, base_image = None, xml=None, max_builds=None, notify_on_missing=[],
122 missing_timeout=60*20, build_wait_timeout=60*10, properties={}, locks=None):
123 AbstractLatentBuildSlave.__init__(self, name, password, max_builds, notify_on_missing,
124 missing_timeout, build_wait_timeout, properties, locks)
125 self.name = name
126 self.connection = connection
127 self.image = hd_image
128 self.base_image = base_image
129 self.xml = xml
130
131 self.insubstantiate_after_build = True
132 self.cheap_copy = True
133 self.graceful_shutdown = False
134
135 self.domain = None
136
138 """
139 I am a private method for creating (possibly cheap) copies of a
140 base_image for start_instance to boot.
141 """
142 if not self.base_image:
143 return defer.succeed(True)
144
145 if self.cheap_copy:
146 clone_cmd = "qemu-img"
147 clone_args = "create -b %(base)s -f qcow2 %(image)s"
148 else:
149 clone_cmd = "cp"
150 clone_args = "%(base)s %(image)s"
151
152 clone_args = clone_args % {
153 "base": self.base_image,
154 "image": self.image,
155 }
156
157 log.msg("Cloning base image: %s %s'" % (clone_cmd, clone_args))
158
159 def _log_result(res):
160 log.msg("Cloning exit code was: %d" % res)
161 return res
162
163 d = utils.getProcessValue(clone_cmd, clone_args.split())
164 d.addBoth(_log_result)
165 return d
166
168 """
169 I start a new instance of a VM.
170
171 If a base_image is specified, I will make a clone of that otherwise i will
172 use image directly.
173
174 If i'm not given libvirt domain definition XML, I will look for my name
175 in the list of defined virtual machines and start that.
176 """
177 if self.domain is not None:
178 raise ValueError('domain active')
179
180 d = self._prepare_base_image()
181
182 def _start(res):
183 if self.xml:
184 return self.connection.create(self.xml)
185 d = self.connection.lookupByName(self.name)
186 def _really_start(res):
187 return res.create()
188 d.addCallback(_really_start)
189 return d
190 d.addCallback(_start)
191
192 def _started(res):
193 self.domain = res
194 return True
195 d.addCallback(_started)
196
197 def _start_failed(failure):
198 log.msg("Cannot start a VM (%s), failing gracefully and triggering a new build check" % self.name)
199 log.err(failure)
200 self.domain = None
201 return False
202 d.addErrback(_start_failed)
203
204 return d
205
207 """
208 I attempt to stop a running VM.
209 I make sure any connection to the slave is removed.
210 If the VM was using a cloned image, I remove the clone
211 When everything is tidied up, I ask that bbot looks for work to do
212 """
213 log.msg("Attempting to stop '%s'" % self.name)
214 if self.domain is None:
215 log.msg("I don't think that domain is evening running, aborting")
216 return defer.succeed(None)
217
218 domain = self.domain
219 self.domain = None
220
221 if self.graceful_shutdown and not fast:
222 log.msg("Graceful shutdown chosen for %s" % self.name)
223 d = domain.shutdown()
224 else:
225 d = domain.destroy()
226
227 def _disconnect(res):
228 log.msg("VM destroyed (%s): Forcing its connection closed." % self.name)
229 return AbstractBuildSlave.disconnect(self)
230 d.addCallback(_disconnect)
231
232 def _disconnected(res):
233 log.msg("We forced disconnection (%s), cleaning up and triggering new build" % self.name)
234 if self.base_image:
235 os.remove(self.image)
236 self.botmaster.triggerNewBuildCheck()
237 return res
238 d.addBoth(_disconnected)
239
240 return d
241
243 """
244 I insubstantiate a slave after it has done a build, if that is
245 desired behaviour.
246 """
247 AbstractLatentBuildSlave.buildFinished(self, *args, **kwargs)
248 if self.insubstantiate_after_build:
249 log.msg("Got buildFinished notification - attempting to insubstantiate")
250 self.insubstantiate()
251