Package buildbot :: Package status :: Package web :: Module baseweb
[frames] | no frames]

Source Code for Module buildbot.status.web.baseweb

  1   
  2  import os, weakref 
  3   
  4  from zope.interface import implements 
  5  from twisted.python import log 
  6  from twisted.application import strports, service 
  7  from twisted.web import server, distrib, static 
  8  from twisted.spread import pb 
  9  from twisted.web.util import Redirect 
 10   
 11  from buildbot.interfaces import IStatusReceiver 
 12   
 13  from buildbot.status.web.base import StaticFile, createJinjaEnv 
 14  from buildbot.status.web.feeds import Rss20StatusResource, \ 
 15       Atom10StatusResource 
 16  from buildbot.status.web.waterfall import WaterfallStatusResource 
 17  from buildbot.status.web.console import ConsoleStatusResource 
 18  from buildbot.status.web.olpb import OneLinePerBuild 
 19  from buildbot.status.web.grid import GridStatusResource, TransposedGridStatusResource 
 20  from buildbot.status.web.changes import ChangesResource 
 21  from buildbot.status.web.builder import BuildersResource 
 22  from buildbot.status.web.buildstatus import BuildStatusStatusResource 
 23  from buildbot.status.web.slaves import BuildSlavesResource 
 24  from buildbot.status.web.status_json import JsonStatusResource 
 25  from buildbot.status.web.xmlrpc import XMLRPCServer 
 26  from buildbot.status.web.about import AboutBuildbot 
 27  from buildbot.status.web.authz import Authz 
 28  from buildbot.status.web.auth import AuthFailResource 
 29  from buildbot.status.web.root import RootPage 
 30   
 31  # this class contains the WebStatus class.  Basic utilities are in base.py, 
 32  # and specific pages are each in their own module. 
 33   
34 -class WebStatus(service.MultiService):
35 implements(IStatusReceiver) 36 # TODO: IStatusReceiver is really about things which subscribe to hear 37 # about buildbot events. We need a different interface (perhaps a parent 38 # of IStatusReceiver) for status targets that don't subscribe, like the 39 # WebStatus class. buildbot.master.BuildMaster.loadConfig:737 asserts 40 # that everything in c['status'] provides IStatusReceiver, but really it 41 # should check that they provide IStatusTarget instead. 42 43 """ 44 The webserver provided by this class has the following resources: 45 46 /waterfall : the big time-oriented 'waterfall' display, with links 47 to individual changes, builders, builds, steps, and logs. 48 A number of query-arguments can be added to influence 49 the display. 50 /rss : a rss feed summarizing all failed builds. The same 51 query-arguments used by 'waterfall' can be added to 52 influence the feed output. 53 /atom : an atom feed summarizing all failed builds. The same 54 query-arguments used by 'waterfall' can be added to 55 influence the feed output. 56 /grid : another summary display that shows a grid of builds, with 57 sourcestamps on the x axis, and builders on the y. Query 58 arguments similar to those for the waterfall can be added. 59 /tgrid : similar to the grid display, but the commits are down the 60 left side, and the build hosts are across the top. 61 /builders/BUILDERNAME: a page summarizing the builder. This includes 62 references to the Schedulers that feed it, 63 any builds currently in the queue, which 64 buildslaves are designated or attached, and a 65 summary of the build process it uses. 66 /builders/BUILDERNAME/builds/NUM: a page describing a single Build 67 /builders/BUILDERNAME/builds/NUM/steps/STEPNAME: describes a single step 68 /builders/BUILDERNAME/builds/NUM/steps/STEPNAME/logs/LOGNAME: a StatusLog 69 /builders/_all/{force,stop}: force a build/stop building on all builders. 70 /changes : summarize all ChangeSources 71 /changes/CHANGENUM: a page describing a single Change 72 /schedulers/SCHEDULERNAME: a page describing a Scheduler, including 73 a description of its behavior, a list of the 74 Builders it triggers, and list of the Changes 75 that are queued awaiting the tree-stable 76 timer, and controls to accelerate the timer. 77 /buildslaves : list all BuildSlaves 78 /buildslaves/SLAVENAME : describe a single BuildSlave 79 /one_line_per_build : summarize the last few builds, one line each 80 /one_line_per_build/BUILDERNAME : same, but only for a single builder 81 /about : describe this buildmaster (Buildbot and support library versions) 82 /xmlrpc : (not yet implemented) an XMLRPC server with build status 83 84 and more! see the manual. 85 86 87 All URLs for pages which are not defined here are used to look 88 for files in PUBLIC_HTML, which defaults to BASEDIR/public_html. 89 This means that /robots.txt or /favicon.ico can be placed in 90 that directory 91 92 This webserver uses the jinja2 template system to generate the web pages 93 (see http://jinja.pocoo.org/2/) and by default loads pages from the 94 buildbot.status.web.templates package. Any file here can be overridden by placing 95 a corresponding file in the master's 'templates' directory. 96 97 The main customization points are layout.html which loads style sheet 98 (css) and provides header and footer content, and root.html, which 99 generates the root page. 100 101 All of the resources provided by this service use relative URLs to reach 102 each other. The only absolute links are the c['projectURL'] links at the 103 top and bottom of the page, and the buildbot home-page link at the 104 bottom. 105 106 Buildbot uses some generic classes to identify the type of object, and 107 some more specific classes for the various kinds of those types. It does 108 this by specifying both in the class attributes where applicable, 109 separated by a space. It is important that in your CSS you declare the 110 more generic class styles above the more specific ones. For example, 111 first define a style for .Event, and below that for .SUCCESS 112 113 The following CSS class names are used: 114 - Activity, Event, BuildStep, LastBuild: general classes 115 - waiting, interlocked, building, offline, idle: Activity states 116 - start, running, success, failure, warnings, skipped, exception: 117 LastBuild and BuildStep states 118 - Change: box with change 119 - Builder: box for builder name (at top) 120 - Project 121 - Time 122 123 """ 124 125 # we are not a ComparableMixin, and therefore the webserver will be 126 # rebuilt every time we reconfig. This is because WebStatus.putChild() 127 # makes it too difficult to tell whether two instances are the same or 128 # not (we'd have to do a recursive traversal of all children to discover 129 # all the changes). 130
131 - def __init__(self, http_port=None, distrib_port=None, allowForce=None, 132 public_html="public_html", site=None, numbuilds=20, 133 num_events=200, num_events_max=None, auth=None, 134 order_console_by_time=False, changecommentlink=None, 135 revlink=None, projects=None, repositories=None, 136 authz=None, logRotateLength=None, maxRotatedFiles=None):
137 """Run a web server that provides Buildbot status. 138 139 @type http_port: int or L{twisted.application.strports} string 140 @param http_port: a strports specification describing which port the 141 buildbot should use for its web server, with the 142 Waterfall display as the root page. For backwards 143 compatibility this can also be an int. Use 144 'tcp:8000' to listen on that port, or 145 'tcp:12345:interface=127.0.0.1' if you only want 146 local processes to connect to it (perhaps because 147 you are using an HTTP reverse proxy to make the 148 buildbot available to the outside world, and do not 149 want to make the raw port visible). 150 151 @type distrib_port: int or L{twisted.application.strports} string 152 @param distrib_port: Use this if you want to publish the Waterfall 153 page using web.distrib instead. The most common 154 case is to provide a string that is an absolute 155 pathname to the unix socket on which the 156 publisher should listen 157 (C{os.path.expanduser(~/.twistd-web-pb)} will 158 match the default settings of a standard 159 twisted.web 'personal web server'). Another 160 possibility is to pass an integer, which means 161 the publisher should listen on a TCP socket, 162 allowing the web server to be on a different 163 machine entirely. Both forms are provided for 164 backwards compatibility; the preferred form is a 165 strports specification like 166 'unix:/home/buildbot/.twistd-web-pb'. Providing 167 a non-absolute pathname will probably confuse 168 the strports parser. 169 170 @param allowForce: deprecated; use authz instead 171 @param auth: deprecated; use with authz 172 173 @param authz: a buildbot.status.web.authz.Authz instance giving the authorization 174 parameters for this view 175 176 @param public_html: the path to the public_html directory for this display, 177 either absolute or relative to the basedir. The default 178 is 'public_html', which selects BASEDIR/public_html. 179 180 @type site: None or L{twisted.web.server.Site} 181 @param site: Use this if you want to define your own object instead of 182 using the default.` 183 184 @type numbuilds: int 185 @param numbuilds: Default number of entries in lists at the /one_line_per_build 186 and /builders/FOO URLs. This default can be overriden both programatically --- 187 by passing the equally named argument to constructors of OneLinePerBuildOneBuilder 188 and OneLinePerBuild --- and via the UI, by tacking ?numbuilds=xy onto the URL. 189 190 @type num_events: int 191 @param num_events: Default number of events to show in the waterfall. 192 193 @type num_events_max: int 194 @param num_events_max: The maximum number of events that are allowed to be 195 shown in the waterfall. The default value of C{None} will disable this 196 check 197 198 @type auth: a L{status.web.auth.IAuth} or C{None} 199 @param auth: an object that performs authentication to restrict access 200 to the C{allowForce} features. Ignored if C{allowForce} 201 is not C{True}. If C{auth} is C{None}, people can force or 202 stop builds without auth. 203 204 @type order_console_by_time: bool 205 @param order_console_by_time: Whether to order changes (commits) in the console 206 view according to the time they were created (for VCS like Git) or 207 according to their integer revision numbers (for VCS like SVN). 208 209 @type changecommentlink: callable, dict, tuple (2 or 3 strings) or C{None} 210 @param changecommentlink: adds links to ticket/bug ids in change comments, 211 see buildbot.status.web.base.changecommentlink for details 212 213 @type revlink: callable, dict, string or C{None} 214 @param revlink: decorations revision ids with links to a web-view, 215 see buildbot.status.web.base.revlink for details 216 217 @type projects: callable, dict or c{None} 218 @param projects: maps project identifiers to URLs, so that any project listed 219 is automatically decorated with a link to it's front page. 220 see buildbot.status.web.base.dictlink for details 221 222 @type repositories: callable, dict or c{None} 223 @param repositories: maps repository identifiers to URLs, so that any project listed 224 is automatically decorated with a link to it's web view. 225 see buildbot.status.web.base.dictlink for details 226 227 @type logRotateLength: None or int 228 @param logRotateLength: file size at which the http.log is rotated/reset. 229 If not set, the value set in the buildbot.tac will be used, 230 falling back to the BuildMaster's default value (1 Mb). 231 232 @type maxRotatedFiles: None or int 233 @param maxRotatedFiles: number of old http.log files to keep during log rotation. 234 If not set, the value set in the buildbot.tac will be used, 235 falling back to the BuildMaster's default value (10 files). 236 237 """ 238 239 service.MultiService.__init__(self) 240 if type(http_port) is int: 241 http_port = "tcp:%d" % http_port 242 self.http_port = http_port 243 if distrib_port is not None: 244 if type(distrib_port) is int: 245 distrib_port = "tcp:%d" % distrib_port 246 if distrib_port[0] in "/~.": # pathnames 247 distrib_port = "unix:%s" % distrib_port 248 self.distrib_port = distrib_port 249 self.num_events = num_events 250 if num_events_max: 251 assert num_events_max >= num_events 252 self.num_events_max = num_events_max 253 self.public_html = public_html 254 255 # make up an authz if allowForce was given 256 if authz: 257 if allowForce is not None: 258 raise ValueError("cannot use both allowForce and authz parameters") 259 if auth: 260 raise ValueError("cannot use both auth and authz parameters (pass " 261 "auth as an Authz parameter)") 262 else: 263 # invent an authz 264 if allowForce and auth: 265 authz = Authz(auth=auth, default_action="auth") 266 elif allowForce: 267 authz = Authz(default_action=True) 268 else: 269 if auth: 270 log.msg("Warning: Ignoring authentication. Search for 'authorization'" 271 " in the manual") 272 authz = Authz() # no authorization for anything 273 274 self.authz = authz 275 276 self.orderConsoleByTime = order_console_by_time 277 278 # If we were given a site object, go ahead and use it. (if not, we add one later) 279 self.site = site 280 281 # store the log settings until we create the site object 282 self.logRotateLength = logRotateLength 283 self.maxRotatedFiles = maxRotatedFiles 284 285 # create the web site page structure 286 self.childrenToBeAdded = {} 287 self.setupUsualPages(numbuilds=numbuilds, num_events=num_events, 288 num_events_max=num_events_max) 289 290 # Set up the jinja templating engine. 291 self.templates = createJinjaEnv(revlink, changecommentlink, 292 repositories, projects) 293 294 # keep track of cached connections so we can break them when we shut 295 # down. See ticket #102 for more details. 296 self.channels = weakref.WeakKeyDictionary()
297 298 299
300 - def setupUsualPages(self, numbuilds, num_events, num_events_max):
301 #self.putChild("", IndexOrWaterfallRedirection()) 302 self.putChild("waterfall", WaterfallStatusResource(num_events=num_events, 303 num_events_max=num_events_max)) 304 self.putChild("grid", GridStatusResource()) 305 self.putChild("console", ConsoleStatusResource( 306 orderByTime=self.orderConsoleByTime)) 307 self.putChild("tgrid", TransposedGridStatusResource()) 308 self.putChild("builders", BuildersResource()) # has builds/steps/logs 309 self.putChild("one_box_per_builder", Redirect("/builders")) 310 self.putChild("changes", ChangesResource()) 311 self.putChild("buildslaves", BuildSlavesResource()) 312 self.putChild("buildstatus", BuildStatusStatusResource()) 313 #self.putChild("schedulers", SchedulersResource()) 314 self.putChild("one_line_per_build", 315 OneLinePerBuild(numbuilds=numbuilds)) 316 self.putChild("xmlrpc", XMLRPCServer()) 317 self.putChild("about", AboutBuildbot()) 318 self.putChild("authfail", AuthFailResource())
319
320 - def __repr__(self):
321 if self.http_port is None: 322 return "<WebStatus on path %s at %s>" % (self.distrib_port, 323 hex(id(self))) 324 if self.distrib_port is None: 325 return "<WebStatus on port %s at %s>" % (self.http_port, 326 hex(id(self))) 327 return ("<WebStatus on port %s and path %s at %s>" % 328 (self.http_port, self.distrib_port, hex(id(self))))
329
330 - def setServiceParent(self, parent):
331 service.MultiService.setServiceParent(self, parent) 332 333 # this class keeps a *separate* link to the buildmaster, rather than 334 # just using self.parent, so that when we are "disowned" (and thus 335 # parent=None), any remaining HTTP clients of this WebStatus will still 336 # be able to get reasonable results. 337 self.master = parent 338 339 def either(a,b): # a if a else b for py2.4 340 if a: 341 return a 342 else: 343 return b
344 345 rotateLength = either(self.logRotateLength, self.master.log_rotation.rotateLength) 346 maxRotatedFiles = either(self.maxRotatedFiles, self.master.log_rotation.maxRotatedFiles) 347 348 if not self.site: 349 350 class RotateLogSite(server.Site): 351 def _openLogFile(self, path): 352 try: 353 from twisted.python.logfile import LogFile 354 log.msg("Setting up http.log rotating %s files of %s bytes each" % 355 (maxRotatedFiles, rotateLength)) 356 if hasattr(LogFile, "fromFullPath"): # not present in Twisted-2.5.0 357 return LogFile.fromFullPath(path, rotateLength=rotateLength, maxRotatedFiles=maxRotatedFiles) 358 else: 359 log.msg("WebStatus: rotated http logs are not supported on this version of Twisted") 360 except ImportError, e: 361 log.msg("WebStatus: Unable to set up rotating http.log: %s" % e) 362 363 # if all else fails, just call the parent method 364 return server.Site._openLogFile(self, path)
365 366 # this will be replaced once we've been attached to a parent (and 367 # thus have a basedir and can reference BASEDIR) 368 root = static.Data("placeholder", "text/plain") 369 httplog = os.path.abspath(os.path.join(self.master.basedir, "http.log")) 370 self.site = RotateLogSite(root, logPath=httplog) 371 372 # the following items are accessed by HtmlResource when it renders 373 # each page. 374 self.site.buildbot_service = self 375 376 if self.http_port is not None: 377 s = strports.service(self.http_port, self.site) 378 s.setServiceParent(self) 379 if self.distrib_port is not None: 380 f = pb.PBServerFactory(distrib.ResourcePublisher(self.site)) 381 s = strports.service(self.distrib_port, f) 382 s.setServiceParent(self) 383 384 self.setupSite() 385
386 - def setupSite(self):
387 # this is responsible for creating the root resource. It isn't done 388 # at __init__ time because we need to reference the parent's basedir. 389 htmldir = os.path.abspath(os.path.join(self.master.basedir, self.public_html)) 390 if os.path.isdir(htmldir): 391 log.msg("WebStatus using (%s)" % htmldir) 392 else: 393 log.msg("WebStatus: warning: %s is missing. Do you need to run" 394 " 'buildbot upgrade-master' on this buildmaster?" % htmldir) 395 # all static pages will get a 404 until upgrade-master is used to 396 # populate this directory. Create the directory, though, since 397 # otherwise we get internal server errors instead of 404s. 398 os.mkdir(htmldir) 399 400 root = StaticFile(htmldir) 401 root.putChild("", RootPage()) 402 403 for name, child_resource in self.childrenToBeAdded.iteritems(): 404 root.putChild(name, child_resource) 405 406 status = self.getStatus() 407 root.putChild("rss", Rss20StatusResource(status)) 408 root.putChild("atom", Atom10StatusResource(status)) 409 root.putChild("json", JsonStatusResource(status)) 410 411 self.site.resource = root
412
413 - def putChild(self, name, child_resource):
414 """This behaves a lot like root.putChild() . """ 415 self.childrenToBeAdded[name] = child_resource
416
417 - def registerChannel(self, channel):
418 self.channels[channel] = 1 # weakrefs
419
420 - def stopService(self):
421 for channel in self.channels: 422 try: 423 channel.transport.loseConnection() 424 except: 425 log.msg("WebStatus.stopService: error while disconnecting" 426 " leftover clients") 427 log.err() 428 return service.MultiService.stopService(self)
429
430 - def getStatus(self):
431 return self.master.getStatus()
432
433 - def getChangeSvc(self):
434 return self.master.change_svc
435
436 - def getPortnum(self):
437 # this is for the benefit of unit tests 438 s = list(self)[0] 439 return s._port.getHost().port
440 441 # What happened to getControl?! 442 # 443 # instead of passing control objects all over the place in the web 444 # code, at the few places where a control instance is required we 445 # find the requisite object manually, starting at the buildmaster. 446 # This is in preparation for removal of the IControl hierarchy 447 # entirely. 448 449 # resources can get access to the IStatus by calling 450 # request.site.buildbot_service.getStatus() 451