Merge lp:~mwhudson/launchpad/lp-serve-report-branch-access-bug-532210 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: not available
Proposed branch: lp:~mwhudson/launchpad/lp-serve-report-branch-access-bug-532210
Merge into: lp:launchpad
Diff against target: 326 lines (+154/-11)
8 files modified
bzrplugins/lpserve.py (+3/-2)
lib/lp/codehosting/vfs/branchfs.py (+14/-6)
lib/lp/codehosting/vfs/branchfsclient.py (+9/-1)
lib/lp/codehosting/vfs/hooks.py (+25/-0)
lib/lp/codehosting/vfs/tests/test_branchfsclient.py (+37/-1)
lib/lp/codehosting/vfs/tests/test_hooks.py (+63/-0)
setup.py (+2/-1)
versions.cfg (+1/-0)
To merge this branch: bzr merge lp:~mwhudson/launchpad/lp-serve-report-branch-access-bug-532210
Reviewer Review Type Date Requested Status
Tim Penhey (community) Approve
Review via email: mp+21907@code.launchpad.net

Commit message

Report the branch being accessed in the lp-serve process' process name

Description of the change

Hi there,

This branch reports which branch an lp-serve accesses in its process title.

I didn't have a pre-imp call for this, and if I did I probably wouldn't be so worried that the code in lp.codehosting.vfs.hooks isn't in a sensibly named file.

Cheers,
mwh

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

Looks good. I hope it works :-)

review: Approve
Revision history for this message
Jonathan Lange (jml) wrote :

Cool! At first I thought this was about logging branch access to a file, but it's still great news.

This might be a silly question, but have you run this locally?

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

On 23/03/10 23:29, Jonathan Lange wrote:
> Cool! At first I thought this was about logging branch access to a file, but it's still great news.

Yeah, we should probably do that, but multiprocess synchronization argh.
  What I'd like is to have bzr lp-serve processes log to ~/.bzr.log.$pid
rather than all smearing into ~/.bzr.log but that was annoying last time
I looked into it.

> This might be a silly question, but have you run this locally?

Yes I have. It seems to work :)

Cheers,
mwh

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bzrplugins/lpserve.py'
--- bzrplugins/lpserve.py 2010-03-16 19:01:26 +0000
+++ bzrplugins/lpserve.py 2010-03-24 00:47:00 +0000
@@ -88,13 +88,14 @@
88 def run(self, user_id, port=None, upload_directory=None,88 def run(self, user_id, port=None, upload_directory=None,
89 mirror_directory=None, branchfs_endpoint_url=None, inet=False):89 mirror_directory=None, branchfs_endpoint_url=None, inet=False):
90 from lp.codehosting.bzrutils import install_oops_handler90 from lp.codehosting.bzrutils import install_oops_handler
91 from lp.codehosting.vfs import get_lp_server91 from lp.codehosting.vfs import get_lp_server, hooks
92 install_oops_handler(user_id)92 install_oops_handler(user_id)
93 four_gig = int(4e9)93 four_gig = int(4e9)
94 resource.setrlimit(resource.RLIMIT_AS, (four_gig, four_gig))94 resource.setrlimit(resource.RLIMIT_AS, (four_gig, four_gig))
95 seen_new_branch = hooks.SetProcTitleHook()
95 lp_server = get_lp_server(96 lp_server = get_lp_server(
96 int(user_id), branchfs_endpoint_url,97 int(user_id), branchfs_endpoint_url,
97 upload_directory, mirror_directory)98 upload_directory, mirror_directory, seen_new_branch.seen)
98 lp_server.start_server()99 lp_server.start_server()
99100
100 old_lockdir_timeout = lockdir._DEFAULT_TIMEOUT_SECONDS101 old_lockdir_timeout = lockdir._DEFAULT_TIMEOUT_SECONDS
101102
=== modified file 'lib/lp/codehosting/vfs/branchfs.py'
--- lib/lp/codehosting/vfs/branchfs.py 2010-02-14 23:02:37 +0000
+++ lib/lp/codehosting/vfs/branchfs.py 2010-03-24 00:47:00 +0000
@@ -368,16 +368,20 @@
368 path on that transport.368 path on that transport.
369 """369 """
370370
371 def __init__(self, scheme, authserver, user_id):371 def __init__(self, scheme, authserver, user_id,
372 seen_new_branch_hook=None):
372 """Construct a LaunchpadServer.373 """Construct a LaunchpadServer.
373374
374 :param scheme: The URL scheme to use.375 :param scheme: The URL scheme to use.
375 :param authserver: An XML-RPC client that implements callRemote.376 :param authserver: An XML-RPC client that implements callRemote.
376 :param user_id: The database ID for the user who is accessing377 :param user_id: The database ID for the user who is accessing
377 branches.378 branches.
379 :param seen_new_branch_hook: A callable that will be called once for
380 each branch accessed via this server.
378 """381 """
379 AsyncVirtualServer.__init__(self, scheme)382 AsyncVirtualServer.__init__(self, scheme)
380 self._authserver = BranchFileSystemClient(authserver, user_id)383 self._authserver = BranchFileSystemClient(
384 authserver, user_id, seen_new_branch_hook=seen_new_branch_hook)
381 self._is_start_server = False385 self._is_start_server = False
382386
383 def translateVirtualPath(self, virtual_url_fragment):387 def translateVirtualPath(self, virtual_url_fragment):
@@ -556,7 +560,7 @@
556 asyncTransportFactory = AsyncLaunchpadTransport560 asyncTransportFactory = AsyncLaunchpadTransport
557561
558 def __init__(self, authserver, user_id, hosted_transport,562 def __init__(self, authserver, user_id, hosted_transport,
559 mirror_transport):563 mirror_transport, seen_new_branch_hook=None):
560 """Construct a `LaunchpadServer`.564 """Construct a `LaunchpadServer`.
561565
562 See `_BaseLaunchpadServer` for more information.566 See `_BaseLaunchpadServer` for more information.
@@ -575,9 +579,12 @@
575 :param mirror_transport: A Bazaar `Transport` that points to the579 :param mirror_transport: A Bazaar `Transport` that points to the
576 "mirrored" area of Launchpad. See module docstring for more580 "mirrored" area of Launchpad. See module docstring for more
577 information.581 information.
582 :param seen_new_branch_hook: A callable that will be called once for
583 each branch accessed via this server.
578 """584 """
579 scheme = 'lp-%d:///' % id(self)585 scheme = 'lp-%d:///' % id(self)
580 super(LaunchpadServer, self).__init__(scheme, authserver, user_id)586 super(LaunchpadServer, self).__init__(
587 scheme, authserver, user_id, seen_new_branch_hook)
581 mirror_transport = get_readonly_transport(mirror_transport)588 mirror_transport = get_readonly_transport(mirror_transport)
582 self._transport_dispatch = TransportDispatch(589 self._transport_dispatch = TransportDispatch(
583 hosted_transport, mirror_transport)590 hosted_transport, mirror_transport)
@@ -641,7 +648,7 @@
641648
642649
643def get_lp_server(user_id, branchfs_endpoint_url=None, hosted_directory=None,650def get_lp_server(user_id, branchfs_endpoint_url=None, hosted_directory=None,
644 mirror_directory=None):651 mirror_directory=None, seen_new_branch_hook=None):
645 """Create a Launchpad server.652 """Create a Launchpad server.
646653
647 :param user_id: A unique database ID of the user whose branches are654 :param user_id: A unique database ID of the user whose branches are
@@ -649,6 +656,7 @@
649 :param branchfs_endpoint_url: URL for the branch file system end-point.656 :param branchfs_endpoint_url: URL for the branch file system end-point.
650 :param hosted_directory: Where the branches are uploaded to.657 :param hosted_directory: Where the branches are uploaded to.
651 :param mirror_directory: Where all Launchpad branches are mirrored.658 :param mirror_directory: Where all Launchpad branches are mirrored.
659 :param seen_new_branch_hook:
652 :return: A `LaunchpadServer`.660 :return: A `LaunchpadServer`.
653 """661 """
654 # Get the defaults from the config.662 # Get the defaults from the config.
@@ -667,7 +675,7 @@
667 mirror_transport = get_chrooted_transport(mirror_url)675 mirror_transport = get_chrooted_transport(mirror_url)
668 lp_server = LaunchpadServer(676 lp_server = LaunchpadServer(
669 BlockingProxy(branchfs_client), user_id,677 BlockingProxy(branchfs_client), user_id,
670 hosted_transport, mirror_transport)678 hosted_transport, mirror_transport, seen_new_branch_hook)
671 return lp_server679 return lp_server
672680
673681
674682
=== modified file 'lib/lp/codehosting/vfs/branchfsclient.py'
--- lib/lp/codehosting/vfs/branchfsclient.py 2009-06-25 04:06:00 +0000
+++ lib/lp/codehosting/vfs/branchfsclient.py 2010-03-24 00:47:00 +0000
@@ -46,18 +46,24 @@
46 """46 """
4747
48 def __init__(self, branchfs_endpoint, user_id, expiry_time=None,48 def __init__(self, branchfs_endpoint, user_id, expiry_time=None,
49 _now=time.time):49 seen_new_branch_hook=None, _now=time.time):
50 """Construct a caching branchfs_endpoint.50 """Construct a caching branchfs_endpoint.
5151
52 :param branchfs_endpoint: An XML-RPC proxy that implements callRemote.52 :param branchfs_endpoint: An XML-RPC proxy that implements callRemote.
53 :param user_id: The database ID of the user who will be making these53 :param user_id: The database ID of the user who will be making these
54 requests. An integer.54 requests. An integer.
55 :param expiry_time: If supplied, only cache the results of
56 translatePath for this many seconds. If not supplied, cache the
57 results of translatePath for as long as this instance exists.
58 :param seen_new_branch_hook: A callable that will be called with the
59 unique_name of each new branch that is accessed.
55 """60 """
56 self._branchfs_endpoint = branchfs_endpoint61 self._branchfs_endpoint = branchfs_endpoint
57 self._cache = {}62 self._cache = {}
58 self._user_id = user_id63 self._user_id = user_id
59 self.expiry_time = expiry_time64 self.expiry_time = expiry_time
60 self._now = _now65 self._now = _now
66 self.seen_new_branch_hook = seen_new_branch_hook
6167
62 def _getMatchedPart(self, path, transport_tuple):68 def _getMatchedPart(self, path, transport_tuple):
63 """Return the part of 'path' that the endpoint actually matched."""69 """Return the part of 'path' that the endpoint actually matched."""
@@ -77,6 +83,8 @@
77 (transport_type, data, trailing_path) = transport_tuple83 (transport_type, data, trailing_path) = transport_tuple
78 matched_part = self._getMatchedPart(path, transport_tuple)84 matched_part = self._getMatchedPart(path, transport_tuple)
79 if transport_type == BRANCH_TRANSPORT:85 if transport_type == BRANCH_TRANSPORT:
86 if self.seen_new_branch_hook:
87 self.seen_new_branch_hook(matched_part.strip('/'))
80 self._cache[matched_part] = (transport_type, data, self._now())88 self._cache[matched_part] = (transport_type, data, self._now())
81 return transport_tuple89 return transport_tuple
8290
8391
=== added file 'lib/lp/codehosting/vfs/hooks.py'
--- lib/lp/codehosting/vfs/hooks.py 1970-01-01 00:00:00 +0000
+++ lib/lp/codehosting/vfs/hooks.py 2010-03-24 00:47:00 +0000
@@ -0,0 +1,25 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Implementations for the `seen_new_branch_hook` of `BranchFileSystemClient`.
5"""
6
7__metaclass__ = type
8__all__ = ['SetProcTitleHook']
9
10import setproctitle
11
12
13class SetProcTitleHook:
14 """Use seen() as the hook to report branch access in ps(1) output."""
15
16 def __init__(self, setproctitle_mod=None):
17 if setproctitle_mod is None:
18 setproctitle_mod = setproctitle
19 self.setproctitle_mod = setproctitle_mod
20 self.basename = setproctitle_mod.getproctitle()
21
22 def seen(self, branch_url):
23 branch_url = branch_url.strip('/')
24 self.setproctitle_mod.setproctitle(
25 self.basename + ' BRANCH:' + branch_url)
026
=== modified file 'lib/lp/codehosting/vfs/tests/test_branchfsclient.py'
--- lib/lp/codehosting/vfs/tests/test_branchfsclient.py 2009-06-25 04:06:00 +0000
+++ lib/lp/codehosting/vfs/tests/test_branchfsclient.py 2010-03-24 00:47:00 +0000
@@ -36,13 +36,14 @@
36 """36 """
37 self.fake_time.advance(amount)37 self.fake_time.advance(amount)
3838
39 def makeClient(self, expiry_time=None):39 def makeClient(self, expiry_time=None, seen_new_branch_hook=None):
40 """Make a `BranchFileSystemClient`.40 """Make a `BranchFileSystemClient`.
4141
42 The created client interacts with the InMemoryFrontend.42 The created client interacts with the InMemoryFrontend.
43 """43 """
44 return BranchFileSystemClient(44 return BranchFileSystemClient(
45 self._xmlrpc_client, self.user.id, expiry_time=expiry_time,45 self._xmlrpc_client, self.user.id, expiry_time=expiry_time,
46 seen_new_branch_hook=seen_new_branch_hook,
46 _now=self.fake_time.now)47 _now=self.fake_time.now)
4748
48 def test_translatePath(self):49 def test_translatePath(self):
@@ -213,6 +214,41 @@
213 return deferred.addCallbacks(214 return deferred.addCallbacks(
214 translated_successfully, failed_translation)215 translated_successfully, failed_translation)
215216
217 def test_seen_new_branch_hook_called_for_new_branch(self):
218 # A callable passed as the seen_new_branch_hook when constructing a
219 # BranchFileSystemClient will be called with a previously unseen
220 # branch's unique_name when a path for a that branch is translated for
221 # the first time.
222 seen_branches = []
223 client = self.makeClient(seen_new_branch_hook=seen_branches.append)
224 branch = self.factory.makeAnyBranch()
225 client.translatePath('/' + branch.unique_name + '/trailing')
226 self.assertEqual([branch.unique_name], seen_branches)
227
228 def test_seen_new_branch_hook_called_for_each_branch(self):
229 # The seen_new_branch_hook is called for a each branch that is
230 # accessed.
231 seen_branches = []
232 client = self.makeClient(seen_new_branch_hook=seen_branches.append)
233 branch1 = self.factory.makeAnyBranch()
234 branch2 = self.factory.makeAnyBranch()
235 client.translatePath('/' + branch1.unique_name + '/trailing')
236 client.translatePath('/' + branch2.unique_name + '/trailing')
237 self.assertEqual(
238 [branch1.unique_name, branch2.unique_name], seen_branches)
239
240 def test_seen_new_branch_hook_called_once_for_a_new_branch(self):
241 # The seen_new_branch_hook is only called once for a given branch.
242 seen_branches = []
243 client = self.makeClient(seen_new_branch_hook=seen_branches.append)
244 branch1 = self.factory.makeAnyBranch()
245 branch2 = self.factory.makeAnyBranch()
246 client.translatePath('/' + branch1.unique_name + '/trailing')
247 client.translatePath('/' + branch2.unique_name + '/trailing')
248 client.translatePath('/' + branch1.unique_name + '/different')
249 self.assertEqual(
250 [branch1.unique_name, branch2.unique_name], seen_branches)
251
216252
217class TestTrapFault(TestCase):253class TestTrapFault(TestCase):
218 """Tests for `trap_fault`."""254 """Tests for `trap_fault`."""
219255
=== added file 'lib/lp/codehosting/vfs/tests/test_hooks.py'
--- lib/lp/codehosting/vfs/tests/test_hooks.py 1970-01-01 00:00:00 +0000
+++ lib/lp/codehosting/vfs/tests/test_hooks.py 2010-03-24 00:47:00 +0000
@@ -0,0 +1,63 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for the hooks in lp.codehosting.vfs.hooks."""
5
6__metaclass__ = type
7
8import unittest
9
10from lp.codehosting.vfs.hooks import SetProcTitleHook
11from lp.testing import TestCase
12
13
14class FakeSetProcTitleModule:
15 """A fake for the setproctitle module.
16
17 The `setproctitle` module (obviously) has global effects, so can't really
18 be used in unit tests. Instances of this class can be used as a safe
19 replacement.
20 """
21
22 def __init__(self, initial_title):
23 self.title = initial_title
24
25 def getproctitle(self):
26 return self.title
27
28 def setproctitle(self, new_title):
29 self.title = new_title
30
31
32class TestSetProcTitleHook(TestCase):
33 """Tests for `SetProcTitleHook`."""
34
35 def test_hook_once(self):
36 # Calling the hook once records the passed branch identifier in the
37 # process title.
38 initial_title = self.factory.getUniqueString()
39 setproctitle_mod = FakeSetProcTitleModule(initial_title)
40 branch_url = self.factory.getUniqueString()
41 hook = SetProcTitleHook(setproctitle_mod)
42 hook.seen(branch_url)
43 self.assertEqual(
44 initial_title + " BRANCH:" + branch_url,
45 setproctitle_mod.getproctitle())
46
47 def test_hook_twice(self):
48 # Calling the hook twice replaces the first branch identifier in the
49 # process title.
50 initial_title = self.factory.getUniqueString()
51 setproctitle_mod = FakeSetProcTitleModule(initial_title)
52 branch_url1 = self.factory.getUniqueString()
53 branch_url2 = self.factory.getUniqueString()
54 hook = SetProcTitleHook(setproctitle_mod)
55 hook.seen(branch_url1)
56 hook.seen(branch_url2)
57 self.assertEqual(
58 initial_title + " BRANCH:" + branch_url2,
59 setproctitle_mod.getproctitle())
60
61
62def test_suite():
63 return unittest.TestLoader().loadTestsFromName(__name__)
064
=== modified file 'setup.py'
--- setup.py 2010-03-19 14:25:54 +0000
+++ setup.py 2010-03-24 00:47:00 +0000
@@ -54,12 +54,13 @@
54 'pytz',54 'pytz',
55 # This appears to be a broken indirect dependency from zope.security:55 # This appears to be a broken indirect dependency from zope.security:
56 'RestrictedPython',56 'RestrictedPython',
57 'setproctitle',
57 'setuptools',58 'setuptools',
58 'sourcecodegen',59 'sourcecodegen',
59 'storm',60 'storm',
60 'testtools',61 'testtools',
61 'transaction',62 'transaction',
62 'Twisted', 63 'Twisted',
63 'wadllib',64 'wadllib',
64 'z3c.pt',65 'z3c.pt',
65 'z3c.ptcompat',66 'z3c.ptcompat',
6667
=== modified file 'versions.cfg'
--- versions.cfg 2010-03-23 18:27:27 +0000
+++ versions.cfg 2010-03-24 00:47:00 +0000
@@ -52,6 +52,7 @@
52pytz = 2009l52pytz = 2009l
53RestrictedPython = 3.5.153RestrictedPython = 3.5.1
54roman = 1.4.054roman = 1.4.0
55setproctitle = 1.0
55setuptools = 0.6c956setuptools = 0.6c9
56simplejson = 2.0.957simplejson = 2.0.9
57simplesettings = 0.458simplesettings = 0.4