Package buildslave :: Package scripts :: Module runner
[frames] | no frames]

Source Code for Module buildslave.scripts.runner

  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  # N.B.: don't import anything that might pull in a reactor yet. Some of our 
 17  # subcommands want to load modules that need the gtk reactor. 
 18  import os, sys, re, time 
 19  from twisted.python import usage 
 20   
21 -def isBuildslaveDir(dir):
22 buildbot_tac = os.path.join(dir, "buildbot.tac") 23 if not os.path.isfile(buildbot_tac): 24 print "no buildbot.tac" 25 return False 26 27 contents = open(buildbot_tac, "r").read() 28 return "Application('buildslave')" in contents
29 30 # the create/start/stop commands should all be run as the same user, 31 # preferably a separate 'buildbot' account. 32 33 # Note that the terms 'options' and 'config' are used intechangeably here - in 34 # fact, they are intercanged several times. Caveat legator. 35
36 -class Maker:
37 - def __init__(self, config):
38 self.config = config 39 self.basedir = config['basedir'] 40 self.force = config.get('force', False) 41 self.quiet = config['quiet']
42
43 - def mkdir(self):
44 if os.path.exists(self.basedir): 45 if not self.quiet: 46 print "updating existing installation" 47 return 48 if not self.quiet: 49 print "mkdir", self.basedir 50 os.mkdir(self.basedir)
51
52 - def mkinfo(self):
53 path = os.path.join(self.basedir, "info") 54 if not os.path.exists(path): 55 if not self.quiet: 56 print "mkdir", path 57 os.mkdir(path) 58 created = False 59 admin = os.path.join(path, "admin") 60 if not os.path.exists(admin): 61 if not self.quiet: 62 print "Creating info/admin, you need to edit it appropriately" 63 f = open(admin, "wt") 64 f.write("Your Name Here <admin@youraddress.invalid>\n") 65 f.close() 66 created = True 67 host = os.path.join(path, "host") 68 if not os.path.exists(host): 69 if not self.quiet: 70 print "Creating info/host, you need to edit it appropriately" 71 f = open(host, "wt") 72 f.write("Please put a description of this build host here\n") 73 f.close() 74 created = True 75 access_uri = os.path.join(path, "access_uri") 76 if not os.path.exists(access_uri): 77 if not self.quiet: 78 print "Not creating info/access_uri - add it if you wish" 79 if created and not self.quiet: 80 print "Please edit the files in %s appropriately." % path
81
82 - def chdir(self):
83 if not self.quiet: 84 print "chdir", self.basedir 85 os.chdir(self.basedir)
86
87 - def makeTAC(self, contents, secret=False):
88 tacfile = "buildbot.tac" 89 if os.path.exists(tacfile): 90 oldcontents = open(tacfile, "rt").read() 91 if oldcontents == contents: 92 if not self.quiet: 93 print "buildbot.tac already exists and is correct" 94 return 95 if not self.quiet: 96 print "not touching existing buildbot.tac" 97 print "creating buildbot.tac.new instead" 98 tacfile = "buildbot.tac.new" 99 f = open(tacfile, "wt") 100 f.write(contents) 101 f.close() 102 if secret: 103 os.chmod(tacfile, 0600)
104 105 slaveTACTemplate = [""" 106 import os 107 108 from twisted.application import service 109 from buildslave.bot import BuildSlave 110 111 basedir = r'%(basedir)s' 112 rotateLength = %(log-size)s 113 maxRotatedFiles = %(log-count)s 114 115 # if this is a relocatable tac file, get the directory containing the TAC 116 if basedir == '.': 117 import os.path 118 basedir = os.path.abspath(os.path.dirname(__file__)) 119 120 # note: this line is matched against to check that this is a buildslave 121 # directory; do not edit it. 122 application = service.Application('buildslave') 123 """, 124 """ 125 try: 126 from twisted.python.logfile import LogFile 127 from twisted.python.log import ILogObserver, FileLogObserver 128 logfile = LogFile.fromFullPath(os.path.join(basedir, "twistd.log"), rotateLength=rotateLength, 129 maxRotatedFiles=maxRotatedFiles) 130 application.setComponent(ILogObserver, FileLogObserver(logfile).emit) 131 except ImportError: 132 # probably not yet twisted 8.2.0 and beyond, can't set log yet 133 pass 134 """, 135 """ 136 buildmaster_host = '%(host)s' 137 port = %(port)d 138 slavename = '%(name)s' 139 passwd = '%(passwd)s' 140 keepalive = %(keepalive)d 141 usepty = %(usepty)d 142 umask = %(umask)s 143 maxdelay = %(maxdelay)d 144 145 s = BuildSlave(buildmaster_host, port, slavename, passwd, basedir, 146 keepalive, usepty, umask=umask, maxdelay=maxdelay) 147 s.setServiceParent(application) 148 149 """] 150
151 -def createSlave(config):
152 m = Maker(config) 153 m.mkdir() 154 m.chdir() 155 if config['relocatable']: 156 config['basedir'] = '.' 157 try: 158 master = config['master'] 159 port = None 160 host, port = re.search(r'^([^:]+)(?:[:](\d+))?', master).groups() 161 if port == None: 162 port = '9989' 163 config['host'] = host 164 config['port'] = int(port) 165 except: 166 print "unparseable master location '%s'" % master 167 print " expecting something more like localhost:8007 or localhost" 168 raise 169 170 if config['no-logrotate']: 171 slaveTAC = "".join([slaveTACTemplate[0]] + slaveTACTemplate[2:]) 172 else: 173 slaveTAC = "".join(slaveTACTemplate) 174 contents = slaveTAC % config 175 176 m.makeTAC(contents, secret=True) 177 m.mkinfo() 178 179 if not m.quiet: 180 print "buildslave configured in %s" % m.basedir
181 182 183
184 -def stop(config, signame="TERM", wait=False, returnFalseOnNotRunning=False):
185 import signal 186 basedir = config['basedir'] 187 quiet = config['quiet'] 188 189 if not isBuildslaveDir(config['basedir']): 190 print "not a buildslave directory" 191 sys.exit(1) 192 193 os.chdir(basedir) 194 try: 195 f = open("twistd.pid", "rt") 196 except: 197 if returnFalseOnNotRunning: 198 return False 199 if not quiet: print "buildslave not running." 200 sys.exit(0) 201 pid = int(f.read().strip()) 202 signum = getattr(signal, "SIG"+signame) 203 timer = 0 204 try: 205 os.kill(pid, signum) 206 except OSError, e: 207 if e.errno != 3: 208 raise 209 210 if not wait: 211 if not quiet: 212 print "sent SIG%s to process" % signame 213 return 214 time.sleep(0.1) 215 while timer < 10: 216 # poll once per second until twistd.pid goes away, up to 10 seconds 217 try: 218 os.kill(pid, 0) 219 except OSError: 220 if not quiet: 221 print "buildslave process %d is dead" % pid 222 return 223 timer += 1 224 time.sleep(1) 225 if not quiet: 226 print "never saw process go away"
227
228 -def restart(config):
229 quiet = config['quiet'] 230 231 if not isBuildslaveDir(config['basedir']): 232 print "not a buildslave directory" 233 sys.exit(1) 234 235 from buildslave.scripts.startup import start 236 if not stop(config, wait=True, returnFalseOnNotRunning=True): 237 if not quiet: 238 print "no old buildslave process found to stop" 239 if not quiet: 240 print "now restarting buildslave process.." 241 start(config)
242 243
244 -class MakerBase(usage.Options):
245 optFlags = [ 246 ['help', 'h', "Display this message"], 247 ["quiet", "q", "Do not emit the commands being run"], 248 ] 249 250 longdesc = """ 251 Operates upon the specified <basedir> (or the current directory, if not 252 specified). 253 """ 254 255 opt_h = usage.Options.opt_help 256
257 - def parseArgs(self, *args):
258 if len(args) > 0: 259 self['basedir'] = args[0] 260 else: 261 # Use the current directory if no basedir was specified. 262 self['basedir'] = os.getcwd() 263 if len(args) > 1: 264 raise usage.UsageError("I wasn't expecting so many arguments")
265
266 - def postOptions(self):
267 self['basedir'] = os.path.abspath(self['basedir'])
268
269 -class StartOptions(MakerBase):
270 optFlags = [ 271 ['quiet', 'q', "Don't display startup log messages"], 272 ]
273 - def getSynopsis(self):
274 return "Usage: buildslave start [<basedir>]"
275
276 -class StopOptions(MakerBase):
277 - def getSynopsis(self):
278 return "Usage: buildslave stop [<basedir>]"
279
280 -class RestartOptions(MakerBase):
281 optFlags = [ 282 ['quiet', 'q', "Don't display startup log messages"], 283 ]
284 - def getSynopsis(self):
285 return "Usage: buildslave restart [<basedir>]"
286
287 -class UpgradeSlaveOptions(MakerBase):
288 optFlags = [ 289 ] 290 optParameters = [ 291 ] 292
293 - def getSynopsis(self):
294 return "Usage: buildslave upgrade-slave [<basedir>]"
295 296 longdesc = """ 297 This command takes an existing buildslave working directory and 298 upgrades it to the current version. 299 """
300
301 -def upgradeSlave(config):
302 basedir = os.path.expanduser(config['basedir']) 303 buildbot_tac = open(os.path.join(basedir, "buildbot.tac")).read() 304 new_buildbot_tac = buildbot_tac.replace( 305 "from buildbot.slave.bot import BuildSlave", 306 "from buildslave.bot import BuildSlave") 307 if new_buildbot_tac != buildbot_tac: 308 open(os.path.join(basedir, "buildbot.tac"), "w").write(new_buildbot_tac) 309 print "buildbot.tac updated" 310 else: 311 print "No changes made" 312 313 return 0
314 315
316 -class SlaveOptions(MakerBase):
317 optFlags = [ 318 ["force", "f", "Re-use an existing directory"], 319 ["relocatable", "r", 320 "Create a relocatable buildbot.tac"], 321 ["no-logrotate", "n", 322 "Do not permit buildmaster rotate logs by itself"] 323 ] 324 optParameters = [ 325 ["keepalive", "k", 600, 326 "Interval at which keepalives should be sent (in seconds)"], 327 ["usepty", None, 0, 328 "(1 or 0) child processes should be run in a pty (default 0)"], 329 ["umask", None, "None", 330 "controls permissions of generated files. Use --umask=022 to be world-readable"], 331 ["maxdelay", None, 300, 332 "Maximum time between connection attempts"], 333 ["log-size", "s", "10000000", 334 "size at which to rotate twisted log files"], 335 ["log-count", "l", "10", 336 "limit the number of kept old twisted log files (None for unlimited)"], 337 ] 338 339 longdesc = """ 340 This command creates a buildslave working directory and buildbot.tac 341 file. The bot will use the <name> and <passwd> arguments to authenticate 342 itself when connecting to the master. All commands are run in a 343 build-specific subdirectory of <basedir>. <master> is a string of the 344 form 'hostname[:port]', and specifies where the buildmaster can be reached. 345 port defaults to 9989 346 347 The appropriate values for <name>, <passwd>, and <master> should be 348 provided to you by the buildmaster administrator. You must choose <basedir> 349 yourself. 350 """ 351
352 - def getSynopsis(self):
353 return "Usage: buildslave create-slave [options] <basedir> <master> <name> <passwd>"
354
355 - def parseArgs(self, *args):
356 if len(args) < 4: 357 raise usage.UsageError("command needs more arguments") 358 basedir, master, name, passwd = args 359 if master[:5] == "http:": 360 raise usage.UsageError("<master> is not a URL - do not use URL") 361 self['basedir'] = basedir 362 self['master'] = master 363 self['name'] = name 364 self['passwd'] = passwd
365
366 - def postOptions(self):
367 MakerBase.postOptions(self) 368 self['usepty'] = int(self['usepty']) 369 self['keepalive'] = int(self['keepalive']) 370 self['maxdelay'] = int(self['maxdelay']) 371 if not re.match('^\d+$', self['log-size']): 372 raise usage.UsageError("log-size parameter needs to be an int") 373 if not re.match('^\d+$', self['log-count']) and \ 374 self['log-count'] != 'None': 375 raise usage.UsageError("log-count parameter needs to be an int "+ 376 " or None")
377
378 -class Options(usage.Options):
379 synopsis = "Usage: buildslave <command> [command options]" 380 381 subCommands = [ 382 # the following are all admin commands 383 ['create-slave', None, SlaveOptions, 384 "Create and populate a directory for a new buildslave"], 385 ['upgrade-slave', None, UpgradeSlaveOptions, 386 "Upgrade an existing buildslave directory for the current version"], 387 ['start', None, StartOptions, "Start a buildslave"], 388 ['stop', None, StopOptions, "Stop a buildslave"], 389 ['restart', None, RestartOptions, 390 "Restart a buildslave"], 391 ] 392
393 - def opt_version(self):
394 import buildslave 395 print "Buildslave version: %s" % buildslave.version 396 usage.Options.opt_version(self)
397
398 - def opt_verbose(self):
399 from twisted.python import log 400 log.startLogging(sys.stderr)
401
402 - def postOptions(self):
403 if not hasattr(self, 'subOptions'): 404 raise usage.UsageError("must specify a command")
405 406
407 -def run():
408 config = Options() 409 try: 410 config.parseOptions() 411 except usage.error, e: 412 print "%s: %s" % (sys.argv[0], e) 413 print 414 c = getattr(config, 'subOptions', config) 415 print str(c) 416 sys.exit(1) 417 418 command = config.subCommand 419 so = config.subOptions 420 421 if command == "create-slave": 422 createSlave(so) 423 elif command == "upgrade-slave": 424 upgradeSlave(so) 425 elif command == "start": 426 if not isBuildslaveDir(so['basedir']): 427 print "not a buildslave directory" 428 sys.exit(1) 429 430 from buildslave.scripts.startup import start 431 start(so) 432 elif command == "stop": 433 stop(so, wait=True) 434 elif command == "restart": 435 restart(so) 436 sys.exit(0)
437