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

Source Code for Module buildbot.status.client

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