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
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, logfile.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 d = self.status.getBuildSets() 431 def make_remotes(buildsets): 432 return [(IRemote(s), s.id) for s in buildsets]
433 d.addCallback(make_remotes) 434 return d
435
436 - def perspective_getBuilderNames(self):
437 return self.status.getBuilderNames()
438
439 - def perspective_getBuilder(self, name):
440 b = self.status.getBuilder(name) 441 return IRemote(b)
442
443 - def perspective_getSlave(self, name):
444 s = self.status.getSlave(name) 445 return IRemote(s)
446
447 - def perspective_ping(self):
448 """Ping method to allow pb clients to validate their connections.""" 449 return "pong"
450 451 # IStatusReceiver methods, invoked if we've subscribed 452 453 # mode >= builder
454 - def builderAdded(self, name, builder):
455 self.client.callRemote("builderAdded", name, IRemote(builder)) 456 if self.subscribed in ("builds", "steps", "logs", "full"): 457 self.subscribed_to_builders.append(name) 458 return self 459 return None
460
461 - def builderChangedState(self, name, state):
462 self.client.callRemote("builderChangedState", name, state, None)
463 # TODO: remove leftover ETA argument 464
465 - def builderRemoved(self, name):
466 if name in self.subscribed_to_builders: 467 self.subscribed_to_builders.remove(name) 468 self.client.callRemote("builderRemoved", name)
469
470 - def buildsetSubmitted(self, buildset):
471 # TODO: deliver to client, somehow 472 pass
473 474 # mode >= builds
475 - def buildStarted(self, name, build):
476 self.client.callRemote("buildStarted", name, IRemote(build)) 477 if self.subscribed in ("steps", "logs", "full"): 478 self.subscribed_to.append(build) 479 return (self, self.interval) 480 return None
481
482 - def buildFinished(self, name, build, results):
483 if build in self.subscribed_to: 484 # we might have joined during the build 485 self.subscribed_to.remove(build) 486 self.client.callRemote("buildFinished", 487 name, IRemote(build), results)
488 489 # mode >= steps
490 - def buildETAUpdate(self, build, eta):
491 self.client.callRemote("buildETAUpdate", 492 build.getBuilder().getName(), IRemote(build), 493 eta)
494
495 - def stepStarted(self, build, step):
496 # we add some information here so the client doesn't have to do an 497 # extra round-trip 498 self.client.callRemote("stepStarted", 499 build.getBuilder().getName(), IRemote(build), 500 step.getName(), IRemote(step)) 501 if self.subscribed in ("logs", "full"): 502 self.subscribed_to.append(step) 503 return (self, self.interval) 504 return None
505
506 - def stepFinished(self, build, step, results):
507 self.client.callRemote("stepFinished", 508 build.getBuilder().getName(), IRemote(build), 509 step.getName(), IRemote(step), 510 results) 511 if step in self.subscribed_to: 512 # eventually (through some new subscription method) we could 513 # join in the middle of the step 514 self.subscribed_to.remove(step)
515 516 # mode >= logs
517 - def stepETAUpdate(self, build, step, ETA, expectations):
518 self.client.callRemote("stepETAUpdate", 519 build.getBuilder().getName(), IRemote(build), 520 step.getName(), IRemote(step), 521 ETA, expectations)
522
523 - def logStarted(self, build, step, log):
524 # TODO: make the HTMLLog adapter 525 rlog = IRemote(log, None) 526 if not rlog: 527 print "hey, couldn't adapt %s to IRemote" % log 528 self.client.callRemote("logStarted", 529 build.getBuilder().getName(), IRemote(build), 530 step.getName(), IRemote(step), 531 log.getName(), IRemote(log, None)) 532 if self.subscribed in ("full",): 533 self.subscribed_to.append(log) 534 return self 535 return None
536
537 - def logFinished(self, build, step, log):
538 self.client.callRemote("logFinished", 539 build.getBuilder().getName(), IRemote(build), 540 step.getName(), IRemote(step), 541 log.getName(), IRemote(log, None)) 542 if log in self.subscribed_to: 543 self.subscribed_to.remove(log)
544 545 # mode >= full
546 - def logChunk(self, build, step, log, channel, text):
547 self.client.callRemote("logChunk", 548 build.getBuilder().getName(), IRemote(build), 549 step.getName(), IRemote(step), 550 log.getName(), IRemote(log), 551 channel, text)
552 553
554 -class PBListener(base.StatusReceiverMultiService):
555 """I am a listener for PB-based status clients.""" 556 557 compare_attrs = ["port", "cred"] 558 implements(portal.IRealm) 559
560 - def __init__(self, port, user="statusClient", passwd="clientpw"):
561 base.StatusReceiverMultiService.__init__(self) 562 if type(port) is int: 563 port = "tcp:%d" % port 564 self.port = port 565 self.cred = (user, passwd) 566 p = portal.Portal(self) 567 c = checkers.InMemoryUsernamePasswordDatabaseDontUse() 568 c.addUser(user, passwd) 569 p.registerChecker(c) 570 f = pb.PBServerFactory(p) 571 s = strports.service(port, f) 572 s.setServiceParent(self)
573
574 - def setServiceParent(self, parent):
577
578 - def setup(self):
579 self.status = self.parent.getStatus()
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