Merge lp:~edwin-grubbs/launchpad/bug-615654-jobqueue-cron-script into lp:launchpad/db-devel
| Status: | Merged |
|---|---|
| Approved by: | Aaron Bentley on 2010-10-04 |
| Approved revision: | no longer in the source branch. |
| Merged at revision: | 9873 |
| Proposed branch: | lp:~edwin-grubbs/launchpad/bug-615654-jobqueue-cron-script |
| Merge into: | lp:launchpad/db-devel |
| Prerequisite: | lp:~edwin-grubbs/launchpad/bug-615654-queue-addmember-emails |
| Diff against target: |
854 lines (+512/-71) 17 files modified
cronscripts/merge-proposal-jobs.py (+1/-1) cronscripts/process-job-source-groups.py (+114/-0) cronscripts/process-job-source.py (+65/-0) database/schema/security.cfg (+16/-0) lib/canonical/config/schema-lazr.conf (+14/-0) lib/canonical/launchpad/scripts/tests/__init__.py (+1/-1) lib/lp/archiveuploader/tests/test_ppauploadprocessor.py (+2/-0) lib/lp/code/interfaces/branchmergeproposal.py (+2/-1) lib/lp/registry/interfaces/persontransferjob.py (+10/-0) lib/lp/registry/model/persontransferjob.py (+0/-10) lib/lp/registry/stories/person/xx-approve-members.txt (+3/-0) lib/lp/registry/tests/test_membership_notification_job_creation.py (+65/-0) lib/lp/registry/tests/test_process_job_sources_cronjob.py (+125/-0) lib/lp/services/job/interfaces/job.py (+8/-0) lib/lp/services/job/runner.py (+32/-16) lib/lp/services/scripts/base.py (+44/-32) lib/lp/testing/__init__.py (+10/-10) |
| To merge this branch: | bzr merge lp:~edwin-grubbs/launchpad/bug-615654-jobqueue-cron-script |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Aaron Bentley (community) | 2010-09-30 | Approve on 2010-10-04 | |
| Gavin Panella (community) | 2010-09-28 | Approve on 2010-09-30 | |
|
Review via email:
|
|||
Description of the Change
Summary
-------
This branch adds a cron script to process the
IMembershipNoti
new job sources can be processed by just adding configs to
schema-lazr.conf, as long as it uses a crontab_group that already has a
crontab entry configured in production.
Implementation details
-------
New db users and configs for cronjobs:
database/
lib/
Cronscripts and tests.
cronscripts
cronscripts
lib/
lib/
Improved error message.
lib/
Added get() to IJobSource definition, since TwistedJobRunner needs it.
Moved the get() method into the class that actually provides IJobSource.
lib/
lib/
Cruft from previous branch.
lib/
Eliminate the need for TwistedJobRunner cron scripts to be run with
bin/py by importing _pythonpath in ampoule child processes like normal
cron scripts. Eliminated the need to specify the error_dir, oops_prefix,
and copy_to_zlog when the defaults in [error_reports] are fine.
lib/
Tests
-----
./bin/test -vv -t 'test_membershi
Demo and Q/A
------------
* Add a member to a team.
* Change the member status to ADMIN.
* Run ./cronscripts/
* Check email.
| Aaron Bentley (abentley) wrote : | # |
Gavin, radical changes in the way we create and run tasks are the purview of the NewTaskSystem. Not having to create new database users is already listed in https:/
| Edwin Grubbs (edwin-grubbs) wrote : | # |
> Hi Edwin,
>
> This branch looks good.
Thanks for the review. Unfortunately, abentley had already started reviewing it, but it was all done on IRC up to this point. I should have just listed him as the requested reviewer. Sorry for the duplicate effort. I'll try to answer your questions below.
> I know you're adding ProcessJobSourc
> membership stuff, but it still seems like a lot of work to do
> something asynchronously. Moving something out of the app server takes
> a lot of plumbing work, and it's not trivial plumbing work either.
>
> I'd really like a way to create, in the app server, something like a
> persistent closure that can be run later, there or somewhere
> else. When run it would have the same user and database access as in
> the app server thread that spawned it. Creating a new job now seems to
> involve creating tables, database users, job sources, and so on, and
> it's getting in the way of the actual work that needs doing.
>
> I'm rambling on, and it's not a fault in this branch, but after doing
> this recently, do you have any thoughts on that?
>
> Gavin.
It seems that the reason we don't support a closure method for processing the jobs is that there are a lot of security restrictions placed on us. For example, each job class is required to have its own dbuser, so that the losas can figure out which job class is causing problems on the db. Of course, the job system seems much better suited for really slow processes, and it might be worthwhile to have a secondary solution that just moves the processing after the request response.
> [1]
>
> + for job_source in selected_
> + child = subprocess.
> + children.
> + for child in children:
> + child.wait()
>
> Is selected_
> be one day, consider filing a bug to limit the number of concurrency
> processes.
Currently, each job source class is given its own process. If we run into a problem with too many concurrent jobs being processed, the job source classes can be reorganized into different crontab_groups, and then different crontab entries can process the jobs at different times. We are not really interested in a long term solution for this, since the job system will most likely be moved to some kind of message queue, so the cron scripts for job processing will disappear.
> [2]
>
> + for child in children:
> + child.wait()
>
> Is there any value in checking and doing something with the exit code
> from the child?
No, waiting for the children to finish is really only helpful for testing, so that all the output from the child processes can be gathered. The cron scripts log to files, and I am actually making the default behavior not to wait on the child processes, so that one really long running child process does not cause the other job sources not to run the next time the crontab entry calls it, because the parent process still has its lock file.
> [3]
>
> + packages=
>
> I just looked again at ...
| Aaron Bentley (abentley) wrote : | # |
Waiting for children to exit will mean that no new jobs can be processed until all the jobs complete. This may severely impair responsiveness. I suggest starting the subprocesses and then exiting.
The NoErrorOptionParser should not exist. Option handling should be taken care of in the ProcessJobSource class. If the layering is getting in our way, we should fix it, not work around it.
Unless you are specifically testing stringification, you should not stringify objects in tests, so that your tests are not fragile.
In test_setstatus_
Since you do person_
Please use lp.testing.
In test_processed, I don't think you really care what the database id of the job is, so use assertTextMatch
get isn't required to run jobs under the non-twisted job runner, so it seems like an unnecessary requirement on IJobSource.
Perhaps ProcessJobSource should go in lp.services.
test_empty_queue looks fragile because it includes the directory of the lock file. Perhaps another case for assertTextMatch
Perhaps PYTHONPATH and LPCONFIG should be handled by common code, eg:
for name in ['PYTHONPATH', 'LPCONFIG']:
if name in os.environ:
env[name] = os.environ[name]
| Gavin Panella (allenap) wrote : | # |
> Thanks for the review. Unfortunately, abentley had already started reviewing
> it, but it was all done on IRC up to this point. I should have just listed him
> as the requested reviewer. Sorry for the duplicate effort. I'll try to answer
> your questions below.
No worries. It's good that Aaron also reviewed this. He spotted a lot
of stuff that I skimmed over; I didn't take as long on this review as
I ought to have done.
>
> > I know you're adding ProcessJobSourc
> > membership stuff, but it still seems like a lot of work to do
> > something asynchronously. Moving something out of the app server takes
> > a lot of plumbing work, and it's not trivial plumbing work either.
> >
> > I'd really like a way to create, in the app server, something like a
> > persistent closure that can be run later, there or somewhere
> > else. When run it would have the same user and database access as in
> > the app server thread that spawned it. Creating a new job now seems to
> > involve creating tables, database users, job sources, and so on, and
> > it's getting in the way of the actual work that needs doing.
> >
> > I'm rambling on, and it's not a fault in this branch, but after doing
> > this recently, do you have any thoughts on that?
> >
> > Gavin.
>
>
> It seems that the reason we don't support a closure method for processing the
> jobs is that there are a lot of security restrictions placed on us. For
> example, each job class is required to have its own dbuser, so that the losas
> can figure out which job class is causing problems on the db. Of course, the
> job system seems much better suited for really slow processes, and it might be
> worthwhile to have a secondary solution that just moves the processing after
> the request response.
Yes, that sounds good. As suggested by Aaron, I'll add it to the
wishlist.
>
> > [1]
> >
> > + for job_source in selected_
> > + child = subprocess.
> > + children.
> > + for child in children:
> > + child.wait()
> >
> > Is selected_
> > be one day, consider filing a bug to limit the number of concurrency
> > processes.
>
>
> Currently, each job source class is given its own process. If we run into a
> problem with too many concurrent jobs being processed, the job source classes
> can be reorganized into different crontab_groups, and then different crontab
> entries can process the jobs at different times. We are not really interested
> in a long term solution for this, since the job system will most likely be
> moved to some kind of message queue, so the cron scripts for job processing
> will disappear.
Okay, fair enough.
>
> > [2]
> >
> > + for child in children:
> > + child.wait()
> >
> > Is there any value in checking and doing something with the exit code
> > from the child?
>
>
> No, waiting for the children to finish is really only helpful for testing, so
> that all the output from the child processes can be gathered. The cron scripts
> log to files, and I am actually making the default behavi...
| Edwin Grubbs (edwin-grubbs) wrote : | # |
On Thu, Sep 30, 2010 at 11:02 AM, Aaron Bentley <email address hidden> wrote:
> Review: Needs Fixing
> Waiting for children to exit will mean that no new jobs can be processed until all the jobs complete. This may severely impair responsiveness. I suggest starting the subprocesses and then exiting.
It no longer waits for child processes by default. I added an option
to wait, which is convenient when running it from the command line
when experimenting.
> The NoErrorOptionParser should not exist. Option handling should be taken care of in the ProcessJobSource class. If the layering is getting in our way, we should fix it, not work around it.
I moved most of the setup code to the run() methods in LaunchpadScript
and LaunchpadCronScript so __init__ does very little now besides
setting up the parser attribute.
> Unless you are specifically testing stringification, you should not stringify objects in tests, so that your tests are not fragile.
Fixed.
> In test_setstatus_
I'm not sure why I was thinking I would need that. Removed.
> Since you do person_
Fixed.
> Please use lp.testing.
Fixed.
> In test_processed, I don't think you really care what the database id of the job is, so use assertTextMatc
Fixed.
> get isn't required to run jobs under the non-twisted job runner, so it seems like an unnecessary requirement on IJobSource.
ok, I've moved get() to a new ITwistedJobSource interface.
> Perhaps ProcessJobSource should go in lp.services.
I'd rather wait until we have a reason for subclassing from it, which
I hope won't happen before we stop using cron for job processing.
> test_empty_queue looks fragile because it includes the directory of the lock file. Perhaps another case for assertTextMatch
Fixed.
> Perhaps PYTHONPATH and LPCONFIG should be handled by common code, eg:
> for name in ['PYTHONPATH', 'LPCONFIG']:
> if name in os.environ:
> env[name] = os.environ[name]
Fixed.
Incremental diff:
=== modified file 'cronscripts/
--- cronscripts/
+++ cronscripts/
@@ -47,6 +47,13 @@
'-e', '--exclude', dest='excluded_
+ self.parser.
+ '--wait', dest='do_wait', default=False, action=
+ help="Wait for the child processes to finish. This is useful "
+ "for testing, but it shouldn't be used in a cronjob, since "
+ "it would prevent the cronjob from processing new jobs "
+ "if just one of the child processes is still processing, "
...
| Edwin Grubbs (edwin-grubbs) wrote : | # |
I realized that I could make process-
=== modified file 'cronscripts/
--- cronscripts/
+++ cronscripts/
@@ -24,12 +24,8 @@
"For more help, run:\n"
" cronscripts/
- def configure(self):
- """Override configs main() is called by run().
-
- It is done here instead of __init__(), so that we get to use its
- option parser.
- """
+ def __init__(self):
+ super(ProcessJo
if len(self.args) != 1:
@@ -54,5 +50,4 @@
if __name__ == '__main__':
script = ProcessJobSource()
- script.configure()
script.
| Edwin Grubbs (edwin-grubbs) wrote : | # |
I ran into some problems merging in db-devel due to some changes in LaunchpadCronSc
Instead of an incremental diff, here is a full diff of the affected files.
=== added file 'cronscripts/
--- cronscripts/
+++ cronscripts/
@@ -0,0 +1,69 @@
+#!/usr/bin/python -S
+#
+# Copyright 2009, 2010 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Handle jobs for a specified job source class."""
+
+__metaclass__ = type
+
+import sys
+
+import _pythonpath
+from twisted.python import log
+
+from canonical.config import config
+from lp.services.job import runner
+from lp.services.
+
+
+class ProcessJobSourc
+ """Run jobs for a specified job source class."""
+ usage = (
+ "Usage: %prog [options] JOB_SOURCE\n\n"
+ "For more help, run:\n"
+ " cronscripts/
+
+ def __init__(self):
+ super(ProcessJo
+ # The fromlist argument is necessary so that __import__()
+ # returns the bottom submodule instead of the top one.
+ module = __import_
+ fromlist=
+ self.source_
+
+ @property
+ def config_name(self):
+ return self.job_
+
+ @property
+ def name(self):
+ return 'process-
+
+ @property
+ def dbuser(self):
+ return self.config_
+
+ @property
+ def runner_class(self):
+ runner_class_name = getattr(
+ self.config_
+ # Override attributes that are normally set in __init__().
+ return getattr(runner, runner_class_name)
+
+ def handle_
+ if len(self.args) != 1:
+ self.parser.
+ sys.exit(1)
+ self.job_
+ super(ProcessJo
+
+ def main(self):
+ if self.options.
+ log.startLoggin
+ super(ProcessJo
+
+
+if __name__ == '__main__':
+ script = ProcessJobSource()
+ script.
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -77,7 +77,6 @@
# We redefine __eq__ and __ne__ here to prevent the security proxy
# from mucking up our comparisons in tests and elsewhere.
-
def __eq__(self, job):
return (
@@ -337,13 +336,12 @@
"""Run Jobs via twisted."""
def __init__(self, job_source, dbuser, logger=None, error_utility=
- env = {'PYTHONPATH': os.e...
| Aaron Bentley (abentley) wrote : | # |
This looks good, except that there are now two definitions assertTextMatch

Hi Edwin,
This branch looks good.
I know you're adding ProcessJobSourc eGroups in here as well as the
membership stuff, but it still seems like a lot of work to do
something asynchronously. Moving something out of the app server takes
a lot of plumbing work, and it's not trivial plumbing work either.
I'd really like a way to create, in the app server, something like a
persistent closure that can be run later, there or somewhere
else. When run it would have the same user and database access as in
the app server thread that spawned it. Creating a new job now seems to
involve creating tables, database users, job sources, and so on, and
it's getting in the way of the actual work that needs doing.
I'm rambling on, and it's not a fault in this branch, but after doing
this recently, do you have any thoughts on that?
Gavin.
[1]
+ for job_source in selected_ job_sources: Popen(child_ args + [job_source]) append( child)
+ child = subprocess.
+ children.
+ for child in children:
+ child.wait()
Is selected_ job_sources ever likely to be large? If not now, but could
be one day, consider filing a bug to limit the number of concurrency
processes.
[2]
+ for child in children:
+ child.wait()
Is there any value in checking and doing something with the exit code
from the child?
[3]
+ packages= ('_pythonpath' , 'twisted', 'ampoule'), env=env)
I just looked again at _pythonpath.py and now I can't see.