Merge lp://staging/~barry/launchpad/325962-makestart into lp://staging/launchpad

Proposed by Barry Warsaw
Status: Merged
Approved by: Edwin Grubbs
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp://staging/~barry/launchpad/325962-makestart
Merge into: lp://staging/launchpad
Diff against target: None lines
To merge this branch: bzr merge lp://staging/~barry/launchpad/325962-makestart
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) code Approve
Canonical Launchpad Engineering Pending
Review via email: mp+10054@code.staging.launchpad.net
To post a comment you must log in.
Revision history for this message
Barry Warsaw (barry) wrote :
Download full text (3.2 KiB)

= Summary =

Bug 325962 originally described what was thought to be a problem with Mailman
starting up after a stale pid file was left, following an unexpected crash.
After diagnosing the issue with the LOSAs though, it was learned that the
traceback is actually caused by the appserver. The interesting problem there
is that when Mailman is started up on forster, the appserver should /not/ be
starting. The root cause then is that there is no way to inhibit the
appserver from starting.

Note that we still want to use 'make start' to control this because the
initscripts are set up that way, and we may want to start other services.

== Proposed fix ==

Add a 'launch: <bool>' key to the [launchpad] section of the config file, and
read this key to decide whether to start the appserver or not.

== Pre-implementation notes ==

I had a conversation with Curtis and Gary about some related issues, and of
course with Steve who helped diagnose the problem, and describe what is
happening in the production system.

== Implementation details ==

Really, there are only two tricky parts to this branch. The first is the more
important one: what do we do if Zope doesn't start up? runlaunchpad.py acts
like a 'master process'; as long as it's running, all the other processes that
get started continue to run in the background. As soon as runlaunchpad.py
exits though, all the other processes are torn down. Once started, Zope runs
forever until it receives an interrupting signal. If we don't start Zope,
then runlaunchpad.py exits immediately, and immediately tears down all the
other child processes, which is not what we want!

Clearly we don't want to busy-loop when Zope doesn't start up, so I chose a
very simple implementation that infinitely loops in a time.sleep(3600). The
foreground process takes up no CPU for an hour at a time, then it wakes up
only to turn right around and sleep again. This seems like a fine trade-off,
especially because control-c continues to work.

The other tricky problem is that there are no good tests for whether processes
actually start up or not, based on the config file. I opted to test that the
config option works correctly. I didn't add any tests to make sure that
Zope doesn't start up. That kind of negative test is quite problematic
because it would require us to poll check for the non-existence of something
(e.g. a pid file or log entry). It's much harder to test for the
non-existence of something, and poll loops can the cause of spurious
failures. So I opted for manual testing of this aspect.

Note that there will be a parallel change to the production configs to take
advantage of this new option.

== Tests ==

bin/test -vv -t runlaunchpad

== Demo and Q/A ==

 * Edit configs/development/launchpad-lazr.conf
 * Find the [launchpad] section
 * Add 'launch: False'
 * Run 'make run_all'
 * Notice that Zope does not start, but Mailman does
 * Hit C-c
 * Notice that Mailman properly shuts down.

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/canonical/config/schema-lazr.conf
  lib/canonic...

Read more...

Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :

Hi Barry,

Wow, I'm glad I didn't have to debug this. I just have one minor suggestion.

merge-conditional

>=== modified file 'lib/canonical/launchpad/scripts/runlaunchpad.py'
>--- lib/canonical/launchpad/scripts/runlaunchpad.py>---2009-07-23 06:57:17 +0000
>+++ lib/canonical/launchpad/scripts/runlaunchpad.py>---2009-08-12 21:00:49 +0000
>@@ -12,6 +12,7 @@
> import atexit
> import signal
> import subprocess
>+import time
>
> from canonical.config import config
> from canonical.lazr.pidfile import make_pidfile, pidfile_path
>@@ -265,7 +266,17 @@
> # Create the ZCML override file based on the instance.
> config.generate_overrides()
>
>- main(argv)
>+ if config.launchpad.launch:
>+ main(argv)
>+ else:
>+ # We just need the foreground process to sit around forever waiting
>+ # for the signal to shut everything down. Normally, Zope itself would
>+ # be this master process, but we're not starting that up, so we need
>+ # to do something else. An infinite looping sleep of 1h seems fine
>+ # since it won't busyloop and it will exit when a C-c is received.
>+ while True:
>+ time.sleep(3600)

I think that signal.pause() would accomplish the exact same thing and be slightly cleaner.

-Edwin

review: Approve (code)
Revision history for this message
Barry Warsaw (barry) wrote :

On Aug 12, 2009, at 09:14 PM, Edwin Grubbs wrote:

>Wow, I'm glad I didn't have to debug this. I just have one minor suggestion.
>
>merge-conditional

Thanks!

>>=== modified file 'lib/canonical/launchpad/scripts/runlaunchpad.py'
>>--- lib/canonical/launchpad/scripts/runlaunchpad.py>---2009-07-23 06:57:17 +0000
>>+++ lib/canonical/launchpad/scripts/runlaunchpad.py>---2009-08-12 21:00:49 +0000
>>@@ -12,6 +12,7 @@
>> import atexit
>> import signal
>> import subprocess
>>+import time
>>
>> from canonical.config import config
>> from canonical.lazr.pidfile import make_pidfile, pidfile_path
>>@@ -265,7 +266,17 @@
>> # Create the ZCML override file based on the instance.
>> config.generate_overrides()
>>
>>- main(argv)
>>+ if config.launchpad.launch:
>>+ main(argv)
>>+ else:
>>+ # We just need the foreground process to sit around forever waiting
>>+ # for the signal to shut everything down. Normally, Zope itself would
>>+ # be this master process, but we're not starting that up, so we need
>>+ # to do something else. An infinite looping sleep of 1h seems fine
>>+ # since it won't busyloop and it will exit when a C-c is received.
>>+ while True:
>>+ time.sleep(3600)
>
>
>I think that signal.pause() would accomplish the exact same thing and be slightly cleaner.

<palm slaps forehead>

Yes of course, how did I forget that!? Thanks. Here's the incremental diff.

=== modified file 'lib/canonical/launchpad/scripts/runlaunchpad.py'
--- lib/canonical/launchpad/scripts/runlaunchpad.py 2009-08-12 19:05:17 +0000
+++ lib/canonical/launchpad/scripts/runlaunchpad.py 2009-08-12 21:39:59 +0000
@@ -12,7 +12,6 @@
 import atexit
 import signal
 import subprocess
-import time

 from canonical.config import config
 from canonical.lazr.pidfile import make_pidfile, pidfile_path
@@ -272,10 +271,11 @@
         # We just need the foreground process to sit around forever waiting
         # for the signal to shut everything down. Normally, Zope itself would
         # be this master process, but we're not starting that up, so we need
- # to do something else. An infinite looping sleep of 1h seems fine
- # since it won't busyloop and it will exit when a C-c is received.
- while True:
- time.sleep(3600)
+ # to do something else.
+ try:
+ signal.pause()
+ except KeyboardInterrupt:
+ pass

 def start_librarian():

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/config/schema-lazr.conf'
2--- lib/canonical/config/schema-lazr.conf 2009-08-10 16:52:41 +0000
3+++ lib/canonical/config/schema-lazr.conf 2009-08-12 19:05:17 +0000
4@@ -862,6 +862,10 @@
5 storm_cache: generational
6 storm_cache_size: 10000
7
8+# If False, do not launch the appserver.
9+# datatype: boolean
10+launch: True
11+
12 # If true, Launchpad is running in read-only mode. Attempts to
13 # write to the Launchpad database will be denied, and an explanatory
14 # error message displayed to the user.
15
16=== modified file 'lib/canonical/launchpad/scripts/runlaunchpad.py'
17--- lib/canonical/launchpad/scripts/runlaunchpad.py 2009-07-23 06:57:17 +0000
18+++ lib/canonical/launchpad/scripts/runlaunchpad.py 2009-08-12 19:05:17 +0000
19@@ -12,6 +12,7 @@
20 import atexit
21 import signal
22 import subprocess
23+import time
24
25 from canonical.config import config
26 from canonical.lazr.pidfile import make_pidfile, pidfile_path
27@@ -265,7 +266,17 @@
28 # Create the ZCML override file based on the instance.
29 config.generate_overrides()
30
31- main(argv)
32+ if config.launchpad.launch:
33+ main(argv)
34+ else:
35+ # We just need the foreground process to sit around forever waiting
36+ # for the signal to shut everything down. Normally, Zope itself would
37+ # be this master process, but we're not starting that up, so we need
38+ # to do something else. An infinite looping sleep of 1h seems fine
39+ # since it won't busyloop and it will exit when a C-c is received.
40+ while True:
41+ time.sleep(3600)
42+
43
44 def start_librarian():
45 """Start the Librarian in the background."""
46
47=== modified file 'lib/canonical/launchpad/scripts/tests/test_runlaunchpad.py'
48--- lib/canonical/launchpad/scripts/tests/test_runlaunchpad.py 2009-07-17 00:26:05 +0000
49+++ lib/canonical/launchpad/scripts/tests/test_runlaunchpad.py 2009-08-12 19:07:45 +0000
50@@ -20,11 +20,10 @@
51
52 from canonical.config import config
53 from canonical.launchpad.scripts.runlaunchpad import (
54- get_services_to_run, SERVICES, process_config_arguments,
55+ SERVICES, get_services_to_run, process_config_arguments,
56 split_out_runlaunchpad_arguments)
57
58
59-
60 class CommandLineArgumentProcessing(lp.testing.TestCase):
61 """runlaunchpad.py's command line arguments fall into two parts. The first
62 part specifies which services to run, then second part is passed directly
63@@ -131,6 +130,8 @@
64 launch: False
65 [codehosting]
66 launch: False
67+ [launchpad]
68+ launch: False
69 """
70 config.push('launch_data', launch_data)
71
72@@ -164,6 +165,9 @@
73 services = get_services_to_run(['sftp'])
74 self.assertEqual([SERVICES['sftp']], services)
75
76+ def test_launchpad_systems_red(self):
77+ self.failIf(config.launchpad.launch)
78+
79
80 def test_suite():
81 return unittest.TestLoader().loadTestsFromName(__name__)