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 Scheduler 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 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 in 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 is
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.allChanges = []
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.allChanges.append(change)
168 self.nextBuildTime = max(self.nextBuildTime,
169 change.when + self.treeStableTimer)
170 self.setTimer(self.nextBuildTime)
171
173 log.msg("%s: change is not important, adding %s" % (self, change))
174 self.allChanges.append(change)
175
185
190
204
208
209
211 """This Scheduler will handle changes on a variety of branches. It will
212 accumulate Changes for each branch separately. It works by creating a
213 separate Scheduler for each new branch it sees."""
214
215 schedulerFactory = Scheduler
216 fileIsImportant = None
217
218 compare_attrs = ('name', 'branches', 'treeStableTimer', 'builderNames',
219 'fileIsImportant', 'properties', 'categories')
220
221 - def __init__(self, name, branches, treeStableTimer, builderNames,
222 fileIsImportant=None, properties={}, categories=None):
223 """
224 @param name: the name of this Scheduler
225 @param branches: The branch names that the Scheduler should pay
226 attention to. Any Change that is not in one of these
227 branches will be ignored. It can be set to None to
228 accept changes from any branch. Don't use [] (an
229 empty list), because that means we don't pay
230 attention to *any* branches, so we'll never build
231 anything.
232 @param treeStableTimer: the duration, in seconds, for which the tree
233 must remain unchanged before a build is
234 triggered. This is intended to avoid builds
235 of partially-committed fixes.
236 @param builderNames: a list of Builder names. When this Scheduler
237 decides to start a set of builds, they will be
238 run on the Builders named by this list.
239
240 @param fileIsImportant: A callable which takes one argument (a Change
241 instance) and returns True if the change is
242 worth building, and False if it is not.
243 Unimportant Changes are accumulated until the
244 build is triggered by an important change.
245 The default value of None means that all
246 Changes are important.
247
248 @param properties: properties to apply to all builds started from this
249 scheduler
250 @param categories: A list of categories of changes to accept
251 """
252
253 BaseUpstreamScheduler.__init__(self, name, properties)
254 self.treeStableTimer = treeStableTimer
255 for b in builderNames:
256 assert isinstance(b, str)
257 self.builderNames = builderNames
258 self.branches = branches
259 if self.branches == []:
260 log.msg("AnyBranchScheduler %s: branches=[], so we will ignore "
261 "all branches, and never trigger any builds. Please set "
262 "branches=None to mean 'all branches'" % self)
263
264
265
266
267 if fileIsImportant:
268 assert callable(fileIsImportant)
269 self.fileIsImportant = fileIsImportant
270 self.schedulers = {}
271 self.categories = categories
272
274 return "<AnyBranchScheduler '%s'>" % self.name
275
277 return self.builderNames
278
280 bts = []
281 for s in self.schedulers.values():
282 if s.nextBuildTime is not None:
283 bts.append(s.nextBuildTime)
284 return bts
285
290
316
317
319 """This scheduler runs some set of 'downstream' builds when the
320 'upstream' scheduler has completed successfully."""
321 implements(interfaces.IDownstreamScheduler)
322
323 compare_attrs = ('name', 'upstream', 'builderNames', 'properties')
324
325 - def __init__(self, name, upstream, builderNames, properties={}):
331
333 return self.builderNames
334
338
343
349
354
356
357 upstream = None
358 for s in self.parent.allSchedulers():
359 if s.name == self.upstream_name and interfaces.IUpstreamScheduler.providedBy(s):
360 upstream = s
361 if not upstream:
362 log.msg("ERROR: Couldn't find upstream scheduler of name <%s>" %
363 self.upstream_name)
364 return upstream
365
383
385 """Instead of watching for Changes, this Scheduler can just start a build
386 at fixed intervals. The C{periodicBuildTimer} parameter sets the number
387 of seconds to wait between such periodic builds. The first build will be
388 run immediately."""
389
390
391
392
393 compare_attrs = ('name', 'builderNames', 'periodicBuildTimer', 'branch', 'properties')
394
395 - def __init__(self, name, builderNames, periodicBuildTimer,
396 branch=None, properties={}):
406
408 return self.builderNames
409
414
421
422
423
424 -class Nightly(BaseUpstreamScheduler):
425 """Imitate 'cron' scheduling. This can be used to schedule a nightly
426 build, or one which runs are certain times of the day, week, or month.
427
428 Pass some subset of minute, hour, dayOfMonth, month, and dayOfWeek; each
429 may be a single number or a list of valid values. The builds will be
430 triggered whenever the current time matches these values. Wildcards are
431 represented by a '*' string. All fields default to a wildcard except
432 'minute', so with no fields this defaults to a build every hour, on the
433 hour.
434
435 For example, the following master.cfg clause will cause a build to be
436 started every night at 3:00am::
437
438 s = Nightly('nightly', ['builder1', 'builder2'], hour=3, minute=0)
439 c['schedules'].append(s)
440
441 This scheduler will perform a build each monday morning at 6:23am and
442 again at 8:23am::
443
444 s = Nightly('BeforeWork', ['builder1'],
445 dayOfWeek=0, hour=[6,8], minute=23)
446
447 The following runs a build every two hours::
448
449 s = Nightly('every2hours', ['builder1'], hour=range(0, 24, 2))
450
451 And this one will run only on December 24th::
452
453 s = Nightly('SleighPreflightCheck', ['flying_circuits', 'radar'],
454 month=12, dayOfMonth=24, hour=12, minute=0)
455
456 For dayOfWeek and dayOfMonth, builds are triggered if the date matches
457 either of them. All time values are compared against the tuple returned
458 by time.localtime(), so month and dayOfMonth numbers start at 1, not
459 zero. dayOfWeek=0 is Monday, dayOfWeek=6 is Sunday.
460
461 onlyIfChanged functionality
462 s = Nightly('nightly', ['builder1', 'builder2'],
463 hour=3, minute=0, onlyIfChanged=True)
464 When the flag is True (False by default), the build is trigged if
465 the date matches and if the branch has changed
466
467 fileIsImportant parameter is implemented as defined in class Scheduler
468 """
469
470 compare_attrs = ('name', 'builderNames',
471 'minute', 'hour', 'dayOfMonth', 'month',
472 'dayOfWeek', 'branch', 'onlyIfChanged',
473 'fileIsImportant', 'properties')
474
475 - def __init__(self, name, builderNames, minute=0, hour='*',
476 dayOfMonth='*', month='*', dayOfWeek='*',
477 branch=None, fileIsImportant=None, onlyIfChanged=False, properties={}):
501
502 - def addTime(self, timetuple, secs):
503 return time.localtime(time.mktime(timetuple)+secs)
505 for v in values:
506 if v >= value: return v
507 return default
508
513
517
521
523 def check(ourvalue, value):
524 if ourvalue == '*': return True
525 if isinstance(ourvalue, int): return value == ourvalue
526 return (value in ourvalue)
527
528 if not check(self.minute, timetuple[4]):
529
530 return False
531
532 if not check(self.hour, timetuple[3]):
533
534 return False
535
536 if not check(self.month, timetuple[1]):
537
538 return False
539
540 if self.dayOfMonth != '*' and self.dayOfWeek != '*':
541
542
543
544 if not (check(self.dayOfMonth, timetuple[2]) or
545 check(self.dayOfWeek, timetuple[6])):
546
547 return False
548 else:
549 if not check(self.dayOfMonth, timetuple[2]):
550
551 return False
552
553 if not check(self.dayOfWeek, timetuple[6]):
554
555 return False
556
557 return True
558
561
563 dateTime = time.localtime(now)
564
565
566 dateTime = self.addTime(dateTime, 60-dateTime[5])
567
568
569
570
571 yearLimit = dateTime[0]+2
572 while not self.isRunTime(dateTime):
573 dateTime = self.addTime(dateTime, 60)
574
575 assert dateTime[0] < yearLimit, 'Something is wrong with this code'
576 return time.mktime(dateTime)
577
579 return self.builderNames
580
582
583
584 if self.nextRunTime is None: return []
585 return [self.nextRunTime]
586
613
628
630 log.msg("Nightly Scheduler <%s>: change %s from %s is important, adding it" % (self.name, change.revision, change.who))
631 self.allChanges.append(change)
632 self.importantChanges.append(change)
633
635 log.msg("Nightly Scheduler <%s>: change %s from %s is not important, adding it" % (self.name, change.revision, change.who))
636 self.allChanges.append(change)
637
638
640 - def __init__(self, name, builderNames, properties={}):
643
645 return self.builderNames
646
650
654
656
657
658
659
660 if builderNames:
661 for b in builderNames:
662 if not b in self.builderNames:
663 log.msg("%s got with builder %s" % (self, b))
664 log.msg(" but that wasn't in our list: %s"
665 % (self.builderNames,))
666 return []
667 else:
668 builderNames = self.builderNames
669 return builderNames
670
673
676 self.strings = []
677 self.transport = self
678 self.error = False
679
681 self.strings.append(s)
682
685
687 compare_attrs = ( 'name', 'builderNames', 'jobdir', 'properties' )
688
689 - def __init__(self, name, builderNames, jobdir, properties={}):
694
698
727
729 md = os.path.join(self.parent.basedir, self.jobdir)
730 if runtime.platformType == "posix":
731
732
733 path = os.path.join(md, "new", filename)
734 f = open(path, "r")
735 os.rename(os.path.join(md, "new", filename),
736 os.path.join(md, "cur", filename))
737 else:
738
739
740
741 os.rename(os.path.join(md, "new", filename),
742 os.path.join(md, "cur", filename))
743 path = os.path.join(md, "cur", filename)
744 f = open(path, "r")
745
746 try:
747 builderNames, ss, bsid = self.parseJob(f)
748 except BadJobfile:
749 log.msg("%s reports a bad jobfile in %s" % (self, filename))
750 log.err()
751 return
752
753 builderNames = self.processBuilderList(builderNames)
754 if not builderNames:
755 return
756 reason = "'try' job"
757 bs = buildset.BuildSet(builderNames, ss, reason=reason,
758 bsid=bsid, properties=self.properties)
759 self.submitBuildSet(bs)
760
762 compare_attrs = ( 'name', 'builderNames', 'port', 'userpass', 'properties' )
763 implements(portal.IRealm)
764
765 - def __init__(self, name, builderNames, port, userpass, properties={}):
766 TryBase.__init__(self, name, builderNames, properties)
767 if type(port) is int:
768 port = "tcp:%d" % port
769 self.port = port
770 self.userpass = userpass
771 c = checkers.InMemoryUsernamePasswordDatabaseDontUse()
772 for user,passwd in self.userpass:
773 c.addUser(user, passwd)
774
775 p = portal.Portal(self)
776 p.registerChecker(c)
777 f = pb.PBServerFactory(p)
778 s = strports.service(port, f)
779 s.setServiceParent(self)
780
782
783 return self.services[0]._port.getHost().port
784
786 log.msg("%s got connection from user %s" % (self, avatarID))
787 assert interface == pb.IPerspective
788 p = Try_Userpass_Perspective(self, avatarID)
789 return (pb.IPerspective, p, lambda: None)
790
795
796 - def perspective_try(self, branch, revision, patch, builderNames, properties={}):
821
823 """This scheduler doesn't do anything until it is triggered by a Trigger
824 step in a factory. In general, that step will not complete until all of
825 the builds that I fire have finished.
826 """
827
828 compare_attrs = ('name', 'builderNames', 'properties')
829
830 - def __init__(self, name, builderNames, properties={}):
833
835 return self.builderNames
836
839
840 - def trigger(self, ss, set_props=None):
855