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

Source Code for Module buildbot.schedulers.basic

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