Create component runner

- JCLRunner handle configuration file and command line arguments to setup a JCL component

darcs-hash:20070518143255-86b55-f5dfd32edeb34c9d30b85a1dd05a12c6a8423806.gz
This commit is contained in:
David Rousselie
2007-05-18 16:32:55 +02:00
parent 1cab0da48c
commit 4bb6d27c33
8 changed files with 435 additions and 2 deletions

34
src/jcl.py Normal file
View File

@@ -0,0 +1,34 @@
##
## jcl.py
## Login : David Rousselie <dax@happycoders.org>
## Started on Fri May 18 15:19:20 2007 David Rousselie
## $Id$
##
## Copyright (C) 2007 David Rousselie
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
del sys.setdefaultencoding
import jcl
from jcl.runner import JCLRunner
if __name__ == '__main__':
runner = JCLRunner("JCL", jcl.version)
runner.configure()
runner.run()

View File

@@ -1,2 +1,5 @@
"""JCL module"""
__revision__ = ""
version = "0.1.0"

View File

@@ -62,7 +62,6 @@ VERSION = "0.1"
###############################################################################
class JCLComponent(Component, object):
"""Implement default JCL component behavior:
- regular interval behavior
- Jabber register process (add, delete, update accounts)
- Jabber presence handling
- passwork request at login

208
src/jcl/runner.py Normal file
View File

@@ -0,0 +1,208 @@
##
## runner.py
## Login : David Rousselie <dax@happycoders.org>
## Started on Thu May 17 22:02:08 2007 David Rousselie
## $Id$
##
## Copyright (C) 2007 David Rousselie
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import logging
import os
import sys
from ConfigParser import ConfigParser
from getopt import gnu_getopt
from sqlobject import *
from jcl.model import account
from jcl.model.account import Account, PresenceAccount
class JCLRunner(object):
def __init__(self, component_name, component_version):
"""
options: list of tuples:
- short_opt: same as getopt
- long_opt: same as getopt
"""
self.component_name = component_name
self.component_version = component_version
self.config_file = None
self.server = "localhost"
self.port = 5347
self.secret = "secret"
self.service_jid = "jcl.localhost"
self.language = "en"
self.db_url = "sqlite:///var/spool/jabber/jcl.db"
self.pid_file = "/var/run/jabber/jcl.pid"
self.options = [("c:", "config-file=", None, \
" FILE\t\t\t\tConfiguration file to use", \
lambda arg: self.set_attr("config_file", arg)), \
("S:", "server=", "jabber", \
" SERVER_ADDRESS\t\t\tAddress of the Jabber server", \
lambda arg: self.set_attr("server", arg)), \
("P:", "port=", "jabber", \
" PORT\t\t\t\t\tPort of the Jabber server to connect the component", \
lambda arg: self.set_attr("port", int(arg))), \
("s:", "secret=", "jabber", \
" SECRET\t\t\t\tComponent password to connect to the Jabber server", \
lambda arg: self.set_attr("secret", arg)), \
("j:", "service-jid=", "jabber", \
" JID\t\t\t\tJID of the component", \
lambda arg: self.set_attr("service_jid", arg)), \
("l:", "language=", "jabber", \
" LANG\t\t\t\tDefault Language of the component", \
lambda arg: self.set_attr("language", arg)), \
("u:", "db-url=", "db", \
" URL\t\t\t\tDatabase URL", \
lambda arg: self.set_attr("db_url", arg)), \
("p:", "pid-file=", "component", \
" FILE\t\t\t\tPath of the PID file", \
lambda arg: self.set_attr("pid_file", arg)), \
("d", "debug", None, \
"\t\t\t\t\tEnable debug traces", \
lambda arg: self.set_attr("debug", True)), \
("h", "help", None, \
"\t\t\t\t\tThis help", \
lambda arg: self.print_help())]
self.logger = logging.getLogger()
self.logger.addHandler(logging.StreamHandler())
self.__debug = False
def set_attr(self, attr, value):
setattr(self, attr, value)
def __configure_commandline_args(self, shortopts, longopts, cleanopts):
(opts, args) = gnu_getopt(sys.argv[1:], shortopts, longopts)
commandline_args = {}
for (arg, value) in opts:
clean_arg = arg.lstrip('-')
if cleanopts.has_key(clean_arg):
commandline_args[clean_arg] = value
return commandline_args
def __apply_commandline_args(self, commandline_args, cleanopts):
for arg in commandline_args:
value = commandline_args[arg]
self.logger.debug("Applying argument " + arg + " = " + \
value)
cleanopts[arg][1](value)
def __apply_configfile(self, commandline_args, cleanopts):
if commandline_args.has_key("config_file"):
self.config_file = commandline_args["config_file"]
elif commandline_args.has_key("c"):
self.config_file = commandline_args["c"]
if self.config_file is not None:
self.config = ConfigParser()
self.logger.debug("Loading config file " + self.config_file)
read_file = self.config.read(self.config_file)
if read_file == []:
raise IOError(2, "No such file or directory", str(self.config_file))
for opt in cleanopts:
if len(opt) > 1:
(section, set_func) = cleanopts[opt]
if section is not None:
attr = opt.replace("-", "_")
config_property = self.config.get(section, attr)
self.logger.debug("Setting " + attr + " = " + \
config_property + \
" from configuration file " + \
self.config_file)
set_func(config_property)
def configure(self):
"""Apply configuration from command line and configuration file.
command line arguments override configuration file properties.
"""
shortopts = ""
longopts = []
cleanopts = {}
for option in self.options:
shortopts += option[0]
longopts += [option[1]]
cleanopts[option[0][0]] = (option[2], option[4])
if option[1][-1:] == '=':
cleanopts[option[1][:-1]] = (option[2], option[4])
else:
cleanopts[option[1]] = (option[2], option[4])
commandline_args = self.__configure_commandline_args(shortopts, longopts, cleanopts)
if commandline_args.has_key("debug") or commandline_args.has_key("d"):
self.debug = True
self.__apply_configfile(commandline_args, cleanopts)
self.__apply_commandline_args(commandline_args, cleanopts)
def _get_help(self):
help = self.component_name + " v" + self.component_version + " help:\n"
for option in self.options:
if option[1][-1:] == '=':
long_option = option[1][:-1]
else:
long_option = option[1]
help += "\t-" + option[0][0] + ", --" + long_option + option[3] + "\n"
return help
def print_help(self):
print self._get_help()
sys.exit(0)
def get_debug(self):
return self.__debug
def set_debug(self, value):
self.__debug = value
if self.__debug:
self.logger.setLevel(logging.DEBUG)
else:
self.logger.setLevel(logging.CRITICAL)
debug = property(get_debug, set_debug)
def setup_db(self):
Account.createTable(ifNotExists = True)
PresenceAccount.createTable(ifNotExists = True)
def setup_pidfile(self):
pidfile = open(self.pid_file, "w")
pidfile.write(str(os.getpid()))
pidfile.close()
def _run(self, run_func):
try:
self.setup_pidfile()
account.hub.threadConnection = connectionForURI(self.db_url)
self.setup_db()
del account.hub.threadConnection
self.logger.debug(self.component_name + " v" + \
self.component_version + " is starting ...")
run_func()
self.logger.debug(self.component_name + " is exiting")
finally:
os.remove(self.pid_file)
def run(self):
def run_func():
component = JCLComponent(jid = self.service_jid, \
secret = self.secret, \
server = self.server, \
port = self.port, \
db_connection_str = self.db_url, \
lang = Lang(self.language))
component.run()
self._run(run_func)

View File

@@ -3,13 +3,14 @@ __revision__ = ""
import unittest
from jcl.tests import lang
from jcl.tests import lang, runner
from jcl.jabber import tests as jabber
from jcl.model import tests as model
def suite():
suite = unittest.TestSuite()
suite.addTest(lang.suite())
suite.addTest(runner.suite())
suite.addTest(jabber.suite())
suite.addTest(model.suite())
return suite

21
src/jcl/tests/jcl.conf Normal file
View File

@@ -0,0 +1,21 @@
[jabber]
server: test_localhost
port: 42
secret: test_secret
service_jid: test_jcl.localhost
#supported language: en, fr (See src/jmc/lang.py to add more)
language: test_en
[db]
#type: mysql
type: test_sqlite
#host: root@localhost
host: root@localhost
name: /var/spool/jabber/test_jcl.db
#url: %(type)%(host)%(name)?debug=1&debugThreading=1
db_url: %(type)s://%(host)s%(name)s
[component]
pid_file: /var/run/jabber/test_jcl.pid

146
src/jcl/tests/runner.py Normal file
View File

@@ -0,0 +1,146 @@
##
## runner.py
## Login : David Rousselie <dax@happycoders.org>
## Started on Fri May 18 13:43:37 2007 David Rousselie
## $Id$
##
## Copyright (C) 2007 David Rousselie
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import unittest
import sys
import os
from sqlobject import *
import jcl
from jcl.runner import JCLRunner
from jcl.model import account
from jcl.model.account import Account, PresenceAccount
if sys.platform == "win32":
DB_PATH = "/c|/temp/test.db"
else:
DB_PATH = "/tmp/test.db"
DB_URL = "sqlite://" + DB_PATH# + "?debug=1&debugThreading=1"
class JCLRunner_TestCase(unittest.TestCase):
def setUp(self):
self.runner = JCLRunner("JCL", jcl.version)
def tearDown(self):
self.runner = None
sys.argv = [""]
def test_configure_default(self):
self.runner.configure()
self.assertEquals(self.runner.config_file, None)
self.assertEquals(self.runner.server, "localhost")
self.assertEquals(self.runner.port, 5347)
self.assertEquals(self.runner.secret, "secret")
self.assertEquals(self.runner.service_jid, "jcl.localhost")
self.assertEquals(self.runner.language, "en")
self.assertEquals(self.runner.db_url, "sqlite:///var/spool/jabber/jcl.db")
self.assertEquals(self.runner.pid_file, "/var/run/jabber/jcl.pid")
self.assertFalse(self.runner.debug)
def test_configure_configfile(self):
self.runner.config_file = "src/jcl/tests/jcl.conf"
self.runner.configure()
self.assertEquals(self.runner.server, "test_localhost")
self.assertEquals(self.runner.port, 42)
self.assertEquals(self.runner.secret, "test_secret")
self.assertEquals(self.runner.service_jid, "test_jcl.localhost")
self.assertEquals(self.runner.language, "test_en")
self.assertEquals(self.runner.db_url, "test_sqlite://root@localhost/var/spool/jabber/test_jcl.db")
self.assertEquals(self.runner.pid_file, "/var/run/jabber/test_jcl.pid")
self.assertFalse(self.runner.debug)
def test_configure_commandline_shortopt(self):
sys.argv = ["", "-c", "src/jcl/tests/jcl.conf", \
"-S", "test2_localhost", \
"-P", "43", \
"-s", "test2_secret", \
"-j", "test2_jcl.localhost", \
"-l", "test2_en", \
"-u", "sqlite:///tmp/test_jcl.db", \
"-p", "/tmp/test_jcl.pid"]
self.runner.configure()
self.assertEquals(self.runner.server, "test2_localhost")
self.assertEquals(self.runner.port, 43)
self.assertEquals(self.runner.secret, "test2_secret")
self.assertEquals(self.runner.service_jid, "test2_jcl.localhost")
self.assertEquals(self.runner.language, "test2_en")
self.assertEquals(self.runner.db_url, "sqlite:///tmp/test_jcl.db")
self.assertEquals(self.runner.pid_file, "/tmp/test_jcl.pid")
self.assertFalse(self.runner.debug)
def test_configure_commandline_longopt(self):
sys.argv = ["", "--config-file", "src/jcl/tests/jcl.conf", \
"--server", "test2_localhost", \
"--port", "43", \
"--secret", "test2_secret", \
"--service-jid", "test2_jcl.localhost", \
"--language", "test2_en", \
"--db-url", "sqlite:///tmp/test_jcl.db", \
"--pid-file", "/tmp/test_jcl.pid"]
self.runner.configure()
self.assertEquals(self.runner.server, "test2_localhost")
self.assertEquals(self.runner.port, 43)
self.assertEquals(self.runner.secret, "test2_secret")
self.assertEquals(self.runner.service_jid, "test2_jcl.localhost")
self.assertEquals(self.runner.language, "test2_en")
self.assertEquals(self.runner.db_url, "sqlite:///tmp/test_jcl.db")
self.assertEquals(self.runner.pid_file, "/tmp/test_jcl.pid")
self.assertFalse(self.runner.debug)
def test_setup_pidfile(self):
try:
self.runner.pid_file = "/tmp/jcl.pid"
self.runner.setup_pidfile()
pidfile = open("/tmp/jcl.pid", "r")
pid = int(pidfile.read())
pidfile.close()
self.assertEquals(pid, os.getpid())
finally:
os.remove("/tmp/jcl.pid")
def test__run(self):
self.runner.pid_file = "/tmp/jcl.pid"
self.runner.db_url = DB_URL
def do_nothing():
pass
self.runner._run(do_nothing)
account.hub.threadConnection = connectionForURI(self.runner.db_url)
# dropTable should succeed because tables should exist
Account.dropTable()
PresenceAccount.dropTable()
del account.hub.threadConnection
os.unlink(DB_PATH)
self.assertFalse(os.access("/tmp/jcl.pid", os.F_OK))
def test__get_help(self):
self.assertNotEquals(self.runner._get_help(), None)
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(JCLRunner_TestCase, 'test'))
return suite
if __name__ == '__main__':
unittest.main(defaultTest='suite')