BIND 10 trac213, updated. be3038ae1b595d1b9942f9aa72fa3d96aed3b22d [213] Parts of the configurator startup
BIND 10 source code commits
bind10-changes at lists.isc.org
Thu Oct 6 12:14:12 UTC 2011
The branch, trac213 has been updated
via be3038ae1b595d1b9942f9aa72fa3d96aed3b22d (commit)
from e81b86767a740bcb1c4d1a0408ad9a70690df0a6 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit be3038ae1b595d1b9942f9aa72fa3d96aed3b22d
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Thu Oct 6 14:12:40 2011 +0200
[213] Parts of the configurator startup
The tests are complete, the implementation of _build_plan is still
missing.
-----------------------------------------------------------------------
Summary of changes:
src/lib/python/isc/bind10/component.py | 57 ++++++-
src/lib/python/isc/bind10/tests/component_test.py | 197 ++++++++++++++++++---
2 files changed, 226 insertions(+), 28 deletions(-)
-----------------------------------------------------------------------
diff --git a/src/lib/python/isc/bind10/component.py b/src/lib/python/isc/bind10/component.py
index 37d948f..c93b2fd 100644
--- a/src/lib/python/isc/bind10/component.py
+++ b/src/lib/python/isc/bind10/component.py
@@ -163,6 +163,11 @@ class Configurator:
This thing keeps track of configuration changes and starts and stops
components as it goes. It also handles the inital startup and final
shutdown.
+
+ Note that this will allow you to stop (by invoking reconfigure) a core
+ component. There should be some kind of layer protecting users from ever
+ doing so (users must not stop the config manager, message queue and stuff
+ like that or the system won't start again).
"""
def __init__(self, boss):
"""
@@ -170,7 +175,12 @@ class Configurator:
The boss parameter is the boss object used to start and stop processes.
"""
- pass
+ self.__boss = boss
+ # These could be __private, but as we access them from within unittest,
+ # it's more comfortable to have them just _protected.
+ self._components = {}
+ self._old_config = {}
+ self._running = False
def startup(self, configuration):
"""
@@ -178,7 +188,12 @@ class Configurator:
to be hardcoded from the boss itself to start the configuration
manager and other similar things.
"""
- pass
+ if self._running:
+ raise ValueError("Trying to start the component configurator " +
+ "twice")
+ self._run_plan(self._build_plan({}, configuration))
+ self._old_config = configuration
+ self._running = True
def shutdown(self):
"""
@@ -192,3 +207,41 @@ class Configurator:
starts and stops all the components as needed.
"""
pass
+
+ def _build_plan(self, old, new):
+ """
+ Builds a plan how to transfer from the old configuration to the new
+ one. It'll be sorted by priority and it will contain the components
+ (already created, but not started). Each command in the plan is a dict,
+ so it can be extended any time in future to include whatever
+ parameters each operation might need.
+
+ Any configuration problems are expected to be handled here, so the
+ plan is not yet run.
+ """
+ pass
+
+ def _run_plan(self, plan):
+ """
+ Run a plan, created beforehead by _build_plan.
+
+ With the start and stop commands, it also adds and removes components
+ in _components.
+
+ Currently implemented commands are:
+ * start
+ * stop
+ """
+ for task in plan:
+ component = task['component']
+ command = task['command']
+ if command == 'start':
+ component.start()
+ self._components[task['name']] = component
+ elif command == 'stop':
+ component.stop()
+ del self._components[task['name']]
+ else:
+ # Can Not Happen (as the plans are generated by ourself).
+ # Therefore not tested.
+ raise NotImplementedError("Command unknown: " + command)
diff --git a/src/lib/python/isc/bind10/tests/component_test.py b/src/lib/python/isc/bind10/tests/component_test.py
index ee93c67..2d15264 100644
--- a/src/lib/python/isc/bind10/tests/component_test.py
+++ b/src/lib/python/isc/bind10/tests/component_test.py
@@ -20,6 +20,7 @@ Tests for the bind10.component module
import unittest
import isc.log
import time
+import copy
from isc.bind10.component import Component, Configurator, specials
class TestError(Exception):
@@ -28,25 +29,24 @@ class TestError(Exception):
"""
pass
-class ComponentTests(unittest.TestCase):
+class BossUtils:
"""
- Tests for the bind10.component.Component class
+ A class that brings some utilities for pretending we're Boss.
+ This is expected to be inherited by the testcases themself.
"""
def setUp(self):
"""
- Pretend a newly started system.
+ Part of setup. Should be called by descendand's setUp.
"""
- self.__shutdown = False
- self.__exitcode = None
- self.__start_called = False
- self.__stop_called = False
- self.__failed_called = False
+ self._shutdown = False
+ self._exitcode = None
# Back up the time function, we may want to replace it with something
self.__orig_time = isc.bind10.component.time.time
def tearDown(self):
"""
- Clean up after tests.
+ Clean up after tests. If the descendand implements a tearDown, it
+ should call this method internally.
"""
# Return the original time function
isc.bind10.component.time.time = self.__orig_time
@@ -55,10 +55,10 @@ class ComponentTests(unittest.TestCase):
"""
Mock function to shut down. We just note we were asked to do so.
"""
- self.__shutdown = True
- self.__exitcode = None
+ self._shutdown = True
+ self._exitcode = None
- def __timeskip(self):
+ def _timeskip(self):
"""
Skip in time to future some 30s. Implemented by replacing the
time.time function in the tested module with function that returns
@@ -67,6 +67,21 @@ class ComponentTests(unittest.TestCase):
tm = time.time()
isc.bind10.component.time.time = lambda: tm + 30
+class ComponentTests(BossUtils, unittest.TestCase):
+ """
+ Tests for the bind10.component.Component class
+ """
+ def setUp(self):
+ """
+ Pretend a newly started system.
+ """
+ BossUtils.setUp(self)
+ self._shutdown = False
+ self._exitcode = None
+ self.__start_called = False
+ self.__stop_called = False
+ self.__failed_called = False
+
def __start(self):
"""
Mock function, installed into the component into start_internal.
@@ -120,7 +135,7 @@ class ComponentTests(unittest.TestCase):
Check that nothing was called yet. A newly created component should
not get started right away, so this should pass after the creation.
"""
- self.assertFalse(self.__shutdown)
+ self.assertFalse(self._shutdown)
self.assertFalse(self.__start_called)
self.assertFalse(self.__stop_called)
self.assertFalse(self.__failed_called)
@@ -133,7 +148,7 @@ class ComponentTests(unittest.TestCase):
"""
Check the component was started, but not stopped anyhow yet.
"""
- self.assertFalse(self.__shutdown)
+ self.assertFalse(self._shutdown)
self.assertTrue(self.__start_called)
self.assertFalse(self.__stop_called)
self.assertFalse(self.__failed_called)
@@ -143,11 +158,11 @@ class ComponentTests(unittest.TestCase):
"""
Check the component is completely dead, and the server too.
"""
- self.assertTrue(self.__shutdown)
+ self.assertTrue(self._shutdown)
self.assertTrue(self.__start_called)
self.assertFalse(self.__stop_called)
self.assertTrue(self.__failed_called)
- self.assertNotEqual(0, self.__exitcode)
+ self.assertNotEqual(0, self._exitcode)
self.assertFalse(component.running())
# Surely it can't be stopped again
self.assertRaises(ValueError, component.stop)
@@ -166,7 +181,7 @@ class ComponentTests(unittest.TestCase):
Reset the self.__start_called to False before calling the function when
the component should fail.
"""
- self.assertFalse(self.__shutdown)
+ self.assertFalse(self._shutdown)
self.assertTrue(self.__start_called)
self.assertFalse(self.__stop_called)
self.assertTrue(self.__failed_called)
@@ -192,7 +207,7 @@ class ComponentTests(unittest.TestCase):
self.assertRaises(ValueError, component.start)
# Stop it again and check
component.stop()
- self.assertFalse(self.__shutdown)
+ self.assertFalse(self._shutdown)
self.assertTrue(self.__start_called)
self.assertTrue(self.__stop_called)
self.assertFalse(self.__failed_called)
@@ -245,7 +260,7 @@ class ComponentTests(unittest.TestCase):
self.__check_startup(component)
component.start()
self.__check_started(component)
- self.__timeskip()
+ self._timeskip()
# Pretend the componend died some time later
component.failed()
# Check the component is still dead
@@ -278,7 +293,7 @@ class ComponentTests(unittest.TestCase):
self.__check_started(component)
# Make it fail later on
self.__start_called = False
- self.__timeskip()
+ self._timeskip()
component.failed()
self.__check_restarted(component)
@@ -308,7 +323,7 @@ class ComponentTests(unittest.TestCase):
self.__check_started(component)
# Make it fail later on
self.__start_called = False
- self.__timeskip()
+ self._timeskip()
component.failed()
self.__check_restarted(component)
@@ -369,6 +384,7 @@ class TestComponent(Component):
self.__owner = owner
self.__name = name
self.log('init')
+ self.log(kind)
def log(self, event):
"""
@@ -386,25 +402,66 @@ class TestComponent(Component):
def failed_internal(self):
self.log('failed')
-class ConfiguratorTest(unittest.TestCase):
+class FailComponent(Component):
+ """
+ A mock component that fails whenever it is started.
+ """
+ def start_internal(self):
+ raise TestError("test error")
+
+class ConfiguratorTest(BossUtils, unittest.TestCase):
"""
Tests for the configurator.
"""
def setUp(self):
"""
Insert the special evaluated test components we use and prepare the
- log.
+ log. Also provide some data for the tests and prepare us to pretend
+ we're boss.
"""
+ BossUtils.setUp(self)
# We put our functions inside instead of class constructors,
# so we can look into what is happening more easily
- self.__orig_specials = copy(specials)
+ self.__orig_specials = copy.copy(specials)
specials['test'] = self.__component_test
- log = []
+ self.log = []
+ # The core "hardcoded" configuration
+ self.__core = {
+ 'core1': {
+ 'priority': 5,
+ 'process': 'core1',
+ 'special': 'test',
+ 'kind': 'core'
+ },
+ 'core2': {
+ 'process': 'core2',
+ 'special': 'test',
+ 'kind': 'core'
+ },
+ 'core3': {
+ 'process': 'core3',
+ 'priority': 3,
+ 'special': 'test',
+ 'kind': 'core'
+ }
+ }
+ # How they should be started. The are created in the order they are
+ # found in the dict, but then they should be started by priority.
+ # This expects that the same dict returns its keys in the same order
+ # every time
+ self.__core_log_create = []
+ for core in self.__core.keys():
+ self.__core_log_create.append((core, 'init'))
+ self.__core_log_create.append((core, 'core'))
+ self.__core_log_start = [('core1', 'start'), ('core3', 'start'),
+ ('core2', 'start')]
+ self.__core_log = self.__core_log_create + self.__core_log_start
def tearDown(self):
"""
- Clean up the special evaluated test components.
+ Clean up the special evaluated test components and other stuff.
"""
+ BossUtils.tearDown(self)
specials = self.__orig_specials
def __component_test(self, process, boss, kind):
@@ -414,6 +471,94 @@ class ConfiguratorTest(unittest.TestCase):
self.assertEqual(self, boss)
return TestComponent(self, process, kind)
+ def test_init(self):
+ """
+ Tests the configurator can be created and it does not create
+ any components yet, nor does it remember anything.
+ """
+ configurator = Configurator(self)
+ self.assertEqual([], self.log)
+ self.assertEqual({}, configurator._components)
+ self.assertEqual({}, configurator._old_config)
+ self.assertFalse(configurator._running)
+
+ def test_run_plan(self):
+ """
+ Test the internal function of running plans. Just see it can handle
+ the commands in the given order. We see that by the log.
+
+ Also includes one that raises, so we see it just stops there.
+ """
+ # Prepare the configurator and the plan
+ configurator = Configurator(self)
+ started = self.__component_test('second', self, 'dispensable')
+ started.start()
+ stopped = self.__component_test('first', self, 'core')
+ configurator._components = {'second': started}
+ plan = [
+ {
+ 'component': stopped,
+ 'command': 'start',
+ 'name': 'first'
+ },
+ {
+ 'component': started,
+ 'command': 'stop',
+ 'name': 'second'
+ },
+ {
+ 'component': FailComponent('third', self, 'needed'),
+ 'command': 'start',
+ 'name': 'third'
+ },
+ {
+ 'component': self.__component_test('fourth', self, 'core'),
+ 'command': 'start',
+ 'name': 'fourth'
+ }
+ ]
+ # Don't include the preparation into the log
+ self.log = []
+ # The error from the third component is propagated
+ self.assertRaises(TestError, configurator._run_plan, plan)
+ # The first two were handled, the rest not, due to the exception
+ self.assertEqual([('first', 'start'), ('second', 'stop')], self.log)
+ self.assertEqual({'first': stopped}, configurator._components)
+
+ def test_build_plan(self):
+ """
+ Test building the plan correctly. Currently, we let it only create
+ the initial plan when the old configuration is empty, as we don't need
+ more for the starts.
+ """
+ configurator = Configurator(self)
+ plan = configurator._build_plan({}, self.__core)
+ # This should have created the components
+ self.assertEqual(self.__core_log_create, self.log)
+ self.assertEqual(3, len(plan))
+ for (task, name) in zip(plan, ['core1', 'core3', 'core2']):
+ self.assertTrue('component' in task)
+ self.assertEqual('start', task['command'])
+ self.assertEqual(name, task['name'])
+ # TODO: More scenarios for changes between configurations are needed
+
+ def test_startup(self):
+ """
+ Passes some configuration to the startup method and sees if
+ the components are started up.
+
+ It also checks the components are kept inside the configurator.
+ """
+ configurator = Configurator(self)
+ configurator.startup(self.__core)
+ self.assertEqual(self.__core_log, self.log)
+ for core in self.__core.keys():
+ self.assertTrue(core in configurator._components)
+ self.assertEqual(self.__core, configurator._old_config)
+ self.assertTrue(configurator._running)
+ # It can't be started twice
+ self.assertRaises(ValueError, configurator.startup, self.__core)
+
if __name__ == '__main__':
isc.log.init("bind10") # FIXME Should this be needed?
isc.log.resetUnitTestRootLogger()
More information about the bind10-changes
mailing list