Browse Source

[213] Component interface, start-stop tests

Michal 'vorner' Vaner 13 years ago
parent
commit
3a6f9f395c

+ 1 - 1
src/lib/python/isc/bind10/Makefile.am

@@ -1,4 +1,4 @@
 SUBDIRS = . tests
 
-python_PYTHON = __init__.py sockcreator.py
+python_PYTHON = __init__.py sockcreator.py component.py
 pythondir = $(pyexecdir)/isc/bind10

+ 111 - 0
src/lib/python/isc/bind10/component.py

@@ -0,0 +1,111 @@
+# Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from isc.log_messages.bind10_messages import *
+
+logger = isc.log.Logger("boss")
+
+"""
+Module for managing components (abstraction of process). It allows starting
+them in given order, handling when they crash (what happens depends on kind
+of component) and shutting down. It also handles the configuration of this.
+
+Dependencies between them are not yet handled. It might turn out they are
+needed, in that case they will be added sometime in future.
+"""
+
+class Component:
+    """
+    This represents a single component. It has some defaults of behaviour,
+    which should be reasonable for majority of ordinary components, but
+    it might be inherited and modified for special-purpose components,
+    like the core modules with different ways of starting up.
+    """
+    def __init__(self, process, boss, kind):
+        """
+        Creates the component in not running mode.
+
+        The parameters are:
+        - `process` is the name of the process to start.
+        - `boss` the boss object to plug into. The component needs to plug
+          into it to know when it failed, etc.
+        - `kind` is the kind of component. It may be one of:
+          * 'core' means the system can't run without it and it can't be
+            safely restarted. If it does not start, the system is brought
+            down. If it crashes, the system is turned off as well (with
+            non-zero exit status).
+          * 'needed' means the system is able to restart the component,
+            but it is vital part of the service (like auth server). If
+            it fails to start or crashes in less than 10s after the first
+            startup, the system is brought down. If it crashes later on,
+            it is restarted.
+          * 'dispensable' means the component should be running, but if it
+            doesn't start or crashes for some reason, the system simply tries
+            to restart it and keeps running.
+        """
+        pass
+    def start(self):
+        """
+        Start the component for the first time or restart it. If you need to
+        modify the way a component is started, do not replace this method,
+        but start_internal. This one does some more bookkeeping around.
+
+        If you try to start an already running component, it raises ValueError.
+        """
+        pass
+    def start_internal(self):
+        """
+        This method does the actual starting of a process. If you need to
+        change the way the component is started, replace this method.
+        """
+        pass
+    def stop(self):
+        """
+        Stop the component. If you need to modify the way a component is
+        stopped, do not replace this method, but stop_internal. This one
+        does some more bookkeeping.
+
+        If you try to stop a component that is not running, it raises
+        ValueError.
+        """
+        pass
+    def stop_internal(self):
+        """
+        This is the method that does the actual stopping of a component.
+        You can replace this method if you want a different way to do it.
+        """
+        pass
+    def failed(self):
+        """
+        Notify the component it crashed. This will be called from boss object.
+
+        If you try to call failed on a component that is not running,
+        a ValueError is raised.
+        """
+        pass
+    def failed_internal(self):
+        """
+        This method is called from failed. You can replace it if you need
+        some specific behaviour when the component crashes. The default
+        implementation is empty.
+        """
+        pass
+    def running(self):
+        """
+        Informs if the component is currently running. It assumes the failed
+        is called whenever the component really fails and there might be some
+        time in between actual failure and the call.
+        """
+        pass

+ 1 - 1
src/lib/python/isc/bind10/tests/Makefile.am

@@ -1,7 +1,7 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 #PYTESTS = args_test.py bind10_test.py
 # NOTE: this has a generated test found in the builddir
-PYTESTS = sockcreator_test.py
+PYTESTS = sockcreator_test.py component_test.py
 
 EXTRA_DIST = $(PYTESTS)
 

+ 137 - 0
src/lib/python/isc/bind10/tests/component_test.py

@@ -0,0 +1,137 @@
+# Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+Tests for the bind10.component module
+"""
+
+import unittest
+import isc.log
+from isc.bind10.component import Component
+
+class ComponentTests(unittest.TestCase):
+    """
+    Tests for the bind10.component.Component class
+    """
+    def setUp(self):
+        """
+        Pretend a newly started system
+        """
+        self.__shutdown = False
+        self.__exitcode = None
+        self.__start_called = False
+        self.__stop_called = False
+        self.__failed_called = False
+
+    def shutdown(self, exitcode=0):
+        """
+        Mock function to shut down. We just note we were asked to do so.
+        """
+        self.__shutdown = True
+        self.__exitcode = None
+
+    def start(self):
+        """
+        Mock function, installed into the component into start_internal.
+        This only notes the component was "started".
+        """
+        self.__start_called = True
+
+    def stop(self):
+        """
+        Mock function, installed into the component into stop_internal.
+        This only notes the component was "stopped".
+        """
+        self.__stop_called = True
+
+    def fail(self):
+        """
+        Mock function, installed into the component into failed_internal.
+        This only notes the component called the method.
+        """
+        self.__failed_called = True
+
+    def create_component(self, kind):
+        """
+        Convenience function that creates a component of given kind
+        and installs the mock functions into it so we can hook up into
+        its behaviour.
+
+        The process used is some nonsense, as this isn't used in this
+        kind of tests and we pretend to be the boss.
+        """
+        component = Component('No process', self, kind)
+        component.start_internal = self.start
+        component.stop_internal = self.stop
+        component.failed_internal = self.fail
+        return component
+
+    def do_start_stop(self, kind):
+        """
+        This is a body of a test. It creates a componend of given kind,
+        then starts it and stops it. It checks correct functions are called
+        and the component's status is correct.
+
+        It also checks the component can't be started/stopped twice.
+        """
+        # Create it and check it did not do any funny stuff yet
+        component = self.create_component(kind)
+        self.assertFalse(self.__shutdown)
+        self.assertFalse(self.__start_called)
+        self.assertFalse(self.__stop_called)
+        self.assertFalse(self.__failed_called)
+        self.assertFalse(component.running())
+        # Start it and check it called the correct starting functions
+        component.start()
+        self.assertFalse(self.__shutdown)
+        self.assertTrue(self.__start_called)
+        self.assertFalse(self.__stop_called)
+        self.assertFalse(self.__failed_called)
+        self.assertTrue(component.running())
+        # Check it can't be started twice
+        self.assertRaises(ValueError, component.start)
+        # Stop it again and check
+        component.stop()
+        self.assertFalse(self.__shutdown)
+        self.assertTrue(self.__start_called)
+        self.assertTrue(self.__stop_called)
+        self.assertFalse(self.__failed_called)
+        self.assertFalse(component.running())
+        # Check it can't be stopped twice
+        self.assertRaises(ValueError, component.stop)
+        # But it can be started again if it is stopped
+        # (no more checking here, just it doesn't crash)
+        component.start()
+
+    def test_start_stop_core(self):
+        """
+        A start-stop test for core component. See do_start_stop.
+        """
+        self.do_start_stop('core')
+    def test_start_stop_needed(self):
+        """
+        A start-stop test for needed component. See do_start_stop.
+        """
+        self.do_start_stop('needed')
+    def test_start_stop_dispensable(self):
+        """
+        A start-stop test for dispensable component. See do_start_stop.
+        """
+        self.do_start_stop('dispensable')
+
+if __name__ == '__main__':
+    isc.log.init("bind10") # FIXME Should this be needed?
+    isc.log.resetUnitTestRootLogger()
+    unittest.main()