Merge lp:~mwhudson/launchpad/no-hosted-area-include-launchpad-loggerhead into lp:launchpad

Proposed by Michael Hudson-Doyle
Status: Merged
Approved by: Tim Penhey
Approved revision: no longer in the source branch.
Merged at revision: 10828
Proposed branch: lp:~mwhudson/launchpad/no-hosted-area-include-launchpad-loggerhead
Merge into: lp:launchpad
Prerequisite: lp:~mwhudson/launchpad/no-hosted-area-server-catchup
Diff against target: 709 lines (+641/-4)
9 files modified
Makefile (+3/-3)
lib/launchpad_loggerhead/__init__.py (+1/-0)
lib/launchpad_loggerhead/app.py (+232/-0)
lib/launchpad_loggerhead/debug.py (+120/-0)
lib/launchpad_loggerhead/session.py (+73/-0)
lib/launchpad_loggerhead/static/robots.txt (+2/-0)
scripts/start-loggerhead.py (+177/-0)
scripts/stop-loggerhead.py (+33/-0)
utilities/sourcedeps.conf (+0/-1)
To merge this branch: bzr merge lp:~mwhudson/launchpad/no-hosted-area-include-launchpad-loggerhead
Reviewer Review Type Date Requested Status
Tim Penhey (community) Approve
Review via email: mp+24193@code.launchpad.net

Description of the change

Hi Tim,

This branch replaces https://code.edge.launchpad.net/~mwhudson/launchpad/no-hosted-area-join-launchpad-loggerhead/+merge/24105 and just adds the files from the launchpad-loggerhead branch without trying to do anything fancy.

Cheers,
mwh

To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Makefile'
--- Makefile 2010-04-27 03:45:51 +0000
+++ Makefile 2010-04-27 03:46:17 +0000
@@ -229,13 +229,13 @@
229 -i $(LPCONFIG)229 -i $(LPCONFIG)
230230
231run_codebrowse: build231run_codebrowse: build
232 BZR_PLUGIN_PATH=bzrplugins $(PY) sourcecode/launchpad-loggerhead/start-loggerhead.py -f232 BZR_PLUGIN_PATH=bzrplugins $(PY) scripts/start-loggerhead.py -f
233233
234start_codebrowse: build234start_codebrowse: build
235 BZR_PLUGIN_PATH=$(shell pwd)/bzrplugins $(PY) sourcecode/launchpad-loggerhead/start-loggerhead.py235 BZR_PLUGIN_PATH=$(shell pwd)/bzrplugins $(PY) scripts/start-loggerhead.py
236236
237stop_codebrowse:237stop_codebrowse:
238 $(PY) sourcecode/launchpad-loggerhead/stop-loggerhead.py238 $(PY) scripts/stop-loggerhead.py
239239
240run_codehosting: check_schema inplace stop hosted_branches240run_codehosting: check_schema inplace stop hosted_branches
241 $(RM) thread*.request241 $(RM) thread*.request
242242
=== added directory 'lib/launchpad_loggerhead'
=== removed symlink 'lib/launchpad_loggerhead'
=== target was u'../sourcecode/launchpad-loggerhead/launchpad_loggerhead/'
=== added file 'lib/launchpad_loggerhead/__init__.py'
--- lib/launchpad_loggerhead/__init__.py 1970-01-01 00:00:00 +0000
+++ lib/launchpad_loggerhead/__init__.py 2010-04-27 03:46:17 +0000
@@ -0,0 +1,1 @@
1
02
=== added file 'lib/launchpad_loggerhead/app.py'
--- lib/launchpad_loggerhead/app.py 1970-01-01 00:00:00 +0000
+++ lib/launchpad_loggerhead/app.py 2010-04-27 03:46:17 +0000
@@ -0,0 +1,232 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4import logging
5import re
6import os
7import threading
8import urllib
9import urlparse
10import xmlrpclib
11
12from bzrlib import branch, errors, lru_cache, urlutils
13
14from loggerhead.apps import favicon_app, static_app
15from loggerhead.apps.branch import BranchWSGIApp
16
17from openid.extensions.sreg import SRegRequest, SRegResponse
18from openid.consumer.consumer import CANCEL, Consumer, FAILURE, SUCCESS
19from openid.store.memstore import MemoryStore
20
21from paste.fileapp import DataApp
22from paste.request import construct_url, parse_querystring, path_info_pop
23from paste.httpexceptions import (
24 HTTPMovedPermanently, HTTPNotFound, HTTPUnauthorized)
25
26from canonical.config import config
27from canonical.launchpad.xmlrpc import faults
28from lp.code.interfaces.codehosting import (
29 BRANCH_TRANSPORT, LAUNCHPAD_ANONYMOUS, LAUNCHPAD_SERVICES)
30from lp.codehosting.vfs import branch_id_to_path
31
32robots_txt = '''\
33User-agent: *
34Disallow: /
35'''
36
37robots_app = DataApp(robots_txt, content_type='text/plain')
38
39
40thread_transports = threading.local()
41
42def valid_launchpad_name(s):
43 return re.match('^[a-z0-9][a-z0-9\+\.\-]*$', s) is not None
44
45
46def valid_launchpad_user_name(s):
47 return re.match('^~[a-z0-9][a-z0-9\+\.\-]*$', s) is not None
48
49
50def valid_launchpad_branch_name(s):
51 return re.match(r'^(?i)[a-z0-9][a-z0-9+\.\-@_]*\Z', s) is not None
52
53
54class RootApp:
55
56 def __init__(self, session_var):
57 self.graph_cache = lru_cache.LRUCache(10)
58 self.branchfs = xmlrpclib.ServerProxy(
59 config.codehosting.branchfs_endpoint)
60 self.session_var = session_var
61 self.store = MemoryStore()
62 self.log = logging.getLogger('lp-loggerhead')
63 branch.Branch.hooks.install_named_hook(
64 'transform_fallback_location',
65 self._transform_fallback_location_hook,
66 'RootApp._transform_fallback_location_hook')
67
68 def _transform_fallback_location_hook(self, branch, url):
69 """Transform a human-readable fallback URL into and id-based one.
70
71 Branches on Launchpad record their stacked-on URLs in the form
72 '/~user/product/branch', but we need to access branches based on
73 database ID to gain access to private branches. So we use this hook
74 into Bazaar's branch-opening process to translate the former to the
75 latter.
76 """
77 # It might seem that using the LAUNCHPAD_SERVICES 'user', which allows
78 # access to all branches, here would be a security risk. But in fact
79 # it isn't, because a user will only have launchpad.View on the
80 # stacked branch if they have it for all the stacked-on branches.
81 # (It would be nice to use the user from the request, but that's far
82 # from simple because branch hooks are global per-process and we
83 # handle different requests in different threads).
84 transport_type, info, trail = self.branchfs.translatePath(
85 LAUNCHPAD_SERVICES, url)
86 return urlparse.urljoin(
87 config.codehosting.internal_branch_by_id_root,
88 branch_id_to_path(info['id']))
89
90 def get_transports(self):
91 t = getattr(thread_transports, 'transports', None)
92 if t is None:
93 thread_transports.transports = []
94 return thread_transports.transports
95
96 def _make_consumer(self, environ):
97 """Build an OpenID `Consumer` object with standard arguments."""
98 return Consumer(environ[self.session_var], self.store)
99
100 def _begin_login(self, environ, start_response):
101 """Start the process of authenticating with OpenID.
102
103 We redirect the user to Launchpad to identify themselves, asking to be
104 sent their nickname. Launchpad will then redirect them to our +login
105 page with enough information that we can then redirect them again to
106 the page they were looking at, with a cookie that gives us the
107 username.
108 """
109 openid_request = self._make_consumer(environ).begin(
110 'https://' + config.vhost.openid.hostname)
111 openid_request.addExtension(
112 SRegRequest(required=['nickname']))
113 back_to = construct_url(environ)
114 raise HTTPMovedPermanently(openid_request.redirectURL(
115 config.codehosting.secure_codebrowse_root,
116 config.codehosting.secure_codebrowse_root + '+login/?'
117 + urllib.urlencode({'back_to':back_to})))
118
119 def _complete_login(self, environ, start_response):
120 """Complete the OpenID authentication process.
121
122 Here we handle the result of the OpenID process. If the process
123 succeeded, we record the username in the session and redirect the user
124 to the page they were trying to view that triggered the login attempt.
125 In the various failures cases we return a 401 Unauthorized response
126 with a brief explanation of what went wrong.
127 """
128 query = dict(parse_querystring(environ))
129 # Passing query['openid.return_to'] here is massive cheating, but
130 # given we control the endpoint who cares.
131 response = self._make_consumer(environ).complete(
132 query, query['openid.return_to'])
133 if response.status == SUCCESS:
134 self.log.error('open id response: SUCCESS')
135 sreg_info = SRegResponse.fromSuccessResponse(response)
136 environ[self.session_var]['user'] = sreg_info['nickname']
137 raise HTTPMovedPermanently(query['back_to'])
138 elif response.status == FAILURE:
139 self.log.error('open id response: FAILURE: %s', response.message)
140 exc = HTTPUnauthorized()
141 exc.explanation = response.message
142 raise exc
143 elif response.status == CANCEL:
144 self.log.error('open id response: CANCEL')
145 exc = HTTPUnauthorized()
146 exc.explanation = "Authetication cancelled."
147 raise exc
148 else:
149 self.log.error('open id response: UNKNOWN')
150 exc = HTTPUnauthorized()
151 exc.explanation = "Unknown OpenID response."
152 raise exc
153
154 def __call__(self, environ, start_response):
155 environ['loggerhead.static.url'] = environ['SCRIPT_NAME']
156 if environ['PATH_INFO'].startswith('/static/'):
157 path_info_pop(environ)
158 return static_app(environ, start_response)
159 elif environ['PATH_INFO'] == '/favicon.ico':
160 return favicon_app(environ, start_response)
161 elif environ['PATH_INFO'] == '/robots.txt':
162 return robots_app(environ, start_response)
163 elif environ['PATH_INFO'].startswith('/+login'):
164 return self._complete_login(environ, start_response)
165 path = environ['PATH_INFO']
166 trailingSlashCount = len(path) - len(path.rstrip('/'))
167 user = environ[self.session_var].get('user', LAUNCHPAD_ANONYMOUS)
168 try:
169 transport_type, info, trail = self.branchfs.translatePath(
170 user, urlutils.escape(path))
171 except xmlrpclib.Fault, f:
172 if faults.check_fault(f, faults.PathTranslationError):
173 raise HTTPNotFound()
174 elif faults.check_fault(f, faults.PermissionDenied):
175 # If we're not allowed to see the branch...
176 if environ['wsgi.url_scheme'] != 'https':
177 # ... the request shouldn't have come in over http, as
178 # requests for private branches over http should be
179 # redirected to https by the dynamic rewrite script we use
180 # (which runs before this code is reached), but just in
181 # case...
182 env_copy = environ.copy()
183 env_copy['wsgi.url_scheme'] = 'https'
184 raise HTTPMovedPermanently(construct_url(env_copy))
185 elif user != LAUNCHPAD_ANONYMOUS:
186 # ... if the user is already logged in and still can't see
187 # the branch, they lose.
188 exc = HTTPUnauthorized()
189 exc.explanation = "You are logged in as %s." % user
190 raise exc
191 else:
192 # ... otherwise, lets give them a chance to log in with
193 # OpenID.
194 return self._begin_login(environ, start_response)
195 else:
196 raise
197 if transport_type != BRANCH_TRANSPORT:
198 raise HTTPNotFound()
199 trail = urlutils.unescape(trail).encode('utf-8')
200 trail += trailingSlashCount * '/'
201 amount_consumed = len(path) - len(trail)
202 consumed = path[:amount_consumed]
203 branch_name = consumed.strip('/')
204 self.log.info('Using branch: %s', branch_name)
205 if trail and not trail.startswith('/'):
206 trail = '/' + trail
207 environ['PATH_INFO'] = trail
208 environ['SCRIPT_NAME'] += consumed.rstrip('/')
209 branch_url = urlparse.urljoin(
210 config.codehosting.internal_branch_by_id_root,
211 branch_id_to_path(info['id']))
212 branch_link = urlparse.urljoin(
213 config.codebrowse.launchpad_root, branch_name)
214 cachepath = os.path.join(
215 config.codebrowse.cachepath, branch_name[1:])
216 if not os.path.isdir(cachepath):
217 os.makedirs(cachepath)
218 self.log.info('branch_url: %s', branch_url)
219 try:
220 bzr_branch = branch.Branch.open(
221 branch_url, possible_transports=self.get_transports())
222 except errors.NotBranchError, err:
223 self.log.warning('Not a branch: %s', err)
224 raise HTTPNotFound()
225 bzr_branch.lock_read()
226 try:
227 view = BranchWSGIApp(
228 bzr_branch, branch_name, {'cachepath': cachepath},
229 self.graph_cache, branch_link=branch_link, served_url=None)
230 return view.app(environ, start_response)
231 finally:
232 bzr_branch.unlock()
0233
=== added file 'lib/launchpad_loggerhead/debug.py'
--- lib/launchpad_loggerhead/debug.py 1970-01-01 00:00:00 +0000
+++ lib/launchpad_loggerhead/debug.py 2010-04-27 03:46:17 +0000
@@ -0,0 +1,120 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4import thread
5import time
6
7from paste.request import construct_url
8
9
10def tabulate(cells):
11 """Format a list of lists of strings in a table.
12
13 The 'cells' are centered.
14
15 >>> print ''.join(tabulate(
16 ... [['title 1', 'title 2'],
17 ... ['short', 'rather longer']]))
18 title 1 title 2
19 short rather longer
20 """
21 widths = {}
22 for row in cells:
23 for col_index, cell in enumerate(row):
24 widths[col_index] = max(len(cell), widths.get(col_index, 0))
25 result = []
26 for row in cells:
27 result_row = ''
28 for col_index, cell in enumerate(row):
29 result_row += cell.center(widths[col_index] + 2)
30 result.append(result_row.rstrip() + '\n')
31 return result
32
33
34def threadpool_debug(app):
35 """Wrap `app` to provide debugging information about the threadpool state.
36
37 The returned application will serve debugging information about the state
38 of the threadpool at '/thread-debug' -- but only when accessed directly,
39 not when accessed through Apache.
40 """
41 def wrapped(environ, start_response):
42 if ('HTTP_X_FORWARDED_SERVER' in environ
43 or environ['PATH_INFO'] != '/thread-debug'):
44 environ['lp.timestarted'] = time.time()
45 return app(environ, start_response)
46 threadpool = environ['paste.httpserver.thread_pool']
47 start_response("200 Ok", [])
48 output = [("url", "time running", "time since last activity")]
49 now = time.time()
50 # Because we're accessing mutable structures without locks here,
51 # we're a bit cautious about things looking like we expect -- if a
52 # worker doesn't seem fully set up, we just ignore it.
53 for worker in threadpool.workers:
54 if not hasattr(worker, 'thread_id'):
55 continue
56 time_started, info = threadpool.worker_tracker.get(
57 worker.thread_id, (None, None))
58 if time_started is not None and info is not None:
59 real_time_started = info.get(
60 'lp.timestarted', time_started)
61 output.append(
62 map(str,
63 (construct_url(info),
64 now - real_time_started,
65 now - time_started,)))
66 return tabulate(output)
67 return wrapped
68
69
70def change_kill_thread_criteria(application):
71 """Interfere with threadpool so that threads are killed for inactivity.
72
73 The usual rules with paste's threadpool is that a thread that takes longer
74 than 'hung_thread_limit' seconds to process a request is considered hung
75 and more than 'kill_thread_limit' seconds is killed.
76
77 Because loggerhead streams its output, how long the entire request takes
78 to process depends on things like how fast the users internet connection
79 is. What we'd like to do is kill threads that don't _start_ to produce
80 output for 'kill_thread_limit' seconds.
81
82 What this class actually does is arrange things so that threads that
83 produce no output for 'kill_thread_limit' are killed, because that's the
84 rule Apache uses when interpreting ProxyTimeout.
85 """
86 def wrapped_application(environ, start_response):
87 threadpool = environ['paste.httpserver.thread_pool']
88 def reset_timer():
89 """Make this thread safe for another 'kill_thread_limit' seconds.
90
91 We do this by hacking the threadpool's record of when this thread
92 started to pretend that it started right now. Hacky, but it's
93 enough to fool paste.httpserver.ThreadPool.kill_hung_threads and
94 that's what matters.
95 """
96 threadpool.worker_tracker[thread.get_ident()][0] = time.time()
97 def response_hook(status, response_headers, exc_info=None):
98 # We reset the timer when the HTTP headers are sent...
99 reset_timer()
100 writer = start_response(status, response_headers, exc_info)
101 def wrapped_writer(arg):
102 # ... and whenever more output has been generated.
103 reset_timer()
104 return writer(arg)
105 return wrapped_writer
106 result = application(environ, response_hook)
107 # WSGI allows the application to return an iterable, which could be a
108 # generator that does significant processing between successive items,
109 # so we should reset the timer between each item.
110 #
111 # This isn't really necessary as loggerhead doesn't return any
112 # non-trivial iterables to the WSGI server. But it's probably better
113 # to cope with this case to avoid nasty suprises if loggerhead
114 # changes.
115 def reset_timer_between_items(iterable):
116 for item in iterable:
117 reset_timer()
118 yield item
119 return reset_timer_between_items(result)
120 return wrapped_application
0121
=== added file 'lib/launchpad_loggerhead/session.py'
--- lib/launchpad_loggerhead/session.py 1970-01-01 00:00:00 +0000
+++ lib/launchpad_loggerhead/session.py 2010-04-27 03:46:17 +0000
@@ -0,0 +1,73 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Simple paste-y session manager tuned for the needs of launchpad-loggerhead.
5"""
6
7import pickle
8
9from paste.auth.cookie import AuthCookieHandler, AuthCookieSigner
10
11
12class MyAuthCookieSigner(AuthCookieSigner):
13 """Fix a bug in AuthCookieSigner."""
14
15 def sign(self, content):
16 # XXX 2008-01-13 Michael Hudson: paste.auth.cookie generates bogus
17 # cookies when the value is long:
18 # http://trac.pythonpaste.org/pythonpaste/ticket/257. This is fixed
19 # now, so when a new version is released and packaged we can remove
20 # this class.
21 r = AuthCookieSigner.sign(self, content)
22 return r.replace('\n', '')
23
24
25class SessionHandler(object):
26 """Middleware that provides a cookie-based session.
27
28 The session dict is stored, pickled (and HMACed), in a cookie, so don't
29 store very much in the session!
30 """
31
32 def __init__(self, application, session_var, secret=None):
33 """Initialize a SessionHandler instance.
34
35 :param application: This is the wrapped application which will have
36 access to the ``environ[session_var]`` dictionary managed by this
37 middleware.
38 :param session_var: The key under which to store the session
39 dictionary in the environment.
40 :param secret: A secret value used for signing the cookie. If not
41 supplied, a new secret will be used for each instantiation of the
42 SessionHandler.
43 """
44 self.application = application
45 self.cookie_handler = AuthCookieHandler(
46 self._process, scanlist=[session_var],
47 signer=MyAuthCookieSigner(secret))
48 self.session_var = session_var
49
50 def __call__(self, environ, start_response):
51 # We need to put the request through the cookie handler first, so we
52 # can access the validated string in the environ in `_process` below.
53 return self.cookie_handler(environ, start_response)
54
55 def _process(self, environ, start_response):
56 """Process a request.
57
58 AuthCookieHandler takes care of getting the text value of the session
59 in and out of the cookie (and validating the text using HMAC) so we
60 just need to convert that string to and from a real dictionary using
61 pickle.
62 """
63 if self.session_var in environ:
64 session = pickle.loads(environ[self.session_var])
65 else:
66 session = {}
67 environ[self.session_var] = session
68 def response_hook(status, response_headers, exc_info=None):
69 session = environ.pop(self.session_var)
70 if session:
71 environ[self.session_var] = pickle.dumps(session)
72 return start_response(status, response_headers, exc_info)
73 return self.application(environ, response_hook)
074
=== added directory 'lib/launchpad_loggerhead/static'
=== added file 'lib/launchpad_loggerhead/static/robots.txt'
--- lib/launchpad_loggerhead/static/robots.txt 1970-01-01 00:00:00 +0000
+++ lib/launchpad_loggerhead/static/robots.txt 2010-04-27 03:46:17 +0000
@@ -0,0 +1,2 @@
1User-agent: *
2Disallow: /
03
=== added file 'scripts/start-loggerhead.py'
--- scripts/start-loggerhead.py 1970-01-01 00:00:00 +0000
+++ scripts/start-loggerhead.py 2010-04-27 03:46:17 +0000
@@ -0,0 +1,177 @@
1#!/usr/bin/python2.5 -S
2#
3# Copyright 2009, 2010 Canonical Ltd. This software is licensed under the
4# GNU Affero General Public License version 3 (see the file LICENSE).
5
6import _pythonpath
7
8import logging
9import os
10import sys
11
12from paste import httpserver
13from paste.deploy.config import PrefixMiddleware
14from paste.httpexceptions import HTTPExceptionHandler
15from paste.request import construct_url
16from paste.translogger import TransLogger
17
18from canonical.config import config
19import lp.codehosting
20
21LISTEN_HOST = '0.0.0.0'
22LISTEN_PORT = 8080
23THREADPOOL_WORKERS = 10
24
25
26class NoLockingFileHandler(logging.FileHandler):
27 """A version of logging.FileHandler that doesn't do it's own locking.
28
29 We experienced occasional hangs in production where gdb-ery on the server
30 revealed that we sometimes end up with many threads blocking on the RLock
31 held by the logging file handler, and log reading finds that an exception
32 managed to kill a thread in an unsafe window for RLock's.
33
34 Luckily, there's no real reason for us to take a lock during logging as
35 each log message translates to one call to .write on a file object, which
36 translates to one fwrite call, and it seems that this does enough locking
37 itself for our purposes.
38
39 So this handler just doesn't lock in log message handling.
40 """
41
42 def acquire(self):
43 pass
44
45 def release(self):
46 pass
47
48
49def setup_logging(home, foreground):
50 # i hate that stupid logging config format, so just set up logging here.
51
52 log_folder = config.codebrowse.log_folder
53 if not log_folder:
54 log_folder = os.path.join(home, 'logs')
55 if not os.path.exists(log_folder):
56 os.mkdir(log_folder)
57
58 f = logging.Formatter(
59 '%(levelname)-.3s [%(asctime)s.%(msecs)03d] [%(thread)d] %(name)s: %(message)s',
60 '%Y%m%d-%H:%M:%S')
61 debug_log = NoLockingFileHandler(os.path.join(log_folder, 'debug.log'))
62 debug_log.setLevel(logging.DEBUG)
63 debug_log.setFormatter(f)
64 if foreground:
65 stdout_log = logging.StreamHandler(sys.stdout)
66 stdout_log.setLevel(logging.DEBUG)
67 stdout_log.setFormatter(f)
68 f = logging.Formatter('[%(asctime)s.%(msecs)03d] %(message)s',
69 '%Y%m%d-%H:%M:%S')
70 access_log = NoLockingFileHandler(os.path.join(log_folder, 'access.log'))
71 access_log.setLevel(logging.INFO)
72 access_log.setFormatter(f)
73
74 logging.getLogger('').setLevel(logging.DEBUG)
75 logging.getLogger('').addHandler(debug_log)
76 logging.getLogger('wsgi').addHandler(access_log)
77
78 if foreground:
79 logging.getLogger('').addHandler(stdout_log)
80 else:
81 class S(object):
82 def write(self, str):
83 logging.getLogger().error(str.rstrip('\n'))
84 def flush(self):
85 pass
86 sys.stderr = S()
87
88
89
90foreground = False
91if len(sys.argv) > 1:
92 if sys.argv[1] == '-f':
93 foreground = True
94
95home = os.path.realpath(os.path.dirname(__file__))
96pidfile = os.path.join(home, 'loggerhead.pid')
97
98if not foreground:
99 sys.stderr.write('\n')
100 sys.stderr.write('Launching loggerhead into the background.\n')
101 sys.stderr.write('PID file: %s\n' % (pidfile,))
102 sys.stderr.write('\n')
103
104 from loggerhead.daemon import daemonize
105 daemonize(pidfile, home)
106
107setup_logging(home, foreground=foreground)
108
109log = logging.getLogger('loggerhead')
110log.info('Starting up...')
111
112log.info('Loading the bzr plugins...')
113from bzrlib.plugin import load_plugins
114load_plugins()
115
116import bzrlib.plugins
117if getattr(bzrlib.plugins, 'loom', None) is None:
118 log.error('Loom plugin loading failed.')
119
120from launchpad_loggerhead.debug import (
121 change_kill_thread_criteria, threadpool_debug)
122from launchpad_loggerhead.app import RootApp
123from launchpad_loggerhead.session import SessionHandler
124
125SESSION_VAR = 'lh.session'
126
127secret = open(os.path.join(config.root, config.codebrowse.secret_path)).read()
128
129app = RootApp(SESSION_VAR)
130app = HTTPExceptionHandler(app)
131app = SessionHandler(app, SESSION_VAR, secret)
132def log_on_request_start(app):
133 def wrapped(environ, start_response):
134 log = logging.getLogger('loggerhead')
135 log.info("Starting to process %s", construct_url(environ))
136 return app(environ, start_response)
137 return wrapped
138app = log_on_request_start(app)
139app = PrefixMiddleware(app)
140app = TransLogger(app)
141app = threadpool_debug(app)
142
143def set_scheme(app):
144 """Set wsgi.url_scheme in the environment correctly.
145
146 We serve requests that originated from both http and https, and
147 distinguish between them by adding a header in the https Apache config.
148 """
149 def wrapped(environ, start_response):
150 environ['wsgi.url_scheme'] = environ.pop(
151 'HTTP_X_FORWARDED_SCHEME', 'http')
152 return app(environ, start_response)
153 return wrapped
154app = set_scheme(app)
155app = change_kill_thread_criteria(app)
156
157try:
158 httpserver.serve(
159 app, host=LISTEN_HOST, port=LISTEN_PORT,
160 threadpool_workers=THREADPOOL_WORKERS,
161 threadpool_options={
162 # Kill threads after 300 seconds. This is insanely high, but
163 # lower enough than the default (1800 seconds!) that evidence
164 # suggests it will be hit occasionally, and there's very little
165 # chance of it having negative consequences.
166 'kill_thread_limit': 300,
167 # Check for threads that should be killed every 10 requests. The
168 # default is every 100, which is easily long enough for things to
169 # gum up completely in between checks.
170 'hung_check_period': 10,
171 })
172finally:
173 log.info('Shutdown.')
174 try:
175 os.remove(pidfile)
176 except OSError:
177 pass
0178
=== added file 'scripts/stop-loggerhead.py'
--- scripts/stop-loggerhead.py 1970-01-01 00:00:00 +0000
+++ scripts/stop-loggerhead.py 2010-04-27 03:46:17 +0000
@@ -0,0 +1,33 @@
1#!/usr/bin/python2.5 -S
2#
3# Copyright 2009, 2010 Canonical Ltd. This software is licensed under the
4# GNU Affero General Public License version 3 (see the file LICENSE).
5
6import _pythonpath
7
8import os
9import sys
10
11home = os.path.realpath(os.path.dirname(__file__))
12pidfile = os.path.join(home, 'loggerhead.pid')
13
14try:
15 f = open(pidfile, 'r')
16except IOError, e:
17 print 'No pid file found.'
18 sys.exit(1)
19
20pid = int(f.readline())
21
22try:
23 os.kill(pid, 0)
24except OSError, e:
25 print 'Stale pid file; server is not running.'
26 sys.exit(1)
27
28print
29print 'Shutting down previous server @ pid %d.' % (pid,)
30print
31
32import signal
33os.kill(pid, signal.SIGTERM)
034
=== modified file 'utilities/sourcedeps.conf'
--- utilities/sourcedeps.conf 2010-04-21 12:30:48 +0000
+++ utilities/sourcedeps.conf 2010-04-27 03:46:17 +0000
@@ -5,7 +5,6 @@
5bzr-svn lp:~launchpad-pqm/bzr-svn/devel;revno=27085bzr-svn lp:~launchpad-pqm/bzr-svn/devel;revno=2708
6cscvs lp:~launchpad-pqm/launchpad-cscvs/devel;revno=4326cscvs lp:~launchpad-pqm/launchpad-cscvs/devel;revno=432
7dulwich lp:~launchpad-pqm/dulwich/devel;revno=4187dulwich lp:~launchpad-pqm/dulwich/devel;revno=418
8launchpad-loggerhead lp:~launchpad-pqm/launchpad-loggerhead/devel;revno=54
9loggerhead lp:~launchpad-pqm/loggerhead/devel;revno=1748loggerhead lp:~launchpad-pqm/loggerhead/devel;revno=174
10lpreview lp:~launchpad-pqm/bzr-lpreview/devel;revno=239lpreview lp:~launchpad-pqm/bzr-lpreview/devel;revno=23
11mailman lp:~launchpad-pqm/mailman/2.1;revno=97610mailman lp:~launchpad-pqm/mailman/2.1;revno=976