From f285836f928bbf4e976a7f7b755b785bda9253ca Mon Sep 17 00:00:00 2001 From: David Rousselie Date: Fri, 7 Sep 2007 20:13:37 +0200 Subject: [PATCH] 'restart' ad-hoc command implementation darcs-hash:20070907181337-86b55-09ec29feccd7d0ea27ea0a3a324eebc89a932aa4.gz --- src/jcl/jabber/command.py | 45 +++++++++++- src/jcl/jabber/component.py | 13 +++- src/jcl/jabber/tests/command.py | 117 ++++++++++++++++++++++++++---- src/jcl/jabber/tests/component.py | 21 +++++- src/jcl/runner.py | 6 +- src/jcl/tests/runner.py | 25 +++++++ 6 files changed, 207 insertions(+), 20 deletions(-) diff --git a/src/jcl/jabber/command.py b/src/jcl/jabber/command.py index b0fc8ca..00bf84d 100644 --- a/src/jcl/jabber/command.py +++ b/src/jcl/jabber/command.py @@ -24,6 +24,7 @@ import re import datetime import logging +import threading from sqlobject.sqlbuilder import AND @@ -885,8 +886,48 @@ class JCLCommandManager(CommandManager): command_node.setProp("status", STATUS_COMPLETED) return (None, []) - def execute_restart(self, info_query): - return [] + def execute_restart_1(self, info_query, session_context, + command_node, lang_class): + self.add_actions(command_node, [ACTION_NEXT]) + result_form = Form(xmlnode_or_type="result", + title="TODO", + instructions="TODO") + result_form.add_field(field_type="hidden", + name="FORM_TYPE", + value="http://jabber.org/protocol/admin") + result_form.add_field(name="delay", + field_type="list-multi", + label="TODO", + required=True) + result_form.add_field(name="announcement", + field_type="text-multi", + label="TODO") + result_form.as_xml(command_node) + return (result_form, []) + + def execute_restart_2(self, info_query, session_context, + command_node, lang_class): + self.__logger.debug("Executing command 'restart' step 2") + announcement = session_context["announcement"][0] + delay = int(session_context["delay"][0]) + if announcement is not None and announcement != "": + users = account.get_all_users(\ + filter=AND(Account.q.userID == User.q.id, + Account.q._status != account.OFFLINE), + distinct=True) + result = [] + for user in users: + result.append(Message(from_jid=self.component.jid, + to_jid=user.jid, + body=announcement)) + command_node.setProp("status", STATUS_COMPLETED) + def delayed_restart(self, delay): + threading.Event().wait(delay) + self.component.restart = True + restart_thread = threading.Thread(target=lambda : delayed_restart(self, delay), + name="TimerThread") + restart_thread.start() + return (None, result) def execute_shutdown(self, info_query): return [] diff --git a/src/jcl/jabber/component.py b/src/jcl/jabber/component.py index a27a47b..075048f 100644 --- a/src/jcl/jabber/component.py +++ b/src/jcl/jabber/component.py @@ -133,6 +133,7 @@ class JCLComponent(Component, object): self.lang = lang self.running = False self.wait_event = threading.Event() + self._restart = False signal.signal(signal.SIGINT, self.signal_handler) signal.signal(signal.SIGTERM, self.signal_handler) @@ -168,6 +169,16 @@ class JCLComponent(Component, object): timer_thread.join(JCLComponent.timeout) self.disconnect() self.__logger.debug("Exitting normally") + return self._restart + + def _get_restart(self): + return self._restart + + def _set_restart(self, __restart): + self.running = not __restart + self._restart = __restart + + restart = property(_get_restart, _set_restart) ########################################################################### # Handlers @@ -183,7 +194,7 @@ class JCLComponent(Component, object): and self.stream.socket is not None): self.wait_event.wait(self.time_unit) self.handle_tick() - self.__logger.debug("Resetting alarm signal") + self.__logger.debug(".") except Exception, exception: type, value, stack = sys.exc_info() self.__logger.error("Error in timer thread\n%s\n%s" diff --git a/src/jcl/jabber/tests/command.py b/src/jcl/jabber/tests/command.py index b83d5d6..4660986 100644 --- a/src/jcl/jabber/tests/command.py +++ b/src/jcl/jabber/tests/command.py @@ -24,6 +24,7 @@ import unittest import os import tempfile from ConfigParser import ConfigParser +import threading from pyxmpp.jid import JID from pyxmpp.presence import Presence @@ -2813,14 +2814,104 @@ class JCLCommandManager_TestCase(JCLTestCase): self.assertEquals(self.comp.config.get("component", "admins"), "admin3@test.com,admin4@test.com") -# def test_execute_restart(self): -# #TODO : implement command -# info_query = Iq(stanza_type="set", -# from_jid="admin@test.com", -# to_jid="jcl.test.com") -# result = self.command_manager.execute_add_user(info_query) -# self.assertNotEquals(result, None) -# self.assertEquals(len(result), 1) + def test_execute_restart(self): + self.comp.account_manager.account_classes = (ExampleAccount, + Example2Account) + self.comp.running = True + model.db_connect() + user1 = User(jid="test1@test.com") + account11 = ExampleAccount(user=user1, + name="account11", + jid="account11@jcl.test.com") + account11.status = account.ONLINE + account12 = Example2Account(user=user1, + name="account12", + jid="account12@jcl.test.com") + account12.status = "away" + user2 = User(jid="test2@test.com") + account21 = ExampleAccount(user=user2, + name="account21", + jid="account21@jcl.test.com") + account22 = ExampleAccount(user=user2, + name="account11", + jid="account11@jcl.test.com") + account22.status = "xa" + model.db_disconnect() + info_query = Iq(stanza_type="set", + from_jid="admin@test.com", + to_jid="jcl.test.com") + command_node = info_query.set_new_content(command.COMMAND_NS, "command") + command_node.setProp("node", + "http://jabber.org/protocol/admin#restart") + result = self.command_manager.apply_command_action(\ + info_query, + "http://jabber.org/protocol/admin#restart", + "execute") + self.assertNotEquals(result, None) + self.assertEquals(len(result), 1) + xml_command = result[0].xpath_eval("c:command", + {"c": "http://jabber.org/protocol/commands"})[0] + self.assertEquals(xml_command.prop("status"), "executing") + self.assertNotEquals(xml_command.prop("sessionid"), None) + self.__check_actions(result[0], ["next"]) + fields = result[0].xpath_eval("c:command/data:x/data:field", + {"c": "http://jabber.org/protocol/commands", + "data": "jabber:x:data"}) + self.assertEquals(len(fields), 3) + self.assertEquals(fields[1].prop("var"), "delay") + self.assertEquals(fields[1].prop("type"), "list-multi") + self.assertEquals(fields[2].prop("var"), "announcement") + self.assertEquals(fields[2].prop("type"), "text-multi") + + # Second step + info_query = Iq(stanza_type="set", + from_jid="admin@test.com", + to_jid="jcl.test.com") + command_node = info_query.set_new_content(command.COMMAND_NS, "command") + command_node.setProp("node", + "http://jabber.org/protocol/admin#restart") + session_id = xml_command.prop("sessionid") + command_node.setProp("sessionid", session_id) + command_node.setProp("action", "next") + submit_form = Form(xmlnode_or_type="submit") + submit_form.add_field(field_type="list-multi", + name="delay", + value=[0]) + submit_form.add_field(field_type="text-multi", + name="announcement", + value=["service will be restarted in 0 second"]) + submit_form.as_xml(command_node) + result = self.command_manager.apply_command_action(\ + info_query, + "http://jabber.org/protocol/admin#restart", + "execute") + self.assertNotEquals(result, None) + self.assertEquals(len(result), 3) + xml_command = result[0].xpath_eval("c:command", + {"c": "http://jabber.org/protocol/commands"})[0] + self.assertEquals(xml_command.prop("status"), "completed") + self.assertEquals(xml_command.prop("sessionid"), session_id) + self.__check_actions(result[0]) + context_session = self.command_manager.sessions[session_id][1] + self.assertEquals(context_session["announcement"], + ["service will be restarted in 0 second"]) + self.assertEquals(context_session["delay"], + ["0"]) + self.assertEquals(result[1].get_from(), "jcl.test.com") + self.assertEquals(result[1].get_to(), "test1@test.com") + self.assertEquals(result[1].get_body(), "service will be restarted in 0 second") + self.assertEquals(result[2].get_from(), "jcl.test.com") + self.assertEquals(result[2].get_to(), "test2@test.com") + self.assertEquals(result[2].get_body(), "service will be restarted in 0 second") + self.assertFalse(self.comp.restart) + self.assertTrue(self.comp.running) + threads = threading.enumerate() + self.assertEquals(len(threads), 2) + threading.Event().wait(1) + threads = threading.enumerate() + self.assertEquals(len(threads), 1) + self.assertTrue(self.comp.restart) + self.assertFalse(self.comp.running) # def test_execute_shutdown(self): # #TODO : implement command @@ -2832,11 +2923,11 @@ class JCLCommandManager_TestCase(JCLTestCase): # self.assertEquals(len(result), 1) def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(CommandManager_TestCase, 'test')) - suite.addTest(unittest.makeSuite(FieldNoType_TestCase, 'test')) - suite.addTest(unittest.makeSuite(JCLCommandManager_TestCase, 'test')) - return suite + test_suite = unittest.TestSuite() + test_suite.addTest(unittest.makeSuite(CommandManager_TestCase, 'test')) + test_suite.addTest(unittest.makeSuite(FieldNoType_TestCase, 'test')) + test_suite.addTest(unittest.makeSuite(JCLCommandManager_TestCase, 'test')) + return test_suite if __name__ == '__main__': unittest.main(defaultTest='suite') diff --git a/src/jcl/jabber/tests/component.py b/src/jcl/jabber/tests/component.py index e770cec..99e7d26 100644 --- a/src/jcl/jabber/tests/component.py +++ b/src/jcl/jabber/tests/component.py @@ -242,7 +242,25 @@ class JCLComponent_TestCase(JCLTestCase): # Tests in subclasses might be more precise self.comp.stream = MockStreamNoConnect() self.comp.stream_class = MockStreamNoConnect - self.comp.run() + result = self.comp.run() + self.assertFalse(result) + self.assertTrue(self.comp.stream.connection_started) + threads = threading.enumerate() + self.assertEquals(len(threads), 1) + self.assertTrue(self.comp.stream.connection_stopped) + if self.comp.queue.qsize(): + raise self.comp.queue.get(0) + + def test_run_restart(self): + """Test main loop execution with restart""" + self.comp.time_unit = 1 + # Do not loop, handle_tick is virtual + # Tests in subclasses might be more precise + self.comp.stream = MockStreamNoConnect() + self.comp.stream_class = MockStreamNoConnect + self.comp.restart = True + result = self.comp.run() + self.assertTrue(result) self.assertTrue(self.comp.stream.connection_started) threads = threading.enumerate() self.assertEquals(len(threads), 1) @@ -258,7 +276,6 @@ class JCLComponent_TestCase(JCLTestCase): self.comp.stream = MockStreamRaiseException() self.comp.stream_class = MockStreamRaiseException self.comp.handle_tick = do_nothing - try: self.comp.run() except Exception, e: diff --git a/src/jcl/runner.py b/src/jcl/runner.py index 1a33f1d..696ad81 100644 --- a/src/jcl/runner.py +++ b/src/jcl/runner.py @@ -194,7 +194,9 @@ class JCLRunner(object): model.db_disconnect() self.logger.debug(self.component_name + " v" + self.component_version + " is starting ...") - run_func() + restart = True + while restart: + restart = run_func() self.logger.debug(self.component_name + " is exiting") finally: if os.path.exists(self.pid_file): @@ -209,6 +211,6 @@ class JCLRunner(object): lang=Lang(self.language), config=self.config, config_file=self.config_file) - component.run() + return component.run() self._run(run_func) diff --git a/src/jcl/tests/runner.py b/src/jcl/tests/runner.py index 662cf7c..18c8375 100644 --- a/src/jcl/tests/runner.py +++ b/src/jcl/tests/runner.py @@ -136,6 +136,31 @@ class JCLRunner_TestCase(unittest.TestCase): model.db_disconnect() os.unlink(db_path) self.assertFalse(os.access("/tmp/jcl.pid", os.F_OK)) + + def test__run_restart(self): + self.runner.pid_file = "/tmp/jcl.pid" + db_path = tempfile.mktemp("db", "jcltest", DB_DIR) + db_url = "sqlite://" + db_path + self.runner.db_url = db_url + self.i = 0 + def restart(self): + self.i += 1 + yield True + self.i += 1 + yield False + self.i += 1 + restart_generator = restart(self) + self.runner._run(lambda : restart_generator.next()) + model.db_connect() + # dropTable should succeed because tables should exist + Account.dropTable() + PresenceAccount.dropTable() + User.dropTable() + LegacyJID.dropTable() + model.db_disconnect() + os.unlink(db_path) + self.assertFalse(os.access("/tmp/jcl.pid", os.F_OK)) + self.assertEquals(self.i, 2) def test__get_help(self): self.assertNotEquals(self.runner._get_help(), None)