"""
Main class of stats module
"""
- def __init__(self):
+ def __init__(self, module_ccsession_class=isc.config.ModuleCCSession):
+ '''Constructor
+
+ module_ccsession_class is parameterized so that test can specify
+ a mocked class to test the behavior without involing network I/O.
+ In other cases this parameter shouldn't be specified.
+
+ '''
self.running = False
# create ModuleCCSession object
- self.mccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
- self.config_handler,
- self.command_handler)
+ self.mccs = module_ccsession_class(SPECFILE_LOCATION,
+ self.config_handler,
+ self.command_handler)
self.cc_session = self.mccs._session
# get module spec
self.module_name = self.mccs.get_module_spec().get_module_name()
])
# set a absolute timestamp polling at next time
self.next_polltime = get_timestamp() + self.get_interval()
- # initialized Statistics data
+
+ self._init_statistics_data()
+
+ def _init_statistics_data(self):
+ """initialized Statistics data.
+
+ This method is a dedicated subroutine of __int__(), but extracted
+ so tests can override it to avoid blocking network operation.
+
+ """
self.update_modules()
if self.update_statistics_data(
self.module_name,
# if successfully done, set the last time of polling
self._lasttime_poll = get_timestamp()
+ def _check_command(self, nonblock=False):
+ """check invoked command by waiting for 'poll-interval' seconds
+
+ This is a dedicated subroutine of start(), but extracted and defined
+ as a 'protected' method so that tests can replace it.
+
+ """
+ # backup original timeout
+ orig_timeout = self.cc_session.get_timeout()
+ # set cc-session timeout to half of a second(500ms)
+ self.cc_session.set_timeout(500)
+ try:
+ answer, env = self.cc_session.group_recvmsg(nonblock)
+ self.mccs.check_command_without_recvmsg(answer, env)
+ except isc.cc.session.SessionTimeout:
+ pass # waited for poll-interval seconds
+ # restore timeout
+ self.cc_session.set_timeout(orig_timeout)
+
def start(self):
"""
Start stats module
"""
logger.info(STATS_STARTING)
- def _check_command(nonblock=False):
- """check invoked command by waiting for 'poll-interval'
- seconds"""
- # backup original timeout
- orig_timeout = self.cc_session.get_timeout()
- # set cc-session timeout to half of a second(500ms)
- self.cc_session.set_timeout(500)
- try:
- answer, env = self.cc_session.group_recvmsg(nonblock)
- self.mccs.check_command_without_recvmsg(answer, env)
- except isc.cc.session.SessionTimeout:
- pass # waited for poll-interval seconds
- # restore timeout
- self.cc_session.set_timeout(orig_timeout)
-
try:
self.running = True
while self.running:
- _check_command()
+ self._check_command()
now = get_timestamp()
intval = self.get_interval()
if intval > 0 and now >= self.next_polltime:
import stats
import isc.log
import isc.cc.session
-from test_utils import BaseModules, ThreadingServerManager, MyStats, SignalHandler, send_command
+from test_utils import BaseModules, ThreadingServerManager, MyStats, \
+ SignalHandler, MyModuleCCSession, send_command
from isc.testutils.ccsession_mock import MockModuleCCSession
class TestUtilties(unittest.TestCase):
self.assertRaises(stats.StatsError, stats.Stats)
stats.SPECFILE_LOCATION = orig_spec_location
+ class SimpleStat(stats.Stats):
+ def __init__(self):
+ stats.Stats.__init__(self, MyModuleCCSession)
+
+ def _init_statistics_data(self):
+ pass
+
+ def __send_command(self, stats, command_name, params=None):
+ '''Emulate a command arriving to stats by directly calling callback'''
+ return isc.config.ccsession.parse_answer(
+ stats.command_handler(command_name, params))
+
def test_start(self):
+ # Define a separate exception class so we can be sure that's actually
+ # the one raised in __check_start() below
+ class CheckException(Exception):
+ pass
+
+ def __check_start(tested_stats):
+ self.assertTrue(tested_stats.running)
+ raise CheckException # terminate the loop
+
# start without err
- self.stats_server = ThreadingServerManager(MyStats)
- self.stats = self.stats_server.server
- self.assertFalse(self.stats.running)
- self.stats_server.run()
- self.assertEqual(send_command("status", "Stats"),
- (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
- self.assertTrue(self.stats.running)
- # Due to a race-condition related to the threads used in these
- # tests, use of the mock session and the stopped check (see
- # below), are temporarily disabled
- # See ticket #1668
- # Override moduleCCSession so we can check if send_stopping is called
- #self.stats.mccs = MockModuleCCSession()
- self.assertEqual(send_command("shutdown", "Stats"), (0, None))
- self.assertFalse(self.stats.running)
- # 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)
- # Also temporarily disabled for #1668, see above
- #self.assertTrue(self.stats.mccs.stopped)
+ stats = self.SimpleStat()
+ self.assertFalse(stats.running)
+ stats._check_command = lambda: __check_start(stats)
+ # We are going to confirm start() will set running to True, avoidng
+ # to fall into a loop with the exception trick.
+ self.assertRaises(CheckException, stats.start)
+ self.assertEqual(self.__send_command(stats, "status"),
+ (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
+
+ def test_shutdown(self):
+ def __check_shutdown(tested_stats):
+ self.assertTrue(tested_stats.running)
+ self.assertEqual(self.__send_command(tested_stats, "shutdown"),
+ (0, None))
+ self.assertFalse(tested_stats.running)
+ # override get_interval() so it won't go poll statistics
+ tested_stats.get_interval = lambda : 0
+
+ stats = self.SimpleStat()
+ stats._check_command = lambda: __check_shutdown(stats)
+ stats.start()
+ self.assertTrue(stats.mccs.stopped)
def test_handlers(self):
self.stats_server = ThreadingServerManager(MyStats)
return isc.config.create_answer(0, sdata)
return isc.config.create_answer(1, "Unknown Command")
+class MyModuleCCSession(isc.config.ConfigData):
+ """Mocked ModuleCCSession class.
+
+ This class incorporates the module spec directly from the file,
+ and works as if the ModuleCCSession class as much as possible
+ without involving network I/O.
+
+ """
+ def __init__(self, spec_file, config_handler, command_handler):
+ module_spec = isc.config.module_spec_from_file(spec_file)
+ isc.config.ConfigData.__init__(self, module_spec)
+ self._session = self
+ self.stopped = False
+
+ def start(self):
+ pass
+
+ def send_stopping(self):
+ self.stopped = True # just record it's called to inspect it later
+
class MyStats(stats.Stats):
stats._BASETIME = CONST_BASETIME