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