Merge lp:~abentley/launchpad/job-db-user into lp:launchpad

Proposed by Aaron Bentley on 2010-02-24
Status: Merged
Approved by: Edwin Grubbs on 2010-02-24
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~abentley/launchpad/job-db-user
Merge into: lp:launchpad
Diff against target: 151 lines (+35/-16)
2 files modified
lib/lp/services/job/runner.py (+25/-14)
lib/lp/services/job/tests/test_runner.py (+10/-2)
To merge this branch: bzr merge lp:~abentley/launchpad/job-db-user
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) 2010-02-24 Approve on 2010-02-24
Review via email: mp+20081@code.launchpad.net
To post a comment you must log in.
Aaron Bentley (abentley) wrote :

= Summary =
Ensure Twisted job runner uses the correct database user. Currently,
update_preview_diff fails because it runs as lp_main, which is not permitted to
run on asuka.

== Proposed fix ==
Pass the database user as a parameter to the child process.

== Pre-implementation notes ==
None

== Implementation details ==
Unfortunately, Ampoule uses the JobRunnerProcess class as a ContextManager, not
the instance of JobRunnerProcess that it creates. I'll propose fixing that
in Ampoule itself, but this approach works in the meantime, and database users
are effectively global.

== Tests ==
bin/test test_runner -t timeout

== Demo and Q/A == Create a merge proposal on staging, get a LOSA to add
--twisted to the flags, push changes to the source branch, and see whether the
diff is updated.

= Launchpad lint =

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

Linting changed files:
  lib/lp/services/job/tests/test_runner.py
  lib/lp/services/job/runner.py

== Pylint notices ==

lib/lp/services/job/runner.py
    31: [F0401] Unable to import 'lazr.delegates' (No module named delegates)

Edwin Grubbs (edwin-grubbs) wrote :

>=== modified file 'lib/lp/services/job/runner.py'
>--- lib/lp/services/job/runner.py 2010-02-16 19:36:10 +0000
>+++ lib/lp/services/job/runner.py 2010-02-24 20:07:05 +0000
>@@ -203,7 +204,7 @@
> return cls(job_class.iterReady(), logger)
>
> @classmethod
>- def runFromSource(cls, job_source, logger):
>+ def runFromSource(cls, job_source, dbuser, logger):
> """Run all ready jobs provided by the specified source."""
> with removeSecurityProxy(job_source.contextManager()):
> logger.info("Running synchronously.")
>@@ -364,10 +368,10 @@
> self.terminated()
>
> @classmethod
>- def runFromSource(cls, job_source, logger, error_utility=None):
>+ def runFromSource(cls, job_source, dbuser, logger, error_utility=None):
> """Run all ready jobs provided by the specified source."""
> logger.info("Running through Twisted.")
>- runner = cls(job_source, logger, error_utility)
>+ runner = cls(job_source, dbuser, logger, error_utility)
> reactor.callWhenRunning(runner.runAll)
> handler = getsignal(SIGCHLD)
> try:

Hi Aaron,

This looks good. Please make the runFromSource() parameter lists match
for the two classes and add info in the docstring about whether the
dbuser parameter is used or ignored. The diff

merge-conditional

-Edwin

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/services/job/runner.py'
2--- lib/lp/services/job/runner.py 2010-02-16 19:36:10 +0000
3+++ lib/lp/services/job/runner.py 2010-02-24 21:47:18 +0000
4@@ -31,6 +31,7 @@
5 from lazr.delegates import delegates
6
7 from canonical.config import config
8+from canonical.lp import initZopeless
9 from canonical.launchpad import scripts
10 from canonical.launchpad.webapp import errorlog
11 from canonical.twistedsupport.task import (
12@@ -203,8 +204,11 @@
13 return cls(job_class.iterReady(), logger)
14
15 @classmethod
16- def runFromSource(cls, job_source, logger):
17- """Run all ready jobs provided by the specified source."""
18+ def runFromSource(cls, job_source, dbuser, logger):
19+ """Run all ready jobs provided by the specified source.
20+
21+ The dbuser parameter is ignored.
22+ """
23 with removeSecurityProxy(job_source.contextManager()):
24 logger.info("Running synchronously.")
25 runner = cls.fromReady(job_source, logger)
26@@ -240,19 +244,22 @@
27 class JobRunnerProcess(child.AMPChild):
28 """Base class for processes that run jobs."""
29
30- def __init__(self, job_source_name):
31+ def __init__(self, job_source_name, dbuser):
32 child.AMPChild.__init__(self)
33 module, name = job_source_name.rsplit('.', 1)
34 source_module = __import__(module, fromlist=[name])
35 self.job_source = getattr(source_module, name)
36 self.context_manager = self.job_source.contextManager()
37+ # icky, but it's really a global value anyhow.
38+ self.__class__.dbuser = dbuser
39
40- @staticmethod
41- def __enter__():
42+ @classmethod
43+ def __enter__(cls):
44 def handler(signum, frame):
45 raise TimeoutError
46 scripts.execute_zcml_for_scripts(use_web_security=False)
47 signal(SIGHUP, handler)
48+ initZopeless(dbuser=cls.dbuser)
49
50 @staticmethod
51 def __exit__(exc_type, exc_val, exc_tb):
52@@ -284,7 +291,7 @@
53 class TwistedJobRunner(BaseJobRunner):
54 """Run Jobs via twisted."""
55
56- def __init__(self, job_source, logger=None, error_utility=None):
57+ def __init__(self, job_source, dbuser, logger=None, error_utility=None):
58 env = {'PYTHONPATH': os.environ['PYTHONPATH'],
59 'PATH': os.environ['PATH']}
60 lp_config = os.environ.get('LPCONFIG')
61@@ -297,8 +304,8 @@
62 import_name = '%s.%s' % (
63 removeSecurityProxy(job_source).__module__, job_source.__name__)
64 self.pool = pool.ProcessPool(
65- JobRunnerProcess, ampChildArgs=[import_name], starter=starter,
66- min=0, timeout_signal=SIGHUP)
67+ JobRunnerProcess, ampChildArgs=[import_name, str(dbuser)],
68+ starter=starter, min=0, timeout_signal=SIGHUP)
69
70 def runJobInSubprocess(self, job):
71 """Run the job_class with the specified id in the process pool.
72@@ -364,10 +371,13 @@
73 self.terminated()
74
75 @classmethod
76- def runFromSource(cls, job_source, logger, error_utility=None):
77- """Run all ready jobs provided by the specified source."""
78+ def runFromSource(cls, job_source, dbuser, logger):
79+ """Run all ready jobs provided by the specified source.
80+
81+ The dbuser parameter is not ignored.
82+ """
83 logger.info("Running through Twisted.")
84- runner = cls(job_source, logger, error_utility)
85+ runner = cls(job_source, dbuser, logger)
86 reactor.callWhenRunning(runner.runAll)
87 handler = getsignal(SIGCHLD)
88 try:
89@@ -381,14 +391,15 @@
90 """Base class for scripts that run jobs."""
91
92 def __init__(self, runner_class=JobRunner):
93- dbuser = getattr(config, self.config_name).dbuser
94- super(JobCronScript, self).__init__(self.config_name, dbuser)
95+ self.dbuser = getattr(config, self.config_name).dbuser
96+ super(JobCronScript, self).__init__(self.config_name, self.dbuser)
97 self.runner_class = runner_class
98
99 def main(self):
100 errorlog.globalErrorUtility.configure(self.config_name)
101 job_source = getUtility(self.source_interface)
102- runner = self.runner_class.runFromSource(job_source, self.logger)
103+ runner = self.runner_class.runFromSource(
104+ job_source, self.dbuser, self.logger)
105 self.logger.info(
106 'Ran %d %s jobs.',
107 len(runner.completed_jobs), self.source_interface.__name__)
108
109=== modified file 'lib/lp/services/job/tests/test_runner.py'
110--- lib/lp/services/job/tests/test_runner.py 2010-02-12 20:43:54 +0000
111+++ lib/lp/services/job/tests/test_runner.py 2010-02-24 21:47:18 +0000
112@@ -26,6 +26,8 @@
113 from lp.services.job.model.job import Job
114 from lp.testing import TestCaseWithFactory
115 from canonical.launchpad.webapp import errorlog
116+from canonical.launchpad.webapp.interfaces import (
117+ IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)
118
119
120 class NullJob(BaseRunnableJob):
121@@ -283,6 +285,10 @@
122 def run(self):
123 if self.id == 2:
124 sleep(30)
125+ else:
126+ store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
127+ assert (
128+ 'user=branchscanner' in store._connection._raw_connection.dsn)
129
130
131 class ListLogger:
132@@ -306,7 +312,9 @@
133 followed by a job that is sure to time out.
134 """
135 logger = ListLogger()
136- runner = TwistedJobRunner.runFromSource(StuckJob, logger)
137+ runner = TwistedJobRunner.runFromSource(
138+ StuckJob, 'branchscanner', logger)
139+
140 self.assertEqual(1, len(runner.completed_jobs))
141 self.assertEqual(1, len(runner.incomplete_jobs))
142 oops = errorlog.globalErrorUtility.getLastOopsReport()
143@@ -327,7 +335,7 @@
144 class DummyRunner:
145
146 @classmethod
147- def runFromSource(cls, source, logger):
148+ def runFromSource(cls, source, dbuser, logger):
149 expected_config = errorlog.ErrorReportingUtility()
150 expected_config.configure('update_preview_diffs')
151 self.assertEqual(