← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~pappacena/launchpad:initial-gunicorn-setup into launchpad:master

 

Thiago F. Pappacena has proposed merging ~pappacena/launchpad:initial-gunicorn-setup into launchpad:master.

Commit message:
Adding initial structure for gunicorn switch

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

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

Related dependencies: https://code.launchpad.net/~pappacena/lp-source-dependencies/+git/lp-source-dependencies/+merge/396614
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~pappacena/launchpad:initial-gunicorn-setup into launchpad:master.
diff --git a/lib/lp/scripts/runlaunchpad.py b/lib/lp/scripts/runlaunchpad.py
index 969cfd6..0557ee8 100644
--- a/lib/lp/scripts/runlaunchpad.py
+++ b/lib/lp/scripts/runlaunchpad.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2021 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 from __future__ import absolute_import, print_function, unicode_literals
@@ -18,8 +18,9 @@ import sys
 import fixtures
 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
+from zope.app.server.main import main as zope_main
 
 from lp.services.config import config
 from lp.services.daemons import tachandler
@@ -331,6 +332,20 @@ def start_testapp(argv=list(sys.argv)):
                 pass
 
 
+def gunicorn_main():
+    orig_argv = sys.argv
+    try:
+        sys.argv = [
+            os.path.join(config.root, "bin", "talisker.gunicorn"),
+            "lp.startwsgi",
+            "-c", os.path.join(config.config_dir, "gunicorn.conf.py")
+        ]
+        run_gunicorn()
+        return
+    finally:
+        sys.argv = orig_argv
+
+
 def start_launchpad(argv=list(sys.argv), setup=None):
     # We really want to replace this with a generic startup harness.
     # However, this should last us until this is developed
@@ -352,7 +367,10 @@ def start_launchpad(argv=list(sys.argv), setup=None):
             # Store our process id somewhere
             make_pidfile('launchpad')
             if config.launchpad.launch:
-                main(argv)
+                if config.use_gunicorn:
+                    gunicorn_main()
+                else:
+                    zope_main(argv)
             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 5dd6977..c460b8f 100644
--- a/lib/lp/scripts/tests/test_runlaunchpad.py
+++ b/lib/lp/scripts/tests/test_runlaunchpad.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2019 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2021 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Tests for runlaunchpad.py"""
@@ -23,7 +23,9 @@ 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
 import lp.testing
@@ -165,3 +167,27 @@ class ServersToStart(testtools.TestCase):
 
     def test_launchpad_systems_red(self):
         self.assertFalse(config.launchpad.launch)
+
+
+class TestAppServerStart(testtools.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/scripts/utilities/importpedant.py b/lib/lp/scripts/utilities/importpedant.py
index 5176e43..5b64bb0 100644
--- a/lib/lp/scripts/utilities/importpedant.py
+++ b/lib/lp/scripts/utilities/importpedant.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2021 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 from __future__ import absolute_import, print_function, unicode_literals
@@ -42,6 +42,7 @@ valid_imports_not_in_all = {
     'shlex': set(['quote']),
     'six.moves.http_cookiejar': set(['domain_match']),
     'storm.database': set(['STATE_DISCONNECTED']),
+    'talisker': set(['run_gunicorn']),
     'textwrap': set(['dedent']),
     'testtools.testresult.real': set(['_details_to_str']),
     'twisted.internet.threads': set(['deferToThreadPool']),
diff --git a/lib/lp/services/config/__init__.py b/lib/lp/services/config/__init__.py
index 9d43346..82c2e9f 100644
--- a/lib/lp/services/config/__init__.py
+++ b/lib/lp/services/config/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2021 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 '''
@@ -147,6 +147,13 @@ 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?"""
+        # XXX pappacena: 2020-01-20: Forced False until we have everything
+        # in place.
+        return False
+
     def setInstance(self, instance_name):
         """Set the instance name where the conf files are stored.
 
diff --git a/lib/lp/startwsgi.py b/lib/lp/startwsgi.py
new file mode 100644
index 0000000..6786263
--- /dev/null
+++ b/lib/lp/startwsgi.py
@@ -0,0 +1,39 @@
+from zope.app.wsgi import getWSGIApplication
+from zope.app.wsgi import interfaces
+import zope.processlifetime
+from zope.app.publication.requestpublicationregistry import (
+    factoryRegistry as publisher_factory_registry,
+    )
+from zope.interface import implementer
+from zope.event import notify
+
+from lp.services.config import config
+
+
+@implementer(interfaces.IWSGIApplication)
+class RegistryLookupFactory(object):
+
+    def __init__(self, db):
+        self._db = db
+        self._publication_cache = {}
+        return
+
+    def __call__(self, input_stream, env):
+        factory = publisher_factory_registry.lookup('*', '*', env)
+        request_class, publication_class = factory()
+        publication = self._publication_cache.get(publication_class)
+        if publication is None:
+            publication = publication_class(self._db)
+            self._publication_cache[publication_class] = publication
+
+        request = request_class(input_stream, env)
+        request.setPublication(publication)
+        return request
+
+
+application = getWSGIApplication(
+    config.zope_config_file,
+    requestFactory=RegistryLookupFactory
+)
+
+notify(zope.processlifetime.ProcessStarting())
diff --git a/setup.py b/setup.py
index b5696f3..d622ecd 100644
--- a/setup.py
+++ b/setup.py
@@ -233,6 +233,8 @@ setup(
         'Sphinx',
         'statsd',
         'storm',
+        'subvertpy',
+        'talisker[gunicorn]',
         'tenacity',
         'testscenarios',
         'testtools',
@@ -333,6 +335,7 @@ setup(
             'version-info = lp.scripts.utilities.versioninfo:main',
             'watch_jsbuild = lp.scripts.utilities.js.watchjsbuild:main',
             'with-xvfb = lp.scripts.utilities.withxvfb:main',
-        ]
+        ],
+        wsgi=["wsigogogo = lp.startwsgi.gogogo"]
     ),
 )

Follow ups