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

Subscribers

People subscribed via source and target branches