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

21
conf/jcl.conf Normal file
View File

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

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""" """JCL module"""
__revision__ = "" __revision__ = ""
version = "0.1.0"

View File

@@ -62,7 +62,6 @@ VERSION = "0.1"
############################################################################### ###############################################################################
class JCLComponent(Component, object): class JCLComponent(Component, object):
"""Implement default JCL component behavior: """Implement default JCL component behavior:
- regular interval behavior
- Jabber register process (add, delete, update accounts) - Jabber register process (add, delete, update accounts)
- Jabber presence handling - Jabber presence handling
- passwork request at login - 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 import unittest
from jcl.tests import lang from jcl.tests import lang, runner
from jcl.jabber import tests as jabber from jcl.jabber import tests as jabber
from jcl.model import tests as model from jcl.model import tests as model
def suite(): def suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTest(lang.suite()) suite.addTest(lang.suite())
suite.addTest(runner.suite())
suite.addTest(jabber.suite()) suite.addTest(jabber.suite())
suite.addTest(model.suite()) suite.addTest(model.suite())
return 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')