1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 import re
17
18 from zope.interface import implements
19 from twisted.internet import reactor, defer, error
20 from twisted.protocols import basic
21 from twisted.spread import pb
22 from twisted.python import log, components
23 from twisted.python.failure import Failure
24 from twisted.web.util import formatFailure
25 from twisted.python.reflect import accumulateClassList
26
27 from buildbot import interfaces, locks, util, config
28 from buildbot.status import progress
29 from buildbot.status.results import SUCCESS, WARNINGS, FAILURE, SKIPPED, \
30 EXCEPTION, RETRY, worst_status
31 from buildbot.process import metrics, properties
35
37
38
39 _commandCounter = 0
40
41 active = False
42 rc = None
43 debug = False
44
45 - def __init__(self, remote_command, args, ignore_updates=False,
46 collectStdout=False, decodeRC={0:SUCCESS}):
47 self.logs = {}
48 self.delayedLogs = {}
49 self._closeWhenFinished = {}
50 self.collectStdout = collectStdout
51 self.stdout = ''
52
53 self._startTime = None
54 self._remoteElapsed = None
55 self.remote_command = remote_command
56 self.args = args
57 self.ignore_updates = ignore_updates
58 self.decodeRC = decodeRC
59
61 return "<RemoteCommand '%s' at %d>" % (self.remote_command, id(self))
62
63 - def run(self, step, remote):
89
90 - def useLog(self, log, closeWhenFinished=False, logfileName=None):
91 assert interfaces.ILogFile.providedBy(log)
92 if not logfileName:
93 logfileName = log.getName()
94 assert logfileName not in self.logs
95 assert logfileName not in self.delayedLogs
96 self.logs[logfileName] = log
97 self._closeWhenFinished[logfileName] = closeWhenFinished
98
99 - def useLogDelayed(self, logfileName, activateCallBack, closeWhenFinished=False):
100 assert logfileName not in self.logs
101 assert logfileName not in self.delayedLogs
102 self.delayedLogs[logfileName] = (activateCallBack, closeWhenFinished)
103
105 self.updates = {}
106 self._startTime = util.now()
107
108
109
110
111
112 d = self.remote.callRemote("startCommand", self, self.commandID,
113 self.remote_command, self.args)
114 return d
115
117 self.active = False
118
119
120
121
122
123 d = defer.maybeDeferred(self.remoteComplete, failure)
124
125
126 d.addCallback(lambda r: self)
127
128
129 d.addBoth(self.deferred.callback)
130
132 log.msg("RemoteCommand.interrupt", self, why)
133 if not self.active:
134 log.msg(" but this RemoteCommand is already inactive")
135 return defer.succeed(None)
136 if not self.remote:
137 log.msg(" but our .remote went away")
138 return defer.succeed(None)
139 if isinstance(why, Failure) and why.check(error.ConnectionLost):
140 log.msg("RemoteCommand.disconnect: lost slave")
141 self.remote = None
142 self._finished(why)
143 return defer.succeed(None)
144
145
146
147
148 d = defer.maybeDeferred(self.remote.callRemote, "interruptCommand",
149 self.commandID, str(why))
150
151 d.addErrback(self._interruptFailed)
152 return d
153
155 log.msg("RemoteCommand._interruptFailed", self)
156
157
158 return None
159
161 """
162 I am called by the slave's L{buildbot.slave.bot.SlaveBuilder} so
163 I can receive updates from the running remote command.
164
165 @type updates: list of [object, int]
166 @param updates: list of updates from the remote command
167 """
168 self.buildslave.messageReceivedFromSlave()
169 max_updatenum = 0
170 for (update, num) in updates:
171
172 try:
173 if self.active and not self.ignore_updates:
174 self.remoteUpdate(update)
175 except:
176
177 self._finished(Failure())
178
179
180 if num > max_updatenum:
181 max_updatenum = num
182 return max_updatenum
183
185 """
186 Called by the slave's L{buildbot.slave.bot.SlaveBuilder} to
187 notify me the remote command has finished.
188
189 @type failure: L{twisted.python.failure.Failure} or None
190
191 @rtype: None
192 """
193 self.buildslave.messageReceivedFromSlave()
194
195
196 if self.active:
197 reactor.callLater(0, self._finished, failure)
198 return None
199
201 if 'stdio' in self.logs:
202 self.logs['stdio'].addStdout(data)
203 if self.collectStdout:
204 self.stdout += data
205
209
213
215
216 if logname in self.delayedLogs:
217 (activateCallBack, closeWhenFinished) = self.delayedLogs[logname]
218 del self.delayedLogs[logname]
219 loog = activateCallBack(self)
220 self.logs[logname] = loog
221 self._closeWhenFinished[logname] = closeWhenFinished
222
223 if logname in self.logs:
224 self.logs[logname].addStdout(data)
225 else:
226 log.msg("%s.addToLog: no such log %s" % (self, logname))
227
228 @metrics.countMethod('RemoteCommand.remoteUpdate()')
259
261 if self._startTime and self._remoteElapsed:
262 delta = (util.now() - self._startTime) - self._remoteElapsed
263 metrics.MetricTimeEvent.log("RemoteCommand.overhead", delta)
264
265 for name,loog in self.logs.items():
266 if self._closeWhenFinished[name]:
267 if maybeFailure:
268 loog.addHeader("\nremoteFailed: %s" % maybeFailure)
269 else:
270 log.msg("closing log %s" % loog)
271 loog.finish()
272 return maybeFailure
273
275 if self.rc in self.decodeRC:
276 return self.decodeRC[self.rc]
277 return FAILURE
278
281 LoggedRemoteCommand = RemoteCommand
285 implements(interfaces.ILogObserver)
286
289
293
294 - def logChunk(self, build, step, log, channel, text):
299
300
301
303 """This will be called with chunks of stdout data. Override this in
304 your observer."""
305 pass
306
308 """This will be called with chunks of stderr data. Override this in
309 your observer."""
310 pass
311
325
327 """
328 Set the maximum line length: lines longer than max_length are
329 dropped. Default is 16384 bytes. Use sys.maxint for effective
330 infinity.
331 """
332 self.stdoutParser.MAX_LENGTH = max_length
333 self.stderrParser.MAX_LENGTH = max_length
334
336 self.stdoutParser.dataReceived(data)
337
339 self.stderrParser.dataReceived(data)
340
342 """This will be called with complete stdout lines (not including the
343 delimiter). Override this in your observer."""
344 pass
345
347 """This will be called with complete lines of stderr (not including
348 the delimiter). Override this in your observer."""
349 pass
350
353 - def __init__(self, workdir, command, env=None,
354 want_stdout=1, want_stderr=1,
355 timeout=20*60, maxTime=None, logfiles={},
356 usePTY="slave-config", logEnviron=True,
357 collectStdout=False, interruptSignal=None,
358 initialStdin=None, decodeRC={0:SUCCESS}):
359
360 self.command = command
361 if env is not None:
362
363
364
365 env = env.copy()
366 args = {'workdir': workdir,
367 'env': env,
368 'want_stdout': want_stdout,
369 'want_stderr': want_stderr,
370 'logfiles': logfiles,
371 'timeout': timeout,
372 'maxTime': maxTime,
373 'usePTY': usePTY,
374 'logEnviron': logEnviron,
375 'initial_stdin': initialStdin
376 }
377 if interruptSignal is not None:
378 args['interruptSignal'] = interruptSignal
379 RemoteCommand.__init__(self, "shell", args, collectStdout=collectStdout,
380 decodeRC=decodeRC)
381
383 self.args['command'] = self.command
384 if self.remote_command == "shell":
385
386
387 if self.step.slaveVersion("shell", "old") == "old":
388 self.args['dir'] = self.args['workdir']
389 what = "command '%s' in dir '%s'" % (self.args['command'],
390 self.args['workdir'])
391 log.msg(what)
392 return RemoteCommand._start(self)
393
395 return "<RemoteShellCommand '%s'>" % repr(self.command)
396
398 """
399 This is a wrapper to record the arguments passed to as BuildStep subclass.
400 We use an instance of this class, rather than a closure mostly to make it
401 easier to test that the right factories are getting created.
402 """
403 compare_attrs = ['factory', 'args', 'kwargs' ]
404 implements(interfaces.IBuildStepFactory)
405
406 - def __init__(self, factory, *args, **kwargs):
407 self.factory = factory
408 self.args = args
409 self.kwargs = kwargs
410
412 try:
413 return self.factory(*self.args, **self.kwargs)
414 except:
415 log.msg("error while creating step, factory=%s, args=%s, kwargs=%s"
416 % (self.factory, self.args, self.kwargs))
417 raise
418
419 -class BuildStep(object, properties.PropertiesMixin):
420
421 haltOnFailure = False
422 flunkOnWarnings = False
423 flunkOnFailure = False
424 warnOnWarnings = False
425 warnOnFailure = False
426 alwaysRun = False
427 doStepIf = True
428 hideStepIf = False
429
430
431 set_runtime_properties = True
432
433
434
435
436
437
438
439
440 parms = ['name', 'locks',
441 'haltOnFailure',
442 'flunkOnWarnings',
443 'flunkOnFailure',
444 'warnOnWarnings',
445 'warnOnFailure',
446 'alwaysRun',
447 'progressMetrics',
448 'useProgress',
449 'doStepIf',
450 'hideStepIf',
451 ]
452
453 name = "generic"
454 locks = []
455 progressMetrics = ()
456 useProgress = True
457 build = None
458 step_status = None
459 progress = None
460
462 for p in self.__class__.parms:
463 if kwargs.has_key(p):
464 setattr(self, p, kwargs[p])
465 del kwargs[p]
466 if kwargs:
467 why = "%s.__init__ got unexpected keyword argument(s) %s" \
468 % (self, kwargs.keys())
469 raise TypeError(why)
470 self._pendingLogObservers = []
471
472 self._acquiringLock = None
473 self.stopped = False
474
475 - def __new__(klass, *args, **kwargs):
476 self = object.__new__(klass)
477 self._factory = _BuildStepFactory(klass, *args, **kwargs)
478 return self
479
482
485
488
491
495
498
501
509
513
544
546 self._acquiringLock = None
547 if not self.locks:
548 return defer.succeed(None)
549 if self.stopped:
550 return defer.succeed(None)
551 log.msg("acquireLocks(step %s, locks %s)" % (self, self.locks))
552 for lock, access in self.locks:
553 if not lock.isAvailable(self, access):
554 self.step_status.setWaitingForLocks(True)
555 log.msg("step %s waiting for lock %s" % (self, lock))
556 d = lock.waitUntilMaybeAvailable(self, access)
557 d.addCallback(self.acquireLocks)
558 self._acquiringLock = (lock, access, d)
559 return d
560
561 for lock, access in self.locks:
562 lock.claim(self, access)
563 self.step_status.setWaitingForLocks(False)
564 return defer.succeed(None)
565
584
585 dl = [ doStep ]
586 for renderable in renderables:
587 d = self.build.render(getattr(self, renderable))
588 d.addCallback(setRenderable, renderable)
589 dl.append(d)
590 dl = defer.gatherResults(dl)
591
592 dl.addCallback(self._startStep_3)
593 return dl
594
595 @defer.inlineCallbacks
597 doStep = doStep[0]
598 try:
599 if doStep:
600 result = yield defer.maybeDeferred(self.start)
601 if result == SKIPPED:
602 doStep = False
603 except:
604 log.msg("BuildStep.startStep exception in .start")
605 self.failed(Failure())
606
607 if not doStep:
608 self.step_status.setText(self.describe(True) + ['skipped'])
609 self.step_status.setSkipped(True)
610
611
612
613
614 reactor.callLater(0, self._finishFinished, SKIPPED)
615
617 raise NotImplementedError("your subclass must implement this method")
618
625
634
648
669
705
706
707
710
718
721
726
732
741
746
748 assert interfaces.ILogObserver.providedBy(observer)
749 observer.setStep(self)
750 self._pendingLogObservers.append((logname, observer))
751 self._connectPendingLogObservers()
752
754 if not self._pendingLogObservers:
755 return
756 if not self.step_status:
757 return
758 current_logs = {}
759 for loog in self.step_status.getLogs():
760 current_logs[loog.getName()] = loog
761 for logname, observer in self._pendingLogObservers[:]:
762 if logname in current_logs:
763 observer.setLog(current_logs[logname])
764 self._pendingLogObservers.remove((logname, observer))
765
768
773
774 @staticmethod
776 if callable(value):
777 value = value(*args, **kwargs)
778 return value
779
780 components.registerAdapter(
781 BuildStep._getStepFactory,
782 BuildStep, interfaces.IBuildStepFactory)
783 components.registerAdapter(
784 lambda step : interfaces.IProperties(step.build),
785 BuildStep, interfaces.IProperties)
789 length = 0
790
793
794 - def logChunk(self, build, step, log, channel, text):
797
799
800 progressMetrics = ('output',)
801 logfiles = {}
802
803 parms = BuildStep.parms + ['logfiles', 'lazylogfiles', 'log_eval_func']
804 cmd = None
805
806 renderables = [ 'logfiles', 'lazylogfiles' ]
807
808 - def __init__(self, logfiles={}, lazylogfiles=False, log_eval_func=None,
809 *args, **kwargs):
826
829
831 kwargs = dict()
832 kwargs['logfiles'] = self.logfiles
833 return kwargs
834
864 d.addCallback(_gotResults)
865 d.addCallbacks(self.finished, self.checkDisconnect)
866 d.addErrback(self.failed)
867
869 for logname,remotefilename in logfiles.items():
870 if self.lazylogfiles:
871
872
873
874
875
876
877 callback = lambda cmd_arg, local_logname=logname: self.addLog(local_logname)
878 cmd.useLogDelayed(logname, callback, True)
879 else:
880
881 newlog = self.addLog(logname)
882
883 cmd.useLog(newlog, True)
884
898
905
908
911
916
917 - def getText(self, cmd, results):
918 if results == SUCCESS:
919 return self.describe(True)
920 elif results == WARNINGS:
921 return self.describe(True) + ["warnings"]
922 elif results == EXCEPTION:
923 return self.describe(True) + ["exception"]
924 else:
925 return self.describe(True) + ["failed"]
926
927 - def getText2(self, cmd, results):
929
930 - def maybeGetText2(self, cmd, results):
931 if results == SUCCESS:
932
933 pass
934 elif results == WARNINGS:
935 if (self.flunkOnWarnings or self.warnOnWarnings):
936
937 return self.getText2(cmd, results)
938 else:
939 if (self.haltOnFailure or self.flunkOnFailure
940 or self.warnOnFailure):
941
942 return self.getText2(cmd, results)
943 return []
944
950
960 worst = cmd.results()
961 for err, possible_status in regexes:
962
963
964
965 if worst_status(worst, possible_status) == possible_status:
966 if isinstance(err, (basestring)):
967 err = re.compile(".*%s.*" % err, re.DOTALL)
968 for l in cmd.logs.values():
969 if err.search(l.getText()):
970 worst = possible_status
971 return worst
972
973
974 from buildbot.process.properties import WithProperties
975 _hush_pyflakes = [WithProperties]
976 del _hush_pyflakes
977