Merge lp:~jtv/launchpad/db-bug-768342 into lp:launchpad/db-devel

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: no longer in the source branch.
Merged at revision: 10473
Proposed branch: lp:~jtv/launchpad/db-bug-768342
Merge into: lp:launchpad/db-devel
Prerequisite: lp:~jtv/launchpad/db-bug-735621
Diff against target: 88 lines (+32/-15)
2 files modified
lib/lp/archivepublisher/scripts/generate_contents_files.py (+27/-10)
lib/lp/services/command_spawner.py (+5/-5)
To merge this branch: bzr merge lp:~jtv/launchpad/db-bug-768342
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+58685@code.launchpad.net

This proposal supersedes a proposal from 2011-04-21.

Commit message

[r=allenap][bug=768342] Log "live" output when generate-contents script runs apt-ftparchive.

Description of the change

= Summary =

The generate-contents script runs apt-ftparchive, which may take a long time. It's not very nice to save up all its log output and dump it just once at the end: we want live output.

== Proposed fix ==

Luckily this is all neatly isolated in a function called "execute." I replaced its subprocess.Popen implementation with one based on CommandSpawner (and its helpful friends for logging and result tracking).

== Pre-implementation notes ==

This was Julian's one gripe from testing the branch for bug 735621, currently landing, which is a requisit for this one. We couldn't think of any meaningful tests for it, since I just re-implemented an existing, tested function.

== Implementation details ==

CommandSpawner is really meant for running multiple commands in parallel, but there's nothing against using it for just one.

== Tests ==

{{{
./bin/test -vvc lp.archivepublisher.tests.test_generate_contents
}}}

== Demo and Q/A ==

Run the generate-contents script on dogfood (or some other sizable archive) and watch its output. It won't be exactly per-line granularity because of buffering, but there should be some output from apt-ftparchive before it completes.

= Launchpad lint =

This lint was previously present and is, as far as I can see, inevitable:

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/canonical/config/schema-lazr.conf
  lib/lp/archivepublisher/tests/test_generate_contents_files.py
  lib/lp/archivepublisher/scripts/generate_contents_files.py
  lib/canonical/launchpad/ftests/script.py
  cronscripts/publishing/gen-contents/apt_conf_dist.template
  cronscripts/publishing/gen-contents/Contents.top
  scripts/generate-contents-files.py
  cronscripts/publishing/gen-contents/apt_conf_header.template
  lib/lp/services/command_spawner.py

./lib/canonical/config/schema-lazr.conf
     536: Line exceeds 78 characters.
     619: Line exceeds 78 characters.
     995: Line exceeds 78 characters.
    1084: Line exceeds 78 characters.
./cronscripts/publishing/gen-contents/apt_conf_dist.template
       4: Line exceeds 78 characters.
       5: Line exceeds 78 characters.
./scripts/generate-contents-files.py
       8: '_pythonpath' imported but unused

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

I like CommandSpawner :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/archivepublisher/scripts/generate_contents_files.py'
2--- lib/lp/archivepublisher/scripts/generate_contents_files.py 2011-04-21 14:28:35 +0000
3+++ lib/lp/archivepublisher/scripts/generate_contents_files.py 2011-04-21 14:28:37 +0000
4@@ -13,9 +13,13 @@
5 from zope.component import getUtility
6
7 from canonical.config import config
8-from canonical.launchpad.ftests.script import run_command
9 from lp.archivepublisher.config import getPubConfig
10 from lp.registry.interfaces.distribution import IDistributionSet
11+from lp.services.command_spawner import (
12+ CommandSpawner,
13+ OutputLineHandler,
14+ ReturnCodeReceiver,
15+ )
16 from lp.services.scripts.base import (
17 LaunchpadScript,
18 LaunchpadScriptFailure,
19@@ -63,18 +67,31 @@
20 """Execute a shell command.
21
22 :param logger: Output from the command will be logged here.
23- :param command_line: Command to execute, as a list of tokens.
24+ :param command: Command to execute, as a string.
25+ :param args: Optional list of arguments for `command`.
26 :raises LaunchpadScriptFailure: If the command returns failure.
27 """
28- if args is None:
29- description = command
30- else:
31- description = command + ' ' + ' '.join(args)
32+ command_line = [command]
33+ if args is not None:
34+ command_line += args
35+ description = ' '.join(command_line)
36+
37 logger.debug("Execute: %s", description)
38- retval, stdout, stderr = run_command(command, args)
39- logger.debug(stdout)
40- logger.warn(stderr)
41- if retval != 0:
42+ # Some of these commands can take a long time. Use CommandSpawner
43+ # and friends to provide "live" log output. Simpler ways of running
44+ # commands tend to save it all up and then dump it at the end, or
45+ # have trouble logging it as neat lines.
46+ stderr_logger = OutputLineHandler(logger.warn)
47+ stdout_logger = OutputLineHandler(logger.debug)
48+ receiver = ReturnCodeReceiver()
49+ spawner = CommandSpawner()
50+ spawner.start(
51+ command_line, completion_handler=receiver,
52+ stderr_handler=stderr_logger, stdout_handler=stdout_logger)
53+ spawner.complete()
54+ stdout_logger.finalize()
55+ stderr_logger.finalize()
56+ if receiver.returncode != 0:
57 raise LaunchpadScriptFailure(
58 "Failure while running command: %s" % description)
59
60
61=== modified file 'lib/lp/services/command_spawner.py'
62--- lib/lp/services/command_spawner.py 2011-04-06 03:43:28 +0000
63+++ lib/lp/services/command_spawner.py 2011-04-21 14:28:37 +0000
64@@ -99,9 +99,9 @@
65 :param stderr_handler: Callback to handle error output received from
66 the sub-process. Must take the output as its sole argument. May
67 be called any number of times as output comes in.
68- :param failure_handler: Callback to be invoked, exactly once, when the
69- sub-process exits. Must take `command`'s numeric return code as
70- its sole argument.
71+ :param completion_handler: Callback to be invoked, exactly once, when
72+ the sub-process exits. Must take `command`'s numeric return code
73+ as its sole argument.
74 """
75 process = self._spawn(command)
76 handlers = {
77@@ -172,9 +172,9 @@
78 try:
79 output = pipe_file.read()
80 except IOError, e:
81+ # "Resource temporarily unavailable"--not an error really,
82+ # just means there's nothing to read.
83 if e.errno != errno.EAGAIN:
84- # "Resource temporarily unavailable"--not an error
85- # really, just means there's nothing to read.
86 raise
87 else:
88 if len(output) > 0:

Subscribers

People subscribed via source and target branches

to status/vote changes: