launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #26052
[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