1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 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   
 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   
 37   
 39          log.msg("Looking to start a piece of work now...") 
 40   
 41           
 42          if not self.queue: 
 43              log.msg("_process called when there is no work") 
 44              return 
 45   
 46           
 47           
 48          d, next_operation, args, kwargs = self.queue[0] 
 49   
 50           
 51          try: 
 52              d2 = next_operation(*args, **kwargs) 
 53          except: 
 54              d2 = defer.fail() 
 55   
 56           
 57           
 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           
 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   
 79          return self.execute(threads.deferToThread, cb, *args, **kwargs) 
  80   
 81   
 82   
 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   
 98   
101   
 104   
105   
107   
108      """ 
109      I am a wrapper around a libvirt Connection object. 
110      """ 
111   
113          self.uri = uri 
114          self.connection = libvirt.open(uri) 
 115   
121          d.addCallback(_) 
122          return d 
 123   
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   
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   
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   
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                  return res.create() 
 202              d.addCallback(_really_start) 
203              return d 
204          d.addCallback(_start) 
205   
206          def _started(res): 
207              self.domain = 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   
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   
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