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.sourcestamps.addSourceStamp( 182 branch=parsed_job['branch'], 183 revision=parsed_job['baserev'], 184 patch_body=parsed_job['patch_body'], 185 patch_level=parsed_job['patch_level'], 186 patch_author=who, 187 patch_comment=comment, 188 patch_subdir='', # TODO: can't set this remotely - #1769 189 project=parsed_job['project'], 190 repository=parsed_job['repository']) 191 def create_buildset(ssid): 192 reason = "'try' job" 193 if parsed_job['who']: 194 reason += " by user %s" % parsed_job['who'] 195 return self.addBuildsetForSourceStamp(ssid=ssid, 196 reason=reason, external_idstring=parsed_job['jobid'], 197 builderNames=builderNames)
198 d.addCallback(create_buildset) 199 return d
200
201 202 -class Try_Userpass_Perspective(pbutil.NewCredPerspective):
203 - def __init__(self, scheduler, username):
204 self.scheduler = scheduler 205 self.username = username
206 207 @defer.deferredGenerator
208 - def perspective_try(self, branch, revision, patch, repository, project, 209 builderNames, who="", comment="", properties={} ):
210 db = self.scheduler.master.db 211 log.msg("user %s requesting build on builders %s" % (self.username, 212 builderNames)) 213 214 # build the intersection of the request and our configured list 215 builderNames = self.scheduler.filterBuilderList(builderNames) 216 if not builderNames: 217 return 218 219 reason = "'try' job" 220 221 if who: 222 reason += " by user %s" % who 223 224 if comment: 225 reason += " (%s)" % comment 226 227 wfd = defer.waitForDeferred( 228 db.sourcestamps.addSourceStamp(branch=branch, revision=revision, 229 repository=repository, project=project, patch_level=patch[0], 230 patch_body=patch[1], patch_subdir='', patch_author=who or '', 231 patch_comment=comment or '')) 232 # note: no way to specify patch subdir - #1769 233 yield wfd 234 ssid = wfd.getResult() 235 236 requested_props = Properties() 237 requested_props.update(properties, "try build") 238 wfd = defer.waitForDeferred( 239 self.scheduler.addBuildsetForSourceStamp(ssid=ssid, 240 reason=reason, properties=requested_props, 241 builderNames=builderNames)) 242 yield wfd 243 (bsid,brids) = wfd.getResult() 244 245 # return a remotely-usable BuildSetStatus object 246 wfd = defer.waitForDeferred( 247 db.buildsets.getBuildset(bsid)) 248 yield wfd 249 bsdict = wfd.getResult() 250 251 bss = BuildSetStatus(bsdict, self.scheduler.master.status) 252 from buildbot.status.client import makeRemote 253 r = makeRemote(bss) 254 yield r # return value
255
257 # Return a list of builder names that are configured 258 # for the try service 259 # This is mostly intended for integrating try services 260 # into other applications 261 return self.scheduler.listBuilderNames()
262
263 264 -class Try_Userpass(TryBase):
265 compare_attrs = ( 'name', 'builderNames', 'port', 'userpass', 'properties' ) 266
267 - def __init__(self, name, builderNames, port, userpass, 268 properties={}):
269 TryBase.__init__(self, name=name, builderNames=builderNames, properties=properties) 270 self.port = port 271 self.userpass = userpass
272
273 - def startService(self):
274 TryBase.startService(self) 275 276 # register each user/passwd with the pbmanager 277 def factory(mind, username): 278 return Try_Userpass_Perspective(self, username)
279 self.registrations = [] 280 for user, passwd in self.userpass: 281 self.registrations.append( 282 self.master.pbmanager.register(self.port, user, passwd, factory))
283
284 - def stopService(self):
285 d = defer.maybeDeferred(TryBase.stopService, self) 286 def unreg(_): 287 return defer.gatherResults( 288 [ reg.unregister() for reg in self.registrations ])
289 d.addCallback(unreg) 290 return d 291