[MERGE] merged oe-related changes:

- added a cron command
- added a web command
- added rst documentation
- changed the read command to use the environment when --addons is not given

bzr revid: vmt@openerp.com-20130121163525-im9rt2pvljz3bs7t
This commit is contained in:
Vo Minh Thu 2013-01-21 17:35:25 +01:00
commit 6fe1c4faec
12 changed files with 242 additions and 26 deletions

40
doc/commands.rst Normal file
View File

@ -0,0 +1,40 @@
.. _commands:
Available commands
==================
This page explain some of the available ``oe`` commands. For an overview about
``oe``, see :doc:`openerp-command`.
Keep in mind that ``oe --help`` and ``oe <command> --help`` already give a lot
of information about the commands and their options and flags.
``web``
-------
The ``web`` command is used to create a single OpenERP server process to handle
regular HTTP requests and XML-RPC requests. It is possible to execute such
process multiple times, possibly on different machines.
It is possible to chose the ``--threaded`` or ``--gevent`` flags. It is
recommanded to use ``--threaded`` only when running a single process.
``--gevent`` is experimental; it is planned to use it for the embedded chat
feature.
Example invocation::
> oe web --addons ../../addons/trunk:../../web/trunk/addons --threaded
``cron``
--------
The ``cron`` command is used to create a single OpenERP process to execute
so-called cron jobs, also called scheduled tasks in the OpenERP interface. As
for the ``web`` command, multiple cron processes can be run side by side.
It is necessary to specify on the command-line which database need to be
watched by the cron process with the ``--database`` option.
Example invocation::
> oe cron --addons ../../addons/trunk:../../web/trunk/addons --database production

View File

@ -25,6 +25,7 @@ OpenERP Command
:maxdepth: 1
openerp-command.rst
commands.rst
adding-command.rst
OpenERP Server API

View File

@ -1,21 +1,22 @@
.. _openerp-command:
OpenERP Command
===============
The ``oe`` script
=================
The ``oe`` script provides a set of command-line tools around the OpenERP
framework.
framework. It is meant to replace the older ``openerp-server`` script (which
is still available).
Using OpenERP Command
---------------------
Using ``oe``
------------
In contrast to the previous ``openerp-server`` script, ``oe`` defines a few
sub-commands, each with its own set of flags and options. You can get some
commands, each with its own set of flags and options. You can get some
information for any of them with
::
> oe <sub-command> --help
> oe <command> --help
For instance::
@ -43,6 +44,11 @@ for instance here is a, say, ``test-trunk-view-validation.sh`` file::
# itself does not need it.
oe run-tests -d ignored -m openerp.test_view_validation
Available commands
-------------------
See the :doc:`commands` page.
Adding new commands
-------------------
@ -57,4 +63,4 @@ A preliminary ``oe-bash-completion`` file is provided. After sourcing it,
> . oe-bash-completion
completion (using the TAB character) in Bash should be working.
completion (using the TAB character) in Bash should work.

View File

@ -51,6 +51,8 @@ wsgi.register_wsgi_handler = wsgi.wsgi_server.register_wsgi_handler
# its own copy of the data structure and we don't need to care about
# locks between threads.
multi_process = False
# Is the server running with gevent.
evented = False
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -135,6 +135,10 @@ class ir_cron(osv.osv):
_logger.debug('%.3fs (%s, %s)' % (end_time - start_time, model_name, method_name))
except Exception, e:
self._handle_callback_exception(cr, uid, model_name, method_name, args, job_id, e)
else:
msg = "Method `%s.%s` do not exist." % (model._name, method_name) \
if model else "Model `%s` do not exist." % model._name
_logger.warning(msg)
def _process_job(self, job_cr, job, cron_cr):
""" Run a given job taking care of the repetition.

View File

@ -181,9 +181,9 @@ def dumpstacks(sig, frame):
code.append(" %s" % (line.strip()))
_logger.info("\n".join(code))
def setup_signal_handlers():
""" Register the signal handler defined above. """
SIGNALS = map(lambda x: getattr(signal, "SIG%s" % x), "INT TERM".split())
def setup_signal_handlers(signal_handler):
""" Register the given signal handler. """
SIGNALS = (signal.SIGINT, signal.SIGTERM)
if os.name == 'posix':
map(lambda sig: signal.signal(sig, signal_handler), SIGNALS)
signal.signal(signal.SIGQUIT, dumpstacks)
@ -238,7 +238,7 @@ def main(args):
configure_babel_localedata_path()
setup_signal_handlers()
setup_signal_handlers(signal_handler)
if config["test_file"]:
run_test_file(config['db_name'], config['test_file'])

View File

@ -410,22 +410,15 @@ def application(environ, start_response):
# The WSGI server, started by start_server(), stopped by stop_server().
httpd = None
def serve():
def serve(interface, port, threaded):
""" Serve HTTP requests via werkzeug development server.
If werkzeug can not be imported, we fall back to wsgiref's simple_server.
Calling this function is blocking, you might want to call it in its own
thread.
"""
global httpd
# TODO Change the xmlrpc_* options to http_*
interface = config['xmlrpc_interface'] or '0.0.0.0'
port = config['xmlrpc_port']
httpd = werkzeug.serving.make_server(interface, port, application, threaded=True)
_logger.info('HTTP service (werkzeug) running on %s:%s', interface, port)
httpd = werkzeug.serving.make_server(interface, port, application, threaded=threaded)
httpd.serve_forever()
def start_service():
@ -433,7 +426,11 @@ def start_service():
The WSGI server can be shutdown with stop_server() below.
"""
threading.Thread(target=serve).start()
# TODO Change the xmlrpc_* options to http_*
interface = config['xmlrpc_interface'] or '0.0.0.0'
port = config['xmlrpc_port']
_logger.info('HTTP service (werkzeug) running on %s:%s', interface, port)
threading.Thread(target=serve, args=(interface, port, True)).start()
def stop_service():
""" Initiate the shutdown of the WSGI server.

View File

@ -8,6 +8,7 @@ from .bench_sale_mrp import BenchSaleMrp
from . import common
from . import conf # Not really server-side (in the `for` below).
from . import cron
from . import drop
from . import initialize
from . import model
@ -17,9 +18,10 @@ from . import run_tests
from . import scaffold
from . import uninstall
from . import update
from . import web
command_list_server = (conf, drop, initialize, model, module, read, run_tests,
scaffold, uninstall, update, )
command_list_server = (conf, cron, drop, initialize, model, module, read, run_tests,
scaffold, uninstall, update, web, )
command_list_client = (Call, Open, Show, ConsumeNothing, ConsumeMemory,
LeakMemory, ConsumeCPU, Bench, BenchRead,

View File

@ -3,6 +3,10 @@ Define a few common arguments for server-side command-line tools.
"""
import argparse
import os
try:
from setproctitle import setproctitle
except ImportError:
setproctitle = lambda x: None
import sys
def add_addons_argument(parser):
@ -12,6 +16,21 @@ def add_addons_argument(parser):
parser.add_argument('--addons', metavar='ADDONS',
**required_or_default('ADDONS',
'colon-separated list of paths to addons'))
def set_addons(args):
"""
Turn args.addons into a list instead of a column-separated strings.
Set openerp.toools.config accordingly.
"""
import openerp.tools.config
config = openerp.tools.config
assert hasattr(args, 'addons')
if args.addons:
args.addons = args.addons.split(':')
else:
args.addons = []
config['addons_path'] = ','.join(args.addons)
def get_addons_from_paths(paths, exclude):
"""

47
openerpcommand/cron.py Normal file
View File

@ -0,0 +1,47 @@
"""
Run an OpenERP cron process.
"""
import os
import common
def run(args):
import openerp
import openerp.cli.server
import openerp.tools.config
import openerp.service.cron
config = openerp.tools.config
os.environ["TZ"] = "UTC"
common.set_addons(args)
args.database = args.database or []
config['log_handler'] = [':WARNING', 'openerp.addons.base.ir.ir_cron:DEBUG']
openerp.multi_process = True
common.setproctitle('openerp-cron [%s]' % ', '.join(args.database))
openerp.cli.server.check_root_user()
openerp.netsvc.init_logger()
#openerp.cli.server.report_configuration()
openerp.cli.server.configure_babel_localedata_path()
openerp.cli.server.setup_signal_handlers(openerp.cli.server.signal_handler)
import openerp.addons.base
if args.database:
for db in args.database:
openerp.cli.server.preload_registry(db)
openerp.service.cron.start_service()
openerp.cli.server.quit_on_signals()
else:
print "No database given."
def add_parser(subparsers):
parser = subparsers.add_parser('cron',
description='Run an OpenERP cron process.')
common.add_addons_argument(parser)
parser.add_argument('--database', action='append',
help='Database for which cron jobs are processed (can be repeated)')
parser.set_defaults(run=run)

View File

@ -5,6 +5,8 @@ import os
import sys
import textwrap
import common
# TODO provide a --rpc flag to use XML-RPC (with a specific username) instead
# of server-side library.
def run(args):
@ -13,6 +15,7 @@ def run(args):
import openerp
config = openerp.tools.config
config['log_handler'] = [':CRITICAL']
common.set_addons(args)
openerp.netsvc.init_logger()
registry = openerp.modules.registry.RegistryManager.get(
args.database, update_module=False)
@ -44,8 +47,9 @@ def run(args):
def add_parser(subparsers):
parser = subparsers.add_parser('read',
description='Display a record.')
parser.add_argument('-d', '--database', metavar='DATABASE', required=True,
help='the database to connect to')
parser.add_argument('-d', '--database', metavar='DATABASE',
**common.required_or_default('DATABASE', 'the database to connect to'))
common.add_addons_argument(parser)
parser.add_argument('-m', '--model', metavar='MODEL', required=True,
help='the model for which a record should be read')
parser.add_argument('-i', '--id', metavar='RECORDID', required=True,

94
openerpcommand/web.py Normal file
View File

@ -0,0 +1,94 @@
"""
Run a normal OpenERP HTTP process.
"""
import logging
import os
import signal
import common
_logger = logging.getLogger(__name__)
def mk_signal_handler(server):
def signal_handler(sig, frame):
"""
Specialized signal handler for the evented process.
"""
print "\n\n\nStopping gevent HTTP server...\n\n\n"
server.stop()
return signal_handler
def setup_signal_handlers(signal_handler):
SIGNALS = (signal.SIGINT, signal.SIGTERM)
map(lambda sig: signal.signal(sig, signal_handler), SIGNALS)
def run(args):
# Note that gevent monkey patching must be done before importing the
# `threading` module, see http://stackoverflow.com/questions/8774958/.
if args.gevent:
import gevent
import gevent.monkey
import gevent.wsgi
import gevent_psycopg2
gevent.monkey.patch_all()
gevent_psycopg2.monkey_patch()
import threading
import openerp
import openerp.cli.server
import openerp.service.wsgi_server
import openerp.tools.config
config = openerp.tools.config
os.environ["TZ"] = "UTC"
common.set_addons(args)
openerp.multi_process = True
common.setproctitle('openerp-web')
openerp.cli.server.check_root_user()
openerp.netsvc.init_logger()
#openerp.cli.server.report_configuration()
openerp.cli.server.configure_babel_localedata_path()
target = openerp.service.wsgi_server.serve
if not args.gevent:
openerp.evented = False
openerp.cli.server.setup_signal_handlers(openerp.cli.server.signal_handler)
# TODO openerp.multi_process with a multi-threaded process probably
# doesn't work very well (e.g. waiting for all threads to complete
# before killing the process is not implemented).
arg = (args.interface, int(args.port), args.threaded)
threading.Thread(target=target, args=arg).start()
openerp.cli.server.quit_on_signals()
else:
openerp.evented = True
app = openerp.service.wsgi_server.application
server = gevent.wsgi.WSGIServer((args.interface, int(args.port)), app)
setup_signal_handlers(mk_signal_handler(server))
try:
server.serve_forever()
except KeyboardInterrupt:
try:
server.stop()
gevent.shutdown()
except KeyboardInterrupt:
sys.stderr.write("Forced shutdown.\n")
gevent.shutdown()
def add_parser(subparsers):
parser = subparsers.add_parser('web',
description='Run a normal OpenERP HTTP process. By default a '
'singly-threaded Werkzeug server is used.')
common.add_addons_argument(parser)
parser.add_argument('--interface', default='0.0.0.0',
help='HTTP interface to listen on (default is %(default)s)')
parser.add_argument('--port', metavar='INT', default=8069,
help='HTTP port to listen on (default is %(default)s)')
parser.add_argument('--threaded', action='store_true',
help='Use a multithreaded Werkzeug server (incompatible with --gevent)')
parser.add_argument('--gevent', action='store_true',
help="Use gevent's WSGI server (incompatible with --threaded)")
parser.set_defaults(run=run)