PageRenderTime 54ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/master/buildbot/test/integration/test_worker_comm.py

https://gitlab.com/murder187ss/buildbot
Python | 327 lines | 224 code | 48 blank | 55 comment | 9 complexity | 8d271e9a3f5e7ee2dc0c301347b7511e MD5 | raw file
  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. import mock
  16. from twisted.cred import credentials
  17. from twisted.internet import defer
  18. from twisted.internet import reactor
  19. from twisted.python import log
  20. from twisted.spread import pb
  21. from twisted.trial import unittest
  22. import buildbot
  23. from buildbot import config
  24. from buildbot import pbmanager
  25. from buildbot import worker
  26. from buildbot.process import botmaster
  27. from buildbot.process import builder
  28. from buildbot.process import factory
  29. from buildbot.status import master
  30. from buildbot.test.fake import fakemaster
  31. from buildbot.test.util.decorators import flaky
  32. from buildbot.util.eventual import eventually
  33. from buildbot.worker import manager as workermanager
  34. class FakeWorkerForBuilder(pb.Referenceable):
  35. """
  36. Fake worker-side WorkerForBuilder object
  37. """
  38. class FakeWorkerWorker(pb.Referenceable):
  39. """
  40. Fake worker-side Worker object
  41. @ivar master_persp: remote perspective on the master
  42. """
  43. def __init__(self, callWhenBuilderListSet):
  44. self.callWhenBuilderListSet = callWhenBuilderListSet
  45. self.master_persp = None
  46. self._detach_deferreds = []
  47. self._detached = False
  48. def waitForDetach(self):
  49. if self._detached:
  50. return defer.succeed(None)
  51. else:
  52. d = defer.Deferred()
  53. self._detach_deferreds.append(d)
  54. return d
  55. def setMasterPerspective(self, persp):
  56. self.master_persp = persp
  57. # clear out master_persp on disconnect
  58. def clear_persp():
  59. self.master_persp = None
  60. persp.broker.notifyOnDisconnect(clear_persp)
  61. def fire_deferreds():
  62. self._detached = True
  63. self._detach_deferreds, deferreds = None, self._detach_deferreds
  64. for d in deferreds:
  65. d.callback(None)
  66. persp.broker.notifyOnDisconnect(fire_deferreds)
  67. def remote_print(self, message):
  68. log.msg("WORKER-SIDE: remote_print(%r)" % (message,))
  69. def remote_getSlaveInfo(self):
  70. return {'info': 'here'}
  71. def remote_getVersion(self):
  72. return buildbot.version
  73. def remote_getCommands(self):
  74. return {'x': 1}
  75. def remote_setBuilderList(self, builder_info):
  76. builder_names = [n for n, dir in builder_info]
  77. slbuilders = [FakeWorkerForBuilder() for n in builder_names]
  78. eventually(self.callWhenBuilderListSet)
  79. return dict(zip(builder_names, slbuilders))
  80. class FakeBuilder(builder.Builder):
  81. def __init__(self, name):
  82. builder.Builder.__init__(self, name)
  83. self.builder_status = mock.Mock()
  84. def attached(self, worker, commands):
  85. assert commands == {'x': 1}
  86. return defer.succeed(None)
  87. def detached(self, worker):
  88. pass
  89. def getOldestRequestTime(self):
  90. return 0
  91. def maybeStartBuild(self):
  92. return defer.succeed(None)
  93. class MyWorker(worker.Worker):
  94. def attached(self, conn):
  95. self.detach_d = defer.Deferred()
  96. return worker.Worker.attached(self, conn)
  97. def detached(self):
  98. worker.Worker.detached(self)
  99. self.detach_d, d = None, self.detach_d
  100. d.callback(None)
  101. class TestWorkerComm(unittest.TestCase):
  102. """
  103. Test handling of connections from workers as integrated with
  104. - Twisted Spread
  105. - real TCP connections.
  106. - PBManager
  107. @ivar master: fake build master
  108. @ivar pbamanger: L{PBManager} instance
  109. @ivar botmaster: L{BotMaster} instance
  110. @ivar worker: master-side L{Worker} instance
  111. @ivar workerworker: worker-side L{FakeWorkerWorker} instance
  112. @ivar port: TCP port to connect to
  113. @ivar connector: outbound TCP connection from worker to master
  114. """
  115. @defer.inlineCallbacks
  116. def setUp(self):
  117. self.master = fakemaster.make_master(testcase=self, wantMq=True,
  118. wantData=True, wantDb=True)
  119. # set the worker port to a loopback address with unspecified
  120. # port
  121. self.pbmanager = self.master.pbmanager = pbmanager.PBManager()
  122. self.pbmanager.setServiceParent(self.master)
  123. # remove the fakeServiceParent from fake service hierarchy, and replace by a real one
  124. yield self.master.workers.disownServiceParent()
  125. self.workers = self.master.workers = workermanager.WorkerManager(self.master)
  126. self.workers.setServiceParent(self.master)
  127. self.botmaster = botmaster.BotMaster()
  128. self.botmaster.setServiceParent(self.master)
  129. self.master.status = master.Status()
  130. self.master.status.setServiceParent(self.master)
  131. self.master.botmaster = self.botmaster
  132. self.master.data.updates.workerConfigured = lambda *a, **k: None
  133. yield self.master.startService()
  134. self.buildworker = None
  135. self.port = None
  136. self.workerworker = None
  137. self.connector = None
  138. self._detach_deferreds = []
  139. # patch in our FakeBuilder for the regular Builder class
  140. self.patch(botmaster, 'Builder', FakeBuilder)
  141. def tearDown(self):
  142. if self.connector:
  143. self.connector.disconnect()
  144. deferreds = self._detach_deferreds + [
  145. self.pbmanager.stopService(),
  146. self.botmaster.stopService(),
  147. self.workers.stopService(),
  148. ]
  149. # if the worker is still attached, wait for it to detach, too
  150. if self.buildworker and self.buildworker.detach_d:
  151. deferreds.append(self.buildworker.detach_d)
  152. return defer.gatherResults(deferreds)
  153. @defer.inlineCallbacks
  154. def addWorker(self, **kwargs):
  155. """
  156. Create a master-side worker instance and add it to the BotMaster
  157. @param **kwargs: arguments to pass to the L{Worker} constructor.
  158. """
  159. self.buildworker = MyWorker("testworker", "pw", **kwargs)
  160. # reconfig the master to get it set up
  161. new_config = self.master.config
  162. new_config.protocols = {"pb": {"port": "tcp:0:interface=127.0.0.1"}}
  163. new_config.workers = [self.buildworker]
  164. new_config.builders = [config.BuilderConfig(name='bldr',
  165. workername='testworker', factory=factory.BuildFactory())]
  166. yield self.botmaster.reconfigServiceWithBuildbotConfig(new_config)
  167. yield self.workers.reconfigServiceWithBuildbotConfig(new_config)
  168. # as part of the reconfig, the worker registered with the pbmanager, so
  169. # get the port it was assigned
  170. self.port = self.buildworker.registration.getPBPort()
  171. def connectWorker(self, waitForBuilderList=True):
  172. """
  173. Connect a worker the master via PB
  174. @param waitForBuilderList: don't return until the setBuilderList has
  175. been called
  176. @returns: L{FakeWorkerWorker} and a Deferred that will fire when it
  177. is detached; via deferred
  178. """
  179. factory = pb.PBClientFactory()
  180. creds = credentials.UsernamePassword("testworker", "pw")
  181. setBuilderList_d = defer.Deferred()
  182. workerworker = FakeWorkerWorker(
  183. lambda: setBuilderList_d.callback(None))
  184. login_d = factory.login(creds, workerworker)
  185. @login_d.addCallback
  186. def logged_in(persp):
  187. workerworker.setMasterPerspective(persp)
  188. # set up to hear when the worker side disconnects
  189. workerworker.detach_d = defer.Deferred()
  190. persp.broker.notifyOnDisconnect(lambda:
  191. workerworker.detach_d.callback(None))
  192. self._detach_deferreds.append(workerworker.detach_d)
  193. return workerworker
  194. self.connector = reactor.connectTCP("127.0.0.1", self.port, factory)
  195. if not waitForBuilderList:
  196. return login_d
  197. else:
  198. d = defer.DeferredList([login_d, setBuilderList_d],
  199. consumeErrors=True, fireOnOneErrback=True)
  200. d.addCallback(lambda _: workerworker)
  201. return d
  202. def workerSideDisconnect(self, worker):
  203. """Disconnect from the worker side"""
  204. worker.master_persp.broker.transport.loseConnection()
  205. @defer.inlineCallbacks
  206. def test_connect_disconnect(self):
  207. """Test a single worker connecting and disconnecting."""
  208. yield self.addWorker()
  209. # connect
  210. worker = yield self.connectWorker()
  211. # disconnect
  212. self.workerSideDisconnect(worker)
  213. # wait for the resulting detach
  214. yield worker.waitForDetach()
  215. @flaky(bugNumber=2761)
  216. @defer.inlineCallbacks
  217. def test_duplicate_worker(self):
  218. yield self.addWorker()
  219. # connect first worker
  220. worker1 = yield self.connectWorker()
  221. # connect second worker; this should fail
  222. try:
  223. yield self.connectWorker(waitForBuilderList=False)
  224. connect_failed = False
  225. except Exception:
  226. connect_failed = True
  227. self.assertTrue(connect_failed)
  228. # disconnect both and wait for that to percolate
  229. self.workerSideDisconnect(worker1)
  230. yield worker1.waitForDetach()
  231. # flush the exception logged for this on the master
  232. self.assertEqual(len(self.flushLoggedErrors(RuntimeError)), 1)
  233. @defer.inlineCallbacks
  234. def test_duplicate_worker_old_dead(self):
  235. yield self.addWorker()
  236. # connect first worker
  237. worker1 = yield self.connectWorker()
  238. # monkeypatch that worker to fail with PBConnectionLost when its
  239. # remote_print method is called
  240. def remote_print(message):
  241. worker1.master_persp.broker.transport.loseConnection()
  242. raise pb.PBConnectionLost("fake!")
  243. worker1.remote_print = remote_print
  244. # connect second worker; this should succeed, and the old worker
  245. # should be disconnected.
  246. worker2 = yield self.connectWorker()
  247. # disconnect both and wait for that to percolate
  248. self.workerSideDisconnect(worker2)
  249. yield worker1.waitForDetach()
  250. # flush the exception logged for this on the worker
  251. self.assertEqual(len(self.flushLoggedErrors(pb.PBConnectionLost)), 1)