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