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

Source Code for Module buildslave.scripts.runner

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