Commit 17e78fa1 authored by Jelte Jansen's avatar Jelte Jansen
Browse files

[master] Merge branch 'trac640'

parents db3d9fdf 2da5a3de
......@@ -679,7 +679,13 @@ class BoB:
def shutdown(self):
"""Stop the BoB instance."""
logger.info(BIND10_SHUTDOWN)
# first try using the BIND 10 request to stop
# If ccsession is still there, inform rest of the system this module
# is stopping. Since everything will be stopped shortly, this is not
# really necessary, but this is done to reflect that boss is also
# 'just' a module.
self.ccs.send_stopping()
# try using the BIND 10 request to stop
try:
self._component_configurator.shutdown()
except:
......
......@@ -36,6 +36,7 @@ import isc.bind10.socket_cache
import errno
from isc.testutils.parse_args import TestOptParser, OptsError
from isc.testutils.ccsession_mock import MockModuleCCSession
class TestProcessInfo(unittest.TestCase):
def setUp(self):
......@@ -1171,8 +1172,12 @@ class TestBossComponents(unittest.TestCase):
bob._component_configurator.shutdown = self.__nullary_hook
self.__called = False
bob.ccs = MockModuleCCSession()
self.assertFalse(bob.ccs.stopped)
bob.shutdown()
self.assertTrue(bob.ccs.stopped)
self.assertEqual([False, True], killed)
self.assertTrue(self.__called)
......
......@@ -310,12 +310,25 @@ class CommandControl():
def command_handler(self, command, args):
answer = ccsession.create_answer(0)
if command == ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE:
# The 'value' of a specification update can be either
# a specification, or None. In the first case, simply
# set it. If it is None, delete the module if it is
# known.
with self._lock:
self.modules_spec[args[0]] = args[1]
if args[1] is None:
if args[0] in self.modules_spec:
del self.modules_spec[args[0]]
else:
answer = ccsession.create_answer(1,
'No such module: ' +
args[0])
else:
self.modules_spec[args[0]] = args[1]
elif command == ccsession.COMMAND_SHUTDOWN:
#When cmdctl get 'shutdown' command from boss,
#shutdown the outer httpserver.
self._module_cc.send_stopping()
self._httpserver.shutdown()
self._serving = False
......
......@@ -345,6 +345,40 @@ class TestCommandControl(unittest.TestCase):
self.assertEqual(rcode, 0)
self.assertTrue(msg != None)
def test_command_handler_spec_update(self):
# Should not be present
self.assertFalse("foo" in self.cmdctl.modules_spec)
answer = self.cmdctl.command_handler(
ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE, [ "foo", {} ])
rcode, msg = ccsession.parse_answer(answer)
self.assertEqual(rcode, 0)
self.assertEqual(msg, None)
# Should now be present
self.assertTrue("foo" in self.cmdctl.modules_spec)
# When sending specification 'None', it should be removed
answer = self.cmdctl.command_handler(
ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE, [ "foo", None ])
rcode, msg = ccsession.parse_answer(answer)
self.assertEqual(rcode, 0)
self.assertEqual(msg, None)
# Should no longer be present
self.assertFalse("foo" in self.cmdctl.modules_spec)
# Don't store 'None' if it wasn't there in the first place!
answer = self.cmdctl.command_handler(
ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE, [ "foo", None ])
rcode, msg = ccsession.parse_answer(answer)
self.assertEqual(rcode, 1)
self.assertEqual(msg, "No such module: foo")
# Should still not present
self.assertFalse("foo" in self.cmdctl.modules_spec)
def test_check_config_handler(self):
answer = self.cmdctl.config_handler({'non-exist': 123})
self._check_answer(answer, 1, 'unknown config item: non-exist')
......
......@@ -150,9 +150,10 @@ class DDNSServer:
Perform any cleanup that is necessary when shutting down the server.
Do NOT call this to initialize shutdown, use trigger_shutdown().
Currently, it does nothing, but cleanup routines are expected.
Currently, it only causes the ModuleCCSession to send a message that
this module is stopping.
'''
pass
self._cc.send_stopping()
def accept(self):
"""
......
......@@ -58,11 +58,16 @@ class MyCCSession(isc.config.ConfigData):
ddns.SPECFILE_LOCATION)
isc.config.ConfigData.__init__(self, module_spec)
self._started = False
self._stopped = False
def start(self):
'''Called by DDNSServer initialization, but not used in tests'''
self._started = True
def send_stopping(self):
'''Called by shutdown code'''
self._stopped = True
def get_socket(self):
"""
Used to get the file number for select.
......@@ -289,6 +294,7 @@ class TestDDNSServer(unittest.TestCase):
self.__select_answer = ([3], [], [])
self.ddns_server.run()
self.assertTrue(self.ddns_server._shutdown)
self.assertTrue(self.__cc_session._stopped)
self.assertIsNone(self.__select_answer)
self.assertEqual(3, self.__hook_called)
......
......@@ -184,8 +184,11 @@ class Stats:
raise StatsError("stats spec file is incorrect: "
+ ", ".join(errors))
while self.running:
self.mccs.check_command(False)
try:
while self.running:
self.mccs.check_command(False)
finally:
self.mccs.send_stopping()
def config_handler(self, new_config):
"""
......
......@@ -203,6 +203,7 @@ class StatsHttpd:
"""Closes a ModuleCCSession object"""
if self.mccs is None:
return
self.mccs.send_stopping()
logger.debug(DBG_STATHTTPD_INIT, STATHTTPD_CLOSING_CC_SESSION)
self.mccs.close()
......
......@@ -37,7 +37,10 @@ import random
import isc
import stats_httpd
import stats
from test_utils import BaseModules, ThreadingServerManager, MyStats, MyStatsHttpd, SignalHandler, send_command, send_shutdown
from test_utils import BaseModules, ThreadingServerManager, MyStats,\
MyStatsHttpd, SignalHandler,\
send_command, send_shutdown
from isc.testutils.ccsession_mock import MockModuleCCSession
DUMMY_DATA = {
'Boss' : {
......@@ -676,7 +679,13 @@ class TestStatsHttpd(unittest.TestCase):
def test_openclose_mccs(self):
self.stats_httpd = MyStatsHttpd(get_availaddr())
mccs = MockModuleCCSession()
self.stats_httpd.mccs = mccs
self.assertFalse(self.stats_httpd.mccs.stopped)
self.assertFalse(self.stats_httpd.mccs.closed)
self.stats_httpd.close_mccs()
self.assertTrue(mccs.stopped)
self.assertTrue(mccs.closed)
self.assertEqual(self.stats_httpd.mccs, None)
self.stats_httpd.open_mccs()
self.assertIsNotNone(self.stats_httpd.mccs)
......
......@@ -31,6 +31,7 @@ import imp
import stats
import isc.cc.session
from test_utils import BaseModules, ThreadingServerManager, MyStats, SignalHandler, send_command, send_shutdown
from isc.testutils.ccsession_mock import MockModuleCCSession
class TestUtilties(unittest.TestCase):
items = [
......@@ -201,9 +202,15 @@ class TestStats(unittest.TestCase):
self.assertEqual(send_command("status", "Stats"),
(0, "Stats is up. (PID " + str(os.getpid()) + ")"))
self.assertTrue(self.stats.running)
# Override moduleCCSession so we can check if send_stopping is called
self.stats.mccs = MockModuleCCSession()
self.assertEqual(send_shutdown("Stats"), (0, None))
self.assertFalse(self.stats.running)
self.stats_server.shutdown()
# Call server.shutdown with argument True so the thread.join() call
# blocks and we are sure the main loop has finished (and set
# mccs.stopped)
self.stats_server.shutdown(True)
self.assertTrue(self.stats.mccs.stopped)
# start with err
self.stats = stats.Stats()
......
......@@ -72,9 +72,19 @@ class ThreadingServerManager:
self.server._started.wait()
self.server._started.clear()
def shutdown(self):
def shutdown(self, blocking=False):
"""Shut down the server by calling its own shutdown() method.
Then wait for its thread to finish. If blocking is True,
the thread.join() blocks until the thread finishes. If not,
it uses a zero timeout. The latter is necessary in a number
of existing tests. We should redo this part (we should not
even need threads in most, if not all, of these threads, see
ticket #1668)"""
self.server.shutdown()
self.server._thread.join(0) # timeout is 0
if blocking:
self.server._thread.join()
else:
self.server._thread.join(0) # timeout is 0
def do_nothing(*args, **kwargs): pass
......
......@@ -20,6 +20,7 @@ import socket
import sys
import io
from isc.testutils.tsigctx_mock import MockTSIGContext
from isc.testutils.ccsession_mock import MockModuleCCSession
from isc.testutils.rrset_utils import *
from xfrin import *
import xfrin
......@@ -105,7 +106,7 @@ class XfrinTestException(Exception):
class XfrinTestTimeoutException(Exception):
pass
class MockCC():
class MockCC(MockModuleCCSession):
def get_default_value(self, identifier):
# The returned values should be identical to the spec file
# XXX: these should be retrieved from the spec file
......@@ -2052,7 +2053,9 @@ class TestXfrin(unittest.TestCase):
self.args['tsig_key'] = ''
def tearDown(self):
self.assertFalse(self.xfr._module_cc.stopped);
self.xfr.shutdown()
self.assertTrue(self.xfr._module_cc.stopped);
sys.stderr= self.stderr_backup
def _do_parse_zone_name_class(self):
......
......@@ -1224,6 +1224,7 @@ class Xfrin:
''' shutdown the xfrin process. the thread which is doing xfrin should be
terminated.
'''
self._module_cc.send_stopping()
self._shutdown_event.set()
main_thread = threading.currentThread()
for th in threading.enumerate():
......
......@@ -19,6 +19,7 @@
import unittest
import os
from isc.testutils.tsigctx_mock import MockTSIGContext
from isc.testutils.ccsession_mock import MockModuleCCSession
from isc.cc.session import *
import isc.config
from isc.dns import *
......@@ -1423,6 +1424,32 @@ class TestInitialization(unittest.TestCase):
xfrout.init_paths()
self.assertEqual(xfrout.UNIX_SOCKET_FILE, "The/Socket/File")
class MyNotifier():
def __init__(self):
self.shutdown_called = False
def shutdown(self):
self.shutdown_called = True
class MyXfroutServer(XfroutServer):
def __init__(self):
self._cc = MockModuleCCSession()
self._shutdown_event = threading.Event()
self._notifier = MyNotifier()
self._unix_socket_server = None
# Disable the wait for threads
self._wait_for_threads = lambda : None
class TestXfroutServer(unittest.TestCase):
def setUp(self):
self.xfrout_server = MyXfroutServer()
def test_shutdown(self):
self.xfrout_server.shutdown()
self.assertTrue(self.xfrout_server._notifier.shutdown_called)
self.assertTrue(self.xfrout_server._cc.stopped)
if __name__== "__main__":
isc.log.resetUnitTestRootLogger()
unittest.main()
......@@ -969,12 +969,18 @@ class XfroutServer:
global xfrout_server
xfrout_server = None #Avoid shutdown is called twice
self._cc.send_stopping()
self._shutdown_event.set()
self._notifier.shutdown()
if self._unix_socket_server:
self._unix_socket_server.shutdown()
self._wait_for_threads()
# Wait for all threads to terminate
def _wait_for_threads(self):
# Wait for all threads to terminate. this is a call that is only used
# in shutdown(), but it has its own method, so we can test shutdown
# without involving thread operations (the test would override this
# method)
main_thread = threading.currentThread()
for th in threading.enumerate():
if th is main_thread:
......
......@@ -20,6 +20,7 @@ import unittest
import os
import tempfile
from zonemgr import *
from isc.testutils.ccsession_mock import MockModuleCCSession
ZONE_NAME_CLASS1_IN = ("example.net.", "IN")
ZONE_NAME_CLASS1_CH = ("example.net.", "CH")
......@@ -48,10 +49,11 @@ class MySession():
def group_recvmsg(self, nonblock, seq):
return None, None
class FakeCCSession(isc.config.ConfigData):
class FakeCCSession(isc.config.ConfigData, MockModuleCCSession):
def __init__(self):
module_spec = isc.config.module_spec_from_file(SPECFILE_LOCATION)
ConfigData.__init__(self, module_spec)
MockModuleCCSession.__init__(self)
def get_remote_config_value(self, module_name, identifier):
if module_name == "Auth" and identifier == "database_file":
......@@ -683,6 +685,12 @@ class TestZonemgr(unittest.TestCase):
self.zonemgr._config_data_check(config_data3)
self.assertEqual(0.5, config_data3.get("refresh_jitter"))
def test_shutdown(self):
self.assertFalse(self.zonemgr._module_cc.stopped)
self.zonemgr._shutdown_event.set()
self.zonemgr.run()
self.assertTrue(self.zonemgr._module_cc.stopped)
def tearDown(self):
pass
......
......@@ -658,8 +658,11 @@ class Zonemgr:
def run(self):
self.running = True
while not self._shutdown_event.is_set():
self._module_cc.check_command(False)
try:
while not self._shutdown_event.is_set():
self._module_cc.check_command(False)
finally:
self._module_cc.send_stopping()
zonemgrd = None
......
......@@ -489,6 +489,18 @@ ModuleCCSession::ModuleCCSession(
}
ModuleCCSession::~ModuleCCSession() {
try {
sendStopping();
} catch (const std::exception& exc) {
LOG_ERROR(config_logger,
CONFIG_CCSESSION_STOPPING).arg(exc.what());
} catch (...) {
LOG_ERROR(config_logger,
CONFIG_CCSESSION_STOPPING_UNKNOWN);
}
};
void
ModuleCCSession::start() {
if (started_) {
......@@ -741,5 +753,16 @@ ModuleCCSession::updateRemoteConfig(const std::string& module_name,
}
}
void
ModuleCCSession::sendStopping() {
// Inform the configuration manager that this module is stopping
ConstElementPtr cmd(createCommand("stopping",
Element::fromJSON(
"{\"module_name\": \"" +
module_name_ + "\"}")));
// It's just an FYI, configmanager is not expected to respond.
session_.group_sendmsg(cmd, "ConfigManager");
}
}
}
......@@ -192,6 +192,14 @@ public:
bool handle_logging = true
);
///
/// Destructor
///
/// The destructor automatically calls sendStopping(), which sends
/// a message to the ConfigManager that this module is stopping
///
virtual ~ModuleCCSession();
/// Start receiving new commands and configuration changes asynchronously.
///
/// This method must be called only once, and only when the ModuleCCSession
......@@ -353,6 +361,7 @@ public:
private:
ModuleSpec readModuleSpecification(const std::string& filename);
void startCheck();
void sendStopping();
bool started_;
std::string module_name_;
......
......@@ -30,6 +30,18 @@ but will not send back an answer.
The most likely cause of this error is a programming error. Please raise
a bug report.
% CONFIG_CCSESSION_STOPPING error sending stopping message: %1
There was a problem when sending a message signaling that the module using
this CCSession is stopping. This message is sent so that the rest of the
system is aware that the module is no longer running. Apart from logging
this message, the error itself is ignored, and the ModuleCCSession is
still stopped. The specific exception message is printed.
% CONFIG_CCSESSION_STOPPING_UNKNOWN unknown error sending stopping message
Similar to CONFIG_CCSESSION_STOPPING, but in this case the exception that
is seen is not a standard exception, and further information is unknown.
This is a bug.
% CONFIG_GET_FAIL error getting configuration from cfgmgr: %1
The configuration manager returned an error when this module requested
the configuration. The full error message answer from the configuration
......@@ -37,6 +49,11 @@ manager is appended to the log error. The most likely cause is that
the module is of a different (command specification) version than the
running configuration manager.
% CONFIG_JSON_PARSE JSON parse error in %1: %2
There was an error parsing the JSON file. The given file does not appear
to be in valid JSON format. Please verify that the filename is correct
and that the contents are valid JSON.
% CONFIG_LOG_EXPLICIT will use logging configuration for explicitly-named logger %1
This is a debug message. When processing the "loggers" part of the
configuration file, the configuration library found an entry for the named
......@@ -62,11 +79,6 @@ wildcard entry (one containing the "*" character) that matches a logger
specification in the program. The logging configuration for the program
will be updated with the information.
% CONFIG_JSON_PARSE JSON parse error in %1: %2
There was an error parsing the JSON file. The given file does not appear
to be in valid JSON format. Please verify that the filename is correct
and that the contents are valid JSON.
% CONFIG_MOD_SPEC_FORMAT module specification error in %1: %2
The given file does not appear to be a valid specification file: details
are included in the message. Please verify that the filename is correct
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment