Package buildbot :: Package schedulers :: Module basic
[frames] | no frames]

Source Code for Module buildbot.schedulers.basic

  1  # This file is part of Buildbot.  Buildbot is free software: you can 
  2  # redistribute it and/or modify it under the terms of the GNU General Public 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  5  # This program is distributed in the hope that it will be useful, but WITHOUT 
  6  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 10  # You should have received a copy of the GNU General Public License along with 
 11  # this program; if not, write to the Free Software Foundation, Inc., 51 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 15   
 16  import time 
 17   
 18  from buildbot import interfaces, util 
 19  from buildbot.util import collections, NotABranch 
 20  from buildbot.sourcestamp import SourceStamp 
 21  from buildbot.status.builder import SUCCESS, WARNINGS 
 22  from buildbot.schedulers import base 
 23   
24 -class Scheduler(base.BaseScheduler, base.ClassifierMixin):
25 fileIsImportant = None 26 compare_attrs = ('name', 'treeStableTimer', 'builderNames', 27 'fileIsImportant', 'properties', 'change_filter') 28
29 - def __init__(self, name, shouldntBeSet=NotABranch, treeStableTimer=None, 30 builderNames=None, branch=NotABranch, fileIsImportant=None, 31 properties={}, categories=None, change_filter=None):
32 """ 33 @param name: the name of this Scheduler 34 @param treeStableTimer: the duration, in seconds, for which the tree 35 must remain unchanged before a build is 36 triggered. This is intended to avoid builds 37 of partially-committed fixes. If None, then 38 a separate build will be made for each 39 Change, regardless of when they arrive. 40 @param builderNames: a list of Builder names. When this Scheduler 41 decides to start a set of builds, they will be 42 run on the Builders named by this list. 43 44 @param fileIsImportant: A callable which takes one argument (a Change 45 instance) and returns True if the change is 46 worth building, and False if it is not. 47 Unimportant Changes are accumulated until the 48 build is triggered by an important change. 49 The default value of None means that all 50 Changes are important. 51 52 @param properties: properties to apply to all builds started from 53 this scheduler 54 55 @param change_filter: a buildbot.schedulers.filter.ChangeFilter instance 56 used to filter changes for this scheduler 57 58 @param branch: The branch name that the Scheduler should pay 59 attention to. Any Change that is not in this branch 60 will be ignored. It can be set to None to only pay 61 attention to the default branch. 62 @param categories: A list of categories of changes to accept 63 """ 64 assert shouldntBeSet is NotABranch, \ 65 "pass arguments to Scheduler using keyword arguments" 66 67 base.BaseScheduler.__init__(self, name, builderNames, properties) 68 self.make_filter(change_filter=change_filter, branch=branch, categories=categories) 69 self.treeStableTimer = treeStableTimer 70 self.stableAt = None 71 self.branch = branch 72 if fileIsImportant: 73 assert callable(fileIsImportant) 74 self.fileIsImportant = fileIsImportant
75
76 - def get_initial_state(self, max_changeid):
77 return {"last_processed": max_changeid}
78
79 - def run(self):
80 db = self.parent.db 81 d = db.runInteraction(self.classify_changes) 82 d.addCallback(lambda ign: db.runInteraction(self._process_changes)) 83 return d
84
85 - def _process_changes(self, t):
86 db = self.parent.db 87 res = db.scheduler_get_classified_changes(self.schedulerid, t) 88 (important, unimportant) = res 89 return self.decide_and_remove_changes(t, important, unimportant)
90
91 - def decide_and_remove_changes(self, t, important, unimportant):
92 """Look at the changes that need to be processed and decide whether 93 to queue a BuildRequest or sleep until something changes. 94 95 If I decide that a build should be performed, I will add the 96 appropriate BuildRequest to the database queue, and remove the 97 (retired) changes that went into it from the scheduler_changes table. 98 99 Returns wakeup_delay: either None, or a float indicating when this 100 scheduler wants to be woken up next. The Scheduler is responsible for 101 padding its desired wakeup time by about a second to avoid frenetic 102 must-wake-up-at-exactly-8AM behavior. The Loop may silently impose a 103 minimum delay request of a couple seconds to prevent this sort of 104 thing, but Schedulers must still add their own padding to avoid at 105 least a double wakeup. 106 """ 107 108 if not important: 109 return None 110 all_changes = important + unimportant 111 most_recent = max([c.when for c in all_changes]) 112 if self.treeStableTimer is not None: 113 now = time.time() 114 self.stableAt = most_recent + self.treeStableTimer 115 if self.stableAt > now: 116 # Wake up one second late, to avoid waking up too early and 117 # looping a lot. 118 return self.stableAt + 1.0 119 120 # ok, do a build 121 self.stableAt = None 122 self._add_build_and_remove_changes(t, important, unimportant) 123 return None
124
125 - def _add_build_and_remove_changes(self, t, important, unimportant):
126 # the changes are segregated into important and unimportant 127 # changes, but we need it ordered earliest to latest, based 128 # on change number, since the SourceStamp will be created 129 # based on the final change. 130 all_changes = sorted(important + unimportant, key=lambda c : c.number) 131 132 db = self.parent.db 133 if self.treeStableTimer is None: 134 # each *important* Change gets a separate build. Unimportant 135 # builds get ignored. 136 for c in sorted(important, key=lambda c : c.number): 137 ss = SourceStamp(changes=[c]) 138 ssid = db.get_sourcestampid(ss, t) 139 self.create_buildset(ssid, "scheduler", t) 140 else: 141 # if we had a treeStableTimer, then trigger a build for the 142 # whole pile - important or not. There's at least one important 143 # change in the list, or we wouldn't have gotten here. 144 ss = SourceStamp(changes=all_changes) 145 ssid = db.get_sourcestampid(ss, t) 146 self.create_buildset(ssid, "scheduler", t) 147 148 # and finally retire all of the changes from scheduler_changes, regardless 149 # of importance level 150 changeids = [c.number for c in all_changes] 151 db.scheduler_retire_changes(self.schedulerid, changeids, t)
152 153 # the waterfall needs to know the next time we plan to trigger a build
154 - def getPendingBuildTimes(self):
155 if self.stableAt and self.stableAt > util.now(): 156 return [ self.stableAt ] 157 return []
158
159 -class AnyBranchScheduler(Scheduler):
160 compare_attrs = ('name', 'treeStableTimer', 'builderNames', 161 'fileIsImportant', 'properties', 'change_filter')
162 - def __init__(self, name, treeStableTimer, builderNames, 163 fileIsImportant=None, properties={}, categories=None, 164 branches=NotABranch, change_filter=None):
165 """ 166 Same parameters as the scheduler, but without 'branch', and adding: 167 168 @param branches: (deprecated) 169 """ 170 171 Scheduler.__init__(self, name, builderNames=builderNames, properties=properties, 172 categories=categories, treeStableTimer=treeStableTimer, 173 fileIsImportant=fileIsImportant, change_filter=change_filter, 174 # this is the interesting part: 175 branch=branches)
176
177 - def _process_changes(self, t):
178 db = self.parent.db 179 res = db.scheduler_get_classified_changes(self.schedulerid, t) 180 (important, unimportant) = res 181 def _twolists(): return [], [] # important, unimportant 182 branch_changes = collections.defaultdict(_twolists) 183 for c in important: 184 branch_changes[c.branch][0].append(c) 185 for c in unimportant: 186 branch_changes[c.branch][1].append(c) 187 delays = [] 188 for branch in branch_changes: 189 (b_important, b_unimportant) = branch_changes[branch] 190 delay = self.decide_and_remove_changes(t, b_important, 191 b_unimportant) 192 if delay is not None: 193 delays.append(delay) 194 if delays: 195 return min(delays) 196 return None
197
198 -class Dependent(base.BaseScheduler):
199 # register with our upstream, so they'll tell us when they submit a 200 # buildset 201 compare_attrs = ('name', 'upstream_name', 'builderNames', 'properties') 202
203 - def __init__(self, name, upstream, builderNames, properties={}):
204 assert interfaces.IScheduler.providedBy(upstream) 205 base.BaseScheduler.__init__(self, name, builderNames, properties) 206 # by setting self.upstream_name, our buildSetSubmitted() method will 207 # be called whenever that upstream Scheduler adds a buildset to the 208 # DB. 209 self.upstream_name = upstream.name
210
211 - def buildSetSubmitted(self, bsid, t):
212 db = self.parent.db 213 db.scheduler_subscribe_to_buildset(self.schedulerid, bsid, t)
214
215 - def run(self):
216 d = self.parent.db.runInteraction(self._run) 217 return d
218 - def _run(self, t):
219 db = self.parent.db 220 res = db.scheduler_get_subscribed_buildsets(self.schedulerid, t) 221 # this returns bsid,ssid,results for all of our active subscriptions. 222 # We ignore the ones that aren't complete yet. This leaves the 223 # subscription in place until the buildset is complete. 224 for (bsid,ssid,complete,results) in res: 225 if complete: 226 if results in (SUCCESS, WARNINGS): 227 self.create_buildset(ssid, "downstream", t) 228 db.scheduler_unsubscribe_buildset(self.schedulerid, bsid, t) 229 return None
230 231 # Dependent/Triggerable schedulers will make a BuildSet with linked 232 # BuildRequests. The rest (which don't generally care when the set 233 # finishes) will just make the BuildRequests. 234 235 # runInteraction() should give us the all-or-nothing transaction 236 # semantics we want, with synchronous operation during the 237 # interaction function, transactions fail instead of retrying. So if 238 # a concurrent actor touches the database in a way that blocks the 239 # transaction, we'll get an errback. That will cause the overall 240 # Scheduler to errback, and not commit its "all Changes before X have 241 # been handled" update. The next time that Scheduler is processed, it 242 # should try everything again. 243