]>
git.immae.eu Git - perso/Immae/Config/Nix.git/blob - flakes/private/buildbot/common/libvirt.py
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
= None
160 def connection(self
):
161 if self
._connection
is not None:
163 if not self
._connection
.isAlive():
164 self
._connection
= None
166 self
._connection
= None
167 if self
._connection
is None:
168 self
._connection
= libvirt
.open(self
.uri
)
169 return self
._connection
171 @defer.inlineCallbacks
172 def create(self
, xml
):
173 """ I take libvirt XML and start a new VM """
174 res
= yield queue
.executeInThread(self
.connection
.createXML
, xml
, 0)
175 return self
.DomainClass(self
, res
)
177 @defer.inlineCallbacks
178 def lookup_pool(self
, name
):
179 res
= yield queue
.executeInThread(self
.connection
.storagePoolLookupByName
, name
)
180 return self
.PoolClass(self
, res
)
182 class LibVirtWorker(AbstractLatentWorker
):
184 def __init__(self
, name
, password
, connection
, master_url
, base_image
=None, **kwargs
):
185 super().__init
__(name
, password
, **kwargs
)
188 "The python module 'libvirt' is needed to use a LibVirtWorker")
190 self
.master_url
= master_url
191 self
.random_name
= random_string_generator()
192 self
.connection
= connection
193 self
.base_image
= base_image
196 self
.domain_name
= "buildbot-" + self
.workername
+ "-" + self
.random_name
198 self
.volume_name
= "buildbot-" + self
.workername
+ "-" + self
.random_name
199 self
.pool_name
= "buildbot-disks"
201 def reconfigService(self
, *args
, **kwargs
):
202 if 'build_wait_timeout' not in kwargs
:
203 kwargs
['build_wait_timeout'] = 0
204 return super().reconfigService(*args
, **kwargs
)
206 def canStartBuild(self
):
207 if self
.domain
and not self
.isConnected():
209 "Not accepting builds as existing domain but worker not connected")
212 return super().canStartBuild()
214 @defer.inlineCallbacks
215 def _prepare_image(self
):
216 log
.msg("Creating temporary image {}".format(self
.volume_name
))
217 pool
= yield self
.connection
.lookup_pool(self
.pool_name
)
220 <name>{vol_name}</name>
221 <capacity unit='G'>10</capacity>
223 <format type='qcow2'/>
231 <path>/etc/libvirtd/base-images/buildbot.qcow2</path>
232 <format type='qcow2'/>
235 """.format(vol_name
= self
.volume_name
)
236 self
.volume
= yield pool
.create_volume(vol_xml
)
238 @defer.inlineCallbacks
239 def start_instance(self
, build
):
241 I start a new instance of a VM.
243 If a base_image is specified, I will make a clone of that otherwise i will
246 If i'm not given libvirt domain definition XML, I will look for my name
247 in the list of defined virtual machines and start that.
251 <name>{domain_name}</name>
252 <memory unit="GiB">2</memory>
254 <sysinfo type='smbios'>
256 <entry>buildbot_master_url={master_url}</entry>
257 <entry>buildbot_worker_name={worker_name}</entry>
261 <type arch="x86_64">hvm</type>
262 <smbios mode='sysinfo'/>
265 <emulator>/run/current-system/sw/bin/qemu-system-x86_64</emulator>
266 <disk type="volume" device="disk">
267 <driver name='qemu' type='qcow2' />
268 <source type="volume" pool="{pool_name}" volume="{volume_name}" />
269 <backingStore type='volume'>
270 <format type='qcow2'/>
271 <source type="volume" pool="niximages" volume="buildbot.qcow2" />
273 <target dev="vda" bus="virtio"/>
275 <input type="keyboard" bus="usb"/>
276 <graphics type="vnc" port="-1" autoport="yes"/>
277 <interface type="network">
278 <source network="immae" />
282 """.format(volume_name
= self
.volume_name
, master_url
= self
.master_url
, pool_name
=
283 self
.pool_name
, domain_name
= self
.domain_name
, worker_name
= self
.workername
)
285 yield self
._prepare
_image
()
288 self
.domain
= yield self
.connection
.create(domain_xml
)
290 log
.err(failure
.Failure(),
291 ("Cannot start a VM ({}), failing gracefully and triggering"
292 "a new build check").format(self
.workername
))
296 return [self
.domain_name
]
298 def stop_instance(self
, fast
=False):
300 I attempt to stop a running VM.
301 I make sure any connection to the worker is removed.
302 If the VM was using a cloned image, I remove the clone
303 When everything is tidied up, I ask that bbot looks for work to do
306 log
.msg("Attempting to stop '{}'".format(self
.workername
))
307 if self
.domain
is None:
308 log
.msg("I don't think that domain is even running, aborting")
309 return defer
.succeed(None)
315 if self
.volume
is not None:
316 self
.volume
.destroy()