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

Source Code for Module buildbot.schedulers.trysched

  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 os 
 17   
 18  from twisted.internet import defer 
 19  from twisted.python import log 
 20  from twisted.protocols import basic 
 21   
 22  from buildbot import pbutil 
 23  from buildbot.util.maildir import MaildirService 
 24  from buildbot.util import netstrings 
 25  from buildbot.process.properties import Properties 
 26  from buildbot.schedulers import base 
 27  from buildbot.status.buildset import BuildSetStatus 
28 29 30 -class TryBase(base.BaseScheduler):
31
32 - def filterBuilderList(self, builderNames):
33 """ 34 Make sure that C{builderNames} is a subset of the configured 35 C{self.builderNames}, returning an empty list if not. If 36 C{builderNames} is empty, use C{self.builderNames}. 37 38 @returns: list of builder names to build on 39 """ 40 41 # self.builderNames is the configured list of builders 42 # available for try. If the user supplies a list of builders, 43 # it must be restricted to the configured list. If not, build 44 # on all of the configured builders. 45 if builderNames: 46 for b in builderNames: 47 if not b in self.builderNames: 48 log.msg("%s got with builder %s" % (self, b)) 49 log.msg(" but that wasn't in our list: %s" 50 % (self.builderNames,)) 51 return [] 52 else: 53 builderNames = self.builderNames 54 return builderNames
55
56 57 -class BadJobfile(Exception):
58 pass
59
60 61 -class JobdirService(MaildirService):
62 # NOTE: tightly coupled with Try_Jobdir, below 63
64 - def messageReceived(self, filename):
65 f = self.moveToCurDir(filename) 66 return self.parent.handleJobFile(filename, f)
67
68 69 -class Try_Jobdir(TryBase):
70 71 compare_attrs = TryBase.compare_attrs + ( 'jobdir', ) 72
73 - def __init__(self, name, builderNames, jobdir, 74 properties={}):
75 TryBase.__init__(self, name=name, builderNames=builderNames, properties=properties) 76 self.jobdir = jobdir 77 self.watcher = JobdirService() 78 self.watcher.setServiceParent(self)
79
80 - def startService(self):
81 # set the watcher's basedir now that we have a master 82 self.watcher.setBasedir(os.path.join(self.master.basedir, self.jobdir)) 83 TryBase.startService(self)
84
85 - def parseJob(self, f):
86 # jobfiles are serialized build requests. Each is a list of 87 # serialized netstrings, in the following order: 88 # "2", the format version number ("1" does not have project/repo) 89 # buildsetID, arbitrary string, used to find the buildSet later 90 # branch name, "" for default-branch 91 # base revision, "" for HEAD 92 # patchlevel, usually "1" 93 # patch 94 # builderNames... 95 p = netstrings.NetstringParser() 96 try: 97 p.feed(f.read()) 98 except basic.NetstringParseError: 99 raise BadJobfile("unable to parse netstrings") 100 if not p.strings: 101 raise BadJobfile("could not find any complete netstrings") 102 ver = p.strings.pop(0) 103 104 if ver == "1": 105 buildsetID, branch, baserev, patchlevel, diff = p.strings[:5] 106 builderNames = p.strings[5:] 107 if branch == "": 108 branch = None 109 if baserev == "": 110 baserev = None 111 patchlevel = int(patchlevel) 112 repository='' 113 project='' 114 who='' 115 comment='' 116 elif ver == "2": # introduced the repository and project property 117 buildsetID, branch, baserev, patchlevel, diff, repository, project = p.strings[:7] 118 builderNames = p.strings[7:] 119 if branch == "": 120 branch = None 121 if baserev == "": 122 baserev = None 123 patchlevel = int(patchlevel) 124 who='' 125 comment='' 126 elif ver == "3": # introduced who property 127 buildsetID, branch, baserev, patchlevel, diff, repository, project, who = p.strings[:8] 128 builderNames = p.strings[8:] 129 if branch == "": 130 branch = None 131 if baserev == "": 132 baserev = None 133 patchlevel = int(patchlevel) 134 comment='' 135 elif ver == "4": # introduced try comments 136 buildsetID, branch, baserev, patchlevel, diff, repository, project, who, comment = p.strings[:9] 137 builderNames = p.strings[9:] 138 if branch == "": 139 branch = None 140 if baserev == "": 141 baserev = None 142 patchlevel = int(patchlevel) 143 144 else: 145 raise BadJobfile("unknown version '%s'" % ver) 146 return dict( 147 builderNames=builderNames, 148 branch=branch, 149 baserev=baserev, 150 patch_body=diff, 151 patch_level=patchlevel, 152 repository=repository, 153 project=project, 154 who=who, 155 comment=comment, 156 jobid=buildsetID)
157
158 - def handleJobFile(self, filename, f):
159 try: 160 parsed_job = self.parseJob(f) 161 builderNames = parsed_job['builderNames'] 162 except BadJobfile: 163 log.msg("%s reports a bad jobfile in %s" % (self, filename)) 164 log.err() 165 return defer.succeed(None) 166 167 # Validate/fixup the builder names. 168 builderNames = self.filterBuilderList(builderNames) 169 if not builderNames: 170 log.msg("incoming Try job did not specify any allowed builder names") 171 return defer.succeed(None) 172 173 who = "" 174 if parsed_job['who']: 175 who = parsed_job['who'] 176 177 comment = "" 178 if parsed_job['comment']: 179 comment = parsed_job['comment'] 180 181 d = self.master.db.sourcestampsets.addSourceStampSet() 182 183 def addsourcestamp(setid): 184 self.master.db.sourcestamps.addSourceStamp( 185 sourcestampsetid=setid, 186 branch=parsed_job['branch'], 187 revision=parsed_job['baserev'], 188 patch_body=parsed_job['patch_body'], 189 patch_level=parsed_job['patch_level'], 190 patch_author=who, 191 patch_comment=comment, 192 patch_subdir='', # TODO: can't set this remotely - #1769 193 project=parsed_job['project'], 194 repository=parsed_job['repository']) 195 return setid
196 197 d.addCallback(addsourcestamp) 198 def create_buildset(setid): 199 reason = "'try' job" 200 if parsed_job['who']: 201 reason += " by user %s" % parsed_job['who'] 202 return self.addBuildsetForSourceStamp(ssid=None, setid=setid, 203 reason=reason, external_idstring=parsed_job['jobid'], 204 builderNames=builderNames)
205 d.addCallback(create_buildset) 206 return d 207
208 209 -class Try_Userpass_Perspective(pbutil.NewCredPerspective):
210 - def __init__(self, scheduler, username):
211 self.scheduler = scheduler 212 self.username = username
213 214 @defer.deferredGenerator
215 - def perspective_try(self, branch, revision, patch, repository, project, 216 builderNames, who="", comment="", properties={} ):
217 db = self.scheduler.master.db 218 log.msg("user %s requesting build on builders %s" % (self.username, 219 builderNames)) 220 221 # build the intersection of the request and our configured list 222 builderNames = self.scheduler.filterBuilderList(builderNames) 223 if not builderNames: 224 return 225 226 reason = "'try' job" 227 228 if who: 229 reason += " by user %s" % who 230 231 if comment: 232 reason += " (%s)" % comment 233 234 wfd = defer.waitForDeferred(db.sourcestampsets.addSourceStampSet()) 235 yield wfd 236 sourcestampsetid = wfd.getResult() 237 238 wfd = defer.waitForDeferred( 239 db.sourcestamps.addSourceStamp(branch=branch, revision=revision, 240 repository=repository, project=project, patch_level=patch[0], 241 patch_body=patch[1], patch_subdir='', patch_author=who or '', 242 patch_comment=comment or '', sourcestampsetid = sourcestampsetid)) 243 # note: no way to specify patch subdir - #1769 244 yield wfd 245 wfd.getResult() 246 247 requested_props = Properties() 248 requested_props.update(properties, "try build") 249 wfd = defer.waitForDeferred( 250 self.scheduler.addBuildsetForSourceStamp(setid=sourcestampsetid, 251 reason=reason, properties=requested_props, 252 builderNames=builderNames)) 253 yield wfd 254 (bsid,brids) = wfd.getResult() 255 256 # return a remotely-usable BuildSetStatus object 257 wfd = defer.waitForDeferred( 258 db.buildsets.getBuildset(bsid)) 259 yield wfd 260 bsdict = wfd.getResult() 261 262 bss = BuildSetStatus(bsdict, self.scheduler.master.status) 263 from buildbot.status.client import makeRemote 264 r = makeRemote(bss) 265 yield r # return value
266
268 # Return a list of builder names that are configured 269 # for the try service 270 # This is mostly intended for integrating try services 271 # into other applications 272 return self.scheduler.listBuilderNames()
273
274 275 -class Try_Userpass(TryBase):
276 compare_attrs = ( 'name', 'builderNames', 'port', 'userpass', 'properties' ) 277
278 - def __init__(self, name, builderNames, port, userpass, 279 properties={}):
280 TryBase.__init__(self, name=name, builderNames=builderNames, properties=properties) 281 self.port = port 282 self.userpass = userpass
283
284 - def startService(self):
285 TryBase.startService(self) 286 287 # register each user/passwd with the pbmanager 288 def factory(mind, username): 289 return Try_Userpass_Perspective(self, username)
290 self.registrations = [] 291 for user, passwd in self.userpass: 292 self.registrations.append( 293 self.master.pbmanager.register(self.port, user, passwd, factory))
294
295 - def stopService(self):
296 d = defer.maybeDeferred(TryBase.stopService, self) 297 def unreg(_): 298 return defer.gatherResults( 299 [ reg.unregister() for reg in self.registrations ])
300 d.addCallback(unreg) 301 return d 302