Package buildbot :: Package status :: Module client
[frames] | no frames]

Source Code for Module buildbot.status.client

  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   
 17  from twisted.spread import pb 
 18  from twisted.python import components, log as twlog 
 19  from twisted.internet import reactor 
 20  from twisted.application import strports 
 21  from twisted.cred import portal, checkers 
 22   
 23  from buildbot import interfaces 
 24  from zope.interface import Interface, implements 
 25  from buildbot.status import logfile, base 
 26  from buildbot.changes import changes 
 27   
28 -class IRemote(Interface):
29 pass
30
31 -def makeRemote(obj):
32 # we want IRemote(None) to be None, but you can't really do that with 33 # adapters, so we fake it 34 if obj is None: 35 return None 36 return IRemote(obj)
37 38
39 -class RemoteBuildSet(pb.Referenceable):
40 - def __init__(self, buildset):
41 self.b = buildset
42
43 - def remote_getSourceStamp(self):
44 return self.b.getSourceStamp()
45
46 - def remote_getReason(self):
47 return self.b.getReason()
48
49 - def remote_getID(self):
50 return self.b.getID()
51
52 - def remote_getBuilderNames(self):
53 return self.b.getBuilderNames() # note: passes along the Deferred
54
55 - def remote_getBuildRequests(self):
56 """Returns a list of (builderName, BuildRequest) tuples.""" 57 d = self.b.getBuilderNamesAndBuildRequests() 58 def add_remote(buildrequests): 59 for k,v in buildrequests.iteritems(): 60 buildrequests[k] = IRemote(v) 61 return buildrequests.items()
62 d.addCallback(add_remote) 63 return d
64
65 - def remote_isFinished(self):
66 return self.b.isFinished()
67
68 - def remote_waitUntilFinished(self):
69 d = self.b.waitUntilFinished() 70 d.addCallback(lambda res: self) 71 return d
72
73 - def remote_getResults(self):
74 return self.b.getResults()
75 76 components.registerAdapter(RemoteBuildSet, 77 interfaces.IBuildSetStatus, IRemote) 78 79
80 -class RemoteBuilder(pb.Referenceable):
81 - def __init__(self, builder):
82 self.b = builder
83
84 - def remote_getName(self):
85 return self.b.getName()
86
87 - def remote_getCategory(self):
88 return self.b.getCategory()
89
90 - def remote_getState(self):
91 state, builds = self.b.getState() 92 return (state, 93 None, # TODO: remove leftover ETA 94 [makeRemote(b) for b in builds])
95
96 - def remote_getSlaves(self):
97 return [IRemote(s) for s in self.b.getSlaves()]
98
100 return makeRemote(self.b.getLastFinishedBuild())
101
102 - def remote_getCurrentBuilds(self):
103 return [IRemote(b) for b in self.b.getCurrentBuilds()]
104
105 - def remote_getBuild(self, number):
106 return makeRemote(self.b.getBuild(number))
107
108 - def remote_getEvent(self, number):
109 return IRemote(self.b.getEvent(number))
110 111 components.registerAdapter(RemoteBuilder, 112 interfaces.IBuilderStatus, IRemote) 113 114
115 -class RemoteBuildRequest(pb.Referenceable):
116 - def __init__(self, buildreq):
117 self.b = buildreq 118 # mapping of observers (RemoteReference instances) to local callable 119 # objects that have been passed to BuildRequestStatus.subscribe 120 self.observers = []
121
122 - def remote_getSourceStamp(self):
123 # note that this now returns a Deferred 124 return self.b.getSourceStamp()
125
126 - def remote_getBuilderName(self):
127 return self.b.getBuilderName()
128
129 - def remote_subscribe(self, observer):
130 """The observer's remote_newbuild method will be called (with two 131 arguments: the RemoteBuild object, and our builderName) for each new 132 Build that is created to handle this BuildRequest.""" 133 def send(bs): 134 d = observer.callRemote("newbuild", 135 IRemote(bs), self.b.getBuilderName()) 136 d.addErrback(twlog.err, 137 "while calling client-side remote_newbuild")
138 self.observers.append((observer, send)) 139 self.b.subscribe(send)
140
141 - def remote_unsubscribe(self, observer):
142 for i, (obs, send) in enumerate(self.observers): 143 if obs == observer: 144 del self.observers[i] 145 self.b.unsubscribe(send) 146 break
147 148 components.registerAdapter(RemoteBuildRequest, 149 interfaces.IBuildRequestStatus, IRemote) 150
151 -class RemoteBuild(pb.Referenceable):
152 - def __init__(self, build):
153 self.b = build 154 self.observers = []
155
156 - def remote_getBuilderName(self):
157 return self.b.getBuilder().getName()
158
159 - def remote_getNumber(self):
160 return self.b.getNumber()
161
162 - def remote_getReason(self):
163 return self.b.getReason()
164
165 - def remote_getChanges(self):
166 return [IRemote(c) for c in self.b.getChanges()]
167
168 - def remote_getRevisions(self):
169 return self.b.getRevisions()
170
172 return self.b.getResponsibleUsers()
173
174 - def remote_getSteps(self):
175 return [IRemote(s) for s in self.b.getSteps()]
176
177 - def remote_getTimes(self):
178 return self.b.getTimes()
179
180 - def remote_isFinished(self):
181 return self.b.isFinished()
182
183 - def remote_waitUntilFinished(self):
184 # the Deferred returned by callRemote() will fire when this build is 185 # finished 186 d = self.b.waitUntilFinished() 187 d.addCallback(lambda res: self) 188 return d
189
190 - def remote_getETA(self):
191 return self.b.getETA()
192
193 - def remote_getCurrentStep(self):
194 return makeRemote(self.b.getCurrentStep())
195
196 - def remote_getText(self):
197 return self.b.getText()
198
199 - def remote_getResults(self):
200 return self.b.getResults()
201
202 - def remote_getLogs(self):
203 logs = {} 204 for name,log in self.b.getLogs().items(): 205 logs[name] = IRemote(log) 206 return logs
207
208 - def remote_subscribe(self, observer, updateInterval=None):
209 """The observer will have remote_stepStarted(buildername, build, 210 stepname, step), remote_stepFinished(buildername, build, stepname, 211 step, results), and maybe remote_buildETAUpdate(buildername, build, 212 eta)) messages sent to it.""" 213 self.observers.append(observer) 214 s = BuildSubscriber(observer) 215 self.b.subscribe(s, updateInterval)
216
217 - def remote_unsubscribe(self, observer):
218 # TODO: is the observer automatically unsubscribed when the build 219 # finishes? Or are they responsible for unsubscribing themselves 220 # anyway? How do we avoid a race condition here? 221 for o in self.observers: 222 if o == observer: 223 self.observers.remove(o)
224 225 226 components.registerAdapter(RemoteBuild, 227 interfaces.IBuildStatus, IRemote) 228
229 -class BuildSubscriber:
230 - def __init__(self, observer):
231 self.observer = observer
232
233 - def buildETAUpdate(self, build, eta):
234 self.observer.callRemote("buildETAUpdate", 235 build.getBuilder().getName(), 236 IRemote(build), 237 eta)
238
239 - def stepStarted(self, build, step):
240 self.observer.callRemote("stepStarted", 241 build.getBuilder().getName(), 242 IRemote(build), 243 step.getName(), IRemote(step)) 244 return None
245
246 - def stepFinished(self, build, step, results):
247 self.observer.callRemote("stepFinished", 248 build.getBuilder().getName(), 249 IRemote(build), 250 step.getName(), IRemote(step), 251 results)
252 253
254 -class RemoteBuildStep(pb.Referenceable):
255 - def __init__(self, step):
256 self.s = step
257
258 - def remote_getName(self):
259 return self.s.getName()
260
261 - def remote_getBuild(self):
262 return IRemote(self.s.getBuild())
263
264 - def remote_getTimes(self):
265 return self.s.getTimes()
266
267 - def remote_getExpectations(self):
268 return self.s.getExpectations()
269
270 - def remote_getLogs(self):
271 logs = {} 272 for log in self.s.getLogs(): 273 logs[log.getName()] = IRemote(log) 274 return logs
275
276 - def remote_isFinished(self):
277 return self.s.isFinished()
278
279 - def remote_waitUntilFinished(self):
280 return self.s.waitUntilFinished() # returns a Deferred
281
282 - def remote_getETA(self):
283 return self.s.getETA()
284
285 - def remote_getText(self):
286 return self.s.getText()
287
288 - def remote_getResults(self):
289 return self.s.getResults()
290 291 components.registerAdapter(RemoteBuildStep, 292 interfaces.IBuildStepStatus, IRemote) 293
294 -class RemoteSlave:
295 - def __init__(self, slave):
296 self.s = slave
297
298 - def remote_getName(self):
299 return self.s.getName()
300 - def remote_getAdmin(self):
301 return self.s.getAdmin()
302 - def remote_getHost(self):
303 return self.s.getHost()
304 - def remote_isConnected(self):
305 return self.s.isConnected()
306 307 components.registerAdapter(RemoteSlave, 308 interfaces.ISlaveStatus, IRemote) 309
310 -class RemoteEvent:
311 - def __init__(self, event):
312 self.e = event
313
314 - def remote_getTimes(self):
315 return self.s.getTimes()
316 - def remote_getText(self):
317 return self.s.getText()
318 319 components.registerAdapter(RemoteEvent, 320 interfaces.IStatusEvent, IRemote) 321
322 -class RemoteLog(pb.Referenceable):
323 - def __init__(self, log):
324 self.l = log
325
326 - def remote_getName(self):
327 return self.l.getName()
328
329 - def remote_isFinished(self):
330 return self.l.isFinished()
331 - def remote_waitUntilFinished(self):
332 d = self.l.waitUntilFinished() 333 d.addCallback(lambda res: self) 334 return d
335
336 - def remote_getText(self):
337 return self.l.getText()
339 return self.l.getTextWithHeaders()
340 - def remote_getChunks(self):
341 return self.l.getChunks()
342 # TODO: subscription interface 343 344 components.registerAdapter(RemoteLog, logfile.LogFile, IRemote) 345 # TODO: something similar for builder.HTMLLogfile ? 346
347 -class RemoteChange:
348 - def __init__(self, change):
349 self.c = change
350
351 - def getWho(self):
352 return self.c.who
353 - def getFiles(self):
354 return self.c.files
355 - def getComments(self):
356 return self.c.comments
357 358 components.registerAdapter(RemoteChange, changes.Change, IRemote) 359 360
361 -class StatusClientPerspective(base.StatusReceiverPerspective):
362 363 subscribed = None 364 client = None 365
366 - def __init__(self, status):
367 self.status = status # the IStatus 368 self.subscribed_to_builders = [] # Builders to which we're subscribed 369 self.subscribed_to = [] # everything else we're subscribed to
370
371 - def __getstate__(self):
372 d = self.__dict__.copy() 373 d['client'] = None 374 return d
375
376 - def attached(self, mind):
377 #twlog.msg("StatusClientPerspective.attached") 378 return self
379
380 - def detached(self, mind):
381 twlog.msg("PB client detached") 382 self.client = None 383 for name in self.subscribed_to_builders: 384 twlog.msg(" unsubscribing from Builder(%s)" % name) 385 self.status.getBuilder(name).unsubscribe(self) 386 for s in self.subscribed_to: 387 twlog.msg(" unsubscribe from %s" % s) 388 s.unsubscribe(self) 389 self.subscribed = None
390
391 - def perspective_subscribe(self, mode, interval, target):
392 """The remote client wishes to subscribe to some set of events. 393 'target' will be sent remote messages when these events happen. 394 'mode' indicates which events are desired: it is a string with one 395 of the following values: 396 397 'builders': builderAdded, builderRemoved 398 'builds': those plus builderChangedState, buildStarted, buildFinished 399 'steps': all those plus buildETAUpdate, stepStarted, stepFinished 400 'logs': all those plus stepETAUpdate, logStarted, logFinished 401 'full': all those plus logChunk (with the log contents) 402 403 404 Messages are defined by buildbot.interfaces.IStatusReceiver . 405 'interval' is used to specify how frequently ETAUpdate messages 406 should be sent. 407 408 Raising or lowering the subscription level will take effect starting 409 with the next build or step.""" 410 411 assert mode in ("builders", "builds", "steps", "logs", "full") 412 assert target 413 twlog.msg("PB subscribe(%s)" % mode) 414 415 self.client = target 416 self.subscribed = mode 417 self.interval = interval 418 self.subscribed_to.append(self.status) 419 # wait a moment before subscribing, so the new-builder messages 420 # won't appear before this remote method finishes 421 reactor.callLater(0, self.status.subscribe, self) 422 return None
423
424 - def perspective_unsubscribe(self):
425 twlog.msg("PB unsubscribe") 426 self.status.unsubscribe(self) 427 self.subscribed_to.remove(self.status) 428 self.client = None
429
430 - def perspective_getBuildSets(self):
431 """This returns tuples of (buildset, bsid), because that is much more 432 convenient for tryclient.""" 433 d = self.status.getBuildSets() 434 def make_remotes(buildsets): 435 return [(IRemote(s), s.id) for s in buildsets]
436 d.addCallback(make_remotes) 437 return d
438
439 - def perspective_getBuilderNames(self):
440 return self.status.getBuilderNames()
441
442 - def perspective_getBuilder(self, name):
443 b = self.status.getBuilder(name) 444 return IRemote(b)
445
446 - def perspective_getSlave(self, name):
447 s = self.status.getSlave(name) 448 return IRemote(s)
449
450 - def perspective_ping(self):
451 """Ping method to allow pb clients to validate their connections.""" 452 return "pong"
453 454 # IStatusReceiver methods, invoked if we've subscribed 455 456 # mode >= builder
457 - def builderAdded(self, name, builder):
458 self.client.callRemote("builderAdded", name, IRemote(builder)) 459 if self.subscribed in ("builds", "steps", "logs", "full"): 460 self.subscribed_to_builders.append(name) 461 return self 462 return None
463
464 - def builderChangedState(self, name, state):
465 self.client.callRemote("builderChangedState", name, state, None)
466 # TODO: remove leftover ETA argument 467
468 - def builderRemoved(self, name):
469 if name in self.subscribed_to_builders: 470 self.subscribed_to_builders.remove(name) 471 self.client.callRemote("builderRemoved", name)
472
473 - def buildsetSubmitted(self, buildset):
474 # TODO: deliver to client, somehow 475 pass
476 477 # mode >= builds
478 - def buildStarted(self, name, build):
479 self.client.callRemote("buildStarted", name, IRemote(build)) 480 if self.subscribed in ("steps", "logs", "full"): 481 self.subscribed_to.append(build) 482 return (self, self.interval) 483 return None
484
485 - def buildFinished(self, name, build, results):
486 if build in self.subscribed_to: 487 # we might have joined during the build 488 self.subscribed_to.remove(build) 489 self.client.callRemote("buildFinished", 490 name, IRemote(build), results)
491 492 # mode >= steps
493 - def buildETAUpdate(self, build, eta):
494 self.client.callRemote("buildETAUpdate", 495 build.getBuilder().getName(), IRemote(build), 496 eta)
497
498 - def stepStarted(self, build, step):
499 # we add some information here so the client doesn't have to do an 500 # extra round-trip 501 self.client.callRemote("stepStarted", 502 build.getBuilder().getName(), IRemote(build), 503 step.getName(), IRemote(step)) 504 if self.subscribed in ("logs", "full"): 505 self.subscribed_to.append(step) 506 return (self, self.interval) 507 return None
508
509 - def stepFinished(self, build, step, results):
510 self.client.callRemote("stepFinished", 511 build.getBuilder().getName(), IRemote(build), 512 step.getName(), IRemote(step), 513 results) 514 if step in self.subscribed_to: 515 # eventually (through some new subscription method) we could 516 # join in the middle of the step 517 self.subscribed_to.remove(step)
518 519 # mode >= logs
520 - def stepETAUpdate(self, build, step, ETA, expectations):
521 self.client.callRemote("stepETAUpdate", 522 build.getBuilder().getName(), IRemote(build), 523 step.getName(), IRemote(step), 524 ETA, expectations)
525
526 - def logStarted(self, build, step, log):
527 # TODO: make the HTMLLog adapter 528 rlog = IRemote(log, None) 529 if not rlog: 530 print "hey, couldn't adapt %s to IRemote" % log 531 self.client.callRemote("logStarted", 532 build.getBuilder().getName(), IRemote(build), 533 step.getName(), IRemote(step), 534 log.getName(), IRemote(log, None)) 535 if self.subscribed in ("full",): 536 self.subscribed_to.append(log) 537 return self 538 return None
539
540 - def logFinished(self, build, step, log):
541 self.client.callRemote("logFinished", 542 build.getBuilder().getName(), IRemote(build), 543 step.getName(), IRemote(step), 544 log.getName(), IRemote(log, None)) 545 if log in self.subscribed_to: 546 self.subscribed_to.remove(log)
547 548 # mode >= full
549 - def logChunk(self, build, step, log, channel, text):
550 self.client.callRemote("logChunk", 551 build.getBuilder().getName(), IRemote(build), 552 step.getName(), IRemote(step), 553 log.getName(), IRemote(log), 554 channel, text)
555 556
557 -class PBListener(base.StatusReceiverMultiService):
558 """I am a listener for PB-based status clients.""" 559 560 compare_attrs = ["port", "cred"] 561 implements(portal.IRealm) 562
563 - def __init__(self, port, user="statusClient", passwd="clientpw"):
564 base.StatusReceiverMultiService.__init__(self) 565 if type(port) is int: 566 port = "tcp:%d" % port 567 self.port = port 568 self.cred = (user, passwd) 569 p = portal.Portal(self) 570 c = checkers.InMemoryUsernamePasswordDatabaseDontUse() 571 c.addUser(user, passwd) 572 p.registerChecker(c) 573 f = pb.PBServerFactory(p) 574 s = strports.service(port, f) 575 s.setServiceParent(self)
576
577 - def setServiceParent(self, parent):
580
581 - def requestAvatar(self, avatarID, mind, interface):
582 assert interface == pb.IPerspective 583 p = StatusClientPerspective(self.status) 584 p.attached(mind) # perhaps .callLater(0) ? 585 return (pb.IPerspective, p, 586 lambda p=p,mind=mind: p.detached(mind))
587