Merge ~pappacena/launchpad:gunicorn-experiments into launchpad:master

Proposed by Thiago F. Pappacena
Status: Work in progress
Proposed branch: ~pappacena/launchpad:gunicorn-experiments
Merge into: launchpad:master
Diff against target: 770 lines (+109/-272)
21 files modified
configs/development/gunicorn.conf.py (+6/-0)
configs/development/launchpad-lazr.conf (+1/-0)
configs/development/launchpad.conf (+0/-51)
configs/test-playground/launchpad.conf (+0/-41)
configs/testrunner-appserver/gunicorn.conf.py (+12/-0)
configs/testrunner-appserver/launchpad.conf (+0/-20)
configs/testrunner/launchpad.conf (+0/-41)
dev/null (+0/-36)
lib/lp/scripts/runlaunchpad.py (+21/-1)
lib/lp/services/config/__init__.py (+4/-18)
lib/lp/services/config/doc/canonical-config.txt (+0/-3)
lib/lp/services/config/schema-lazr.conf (+6/-2)
lib/lp/services/config/tests/test_config.py (+5/-3)
lib/lp/services/webapp/configure.zcml (+0/-7)
lib/lp/services/webapp/doc/webapp-publication.txt (+1/-3)
lib/lp/services/webapp/publication.py (+2/-2)
lib/lp/services/webapp/servers.py (+3/-40)
lib/lp/startwsgi.py (+26/-0)
lib/lp/testing/layers.py (+18/-0)
setup.py (+4/-2)
zcml/webapp.zcml (+0/-2)
Reviewer Review Type Date Requested Status
Launchpad code reviewers Pending
Review via email: mp+396272@code.launchpad.net

Commit message

[DONT MERGE] Experiments to use gunicorn instead of Zope Server.

Description of the change

This MP should not be merged. Instead, we will pave a way to migrate to gunicorn slowly to avoid big production disruption. But anyway, the changes here work with most (all?) tests running successfully and the dev server running smoothly (including auto-reload on code changes).

To post a comment you must log in.
5fb6bd9... by Thiago F. Pappacena

Fixing port binding for brz acceptance tests

7927365... by Thiago F. Pappacena

Merge branch 'master' into gunicorn-experiments

40cd43d... by Thiago F. Pappacena

Pipe

b7c3579... by Thiago F. Pappacena

Merge branch 'master' into gunicorn-experiments

03d978a... by Thiago F. Pappacena

Avoiding block gunicorn subprocess due to output buffer not beign consumed

cca3c62... by Thiago F. Pappacena

Moving gunicorn config to its own file

8c5ac5f... by Thiago F. Pappacena

Fixing output buffer consumption on app server layer

6aef0db... by Thiago F. Pappacena

Merge branch 'master' into gunicorn-experiments

f066609... by Thiago F. Pappacena

Autoreload in dev

ce702df... by Thiago F. Pappacena

Using original HTTPPublicationRequestFactory on startwsgi

Unmerged commits

ce702df... by Thiago F. Pappacena

Using original HTTPPublicationRequestFactory on startwsgi

f066609... by Thiago F. Pappacena

Autoreload in dev

6aef0db... by Thiago F. Pappacena

Merge branch 'master' into gunicorn-experiments

8c5ac5f... by Thiago F. Pappacena

Fixing output buffer consumption on app server layer

cca3c62... by Thiago F. Pappacena

Moving gunicorn config to its own file

03d978a... by Thiago F. Pappacena

Avoiding block gunicorn subprocess due to output buffer not beign consumed

b7c3579... by Thiago F. Pappacena

Merge branch 'master' into gunicorn-experiments

40cd43d... by Thiago F. Pappacena

Pipe

7927365... by Thiago F. Pappacena

Merge branch 'master' into gunicorn-experiments

5fb6bd9... by Thiago F. Pappacena

Fixing port binding for brz acceptance tests

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/configs/development/gunicorn.conf.py b/configs/development/gunicorn.conf.py
2new file mode 100644
3index 0000000..b667109
4--- /dev/null
5+++ b/configs/development/gunicorn.conf.py
6@@ -0,0 +1,6 @@
7+bind = [":8085", ":8086", ":8087", ":8088", ":8089"]
8+workers = 2
9+threads = 10
10+max_requests = 1000
11+log_level = "DEBUG"
12+reload = True
13diff --git a/configs/development/launchpad-lazr.conf b/configs/development/launchpad-lazr.conf
14index a4956f3..e56bdd8 100644
15--- a/configs/development/launchpad-lazr.conf
16+++ b/configs/development/launchpad-lazr.conf
17@@ -223,6 +223,7 @@ hostname: xmlrpc.launchpad.test
18
19 [vhost.xmlrpc_private]
20 hostname: xmlrpc-private.launchpad.test
21+private_port: 8087
22
23 [vhost.feeds]
24 hostname: feeds.launchpad.test
25diff --git a/configs/development/launchpad.conf b/configs/development/launchpad.conf
26index fe7e088..8281e09 100644
27--- a/configs/development/launchpad.conf
28+++ b/configs/development/launchpad.conf
29@@ -5,52 +5,11 @@ site-definition zcml/webapp.zcml
30 # Turn on Zope3 developer mode.
31 devmode on
32
33-# number of bytecode instructions to execute between checks for
34-# interruptions (SIGINTR, thread switches):
35-interrupt-check-interval 200
36-
37-<server>
38- type HTTP
39- address 8085
40-</server>
41-
42-# For debugging purposes, you can use this publisher instead/as well
43-# (obviously if it's as well, use a different port number). If there's
44-# an exception, Zope will drop into pdb at the point of the exception.
45-<server>
46- type PostmortemDebuggingHTTP
47- address 8088
48-</server>
49-
50-<server>
51- type DebugLayerHTTP
52- address 8086
53-</server>
54-
55-<server>
56- type PrivateXMLRPC
57- address 8087
58-</server>
59-
60 # non-persistent in-memory storage
61 <zodb>
62 <mappingstorage/>
63 </zodb>
64
65-<accesslog>
66- # This sets up logging to both a file (access.log) and to standard
67- # output (STDOUT). The "path" setting can be a relative or absolute
68- # filesystem path or the tokens STDOUT or STDERR.
69-
70- <logfile>
71- path logs/launchpad-access.log
72- </logfile>
73-
74- <logfile>
75- path STDOUT
76- </logfile>
77-</accesslog>
78-
79 <eventlog>
80 # This sets up logging to both a file (z3.log) and to standard
81 # output (STDOUT). The "path" setting can be a relative or absolute
82@@ -65,13 +24,3 @@ interrupt-check-interval 200
83 </logfile>
84 </eventlog>
85
86-<logger>
87- name zc.tracelog
88- propagate false
89-
90- <logfile>
91- format %(message)s
92- path logs/trace.log
93- </logfile>
94-
95-</logger>
96diff --git a/configs/test-playground/launchpad.conf b/configs/test-playground/launchpad.conf
97index ec8095a..0ce913b 100644
98--- a/configs/test-playground/launchpad.conf
99+++ b/configs/test-playground/launchpad.conf
100@@ -5,52 +5,11 @@ site-definition zcml/webapp.zcml
101 # Turn on Zope3 developer mode.
102 devmode on
103
104-# number of bytecode instructions to execute between checks for
105-# interruptions (SIGINTR, thread switches):
106-interrupt-check-interval 200
107-
108-<server>
109- type HTTP
110- address 8085
111-</server>
112-
113-# For debugging purposes, you can use this publisher instead/as well
114-# (obviously if it's as well, use a different port number). If there's
115-# an exception, Zope will drop into pdb at the point of the exception.
116-<server>
117- type PostmortemDebuggingHTTP
118- address 8089
119-</server>
120-
121-<server>
122- type DebugLayerHTTP
123- address 8086
124-</server>
125-
126-<server>
127- type PrivateXMLRPC
128- address 8087
129-</server>
130-
131 # non-persistent in-memory storage
132 <zodb>
133 <mappingstorage/>
134 </zodb>
135
136-<accesslog>
137- # This sets up logging to both a file (access.log) and to standard
138- # output (STDOUT). The "path" setting can be a relative or absolute
139- # filesystem path or the tokens STDOUT or STDERR.
140-
141- <logfile>
142- path logs/launchpad-access.log
143- </logfile>
144-
145- <logfile>
146- path STDOUT
147- </logfile>
148-</accesslog>
149-
150 <eventlog>
151 # This sets up logging to both a file (z3.log) and to standard
152 # output (STDOUT). The "path" setting can be a relative or absolute
153diff --git a/configs/testrunner-appserver/gunicorn.conf.py b/configs/testrunner-appserver/gunicorn.conf.py
154new file mode 100644
155index 0000000..ba22ec1
156--- /dev/null
157+++ b/configs/testrunner-appserver/gunicorn.conf.py
158@@ -0,0 +1,12 @@
159+import os
160+config_dir = os.path.dirname(__file__)
161+log_dir = os.path.join(config_dir, '..', '..', 'logs')
162+
163+bind = [":8085", ":8087"]
164+workers = 1
165+threads = 10
166+log_level = "DEBUG"
167+
168+log_file = os.path.join(log_dir, 'gunicorn.log')
169+error_logfile = os.path.join(log_dir, 'gunicorn-error.log')
170+access_logfile = os.path.join(log_dir, 'gunicorn-access.log')
171diff --git a/configs/testrunner-appserver/launchpad.conf b/configs/testrunner-appserver/launchpad.conf
172index 2f9ce03..dde05d8 100644
173--- a/configs/testrunner-appserver/launchpad.conf
174+++ b/configs/testrunner-appserver/launchpad.conf
175@@ -7,31 +7,11 @@ site-definition zcml/webapp.zcml
176 # Make this work a little more like production.
177 devmode off
178
179-# number of bytecode instructions to execute between checks for
180-# interruptions (SIGINTR, thread switches):
181-interrupt-check-interval 200
182-
183-<server>
184- type HTTP
185- address 8085
186-</server>
187-
188-<server>
189- type PrivateXMLRPC
190- address 8087
191-</server>
192-
193 # non-persistent in-memory storage
194 <zodb>
195 <mappingstorage/>
196 </zodb>
197
198-<accesslog>
199- <logfile>
200- path logs/test-appserver-layer.log
201- </logfile>
202-</accesslog>
203-
204 <eventlog>
205 <logfile>
206 path logs/test-appserver-layer.log
207diff --git a/configs/testrunner/launchpad.conf b/configs/testrunner/launchpad.conf
208index e1aee02..252fd3e 100644
209--- a/configs/testrunner/launchpad.conf
210+++ b/configs/testrunner/launchpad.conf
211@@ -5,52 +5,11 @@ site-definition zcml/webapp.zcml
212 # Turn on Zope3 developer mode.
213 devmode on
214
215-# number of bytecode instructions to execute between checks for
216-# interruptions (SIGINTR, thread switches):
217-interrupt-check-interval 200
218-
219-<server>
220- type HTTP
221- address 8085
222-</server>
223-
224-# For debugging purposes, you can use this publisher instead/as well
225-# (obviously if it's as well, use a different port number). If there's
226-# an exception, Zope will drop into pdb at the point of the exception.
227-<server>
228- type PostmortemDebuggingHTTP
229- address 8089
230-</server>
231-
232-<server>
233- type DebugLayerHTTP
234- address 8086
235-</server>
236-
237-<server>
238- type PrivateXMLRPC
239- address 8087
240-</server>
241-
242 # non-persistent in-memory storage
243 <zodb>
244 <mappingstorage/>
245 </zodb>
246
247-<accesslog>
248- # This sets up logging to both a file (access.log) and to standard
249- # output (STDOUT). The "path" setting can be a relative or absolute
250- # filesystem path or the tokens STDOUT or STDERR.
251-
252- <logfile>
253- path logs/launchpad-access.log
254- </logfile>
255-
256- <logfile>
257- path STDOUT
258- </logfile>
259-</accesslog>
260-
261 <eventlog>
262 # This sets up logging to both a file (z3.log) and to standard
263 # output (STDOUT). The "path" setting can be a relative or absolute
264diff --git a/lib/lp/scripts/runlaunchpad.py b/lib/lp/scripts/runlaunchpad.py
265index 969cfd6..42f2806 100644
266--- a/lib/lp/scripts/runlaunchpad.py
267+++ b/lib/lp/scripts/runlaunchpad.py
268@@ -6,6 +6,9 @@ from __future__ import absolute_import, print_function, unicode_literals
269 __metaclass__ = type
270 __all__ = ['start_launchpad']
271
272+import six
273+from talisker import run_gunicorn
274+
275 try:
276 from contextlib import ExitStack
277 except ImportError:
278@@ -16,10 +19,10 @@ import subprocess
279 import sys
280
281 import fixtures
282+from gunicorn.app.wsgiapp import WSGIApplication
283 from lazr.config import as_host_port
284 from rabbitfixture.server import RabbitServerResources
285 from testtools.testresult.real import _details_to_str
286-from zope.app.server.main import main
287
288 from lp.services.config import config
289 from lp.services.daemons import tachandler
290@@ -331,6 +334,23 @@ def start_testapp(argv=list(sys.argv)):
291 pass
292
293
294+def main(argv):
295+ orig_argv = sys.argv
296+ try:
297+ # This is a bit hacky, but talisker gets the arguments directly from
298+ # sys.argv. There is no way to send it using parameters. We set it
299+ # back as soon as run_gunicorn finishes.
300+ sys.argv = [
301+ os.path.join(config.root, "bin", "talisker.gunicorn"),
302+ "lp.startwsgi",
303+ "-c", os.path.join(config.config_dir, "gunicorn.conf.py")
304+ ]
305+ run_gunicorn()
306+ return
307+ finally:
308+ sys.argv = orig_argv
309+
310+
311 def start_launchpad(argv=list(sys.argv), setup=None):
312 # We really want to replace this with a generic startup harness.
313 # However, this should last us until this is developed
314diff --git a/lib/lp/services/config/__init__.py b/lib/lp/services/config/__init__.py
315index 9d43346..c7828a8 100644
316--- a/lib/lp/services/config/__init__.py
317+++ b/lib/lp/services/config/__init__.py
318@@ -30,6 +30,7 @@ from six.moves.urllib.parse import (
319 urlunparse,
320 )
321 import ZConfig
322+from zope.app.appsetup import appsetup
323
324 from lp.services.osutils import open_for_writing
325
326@@ -124,6 +125,7 @@ class LaunchpadConfig:
327 else:
328 self._process_name = process_name
329 self._instance_name = instance_name
330+ self._devmode = None
331 self.root = TREE_ROOT
332
333 def _make_process_name(self):
334@@ -165,7 +167,6 @@ class LaunchpadConfig:
335 """Invalidate the config, causing the config to be regenerated."""
336 self._config = None
337 self._devmode = None
338- self._servers = None
339
340 def reloadConfig(self):
341 """Reload the config."""
342@@ -246,15 +247,6 @@ class LaunchpadConfig:
343 """Return the path to the ZConfig file for this instance."""
344 return os.path.join(self.config_dir, 'launchpad.conf')
345
346- def _getZConfig(self):
347- """Modify the config, adding automatically generated settings"""
348- with resources.path('zope.app.server', 'schema.xml') as schemafile:
349- schema = ZConfig.loadSchema(str(schemafile))
350- root_options, handlers = ZConfig.loadConfig(
351- schema, self.zope_config_file)
352- self._devmode = root_options.devmode
353- self._servers = root_options.servers
354-
355 @property
356 def devmode(self):
357 """Devmode from the zope.app.server.main config.
358@@ -262,20 +254,14 @@ class LaunchpadConfig:
359 Copied here for ease of access.
360 """
361 if self._devmode is None:
362- self._getZConfig()
363+ self._getConfig()
364+ self._devmode = self._config.launchpad.devmode
365 return self._devmode
366
367 @devmode.setter
368 def devmode(self, value):
369 self._devmode = value
370
371- @property
372- def servers(self):
373- """The defined servers."""
374- if self._servers is None:
375- self._getZConfig()
376- return self._servers
377-
378 def generate_overrides(self):
379 """Ensure correct config.zcml overrides will be called.
380
381diff --git a/lib/lp/services/config/doc/canonical-config.txt b/lib/lp/services/config/doc/canonical-config.txt
382index 91f676b..7b70660 100644
383--- a/lib/lp/services/config/doc/canonical-config.txt
384+++ b/lib/lp/services/config/doc/canonical-config.txt
385@@ -86,9 +86,6 @@ ZConfig.
386 >>> config.devmode
387 True
388
389- >>> len(config.servers)
390- 4
391-
392
393 Working with test configurations
394 --------------------------------
395diff --git a/lib/lp/services/config/schema-lazr.conf b/lib/lp/services/config/schema-lazr.conf
396index aaa9d30..f4d9193 100644
397--- a/lib/lp/services/config/schema-lazr.conf
398+++ b/lib/lp/services/config/schema-lazr.conf
399@@ -539,7 +539,6 @@ storm_cache_size: 10000
400 # datatype: existing_directory
401 replication_logdir: database/replication
402
403-
404 [diff]
405 # The maximum size in bytes to read from the librarian to make available in
406 # the web UI. 512k == 524288 bytes.
407@@ -841,8 +840,9 @@ dbuser: karma
408 # datatype: integer
409 max_scaling: 500
410
411-
412 [launchpad]
413+devmode: true
414+
415 # A directory of files from which *-lazr.conf will be loaded and
416 # overlaid in order on top of the main config.
417 # Note that this is relative to the top-level config file, not
418@@ -1640,6 +1640,10 @@ althostnames: none
419 # datatype: string
420 rooturl: none
421
422+# Is this vhost enabled?
423+# Used to create the correct publishers
424+private_port: None
425+
426 [vhost.mainsite]
427 # Can the profile page act as a OpenID delegated identity?
428 # data-type: boolean
429diff --git a/lib/lp/services/config/tests/test_config.py b/lib/lp/services/config/tests/test_config.py
430index 34921fd..faf37a0 100644
431--- a/lib/lp/services/config/tests/test_config.py
432+++ b/lib/lp/services/config/tests/test_config.py
433@@ -25,6 +25,7 @@ from lazr.config.interfaces import ConfigErrors
434 import scandir
435 import testtools
436 import ZConfig
437+from zope.app.appsetup import appsetup
438
439 import lp.services.config
440 from lp.services.config.fixture import ConfigUseFixture
441@@ -33,8 +34,8 @@ from lp.services.config.fixture import ConfigUseFixture
442 EXCLUDED_CONFIGS = ['lpnet-template']
443
444 # Calculate some landmark paths.
445-with resources.path('zope.app.server', 'schema.xml') as schema_file:
446- schema = ZConfig.loadSchema(str(schema_file))
447+schema = os.path.join(
448+ os.path.dirname(appsetup.__file__), 'schema', 'schema.xml')
449
450 here = os.path.dirname(lp.services.config.__file__)
451 lazr_schema_file = os.path.join(here, 'schema-lazr.conf')
452@@ -42,7 +43,8 @@ lazr_schema_file = os.path.join(here, 'schema-lazr.conf')
453
454 def make_test(config_file, description):
455 def test_function():
456- root, handlers = ZConfig.loadConfig(schema, config_file)
457+ loaded_schema = ZConfig.loadSchema(schema)
458+ root, handlers = ZConfig.loadConfig(loaded_schema, config_file)
459 # Hack the config file name into test_function's __name__ so that the test
460 # -vv output is more informative. Unfortunately, FunctionTestCase's
461 # description argument doesn't do what we want.
462diff --git a/lib/lp/services/webapp/configure.zcml b/lib/lp/services/webapp/configure.zcml
463index 95f495d..6642ec3 100644
464--- a/lib/lp/services/webapp/configure.zcml
465+++ b/lib/lp/services/webapp/configure.zcml
466@@ -8,7 +8,6 @@
467 xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
468 i18n_domain="launchpad">
469
470- <include file="servers.zcml" />
471 <include file="errorlog.zcml" />
472
473 <browser:defaultView name="index.html" />
474@@ -354,12 +353,6 @@
475 handler="lp.services.webapp.sigdumpmem.setup_sigdumpmem"
476 />
477
478- <!-- Confirm that no main thread event handlers use the connection cache -->
479- <subscriber
480- for="zope.processlifetime.IProcessStarting"
481- handler="lp.services.webapp.adapter.break_main_thread_db_access"
482- />
483-
484 <!-- Set the default timeout function. -->
485 <subscriber
486 for="zope.processlifetime.IProcessStarting"
487diff --git a/lib/lp/services/webapp/doc/webapp-publication.txt b/lib/lp/services/webapp/doc/webapp-publication.txt
488index ffe6a69..9b8b53e 100644
489--- a/lib/lp/services/webapp/doc/webapp-publication.txt
490+++ b/lib/lp/services/webapp/doc/webapp-publication.txt
491@@ -417,9 +417,7 @@ listens on a particular port.
492
493 Find the port the Private XMLRPC service is listening on.
494
495- >>> for server in config.servers:
496- ... if server.type == 'PrivateXMLRPC':
497- ... private_port = server.address[1]
498+ >>> private_port = config.vhost.xmlrpc_private.private_port
499
500 >>> print_request_and_publication(
501 ... 'xmlrpc-private.launchpad.test', method='POST',
502diff --git a/lib/lp/services/webapp/publication.py b/lib/lp/services/webapp/publication.py
503index 7325d9f..6b1beea 100644
504--- a/lib/lp/services/webapp/publication.py
505+++ b/lib/lp/services/webapp/publication.py
506@@ -28,7 +28,6 @@ from storm.exceptions import (
507 )
508 from storm.zope.interfaces import IZStorm
509 import transaction
510-from zc.zservertracelog.interfaces import ITraceLog
511 import zope.app.publication.browser
512 from zope.authentication.interfaces import IUnauthenticatedPrincipal
513 from zope.component import (
514@@ -871,6 +870,7 @@ def tracelog(request, prefix, msg):
515 easier. ``prefix`` should be unique and contain no spaces, and
516 preferably a single character to save space.
517 """
518- tracelog = ITraceLog(request, None)
519+ #tracelog = ITraceLog(request, None)
520+ tracelog = None
521 if tracelog is not None:
522 tracelog.log('%s %s' % (prefix, six.ensure_str(msg, 'US-ASCII')))
523diff --git a/lib/lp/services/webapp/servers.py b/lib/lp/services/webapp/servers.py
524index d7bcad2..d592c96 100644
525--- a/lib/lp/services/webapp/servers.py
526+++ b/lib/lp/services/webapp/servers.py
527@@ -23,13 +23,11 @@ from six.moves import xmlrpc_client
528 from six.moves.urllib.parse import parse_qs
529 import transaction
530 from transaction.interfaces import ISynchronizer
531-from zc.zservertracelog.tracelog import Server as ZServerTracelogServer
532 from zope.app.publication.httpfactory import HTTPPublicationRequestFactory
533 from zope.app.publication.interfaces import IRequestPublicationFactory
534 from zope.app.publication.requestpublicationregistry import (
535 factoryRegistry as publisher_factory_registry,
536 )
537-from zope.app.server import wsgi
538 from zope.app.wsgi import WSGIPublisherApplication
539 from zope.component import getUtility
540 from zope.formlib.itemswidgets import MultiDataHelper
541@@ -1111,36 +1109,6 @@ class LaunchpadAccessLogger(CommonAccessLogger):
542 )
543
544
545-http = wsgi.ServerType(
546- ZServerTracelogServer, # subclass of WSGIHTTPServer
547- WSGIPublisherApplication,
548- LaunchpadAccessLogger,
549- 8080,
550- True)
551-
552-pmhttp = wsgi.ServerType(
553- PMDBWSGIHTTPServer,
554- WSGIPublisherApplication,
555- LaunchpadAccessLogger,
556- 8081,
557- True)
558-
559-debughttp = wsgi.ServerType(
560- ZServerTracelogServer, # subclass of WSGIHTTPServer
561- WSGIPublisherApplication,
562- LaunchpadAccessLogger,
563- 8082,
564- True,
565- requestFactory=DebugLayerRequestFactory)
566-
567-privatexmlrpc = wsgi.ServerType(
568- ZServerTracelogServer, # subclass of WSGIHTTPServer
569- WSGIPublisherApplication,
570- LaunchpadAccessLogger,
571- 8080,
572- True)
573-
574-
575 # ---- mainsite
576
577 class MainLaunchpadPublication(LaunchpadBrowserPublication):
578@@ -1576,16 +1544,11 @@ def register_launchpad_request_publication_factories():
579 TestOpenIDBrowserPublication))
580
581 # We may also have a private XML-RPC server.
582- private_port = None
583- for server in config.servers:
584- if server.type == 'PrivateXMLRPC':
585- ip, private_port = server.address
586- break
587-
588- if private_port is not None:
589+ if config.vhost.xmlrpc_private.private_port:
590 factories.append(XMLRPCRequestPublicationFactory(
591 'xmlrpc_private', PrivateXMLRPCRequest,
592- PrivateXMLRPCPublication, port=private_port))
593+ PrivateXMLRPCPublication,
594+ port=config.vhost.xmlrpc_private.private_port))
595
596 # Register those factories, in priority order corresponding to
597 # their order in the list. This means picking a large number for
598diff --git a/lib/lp/services/webapp/servers.zcml b/lib/lp/services/webapp/servers.zcml
599deleted file mode 100644
600index 0235cd2..0000000
601--- a/lib/lp/services/webapp/servers.zcml
602+++ /dev/null
603@@ -1,36 +0,0 @@
604-<!-- Copyright 2009 Canonical Ltd. This software is licensed under the
605- GNU Affero General Public License version 3 (see the file LICENSE).
606--->
607-
608-<configure xmlns="http://namespaces.zope.org/zope">
609- <!-- This is the HTTP server, set up to do ZODB-free publication. -->
610- <utility
611- name="HTTP"
612- component="lp.services.webapp.servers.http"
613- provides="zope.app.server.servertype.IServerType"
614- />
615-
616- <!-- This is the HTTP server, with post-mortem debugging support,
617- set up to do ZODB-free publication. -->
618- <utility
619- name="PostmortemDebuggingHTTP"
620- component="lp.services.webapp.servers.pmhttp"
621- provides="zope.app.server.servertype.IServerType"
622- />
623-
624- <!-- This is the HTTP server, but making each request have the
625- DebugLayer as the first layer searched. Effectively, this turns
626- on the debug error pages. -->
627- <utility
628- name="DebugLayerHTTP"
629- component="lp.services.webapp.servers.debughttp"
630- provides="zope.app.server.servertype.IServerType"
631- />
632-
633- <utility
634- name="PrivateXMLRPC"
635- component="lp.services.webapp.servers.privatexmlrpc"
636- provides="zope.app.server.servertype.IServerType"
637- />
638-
639-</configure>
640diff --git a/lib/lp/startwsgi.py b/lib/lp/startwsgi.py
641new file mode 100644
642index 0000000..92da007
643--- /dev/null
644+++ b/lib/lp/startwsgi.py
645@@ -0,0 +1,26 @@
646+# Copyright 2021 Canonical Ltd. This software is licensed under the
647+# GNU Affero General Public License version 3 (see the file LICENSE).
648+
649+"""WSGI script to start web server."""
650+
651+from __future__ import absolute_import, print_function, unicode_literals
652+
653+__metaclass__ = type
654+__all__ = []
655+
656+from zope.app.publication.httpfactory import HTTPPublicationRequestFactory
657+from zope.app.wsgi import (
658+ getWSGIApplication,
659+ )
660+from zope.event import notify
661+import zope.processlifetime
662+
663+from lp.services.config import config
664+
665+
666+application = getWSGIApplication(
667+ config.zope_config_file,
668+ requestFactory=HTTPPublicationRequestFactory
669+)
670+
671+notify(zope.processlifetime.ProcessStarting())
672diff --git a/lib/lp/testing/layers.py b/lib/lp/testing/layers.py
673index 9089c60..e0722fd 100644
674--- a/lib/lp/testing/layers.py
675+++ b/lib/lp/testing/layers.py
676@@ -48,6 +48,7 @@ __all__ = [
677 'reconnect_stores',
678 ]
679
680+import select
681 from cProfile import Profile
682 import datetime
683 import errno
684@@ -80,6 +81,7 @@ from six.moves.urllib.parse import (
685 urlparse,
686 )
687 from six.moves.urllib.request import urlopen
688+from six.moves.urllib.error import HTTPError
689 import transaction
690 from webob.request import environ_from_url as orig_environ_from_url
691 import wsgi_intercept
692@@ -1794,6 +1796,14 @@ class LayerProcessController:
693 raise LayerIsolationError(
694 "App server died in this test (status=%s):\n%s" % (
695 cls.appserver.returncode, cls.appserver.stdout.read()))
696+ # Cleanup the app server's output buffer between tests.
697+ while True:
698+ # Read while we have something available at the stdout.
699+ r, w, e = select.select([cls.appserver.stdout], [], [], 0)
700+ if cls.appserver.stdout in r:
701+ cls.appserver.stdout.readline()
702+ else:
703+ break
704 DatabaseLayer.force_dirty_database()
705
706 @classmethod
707@@ -1820,7 +1830,9 @@ class LayerProcessController:
708 environ['LPCONFIG'] = _config.instance_name
709 cls.appserver = subprocess.Popen(
710 cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
711+ stdin=subprocess.PIPE,
712 env=environ, cwd=_config.root)
713+ cls.appserver.stdin.close()
714
715 @classmethod
716 def appserver_root_url(cls):
717@@ -1836,6 +1848,12 @@ class LayerProcessController:
718 try:
719 connection = urlopen(root_url)
720 connection.read()
721+ except HTTPError as error:
722+ if error.code == 503:
723+ raise RuntimeError(
724+ "App server is returning unknown error code %s. Is "
725+ "there another instance running in the same port?" %
726+ error.code)
727 except URLError as error:
728 # We are interested in a wrapped socket.error.
729 if not isinstance(error.reason, socket.error):
730diff --git a/setup.py b/setup.py
731index b5696f3..49c56e0 100644
732--- a/setup.py
733+++ b/setup.py
734@@ -233,6 +233,8 @@ setup(
735 'Sphinx',
736 'statsd',
737 'storm',
738+ 'subvertpy',
739+ 'talisker[gunicorn]',
740 'tenacity',
741 'testscenarios',
742 'testtools',
743@@ -252,7 +254,6 @@ setup(
744 'zope.app.http',
745 'zope.app.publication',
746 'zope.app.publisher',
747- 'zope.app.server',
748 'zope.app.wsgi[testlayer]',
749 'zope.authentication',
750 'zope.browser',
751@@ -333,6 +334,7 @@ setup(
752 'version-info = lp.scripts.utilities.versioninfo:main',
753 'watch_jsbuild = lp.scripts.utilities.js.watchjsbuild:main',
754 'with-xvfb = lp.scripts.utilities.withxvfb:main',
755- ]
756+ ],
757+ wsgi=["wsigogogo = lp.startwsgi.gogogo"]
758 ),
759 )
760diff --git a/zcml/webapp.zcml b/zcml/webapp.zcml
761index 948c4eb..a574200 100644
762--- a/zcml/webapp.zcml
763+++ b/zcml/webapp.zcml
764@@ -14,6 +14,4 @@
765 <includeOverrides file="+config-overrides.zcml" />
766
767 <include file="summarizerequests.zcml" />
768- <include package="zc.zservertracelog" />
769-
770 </configure>