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 
 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 filter, 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.branch = branch 93 if fileIsImportant: 94 assert callable(fileIsImportant) 95 self.fileIsImportant = fileIsImportant
96
97 - def get_initial_state(self, max_changeid):
98 return {"last_processed": max_changeid}
99
100 - def run(self):
101 db = self.parent.db 102 d = db.runInteraction(self.classify_changes) 103 d.addCallback(lambda ign: db.runInteraction(self._process_changes)) 104 return d
105
106 - def _process_changes(self, t):
107 db = self.parent.db 108 res = db.scheduler_get_classified_changes(self.schedulerid, t) 109 (important, unimportant) = res 110 return self.decide_and_remove_changes(t, important, unimportant)
111
112 - def decide_and_remove_changes(self, t, important, unimportant):
113 """Look at the changes that need to be processed and decide whether 114 to queue a BuildRequest or sleep until something changes. 115 116 If I decide that a build should be performed, I will add the 117 appropriate BuildRequest to the database queue, and remove the 118 (retired) changes that went into it from the scheduler_changes tabke. 119 120 Returns wakeup_delay: either None, or a float indicating when this 121 scheduler wants to be woken up next. The Scheduler is responsible for 122 padding its desired wakeup time by about a second to avoid frenetic 123 must-wake-up-at-exactly-8AM behavior. The Loop may silently impose a 124 minimum delay request of a couple seconds to prevent this sort of 125 thing, but Schedulers must still add their own padding to avoid at 126 least a double wakeup. 127 """ 128 129 if not important: 130 return None 131 all_changes = important + unimportant 132 most_recent = max([c.when for c in all_changes]) 133 if self.treeStableTimer is not None: 134 now = time.time() 135 stable_at = most_recent + self.treeStableTimer 136 if stable_at > now: 137 # Wake up one second late, to avoid waking up too early and 138 # looping a lot. 139 return stable_at + 1.0 140 141 # ok, do a build 142 self._add_build_and_remove_changes(t, all_changes) 143 return None
144
145 - def _add_build_and_remove_changes(self, t, all_changes):
146 db = self.parent.db 147 if self.treeStableTimer is None: 148 # each Change gets a separate build 149 for c in all_changes: 150 ss = SourceStamp(changes=[c]) 151 ssid = db.get_sourcestampid(ss, t) 152 self.create_buildset(ssid, "scheduler", t) 153 else: 154 ss = SourceStamp(changes=all_changes) 155 ssid = db.get_sourcestampid(ss, t) 156 self.create_buildset(ssid, "scheduler", t) 157 158 # and finally retire the changes from scheduler_changes 159 changeids = [c.number for c in all_changes] 160 db.scheduler_retire_changes(self.schedulerid, changeids, t)
161 162
163 -class AnyBranchScheduler(Scheduler):
164 compare_attrs = ('name', 'treeStableTimer', 'builderNames', 165 'fileIsImportant', 'properties', 'change_filter')
166 - def __init__(self, name, treeStableTimer, builderNames, 167 fileIsImportant=None, properties={}, categories=None, 168 branches=NotABranch, change_filter=None):
169 """ 170 @param name: the name of this Scheduler 171 @param treeStableTimer: the duration, in seconds, for which the tree 172 must remain unchanged before a build is 173 triggered. This is intended to avoid builds 174 of partially-committed fixes. 175 @param builderNames: a list of Builder names. When this Scheduler 176 decides to start a set of builds, they will be 177 run on the Builders named by this list. 178 179 @param fileIsImportant: A callable which takes one argument (a Change 180 instance) and returns True if the change is 181 worth building, and False if it is not. 182 Unimportant Changes are accumulated until the 183 build is triggered by an important change. 184 The default value of None means that all 185 Changes are important. 186 187 @param properties: properties to apply to all builds started from 188 this scheduler 189 190 @param change_filter: a buildbot.schedulers.filter.ChangeFilter instance 191 used to filter changes for this scheduler 192 193 @param branches: (deprecated) 194 @param categories: (deprecated) 195 """ 196 197 base.BaseScheduler.__init__(self, name, builderNames, properties) 198 self.make_filter(change_filter=change_filter, branch=branches, categories=categories) 199 self.treeStableTimer = treeStableTimer 200 if fileIsImportant: 201 assert callable(fileIsImportant) 202 self.fileIsImportant = fileIsImportant
203
204 - def _process_changes(self, t):
205 db = self.parent.db 206 res = db.scheduler_get_classified_changes(self.schedulerid, t) 207 (important, unimportant) = res 208 def _twolists(): return [], [] # important, unimportant 209 branch_changes = collections.defaultdict(_twolists) 210 for c in important: 211 branch_changes[c.branch][0].append(c) 212 for c in unimportant: 213 branch_changes[c.branch][1].append(c) 214 delays = [] 215 for branch in branch_changes: 216 (b_important, b_unimportant) = branch_changes[branch] 217 delay = self.decide_and_remove_changes(t, b_important, 218 b_unimportant) 219 if delay is not None: 220 delays.append(delay) 221 if delays: 222 return min(delays) 223 return None
224
225 -class Dependent(base.BaseScheduler):
226 # register with our upstream, so they'll tell us when they submit a 227 # buildset 228 compare_attrs = ('name', 'upstream_name', 'builderNames', 'properties') 229
230 - def __init__(self, name, upstream, builderNames, properties={}):
231 assert interfaces.IScheduler.providedBy(upstream) 232 base.BaseScheduler.__init__(self, name, builderNames, properties) 233 # by setting self.upstream_name, our buildSetSubmitted() method will 234 # be called whenever that upstream Scheduler adds a buildset to the 235 # DB. 236 self.upstream_name = upstream.name
237
238 - def buildSetSubmitted(self, bsid, t):
239 db = self.parent.db 240 db.scheduler_subscribe_to_buildset(self.schedulerid, bsid, t)
241
242 - def run(self):
243 d = self.parent.db.runInteraction(self._run) 244 return d
245 - def _run(self, t):
246 db = self.parent.db 247 res = db.scheduler_get_subscribed_buildsets(self.schedulerid, t) 248 # this returns bsid,ssid,results for all of our active subscriptions. 249 # We ignore the ones that aren't complete yet. This leaves the 250 # subscription in place until the buildset is complete. 251 for (bsid,ssid,complete,results) in res: 252 if complete: 253 if results in (SUCCESS, WARNINGS): 254 self.create_buildset(ssid, "downstream", t) 255 db.scheduler_unsubscribe_buildset(self.schedulerid, bsid, t) 256 return None
257 258 # Dependent/Triggerable schedulers will make a BuildSet with linked 259 # BuildRequests. The rest (which don't generally care when the set 260 # finishes) will just make the BuildRequests. 261 262 # runInteraction() should give us the all-or-nothing transaction 263 # semantics we want, with synchronous operation during the 264 # interaction function, transactions fail instead of retrying. So if 265 # a concurrent actor touches the database in a way that blocks the 266 # transaction, we'll get an errback. That will cause the overall 267 # Scheduler to errback, and not commit its "all Changes before X have 268 # been handled" update. The next time that Scheduler is processed, it 269 # should try everything again. 270