Browse Source

Merge branch 'work/configuration'

Michal 'vorner' Vaner 14 years ago
parent
commit
5514dd78f2

+ 1 - 0
configure.ac

@@ -663,6 +663,7 @@ AC_CONFIG_FILES([Makefile
                  src/lib/python/isc/net/tests/Makefile
                  src/lib/python/isc/notify/Makefile
                  src/lib/python/isc/notify/tests/Makefile
+                 src/lib/python/isc/testutils/Makefile
                  src/lib/config/Makefile
                  src/lib/config/tests/Makefile
                  src/lib/config/tests/testdata/Makefile

+ 74 - 28
src/bin/bind10/bind10.py.in

@@ -194,14 +194,21 @@ class CChannelConnectError(Exception): pass
 class BoB:
     """Boss of BIND class."""
     
-    def __init__(self, msgq_socket_file=None, nocache=False, verbose=False,
-    setuid=None, username=None):
+    def __init__(self, msgq_socket_file=None, data_path=None,
+    config_filename=None, nocache=False, verbose=False, setuid=None,
+    username=None, cmdctl_port=None):
         """
             Initialize the Boss of BIND. This is a singleton (only one can run).
         
             The msgq_socket_file specifies the UNIX domain socket file that the
             msgq process listens on.  If verbose is True, then the boss reports
             what it is doing.
+
+            Data path and config filename are passed trough to config manager
+            (if provided) and specify the config file to be used.
+
+            The cmdctl_port is passed to cmdctl and specify on which port it
+            should listen.
         """
         self.cc_session = None
         self.ccs = None
@@ -219,6 +226,9 @@ class BoB:
         self.uid = setuid
         self.username = username
         self.verbose = verbose
+        self.data_path = data_path
+        self.config_filename = config_filename
+        self.cmdctl_port = cmdctl_port
 
     def config_handler(self, new_config):
         # If this is initial update, don't do anything now, leave it to startup
@@ -390,7 +400,12 @@ class BoB:
             Starts the configuration manager process
         """
         self.log_starting("b10-cfgmgr")
-        bind_cfgd = ProcessInfo("b10-cfgmgr", ["b10-cfgmgr"],
+        args = ["b10-cfgmgr"]
+        if self.data_path is not None:
+            args.append("--data-path=" + self.data_path)
+        if self.config_filename is not None:
+            args.append("--config-filename=" + self.config_filename)
+        bind_cfgd = ProcessInfo("b10-cfgmgr", args,
                                 c_channel_env, uid=self.uid,
                                 username=self.username)
         self.processes[bind_cfgd.pid] = bind_cfgd
@@ -500,8 +515,13 @@ class BoB:
         self.start_simple("b10-stats", c_channel_env)
 
     def start_cmdctl(self, c_channel_env):
-        # XXX: we hardcode port 8080
-        self.start_simple("b10-cmdctl", c_channel_env, 8080)
+        """
+            Starts the command control process
+        """
+        args = ["b10-cmdctl"]
+        if self.cmdctl_port is not None:
+            args.append("--port=" + str(self.cmdctl_port))
+        self.start_process("b10-cmdctl", args, c_channel_env, self.cmdctl_port)
 
     def start_all_processes(self):
         """
@@ -785,6 +805,50 @@ def process_rename(option, opt_str, value, parser):
     """Function that renames the process if it is requested by a option."""
     isc.util.process.rename(value)
 
+def parse_args(args=sys.argv[1:], Parser=OptionParser):
+    """
+    Function for parsing command line arguments. Returns the
+    options object from OptionParser.
+    """
+    parser = Parser(version=VERSION)
+    parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
+                      type="string", default=None,
+                      help="UNIX domain socket file the b10-msgq daemon will use")
+    parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
+                      default=False, help="disable hot-spot cache in authoritative DNS server")
+    parser.add_option("-u", "--user", dest="user", type="string", default=None,
+                      help="Change user after startup (must run as root)")
+    parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
+                      help="display more about what is going on")
+    parser.add_option("--pretty-name", type="string", action="callback",
+                      callback=process_rename,
+                      help="Set the process name (displayed in ps, top, ...)")
+    parser.add_option("-c", "--config-file", action="store",
+                      dest="config_file", default=None,
+                      help="Configuration database filename")
+    parser.add_option("-p", "--data-path", dest="data_path",
+                      help="Directory to search for configuration files",
+                      default=None)
+    parser.add_option("--cmdctl-port", dest="cmdctl_port", type="int",
+                      default=None, help="Port of command control")
+    parser.add_option("--pid-file", dest="pid_file", type="string",
+                      default=None,
+                      help="file to dump the PID of the BIND 10 process")
+
+    (options, args) = parser.parse_args(args)
+
+    if options.cmdctl_port is not None:
+        try:
+            isc.net.parse.port_parse(options.cmdctl_port)
+        except ValueError as e:
+            parser.error(e)
+
+    if args:
+        parser.print_help()
+        sys.exit(1)
+
+    return options
+
 def dump_pid(pid_file):
     """
     Dump the PID of the current process to the specified file.  If the given
@@ -814,33 +878,14 @@ def unlink_pid_file(pid_file):
         if error.errno is not errno.ENOENT:
             raise
 
+
 def main():
     global options
     global boss_of_bind
     # Enforce line buffering on stdout, even when not a TTY
     sys.stdout = io.TextIOWrapper(sys.stdout.detach(), line_buffering=True)
 
-    # Parse any command-line options.
-    parser = OptionParser(version=VERSION)
-    parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
-                      type="string", default=None,
-                      help="UNIX domain socket file the b10-msgq daemon will use")
-    parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
-                      default=False, help="disable hot-spot cache in authoritative DNS server")
-    parser.add_option("-u", "--user", dest="user", type="string", default=None,
-                      help="Change user after startup (must run as root)")
-    parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
-                      help="display more about what is going on")
-    parser.add_option("--pretty-name", type="string", action="callback",
-                      callback=process_rename,
-                      help="Set the process name (displayed in ps, top, ...)")
-    parser.add_option("--pid-file", dest="pid_file", type="string",
-                      default=None,
-                      help="file to dump the PID of the BIND 10 process")
-    (options, args) = parser.parse_args()
-    if args:
-        parser.print_help()
-        sys.exit(1)
+    options = parse_args()
 
     # Check user ID.
     setuid = None
@@ -890,8 +935,9 @@ def main():
     signal.signal(signal.SIGPIPE, signal.SIG_IGN)
 
     # Go bob!
-    boss_of_bind = BoB(options.msgq_socket_file, options.nocache,
-                       options.verbose, setuid, username)
+    boss_of_bind = BoB(options.msgq_socket_file, options.data_path,
+                       options.config_file, options.nocache, options.verbose,
+                       setuid, username, options.cmdctl_port)
     startup_result = boss_of_bind.startup()
     if startup_result:
         sys.stderr.write("[bind10] Error on startup: %s\n" % startup_result)

+ 27 - 0
src/bin/bind10/bind10.xml

@@ -48,6 +48,8 @@
       <arg><option>-n</option></arg>
       <arg><option>-u <replaceable>user</replaceable></option></arg>
       <arg><option>-v</option></arg>
+      <arg><option>-c<replaceable>config-filename</replaceable></option></arg>
+      <arg><option>-p<replaceable>data_path</replaceable></option></arg>
       <arg><option>--msgq-socket-file <replaceable>file</replaceable></option></arg>
       <arg><option>--no-cache</option></arg>
       <arg><option>--user <replaceable>user</replaceable></option></arg>
@@ -80,6 +82,31 @@
     <para>The arguments are as follows:</para>
 
     <variablelist>
+      <varlistentry>
+        <term>
+          <option>-c</option><replaceable>config-filename</replaceable>,
+          <option>--config-file</option> <replaceable>config-filename</replaceable>
+        </term>
+        <listitem>
+          <para>The configuration filename to use. Can be either absolute or
+          relative to data path. In case it is absolute, value of data path is
+          not considered.</para>
+          <para>Defaults to b10-config.db.</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          <option>-p</option><replaceable>data-path</replaceable>,
+          <option>--data-path</option> <replaceable>data-path</replaceable>
+        </term>
+        <listitem>
+          <para>The path where BIND 10 programs look for various data files.
+          Currently only b10-cfgmgr uses it to locate the configuration file,
+          but the usage might be extended for other programs and other types
+          of files.</para>
+        </listitem>
+      </varlistentry>
 
       <varlistentry>
         <term><option>-m</option> <replaceable>file</replaceable>,

+ 53 - 1
src/bin/bind10/tests/bind10_test.py.in

@@ -1,4 +1,4 @@
-from bind10 import ProcessInfo, BoB, dump_pid, unlink_pid_file
+from bind10 import ProcessInfo, BoB, parse_args, dump_pid, unlink_pid_file
 
 # XXX: environment tests are currently disabled, due to the preprocessor
 #      setup that we have now complicating the environment
@@ -9,6 +9,7 @@ import os
 import signal
 import socket
 from isc.net.addr import IPAddr
+from isc.testutils.parse_args import TestOptParser, OptsError
 
 class TestProcessInfo(unittest.TestCase):
     def setUp(self):
@@ -412,6 +413,57 @@ class TestStartStopProcessesBob(unittest.TestCase):
 
         bob.config_handler({'start_auth': True, 'start_resolver': True})
 
+class TestParseArgs(unittest.TestCase):
+    """
+    This tests parsing of arguments of the bind10 master process.
+    """
+    #TODO: Write tests for the original parsing, bad options, etc.
+    def test_no_opts(self):
+        """
+        Test correct default values when no options are passed.
+        """
+        options = parse_args([], TestOptParser)
+        self.assertEqual(None, options.data_path)
+        self.assertEqual(None, options.config_file)
+        self.assertEqual(None, options.cmdctl_port)
+
+    def test_data_path(self):
+        """
+        Test it can parse the data path.
+        """
+        self.assertRaises(OptsError, parse_args, ['-p'], TestOptParser)
+        self.assertRaises(OptsError, parse_args, ['--data-path'],
+                          TestOptParser)
+        options = parse_args(['-p', '/data/path'], TestOptParser)
+        self.assertEqual('/data/path', options.data_path)
+        options = parse_args(['--data-path=/data/path'], TestOptParser)
+        self.assertEqual('/data/path', options.data_path)
+
+    def test_config_filename(self):
+        """
+        Test it can parse the config switch.
+        """
+        self.assertRaises(OptsError, parse_args, ['-c'], TestOptParser)
+        self.assertRaises(OptsError, parse_args, ['--config-file'],
+                          TestOptParser)
+        options = parse_args(['-c', 'config-file'], TestOptParser)
+        self.assertEqual('config-file', options.config_file)
+        options = parse_args(['--config-file=config-file'], TestOptParser)
+        self.assertEqual('config-file', options.config_file)
+
+    def test_cmdctl_port(self):
+        """
+        Test it can parse the command control port.
+        """
+        self.assertRaises(OptsError, parse_args, ['--cmdctl-port=abc'],
+                                                TestOptParser)
+        self.assertRaises(OptsError, parse_args, ['--cmdctl-port=100000000'],
+                                                TestOptParser)
+        self.assertRaises(OptsError, parse_args, ['--cmdctl-port'],
+                          TestOptParser)
+        options = parse_args(['--cmdctl-port=1234'], TestOptParser)
+        self.assertEqual(1234, options.cmdctl_port)
+
 class TestPIDFile(unittest.TestCase):
     def setUp(self):
         self.pid_file = '@builddir@' + os.sep + 'bind10.pid'

+ 3 - 17
src/bin/bindctl/tests/bindctl_test.py

@@ -26,6 +26,7 @@ import getpass
 from optparse import OptionParser
 from isc.config.config_data import ConfigData, MultiConfigData
 from isc.config.module_spec import ModuleSpec
+from isc.testutils.parse_args import TestOptParser, OptsError
 from bindctl_main import set_bindctl_options
 from bindctl import cmdparse
 from bindctl import bindcmd
@@ -452,23 +453,8 @@ class TestBindCmdInterpreter(unittest.TestCase):
 
 
 class TestCommandLineOptions(unittest.TestCase):
-    class FakeParserError(Exception):
-        """An exception thrown from FakeOptionParser on parser error.
-        """
-        pass
-
-    class FakeOptionParser(OptionParser):
-        """This fake class emulates the OptionParser class with customized
-        error handling for the convenient of tests.
-        """
-        def __init__(self):
-            OptionParser.__init__(self)
-
-        def error(self, msg):
-            raise TestCommandLineOptions.FakeParserError
-
     def setUp(self):
-        self.parser = self.FakeOptionParser()
+        self.parser = TestOptParser()
         set_bindctl_options(self.parser)
 
     def test_csv_file_dir(self):
@@ -481,7 +467,7 @@ class TestCommandLineOptions(unittest.TestCase):
         self.assertEqual('some_dir', options.csv_file_dir)
 
         # missing option arg; should trigger parser error.
-        self.assertRaises(self.FakeParserError, self.parser.parse_args,
+        self.assertRaises(OptsError, self.parser.parse_args,
                           ['--csv-file-dir'])
 
 if __name__== "__main__":

+ 18 - 1
src/bin/cfgmgr/b10-cfgmgr.py.in

@@ -22,6 +22,7 @@ from isc.cc import SessionError
 import isc.util.process
 import signal
 import os
+from optparse import OptionParser
 
 isc.util.process.rename()
 
@@ -41,18 +42,34 @@ if "B10_FROM_SOURCE" in os.environ:
 else:
     PREFIX = "@prefix@"
     DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)
+DEFAULT_CONFIG_FILE = "b10-config.db"
 
 cm = None
 
+def parse_options(args=sys.argv[1:], Parser=OptionParser):
+    parser = Parser()
+    parser.add_option("-p", "--data-path", dest="data_path",
+                      help="Directory to search for configuration files " +
+                      "(default=" + DATA_PATH + ")", default=DATA_PATH)
+    parser.add_option("-c", "--config-filename", dest="config_file",
+                      help="Configuration database filename " +
+                      "(default=" + DEFAULT_CONFIG_FILE + ")",
+                      default=DEFAULT_CONFIG_FILE)
+    (options, args) = parser.parse_args(args)
+    if args:
+        parser.error("No non-option arguments allowed")
+    return options
+
 def signal_handler(signal, frame):
     global cm
     if cm:
         cm.running = False
 
 def main():
+    options = parse_options()
     global cm
     try:
-        cm = ConfigManager(DATA_PATH)
+        cm = ConfigManager(options.data_path, options.config_file)
         signal.signal(signal.SIGINT, signal_handler)
         signal.signal(signal.SIGTERM, signal_handler)
         cm.read_config()

+ 28 - 17
src/bin/cfgmgr/b10-cfgmgr.xml

@@ -41,16 +41,13 @@
     </copyright>
   </docinfo>
 
-<!--
   <refsynopsisdiv>
     <cmdsynopsis>
-      <command></command>
-      <arg><option></option></arg>
-      <arg choice="opt"></arg>
-      <arg choice="opt"></arg>
+      <command>b10-cfgmgr</command>
+      <arg><option>-c<replaceable>config-filename</replaceable></option></arg>
+      <arg><option>-p<replaceable>data_path</replaceable></option></arg>
     </cmdsynopsis>
   </refsynopsisdiv>
--->
 
   <refsect1>
     <title>DESCRIPTION</title>
@@ -93,24 +90,38 @@
     </para>
   </refsect1>
 
-<!--
   <refsect1>
     <title>ARGUMENTS</title>
-    <para>
-      <orderedlist numeration="loweralpha">
+
+    <para>The arguments are as follows:</para>
+
+    <variablelist>
+      <varlistentry>
+        <term>
+          <option>-c</option><replaceable>config-filename</replaceable>,
+          <option>--config-filename</option> <replaceable>config-filename</replaceable>
+        </term>
         <listitem>
-          <para>
-          </para>
+          <para>The configuration database filename to use. Can be either
+          absolute or relative to data path.</para>
+          <para>Defaults to b10-config.db</para>
         </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          <option>-p</option><replaceable>data-path</replaceable>,
+          <option>--data-path</option> <replaceable>data-path</replaceable>
+        </term>
         <listitem>
-          <para>
-          </para>
+          <para>The path where BIND 10 looks for files. The
+          configuration file is looked for here, if it is relative. If it is
+          absolute, the path is ignored.</para>
         </listitem>
-      </orderedlist>
-    </para>
-
+      </varlistentry>
+    </variablelist>
   </refsect1>
--->
+
   <refsect1>
     <title>FILES</title>
 <!-- TODO: fix path -->

+ 65 - 1
src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in

@@ -20,9 +20,10 @@
 import unittest
 import os
 import sys
+from isc.testutils.parse_args import OptsError, TestOptParser
 
 class MyConfigManager:
-    def __init__(self, path):
+    def __init__(self, path, filename):
         self._path = path
         self.read_config_called = False
         self.notify_boss_called = False
@@ -88,6 +89,69 @@ class TestConfigManagerStartup(unittest.TestCase):
 
         sys.modules.pop("b10-cfgmgr")
 
+class TestParseArgs(unittest.TestCase):
+    """
+    Test for the parsing of command line arguments. We provide a different
+    array to parse instead.
+    """
+
+    def test_defaults(self):
+        """
+        Test the default values when no options are provided.
+        """
+        # Pass it empty array, not our arguments
+        b = __import__("b10-cfgmgr")
+        parsed = b.parse_options([], TestOptParser)
+        self.assertEqual(b.DATA_PATH, parsed.data_path)
+        self.assertEqual(b.DEFAULT_CONFIG_FILE, parsed.config_file)
+
+    def test_wrong_args(self):
+        """
+        Test it fails when we pass invalid option.
+        """
+        b = __import__("b10-cfgmgr")
+        self.assertRaises(OptsError, b.parse_options, ['--wrong-option'],
+                          TestOptParser)
+
+    def test_not_arg(self):
+        """
+        Test it fails when there's an argument that's not option
+        (eg. without -- at the beginning).
+        """
+        b = __import__("b10-cfgmgr")
+        self.assertRaises(OptsError, b.parse_options, ['not-option'],
+                          TestOptParser)
+
+    def test_datapath(self):
+        """
+        Test overwriting the data path.
+        """
+        b = __import__("b10-cfgmgr")
+        parsed = b.parse_options(['--data-path=/path'], TestOptParser)
+        self.assertEqual('/path', parsed.data_path)
+        self.assertEqual(b.DEFAULT_CONFIG_FILE, parsed.config_file)
+        parsed = b.parse_options(['-p', '/path'], TestOptParser)
+        self.assertEqual('/path', parsed.data_path)
+        self.assertEqual(b.DEFAULT_CONFIG_FILE, parsed.config_file)
+        self.assertRaises(OptsError, b.parse_options, ['-p'], TestOptParser)
+        self.assertRaises(OptsError, b.parse_options, ['--data-path'],
+                          TestOptParser)
+
+    def test_db_filename(self):
+        """
+        Test setting the configuration database file.
+        """
+        b = __import__("b10-cfgmgr")
+        parsed = b.parse_options(['--config-filename=filename'],
+                                 TestOptParser)
+        self.assertEqual(b.DATA_PATH, parsed.data_path)
+        self.assertEqual("filename", parsed.config_file)
+        parsed = b.parse_options(['-c', 'filename'], TestOptParser)
+        self.assertEqual(b.DATA_PATH, parsed.data_path)
+        self.assertEqual("filename", parsed.config_file)
+        self.assertRaises(OptsError, b.parse_options, ['-c'], TestOptParser)
+        self.assertRaises(OptsError, b.parse_options, ['--config-filename'],
+                          TestOptParser)
 
 if __name__ == '__main__':
     unittest.main()

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

@@ -1,4 +1,4 @@
-SUBDIRS = datasrc cc config log net notify util 
+SUBDIRS = datasrc cc config log net notify util testutils
 
 python_PYTHON = __init__.py
 

+ 40 - 24
src/lib/python/isc/config/cfgmgr.py

@@ -44,25 +44,36 @@ class ConfigManagerData:
     """This class hold the actual configuration information, and
        reads it from and writes it to persistent storage"""
 
-    def __init__(self, data_path, file_name = "b10-config.db"):
+    def __init__(self, data_path, file_name):
         """Initialize the data for the configuration manager, and
            set the version and path for the data store. Initializing
            this does not yet read the database, a call to
-           read_from_file is needed for that."""
+           read_from_file is needed for that.
+
+           In case the file_name is absolute, data_path is ignored
+           and the directory where the file_name lives is used instead.
+           """
         self.data = {}
         self.data['version'] = config_data.BIND10_CONFIG_DATA_VERSION
-        self.data_path = data_path
-        self.db_filename = data_path + os.sep + file_name
+        if os.path.isabs(file_name):
+            self.db_filename = file_name
+            self.data_path = os.path.dirname(file_name)
+        else:
+            self.db_filename = data_path + os.sep + file_name
+            self.data_path = data_path
+
+    def read_from_file(data_path, file_name):
+        """Read the current configuration found in the file file_name.
+           If file_name is absolute, data_path is ignored. Otherwise
+           we look for the file_name in data_path directory.
 
-    def read_from_file(data_path, file_name = "b10-config.db"):
-        """Read the current configuration found in the file at
-           data_path. If the file does not exist, a
-           ConfigManagerDataEmpty exception is raised. If there is a
-           parse error, or if the data in the file has the wrong
-           version, a ConfigManagerDataReadError is raised. In the first
-           case, it is probably safe to log and ignore. In the case of
-           the second exception, the best way is probably to report the
-           error and stop loading the system."""
+           If the file does not exist, a ConfigManagerDataEmpty exception is
+           raised. If there is a parse error, or if the data in the file has
+           the wrong version, a ConfigManagerDataReadError is raised. In the
+           first case, it is probably safe to log and ignore. In the case of
+           the second exception, the best way is probably to report the error
+           and stop loading the system.
+           """
         config = ConfigManagerData(data_path, file_name)
         file = None
         try:
@@ -142,20 +153,24 @@ class ConfigManagerData:
 
 class ConfigManager:
     """Creates a configuration manager. The data_path is the path
-       to the directory containing the b10-config.db file.
+       to the directory containing the configuraton file,
+       database_filename points to the configuration file.
        If session is set, this will be used as the communication
        channel session. If not, a new session will be created.
        The ability to specify a custom session is for testing purposes
        and should not be needed for normal usage."""
-    def __init__(self, data_path, session = None):
+    def __init__(self, data_path, database_filename, session=None):
         """Initialize the configuration manager. The data_path string
            is the path to the directory where the configuration is
-           stored (in <data_path>/b10-config.db). Session is an optional
+           stored (in <data_path>/<database_filename> or in
+           <database_filename>, if it is absolute). The dabase_filename
+           is the config file to load. Session is an optional
            cc-channel session. If this is not given, a new one is
-           created"""
+           created."""
         self.data_path = data_path
+        self.database_filename = database_filename
         self.module_specs = {}
-        self.config = ConfigManagerData(data_path)
+        self.config = ConfigManagerData(data_path, database_filename)
         if session:
             self.cc = session
         else:
@@ -223,17 +238,18 @@ class ConfigManager:
         return commands
 
     def read_config(self):
-        """Read the current configuration from the b10-config.db file
-           at the path specificied at init()"""
+        """Read the current configuration from the file specificied at init()"""
         try:
-            self.config = ConfigManagerData.read_from_file(self.data_path)
+            self.config = ConfigManagerData.read_from_file(self.data_path,
+                                                           self.\
+                                                           database_filename)
         except ConfigManagerDataEmpty:
             # ok, just start with an empty config
-            self.config = ConfigManagerData(self.data_path)
+            self.config = ConfigManagerData(self.data_path,
+                                            self.database_filename)
         
     def write_config(self):
-        """Write the current configuration to the b10-config.db file
-           at the path specificied at init()"""
+        """Write the current configuration to the file specificied at init()"""
         self.config.write_to_file()
 
     def _handle_get_module_spec(self, cmd):

+ 33 - 7
src/lib/python/isc/config/tests/cfgmgr_test.py

@@ -27,9 +27,20 @@ class TestConfigManagerData(unittest.TestCase):
     def setUp(self):
         self.data_path = os.environ['CONFIG_TESTDATA_PATH']
         self.writable_data_path = os.environ['CONFIG_WR_TESTDATA_PATH']
-        self.config_manager_data = ConfigManagerData(self.writable_data_path)
+        self.config_manager_data = ConfigManagerData(self.writable_data_path,
+                                                     file_name="b10-config.db")
         self.assert_(self.config_manager_data)
 
+    def test_abs_file(self):
+        """
+        Test what happens if we give the config manager an absolute path.
+        It shouldn't append the data path to it.
+        """
+        abs_path = self.data_path + os.sep + "b10-config-imaginary.db"
+        data = ConfigManagerData(os.getcwd(), abs_path)
+        self.assertEqual(abs_path, data.db_filename)
+        self.assertEqual(self.data_path, data.data_path)
+
     def test_init(self):
         self.assertEqual(self.config_manager_data.data['version'],
                          config_data.BIND10_CONFIG_DATA_VERSION)
@@ -39,10 +50,10 @@ class TestConfigManagerData(unittest.TestCase):
                          self.writable_data_path + os.sep + "b10-config.db")
 
     def test_read_from_file(self):
-        ConfigManagerData.read_from_file(self.writable_data_path)
+        ConfigManagerData.read_from_file(self.writable_data_path, "b10-config.db")
         self.assertRaises(ConfigManagerDataEmpty,
                           ConfigManagerData.read_from_file,
-                          "doesnotexist")
+                          "doesnotexist", "b10-config.db")
         self.assertRaises(ConfigManagerDataReadError,
                           ConfigManagerData.read_from_file,
                           self.data_path, "b10-config-bad1.db")
@@ -68,8 +79,8 @@ class TestConfigManagerData(unittest.TestCase):
         # by equality of the .data element. If data_path or db_filename
         # are different, but the contents are the same, it's still
         # considered equal
-        cfd1 = ConfigManagerData(self.data_path)
-        cfd2 = ConfigManagerData(self.data_path)
+        cfd1 = ConfigManagerData(self.data_path, file_name="b10-config.db")
+        cfd2 = ConfigManagerData(self.data_path, file_name="b10-config.db")
         self.assertEqual(cfd1, cfd2)
         cfd2.data_path = "some/unknown/path"
         self.assertEqual(cfd1, cfd2)
@@ -85,10 +96,25 @@ class TestConfigManager(unittest.TestCase):
         self.data_path = os.environ['CONFIG_TESTDATA_PATH']
         self.writable_data_path = os.environ['CONFIG_WR_TESTDATA_PATH']
         self.fake_session = FakeModuleCCSession()
-        self.cm = ConfigManager(self.writable_data_path, self.fake_session)
+        self.cm = ConfigManager(self.writable_data_path,
+                                database_filename="b10-config.db",
+                                session=self.fake_session)
         self.name = "TestModule"
         self.spec = isc.config.module_spec_from_file(self.data_path + os.sep + "/spec2.spec")
-    
+
+    def test_paths(self):
+        """
+        Test data_path and database filename is passed trough to
+        underlying ConfigManagerData.
+        """
+        cm = ConfigManager("datapath", "filename", self.fake_session)
+        self.assertEqual("datapath" + os.sep + "filename",
+                         cm.config.db_filename)
+        # It should preserve it while reading
+        cm.read_config()
+        self.assertEqual("datapath" + os.sep + "filename",
+                         cm.config.db_filename)
+
     def test_init(self):
         self.assert_(self.cm.module_specs == {})
         self.assert_(self.cm.data_path == self.writable_data_path)

+ 1 - 0
src/lib/python/isc/testutils/Makefile.am

@@ -0,0 +1 @@
+EXTRA_DIST = __init__.py parse_args.py

+ 3 - 0
src/lib/python/isc/testutils/README

@@ -0,0 +1,3 @@
+This contains some shared test code for other modules and python processes.
+That's why it doesn't have its own test subdirectory and why it isn't
+installed.

+ 17 - 0
src/lib/python/isc/testutils/__init__.py

@@ -0,0 +1,17 @@
+# Copyright (C) 2011  Internet Systems Consortium.
+#
+# 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.
+
+# Nothing here, really, it's just to tell python this directory is in
+# module hierarchy

+ 30 - 0
src/lib/python/isc/testutils/parse_args.py

@@ -0,0 +1,30 @@
+# Copyright (C) 2011  Internet Systems Consortium.
+#
+# 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 optparse import OptionParser
+
+class OptsError(Exception):
+    """To know when OptionParser would exit"""
+    pass
+
+class TestOptParser(OptionParser):
+    """
+    We define our own option parser to push into the parsing routine.
+    This one does not exit the whole application on error, it just raises
+    exception. It doesn't change anything else. The application uses the
+    stock one.
+    """
+    def error(self, message):
+        raise OptsError(message)