← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:gunicorn-only into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:gunicorn-only into launchpad:master.

Commit message:
Remove support for running with zope.server

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/405893

Now that we're on gunicorn everywhere and it's all working, remove support for the old zope.server framework.  The tracelog code is superseded by adding items to the Talisker logging context.

There are still various other bits and pieces to remove, but this gets us started.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:gunicorn-only into launchpad:master.
diff --git a/Makefile b/Makefile
index 4b68d4c..27f25f5 100644
--- a/Makefile
+++ b/Makefile
@@ -91,7 +91,6 @@ PIP_BIN = \
     bin/sprite-util \
     bin/start_librarian \
     bin/test \
-    bin/tracereport \
     bin/twistd \
     bin/watch_jsbuild \
     bin/with-xvfb
diff --git a/configs/development/launchpad-lazr.conf b/configs/development/launchpad-lazr.conf
index 4f34d76..2b41199 100644
--- a/configs/development/launchpad-lazr.conf
+++ b/configs/development/launchpad-lazr.conf
@@ -91,7 +91,6 @@ public_https: False
 
 [launchpad]
 devmode: true
-use_gunicorn: true
 enable_test_openid_provider: True
 test_openid_provider_store: /var/tmp/testopenid
 openid_canonical_root: https://testopenid.test/
diff --git a/configs/replicated-development/launchpad-lazr.conf b/configs/replicated-development/launchpad-lazr.conf
index 750c283..30ef01b 100644
--- a/configs/replicated-development/launchpad-lazr.conf
+++ b/configs/replicated-development/launchpad-lazr.conf
@@ -11,4 +11,3 @@ rw_main_slave: dbname=launchpad_dev_slave port=5433
 
 [launchpad]
 devmode: true
-use_gunicorn: true
diff --git a/configs/test-playground/launchpad-lazr.conf b/configs/test-playground/launchpad-lazr.conf
index 77818e4..422485f 100644
--- a/configs/test-playground/launchpad-lazr.conf
+++ b/configs/test-playground/launchpad-lazr.conf
@@ -11,4 +11,3 @@ rw_main_slave:  dbname=launchpad_ftest_playground
 
 [launchpad]
 devmode: true
-use_gunicorn: true
diff --git a/configs/testrunner-appserver/launchpad-lazr.conf b/configs/testrunner-appserver/launchpad-lazr.conf
index 3653a4d..f184b1f 100644
--- a/configs/testrunner-appserver/launchpad-lazr.conf
+++ b/configs/testrunner-appserver/launchpad-lazr.conf
@@ -14,7 +14,6 @@ launch: False
 [launchpad]
 # Make this work a little more like production.
 devmode: false
-use_gunicorn: true
 openid_provider_root: http://testopenid.test:8085/
 internal_macaroon_secret_key: internal-dev-macaroon-secret
 
diff --git a/configs/testrunner/launchpad-lazr.conf b/configs/testrunner/launchpad-lazr.conf
index 1e930d6..eeaabb2 100644
--- a/configs/testrunner/launchpad-lazr.conf
+++ b/configs/testrunner/launchpad-lazr.conf
@@ -102,7 +102,6 @@ max_scaling: 2
 
 [launchpad]
 devmode: true
-use_gunicorn: true
 basic_auth_password: test
 max_attachment_size: 1024
 geoip_database: lib/lp/services/geoip/tests/data/test.mmdb
diff --git a/lib/lp/scripts/runlaunchpad.py b/lib/lp/scripts/runlaunchpad.py
index 0557ee8..7a26b6d 100644
--- a/lib/lp/scripts/runlaunchpad.py
+++ b/lib/lp/scripts/runlaunchpad.py
@@ -20,7 +20,6 @@ from lazr.config import as_host_port
 from rabbitfixture.server import RabbitServerResources
 from talisker import run_gunicorn
 from testtools.testresult.real import _details_to_str
-from zope.app.server.main import main as zope_main
 
 from lp.services.config import config
 from lp.services.daemons import tachandler
@@ -242,22 +241,11 @@ def process_config_arguments(args):
     """Process the arguments related to the config.
 
     -i  Will set the instance name aka LPCONFIG env.
-
-    If there is no ZConfig file passed, one will add to the argument
-    based on the selected instance.
     """
     if '-i' in args:
         index = args.index('-i')
         config.setInstance(args[index + 1])
         del args[index:index + 2]
-
-    if '-C' not in args:
-        zope_config_file = config.zope_config_file
-        if not os.path.isfile(zope_config_file):
-            raise ValueError(
-                "Cannot find ZConfig file for instance %s: %s" % (
-                    config.instance_name, zope_config_file))
-        args.extend(['-C', zope_config_file])
     return args
 
 
@@ -367,10 +355,7 @@ def start_launchpad(argv=list(sys.argv), setup=None):
             # Store our process id somewhere
             make_pidfile('launchpad')
             if config.launchpad.launch:
-                if config.use_gunicorn:
-                    gunicorn_main()
-                else:
-                    zope_main(argv)
+                gunicorn_main()
             else:
                 # We just need the foreground process to sit around forever
                 # waiting for the signal to shut everything down.  Normally,
diff --git a/lib/lp/scripts/tests/test_runlaunchpad.py b/lib/lp/scripts/tests/test_runlaunchpad.py
index 3a5d998..e2d429f 100644
--- a/lib/lp/scripts/tests/test_runlaunchpad.py
+++ b/lib/lp/scripts/tests/test_runlaunchpad.py
@@ -11,8 +11,6 @@ __all__ = [
     'ServersToStart',
     ]
 
-
-import os
 import shutil
 import tempfile
 
@@ -21,9 +19,7 @@ from lp.scripts.runlaunchpad import (
     process_config_arguments,
     SERVICES,
     split_out_runlaunchpad_arguments,
-    start_launchpad,
     )
-from lp.services.compat import mock
 import lp.services.config
 from lp.services.config import config
 from lp.testing import TestCase
@@ -74,7 +70,7 @@ class CommandLineArgumentProcessing(TestCase):
 
 
 class TestDefaultConfigArgument(TestCase):
-    """Tests for the processing of the -C argument."""
+    """Tests for the processing of config arguments."""
 
     def setUp(self):
         super(TestDefaultConfigArgument, self).setUp()
@@ -89,36 +85,11 @@ class TestDefaultConfigArgument(TestCase):
         lp.services.config.CONFIG_ROOT_DIRS = self.saved_config_roots
         config.setInstance(self.saved_instance)
 
-    def test_keep_argument(self):
-        """Make sure that a -C is processed unchanged."""
-        self.assertEqual(
-            ['-v', '-C', 'a_file.conf', '-h'],
-            process_config_arguments(['-v', '-C', 'a_file.conf', '-h']))
-
-    def test_default_config(self):
-        """Make sure that the -C option is set to the correct instance."""
-        instance_config_dir = os.path.join(self.config_root, 'instance1')
-        os.mkdir(instance_config_dir)
-        open(os.path.join(instance_config_dir, 'launchpad.conf'), 'w').close()
-        config.setInstance('instance1')
-        self.assertEqual(
-            ['-a_flag', '-C', '%s/launchpad.conf' % instance_config_dir],
-            process_config_arguments(['-a_flag']))
-
-    def test_instance_not_found_raises_ValueError(self):
-        """Make sure that an unknown instance fails."""
-        config.setInstance('unknown')
-        self.assertRaises(ValueError, process_config_arguments, [])
-
     def test_i_sets_the_instance(self):
         """The -i parameter will set the config instance name."""
-        instance_config_dir = os.path.join(self.config_root, 'test')
-        os.mkdir(instance_config_dir)
-        open(os.path.join(instance_config_dir, 'launchpad.conf'), 'w').close()
         self.assertEqual(
-            ['-o', 'foo', '-C', '%s/launchpad.conf' % instance_config_dir],
-            process_config_arguments(
-                ['-i', 'test', '-o', 'foo']))
+            ['-o', 'foo'],
+            process_config_arguments(['-i', 'test', '-o', 'foo']))
         self.assertEqual('test', config.instance_name)
 
 
@@ -164,27 +135,3 @@ class ServersToStart(TestCase):
 
     def test_launchpad_systems_red(self):
         self.assertFalse(config.launchpad.launch)
-
-
-class TestAppServerStart(lp.testing.TestCase):
-    @mock.patch('lp.scripts.runlaunchpad.zope_main')
-    @mock.patch('lp.scripts.runlaunchpad.gunicorn_main')
-    @mock.patch('lp.scripts.runlaunchpad.make_pidfile')
-    def test_call_correct_method(self, make_pidfile, gmain, zmain):
-        # Makes sure zope_main or gunicorn_main is called according to
-        # launchpad configuration.
-        patched_cfg = mock.patch(
-            'lp.services.config.LaunchpadConfig.use_gunicorn',
-            new_callable=mock.PropertyMock)
-        with patched_cfg as mock_use_gunicorn:
-            mock_use_gunicorn.return_value = True
-            start_launchpad([])
-            self.assertEqual(1, gmain.call_count)
-            self.assertEqual(0, zmain.call_count)
-        gmain.reset_mock()
-        zmain.reset_mock()
-        with patched_cfg as mock_use_gunicorn:
-            mock_use_gunicorn.return_value = False
-            start_launchpad([])
-            self.assertEqual(0, gmain.call_count)
-            self.assertEqual(1, zmain.call_count)
diff --git a/lib/lp/services/config/__init__.py b/lib/lp/services/config/__init__.py
index 64e47b7..2368d4a 100644
--- a/lib/lp/services/config/__init__.py
+++ b/lib/lp/services/config/__init__.py
@@ -2,7 +2,7 @@
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 '''
-Configuration information pulled from launchpad.conf.
+Configuration information pulled from launchpad-lazr.conf.
 
 The configuration section used is specified using the LPCONFIG
 environment variable, and defaults to 'development'
@@ -14,10 +14,6 @@ __metaclass__ = type
 
 
 import glob
-try:
-    from importlib import resources
-except ImportError:
-    import importlib_resources as resources
 import logging
 import os
 import random
@@ -30,7 +26,6 @@ from six.moves.urllib.parse import (
     urlparse,
     urlunparse,
     )
-import ZConfig
 
 from lp.services.osutils import open_for_writing
 from lp.services.propertycache import (
@@ -158,11 +153,6 @@ class LaunchpadConfig:
         """Return the directory containing this instance configuration."""
         return find_config_dir(self._instance_name)
 
-    @property
-    def use_gunicorn(self):
-        """When running launchpad server, shall we use gunicorn?"""
-        return self.launchpad.use_gunicorn
-
     def setInstance(self, instance_name):
         """Set the instance name where the conf files are stored.
 
@@ -180,8 +170,6 @@ class LaunchpadConfig:
     def _invalidateConfig(self):
         """Invalidate the config, causing the config to be regenerated."""
         self._config = None
-        self._devmode = None
-        self._servers = None
 
     def reloadConfig(self):
         """Reload the config."""
@@ -268,51 +256,13 @@ class LaunchpadConfig:
     def zope_config_file(self, value):
         self._zope_config_file = value
 
-    def _getZConfig(self):
-        """Modify the config, adding automatically generated settings
-
-        XXX pappacena 2021-01-21 This method should probably be deprecated
-        once we move to gunicorn since we read zope's configs here mostly to
-        get "devmode" directive, which is moved to launchpad-lazr.conf when
-        running launchpad server on gunicorn.
-        """
-        with resources.path('zope.app.server', 'schema.xml') as schemafile:
-            schema = ZConfig.loadSchema(str(schemafile))
-        if isinstance(self.zope_config_file, six.string_types):
-            root_options, handlers = ZConfig.loadConfig(
-                schema, self.zope_config_file)
-        else:
-            root_options, handlers = ZConfig.loadConfigFile(
-                schema, self.zope_config_file)
-        self._devmode = root_options.devmode
-        self._servers = root_options.servers
-
     @property
     def devmode(self):
-        """Devmode from the zope.app.server.main config.
+        """Devmode from launchpad-lazr.conf.
 
         Copied here for ease of access.
         """
-        if self.use_gunicorn:
-            return self.launchpad.devmode
-        # XXX pappacena 2021-01-21: Read only from launchpad-lazr.conf file
-        # once we will be running on gunicorn.
-        if self._devmode is None:
-            self._getZConfig()
-        return self._devmode
-
-    @devmode.setter
-    def devmode(self, value):
-        self._devmode = value
-
-    @property
-    def servers(self):
-        """The defined servers."""
-        if self.use_gunicorn:
-            return []
-        if self._servers is None:
-            self._getZConfig()
-        return self._servers
+        return self.launchpad.devmode
 
     def generate_overrides(self):
         """Ensure correct config.zcml overrides will be called.
diff --git a/lib/lp/services/config/doc/canonical-config.txt b/lib/lp/services/config/doc/canonical-config.txt
index 796fd0b..d3c330a 100644
--- a/lib/lp/services/config/doc/canonical-config.txt
+++ b/lib/lp/services/config/doc/canonical-config.txt
@@ -80,20 +80,6 @@ The application root directory is assigned to the root attribute.
     >>> example_path[len(config.root):]
     '/lib/lp/services/...'
 
-The config instance has additional attributes that come from the
-ZConfig.
-
-    >>> config.devmode
-    True
-
-    >>> config.use_gunicorn
-    True
-
-config.servers are only available when using Zope server (deprecated).
-
-    >>> len(config.servers)
-    0
-
 
 Working with test configurations
 --------------------------------
@@ -205,9 +191,6 @@ argument to the constructor.
     >>> dev_config.process_name
     'authserver'
 
-    >>> dev_config.devmode
-    True
-
 # XXX sinzui 2008-03-25 bug=78545: This cannot be tested until the
 # config can be restored when this test is torn down.
 #    >>> true_config = config
@@ -227,17 +210,6 @@ argument to the constructor.
 #    >>> config.database.dbname
 #    'launchpad_dev'
 
-#And the staging ZConfig (that is deprecated) is also selected.
-
-#    >>> config.dbname
-#    'launchpad_dev'
-#    >>> config._cache.default
-#    <SectionValue for canonical 'default'>
-#    >>> config._cache.testrunner
-#    Traceback (most recent call last):
-#    ...
-#    AttributeError: 'zope.thread.local' object has no attribute 'testrunner'
-
 #We need to reset the config for the testrunner.
 
 #    >>> config = true_config
diff --git a/lib/lp/services/config/schema-lazr.conf b/lib/lp/services/config/schema-lazr.conf
index 749ecaa..8bdc3f1 100644
--- a/lib/lp/services/config/schema-lazr.conf
+++ b/lib/lp/services/config/schema-lazr.conf
@@ -855,7 +855,9 @@ max_scaling: 500
 devmode: false
 
 # Should use gunicorn when booting, or fallback to Zope Server?
-use_gunicorn: false
+# XXX cjwatson 2021-07-16: Pending removal; setting this to false now has no
+# effect.
+use_gunicorn: true
 
 # A directory of files from which *-lazr.conf will be loaded and
 # overlaid in order on top of the main config.
diff --git a/lib/lp/services/webapp/adapter.py b/lib/lp/services/webapp/adapter.py
index 9494141..d2d1d86 100644
--- a/lib/lp/services/webapp/adapter.py
+++ b/lib/lp/services/webapp/adapter.py
@@ -441,51 +441,6 @@ def print_queries(queries, file=None):
         file.write("-" * 70 + "\n")
 
 
-# ---- Prevent database access in the main thread of the app server
-
-class StormAccessFromMainThread(Exception):
-    """The main thread must not access the database via Storm.
-
-    Occurs only if the appserver is running. Other code, such as the test
-    suite, can do what it likes.
-    """
-
-_main_thread_id = None
-
-
-def break_main_thread_db_access(*ignored):
-    """Ensure that Storm connections are not made in the main thread.
-
-    When the app server is running, we want ensure we don't use the
-    connection cache from the main thread as this would only be done
-    on process startup and would leave an open connection dangling,
-    wasting resources.
-
-    This method is invoked by an IProcessStartingEvent - it would be
-    easier to do on module load, but the test suite has legitimate uses
-    for using connections from the main thread.
-    """
-    # This check is only applicable to zope.server.  gunicorn uses a
-    # different model with an arbiter parent process.
-    if config.use_gunicorn:
-        return
-
-    # Record the ID of the main thread.
-    global _main_thread_id
-    _main_thread_id = threading.current_thread().ident
-
-    try:
-        getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
-    except StormAccessFromMainThread:
-        # LaunchpadDatabase correctly refused to create a connection
-        pass
-    else:
-        # We can't specify the order event handlers are called, so
-        # this means some other code has used storm before this
-        # handler.
-        raise StormAccessFromMainThread()
-
-
 # ---- Storm database classes
 
 isolation_level_map = {
@@ -513,12 +468,6 @@ class LaunchpadDatabase(Postgres):
         return self._dsn_user_re.sub('', self._dsn)
 
     def raw_connect(self):
-        # Prevent database connections from the main thread if
-        # break_main_thread_db_access() has been run.
-        if (_main_thread_id is not None and
-            _main_thread_id == threading.current_thread().ident):
-            raise StormAccessFromMainThread()
-
         try:
             realm, flavor = self._uri.database.split('-')
         except ValueError:
diff --git a/lib/lp/services/webapp/configure.zcml b/lib/lp/services/webapp/configure.zcml
index 11749a3..edd452c 100644
--- a/lib/lp/services/webapp/configure.zcml
+++ b/lib/lp/services/webapp/configure.zcml
@@ -8,7 +8,6 @@
     xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc";
     i18n_domain="launchpad">
 
-    <include file="servers.zcml" />
     <include file="errorlog.zcml" />
 
     <browser:defaultView name="index.html" />
@@ -354,12 +353,6 @@
         handler="lp.services.webapp.sigdumpmem.setup_sigdumpmem"
         />
 
-    <!-- Confirm that no main thread event handlers use the connection cache -->
-    <subscriber
-        for="zope.processlifetime.IProcessStarting"
-        handler="lp.services.webapp.adapter.break_main_thread_db_access"
-        />
-
     <!-- Set the default timeout function. -->
     <subscriber
         for="zope.processlifetime.IProcessStarting"
diff --git a/lib/lp/services/webapp/doc/webapp-publication.txt b/lib/lp/services/webapp/doc/webapp-publication.txt
index 80aca73..ce008d1 100644
--- a/lib/lp/services/webapp/doc/webapp-publication.txt
+++ b/lib/lp/services/webapp/doc/webapp-publication.txt
@@ -417,9 +417,6 @@ listens on a particular port.
 
 Find the port the Private XMLRPC service is listening on.
 
-    >>> config.use_gunicorn
-    True
-
     >>> private_port = config.vhost.xmlrpc_private.private_port
     >>> print_request_and_publication(
     ...     'xmlrpc-private.launchpad.test', method='POST',
diff --git a/lib/lp/services/webapp/publication.py b/lib/lp/services/webapp/publication.py
index 575e855..da0b2d2 100644
--- a/lib/lp/services/webapp/publication.py
+++ b/lib/lp/services/webapp/publication.py
@@ -30,7 +30,6 @@ from storm.exceptions import (
 from storm.zope.interfaces import IZStorm
 from talisker.logs import logging_context
 import transaction
-from zc.zservertracelog.interfaces import ITraceLog
 import zope.app.publication.browser
 from zope.authentication.interfaces import IUnauthenticatedPrincipal
 from zope.component import (
@@ -451,8 +450,6 @@ class LaunchpadBrowserPublication(
         pageid = request._orig_env.get('launchpad.pageid')
         if not pageid:
             pageid = self._setPageIDInEnvironment(request, ob)
-        # And spit the pageid out to our tracelog.
-        tracelog(request, 'p', pageid)
 
         # For status URLs, where we really don't want to have any DB access
         # at all, ensure that all flag lookups will stop early.
@@ -526,16 +523,7 @@ class LaunchpadBrowserPublication(
             endtime - starttime
                 for starttime, endtime, id, statement, tb in sql_statements)
 
-        # Log publication duration (in milliseconds), sql statement count,
-        # and sql time (in milliseconds) to the tracelog.  If we have the
-        # publication time spent in this thread, then log that too (in
-        # milliseconds).
-        tracelog_entry = '%d %d %d' % (
-            publication_duration * 1000,
-            len(sql_statements), sql_milliseconds)
-        if publication_thread_duration is not None:
-            tracelog_entry += ' %d' % (publication_thread_duration * 1000)
-        tracelog(request, 't', tracelog_entry)
+        # Log sql statement count and sql time (in milliseconds).
         logging_context.push(
             sql_statements=len(sql_statements), sql_ms=sql_milliseconds)
 
@@ -616,9 +604,6 @@ class LaunchpadBrowserPublication(
         in zopepublication.py because we don't want to call
         _maybePlacefullyAuthenticate.
         """
-        # Log the URL including vhost information to the ZServer tracelog.
-        tracelog(request, 'u', request.getURL())
-
         pageid = self._setPageIDInEnvironment(request, ob)
 
         assert hasattr(request, '_traversal_start'), (
@@ -933,17 +918,3 @@ def is_browser(request):
     return (
         user_agent is not None
         and _browser_re.search(user_agent) is not None)
-
-
-def tracelog(request, prefix, msg):
-    """Emit a message to the ITraceLog, or do nothing if there is none.
-
-    The message will be prefixed by ``prefix`` to make writing parsers
-    easier. ``prefix`` should be unique and contain no spaces, and
-    preferably a single character to save space.
-    """
-    if not config.use_gunicorn:
-        msg = '%s %s' % (prefix, six.ensure_str(msg, 'US-ASCII'))
-        tracelog = ITraceLog(request, None)
-        if tracelog is not None:
-            tracelog.log(msg)
diff --git a/lib/lp/services/webapp/servers.py b/lib/lp/services/webapp/servers.py
index 1e040b3..6a7def3 100644
--- a/lib/lp/services/webapp/servers.py
+++ b/lib/lp/services/webapp/servers.py
@@ -26,14 +26,11 @@ from six.moves.urllib.parse import parse_qs
 from talisker.logs import logging_context
 import transaction
 from transaction.interfaces import ISynchronizer
-from zc.zservertracelog.tracelog import Server as ZServerTracelogServer
 from zope.app.publication.httpfactory import HTTPPublicationRequestFactory
 from zope.app.publication.interfaces import IRequestPublicationFactory
 from zope.app.publication.requestpublicationregistry import (
     factoryRegistry as publisher_factory_registry,
     )
-from zope.app.server import wsgi
-from zope.app.wsgi import WSGIPublisherApplication
 from zope.component import getUtility
 from zope.formlib.itemswidgets import MultiDataHelper
 from zope.formlib.widget import SimpleInputWidget
@@ -60,7 +57,6 @@ from zope.security.proxy import (
     removeSecurityProxy,
     )
 from zope.server.http.commonaccesslogger import CommonAccessLogger
-from zope.server.http.wsgihttpserver import PMDBWSGIHTTPServer
 from zope.session.interfaces import ISession
 
 from lp.app import versioninfo
@@ -1137,37 +1133,6 @@ class LaunchpadAccessLogger(CommonAccessLogger):
                 )
            )
 
-# XXX pappacena 2021-01-21: These 4 server definitions can be removed once
-# we are using only gunicorn (and not Zope Server).
-http = wsgi.ServerType(
-    ZServerTracelogServer,  # subclass of WSGIHTTPServer
-    WSGIPublisherApplication,
-    LaunchpadAccessLogger,
-    8080,
-    True)
-
-pmhttp = wsgi.ServerType(
-    PMDBWSGIHTTPServer,
-    WSGIPublisherApplication,
-    LaunchpadAccessLogger,
-    8081,
-    True)
-
-debughttp = wsgi.ServerType(
-    ZServerTracelogServer,  # subclass of WSGIHTTPServer
-    WSGIPublisherApplication,
-    LaunchpadAccessLogger,
-    8082,
-    True,
-    requestFactory=DebugLayerRequestFactory)
-
-privatexmlrpc = wsgi.ServerType(
-    ZServerTracelogServer,  # subclass of WSGIHTTPServer
-    WSGIPublisherApplication,
-    LaunchpadAccessLogger,
-    8080,
-    True)
-
 
 # ---- mainsite
 
@@ -1608,14 +1573,7 @@ def register_launchpad_request_publication_factories():
                               TestOpenIDBrowserPublication))
 
     # We may also have a private XML-RPC server.
-    private_port = None
-    if config.use_gunicorn:
-        private_port = config.vhost.xmlrpc_private.private_port
-    else:
-        for server in config.servers:
-            if server.type == 'PrivateXMLRPC':
-                ip, private_port = server.address
-                break
+    private_port = config.vhost.xmlrpc_private.private_port
 
     if private_port is not None:
         factories.append(XMLRPCRequestPublicationFactory(
diff --git a/lib/lp/services/webapp/servers.zcml b/lib/lp/services/webapp/servers.zcml
deleted file mode 100644
index 0235cd2..0000000
--- a/lib/lp/services/webapp/servers.zcml
+++ /dev/null
@@ -1,36 +0,0 @@
-<!-- Copyright 2009 Canonical Ltd.  This software is licensed under the
-     GNU Affero General Public License version 3 (see the file LICENSE).
--->
-
-<configure xmlns="http://namespaces.zope.org/zope";>
-    <!-- This is the HTTP server, set up to do ZODB-free publication.  -->
-    <utility
-        name="HTTP"
-        component="lp.services.webapp.servers.http"
-        provides="zope.app.server.servertype.IServerType"
-        />
-
-    <!-- This is the HTTP server, with post-mortem debugging support,
-        set up to do ZODB-free publication.  -->
-    <utility
-        name="PostmortemDebuggingHTTP"
-        component="lp.services.webapp.servers.pmhttp"
-        provides="zope.app.server.servertype.IServerType"
-        />
-
-    <!-- This is the HTTP server, but making each request have the
-        DebugLayer as the first layer searched.  Effectively, this turns
-        on the debug error pages. -->
-    <utility
-        name="DebugLayerHTTP"
-        component="lp.services.webapp.servers.debughttp"
-        provides="zope.app.server.servertype.IServerType"
-        />
-
-    <utility
-        name="PrivateXMLRPC"
-        component="lp.services.webapp.servers.privatexmlrpc"
-        provides="zope.app.server.servertype.IServerType"
-        />
-
-</configure>
diff --git a/lib/lp/testing/layers.py b/lib/lp/testing/layers.py
index b8842e5..d20d15c 100644
--- a/lib/lp/testing/layers.py
+++ b/lib/lp/testing/layers.py
@@ -1835,9 +1835,7 @@ class LayerProcessController:
     def _runAppServer(cls, run_name):
         """Start the app server using runlaunchpad.py"""
         _config = cls.appserver_config
-        cmd = [
-            os.path.join(_config.root, 'bin', run_name),
-            '-C', 'configs/%s/launchpad.conf' % _config.instance_name]
+        cmd = [os.path.join(_config.root, 'bin', run_name)]
         environ = dict(os.environ)
         environ['LPCONFIG'] = _config.instance_name
         cls.appserver = subprocess.Popen(
diff --git a/lib/lp/translations/stories/standalone/xx-product-export.txt b/lib/lp/translations/stories/standalone/xx-product-export.txt
index bad2d2a..60d4412 100644
--- a/lib/lp/translations/stories/standalone/xx-product-export.txt
+++ b/lib/lp/translations/stories/standalone/xx-product-export.txt
@@ -76,16 +76,16 @@ Only logged-in users get the option to request downloads.
 We can't see its placeholder in non-development mode:
 
     >>> from lp.services.config import config
-    >>> config.devmode
+    >>> config.launchpad.devmode
     True
-    >>> config.devmode = False
+    >>> config.push('devmode_test', '\n[launchpad]\ndevmode: true\n')
     >>> anon_browser.open('http://translations.launchpad.test/evolution/')
     >>> for tag in find_tags_by_class(
     ...     anon_browser.contents, 'menu-link-translationdownload'):
     ...     print(tag.decode_contents())
 
     # Reset global configuration...
-    >>> config.devmode = True
+    >>> _ = config.pop('devmode_test')
 
 Even "hacking the URL" to the download option will fail.
 
diff --git a/requirements/launchpad.txt b/requirements/launchpad.txt
index 8056b79..33d58a2 100644
--- a/requirements/launchpad.txt
+++ b/requirements/launchpad.txt
@@ -170,7 +170,6 @@ WSGIProxy2==0.4.6
 wsgiref==0.1.2
 z3c.pt==3.2.0
 z3c.ptcompat==2.2.0
-zc.zservertracelog==2.0.0
 zope.app.applicationcontrol==4.0.0
 zope.app.appsetup==4.1.0
 zope.app.http==4.0.1
diff --git a/setup.py b/setup.py
index 4c9aaac..058a287 100644
--- a/setup.py
+++ b/setup.py
@@ -251,7 +251,6 @@ setup(
         'Werkzeug',
         'WSGIProxy2',
         'z3c.ptcompat',
-        'zc.zservertracelog',
         'zope.app.appsetup',
         'zope.app.http',
         'zope.app.publication',
@@ -332,7 +331,6 @@ setup(
             'sprite-util = lp.scripts.utilities.spriteutil:main',
             'start_librarian = lp.scripts.runlaunchpad:start_librarian',
             'test = lp.scripts.utilities.test:main',
-            'tracereport = zc.zservertracelog.tracereport:main',
             'twistd = twisted.scripts.twistd:run',
             'version-info = lp.scripts.utilities.versioninfo:main',
             'watch_jsbuild = lp.scripts.utilities.js.watchjsbuild:main',
diff --git a/zcml/README b/zcml/README
index b4e6252..ca7e1c3 100644
--- a/zcml/README
+++ b/zcml/README
@@ -7,8 +7,7 @@ These are the bits of ZCML that are explicitly loaded by Python code. These
 ZCML files in turn load other parts.
 
   webapp.zcml
-    The big kahuna. Various config files (e.g. launchpad.conf) refer to
-    this file.
+    The big kahuna.  lp.services.webapp.wsgi uses this file.
 
   ftesting.zcml
     Used to configure the functional testing layer.  Look in
diff --git a/zcml/webapp.zcml b/zcml/webapp.zcml
index 948c4eb..f949d9f 100644
--- a/zcml/webapp.zcml
+++ b/zcml/webapp.zcml
@@ -14,6 +14,5 @@
     <includeOverrides file="+config-overrides.zcml" />
 
     <include file="summarizerequests.zcml" />
-    <include package="zc.zservertracelog" />
 
 </configure>