Merge lp:~cjwatson/launchpad/buildmaster-twisted-agent into lp:launchpad
- buildmaster-twisted-agent
- Merge into devel
Proposed by
Colin Watson
Status: | Merged |
---|---|
Merged at revision: | 18068 |
Proposed branch: | lp:~cjwatson/launchpad/buildmaster-twisted-agent |
Merge into: | lp:launchpad |
Diff against target: |
363 lines (+189/-20) 5 files modified
daemons/buildd-manager.tac (+13/-4) lib/lp/buildmaster/interactor.py (+112/-10) lib/lp/buildmaster/tests/mock_slaves.py (+3/-3) lib/lp/buildmaster/tests/test_interactor.py (+53/-3) lib/lp/services/config/schema-lazr.conf (+8/-0) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/buildmaster-twisted-agent |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Celso Providelo (community) | Approve | ||
Review via email: mp+295593@code.launchpad.net |
Commit message
Convert BuilderSlave to twisted.
Description of the change
Convert BuilderSlave to twisted.
We'll need to give this a good workout on dogfood.
To post a comment you must log in.
Revision history for this message
William Grant (wgrant) : | # |
review:
Approve
(code)
Revision history for this message
Colin Watson (cjwatson) : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'daemons/buildd-manager.tac' | |||
2 | --- daemons/buildd-manager.tac 2011-12-29 05:29:36 +0000 | |||
3 | +++ daemons/buildd-manager.tac 2016-05-25 15:34:01 +0000 | |||
4 | @@ -1,14 +1,18 @@ | |||
6 | 1 | # Copyright 2009-2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2016 Canonical Ltd. This software is licensed under the |
7 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
8 | 3 | 3 | ||
9 | 4 | # Twisted Application Configuration file. | 4 | # Twisted Application Configuration file. |
10 | 5 | # Use with "twistd2.4 -y <file.tac>", e.g. "twistd -noy server.tac" | 5 | # Use with "twistd2.4 -y <file.tac>", e.g. "twistd -noy server.tac" |
11 | 6 | 6 | ||
12 | 7 | import resource | ||
13 | 8 | |||
14 | 7 | from twisted.application import service | 9 | from twisted.application import service |
15 | 8 | from twisted.scripts.twistd import ServerOptions | 10 | from twisted.scripts.twistd import ServerOptions |
16 | 9 | from twisted.web import server | ||
17 | 10 | 11 | ||
19 | 11 | from lp.services.config import dbconfig | 12 | from lp.services.config import ( |
20 | 13 | config, | ||
21 | 14 | dbconfig, | ||
22 | 15 | ) | ||
23 | 12 | from lp.services.daemons import readyservice | 16 | from lp.services.daemons import readyservice |
24 | 13 | from lp.services.scripts import execute_zcml_for_scripts | 17 | from lp.services.scripts import execute_zcml_for_scripts |
25 | 14 | from lp.buildmaster.manager import BuilddManager | 18 | from lp.buildmaster.manager import BuilddManager |
26 | @@ -21,6 +25,12 @@ | |||
27 | 21 | # Should be removed from callsites verified to not need it. | 25 | # Should be removed from callsites verified to not need it. |
28 | 22 | set_immediate_mail_delivery(True) | 26 | set_immediate_mail_delivery(True) |
29 | 23 | 27 | ||
30 | 28 | # Allow generous slack for database connections, idle download connections, | ||
31 | 29 | # etc. | ||
32 | 30 | soft_nofile = config.builddmaster.download_connections + 1024 | ||
33 | 31 | _, hard_nofile = resource.getrlimit(resource.RLIMIT_NOFILE) | ||
34 | 32 | resource.setrlimit(resource.RLIMIT_NOFILE, (soft_nofile, hard_nofile)) | ||
35 | 33 | |||
36 | 24 | options = ServerOptions() | 34 | options = ServerOptions() |
37 | 25 | options.parseOptions() | 35 | options.parseOptions() |
38 | 26 | 36 | ||
39 | @@ -34,4 +44,3 @@ | |||
40 | 34 | # Service for scanning buildd slaves. | 44 | # Service for scanning buildd slaves. |
41 | 35 | service = BuilddManager() | 45 | service = BuilddManager() |
42 | 36 | service.setServiceParent(application) | 46 | service.setServiceParent(application) |
43 | 37 | |||
44 | 38 | 47 | ||
45 | === modified file 'lib/lp/buildmaster/interactor.py' | |||
46 | --- lib/lp/buildmaster/interactor.py 2015-02-17 05:38:37 +0000 | |||
47 | +++ lib/lp/buildmaster/interactor.py 2016-05-25 15:34:01 +0000 | |||
48 | @@ -1,4 +1,4 @@ | |||
50 | 1 | # Copyright 2009-2014 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2016 Canonical Ltd. This software is licensed under the |
51 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
52 | 3 | 3 | ||
53 | 4 | __metaclass__ = type | 4 | __metaclass__ = type |
54 | @@ -13,9 +13,17 @@ | |||
55 | 13 | from urlparse import urlparse | 13 | from urlparse import urlparse |
56 | 14 | 14 | ||
57 | 15 | import transaction | 15 | import transaction |
59 | 16 | from twisted.internet import defer | 16 | from twisted.internet import ( |
60 | 17 | defer, | ||
61 | 18 | reactor as default_reactor, | ||
62 | 19 | ) | ||
63 | 20 | from twisted.internet.protocol import Protocol | ||
64 | 17 | from twisted.web import xmlrpc | 21 | from twisted.web import xmlrpc |
66 | 18 | from twisted.web.client import downloadPage | 22 | from twisted.web.client import ( |
67 | 23 | Agent, | ||
68 | 24 | HTTPConnectionPool, | ||
69 | 25 | ResponseDone, | ||
70 | 26 | ) | ||
71 | 19 | from zope.security.proxy import ( | 27 | from zope.security.proxy import ( |
72 | 20 | isinstance as zope_isinstance, | 28 | isinstance as zope_isinstance, |
73 | 21 | removeSecurityProxy, | 29 | removeSecurityProxy, |
74 | @@ -46,6 +54,89 @@ | |||
75 | 46 | noisy = False | 54 | noisy = False |
76 | 47 | 55 | ||
77 | 48 | 56 | ||
78 | 57 | class FileWritingProtocol(Protocol): | ||
79 | 58 | """A protocol that saves data to a file.""" | ||
80 | 59 | |||
81 | 60 | def __init__(self, finished, filename): | ||
82 | 61 | self.finished = finished | ||
83 | 62 | self.filename = filename | ||
84 | 63 | self.file = None | ||
85 | 64 | |||
86 | 65 | def dataReceived(self, data): | ||
87 | 66 | if self.file is None: | ||
88 | 67 | self.file = open(self.filename, "wb") | ||
89 | 68 | try: | ||
90 | 69 | self.file.write(data) | ||
91 | 70 | except IOError: | ||
92 | 71 | try: | ||
93 | 72 | self.file.close() | ||
94 | 73 | except IOError: | ||
95 | 74 | pass | ||
96 | 75 | self.file = None | ||
97 | 76 | self.finished.errback() | ||
98 | 77 | |||
99 | 78 | def connectionLost(self, reason): | ||
100 | 79 | try: | ||
101 | 80 | self.file.close() | ||
102 | 81 | except IOError: | ||
103 | 82 | self.finished.errback() | ||
104 | 83 | else: | ||
105 | 84 | if reason.check(ResponseDone): | ||
106 | 85 | self.finished.callback(None) | ||
107 | 86 | else: | ||
108 | 87 | self.finished.errback(reason) | ||
109 | 88 | |||
110 | 89 | |||
111 | 90 | class LimitedHTTPConnectionPool(HTTPConnectionPool): | ||
112 | 91 | """A connection pool with an upper limit on open connections.""" | ||
113 | 92 | |||
114 | 93 | # XXX cjwatson 2016-05-25: This actually only limits active connections, | ||
115 | 94 | # and doesn't count idle but open connections towards the limit; this is | ||
116 | 95 | # because it's very difficult to do the latter with HTTPConnectionPool's | ||
117 | 96 | # current design. Users of this pool must therefore expect some | ||
118 | 97 | # additional file descriptors to be open for idle connections. | ||
119 | 98 | |||
120 | 99 | def __init__(self, reactor, limit, persistent=True): | ||
121 | 100 | super(LimitedHTTPConnectionPool, self).__init__( | ||
122 | 101 | reactor, persistent=persistent) | ||
123 | 102 | self._semaphore = defer.DeferredSemaphore(limit) | ||
124 | 103 | |||
125 | 104 | def getConnection(self, key, endpoint): | ||
126 | 105 | d = self._semaphore.acquire() | ||
127 | 106 | d.addCallback( | ||
128 | 107 | lambda _: super(LimitedHTTPConnectionPool, self).getConnection( | ||
129 | 108 | key, endpoint)) | ||
130 | 109 | return d | ||
131 | 110 | |||
132 | 111 | def _putConnection(self, key, connection): | ||
133 | 112 | super(LimitedHTTPConnectionPool, self)._putConnection(key, connection) | ||
134 | 113 | # Only release the semaphore in the next main loop iteration; if we | ||
135 | 114 | # release it here then the next request may start using this | ||
136 | 115 | # connection's parser before this request has quite finished with | ||
137 | 116 | # it. | ||
138 | 117 | self._reactor.callLater(0, self._semaphore.release) | ||
139 | 118 | |||
140 | 119 | |||
141 | 120 | _default_pool = None | ||
142 | 121 | |||
143 | 122 | |||
144 | 123 | def default_pool(reactor=None): | ||
145 | 124 | global _default_pool | ||
146 | 125 | if reactor is None: | ||
147 | 126 | reactor = default_reactor | ||
148 | 127 | if _default_pool is None: | ||
149 | 128 | # Circular import. | ||
150 | 129 | from lp.buildmaster.manager import SlaveScanner | ||
151 | 130 | # Short cached connection timeout to avoid potential weirdness with | ||
152 | 131 | # virtual builders that reboot frequently. | ||
153 | 132 | _default_pool = LimitedHTTPConnectionPool( | ||
154 | 133 | reactor, config.builddmaster.download_connections) | ||
155 | 134 | _default_pool.maxPersistentPerHost = ( | ||
156 | 135 | config.builddmaster.idle_download_connections_per_builder) | ||
157 | 136 | _default_pool.cachedConnectionTimeout = SlaveScanner.SCAN_INTERVAL | ||
158 | 137 | return _default_pool | ||
159 | 138 | |||
160 | 139 | |||
161 | 49 | class BuilderSlave(object): | 140 | class BuilderSlave(object): |
162 | 50 | """Add in a few useful methods for the XMLRPC slave. | 141 | """Add in a few useful methods for the XMLRPC slave. |
163 | 51 | 142 | ||
164 | @@ -58,7 +149,7 @@ | |||
165 | 58 | # many false positives in your test run and will most likely break | 149 | # many false positives in your test run and will most likely break |
166 | 59 | # production. | 150 | # production. |
167 | 60 | 151 | ||
169 | 61 | def __init__(self, proxy, builder_url, vm_host, timeout, reactor): | 152 | def __init__(self, proxy, builder_url, vm_host, timeout, reactor, pool): |
170 | 62 | """Initialize a BuilderSlave. | 153 | """Initialize a BuilderSlave. |
171 | 63 | 154 | ||
172 | 64 | :param proxy: An XML-RPC proxy, implementing 'callRemote'. It must | 155 | :param proxy: An XML-RPC proxy, implementing 'callRemote'. It must |
173 | @@ -71,11 +162,16 @@ | |||
174 | 71 | self._file_cache_url = urlappend(builder_url, 'filecache') | 162 | self._file_cache_url = urlappend(builder_url, 'filecache') |
175 | 72 | self._server = proxy | 163 | self._server = proxy |
176 | 73 | self.timeout = timeout | 164 | self.timeout = timeout |
177 | 165 | if reactor is None: | ||
178 | 166 | reactor = default_reactor | ||
179 | 74 | self.reactor = reactor | 167 | self.reactor = reactor |
180 | 168 | if pool is None: | ||
181 | 169 | pool = default_pool(reactor=reactor) | ||
182 | 170 | self.pool = pool | ||
183 | 75 | 171 | ||
184 | 76 | @classmethod | 172 | @classmethod |
185 | 77 | def makeBuilderSlave(cls, builder_url, vm_host, timeout, reactor=None, | 173 | def makeBuilderSlave(cls, builder_url, vm_host, timeout, reactor=None, |
187 | 78 | proxy=None): | 174 | proxy=None, pool=None): |
188 | 79 | """Create and return a `BuilderSlave`. | 175 | """Create and return a `BuilderSlave`. |
189 | 80 | 176 | ||
190 | 81 | :param builder_url: The URL of the slave buildd machine, | 177 | :param builder_url: The URL of the slave buildd machine, |
191 | @@ -84,6 +180,7 @@ | |||
192 | 84 | here. | 180 | here. |
193 | 85 | :param reactor: Used by tests to override the Twisted reactor. | 181 | :param reactor: Used by tests to override the Twisted reactor. |
194 | 86 | :param proxy: Used By tests to override the xmlrpc.Proxy. | 182 | :param proxy: Used By tests to override the xmlrpc.Proxy. |
195 | 183 | :param pool: Used by tests to override the HTTPConnectionPool. | ||
196 | 87 | """ | 184 | """ |
197 | 88 | rpc_url = urlappend(builder_url.encode('utf-8'), 'rpc') | 185 | rpc_url = urlappend(builder_url.encode('utf-8'), 'rpc') |
198 | 89 | if proxy is None: | 186 | if proxy is None: |
199 | @@ -92,7 +189,7 @@ | |||
200 | 92 | server_proxy.queryFactory = QuietQueryFactory | 189 | server_proxy.queryFactory = QuietQueryFactory |
201 | 93 | else: | 190 | else: |
202 | 94 | server_proxy = proxy | 191 | server_proxy = proxy |
204 | 95 | return cls(server_proxy, builder_url, vm_host, timeout, reactor) | 192 | return cls(server_proxy, builder_url, vm_host, timeout, reactor, pool) |
205 | 96 | 193 | ||
206 | 97 | def _with_timeout(self, d, timeout=None): | 194 | def _with_timeout(self, d, timeout=None): |
207 | 98 | return cancel_on_timeout(d, timeout or self.timeout, self.reactor) | 195 | return cancel_on_timeout(d, timeout or self.timeout, self.reactor) |
208 | @@ -138,10 +235,15 @@ | |||
209 | 138 | errback with the error string. | 235 | errback with the error string. |
210 | 139 | """ | 236 | """ |
211 | 140 | file_url = urlappend(self._file_cache_url, sha_sum).encode('utf8') | 237 | file_url = urlappend(self._file_cache_url, sha_sum).encode('utf8') |
216 | 141 | # If desired we can pass a param "timeout" here but let's leave | 238 | d = Agent(self.reactor, pool=self.pool).request("GET", file_url) |
217 | 142 | # it at the default value if it becomes obvious we need to | 239 | |
218 | 143 | # change it. | 240 | def got_response(response): |
219 | 144 | return downloadPage(file_url, file_to_write, followRedirect=0) | 241 | finished = defer.Deferred() |
220 | 242 | response.deliverBody(FileWritingProtocol(finished, file_to_write)) | ||
221 | 243 | return finished | ||
222 | 244 | |||
223 | 245 | d.addCallback(got_response) | ||
224 | 246 | return d | ||
225 | 145 | 247 | ||
226 | 146 | def getFiles(self, files): | 248 | def getFiles(self, files): |
227 | 147 | """Fetch many files from the builder. | 249 | """Fetch many files from the builder. |
228 | 148 | 250 | ||
229 | === modified file 'lib/lp/buildmaster/tests/mock_slaves.py' | |||
230 | --- lib/lp/buildmaster/tests/mock_slaves.py 2015-09-28 17:38:45 +0000 | |||
231 | +++ lib/lp/buildmaster/tests/mock_slaves.py 2016-05-25 15:34:01 +0000 | |||
232 | @@ -1,4 +1,4 @@ | |||
234 | 1 | # Copyright 2009-2015 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2016 Canonical Ltd. This software is licensed under the |
235 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
236 | 3 | 3 | ||
237 | 4 | """Mock Build objects for tests soyuz buildd-system.""" | 4 | """Mock Build objects for tests soyuz buildd-system.""" |
238 | @@ -290,14 +290,14 @@ | |||
239 | 290 | lambda: open(tachandler.logfile, 'r').readlines())) | 290 | lambda: open(tachandler.logfile, 'r').readlines())) |
240 | 291 | return tachandler | 291 | return tachandler |
241 | 292 | 292 | ||
243 | 293 | def getClientSlave(self, reactor=None, proxy=None): | 293 | def getClientSlave(self, reactor=None, proxy=None, pool=None): |
244 | 294 | """Return a `BuilderSlave` for use in testing. | 294 | """Return a `BuilderSlave` for use in testing. |
245 | 295 | 295 | ||
246 | 296 | Points to a fixed URL that is also used by `BuilddSlaveTestSetup`. | 296 | Points to a fixed URL that is also used by `BuilddSlaveTestSetup`. |
247 | 297 | """ | 297 | """ |
248 | 298 | return BuilderSlave.makeBuilderSlave( | 298 | return BuilderSlave.makeBuilderSlave( |
249 | 299 | self.BASE_URL, 'vmhost', config.builddmaster.socket_timeout, | 299 | self.BASE_URL, 'vmhost', config.builddmaster.socket_timeout, |
251 | 300 | reactor, proxy) | 300 | reactor=reactor, proxy=proxy, pool=pool) |
252 | 301 | 301 | ||
253 | 302 | def makeCacheFile(self, tachandler, filename): | 302 | def makeCacheFile(self, tachandler, filename): |
254 | 303 | """Make a cache file available on the remote slave. | 303 | """Make a cache file available on the remote slave. |
255 | 304 | 304 | ||
256 | === modified file 'lib/lp/buildmaster/tests/test_interactor.py' | |||
257 | --- lib/lp/buildmaster/tests/test_interactor.py 2015-11-04 14:30:09 +0000 | |||
258 | +++ lib/lp/buildmaster/tests/test_interactor.py 2016-05-25 15:34:01 +0000 | |||
259 | @@ -1,4 +1,4 @@ | |||
261 | 1 | # Copyright 2009-2015 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2016 Canonical Ltd. This software is licensed under the |
262 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
263 | 3 | 3 | ||
264 | 4 | """Test BuilderInteractor features.""" | 4 | """Test BuilderInteractor features.""" |
265 | @@ -20,9 +20,16 @@ | |||
266 | 20 | AsynchronousDeferredRunTestForBrokenTwisted, | 20 | AsynchronousDeferredRunTestForBrokenTwisted, |
267 | 21 | SynchronousDeferredRunTest, | 21 | SynchronousDeferredRunTest, |
268 | 22 | ) | 22 | ) |
270 | 23 | from testtools.matchers import ContainsAll | 23 | from testtools.matchers import ( |
271 | 24 | ContainsAll, | ||
272 | 25 | HasLength, | ||
273 | 26 | MatchesDict, | ||
274 | 27 | ) | ||
275 | 24 | from testtools.testcase import ExpectedException | 28 | from testtools.testcase import ExpectedException |
277 | 25 | from twisted.internet import defer | 29 | from twisted.internet import ( |
278 | 30 | defer, | ||
279 | 31 | reactor as default_reactor, | ||
280 | 32 | ) | ||
281 | 26 | from twisted.internet.task import Clock | 33 | from twisted.internet.task import Clock |
282 | 27 | from twisted.python.failure import Failure | 34 | from twisted.python.failure import Failure |
283 | 28 | from twisted.web.client import getPage | 35 | from twisted.web.client import getPage |
284 | @@ -38,6 +45,7 @@ | |||
285 | 38 | BuilderInteractor, | 45 | BuilderInteractor, |
286 | 39 | BuilderSlave, | 46 | BuilderSlave, |
287 | 40 | extract_vitals_from_db, | 47 | extract_vitals_from_db, |
288 | 48 | LimitedHTTPConnectionPool, | ||
289 | 41 | ) | 49 | ) |
290 | 42 | from lp.buildmaster.interfaces.builder import ( | 50 | from lp.buildmaster.interfaces.builder import ( |
291 | 43 | BuildDaemonIsolationError, | 51 | BuildDaemonIsolationError, |
292 | @@ -789,6 +797,7 @@ | |||
293 | 789 | for sha1, local_file in files: | 797 | for sha1, local_file in files: |
294 | 790 | with open(local_file) as f: | 798 | with open(local_file) as f: |
295 | 791 | self.assertEqual(content_map[sha1], f.read()) | 799 | self.assertEqual(content_map[sha1], f.read()) |
296 | 800 | return slave.pool.closeCachedConnections() | ||
297 | 792 | 801 | ||
298 | 793 | def finished_uploading(ignored): | 802 | def finished_uploading(ignored): |
299 | 794 | d = slave.getFiles(files) | 803 | d = slave.getFiles(files) |
300 | @@ -812,3 +821,44 @@ | |||
301 | 812 | dl.append(d) | 821 | dl.append(d) |
302 | 813 | 822 | ||
303 | 814 | return defer.DeferredList(dl).addCallback(finished_uploading) | 823 | return defer.DeferredList(dl).addCallback(finished_uploading) |
304 | 824 | |||
305 | 825 | def test_getFiles_open_connections(self): | ||
306 | 826 | # getFiles honours the configured limit on active download | ||
307 | 827 | # connections. | ||
308 | 828 | pool = LimitedHTTPConnectionPool(default_reactor, 2) | ||
309 | 829 | contents = [self.factory.getUniqueString() for _ in range(10)] | ||
310 | 830 | self.slave_helper.getServerSlave() | ||
311 | 831 | slave = self.slave_helper.getClientSlave(pool=pool) | ||
312 | 832 | files = [] | ||
313 | 833 | content_map = {} | ||
314 | 834 | |||
315 | 835 | def got_files(ignored): | ||
316 | 836 | # Called back when getFiles finishes. Make sure all the | ||
317 | 837 | # content is as expected. | ||
318 | 838 | for sha1, local_file in files: | ||
319 | 839 | with open(local_file) as f: | ||
320 | 840 | self.assertEqual(content_map[sha1], f.read()) | ||
321 | 841 | # Only two connections were used. | ||
322 | 842 | self.assertThat( | ||
323 | 843 | slave.pool._connections, | ||
324 | 844 | MatchesDict({("http", "localhost", 8221): HasLength(2)})) | ||
325 | 845 | return slave.pool.closeCachedConnections() | ||
326 | 846 | |||
327 | 847 | def finished_uploading(ignored): | ||
328 | 848 | d = slave.getFiles(files) | ||
329 | 849 | return d.addCallback(got_files) | ||
330 | 850 | |||
331 | 851 | # Set up some files on the builder and store details in | ||
332 | 852 | # content_map so we can compare downloads later. | ||
333 | 853 | dl = [] | ||
334 | 854 | for content in contents: | ||
335 | 855 | filename = content + '.txt' | ||
336 | 856 | lf = self.factory.makeLibraryFileAlias(filename, content=content) | ||
337 | 857 | content_map[lf.content.sha1] = content | ||
338 | 858 | files.append((lf.content.sha1, tempfile.mkstemp()[1])) | ||
339 | 859 | self.addCleanup(os.remove, files[-1][1]) | ||
340 | 860 | self.layer.txn.commit() | ||
341 | 861 | d = slave.ensurepresent(lf.content.sha1, lf.http_url, "", "") | ||
342 | 862 | dl.append(d) | ||
343 | 863 | |||
344 | 864 | return defer.DeferredList(dl).addCallback(finished_uploading) | ||
345 | 815 | 865 | ||
346 | === modified file 'lib/lp/services/config/schema-lazr.conf' | |||
347 | --- lib/lp/services/config/schema-lazr.conf 2016-05-18 03:59:44 +0000 | |||
348 | +++ lib/lp/services/config/schema-lazr.conf 2016-05-25 15:34:01 +0000 | |||
349 | @@ -72,6 +72,14 @@ | |||
350 | 72 | # datatype: integer | 72 | # datatype: integer |
351 | 73 | virtualized_socket_timeout: 30 | 73 | virtualized_socket_timeout: 30 |
352 | 74 | 74 | ||
353 | 75 | # The maximum number of idle file download connections per builder that | ||
354 | 76 | # may be kept open. | ||
355 | 77 | idle_download_connections_per_builder: 10 | ||
356 | 78 | |||
357 | 79 | # The maximum number of file download connections that may be open | ||
358 | 80 | # across all builders. | ||
359 | 81 | download_connections: 2048 | ||
360 | 82 | |||
361 | 75 | # Activate the Build Notification system. | 83 | # Activate the Build Notification system. |
362 | 76 | # datatype: boolean | 84 | # datatype: boolean |
363 | 77 | send_build_notification: True | 85 | send_build_notification: True |
Thanks Colin, great solution!
From http:// twistedmatrix. com/documents/ 15.2.0/ web/howto/ client. html#multiple- connections- to-the- same-server it seems that it will sustain at most 2 simultaneous downloads from each slave. Is that reasonable and sufficient ?