Ticket #56: triggerable.patch

File triggerable.patch, 13.9 KB (added by dustin, 12 years ago)
  • buildbot/scheduler.py

    Sun Jan 13 21:48:39 EST 2008  dustin@zmanda.com
      * triggerable.patch
    diff -rN -u old-trunk/buildbot/scheduler.py new-trunk/buildbot/scheduler.py
    old new  
    681681        from buildbot.status.client import makeRemote
    682682        return makeRemote(bs.status)
    683683
     684class Triggerable(BaseUpstreamScheduler):
     685    """
     686    This scheduler doesn't do anything until it is triggered by
     687    a Trigger step in a factory.
     688    """
     689
     690    def __init__(self, name, builderNames):
     691        BaseUpstreamScheduler.__init__(self, name)
     692        self.builderNames = builderNames
     693
     694    def listBuilderNames(self):
     695        return self.builderNames
     696
     697    def getPendingBuildTimes(self):
     698        return []
     699
     700    def trigger(self, ss):
     701        """
     702        Trigger this scheduler.  Returns a deferred that will fire when the buildset
     703        is finished.
     704        """
     705        bs = buildset.BuildSet(self.builderNames, ss)
     706        d = bs.waitUntilFinished()
     707        self.submit(bs)
     708        return d
  • buildbot/steps/trigger.py

    diff -rN -u old-trunk/buildbot/steps/trigger.py new-trunk/buildbot/steps/trigger.py
    old new  
     1from buildbot.process.buildstep import LoggingBuildStep, SUCCESS, FAILURE, EXCEPTION
     2from buildbot.steps.shell import WithProperties
     3from buildbot.sourcestamp import SourceStamp
     4from buildbot.scheduler import Triggerable
     5from twisted.internet import defer
     6from twisted.python import log
     7import os
     8
     9class Trigger(LoggingBuildStep):
     10    """
     11    I trigger a Triggerable.  It's fun.
     12    """
     13    name = "trigger"
     14
     15    flunkOnFailure = True
     16
     17    def __init__(self,
     18        schedulers=[],
     19        updateSourceStamp=False,
     20        waitForFinish=False,
     21        **kwargs):
     22        """
     23        Trigger the given schedulers when this step is executed.
     24
     25        @var schedulers: list of schedulers' names that should be triggered.  Schedulers
     26        can be specified using WithProperties, if desired.
     27
     28        @var updateSourceStamp: should I update the source stamp to
     29        an absolute SourceStamp before triggering a new build?
     30
     31        @var waitForFinish: should I wait for all of the triggered schedulers to finish
     32        their builds?
     33        """
     34        assert schedulers, "You must specify a scheduler to trigger"
     35        self.schedulers = schedulers
     36        self.updateSourceStamp = updateSourceStamp
     37        self.waitForFinish = waitForFinish
     38        self.running = False
     39        LoggingBuildStep.__init__(self, **kwargs)
     40        self.addFactoryArguments(schedulers=schedulers,
     41                                 updateSourceStamp=updateSourceStamp,
     42                                 waitForFinish=waitForFinish)
     43
     44    def interrupt(self, reason):
     45        if self.running:
     46            self.step_status.setColor("red")
     47            self.step_status.setText(["interrupted"])
     48
     49    def start(self):
     50        self.running = True
     51        ss = self.build.getSourceStamp()
     52        if self.updateSourceStamp:
     53            ss = ss.getAbsoluteSourceStamp(self.build.getProperty('got_revision'))
     54        # (is there an easier way to find the BuildMaster?)
     55        all_schedulers = self.build.builder.botmaster.parent.allSchedulers()
     56        all_schedulers = dict([(sch.name, sch) for sch in all_schedulers])
     57        unknown_schedulers = []
     58        triggered_schedulers = []
     59
     60        dl = []
     61        for scheduler in self.schedulers:
     62            if isinstance(scheduler, WithProperties):
     63                scheduler = scheduler.render(self.build)
     64            if all_schedulers.has_key(scheduler):
     65                sch = all_schedulers[scheduler]
     66                if isinstance(sch, Triggerable):
     67                    dl.append(sch.trigger(ss))
     68                    triggered_schedulers.append(scheduler)
     69                else:
     70                    unknown_schedulers.append(scheduler)
     71            else:
     72                unknown_schedulers.append(scheduler)
     73
     74        if unknown_schedulers:
     75            self.step_status.setColor("red")
     76            self.step_status.setText(['no scheduler:'] + unknown_schedulers)
     77            rc = FAILURE
     78        else:
     79            rc = SUCCESS
     80            self.step_status.setText(['triggered'] + triggered_schedulers)
     81            if self.waitForFinish:
     82                self.step_status.setColor("yellow")
     83            else:
     84                self.step_status.setColor("green")
     85
     86        if self.waitForFinish:
     87            d = defer.DeferredList(dl, consumeErrors=1)
     88        else:
     89            d = defer.succeed([])
     90
     91        def cb(rclist):
     92            rc = SUCCESS
     93            for was_cb, buildsetstatus in rclist:
     94                # TODO: make this algo more configurable
     95                if not was_cb:
     96                    rc = EXCEPTION
     97                    break
     98                if buildsetstatus.getResults() == FAILURE:
     99                    rc = FAILURE
     100            return self.finished(rc)
     101
     102        def eb(why):
     103            return self.finished(FAILURE)
     104
     105        d.addCallbacks(cb, eb)
  • buildbot/test/test_run.py

    diff -rN -u old-trunk/buildbot/test/test_run.py new-trunk/buildbot/test/test_run.py
    old new  
    628628        d = self.master.loadConfig(config_4_newbuilder)
    629629        return d
    630630
     631class Triggers(RunMixin, TestFlagMixin, unittest.TestCase):
     632    config_trigger = config_base + """
     633from buildbot.scheduler import Triggerable, Scheduler
     634from buildbot.steps.trigger import Trigger
     635from buildbot.steps.dummy import Dummy
     636from buildbot.test.runutils import SetTestFlagStep
     637c['schedulers'] = [
     638    Scheduler('triggerer', None, 0.1, ['triggerer']),
     639    Triggerable('triggeree', ['triggeree'])
     640]
     641triggerer = factory.BuildFactory([
     642    s(SetTestFlagStep, flagname='triggerer_started'),
     643    s(Trigger, flunkOnFailure=True, @ARGS@),
     644    s(SetTestFlagStep, flagname='triggerer_finished'),
     645    ])
     646triggeree = factory.BuildFactory([
     647    s(SetTestFlagStep, flagname='triggeree_started'),
     648    s(@DUMMYCLASS@),
     649    s(SetTestFlagStep, flagname='triggeree_finished'),
     650    ])
     651c['builders'] = [{'name': 'triggerer', 'slavename': 'bot1',
     652                  'builddir': 'triggerer', 'factory': triggerer},
     653                 {'name': 'triggeree', 'slavename': 'bot1',
     654                  'builddir': 'triggeree', 'factory': triggeree}]
     655"""
     656
     657    def mkConfig(self, args, dummyclass="Dummy"):
     658        return self.config_trigger.replace("@ARGS@", args).replace("@DUMMYCLASS@", dummyclass)
     659
     660    def setupTest(self, args, dummyclass, checkFn):
     661        self.clearFlags()
     662        m = self.master
     663        m.loadConfig(self.mkConfig(args, dummyclass))
     664        m.readConfig = True
     665        m.startService()
     666
     667        c = changes.Change("bob", ["Makefile", "foo/bar.c"], "changed stuff")
     668        m.change_svc.addChange(c)
     669
     670        d = self.connectSlave(builders=['triggerer', 'triggeree'])
     671        d.addCallback(self.startTimer, 0.5, checkFn)
     672        return d
     673
     674    def startTimer(self, res, time, next_fn):
     675        d = defer.Deferred()
     676        reactor.callLater(time, d.callback, None)
     677        d.addCallback(next_fn)
     678        return d
     679
     680    def testTriggerBuild(self):
     681        return self.setupTest("schedulers=['triggeree']",
     682                "Dummy",
     683                self._checkTriggerBuild)
     684
     685    def _checkTriggerBuild(self, res):
     686        self.failIfFlagNotSet('triggerer_started')
     687        self.failIfFlagNotSet('triggeree_started')
     688        self.failIfFlagSet('triggeree_finished')
     689        self.failIfFlagNotSet('triggerer_finished')
     690
     691    def testTriggerBuildWait(self):
     692        return self.setupTest("schedulers=['triggeree'], waitForFinish=1",
     693                "Dummy",
     694                self._checkTriggerBuildWait)
     695
     696    def _checkTriggerBuildWait(self, res):
     697        self.failIfFlagNotSet('triggerer_started')
     698        self.failIfFlagNotSet('triggeree_started')
     699        self.failIfFlagSet('triggeree_finished')
     700        self.failIfFlagSet('triggerer_finished')
     701
    631702# TODO: test everything, from Change submission to Scheduler to Build to
    632703# Status. Use all the status types. Specifically I want to catch recurrences
    633704# of the bug where I forgot to make Waterfall inherit from StatusReceiver
  • docs/buildbot.texinfo

    diff -rN -u old-trunk/docs/buildbot.texinfo new-trunk/docs/buildbot.texinfo
    old new  
    21862186scheduler is triggered. The next section (@pxref{Build Dependencies})
    21872187describes this scheduler in more detail.
    21882188
     2189@item Triggerable
     2190This scheduler does nothing until it is triggered by a Trigger
     2191step in another build.  This facilitates a more general form of
     2192build dependencies, as described in the next section (@pxref{Build
     2193Dependencies}).
     2194
    21892195@item Periodic
    21902196This simple scheduler just triggers a build every N seconds.
    21912197
     
    22122218@cindex Dependent
    22132219@cindex Dependencies
    22142220@slindex buildbot.scheduler.Dependent
     2221@slindex buildbot.scheduler.Triggerable
    22152222
    22162223It is common to wind up with one kind of build which should only be
    22172224performed if the same source code was successfully handled by some
     
    22542261@code{Scheduler} @emph{instance}, not a name. This makes it impossible
    22552262to create circular dependencies in the config file.
    22562263
     2264A more general way to coordinate builds is by ``triggering'' schedulers
     2265from builds.  The Triggerable waits to be triggered by a
     2266Trigger step in another build.  That step can optionally
     2267wait for the scheduler's builds to complete.  This provides two
     2268advantages over Dependent schedulers.  First, the same scheduler
     2269can be triggered from multiple builds.  Second, the ability to wait
     2270for a Triggerable's builds to complete provides a form of
     2271"subroutine call", where one or more builds can "call" a scheduler
     2272to perform some work for them,  perhaps on other buildslaves.
     2273
     2274@example
     2275from buildbot import scheduler
     2276from buildbot.steps import trigger
     2277checkin = scheduler.Scheduler("checkin", None, 5*60,
     2278                            ["checkin"])
     2279nightly = scheduler.Scheduler("nightly", ...
     2280                            ["nightly"])
     2281mktarball = scheduler.Triggerable("mktarball",
     2282                              ["mktarball"])
     2283build = scheduler.Triggerable("build-all-platforms",
     2284                              ["build-all-platforms"])
     2285test = scheduler.Triggerable("distributed-test",
     2286                              ["distributed-test"])
     2287package = scheduler.Triggerable("package-all-platforms",
     2288                              ["package-all-platforms"])
     2289c['schedulers'] = [checkin, nightly, build, test, package]
     2290
     2291checkin_factory = factory.BuildFactory()
     2292f.addStep(trigger.TriggerStep('mktarball',
     2293    schedulers=['mktarball'],
     2294    waitForFinish=1)
     2295f.addStep(trigger.TriggerStep('build',
     2296    schedulers=['build-all-platforms'],
     2297    waitForFinish=1)
     2298f.addStep(trigger.TriggerStep('test',
     2299    schedulers=['distributed-test'],
     2300    waitForFinish=1)
     2301
     2302nightly_factory = factory.BuildFactory()
     2303f.addStep(trigger.TriggerStep('mktarball',
     2304    schedulers=['mktarball'],
     2305    waitForFinish=1)
     2306f.addStep(trigger.TriggerStep('build',
     2307    schedulers=['build-all-platforms'],
     2308    waitForFinish=1)
     2309f.addStep(trigger.TriggerStep('package',
     2310    schedulers=['package-all-platforms'],
     2311    waitForFinish=1)
     2312@end example
    22572313
    22582314@node Setting the slaveport, Buildslave Specifiers, Listing Change Sources and Schedulers, Configuration
    22592315@section Setting the slaveport
     
    36603716* Simple ShellCommand Subclasses:: 
    36613717* Python BuildSteps::           
    36623718* Transferring Files::         
     3719* Triggering Schedulers::
    36633720* Writing New BuildSteps::     
    36643721@end menu
    36653722
     
    45964653
    45974654@end table
    45984655
    4599 @node Python BuildSteps, Transferring Files, Simple ShellCommand Subclasses, Build Steps
     4656@node Python BuildSteps, Triggering Schedulers, Simple ShellCommand Subclasses, Build Steps
    46004657@subsection Python BuildSteps
    46014658
    46024659Here are some BuildSteps that are specifcally useful for projects
     
    46724729@end example
    46734730
    46744731
    4675 @node Transferring Files, Writing New BuildSteps, Python BuildSteps, Build Steps
     4732@node Transferring Files, Triggering Schedulers, Python BuildSteps, Build Steps
    46764733@subsection Transferring Files
    46774734
    46784735@cindex File Transfer
     
    47614818creation time (@pxref{Buildslave Options}).
    47624819
    47634820
    4764 @node Writing New BuildSteps,  , Transferring Files, Build Steps
     4821@node Triggering Schedulers, Writing New BuildSteps, Transferring Files, Build Steps
     4822@subsection Triggering Schedulers
     4823
     4824The counterpart to the Triggerable described in section
     4825@pxref{Build Dependencies} is the Trigger BuildStep.
     4826
     4827@example
     4828from buildbot.steps.trigger import Trigger
     4829f.addStep(Trigger,
     4830          schedulers=['build-prep'],
     4831          waitForFinish=1,
     4832          updateSourceStamp=1)
     4833@end example
     4834
     4835The @code{schedulers=} argument lists the Triggerables
     4836that should be triggered when this step is executed.  Note that
     4837it is possible, but not advisable, to create a cycle where a build
     4838continually triggers itself, because the schedulers are specified
     4839by name.
     4840
     4841If @code{waitForFinish} is true, then the step will not finish until
     4842all of the builds from the triggered schedulers have finished.  If this
     4843argument is not given, then the buildstep succeeds immediately after
     4844triggering the schedulers.
     4845
     4846If @code{updateSourceStamp} is true, then step updates the SourceStamp
     4847given to the Triggerables to include @code{got_revision}
     4848(the revision actually used in this build) as @code{revision} (the
     4849revision to use in the triggered builds).  This is useful to ensure
     4850that all of the builds use exactly the same SourceStamp, even if
     4851other Changes have occurred while the build was running.
     4852
     4853@node Writing New BuildSteps,  , Triggering Schedulers, Build Steps
    47654854@subsection Writing New BuildSteps
    47664855
    47674856While it is a good idea to keep your build process self-contained in