1
2
3 import time, os.path
4
5 from zope.interface import implements
6 from twisted.internet import reactor
7 from twisted.application import service, internet, strports
8 from twisted.python import log, runtime
9 from twisted.protocols import basic
10 from twisted.cred import portal, checkers
11 from twisted.spread import pb
12
13 from buildbot import interfaces, buildset, util, pbutil
14 from buildbot.status import builder
15 from buildbot.sourcestamp import SourceStamp
16 from buildbot.changes.maildir import MaildirService
17 from buildbot.process.properties import Properties
18
19
21 """
22 A Schduler creates BuildSets and submits them to the BuildMaster.
23
24 @ivar name: name of the scheduler
25
26 @ivar properties: additional properties specified in this
27 scheduler's configuration
28 @type properties: Properties object
29 """
30 implements(interfaces.IScheduler)
31
32 - def __init__(self, name, properties={}):
44
46
47 return "<Scheduler '%s' at %d>" % (self.name, id(self))
48
51
54
79
80
82 """The default Scheduler class will run a build after some period of time
83 called the C{treeStableTimer}, on a given set of Builders. It only pays
84 attention to a single branch. You you can provide a C{fileIsImportant}
85 function which will evaluate each Change to decide whether or not it
86 should trigger a new build.
87 """
88
89 fileIsImportant = None
90 compare_attrs = ('name', 'treeStableTimer', 'builderNames', 'branch',
91 'fileIsImportant', 'properties', 'categories')
92
93 - def __init__(self, name, branch, treeStableTimer, builderNames,
94 fileIsImportant=None, properties={}, categories=None):
95 """
96 @param name: the name of this Scheduler
97 @param branch: The branch name that the Scheduler should pay
98 attention to. Any Change that is not on this branch
99 will be ignored. It can be set to None to only pay
100 attention to the default branch.
101 @param treeStableTimer: the duration, in seconds, for which the tree
102 must remain unchanged before a build will be
103 triggered. This is intended to avoid builds
104 of partially-committed fixes.
105 @param builderNames: a list of Builder names. When this Scheduler
106 decides to start a set of builds, they will be
107 run on the Builders named by this list.
108
109 @param fileIsImportant: A callable which takes one argument (a Change
110 instance) and returns True if the change is
111 worth building, and False if it is not.
112 Unimportant Changes are accumulated until the
113 build is triggered by an important change.
114 The default value of None means that all
115 Changes are important.
116
117 @param properties: properties to apply to all builds started from this
118 scheduler
119 @param categories: A list of categories of changes to accept
120 """
121
122 BaseUpstreamScheduler.__init__(self, name, properties)
123 self.treeStableTimer = treeStableTimer
124 errmsg = ("The builderNames= argument to Scheduler must be a list "
125 "of Builder description names (i.e. the 'name' key of the "
126 "Builder specification dictionary)")
127 assert isinstance(builderNames, (list, tuple)), errmsg
128 for b in builderNames:
129 assert isinstance(b, str), errmsg
130 self.builderNames = builderNames
131 self.branch = branch
132 if fileIsImportant:
133 assert callable(fileIsImportant)
134 self.fileIsImportant = fileIsImportant
135
136 self.importantChanges = []
137 self.unimportantChanges = []
138 self.nextBuildTime = None
139 self.timer = None
140 self.categories = categories
141
143 return self.builderNames
144
146 if self.nextBuildTime is not None:
147 return [self.nextBuildTime]
148 return []
149
163
165 log.msg("%s: change is important, adding %s" % (self, change))
166 self.importantChanges.append(change)
167 self.nextBuildTime = max(self.nextBuildTime,
168 change.when + self.treeStableTimer)
169 self.setTimer(self.nextBuildTime)
170
172 log.msg("%s: change is not important, adding %s" % (self, change))
173 self.unimportantChanges.append(change)
174
184
189
203
207
208
210 """This Scheduler will handle changes on a variety of branches. It will
211 accumulate Changes for each branch separately. It works by creating a
212 separate Scheduler for each new branch it sees."""
213
214 schedulerFactory = Scheduler
215 fileIsImportant = None
216
217 compare_attrs = ('name', 'branches', 'treeStableTimer', 'builderNames',
218 'fileIsImportant', 'properties')
219
220 - def __init__(self, name, branches, treeStableTimer, builderNames,
221 fileIsImportant=None, properties={}):
222 """
223 @param name: the name of this Scheduler
224 @param branches: The branch names that the Scheduler should pay
225 attention to. Any Change that is not on one of these
226 branches will be ignored. It can be set to None to
227 accept changes from any branch. Don't use [] (an
228 empty list), because that means we don't pay
229 attention to *any* branches, so we'll never build
230 anything.
231 @param treeStableTimer: the duration, in seconds, for which the tree
232 must remain unchanged before a build will be
233 triggered. This is intended to avoid builds
234 of partially-committed fixes.
235 @param builderNames: a list of Builder names. When this Scheduler
236 decides to start a set of builds, they will be
237 run on the Builders named by this list.
238
239 @param fileIsImportant: A callable which takes one argument (a Change
240 instance) and returns True if the change is
241 worth building, and False if it is not.
242 Unimportant Changes are accumulated until the
243 build is triggered by an important change.
244 The default value of None means that all
245 Changes are important.
246
247 @param properties: properties to apply to all builds started from this
248 scheduler
249 """
250
251 BaseUpstreamScheduler.__init__(self, name, properties)
252 self.treeStableTimer = treeStableTimer
253 for b in builderNames:
254 assert isinstance(b, str)
255 self.builderNames = builderNames
256 self.branches = branches
257 if self.branches == []:
258 log.msg("AnyBranchScheduler %s: branches=[], so we will ignore "
259 "all branches, and never trigger any builds. Please set "
260 "branches=None to mean 'all branches'" % self)
261
262
263
264
265 if fileIsImportant:
266 assert callable(fileIsImportant)
267 self.fileIsImportant = fileIsImportant
268 self.schedulers = {}
269
271 return "<AnyBranchScheduler '%s'>" % self.name
272
274 return self.builderNames
275
277 bts = []
278 for s in self.schedulers.values():
279 if s.nextBuildTime is not None:
280 bts.append(s.nextBuildTime)
281 return bts
282
287
310
311
313 """This scheduler runs some set of 'downstream' builds when the
314 'upstream' scheduler has completed successfully."""
315 implements(interfaces.IDownstreamScheduler)
316
317 compare_attrs = ('name', 'upstream', 'builderNames', 'properties')
318
319 - def __init__(self, name, upstream, builderNames, properties={}):
325
327 return self.builderNames
328
332
337
343
348
350
351 upstream = None
352 for s in self.parent.allSchedulers():
353 if s.name == self.upstream_name and interfaces.IUpstreamScheduler.providedBy(s):
354 upstream = s
355 if not upstream:
356 log.msg("ERROR: Couldn't find upstream scheduler of name <%s>" %
357 self.upstream_name)
358 return upstream
359
377
379 """Instead of watching for Changes, this Scheduler can just start a build
380 at fixed intervals. The C{periodicBuildTimer} parameter sets the number
381 of seconds to wait between such periodic builds. The first build will be
382 run immediately."""
383
384
385
386
387 compare_attrs = ('name', 'builderNames', 'periodicBuildTimer', 'branch', 'properties')
388
389 - def __init__(self, name, builderNames, periodicBuildTimer,
390 branch=None, properties={}):
400
402 return self.builderNames
403
408
415
416
417
418 -class Nightly(BaseUpstreamScheduler):
419 """Imitate 'cron' scheduling. This can be used to schedule a nightly
420 build, or one which runs are certain times of the day, week, or month.
421
422 Pass some subset of minute, hour, dayOfMonth, month, and dayOfWeek; each
423 may be a single number or a list of valid values. The builds will be
424 triggered whenever the current time matches these values. Wildcards are
425 represented by a '*' string. All fields default to a wildcard except
426 'minute', so with no fields this defaults to a build every hour, on the
427 hour.
428
429 For example, the following master.cfg clause will cause a build to be
430 started every night at 3:00am::
431
432 s = Nightly('nightly', ['builder1', 'builder2'], hour=3, minute=0)
433 c['schedules'].append(s)
434
435 This scheduler will perform a build each monday morning at 6:23am and
436 again at 8:23am::
437
438 s = Nightly('BeforeWork', ['builder1'],
439 dayOfWeek=0, hour=[6,8], minute=23)
440
441 The following runs a build every two hours::
442
443 s = Nightly('every2hours', ['builder1'], hour=range(0, 24, 2))
444
445 And this one will run only on December 24th::
446
447 s = Nightly('SleighPreflightCheck', ['flying_circuits', 'radar'],
448 month=12, dayOfMonth=24, hour=12, minute=0)
449
450 For dayOfWeek and dayOfMonth, builds are triggered if the date matches
451 either of them. All time values are compared against the tuple returned
452 by time.localtime(), so month and dayOfMonth numbers start at 1, not
453 zero. dayOfWeek=0 is Monday, dayOfWeek=6 is Sunday.
454
455 onlyIfChanged functionality
456 s = Nightly('nightly', ['builder1', 'builder2'],
457 hour=3, minute=0, onlyIfChanged=True)
458 When the flag is True (False by default), the build is trigged if
459 the date matches and if the branch has changed
460
461 fileIsImportant parameter is implemented as defined in class Scheduler
462 """
463
464 compare_attrs = ('name', 'builderNames',
465 'minute', 'hour', 'dayOfMonth', 'month',
466 'dayOfWeek', 'branch', 'onlyIfChanged',
467 'fileIsImportant', 'properties')
468
469 - def __init__(self, name, builderNames, minute=0, hour='*',
470 dayOfMonth='*', month='*', dayOfWeek='*',
471 branch=None, fileIsImportant=None, onlyIfChanged=False, properties={}):
495
496 - def addTime(self, timetuple, secs):
497 return time.localtime(time.mktime(timetuple)+secs)
499 for v in values:
500 if v >= value: return v
501 return default
502
507
511
515
517 def check(ourvalue, value):
518 if ourvalue == '*': return True
519 if isinstance(ourvalue, int): return value == ourvalue
520 return (value in ourvalue)
521
522 if not check(self.minute, timetuple[4]):
523
524 return False
525
526 if not check(self.hour, timetuple[3]):
527
528 return False
529
530 if not check(self.month, timetuple[1]):
531
532 return False
533
534 if self.dayOfMonth != '*' and self.dayOfWeek != '*':
535
536
537
538 if not (check(self.dayOfMonth, timetuple[2]) or
539 check(self.dayOfWeek, timetuple[6])):
540
541 return False
542 else:
543 if not check(self.dayOfMonth, timetuple[2]):
544
545 return False
546
547 if not check(self.dayOfWeek, timetuple[6]):
548
549 return False
550
551 return True
552
555
557 dateTime = time.localtime(now)
558
559
560 dateTime = self.addTime(dateTime, 60-dateTime[5])
561
562
563
564
565 yearLimit = dateTime[0]+2
566 while not self.isRunTime(dateTime):
567 dateTime = self.addTime(dateTime, 60)
568
569 assert dateTime[0] < yearLimit, 'Something is wrong with this code'
570 return time.mktime(dateTime)
571
573 return self.builderNames
574
576
577
578 if self.nextRunTime is None: return []
579 return [self.nextRunTime]
580
607
622
624 log.msg("Nightly Scheduler <%s>: change %s from %s is important, adding it" % (self.name, change.revision, change.who))
625 self.importantChanges.append(change)
626
628 log.msg("Nightly Scheduler <%s>: change %s from %s is not important, adding it" % (self.name, change.revision, change.who))
629 self.unimportantChanges.append(change)
630
631
633 - def __init__(self, name, builderNames, properties={}):
636
638 return self.builderNames
639
643
647
649
650
651
652
653 if builderNames:
654 for b in builderNames:
655 if not b in self.builderNames:
656 log.msg("%s got with builder %s" % (self, b))
657 log.msg(" but that wasn't in our list: %s"
658 % (self.builderNames,))
659 return []
660 else:
661 builderNames = self.builderNames
662 return builderNames
663
666
669 self.strings = []
670 self.transport = self
671 self.error = False
672
674 self.strings.append(s)
675
678
680 compare_attrs = ( 'name', 'builderNames', 'jobdir', 'properties' )
681
682 - def __init__(self, name, builderNames, jobdir, properties={}):
687
691
720
722 md = os.path.join(self.parent.basedir, self.jobdir)
723 if runtime.platformType == "posix":
724
725
726 path = os.path.join(md, "new", filename)
727 f = open(path, "r")
728 os.rename(os.path.join(md, "new", filename),
729 os.path.join(md, "cur", filename))
730 else:
731
732
733
734 os.rename(os.path.join(md, "new", filename),
735 os.path.join(md, "cur", filename))
736 path = os.path.join(md, "cur", filename)
737 f = open(path, "r")
738
739 try:
740 builderNames, ss, bsid = self.parseJob(f)
741 except BadJobfile:
742 log.msg("%s reports a bad jobfile in %s" % (self, filename))
743 log.err()
744 return
745
746 builderNames = self.processBuilderList(builderNames)
747 if not builderNames:
748 return
749 reason = "'try' job"
750 bs = buildset.BuildSet(builderNames, ss, reason=reason,
751 bsid=bsid, properties=self.properties)
752 self.submitBuildSet(bs)
753
755 compare_attrs = ( 'name', 'builderNames', 'port', 'userpass', 'properties' )
756 implements(portal.IRealm)
757
758 - def __init__(self, name, builderNames, port, userpass, properties={}):
759 TryBase.__init__(self, name, builderNames, properties)
760 if type(port) is int:
761 port = "tcp:%d" % port
762 self.port = port
763 self.userpass = userpass
764 c = checkers.InMemoryUsernamePasswordDatabaseDontUse()
765 for user,passwd in self.userpass:
766 c.addUser(user, passwd)
767
768 p = portal.Portal(self)
769 p.registerChecker(c)
770 f = pb.PBServerFactory(p)
771 s = strports.service(port, f)
772 s.setServiceParent(self)
773
775
776 return self.services[0]._port.getHost().port
777
779 log.msg("%s got connection from user %s" % (self, avatarID))
780 assert interface == pb.IPerspective
781 p = Try_Userpass_Perspective(self, avatarID)
782 return (pb.IPerspective, p, lambda: None)
783
788
789 - def perspective_try(self, branch, revision, patch, builderNames, properties={}):
814
816 """This scheduler doesn't do anything until it is triggered by a Trigger
817 step in a factory. In general, that step will not complete until all of
818 the builds that I fire have finished.
819 """
820
821 compare_attrs = ('name', 'builderNames', 'properties')
822
823 - def __init__(self, name, builderNames, properties={}):
826
828 return self.builderNames
829
832
833 - def trigger(self, ss, set_props=None):
848