Package buildbot :: Module libvirtbuildslave
[frames] | no frames]

Source Code for Module buildbot.libvirtbuildslave

  1  # Copyright 2010 Isotoma Limited 
  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   
12 -class WorkQueue(object):
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
21 - def __init__(self):
22 self.queue = []
23
24 - def _process(self):
25 log.msg("Looking to start a piece of work now...") 26 27 # Is there anything to do? 28 if not self.queue: 29 log.msg("_process called when there is no work") 30 return 31 32 # Peek at the top of the stack - get a function to call and 33 # a deferred to fire when its all over 34 d, next_operation, args, kwargs = self.queue[0] 35 36 # Start doing some work - expects a deferred 37 try: 38 d2 = next_operation(*args, **kwargs) 39 except: 40 d2 = defer.fail() 41 42 # Whenever a piece of work is done, whether it worked or not 43 # call this to schedule the next piece of work 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 # When the work is done, trigger d 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
64 - def executeInThread(self, cb, *args, **kwargs):
65 return self.execute(threads.deferToThread, cb, *args, **kwargs)
66 67 68 # A module is effectively a singleton class, so this is OK 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
82 - def create(self):
83 return queue.executeInThread(self.domain.create)
84
85 - def shutdown(self):
86 return queue.executeInThread(self.domain.shutdown)
87
88 - def destroy(self):
89 return queue.executeInThread(self.domain.destroy)
90 91
92 -class Connection(object):
93 94 """ 95 I am a wrapper around a libvirt Connection object. 96 """ 97
98 - def __init__(self, uri):
99 self.uri = uri 100 self.connection = libvirt.open(uri)
101
102 - def lookupByName(self, name):
103 """ I lookup an existing prefined domain """ 104 d = queue.executeInThread(self.connection.lookupByName, name) 105 def _(res): 106 return Domain(self, res)
107 d.addCallback(_) 108 return d
109
110 - def create(self, xml):
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
119 -class LibVirtSlave(AbstractLatentBuildSlave):
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
137 - def _prepare_base_image(self):
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
167 - def start_instance(self):
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
206 - def stop_instance(self, fast=False):
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
242 - def buildFinished(self, *args, **kwargs):
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