Merge lp:~linaro-validation/lava-core/main into lp:lava-core

Proposed by Zygmunt Krynicki
Status: Merged
Merged at revision: 13
Proposed branch: lp:~linaro-validation/lava-core/main
Merge into: lp:lava-core
Prerequisite: lp:~zyga/lava-core/scratch
Diff against target: 375 lines (+352/-1)
2 files modified
lava/core/main.py (+346/-0)
setup.py (+6/-1)
To merge this branch: bzr merge lp:~linaro-validation/lava-core/main
Reviewer Review Type Date Requested Status
Linaro Validation Team Pending
Review via email: mp+105000@code.launchpad.net

Description of the change

This branch adds the main module (as in "main" function in C). Please have a look at the output of $ lava-dev --help, if you can and suggest changes to the content displayed there.

NOTE: There is an unrelated code remove from lava.commands module that just need to wait a little longer but was not spotted before (in the demo -> trunk transplant that I'm doing). That code works, it just needs dependencies that come later.

  Add lava.core.main

  The .main module defines one, relatively big, lava.commands.Dispatcher
  subclass, LavaDispatcher, that is specialized for being the entry point
  to all of lava form the shell. There are a number of customizations as
  compared to the plain Dispatcher class.

  There is a custom argument parser, as produced by construct_parser(), that has
  a lot of additional options for debugging and logging. There is a set of
  methods that conditionally enable plain logging, verbose logging,
  exception/crash logging, and developer-debug logging. Those are all called from
  the private method _adjust_logging_level() (that is a part of the Command class
  API)

  There way commands are processed, via dispatch(), is customized to handle
  various exceptions, including CommandError which is considered normal (not an
  application crash) and KeyboardInterrupt (which, by default, is not producing a
  backtrace). This function also knows how to spawn pdb, the python debugger, for
  a post-mortem session, if requested.

  Lastly there is a helper method save_history_now() that exists there to work
  around scratch space going away by the time the command finishes (as typically
  commands manage their scratch space internally). This part could be changed so
  that commands get a scratch space from the Dispatcher (and thus extend the
  lifetime of temporary files long enough so that the dispatcher gets to save
  global history, including any attachments, just before discarding the top-level
  Scratch object)

To post a comment you must log in.
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

Hi. This seems mostly fine. Two comments:

1) I don't think the help for -D is quite right: yes it enables DEBUG on the root logger, but the debug output only goes to ~/.cache/lava/logs/debug.log right? The way the help is written I think I expected it to be on stdout/stderr. I think the behaviour you have is better, can you update the description.

2) When I run lava-dev --help, I see this: http://people.linaro.org/~mwh/lava-dev-help.png -- all that epilog stuff is not what I want to see! Compare bzr --help or git --help. It's good information and should be accessible somewhere, but maybe it should be somewhere else? I don't know where. bzr has 'topics' -- bzr help topics -- maybe something like that?

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

Hi. This seems mostly fine. Two comments:

1) I don't think the help for -D is quite right: yes it enables DEBUG on the root logger, but the debug output only goes to ~/.cache/lava/logs/debug.log right? The way the help is written I think I expected it to be on stdout/stderr. I think the behaviour you have is better, can you update the description.

2) When I run lava-dev --help, I see this: http://people.linaro.org/~mwh/lava-dev-help.png -- all that epilog stuff is not what I want to see! Compare bzr --help or git --help. It's good information and should be accessible somewhere, but maybe it should be somewhere else? I don't know where. bzr has 'topics' -- bzr help topics -- maybe something like that?

lp:~linaro-validation/lava-core/main updated
8. By Zygmunt Krynicki

Infrastructure for history and logging

9. By Zygmunt Krynicki

Infrastructure for plugins

10. By Zygmunt Krynicki

Infrastructure for programming interfaces

11. By Zygmunt Krynicki

Infrastructure for command line tools

12. By Zygmunt Krynicki

Infrastructure for temporary files (scratch space)

13. By Zygmunt Krynicki

Add lava.core.main

The .main module defines one, relatively big, lava.commands.Dispatcher
subclass, LavaDispatcher, that is specialized for being the entry point
to all of lava form the shell. There are a number of customizations as
compared to the plain Dispatcher class.

There is a custom argument parser, as produced by construct_parser(), that has
a lot of additional options for debugging and logging. There is a set of
methods that conditionally enable plain logging, verbose logging,
exception/crash logging, and developer-debug logging. Those are all called from
the private method _adjust_logging_level() (that is a part of the Command class
API)

There way commands are processed, via dispatch(), is customized to handle
various exceptions, including CommandError which is considered normal (not an
application crash) and KeyboardInterrupt (which, by default, is not producing a
backtrace). This function also knows how to spawn pdb, the python debugger, for
a post-mortem session, if requested.

Lastly there is a helper method save_history_now() that exists there to work
around scratch space going away by the time the command finishes (as typically
commands manage their scratch space internally). This part could be changed so
that commands get a scratch space from the Dispatcher (and thus extend the
lifetime of temporary files long enough so that the dispatcher gets to save
global history, including any attachments, just before discarding the top-level
Scratch object)

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

> Hi. This seems mostly fine. Two comments:
>
> 1) I don't think the help for -D is quite right: yes it enables DEBUG on the
> root logger, but the debug output only goes to ~/.cache/lava/logs/debug.log
> right? The way the help is written I think I expected it to be on
> stdout/stderr. I think the behaviour you have is better, can you update the
> description.

I gave it a thought but failed to come up with a better description. Please advise.

> 2) When I run lava-dev --help, I see this: http://people.linaro.org/~mwh/lava-
> dev-help.png -- all that epilog stuff is not what I want to see! Compare bzr
> --help or git --help. It's good information and should be accessible
> somewhere, but maybe it should be somewhere else? I don't know where. bzr
> has 'topics' -- bzr help topics -- maybe something like that?

Quite right. I've split most of the former `lava --help` to new `lava --about`> This made --help short and tidy.

lp:~linaro-validation/lava-core/main updated
14. By Michael Hudson-Doyle

add a description to the logging group, format epilog more carefully

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'lava/core/main.py'
2--- lava/core/main.py 1970-01-01 00:00:00 +0000
3+++ lava/core/main.py 2012-05-23 22:00:25 +0000
4@@ -0,0 +1,346 @@
5+# Copyright (C) 2010-2012 Linaro Limited
6+#
7+# Author: Zygmunt Krynicki <zygmunt.krynicki@linaro.org>
8+#
9+# This file is part of lava-core
10+#
11+# lava-core is free software: you can redistribute it and/or modify
12+# it under the terms of the GNU Lesser General Public License version 3
13+# as published by the Free Software Foundation
14+#
15+# lava-core is distributed in the hope that it will be useful,
16+# but WITHOUT ANY WARRANTY; without even the implied warranty of
17+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+# GNU General Public License for more details.
19+#
20+# You should have received a copy of the GNU Lesser General Public License
21+# along with lava-core. If not, see <http://www.gnu.org/licenses/>.
22+
23+from __future__ import absolute_import, print_function
24+
25+"""
26+lava.core.main
27+==============
28+
29+Implementation of the `lava-dev` shell command.
30+
31+See the note in LavaDispatcher docstring about `lava-dev` vs `lava`
32+"""
33+
34+import argparse
35+import logging
36+import os
37+import pdb
38+import sys
39+
40+from lava.core.commands import Dispatcher
41+from lava.core.errors import CommandError
42+from lava.core.utils import Scratch
43+from lava.core.history import history
44+
45+
46+class AboutLavaAction(argparse.Action):
47+ """
48+ Argparse action that implements the `lava-dev --about` command
49+ """
50+
51+ message = """
52+About LAVA
53+ LAVA, which stands for Linaro Automated Validation Architecture, is a
54+ project started by Linaro tasked with build a collection of freely
55+ available tools and equipment for test automation. LAVA is composed of many
56+ extensible pieces that together help to plan, schedule, execute, track, and
57+ run tests on virtual and physical devices, then finally to store and
58+ visualize the results.
59+
60+ To learn more about LAVA please visit https://launchpad.net/lava-project/
61+
62+About Linaro
63+ Linaro was established in June 2010 by founding members ARM, Freescale,
64+ IBM, Samsung, ST-Ericsson and Texas instruments (TI). Members provide
65+ engineering resources and funding. Linaro's goals are to deliver value to
66+ its members through enabling their engineering teams to focus on
67+ differentiation and product delivery, and to reduce time to market for
68+ OEM/ODMs delivering open source based products using ARM technology.
69+
70+ To learn more about Linaro please visit http://www.linaro.org/about/
71+
72+Software development and hacking:
73+ Source code is freely available and is managed by the Bazaar version
74+ control system and the Launchpad (http://launchpad.net/) collaboration
75+ platform. Note that while LAVA is located in more than one project you
76+ should eventually be able to look at the lava-core project for most of
77+ the code. The process of merging small lava projects into lava-core is
78+ not complete yet, as of May 2012.
79+
80+ To get a copy of LAVA install bazaar and run the following command:
81+
82+ $ bzr branch lp:lava-core
83+
84+ We welcome all contributors, see HACKING to get started
85+
86+ Development is discussed on the linaro-validation@lists.linaro.org mailing
87+ list, you can subscribe by visiting http://lists.linaro.org/
88+
89+Support:
90+ Report bugs at https://bugs.launchpad.net/lava-project/+filebug
91+
92+ Ask questions in your native language at
93+ https://answers.launchpad.net/lava-project/+addquestion.
94+
95+ You can also ask for real-time support in the #linaro-lava channel on the
96+ freenode IRC network. Keep in mind that although geographically distributed
97+ we (LAVA developers) may be away at a given time. Note that developer time
98+ is precious so please be considerate.
99+
100+Copyright and license:
101+ Copyright (c) Linaro Limited 2010-2012
102+
103+ LAVA is free software, licensed under GLPv3, LGPLv3 and AGLPv3.
104+ Check the license header in a specific file to know more.
105+"""
106+ message = message.lstrip()
107+
108+ def __init__(self,
109+ option_strings, dest, default=None, required=False,
110+ help=None):
111+ super(AboutLavaAction, self).__init__(
112+ option_strings=option_strings, dest=dest, default=default, nargs=0,
113+ help=help)
114+
115+ def __call__(self, parser, namespace, values, option_string=None):
116+ parser.exit(message=self.message)
117+
118+
119+class LavaDispatcher(Dispatcher):
120+ """
121+ Dispatcher implementing the `lava-dev` shell command (note that
122+ `lava-dev` shall be renamed to `lava` once lava-core replaces
123+ current group of lava-* projects.
124+
125+ This dispatcher imports plugins from the pkg_resources namespace
126+ `lava-dev.commands`. Additional plugins can be registered as either
127+ :class:`lava.command.Command` or :class:`lava.command.CommandGroup`
128+ sub-classes.
129+ """
130+
131+ name = "lava-dev"
132+ help = None
133+ description = """
134+This executable allows you to work with LAVA. LAVA commands form a tree, where
135+you typically invoke sub or sub-sub commands to do actual things. Please
136+inspect the list below for details.
137+"""
138+
139+ epilog = """
140+For information about LAVA and Linaro run: lava-dev --about"""
141+
142+ def __init__(self):
143+ # Call this early so that we don't get logging.basicConfig
144+ # being called by accident. Otherwise we'd have to
145+ # purge all loggers from the root logger and that sucks
146+ self.setup_logging()
147+ # Initialize the base dispatcher
148+ super(LavaDispatcher, self).__init__()
149+ # And import the non-flat namespace commands
150+ self.import_commands('lava_dev.commands')
151+ self.args = None
152+ self._history_needs_saving = True
153+
154+ def construct_parser(self):
155+ """
156+ Construct a parser for this dispatcher.
157+
158+ This is only used if the parser is not provided by the parent
159+ dispatcher instance.
160+ """
161+ # Construct a basic parser
162+ parser = super(LavaDispatcher, self).construct_parser()
163+ # Add the --about command
164+ parser.register("action", "about_lava", AboutLavaAction)
165+ parser.add_argument(
166+ "--about",
167+ action="about_lava",
168+ default=argparse.SUPPRESS,
169+ help="Provide detailed information about LAVA and Linaro")
170+ # Add the --verbose flag
171+ parser.add_argument(
172+ "-v", "--verbose",
173+ default=False,
174+ action="store_true",
175+ help="be more verbose (displays all INFO messages)")
176+ group = parser.add_argument_group(title="history management")
177+ group.add_argument(
178+ "-H", "--history",
179+ type=argparse.FileType(mode="wb"),
180+ metavar="TARBALL",
181+ help="Save session history, as tarball, to TARBALL")
182+ group = parser.add_argument_group(
183+ title="logging and software development",
184+ description="""\
185+Log messages at the WARNING level or higher are sent to stderr. DEBUG
186+messages (when enabled by -D or -T) go to ~/.cache/lava/logs/debug.log.
187+""")
188+ # Add the --debug flag
189+ group.add_argument(
190+ "-D", "--debug",
191+ action="store_true",
192+ default=False,
193+ help="enable DEBUG messages on the root logger")
194+ # Add the --trace flag
195+ group.add_argument(
196+ "-T", "--trace",
197+ metavar="LOGGER",
198+ action="append",
199+ default=[],
200+ help=("enable DEBUG messages on the specified logger "
201+ "(can be used multiple times)"))
202+ # Add the --pdb flag
203+ group.add_argument(
204+ "-P", "--pdb",
205+ action="store_true",
206+ default=False,
207+ help="jump into pdb (python debugger) when a command crashes")
208+ # Add the --debug-command-errors flag
209+ group.add_argument(
210+ "-C", "--debug-command-errors",
211+ action="store_true",
212+ default=False,
213+ help="crash on CommandError, useful with --pdb")
214+ # Add the --debug-interrupt flag
215+ group.add_argument(
216+ "-I", "--debug-interrupt",
217+ action="store_true",
218+ default=False,
219+ help="crash on SIGINT/KeyboardInterrupt, useful with --pdb")
220+ # Return the improved parser
221+ return parser
222+
223+ def setup_logging(self):
224+ """
225+ Setup logging for the root dispatcher
226+ """
227+ self._setup_error_handler()
228+ self._setup_warning_handler()
229+
230+ def dispatch(self, raw_args=None):
231+ """
232+ Enhanced version of Dispatcher.dispatch() that logs command duration
233+ and handles all exceptions (swallowing them)
234+ """
235+ try:
236+ return super(LavaDispatcher, self).dispatch(raw_args, True)
237+ except SystemExit:
238+ pass
239+ except BaseException as exc:
240+ if isinstance(exc, KeyboardInterrupt):
241+ self.say(None, "Interrupted!")
242+ if self.args is None or not self.args.debug_interrupt:
243+ return 1
244+ elif isinstance(exc, CommandError):
245+ if self.args is None or not self.args.debug_command_errors:
246+ return 1
247+ else:
248+ self.history.error(
249+ "Runaway exception while invoking command: {exc}",
250+ exc=str(exc))
251+ if self.args and self.args.pdb:
252+ self.logger.info("Starting pdb...")
253+ pdb.post_mortem()
254+ else:
255+ self.say(None, "This command has crashed")
256+ self.say(None, "The problem was: {0}", exc)
257+ self.say(None, "See {0} for full traceback",
258+ self.error_log_pathname),
259+ return 1
260+ finally:
261+ # Save history as tarball
262+ if self._history_needs_saving and self.args and self.args.history:
263+ self.history.debug("Saving history to {name}",
264+ name=self.args.history.name)
265+ with Scratch() as scratch:
266+ self.save_history_now(scratch)
267+
268+ def save_history_now(self, scratch):
269+ self._history_needs_saving = False
270+ if self.args is not None and self.args.history:
271+ history.top.save_as_tarball(scratch, self.args.history)
272+
273+ def _setup_error_handler(self):
274+ """
275+ Add a handler that logs all ERRORs to a dedicated file
276+ """
277+ # TODO: support XDA dirs
278+ pathname = os.path.expanduser("~/.cache/lava/logs/error.log")
279+ self.error_log_pathname = pathname
280+ if not os.path.exists(os.path.dirname(pathname)):
281+ os.makedirs(os.path.dirname(pathname))
282+ handler = logging.FileHandler(pathname, delay=True)
283+ handler.setLevel(logging.ERROR)
284+ handler.setFormatter(
285+ logging.Formatter("%(levelname)s %(name)s: %(message)s"))
286+ logging.getLogger().addHandler(handler)
287+
288+ def _setup_debug_handler(self):
289+ """
290+ Add a handler that logs everything to a dedicated file
291+ """
292+ # TODO: support XDA dirs
293+ pathname = os.path.expanduser("~/.cache/lava/logs/debug.log")
294+ self.say(None, "Note: debugging messages are saved to {0}", pathname)
295+ if not os.path.exists(os.path.dirname(pathname)):
296+ os.makedirs(os.path.dirname(pathname))
297+ handler = logging.FileHandler(pathname)
298+ handler.setLevel(logging.DEBUG)
299+ handler.setFormatter(
300+ logging.Formatter((
301+ "%(asctime)s "
302+ "[pid:%(process)s, thread:%(threadName)s, "
303+ "reltime:%(relativeCreated)dms] "
304+ "%(levelname)s %(name)s: %(message)s")))
305+ logging.getLogger().addHandler(handler)
306+
307+ def _setup_warning_handler(self):
308+ """
309+ Add a handler that logs warnings, and other important stuff to stderr
310+ """
311+ handler = logging.StreamHandler(sys.stderr)
312+ handler.setLevel(logging.WARN)
313+ handler.setFormatter(
314+ logging.Formatter("%(levelname)s: %(name)s: %(message)s"))
315+ logging.getLogger().addHandler(handler)
316+
317+ def _setup_info_handler(self):
318+ class OnlyInfoFilter(logging.Filterer):
319+ def filter(self, record):
320+ if record.levelno == logging.INFO:
321+ return 1
322+ return 0
323+
324+ class SayHandler(logging.Handler):
325+ def emit(inner_self, record):
326+ self.say(None, "{0}", record.getMessage())
327+ handler = SayHandler()
328+ handler.setLevel(logging.INFO)
329+ handler.setFormatter(logging.Formatter("%(message)s"))
330+ handler.addFilter(OnlyInfoFilter())
331+ logging.getLogger().addHandler(handler)
332+
333+ def _adjust_logging_level(self, args):
334+ # XXX: store arguments as we want to look at some more esoteric options
335+ # in dispatch() later on, this feels ugly, perhaps the dispatcher
336+ # should just know the arguments
337+ self.args = args
338+ # Enable verbose message handler
339+ if args.verbose:
340+ logging.getLogger().setLevel(logging.INFO)
341+ self._setup_info_handler()
342+ # Enable debug.log if needed
343+ if args.debug or args.trace:
344+ self._setup_debug_handler()
345+ # Enable application-wide debugging
346+ if args.debug:
347+ logging.getLogger().setLevel(logging.DEBUG)
348+ # Enable trace loggers
349+ for name in args.trace:
350+ logging.getLogger(name).setLevel(logging.DEBUG)
351
352=== modified file 'setup.py'
353--- setup.py 2012-05-16 10:38:09 +0000
354+++ setup.py 2012-05-23 22:00:25 +0000
355@@ -34,6 +34,10 @@
356 url='https://launchpad.net/lava-core',
357 license="LGPLv3, GPLv3",
358 test_suite="unittest2.collector",
359+ entry_points="""
360+ [console_scripts]
361+ lava-dev = lava.core.main:LavaDispatcher.run
362+ """,
363 classifiers=[
364 "Development Status :: 4 - Beta",
365 "Intended Audience :: Developers",
366@@ -45,7 +49,8 @@
367 ],
368 extras_require={},
369 install_requires=[
370- 'json-document',
371+ 'argparse >= 1.1',
372+ 'json-document >= 0.8',
373 'json-schema-validator',
374 'simplejson',
375 ],

Subscribers

People subscribed via source and target branches