BIND 10 bind10-1.0.0-rc-release, updated. fe5478b124614d5d6b6abc8bb4815288e53fc9c7 [bind10-1.0.0-rc-release][2145-revert] Revert "[master] Merge branch 'trac2145'"
BIND 10 source code commits
bind10-changes at lists.isc.org
Thu Feb 14 21:45:29 UTC 2013
The branch, bind10-1.0.0-rc-release has been updated
via fe5478b124614d5d6b6abc8bb4815288e53fc9c7 (commit)
from 62cb314a492feb5f367953dce2f39326858abc73 (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 fe5478b124614d5d6b6abc8bb4815288e53fc9c7
Author: JINMEI Tatuya <jinmei at isc.org>
Date: Thu Feb 14 10:52:47 2013 -0800
[bind10-1.0.0-rc-release][2145-revert] Revert "[master] Merge branch 'trac2145'"
This reverts commit 530f569e47b06f49402611bda07e1956cdf04a24, reversing
changes made to da67c0642c9403f08e278e2424bc7bfde74e034a.
and fix conflict
-----------------------------------------------------------------------
Summary of changes:
configure.ac | 2 +-
src/bin/bind10/bind10.in | 2 --
src/bin/bind10/init.py.in | 1 -
src/bin/bind10/tests/Makefile.am | 2 +-
.../tests/{init_test.py.in => bind10_test.py.in} | 2 +-
src/bin/bindctl/bindcmd.py | 2 +-
src/bin/bindctl/run_bindctl.sh.in | 2 +-
src/bin/stats/stats.py.in | 3 +--
src/bin/sysinfo/run_sysinfo.sh.in | 14 +++++++++++++-
src/lib/python/isc/__init__.py | 10 +++++++---
.../python/isc/datasrc/tests/zone_loader_test.py | 1 -
11 files changed, 26 insertions(+), 15 deletions(-)
rename src/bin/bind10/tests/{init_test.py.in => bind10_test.py.in} (99%)
-----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index f843132..5683b65 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1342,7 +1342,7 @@ AC_OUTPUT([doc/version.ent
src/bin/stats/stats_httpd.py
src/bin/bind10/init.py
src/bin/bind10/run_bind10.sh
- src/bin/bind10/tests/init_test.py
+ src/bin/bind10/tests/bind10_test.py
src/bin/bindctl/run_bindctl.sh
src/bin/bindctl/bindctl_main.py
src/bin/bindctl/tests/bindctl_test
diff --git a/src/bin/bind10/bind10.in b/src/bin/bind10/bind10.in
index 88c45c9..6421bab 100755
--- a/src/bin/bind10/bind10.in
+++ b/src/bin/bind10/bind10.in
@@ -1,7 +1,5 @@
#!/bin/sh
-# We use this wrapper script both for production and in-source tests; in
-# the latter case B10_FROM_BUILD environment is expected to be defined.
if test -n "${B10_FROM_BUILD}"; then
exec ${B10_FROM_BUILD}/src/bin/bind10/b10-init $*
else
diff --git a/src/bin/bind10/init.py.in b/src/bin/bind10/init.py.in
index f47de31..874fb34 100755
--- a/src/bin/bind10/init.py.in
+++ b/src/bin/bind10/init.py.in
@@ -72,7 +72,6 @@ import isc.cc
import isc.util.process
import isc.net.parse
import isc.log
-import isc.config
from isc.log_messages.init_messages import *
import isc.bind10.component
import isc.bind10.special_component
diff --git a/src/bin/bind10/tests/Makefile.am b/src/bin/bind10/tests/Makefile.am
index 6d59dbd..a5e3fab 100644
--- a/src/bin/bind10/tests/Makefile.am
+++ b/src/bin/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 = init_test.py
+PYTESTS = bind10_test.py
noinst_SCRIPTS = $(PYTESTS)
# If necessary (rare cases), explicitly specify paths to dynamic libraries
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
new file mode 100644
index 0000000..36b98fe
--- /dev/null
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -0,0 +1,2426 @@
+# 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.
+
+# Most of the time, we omit the "init" for brevity. Sometimes,
+# we want to be explicit about what we do, like when hijacking a library
+# call used by the b10-init.
+from init import Init, ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
+import init
+
+# XXX: environment tests are currently disabled, due to the preprocessor
+# setup that we have now complicating the environment
+
+import unittest
+import sys
+import os
+import os.path
+import copy
+import signal
+import socket
+from isc.net.addr import IPAddr
+import time
+import isc
+import isc.log
+import isc.bind10.socket_cache
+import errno
+import random
+
+from isc.testutils.parse_args import TestOptParser, OptsError
+from isc.testutils.ccsession_mock import MockModuleCCSession
+
+class TestProcessInfo(unittest.TestCase):
+ def setUp(self):
+ # redirect stdout to a pipe so we can check that our
+ # process spawning is doing the right thing with stdout
+ self.old_stdout = os.dup(sys.stdout.fileno())
+ self.pipes = os.pipe()
+ os.dup2(self.pipes[1], sys.stdout.fileno())
+ os.close(self.pipes[1])
+ # note that we use dup2() to restore the original stdout
+ # to the main program ASAP in each test... this prevents
+ # hangs reading from the child process (as the pipe is only
+ # open in the child), and also insures nice pretty output
+
+ def tearDown(self):
+ # clean up our stdout munging
+ os.dup2(self.old_stdout, sys.stdout.fileno())
+ os.close(self.pipes[0])
+
+ def test_init(self):
+ pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
+ pi.spawn()
+ os.dup2(self.old_stdout, sys.stdout.fileno())
+ self.assertEqual(pi.name, 'Test Process')
+ self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
+# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
+# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
+ self.assertEqual(pi.dev_null_stdout, False)
+ self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
+ self.assertNotEqual(pi.process, None)
+ self.assertTrue(type(pi.pid) is int)
+
+# def test_setting_env(self):
+# pi = ProcessInfo('Test Process', [ '/bin/true' ], env={'FOO': 'BAR'})
+# os.dup2(self.old_stdout, sys.stdout.fileno())
+# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
+# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'],
+# 'FOO': 'BAR' })
+
+ def test_setting_null_stdout(self):
+ pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ],
+ dev_null_stdout=True)
+ pi.spawn()
+ os.dup2(self.old_stdout, sys.stdout.fileno())
+ self.assertEqual(pi.dev_null_stdout, True)
+ self.assertEqual(os.read(self.pipes[0], 100), b"")
+
+ def test_respawn(self):
+ pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
+ pi.spawn()
+ # wait for old process to work...
+ self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
+ # respawn it
+ old_pid = pi.pid
+ pi.respawn()
+ os.dup2(self.old_stdout, sys.stdout.fileno())
+ # make sure the new one started properly
+ self.assertEqual(pi.name, 'Test Process')
+ self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
+# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
+# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
+ self.assertEqual(pi.dev_null_stdout, False)
+ self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
+ self.assertNotEqual(pi.process, None)
+ self.assertTrue(type(pi.pid) is int)
+ self.assertNotEqual(pi.pid, old_pid)
+
+class TestCacheCommands(unittest.TestCase):
+ """
+ Test methods of b10-init related to the socket cache and socket handling.
+ """
+ def setUp(self):
+ """
+ Prepare b10-init for some tests.
+
+ Also prepare some variables we need.
+ """
+ self.__b10_init = Init()
+ # Fake the cache here so we can pretend it is us and hijack the
+ # calls to its methods.
+ self.__b10_init._socket_cache = self
+ self.__b10_init._socket_path = '/socket/path'
+ self.__raise_exception = None
+ self.__socket_args = {
+ "port": 53,
+ "address": "::",
+ "protocol": "UDP",
+ "share_mode": "ANY",
+ "share_name": "app"
+ }
+ # What was and wasn't called.
+ self.__drop_app_called = None
+ self.__get_socket_called = None
+ self.__send_fd_called = None
+ self.__get_token_called = None
+ self.__drop_socket_called = None
+ init.libutil_io_python.send_fd = self.__send_fd
+
+ def __send_fd(self, to, socket):
+ """
+ A function to hook the send_fd in the b10-init.
+ """
+ self.__send_fd_called = (to, socket)
+
+ class FalseSocket:
+ """
+ A socket where we can fake methods we need instead of having a real
+ socket.
+ """
+ def __init__(self):
+ self.send = b""
+ def fileno(self):
+ """
+ The file number. Used for identifying the remote application.
+ """
+ return 42
+
+ def sendall(self, data):
+ """
+ Adds data to the self.send.
+ """
+ self.send += data
+
+ def drop_application(self, application):
+ """
+ Part of pretending to be the cache. Logs the parameter to
+ self.__drop_app_called.
+
+ In the case self.__raise_exception is set, the exception there
+ is raised instead.
+ """
+ if self.__raise_exception is not None:
+ raise self.__raise_exception
+ self.__drop_app_called = application
+
+ def test_consumer_dead(self):
+ """
+ Test that it calls the drop_application method of the cache.
+ """
+ self.__b10_init.socket_consumer_dead(self.FalseSocket())
+ self.assertEqual(42, self.__drop_app_called)
+
+ def test_consumer_dead_invalid(self):
+ """
+ Test that it doesn't crash in case the application is not known to
+ the cache, the b10_init doesn't crash, as this actually can happen in
+ practice.
+ """
+ self.__raise_exception = ValueError("This application is unknown")
+ # This doesn't crash
+ self.__b10_init.socket_consumer_dead(self.FalseSocket())
+
+ def get_socket(self, token, application):
+ """
+ Part of pretending to be the cache. If there's anything in
+ __raise_exception, it is raised. Otherwise, the call is logged
+ into __get_socket_called and a number is returned.
+ """
+ if self.__raise_exception is not None:
+ raise self.__raise_exception
+ self.__get_socket_called = (token, application)
+ return 13
+
+ def test_request_handler(self):
+ """
+ Test that a request for socket is forwarded and the socket is sent
+ back, if it returns a socket.
+ """
+ socket = self.FalseSocket()
+ # An exception from the cache
+ self.__raise_exception = ValueError("Test value error")
+ self.__b10_init.socket_request_handler(b"token", socket)
+ # It was called, but it threw, so it is not noted here
+ self.assertIsNone(self.__get_socket_called)
+ self.assertEqual(b"0\n", socket.send)
+ # It should not have sent any socket.
+ self.assertIsNone(self.__send_fd_called)
+ # Now prepare a valid scenario
+ self.__raise_exception = None
+ socket.send = b""
+ self.__b10_init.socket_request_handler(b"token", socket)
+ self.assertEqual(b"1\n", socket.send)
+ self.assertEqual((42, 13), self.__send_fd_called)
+ self.assertEqual(("token", 42), self.__get_socket_called)
+
+ def get_token(self, protocol, address, port, share_mode, share_name):
+ """
+ Part of pretending to be the cache. If there's anything in
+ __raise_exception, it is raised. Otherwise, the parameters are
+ logged into __get_token_called and a token is returned.
+ """
+ if self.__raise_exception is not None:
+ raise self.__raise_exception
+ self.__get_token_called = (protocol, address, port, share_mode,
+ share_name)
+ return "token"
+
+ def test_get_socket_ok(self):
+ """
+ Test the successful scenario of getting a socket.
+ """
+ result = self.__b10_init._get_socket(self.__socket_args)
+ [code, answer] = result['result']
+ self.assertEqual(0, code)
+ self.assertEqual({
+ 'token': 'token',
+ 'path': '/socket/path'
+ }, answer)
+ addr = self.__get_token_called[1]
+ self.assertTrue(isinstance(addr, IPAddr))
+ self.assertEqual("::", str(addr))
+ self.assertEqual(("UDP", addr, 53, "ANY", "app"),
+ self.__get_token_called)
+
+ def test_get_socket_error(self):
+ """
+ Test that bad inputs are handled correctly, etc.
+ """
+ def check_code(code, args):
+ """
+ Pass the args there and check if it returns success or not.
+
+ The rest is not tested, as it is already checked in the
+ test_get_socket_ok.
+ """
+ [rcode, ranswer] = self.__b10_init._get_socket(args)['result']
+ self.assertEqual(code, rcode)
+ if code != 0:
+ # This should be an error message. The exact formatting
+ # is unknown, but we check it is string at least
+ self.assertTrue(isinstance(ranswer, str))
+
+ def mod_args(name, value):
+ """
+ Override a parameter in the args.
+ """
+ result = dict(self.__socket_args)
+ result[name] = value
+ return result
+
+ # Port too large
+ check_code(1, mod_args('port', 65536))
+ # Not numeric address
+ check_code(1, mod_args('address', 'example.org.'))
+ # Some bad values of enum-like params
+ check_code(1, mod_args('protocol', 'BAD PROTO'))
+ check_code(1, mod_args('share_mode', 'BAD SHARE'))
+ # Check missing parameters
+ for param in self.__socket_args.keys():
+ args = dict(self.__socket_args)
+ del args[param]
+ check_code(1, args)
+ # These are OK values for the enum-like parameters
+ # The ones from test_get_socket_ok are not tested here
+ check_code(0, mod_args('protocol', 'TCP'))
+ check_code(0, mod_args('share_mode', 'SAMEAPP'))
+ check_code(0, mod_args('share_mode', 'NO'))
+ # If an exception is raised from within the cache, it is converted
+ # to an error, not propagated
+ self.__raise_exception = Exception("Test exception")
+ check_code(1, self.__socket_args)
+ # The special "expected" exceptions
+ self.__raise_exception = \
+ isc.bind10.socket_cache.ShareError("Not shared")
+ check_code(3, self.__socket_args)
+ self.__raise_exception = \
+ isc.bind10.socket_cache.SocketError("Not shared", 13)
+ check_code(2, self.__socket_args)
+
+ def drop_socket(self, token):
+ """
+ Part of pretending to be the cache. If there's anything in
+ __raise_exception, it is raised. Otherwise, the parameter is stored
+ in __drop_socket_called.
+ """
+ if self.__raise_exception is not None:
+ raise self.__raise_exception
+ self.__drop_socket_called = token
+
+ def test_drop_socket(self):
+ """
+ Check the drop_socket command. It should directly call the method
+ on the cache. Exceptions should be translated to error messages.
+ """
+ # This should be OK and just propagated to the call.
+ self.assertEqual({"result": [0]},
+ self.__b10_init.command_handler("drop_socket",
+ {"token": "token"}))
+ self.assertEqual("token", self.__drop_socket_called)
+ self.__drop_socket_called = None
+ # Missing parameter
+ self.assertEqual({"result": [1, "Missing token parameter"]},
+ self.__b10_init.command_handler("drop_socket", {}))
+ self.assertIsNone(self.__drop_socket_called)
+ # An exception is raised from within the cache
+ self.__raise_exception = ValueError("Test error")
+ self.assertEqual({"result": [1, "Test error"]},
+ self.__b10_init.command_handler("drop_socket",
+ {"token": "token"}))
+
+
+class TestInit(unittest.TestCase):
+ def setUp(self):
+ # Save original values that may be tweaked in some tests
+ self.__orig_setgid = init.posix.setgid
+ self.__orig_setuid = init.posix.setuid
+ self.__orig_logger_class = isc.log.Logger
+
+ def tearDown(self):
+ # Restore original values saved in setUp()
+ init.posix.setgid = self.__orig_setgid
+ init.posix.setuid = self.__orig_setuid
+ isc.log.Logger = self.__orig_logger_class
+
+ def test_init(self):
+ b10_init = Init()
+ self.assertEqual(b10_init.verbose, False)
+ self.assertEqual(b10_init.msgq_socket_file, None)
+ self.assertEqual(b10_init.cc_session, None)
+ self.assertEqual(b10_init.ccs, None)
+ self.assertEqual(b10_init.components, {})
+ self.assertEqual(b10_init.runnable, False)
+ self.assertEqual(b10_init.username, None)
+ self.assertIsNone(b10_init._socket_cache)
+
+ def __setgid(self, gid):
+ self.__gid_set = gid
+
+ def __setuid(self, uid):
+ self.__uid_set = uid
+
+ def test_change_user(self):
+ init.posix.setgid = self.__setgid
+ init.posix.setuid = self.__setuid
+
+ self.__gid_set = None
+ self.__uid_set = None
+ b10_init = Init()
+ b10_init.change_user()
+ # No gid/uid set in init, nothing called.
+ self.assertIsNone(self.__gid_set)
+ self.assertIsNone(self.__uid_set)
+
+ Init(setuid=42, setgid=4200).change_user()
+ # This time, it get's called
+ self.assertEqual(4200, self.__gid_set)
+ self.assertEqual(42, self.__uid_set)
+
+ def raising_set_xid(gid_or_uid):
+ ex = OSError()
+ ex.errno, ex.strerror = errno.EPERM, 'Operation not permitted'
+ raise ex
+
+ # Let setgid raise an exception
+ init.posix.setgid = raising_set_xid
+ init.posix.setuid = self.__setuid
+ self.assertRaises(init.ChangeUserError,
+ Init(setuid=42, setgid=4200).change_user)
+
+ # Let setuid raise an exception
+ init.posix.setgid = self.__setgid
+ init.posix.setuid = raising_set_xid
+ self.assertRaises(init.ChangeUserError,
+ Init(setuid=42, setgid=4200).change_user)
+
+ # Let initial log output after setuid raise an exception
+ init.posix.setgid = self.__setgid
+ init.posix.setuid = self.__setuid
+ isc.log.Logger = raising_set_xid
+ self.assertRaises(init.ChangeUserError,
+ Init(setuid=42, setgid=4200).change_user)
+
+ def test_set_creator(self):
+ """
+ Test the call to set_creator. First time, the cache is created
+ with the passed creator. The next time, it throws an exception.
+ """
+ init = Init()
+ # The cache doesn't use it at start, so just create an empty class
+ class Creator: pass
+ creator = Creator()
+ init.set_creator(creator)
+ self.assertTrue(isinstance(init._socket_cache,
+ isc.bind10.socket_cache.Cache))
+ self.assertEqual(creator, init._socket_cache._creator)
+ self.assertRaises(ValueError, init.set_creator, creator)
+
+ def test_socket_srv(self):
+ """Tests init_socket_srv() and remove_socket_srv() work as expected."""
+ init = Init()
+
+ self.assertIsNone(init._srv_socket)
+ self.assertIsNone(init._tmpdir)
+ self.assertIsNone(init._socket_path)
+
+ init.init_socket_srv()
+
+ self.assertIsNotNone(init._srv_socket)
+ self.assertNotEqual(-1, init._srv_socket.fileno())
+ self.assertEqual(os.path.join(init._tmpdir, 'sockcreator'),
+ init._srv_socket.getsockname())
+
+ self.assertIsNotNone(init._tmpdir)
+ self.assertTrue(os.path.isdir(init._tmpdir))
+ self.assertIsNotNone(init._socket_path)
+ self.assertTrue(os.path.exists(init._socket_path))
+
+ # Check that it's possible to connect to the socket file (this
+ # only works if the socket file exists and the server listens on
+ # it).
+ s = socket.socket(socket.AF_UNIX)
+ try:
+ s.connect(init._socket_path)
+ can_connect = True
+ s.close()
+ except socket.error as e:
+ can_connect = False
+
+ self.assertTrue(can_connect)
+
+ init.remove_socket_srv()
+
+ self.assertEqual(-1, init._srv_socket.fileno())
+ self.assertFalse(os.path.exists(init._socket_path))
+ self.assertFalse(os.path.isdir(init._tmpdir))
+
+ # These should not fail either:
+
+ # second call
+ init.remove_socket_srv()
+
+ init._srv_socket = None
+ init.remove_socket_srv()
+
+ def test_init_alternate_socket(self):
+ init = Init("alt_socket_file")
+ self.assertEqual(init.verbose, False)
+ self.assertEqual(init.msgq_socket_file, "alt_socket_file")
+ self.assertEqual(init.cc_session, None)
+ self.assertEqual(init.ccs, None)
+ self.assertEqual(init.components, {})
+ self.assertEqual(init.runnable, False)
+ self.assertEqual(init.username, None)
+
+ def test_command_handler(self):
+ class DummySession():
+ def group_sendmsg(self, msg, group):
+ (self.msg, self.group) = (msg, group)
+ def group_recvmsg(self, nonblock, seq): pass
+ class DummyModuleCCSession():
+ module_spec = isc.config.module_spec.ModuleSpec({
+ "module_name": "Init",
+ "statistics": [
+ {
+ "item_name": "boot_time",
+ "item_type": "string",
+ "item_optional": False,
+ "item_default": "1970-01-01T00:00:00Z",
+ "item_title": "Boot time",
+ "item_description": "A date time when bind10 process starts initially",
+ "item_format": "date-time"
+ }
+ ]
+ })
+ def get_module_spec(self):
+ return self.module_spec
+ init = Init()
+ init.verbose = True
+ init.cc_session = DummySession()
+ init.ccs = DummyModuleCCSession()
+ # a bad command
+ self.assertEqual(init.command_handler(-1, None),
+ isc.config.ccsession.create_answer(1, "bad command"))
+ # "shutdown" command
+ self.assertEqual(init.command_handler("shutdown", None),
+ isc.config.ccsession.create_answer(0))
+ self.assertFalse(init.runnable)
+ # "getstats" command
+ self.assertEqual(init.command_handler("getstats", None),
+ isc.config.ccsession.create_answer(0,
+ { 'boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME) }))
+ # "ping" command
+ self.assertEqual(init.command_handler("ping", None),
+ isc.config.ccsession.create_answer(0, "pong"))
+ # "show_processes" command
+ self.assertEqual(init.command_handler("show_processes", None),
+ isc.config.ccsession.create_answer(0,
+ init.get_processes()))
+ # an unknown command
+ self.assertEqual(init.command_handler("__UNKNOWN__", None),
+ isc.config.ccsession.create_answer(1, "Unknown command"))
+
+ # Fake the get_token of cache and test the command works
+ init._socket_path = '/socket/path'
+ class cache:
+ def get_token(self, protocol, addr, port, share_mode, share_name):
+ return str(addr) + ':' + str(port)
+ init._socket_cache = cache()
+ args = {
+ "port": 53,
+ "address": "0.0.0.0",
+ "protocol": "UDP",
+ "share_mode": "ANY",
+ "share_name": "app"
+ }
+ # at all and this is the easiest way to check.
+ self.assertEqual({'result': [0, {'token': '0.0.0.0:53',
+ 'path': '/socket/path'}]},
+ init.command_handler("get_socket", args))
+ # The drop_socket is not tested here, but in TestCacheCommands.
+ # It needs the cache mocks to be in place and they are there.
+
+ def test_stop_process(self):
+ """
+ Test checking the stop_process method sends the right message over
+ the message bus.
+ """
+ class DummySession():
+ def group_sendmsg(self, msg, group, instance="*"):
+ (self.msg, self.group, self.instance) = (msg, group, instance)
+ init = Init()
+ init.cc_session = DummySession()
+ init.stop_process('process', 'address', 42)
+ self.assertEqual('address', init.cc_session.group)
+ self.assertEqual('address', init.cc_session.instance)
+ self.assertEqual({'command': ['shutdown', {'pid': 42}]},
+ init.cc_session.msg)
+
+# Mock class for testing Init's usage of ProcessInfo
+class MockProcessInfo:
+ def __init__(self, name, args, env={}, dev_null_stdout=False,
+ dev_null_stderr=False):
+ self.name = name
+ self.args = args
+ self.env = env
+ self.dev_null_stdout = dev_null_stdout
+ self.dev_null_stderr = dev_null_stderr
+ self.process = None
+ self.pid = None
+
+ def spawn(self):
+ # set some pid (only used for testing that it is not None anymore)
+ self.pid = 42147
+
+# Class for testing the Init without actually starting processes.
+# This is used for testing the start/stop components routines and
+# the Init commands.
+#
+# Testing that external processes start is outside the scope
+# of the unit test, by overriding the process start methods we can check
+# that the right processes are started depending on the configuration
+# options.
+class MockInit(Init):
+ def __init__(self):
+ Init.__init__(self)
+
+ # Set flags as to which of the overridden methods has been run.
+ self.msgq = False
+ self.cfgmgr = False
+ self.ccsession = False
+ self.auth = False
+ self.resolver = False
+ self.xfrout = False
+ self.xfrin = False
+ self.zonemgr = False
+ self.stats = False
+ self.stats_httpd = False
+ self.cmdctl = False
+ self.dhcp6 = False
+ self.dhcp4 = False
+ self.c_channel_env = {}
+ self.components = { }
+ self.creator = False
+ self.get_process_exit_status_called = False
+
+ class MockSockCreator(isc.bind10.component.Component):
+ def __init__(self, process, b10_init, kind, address=None,
+ params=None):
+ isc.bind10.component.Component.__init__(self, process,
+ b10_init, kind,
+ 'SockCreator')
+ self._start_func = b10_init.start_creator
+
+ specials = isc.bind10.special_component.get_specials()
+ specials['sockcreator'] = MockSockCreator
+ self._component_configurator = \
+ isc.bind10.component.Configurator(self, specials)
+
+ def start_creator(self):
+ self.creator = True
+ procinfo = ProcessInfo('b10-sockcreator', ['/bin/false'])
+ procinfo.pid = 1
+ return procinfo
+
+ def _read_bind10_config(self):
+ # Configuration options are set directly
+ pass
+
+ def start_msgq(self):
+ self.msgq = True
+ procinfo = ProcessInfo('b10-msgq', ['/bin/false'])
+ procinfo.pid = 2
+ return procinfo
+
+ def start_ccsession(self, c_channel_env):
+ # this is not a process, don't have to do anything with procinfo
+ self.ccsession = True
+
+ def start_cfgmgr(self):
+ self.cfgmgr = True
+ procinfo = ProcessInfo('b10-cfgmgr', ['/bin/false'])
+ procinfo.pid = 3
+ return procinfo
+
+ def start_auth(self):
+ self.auth = True
+ procinfo = ProcessInfo('b10-auth', ['/bin/false'])
+ procinfo.pid = 5
+ return procinfo
+
+ def start_resolver(self):
+ self.resolver = True
+ procinfo = ProcessInfo('b10-resolver', ['/bin/false'])
+ procinfo.pid = 6
+ return procinfo
+
+ def start_simple(self, name):
+ procmap = { 'b10-zonemgr': self.start_zonemgr,
+ 'b10-stats': self.start_stats,
+ 'b10-stats-httpd': self.start_stats_httpd,
+ 'b10-cmdctl': self.start_cmdctl,
+ 'b10-dhcp6': self.start_dhcp6,
+ 'b10-dhcp4': self.start_dhcp4,
+ 'b10-xfrin': self.start_xfrin,
+ 'b10-xfrout': self.start_xfrout }
+ return procmap[name]()
+
+ def start_xfrout(self):
+ self.xfrout = True
+ procinfo = ProcessInfo('b10-xfrout', ['/bin/false'])
+ procinfo.pid = 7
+ return procinfo
+
+ def start_xfrin(self):
+ self.xfrin = True
+ procinfo = ProcessInfo('b10-xfrin', ['/bin/false'])
+ procinfo.pid = 8
+ return procinfo
+
+ def start_zonemgr(self):
+ self.zonemgr = True
+ procinfo = ProcessInfo('b10-zonemgr', ['/bin/false'])
+ procinfo.pid = 9
+ return procinfo
+
+ def start_stats(self):
+ self.stats = True
+ procinfo = ProcessInfo('b10-stats', ['/bin/false'])
+ procinfo.pid = 10
+ return procinfo
+
+ def start_stats_httpd(self):
+ self.stats_httpd = True
+ procinfo = ProcessInfo('b10-stats-httpd', ['/bin/false'])
+ procinfo.pid = 11
+ return procinfo
+
+ def start_cmdctl(self):
+ self.cmdctl = True
+ procinfo = ProcessInfo('b10-cmdctl', ['/bin/false'])
+ procinfo.pid = 12
+ return procinfo
+
+ def start_dhcp6(self):
+ self.dhcp6 = True
+ procinfo = ProcessInfo('b10-dhcp6', ['/bin/false'])
+ procinfo.pid = 13
+ return procinfo
+
+ def start_dhcp4(self):
+ self.dhcp4 = True
+ procinfo = ProcessInfo('b10-dhcp4', ['/bin/false'])
+ procinfo.pid = 14
+ return procinfo
+
+ def stop_process(self, process, recipient, pid):
+ procmap = { 'b10-auth': self.stop_auth,
+ 'b10-resolver': self.stop_resolver,
+ 'b10-xfrout': self.stop_xfrout,
+ 'b10-xfrin': self.stop_xfrin,
+ 'b10-zonemgr': self.stop_zonemgr,
+ 'b10-stats': self.stop_stats,
+ 'b10-stats-httpd': self.stop_stats_httpd,
+ 'b10-cmdctl': self.stop_cmdctl }
+ procmap[process]()
+
+ # Some functions to pretend we stop processes, use by stop_process
+ def stop_msgq(self):
+ if self.msgq:
+ del self.components[2]
+ self.msgq = False
+
+ def stop_cfgmgr(self):
+ if self.cfgmgr:
+ del self.components[3]
+ self.cfgmgr = False
+
+ def stop_auth(self):
+ if self.auth:
+ del self.components[5]
+ self.auth = False
+
+ def stop_resolver(self):
+ if self.resolver:
+ del self.components[6]
+ self.resolver = False
+
+ def stop_xfrout(self):
+ if self.xfrout:
+ del self.components[7]
+ self.xfrout = False
+
+ def stop_xfrin(self):
+ if self.xfrin:
+ del self.components[8]
+ self.xfrin = False
+
+ def stop_zonemgr(self):
+ if self.zonemgr:
+ del self.components[9]
+ self.zonemgr = False
+
+ def stop_stats(self):
+ if self.stats:
+ del self.components[10]
+ self.stats = False
+
+ def stop_stats_httpd(self):
+ if self.stats_httpd:
+ del self.components[11]
+ self.stats_httpd = False
+
+ def stop_cmdctl(self):
+ if self.cmdctl:
+ del self.components[12]
+ self.cmdctl = False
+
+ def _get_process_exit_status(self):
+ if self.get_process_exit_status_called:
+ return (0, 0)
+ self.get_process_exit_status_called = True
+ return (53, 0)
+
+ def _get_process_exit_status_unknown_pid(self):
+ if self.get_process_exit_status_called:
+ return (0, 0)
+ self.get_process_exit_status_called = True
+ return (42, 0)
+
+ def _get_process_exit_status_raises_oserror_echild(self):
+ raise OSError(errno.ECHILD, 'Mock error')
+
+ def _get_process_exit_status_raises_oserror_other(self):
+ raise OSError(0, 'Mock error')
+
+ def _get_process_exit_status_raises_other(self):
+ raise Exception('Mock error')
+
+ def _make_mock_process_info(self, name, args, c_channel_env,
+ dev_null_stdout=False, dev_null_stderr=False):
+ return MockProcessInfo(name, args, c_channel_env,
+ dev_null_stdout, dev_null_stderr)
+
+class MockInitSimple(Init):
+ def __init__(self):
+ Init.__init__(self)
+ # Set which process has been started
+ self.started_process_name = None
+ self.started_process_args = None
+ self.started_process_env = None
+
+ def _make_mock_process_info(self, name, args, c_channel_env,
+ dev_null_stdout=False, dev_null_stderr=False):
+ return MockProcessInfo(name, args, c_channel_env,
+ dev_null_stdout, dev_null_stderr)
+
+ def start_process(self, name, args, c_channel_env, port=None,
+ address=None):
+ self.started_process_name = name
+ self.started_process_args = args
+ self.started_process_env = c_channel_env
+ return None
+
+class TestStartStopProcessesInit(unittest.TestCase):
+ """
+ Check that the start_all_components method starts the right combination
+ of components and that the right components are started and stopped
+ according to changes in configuration.
+ """
+ def check_environment_unchanged(self):
+ # Check whether the environment has not been changed
+ self.assertEqual(original_os_environ, os.environ)
+
+ def check_started(self, init, core, auth, resolver):
+ """
+ Check that the right sets of services are started. The ones that
+ should be running are specified by the core, auth and resolver parameters
+ (they are groups of processes, eg. auth means b10-auth, -xfrout, -xfrin
+ and -zonemgr).
+ """
+ self.assertEqual(init.msgq, core)
+ self.assertEqual(init.cfgmgr, core)
+ self.assertEqual(init.ccsession, core)
+ self.assertEqual(init.creator, core)
+ self.assertEqual(init.auth, auth)
+ self.assertEqual(init.resolver, resolver)
+ self.assertEqual(init.xfrout, auth)
+ self.assertEqual(init.xfrin, auth)
+ self.assertEqual(init.zonemgr, auth)
+ self.assertEqual(init.stats, core)
+ self.assertEqual(init.stats_httpd, core)
+ self.assertEqual(init.cmdctl, core)
+ self.check_environment_unchanged()
+
+ def check_preconditions(self, init):
+ self.check_started(init, False, False, False)
+
+ def check_started_none(self, init):
+ """
+ Check that the situation is according to configuration where no servers
+ should be started. Some components still need to be running.
+ """
+ self.check_started(init, True, False, False)
+ self.check_environment_unchanged()
+
+ def check_started_both(self, init):
+ """
+ Check the situation is according to configuration where both servers
+ (auth and resolver) are enabled.
+ """
+ self.check_started(init, True, True, True)
+ self.check_environment_unchanged()
+
+ def check_started_auth(self, init):
+ """
+ Check the set of components needed to run auth only is started.
+ """
+ self.check_started(init, True, True, False)
+ self.check_environment_unchanged()
+
+ def check_started_resolver(self, init):
+ """
+ Check the set of components needed to run resolver only is started.
+ """
+ self.check_started(init, True, False, True)
+ self.check_environment_unchanged()
+
+ def check_started_dhcp(self, init, v4, v6):
+ """
+ Check if proper combinations of DHCPv4 and DHCpv6 can be started
+ """
+ self.assertEqual(v4, init.dhcp4)
+ self.assertEqual(v6, init.dhcp6)
+ self.check_environment_unchanged()
+
+ def construct_config(self, start_auth, start_resolver):
+ # The things that are common, not turned on an off
+ config = {}
+ config['b10-stats'] = { 'kind': 'dispensable', 'address': 'Stats' }
+ config['b10-stats-httpd'] = { 'kind': 'dispensable',
+ 'address': 'StatsHttpd' }
+ config['b10-cmdctl'] = { 'kind': 'needed', 'special': 'cmdctl' }
+ if start_auth:
+ config['b10-auth'] = { 'kind': 'needed', 'special': 'auth' }
+ config['b10-xfrout'] = { 'kind': 'dispensable',
+ 'address': 'Xfrout' }
+ config['b10-xfrin'] = { 'kind': 'dispensable',
+ 'address': 'Xfrin' }
+ config['b10-zonemgr'] = { 'kind': 'dispensable',
+ 'address': 'Zonemgr' }
+ if start_resolver:
+ config['b10-resolver'] = { 'kind': 'needed',
+ 'special': 'resolver' }
+ return {'components': config}
+
+ def config_start_init(self, start_auth, start_resolver):
+ """
+ Test the configuration is loaded at the startup.
+ """
+ init = MockInit()
+ config = self.construct_config(start_auth, start_resolver)
+ class CC:
+ def get_full_config(self):
+ return config
+ # Provide the fake CC with data
+ init.ccs = CC()
+ # And make sure it's not overwritten
+ def start_ccsession():
+ init.ccsession = True
+ init.start_ccsession = lambda _: start_ccsession()
+ # We need to return the original _read_bind10_config
+ init._read_bind10_config = lambda: Init._read_bind10_config(init)
+ init.start_all_components()
+ self.check_started(init, True, start_auth, start_resolver)
+ self.check_environment_unchanged()
+
+ def test_start_none(self):
+ self.config_start_init(False, False)
+
+ def test_start_resolver(self):
+ self.config_start_init(False, True)
+
+ def test_start_auth(self):
+ self.config_start_init(True, False)
+
+ def test_start_both(self):
+ self.config_start_init(True, True)
+
+ def test_config_start(self):
+ """
+ Test that the configuration starts and stops components according
+ to configuration changes.
+ """
+
+ # Create Init and ensure correct initialization
+ init = MockInit()
+ self.check_preconditions(init)
+
+ init.start_all_components()
+ init.runnable = True
+ init.config_handler(self.construct_config(False, False))
+ self.check_started_none(init)
+
+ # Enable both at once
+ init.config_handler(self.construct_config(True, True))
+ self.check_started_both(init)
+
+ # Not touched by empty change
+ init.config_handler({})
+ self.check_started_both(init)
+
+ # Not touched by change to the same configuration
+ init.config_handler(self.construct_config(True, True))
+ self.check_started_both(init)
+
+ # Turn them both off again
+ init.config_handler(self.construct_config(False, False))
+ self.check_started_none(init)
+
+ # Not touched by empty change
+ init.config_handler({})
+ self.check_started_none(init)
+
+ # Not touched by change to the same configuration
+ init.config_handler(self.construct_config(False, False))
+ self.check_started_none(init)
+
+ # Start and stop auth separately
+ init.config_handler(self.construct_config(True, False))
+ self.check_started_auth(init)
+
+ init.config_handler(self.construct_config(False, False))
+ self.check_started_none(init)
+
+ # Start and stop resolver separately
+ init.config_handler(self.construct_config(False, True))
+ self.check_started_resolver(init)
+
+ init.config_handler(self.construct_config(False, False))
+ self.check_started_none(init)
+
+ # Alternate
+ init.config_handler(self.construct_config(True, False))
+ self.check_started_auth(init)
+
+ init.config_handler(self.construct_config(False, True))
+ self.check_started_resolver(init)
+
+ init.config_handler(self.construct_config(True, False))
+ self.check_started_auth(init)
+
+ def test_config_start_once(self):
+ """
+ Tests that a component is started only once.
+ """
+ # Create Init and ensure correct initialization
+ init = MockInit()
+ self.check_preconditions(init)
+
+ init.start_all_components()
+
+ init.runnable = True
+ init.config_handler(self.construct_config(True, True))
+ self.check_started_both(init)
+
+ init.start_auth = lambda: self.fail("Started auth again")
+ init.start_xfrout = lambda: self.fail("Started xfrout again")
+ init.start_xfrin = lambda: self.fail("Started xfrin again")
+ init.start_zonemgr = lambda: self.fail("Started zonemgr again")
+ init.start_resolver = lambda: self.fail("Started resolver again")
+
+ # Send again we want to start them. Should not do it, as they are.
+ init.config_handler(self.construct_config(True, True))
+
+ def test_config_not_started_early(self):
+ """
+ Test that components are not started by the config handler before
+ startup.
+ """
+ init = MockInit()
+ self.check_preconditions(init)
+
+ init.start_auth = lambda: self.fail("Started auth again")
+ init.start_xfrout = lambda: self.fail("Started xfrout again")
+ init.start_xfrin = lambda: self.fail("Started xfrin again")
+ init.start_zonemgr = lambda: self.fail("Started zonemgr again")
+ init.start_resolver = lambda: self.fail("Started resolver again")
+
+ init.config_handler({'start_auth': True, 'start_resolver': True})
+
+ # Checks that DHCP (v4 and v6) components are started when expected
+ def test_start_dhcp(self):
+
+ # Create Init and ensure correct initialization
+ init = MockInit()
+ self.check_preconditions(init)
+
+ init.start_all_components()
+ init.config_handler(self.construct_config(False, False))
+ self.check_started_dhcp(init, False, False)
+
+ def test_start_dhcp_v6only(self):
+ # Create Init and ensure correct initialization
+ init = MockInit()
+ self.check_preconditions(init)
+ # v6 only enabled
+ init.start_all_components()
+ init.runnable = True
+ init._Init_started = True
+ config = self.construct_config(False, False)
+ config['components']['b10-dhcp6'] = { 'kind': 'needed',
+ 'address': 'Dhcp6' }
+ init.config_handler(config)
+ self.check_started_dhcp(init, False, True)
+
+ # uncomment when dhcpv4 becomes implemented
+ # v4 only enabled
+ #init.cfg_start_dhcp6 = False
+ #init.cfg_start_dhcp4 = True
+ #self.check_started_dhcp(init, True, False)
+
+ # both v4 and v6 enabled
+ #init.cfg_start_dhcp6 = True
+ #init.cfg_start_dhcp4 = True
+ #self.check_started_dhcp(init, True, True)
+
+class MockComponent:
+ def __init__(self, name, pid, address=None):
+ self.name = lambda: name
+ self.pid = lambda: pid
+ self.address = lambda: address
+ self.restarted = False
+ self.forceful = False
+ self.running = True
+ self.has_failed = False
+
+ def get_restart_time(self):
+ return 0 # arbitrary dummy value
+
+ def restart(self, now):
+ self.restarted = True
+ return True
+
+ def is_running(self):
+ return self.running
+
+ def failed(self, status):
+ return self.has_failed
+
+ def kill(self, forceful):
+ self.forceful = forceful
+
+class TestInitCmd(unittest.TestCase):
+ def test_ping(self):
+ """
+ Confirm simple ping command works.
+ """
+ init = MockInit()
+ answer = init.command_handler("ping", None)
+ self.assertEqual(answer, {'result': [0, 'pong']})
+
+ def test_show_processes_empty(self):
+ """
+ Confirm getting a list of processes works.
+ """
+ init = MockInit()
+ answer = init.command_handler("show_processes", None)
+ self.assertEqual(answer, {'result': [0, []]})
+
+ def test_show_processes(self):
+ """
+ Confirm getting a list of processes works.
+ """
+ init = MockInit()
+ init.register_process(1, MockComponent('first', 1))
+ init.register_process(2, MockComponent('second', 2, 'Second'))
+ answer = init.command_handler("show_processes", None)
+ processes = [[1, 'first', None],
+ [2, 'second', 'Second']]
+ self.assertEqual(answer, {'result': [0, processes]})
+
+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_clear_config(self):
+ options = parse_args([], TestOptParser)
+ self.assertEqual(False, options.clear_config)
+ options = parse_args(['--clear-config'], TestOptParser)
+ self.assertEqual(True, options.clear_config)
+
+ def test_nokill(self):
+ options = parse_args([], TestOptParser)
+ self.assertEqual(False, options.nokill)
+ options = parse_args(['--no-kill'], TestOptParser)
+ self.assertEqual(True, options.nokill)
+ options = parse_args([], TestOptParser)
+ self.assertEqual(False, options.nokill)
+ options = parse_args(['-i'], TestOptParser)
+ self.assertEqual(True, options.nokill)
+
+ 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'
+ if os.path.exists(self.pid_file):
+ os.unlink(self.pid_file)
+
+ def tearDown(self):
+ if os.path.exists(self.pid_file):
+ os.unlink(self.pid_file)
+
+ def check_pid_file(self):
+ # dump PID to the file, and confirm the content is correct
+ dump_pid(self.pid_file)
+ my_pid = os.getpid()
+ with open(self.pid_file, "r") as f:
+ self.assertEqual(my_pid, int(f.read()))
+
+ def test_dump_pid(self):
+ self.check_pid_file()
+
+ # make sure any existing content will be removed
+ with open(self.pid_file, "w") as f:
+ f.write('dummy data\n')
+ self.check_pid_file()
+
+ def test_unlink_pid_file_notexist(self):
+ dummy_data = 'dummy_data\n'
+
+ with open(self.pid_file, "w") as f:
+ f.write(dummy_data)
+
+ unlink_pid_file("no_such_pid_file")
+
+ # the file specified for unlink_pid_file doesn't exist,
+ # and the original content of the file should be intact.
+ with open(self.pid_file, "r") as f:
+ self.assertEqual(dummy_data, f.read())
+
+ def test_dump_pid_with_none(self):
+ # Check the behavior of dump_pid() and unlink_pid_file() with None.
+ # This should be no-op.
+ dump_pid(None)
+ self.assertFalse(os.path.exists(self.pid_file))
+
+ dummy_data = 'dummy_data\n'
+
+ with open(self.pid_file, "w") as f:
+ f.write(dummy_data)
+
+ unlink_pid_file(None)
+
+ with open(self.pid_file, "r") as f:
+ self.assertEqual(dummy_data, f.read())
+
+ def test_dump_pid_failure(self):
+ # the attempt to open file will fail, which should result in exception.
+ self.assertRaises(IOError, dump_pid,
+ 'nonexistent_dir' + os.sep + 'bind10.pid')
+
+class TestInitComponents(unittest.TestCase):
+ """
+ Test b10-init propagates component configuration properly to the
+ component configurator and acts sane.
+ """
+ def setUp(self):
+ self.__param = None
+ self.__called = False
+ self.__compconfig = {
+ 'comp': {
+ 'kind': 'needed',
+ 'process': 'cat'
+ }
+ }
+ self._tmp_time = None
+ self._tmp_sleep = None
+ self._tmp_module_cc_session = None
+ self._tmp_cc_session = None
+
+ def tearDown(self):
+ if self._tmp_time is not None:
+ time.time = self._tmp_time
+ if self._tmp_sleep is not None:
+ time.sleep = self._tmp_sleep
+ if self._tmp_module_cc_session is not None:
+ isc.config.ModuleCCSession = self._tmp_module_cc_session
+ if self._tmp_cc_session is not None:
+ isc.cc.Session = self._tmp_cc_session
+
+ def __unary_hook(self, param):
+ """
+ A hook function that stores the parameter for later examination.
+ """
+ self.__param = param
+
+ def __nullary_hook(self):
+ """
+ A hook function that notes down it was called.
+ """
+ self.__called = True
+
+ def __check_core(self, config):
+ """
+ A function checking that the config contains parts for the valid
+ core component configuration.
+ """
+ self.assertIsNotNone(config)
+ for component in ['sockcreator', 'msgq', 'cfgmgr']:
+ self.assertTrue(component in config)
+ self.assertEqual(component, config[component]['special'])
+ self.assertEqual('core', config[component]['kind'])
+
+ def __check_extended(self, config):
+ """
+ This checks that the config contains the core and one more component.
+ """
+ self.__check_core(config)
+ self.assertTrue('comp' in config)
+ self.assertEqual('cat', config['comp']['process'])
+ self.assertEqual('needed', config['comp']['kind'])
+ self.assertEqual(4, len(config))
+
+ def test_correct_run(self):
+ """
+ Test the situation when we run in usual scenario, nothing fails,
+ we just start, reconfigure and then stop peacefully.
+ """
+ init = MockInit()
+ # Start it
+ orig = init._component_configurator.startup
+ init._component_configurator.startup = self.__unary_hook
+ init.start_all_components()
+ init._component_configurator.startup = orig
+ self.__check_core(self.__param)
+ self.assertEqual(3, len(self.__param))
+
+ # Reconfigure it
+ self.__param = None
+ orig = init._component_configurator.reconfigure
+ init._component_configurator.reconfigure = self.__unary_hook
+ # Otherwise it does not work
+ init.runnable = True
+ init.config_handler({'components': self.__compconfig})
+ self.__check_extended(self.__param)
+ currconfig = self.__param
+ # If we reconfigure it, but it does not contain the components part,
+ # nothing is called
+ init.config_handler({})
+ self.assertEqual(self.__param, currconfig)
+ self.__param = None
+ init._component_configurator.reconfigure = orig
+ # Check a configuration that messes up the core components is rejected.
+ compconf = dict(self.__compconfig)
+ compconf['msgq'] = { 'process': 'echo' }
+ result = init.config_handler({'components': compconf})
+ # Check it rejected it
+ self.assertEqual(1, result['result'][0])
+
+ # We can't call shutdown, that one relies on the stuff in main
+ # We check somewhere else that the shutdown is actually called
+ # from there (the test_kills).
+
+ def __real_test_kill(self, nokill=False, ex_on_kill=None):
+ """
+ Helper function that does the actual kill functionality testing.
+ """
+ init = MockInit()
+ init.nokill = nokill
+
+ killed = []
+ class ImmortalComponent:
+ """
+ An immortal component. It does not stop when it is told so
+ (anyway it is not told so). It does not die if it is killed
+ the first time. It dies only when killed forcefully.
+ """
+ def __init__(self):
+ # number of kill() calls, preventing infinite loop.
+ self.__call_count = 0
+
+ def kill(self, forceful=False):
+ self.__call_count += 1
+ if self.__call_count > 2:
+ raise Exception('Too many calls to ImmortalComponent.kill')
+
+ killed.append(forceful)
+ if ex_on_kill is not None:
+ # If exception is given by the test, raise it here.
+ # In the case of ESRCH, the process should have gone
+ # somehow, so we clear the components.
+ if ex_on_kill.errno == errno.ESRCH:
+ init.components = {}
+ raise ex_on_kill
+ if forceful:
+ init.components = {}
+ def pid(self):
+ return 1
+ def name(self):
+ return "Immortal"
+ init.components = {}
+ init.register_process(1, ImmortalComponent())
+
+ # While at it, we check the configurator shutdown is actually called
+ orig = init._component_configurator.shutdown
+ init._component_configurator.shutdown = self.__nullary_hook
+ self.__called = False
+
+ init.ccs = MockModuleCCSession()
+ self.assertFalse(init.ccs.stopped)
+
+ init.shutdown()
+
+ self.assertTrue(init.ccs.stopped)
+
+ # Here, killed is an array where False is added if SIGTERM
+ # should be sent, or True if SIGKILL should be sent, in order in
+ # which they're sent.
+ if nokill:
+ self.assertEqual([], killed)
+ else:
+ if ex_on_kill is not None:
+ self.assertEqual([False], killed)
+ else:
+ self.assertEqual([False, True], killed)
+
+ self.assertTrue(self.__called)
+
+ init._component_configurator.shutdown = orig
+
+ def test_kills(self):
+ """
+ Test that b10-init kills components which don't want to stop.
+ """
+ self.__real_test_kill()
+
+ def test_kill_fail(self):
+ """Test cases where kill() results in an exception due to OS error.
+
+ The behavior should be different for EPERM, so we test two cases.
+
+ """
+
+ ex = OSError()
+ ex.errno, ex.strerror = errno.ESRCH, 'No such process'
+ self.__real_test_kill(ex_on_kill=ex)
+
+ ex.errno, ex.strerror = errno.EPERM, 'Operation not permitted'
+ self.__real_test_kill(ex_on_kill=ex)
+
+ def test_nokill(self):
+ """
+ Test that b10-init *doesn't* kill components which don't want to
+ stop, when asked not to (by passing the --no-kill option which
+ sets init.nokill to True).
+ """
+ self.__real_test_kill(True)
+
+ def test_component_shutdown(self):
+ """
+ Test the component_shutdown sets all variables accordingly.
+ """
+ init = MockInit()
+ self.assertRaises(Exception, init.component_shutdown, 1)
+ self.assertEqual(1, init.exitcode)
+ init._Init__started = True
+ init.component_shutdown(2)
+ self.assertEqual(2, init.exitcode)
+ self.assertFalse(init.runnable)
+
+ def test_init_config(self):
+ """
+ Test initial configuration is loaded.
+ """
+ init = MockInit()
+ # Start it
+ init._component_configurator.reconfigure = self.__unary_hook
+ # We need to return the original read_bind10_config
+ init._read_bind10_config = lambda: Init._read_bind10_config(init)
+ # And provide a session to read the data from
+ class CC:
+ pass
+ init.ccs = CC()
+ init.ccs.get_full_config = lambda: {'components': self.__compconfig}
+ init.start_all_components()
+ self.__check_extended(self.__param)
+
+ def __setup_restart(self, init, component):
+ '''Common procedure for restarting a component used below.'''
+ init.components_to_restart = { component }
+ component.restarted = False
+ init.restart_processes()
+
+ def test_restart_processes(self):
+ '''Check some behavior on restarting processes.'''
+ init = MockInit()
+ init.runnable = True
+ component = MockComponent('test', 53)
+
+ # A component to be restarted will actually be restarted iff it's
+ # in the configurator's configuration.
+ # We bruteforce the configurator internal below; ugly, but the easiest
+ # way for the test.
+ init._component_configurator._components['test'] = (None, component)
+ self.__setup_restart(init, component)
+ self.assertTrue(component.restarted)
+ self.assertNotIn(component, init.components_to_restart)
+
+ # Remove the component from the configuration. It won't be restarted
+ # even if scheduled, nor will remain in the to-be-restarted list.
+ del init._component_configurator._components['test']
+ self.__setup_restart(init, component)
+ self.assertFalse(component.restarted)
+ self.assertNotIn(component, init.components_to_restart)
+
+ def test_get_processes(self):
+ '''Test that procsses are returned correctly, sorted by pid.'''
+ init = MockInit()
+
+ pids = list(range(0, 20))
+ random.shuffle(pids)
+
+ for i in range(0, 20):
+ pid = pids[i]
+ component = MockComponent('test' + str(pid), pid,
+ 'Test' + str(pid))
+ init.components[pid] = component
+
+ process_list = init.get_processes()
+ self.assertEqual(20, len(process_list))
+
+ last_pid = -1
+ for process in process_list:
+ pid = process[0]
+ self.assertLessEqual(last_pid, pid)
+ last_pid = pid
+ self.assertEqual([pid, 'test' + str(pid), 'Test' + str(pid)],
+ process)
+
+ def _test_reap_children_helper(self, runnable, is_running, failed):
+ '''Construct a Init instance, set various data in it according to
+ passed args and check if the component was added to the list of
+ components to restart.'''
+ init = MockInit()
+ init.runnable = runnable
+
+ component = MockComponent('test', 53)
+ component.running = is_running
+ component.has_failed = failed
+ init.components[53] = component
+
+ self.assertNotIn(component, init.components_to_restart)
+
+ init.reap_children()
+
+ if runnable and is_running and not failed:
+ self.assertIn(component, init.components_to_restart)
+ else:
+ self.assertEqual([], init.components_to_restart)
+
+ def test_reap_children(self):
+ '''Test that children are queued to be restarted when they ask for it.'''
+ # test various combinations of 3 booleans
+ # (Init.runnable, component.is_running(), component.failed())
+ self._test_reap_children_helper(False, False, False)
+ self._test_reap_children_helper(False, False, True)
+ self._test_reap_children_helper(False, True, False)
+ self._test_reap_children_helper(False, True, True)
+ self._test_reap_children_helper(True, False, False)
+ self._test_reap_children_helper(True, False, True)
+ self._test_reap_children_helper(True, True, False)
+ self._test_reap_children_helper(True, True, True)
+
+ # setup for more tests below
+ init = MockInit()
+ init.runnable = True
+ component = MockComponent('test', 53)
+ init.components[53] = component
+
+ # case where the returned pid is unknown to us. nothing should
+ # happpen then.
+ init.get_process_exit_status_called = False
+ init._get_process_exit_status = init._get_process_exit_status_unknown_pid
+ init.components_to_restart = []
+ # this should do nothing as the pid is unknown
+ init.reap_children()
+ self.assertEqual([], init.components_to_restart)
+
+ # case where init._get_process_exit_status() raises OSError with
+ # errno.ECHILD
+ init._get_process_exit_status = \
+ init._get_process_exit_status_raises_oserror_echild
+ init.components_to_restart = []
+ # this should catch and handle the OSError
+ init.reap_children()
+ self.assertEqual([], init.components_to_restart)
+
+ # case where init._get_process_exit_status() raises OSError with
+ # errno other than ECHILD
+ init._get_process_exit_status = \
+ init._get_process_exit_status_raises_oserror_other
+ with self.assertRaises(OSError):
+ init.reap_children()
+
+ # case where init._get_process_exit_status() raises something
+ # other than OSError
+ init._get_process_exit_status = \
+ init._get_process_exit_status_raises_other
+ with self.assertRaises(Exception):
+ init.reap_children()
+
+ def test_kill_started_components(self):
+ '''Test that started components are killed.'''
+ init = MockInit()
+
+ component = MockComponent('test', 53, 'Test')
+ init.components[53] = component
+
+ self.assertEqual([[53, 'test', 'Test']], init.get_processes())
+ init.kill_started_components()
+ self.assertEqual([], init.get_processes())
+ self.assertTrue(component.forceful)
+
+ def _start_msgq_helper(self, init, verbose):
+ init.verbose = verbose
+ pi = init.start_msgq()
+ self.assertEqual('b10-msgq', pi.name)
+ self.assertEqual(['b10-msgq'], pi.args)
+ self.assertTrue(pi.dev_null_stdout)
+ self.assertEqual(pi.dev_null_stderr, not verbose)
+ self.assertEqual({'FOO': 'an env string'}, pi.env)
+
+ # this is set by ProcessInfo.spawn()
+ self.assertEqual(42147, pi.pid)
+
+ def test_start_msgq(self):
+ '''Test that b10-msgq is started.'''
+ init = MockInitSimple()
+ init.c_channel_env = {'FOO': 'an env string'}
+ init._run_under_unittests = True
+
+ # use the MockProcessInfo creator
+ init._make_process_info = init._make_mock_process_info
+
+ # non-verbose case
+ self._start_msgq_helper(init, False)
+
+ # verbose case
+ self._start_msgq_helper(init, True)
+
+ def test_start_msgq_timeout(self):
+ '''Test that b10-msgq startup attempts connections several times
+ and times out eventually.'''
+ b10_init = MockInitSimple()
+ b10_init.c_channel_env = {}
+ # set the timeout to an arbitrary pre-determined value (which
+ # code below depends on)
+ b10_init.msgq_timeout = 1
+ b10_init._run_under_unittests = False
+
+ # use the MockProcessInfo creator
+ b10_init._make_process_info = b10_init._make_mock_process_info
+
+ global attempts
+ global tsec
+ attempts = 0
+ tsec = 0
+ self._tmp_time = time.time
+ self._tmp_sleep = time.sleep
+ def _my_time():
+ global attempts
+ global tsec
+ attempts += 1
+ return tsec
+ def _my_sleep(nsec):
+ global tsec
+ tsec += nsec
+ time.time = _my_time
+ time.sleep = _my_sleep
+
+ global cc_sub
+ cc_sub = None
+ class DummySessionAlwaysFails():
+ def __init__(self, socket_file):
+ raise isc.cc.session.SessionError('Connection fails')
+ def group_subscribe(self, s):
+ global cc_sub
+ cc_sub = s
+
+ isc.cc.Session = DummySessionAlwaysFails
+
+ with self.assertRaises(init.CChannelConnectError):
+ # An exception will be thrown here when it eventually times
+ # out.
+ pi = b10_init.start_msgq()
+
+ # time.time() should be called 12 times within the while loop:
+ # starting from 0, and 11 more times from 0.1 to 1.1. There's
+ # another call to time.time() outside the loop, which makes it
+ # 13.
+ self.assertEqual(attempts, 13)
+
+ # group_subscribe() should not have been called here.
+ self.assertIsNone(cc_sub)
+
+ global cc_socket_file
+ cc_socket_file = None
+ cc_sub = None
+ class DummySession():
+ def __init__(self, socket_file):
+ global cc_socket_file
+ cc_socket_file = socket_file
+ def group_subscribe(self, s):
+ global cc_sub
+ cc_sub = s
+
+ isc.cc.Session = DummySession
+
+ # reset values
+ attempts = 0
+ tsec = 0
+
+ pi = b10_init.start_msgq()
+
+ # just one attempt, but 2 calls to time.time()
+ self.assertEqual(attempts, 2)
+
+ self.assertEqual(cc_socket_file, b10_init.msgq_socket_file)
+ self.assertEqual(cc_sub, 'Init')
+
+ # isc.cc.Session, time.time() and time.sleep() are restored
+ # during tearDown().
+
+ def _start_cfgmgr_helper(self, init, data_path, filename, clear_config):
+ expect_args = ['b10-cfgmgr']
+ if data_path is not None:
+ init.data_path = data_path
+ expect_args.append('--data-path=' + data_path)
+ if filename is not None:
+ init.config_filename = filename
+ expect_args.append('--config-filename=' + filename)
+ if clear_config:
+ init.clear_config = clear_config
+ expect_args.append('--clear-config')
+
+ pi = init.start_cfgmgr()
+ self.assertEqual('b10-cfgmgr', pi.name)
+ self.assertEqual(expect_args, pi.args)
+ self.assertEqual({'TESTENV': 'A test string'}, pi.env)
+
+ # this is set by ProcessInfo.spawn()
+ self.assertEqual(42147, pi.pid)
+
+ def test_start_cfgmgr(self):
+ '''Test that b10-cfgmgr is started.'''
+ class DummySession():
+ def __init__(self):
+ self._tries = 0
+ def group_recvmsg(self):
+ self._tries += 1
+ # return running on the 3rd try onwards
+ if self._tries >= 3:
+ return ({'running': 'ConfigManager'}, None)
+ else:
+ return ({}, None)
+
+ init = MockInitSimple()
+ init.c_channel_env = {'TESTENV': 'A test string'}
+ init.cc_session = DummySession()
+ init.wait_time = 5
+
+ # use the MockProcessInfo creator
+ init._make_process_info = init._make_mock_process_info
+
+ global attempts
+ attempts = 0
+ self._tmp_sleep = time.sleep
+ def _my_sleep(nsec):
+ global attempts
+ attempts += 1
+ time.sleep = _my_sleep
+
+ # defaults
+ self._start_cfgmgr_helper(init, None, None, False)
+
+ # check that 2 attempts were made. on the 3rd attempt,
+ # process_running() returns that ConfigManager is running.
+ self.assertEqual(attempts, 2)
+
+ # data_path is specified
+ self._start_cfgmgr_helper(init, '/var/lib/test', None, False)
+
+ # config_filename is specified. Because `init` is not
+ # reconstructed, data_path is retained from the last call to
+ # _start_cfgmgr_helper().
+ self._start_cfgmgr_helper(init, '/var/lib/test', 'foo.cfg', False)
+
+ # clear_config is specified. Because `init` is not reconstructed,
+ # data_path and config_filename are retained from the last call
+ # to _start_cfgmgr_helper().
+ self._start_cfgmgr_helper(init, '/var/lib/test', 'foo.cfg', True)
+
+ def test_start_cfgmgr_timeout(self):
+ '''Test that b10-cfgmgr startup attempts connections several times
+ and times out eventually.'''
+ class DummySession():
+ def group_recvmsg(self):
+ return (None, None)
+ b10_init = MockInitSimple()
+ b10_init.c_channel_env = {}
+ b10_init.cc_session = DummySession()
+ # set wait_time to an arbitrary pre-determined value (which code
+ # below depends on)
+ b10_init.wait_time = 2
+
+ # use the MockProcessInfo creator
+ b10_init._make_process_info = b10_init._make_mock_process_info
+
+ global attempts
+ attempts = 0
+ self._tmp_sleep = time.sleep
+ def _my_sleep(nsec):
+ global attempts
+ attempts += 1
+ time.sleep = _my_sleep
+
+ # We just check that an exception was thrown, and that several
+ # attempts were made to connect.
+ with self.assertRaises(init.ProcessStartError):
+ pi = b10_init.start_cfgmgr()
+
+ # 2 seconds of attempts every 1 second should result in 2 attempts
+ self.assertEqual(attempts, 2)
+
+ # time.sleep() is restored during tearDown().
+
+ def test_start_ccsession(self):
+ '''Test that CC session is started.'''
+ class DummySession():
+ def __init__(self, specfile, config_handler, command_handler,
+ socket_file):
+ self.specfile = specfile
+ self.config_handler = config_handler
+ self.command_handler = command_handler
+ self.socket_file = socket_file
+ self.started = False
+ def start(self):
+ self.started = True
+ b10_init = MockInitSimple()
+ self._tmp_module_cc_session = isc.config.ModuleCCSession
+ isc.config.ModuleCCSession = DummySession
+
+ b10_init.start_ccsession({})
+ self.assertEqual(init.SPECFILE_LOCATION, b10_init.ccs.specfile)
+ self.assertEqual(b10_init.config_handler, b10_init.ccs.config_handler)
+ self.assertEqual(b10_init.command_handler,
+ b10_init.ccs.command_handler)
+ self.assertEqual(b10_init.msgq_socket_file, b10_init.ccs.socket_file)
+ self.assertTrue(b10_init.ccs.started)
+
+ # isc.config.ModuleCCSession is restored during tearDown().
+
+ def test_start_process(self):
+ '''Test that processes can be started.'''
+ init = MockInit()
+
+ # use the MockProcessInfo creator
+ init._make_process_info = init._make_mock_process_info
+
+ pi = init.start_process('Test Process', ['/bin/true'], {})
+ self.assertEqual('Test Process', pi.name)
+ self.assertEqual(['/bin/true'], pi.args)
+ self.assertEqual({}, pi.env)
+
+ # this is set by ProcessInfo.spawn()
+ self.assertEqual(42147, pi.pid)
+
+ def test_register_process(self):
+ '''Test that processes can be registered with Init.'''
+ init = MockInit()
+ component = MockComponent('test', 53, 'Test')
+
+ self.assertFalse(53 in init.components)
+ init.register_process(53, component)
+ self.assertTrue(53 in init.components)
+ self.assertEqual(init.components[53].name(), 'test')
+ self.assertEqual(init.components[53].pid(), 53)
+ self.assertEqual(init.components[53].address(), 'Test')
+
+ def _start_simple_helper(self, init, verbose):
+ init.verbose = verbose
+
+ args = ['/bin/true']
+ if verbose:
+ args.append('-v')
+
+ init.start_simple('/bin/true')
+ self.assertEqual('/bin/true', init.started_process_name)
+ self.assertEqual(args, init.started_process_args)
+ self.assertEqual({'TESTENV': 'A test string'}, init.started_process_env)
+
+ def test_start_simple(self):
+ '''Test simple process startup.'''
+ init = MockInitSimple()
+ init.c_channel_env = {'TESTENV': 'A test string'}
+
+ # non-verbose case
+ self._start_simple_helper(init, False)
+
+ # verbose case
+ self._start_simple_helper(init, True)
+
+ def _start_auth_helper(self, init, verbose):
+ init.verbose = verbose
+
+ args = ['b10-auth']
+ if verbose:
+ args.append('-v')
+
+ init.start_auth()
+ self.assertEqual('b10-auth', init.started_process_name)
+ self.assertEqual(args, init.started_process_args)
+ self.assertEqual({'FOO': 'an env string'}, init.started_process_env)
+
+ def test_start_auth(self):
+ '''Test that b10-auth is started.'''
+ init = MockInitSimple()
+ init.c_channel_env = {'FOO': 'an env string'}
+
+ # non-verbose case
+ self._start_auth_helper(init, False)
+
+ # verbose case
+ self._start_auth_helper(init, True)
+
+ def _start_resolver_helper(self, init, verbose):
+ init.verbose = verbose
+
+ args = ['b10-resolver']
+ if verbose:
+ args.append('-v')
+
+ init.start_resolver()
+ self.assertEqual('b10-resolver', init.started_process_name)
+ self.assertEqual(args, init.started_process_args)
+ self.assertEqual({'BAR': 'an env string'}, init.started_process_env)
+
+ def test_start_resolver(self):
+ '''Test that b10-resolver is started.'''
+ init = MockInitSimple()
+ init.c_channel_env = {'BAR': 'an env string'}
+
+ # non-verbose case
+ self._start_resolver_helper(init, False)
+
+ # verbose case
+ self._start_resolver_helper(init, True)
+
+ def _start_cmdctl_helper(self, init, verbose, port = None):
+ init.verbose = verbose
+
+ args = ['b10-cmdctl']
+
+ if port is not None:
+ init.cmdctl_port = port
+ args.append('--port=9353')
+
+ if verbose:
+ args.append('-v')
+
+ init.start_cmdctl()
+ self.assertEqual('b10-cmdctl', init.started_process_name)
+ self.assertEqual(args, init.started_process_args)
+ self.assertEqual({'BAZ': 'an env string'}, init.started_process_env)
+
+ def test_start_cmdctl(self):
+ '''Test that b10-cmdctl is started.'''
+ init = MockInitSimple()
+ init.c_channel_env = {'BAZ': 'an env string'}
+
+ # non-verbose case
+ self._start_cmdctl_helper(init, False)
+
+ # verbose case
+ self._start_cmdctl_helper(init, True)
+
+ # with port, non-verbose case
+ self._start_cmdctl_helper(init, False, 9353)
+
+ # with port, verbose case
+ self._start_cmdctl_helper(init, True, 9353)
+
+ def test_socket_data(self):
+ '''Test that Init._socket_data works as expected.'''
+ class MockSock:
+ def __init__(self, fd, throw):
+ self.fd = fd
+ self.throw = throw
+ self.buf = b'Hello World.\nYou are so nice today.\nXX'
+ self.i = 0
+
+ def recv(self, bufsize, flags = 0):
+ if bufsize != 1:
+ raise Exception('bufsize != 1')
+ if flags != socket.MSG_DONTWAIT:
+ raise Exception('flags != socket.MSG_DONTWAIT')
+ # after 15 recv()s, throw a socket.error with EAGAIN to
+ # get _socket_data() to save back what's been read. The
+ # number 15 is arbitrarily chosen, but the checks then
+ # depend on this being 15, i.e., if you adjust this
+ # number, you may have to adjust the checks below too.
+ if self.throw and self.i > 15:
+ raise socket.error(errno.EAGAIN, 'Try again')
+ if self.i >= len(self.buf):
+ return b'';
+ t = self.i
+ self.i += 1
+ return self.buf[t:t+1]
+
+ def close(self):
+ return
+
+ class MockInitSocketData(Init):
+ def __init__(self, throw):
+ self._unix_sockets = {42: (MockSock(42, throw), b'')}
+ self.requests = []
+ self.dead = []
+
+ def socket_request_handler(self, previous, sock):
+ self.requests.append({sock.fd: previous})
+
+ def socket_consumer_dead(self, sock):
+ self.dead.append(sock.fd)
+
+ # Case where we get data every time we call recv()
+ init = MockInitSocketData(False)
+ init._socket_data(42)
+ self.assertEqual(init.requests,
+ [{42: b'Hello World.'},
+ {42: b'You are so nice today.'}])
+ self.assertEqual(init.dead, [42])
+ self.assertEqual({}, init._unix_sockets)
+
+ # Case where socket.recv() raises EAGAIN. In this case, the
+ # routine is supposed to save what it has back to
+ # Init._unix_sockets.
+ init = MockInitSocketData(True)
+ init._socket_data(42)
+ self.assertEqual(init.requests, [{42: b'Hello World.'}])
+ self.assertFalse(init.dead)
+ self.assertEqual(len(init._unix_sockets), 1)
+ self.assertEqual(init._unix_sockets[42][1], b'You')
+
+ def test_startup(self):
+ '''Test that Init.startup() handles failures properly.'''
+ class MockInitStartup(Init):
+ def __init__(self, throw):
+ self.throw = throw
+ self.started = False
+ self.killed = False
+ self.msgq_socket_file = None
+ self.curproc = 'myproc'
+ self.runnable = False
+
+ def start_all_components(self):
+ self.started = True
+ if self.throw is True:
+ raise Exception('Assume starting components has failed.')
+ elif self.throw:
+ raise self.throw
+
+ def kill_started_components(self):
+ self.killed = True
+
+ class DummySession():
+ def __init__(self, socket_file):
+ raise isc.cc.session.SessionError('This is the expected case.')
+
+ class DummySessionSocketExists():
+ def __init__(self, socket_file):
+ # simulate that connect passes
+ return
+
+ isc.cc.Session = DummySession
+
+ # All is well case, where all components are started
+ # successfully. We check that the actual call to
+ # start_all_components() is made, and Init.runnable is true.
+ b10_init = MockInitStartup(False)
+ r = b10_init.startup()
+ self.assertIsNone(r)
+ self.assertTrue(b10_init.started)
+ self.assertFalse(b10_init.killed)
+ self.assertTrue(b10_init.runnable)
+ self.assertEqual({}, b10_init.c_channel_env)
+
+ # Case where starting components fails. We check that
+ # kill_started_components() is called right after, and
+ # Init.runnable is not modified.
+ b10_init = MockInitStartup(True)
+ r = b10_init.startup()
+ # r contains an error message
+ self.assertEqual(r, 'Unable to start myproc: Assume starting components has failed.')
+ self.assertTrue(b10_init.started)
+ self.assertTrue(b10_init.killed)
+ self.assertFalse(b10_init.runnable)
+ self.assertEqual({}, b10_init.c_channel_env)
+
+ # Check if msgq_socket_file is carried over
+ b10_init = MockInitStartup(False)
+ b10_init.msgq_socket_file = 'foo'
+ r = b10_init.startup()
+ self.assertEqual({'BIND10_MSGQ_SOCKET_FILE': 'foo'},
+ b10_init.c_channel_env)
+
+ # Check failure of changing user results in a different message
+ b10_init = MockInitStartup(init.ChangeUserError('failed to chusr'))
+ r = b10_init.startup()
+ self.assertIn('failed to chusr', r)
+ self.assertTrue(b10_init.killed)
+
+ # Check the case when socket file already exists
+ isc.cc.Session = DummySessionSocketExists
+ b10_init = MockInitStartup(False)
+ r = b10_init.startup()
+ self.assertIn('already running', r)
+
+ # isc.cc.Session is restored during tearDown().
+
+class SocketSrvTest(unittest.TestCase):
+ """
+ This tests some methods of b10-init related to the unix domain sockets
+ used to transfer other sockets to applications.
+ """
+ def setUp(self):
+ """
+ Create the b10-init to test, testdata and backup some functions.
+ """
+ self.__b10_init = Init()
+ self.__select_backup = init.select.select
+ self.__select_called = None
+ self.__socket_data_called = None
+ self.__consumer_dead_called = None
+ self.__socket_request_handler_called = None
+
+ def tearDown(self):
+ """
+ Restore functions.
+ """
+ init.select.select = self.__select_backup
+
+ class __FalseSocket:
+ """
+ A mock socket for the select and accept and stuff like that.
+ """
+ def __init__(self, owner, fileno=42):
+ self.__owner = owner
+ self.__fileno = fileno
+ self.data = None
+ self.closed = False
+
+ def fileno(self):
+ return self.__fileno
+
+ def accept(self):
+ return (self.__class__(self.__owner, 13), "/path/to/socket")
+
+ def recv(self, bufsize, flags=0):
+ self.__owner.assertEqual(1, bufsize)
+ self.__owner.assertEqual(socket.MSG_DONTWAIT, flags)
+ if isinstance(self.data, socket.error):
+ raise self.data
+ elif self.data is not None:
+ if len(self.data):
+ result = self.data[0:1]
+ self.data = self.data[1:]
+ return result
+ else:
+ raise socket.error(errno.EAGAIN, "Would block")
+ else:
+ return b''
+
+ def close(self):
+ self.closed = True
+
+ class __CCS:
+ """
+ A mock CCS, just to provide the socket file number.
+ """
+ class __Socket:
+ def fileno(self):
+ return 1
+ def get_socket(self):
+ return self.__Socket()
+
+ def __select_accept(self, r, w, x, t):
+ self.__select_called = (r, w, x, t)
+ return ([42], [], [])
+
+ def __select_data(self, r, w, x, t):
+ self.__select_called = (r, w, x, t)
+ return ([13], [], [])
+
+ def __accept(self):
+ """
+ Hijack the accept method of the b10-init.
+
+ Notes down it was called and stops b10-init.
+ """
+ self.__accept_called = True
+ self.__b10_init.runnable = False
+
+ def test_srv_accept_called(self):
+ """
+ Test that the _srv_accept method of b10-init is called when the
+ listening socket is readable.
+ """
+ self.__b10_init.runnable = True
+ self.__b10_init._srv_socket = self.__FalseSocket(self)
+ self.__b10_init._srv_accept = self.__accept
+ self.__b10_init.ccs = self.__CCS()
+ init.select.select = self.__select_accept
+ self.__b10_init.run(2)
+ # It called the accept
+ self.assertTrue(self.__accept_called)
+ # And the select had the right parameters
+ self.assertEqual(([2, 1, 42], [], [], None), self.__select_called)
+
+ def test_srv_accept(self):
+ """
+ Test how the _srv_accept method works.
+ """
+ self.__b10_init._srv_socket = self.__FalseSocket(self)
+ self.__b10_init._srv_accept()
+ # After we accepted, a new socket is added there
+ socket = self.__b10_init._unix_sockets[13][0]
+ # The socket is properly stored there
+ self.assertTrue(isinstance(socket, self.__FalseSocket))
+ # And the buffer (yet empty) is there
+ self.assertEqual({13: (socket, b'')}, self.__b10_init._unix_sockets)
+
+ def __socket_data(self, socket):
+ self.__b10_init.runnable = False
+ self.__socket_data_called = socket
+
+ def test_socket_data(self):
+ """
+ Test that a socket that wants attention gets it.
+ """
+ self.__b10_init._srv_socket = self.__FalseSocket(self)
+ self.__b10_init._socket_data = self.__socket_data
+ self.__b10_init.ccs = self.__CCS()
+ self.__b10_init._unix_sockets = {13: (self.__FalseSocket(self, 13), b'')}
+ self.__b10_init.runnable = True
+ init.select.select = self.__select_data
+ self.__b10_init.run(2)
+ self.assertEqual(13, self.__socket_data_called)
+ self.assertEqual(([2, 1, 42, 13], [], [], None), self.__select_called)
+
+ def __prepare_data(self, data):
+ socket = self.__FalseSocket(self, 13)
+ self.__b10_init._unix_sockets = {13: (socket, b'')}
+ socket.data = data
+ self.__b10_init.socket_consumer_dead = self.__consumer_dead
+ self.__b10_init.socket_request_handler = self.__socket_request_handler
+ return socket
+
+ def __consumer_dead(self, socket):
+ self.__consumer_dead_called = socket
+
+ def __socket_request_handler(self, token, socket):
+ self.__socket_request_handler_called = (token, socket)
+
+ def test_socket_closed(self):
+ """
+ Test that a socket is removed and the socket_consumer_dead is called
+ when it is closed.
+ """
+ socket = self.__prepare_data(None)
+ self.__b10_init._socket_data(13)
+ self.assertEqual(socket, self.__consumer_dead_called)
+ self.assertEqual({}, self.__b10_init._unix_sockets)
+ self.assertTrue(socket.closed)
+
+ def test_socket_short(self):
+ """
+ Test that if there's not enough data to get the whole socket, it is
+ kept there, but nothing is called.
+ """
+ socket = self.__prepare_data(b'tok')
+ self.__b10_init._socket_data(13)
+ self.assertEqual({13: (socket, b'tok')}, self.__b10_init._unix_sockets)
+ self.assertFalse(socket.closed)
+ self.assertIsNone(self.__consumer_dead_called)
+ self.assertIsNone(self.__socket_request_handler_called)
+
+ def test_socket_continue(self):
+ """
+ Test that we call the token handling function when the whole token
+ comes. This test pretends to continue reading where the previous one
+ stopped.
+ """
+ socket = self.__prepare_data(b"en\nanothe")
+ # The data to finish
+ self.__b10_init._unix_sockets[13] = (socket, b'tok')
+ self.__b10_init._socket_data(13)
+ self.assertEqual({13: (socket, b'anothe')}, self.__b10_init._unix_sockets)
+ self.assertFalse(socket.closed)
+ self.assertIsNone(self.__consumer_dead_called)
+ self.assertEqual((b'token', socket),
+ self.__socket_request_handler_called)
+
+ def test_broken_socket(self):
+ """
+ If the socket raises an exception during the read other than EAGAIN,
+ it is broken and we remove it.
+ """
+ sock = self.__prepare_data(socket.error(errno.ENOMEM,
+ "There's more memory available, but not for you"))
+ self.__b10_init._socket_data(13)
+ self.assertEqual(sock, self.__consumer_dead_called)
+ self.assertEqual({}, self.__b10_init._unix_sockets)
+ self.assertTrue(sock.closed)
+
+class TestFunctions(unittest.TestCase):
+ def setUp(self):
+ self.lockfile_testpath = \
+ "@abs_top_builddir@/src/bin/bind10/tests/lockfile_test"
+ self.assertFalse(os.path.exists(self.lockfile_testpath))
+ os.mkdir(self.lockfile_testpath)
+ self.assertTrue(os.path.isdir(self.lockfile_testpath))
+ self.__isfile_orig = init.os.path.isfile
+ self.__unlink_orig = init.os.unlink
+
+ def tearDown(self):
+ os.rmdir(self.lockfile_testpath)
+ self.assertFalse(os.path.isdir(self.lockfile_testpath))
+ os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = "@abs_top_builddir@"
+ init.os.path.isfile = self.__isfile_orig
+ init.os.unlink = self.__unlink_orig
+
+ def test_remove_lock_files(self):
+ os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = self.lockfile_testpath
+
+ # create lockfiles for the testcase
+ lockfiles = ["logger_lockfile"]
+ for f in lockfiles:
+ fname = os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] + '/' + f
+ self.assertFalse(os.path.exists(fname))
+ open(fname, "w").close()
+ self.assertTrue(os.path.isfile(fname))
+
+ # first call should clear up all the lockfiles
+ init.remove_lock_files()
+
+ # check if the lockfiles exist
+ for f in lockfiles:
+ fname = os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] + '/' + f
+ self.assertFalse(os.path.isfile(fname))
+
+ # second call should not assert anyway
+ init.remove_lock_files()
+
+ def test_remove_lock_files_fail(self):
+ # Permission error on unlink is ignored; other exceptions are really
+ # unexpected and propagated.
+ def __raising_unlink(unused, ex):
+ raise ex
+
+ init.os.path.isfile = lambda _: True
+ os_error = OSError()
+ init.os.unlink = lambda f: __raising_unlink(f, os_error)
+
+ os_error.errno = errno.EPERM
+ init.remove_lock_files() # no disruption
+
+ os_error.errno = errno.EACCES
+ init.remove_lock_files() # no disruption
+
+ os_error.errno = errno.ENOENT
+ self.assertRaises(OSError, init.remove_lock_files)
+
+ init.os.unlink = lambda f: __raising_unlink(f, Exception('bad'))
+ self.assertRaises(Exception, init.remove_lock_files)
+
+ def test_get_signame(self):
+ # just test with some samples
+ signame = init.get_signame(signal.SIGTERM)
+ self.assertEqual('SIGTERM', signame)
+ signame = init.get_signame(signal.SIGKILL)
+ self.assertEqual('SIGKILL', signame)
+ # 59426 is hopefully an unused signal on most platforms
+ signame = init.get_signame(59426)
+ self.assertEqual('Unknown signal 59426', signame)
+
+ def test_fatal_signal(self):
+ self.assertIsNone(init.b10_init)
+ init.b10_init = Init()
+ init.b10_init.runnable = True
+ init.fatal_signal(signal.SIGTERM, None)
+ # Now, runnable must be False
+ self.assertFalse(init.b10_init.runnable)
+ init.b10_init = None
+
+if __name__ == '__main__':
+ # store os.environ for test_unchanged_environment
+ original_os_environ = copy.deepcopy(os.environ)
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/bin/bind10/tests/init_test.py.in b/src/bin/bind10/tests/init_test.py.in
deleted file mode 100644
index 9a591ef..0000000
--- a/src/bin/bind10/tests/init_test.py.in
+++ /dev/null
@@ -1,2426 +0,0 @@
-# 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.
-
-# Most of the time, we omit the "init" for brevity. Sometimes,
-# we want to be explicit about what we do, like when hijacking a library
-# call used by the b10-init.
-from init import Init, ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
-import init
-
-# XXX: environment tests are currently disabled, due to the preprocessor
-# setup that we have now complicating the environment
-
-import unittest
-import sys
-import os
-import os.path
-import copy
-import signal
-import socket
-from isc.net.addr import IPAddr
-import time
-import isc.log
-import isc.config
-import isc.bind10.socket_cache
-import errno
-import random
-
-from isc.testutils.parse_args import TestOptParser, OptsError
-from isc.testutils.ccsession_mock import MockModuleCCSession
-
-class TestProcessInfo(unittest.TestCase):
- def setUp(self):
- # redirect stdout to a pipe so we can check that our
- # process spawning is doing the right thing with stdout
- self.old_stdout = os.dup(sys.stdout.fileno())
- self.pipes = os.pipe()
- os.dup2(self.pipes[1], sys.stdout.fileno())
- os.close(self.pipes[1])
- # note that we use dup2() to restore the original stdout
- # to the main program ASAP in each test... this prevents
- # hangs reading from the child process (as the pipe is only
- # open in the child), and also insures nice pretty output
-
- def tearDown(self):
- # clean up our stdout munging
- os.dup2(self.old_stdout, sys.stdout.fileno())
- os.close(self.pipes[0])
-
- def test_init(self):
- pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
- pi.spawn()
- os.dup2(self.old_stdout, sys.stdout.fileno())
- self.assertEqual(pi.name, 'Test Process')
- self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
-# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
-# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
- self.assertEqual(pi.dev_null_stdout, False)
- self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
- self.assertNotEqual(pi.process, None)
- self.assertTrue(type(pi.pid) is int)
-
-# def test_setting_env(self):
-# pi = ProcessInfo('Test Process', [ '/bin/true' ], env={'FOO': 'BAR'})
-# os.dup2(self.old_stdout, sys.stdout.fileno())
-# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
-# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'],
-# 'FOO': 'BAR' })
-
- def test_setting_null_stdout(self):
- pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ],
- dev_null_stdout=True)
- pi.spawn()
- os.dup2(self.old_stdout, sys.stdout.fileno())
- self.assertEqual(pi.dev_null_stdout, True)
- self.assertEqual(os.read(self.pipes[0], 100), b"")
-
- def test_respawn(self):
- pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
- pi.spawn()
- # wait for old process to work...
- self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
- # respawn it
- old_pid = pi.pid
- pi.respawn()
- os.dup2(self.old_stdout, sys.stdout.fileno())
- # make sure the new one started properly
- self.assertEqual(pi.name, 'Test Process')
- self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
-# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
-# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
- self.assertEqual(pi.dev_null_stdout, False)
- self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
- self.assertNotEqual(pi.process, None)
- self.assertTrue(type(pi.pid) is int)
- self.assertNotEqual(pi.pid, old_pid)
-
-class TestCacheCommands(unittest.TestCase):
- """
- Test methods of b10-init related to the socket cache and socket handling.
- """
- def setUp(self):
- """
- Prepare b10-init for some tests.
-
- Also prepare some variables we need.
- """
- self.__b10_init = Init()
- # Fake the cache here so we can pretend it is us and hijack the
- # calls to its methods.
- self.__b10_init._socket_cache = self
- self.__b10_init._socket_path = '/socket/path'
- self.__raise_exception = None
- self.__socket_args = {
- "port": 53,
- "address": "::",
- "protocol": "UDP",
- "share_mode": "ANY",
- "share_name": "app"
- }
- # What was and wasn't called.
- self.__drop_app_called = None
- self.__get_socket_called = None
- self.__send_fd_called = None
- self.__get_token_called = None
- self.__drop_socket_called = None
- init.libutil_io_python.send_fd = self.__send_fd
-
- def __send_fd(self, to, socket):
- """
- A function to hook the send_fd in the b10-init.
- """
- self.__send_fd_called = (to, socket)
-
- class FalseSocket:
- """
- A socket where we can fake methods we need instead of having a real
- socket.
- """
- def __init__(self):
- self.send = b""
- def fileno(self):
- """
- The file number. Used for identifying the remote application.
- """
- return 42
-
- def sendall(self, data):
- """
- Adds data to the self.send.
- """
- self.send += data
-
- def drop_application(self, application):
- """
- Part of pretending to be the cache. Logs the parameter to
- self.__drop_app_called.
-
- In the case self.__raise_exception is set, the exception there
- is raised instead.
- """
- if self.__raise_exception is not None:
- raise self.__raise_exception
- self.__drop_app_called = application
-
- def test_consumer_dead(self):
- """
- Test that it calls the drop_application method of the cache.
- """
- self.__b10_init.socket_consumer_dead(self.FalseSocket())
- self.assertEqual(42, self.__drop_app_called)
-
- def test_consumer_dead_invalid(self):
- """
- Test that it doesn't crash in case the application is not known to
- the cache, the b10_init doesn't crash, as this actually can happen in
- practice.
- """
- self.__raise_exception = ValueError("This application is unknown")
- # This doesn't crash
- self.__b10_init.socket_consumer_dead(self.FalseSocket())
-
- def get_socket(self, token, application):
- """
- Part of pretending to be the cache. If there's anything in
- __raise_exception, it is raised. Otherwise, the call is logged
- into __get_socket_called and a number is returned.
- """
- if self.__raise_exception is not None:
- raise self.__raise_exception
- self.__get_socket_called = (token, application)
- return 13
-
- def test_request_handler(self):
- """
- Test that a request for socket is forwarded and the socket is sent
- back, if it returns a socket.
- """
- socket = self.FalseSocket()
- # An exception from the cache
- self.__raise_exception = ValueError("Test value error")
- self.__b10_init.socket_request_handler(b"token", socket)
- # It was called, but it threw, so it is not noted here
- self.assertIsNone(self.__get_socket_called)
- self.assertEqual(b"0\n", socket.send)
- # It should not have sent any socket.
- self.assertIsNone(self.__send_fd_called)
- # Now prepare a valid scenario
- self.__raise_exception = None
- socket.send = b""
- self.__b10_init.socket_request_handler(b"token", socket)
- self.assertEqual(b"1\n", socket.send)
- self.assertEqual((42, 13), self.__send_fd_called)
- self.assertEqual(("token", 42), self.__get_socket_called)
-
- def get_token(self, protocol, address, port, share_mode, share_name):
- """
- Part of pretending to be the cache. If there's anything in
- __raise_exception, it is raised. Otherwise, the parameters are
- logged into __get_token_called and a token is returned.
- """
- if self.__raise_exception is not None:
- raise self.__raise_exception
- self.__get_token_called = (protocol, address, port, share_mode,
- share_name)
- return "token"
-
- def test_get_socket_ok(self):
- """
- Test the successful scenario of getting a socket.
- """
- result = self.__b10_init._get_socket(self.__socket_args)
- [code, answer] = result['result']
- self.assertEqual(0, code)
- self.assertEqual({
- 'token': 'token',
- 'path': '/socket/path'
- }, answer)
- addr = self.__get_token_called[1]
- self.assertTrue(isinstance(addr, IPAddr))
- self.assertEqual("::", str(addr))
- self.assertEqual(("UDP", addr, 53, "ANY", "app"),
- self.__get_token_called)
-
- def test_get_socket_error(self):
- """
- Test that bad inputs are handled correctly, etc.
- """
- def check_code(code, args):
- """
- Pass the args there and check if it returns success or not.
-
- The rest is not tested, as it is already checked in the
- test_get_socket_ok.
- """
- [rcode, ranswer] = self.__b10_init._get_socket(args)['result']
- self.assertEqual(code, rcode)
- if code != 0:
- # This should be an error message. The exact formatting
- # is unknown, but we check it is string at least
- self.assertTrue(isinstance(ranswer, str))
-
- def mod_args(name, value):
- """
- Override a parameter in the args.
- """
- result = dict(self.__socket_args)
- result[name] = value
- return result
-
- # Port too large
- check_code(1, mod_args('port', 65536))
- # Not numeric address
- check_code(1, mod_args('address', 'example.org.'))
- # Some bad values of enum-like params
- check_code(1, mod_args('protocol', 'BAD PROTO'))
- check_code(1, mod_args('share_mode', 'BAD SHARE'))
- # Check missing parameters
- for param in self.__socket_args.keys():
- args = dict(self.__socket_args)
- del args[param]
- check_code(1, args)
- # These are OK values for the enum-like parameters
- # The ones from test_get_socket_ok are not tested here
- check_code(0, mod_args('protocol', 'TCP'))
- check_code(0, mod_args('share_mode', 'SAMEAPP'))
- check_code(0, mod_args('share_mode', 'NO'))
- # If an exception is raised from within the cache, it is converted
- # to an error, not propagated
- self.__raise_exception = Exception("Test exception")
- check_code(1, self.__socket_args)
- # The special "expected" exceptions
- self.__raise_exception = \
- isc.bind10.socket_cache.ShareError("Not shared")
- check_code(3, self.__socket_args)
- self.__raise_exception = \
- isc.bind10.socket_cache.SocketError("Not shared", 13)
- check_code(2, self.__socket_args)
-
- def drop_socket(self, token):
- """
- Part of pretending to be the cache. If there's anything in
- __raise_exception, it is raised. Otherwise, the parameter is stored
- in __drop_socket_called.
- """
- if self.__raise_exception is not None:
- raise self.__raise_exception
- self.__drop_socket_called = token
-
- def test_drop_socket(self):
- """
- Check the drop_socket command. It should directly call the method
- on the cache. Exceptions should be translated to error messages.
- """
- # This should be OK and just propagated to the call.
- self.assertEqual({"result": [0]},
- self.__b10_init.command_handler("drop_socket",
- {"token": "token"}))
- self.assertEqual("token", self.__drop_socket_called)
- self.__drop_socket_called = None
- # Missing parameter
- self.assertEqual({"result": [1, "Missing token parameter"]},
- self.__b10_init.command_handler("drop_socket", {}))
- self.assertIsNone(self.__drop_socket_called)
- # An exception is raised from within the cache
- self.__raise_exception = ValueError("Test error")
- self.assertEqual({"result": [1, "Test error"]},
- self.__b10_init.command_handler("drop_socket",
- {"token": "token"}))
-
-
-class TestInit(unittest.TestCase):
- def setUp(self):
- # Save original values that may be tweaked in some tests
- self.__orig_setgid = init.posix.setgid
- self.__orig_setuid = init.posix.setuid
- self.__orig_logger_class = isc.log.Logger
-
- def tearDown(self):
- # Restore original values saved in setUp()
- init.posix.setgid = self.__orig_setgid
- init.posix.setuid = self.__orig_setuid
- isc.log.Logger = self.__orig_logger_class
-
- def test_init(self):
- b10_init = Init()
- self.assertEqual(b10_init.verbose, False)
- self.assertEqual(b10_init.msgq_socket_file, None)
- self.assertEqual(b10_init.cc_session, None)
- self.assertEqual(b10_init.ccs, None)
- self.assertEqual(b10_init.components, {})
- self.assertEqual(b10_init.runnable, False)
- self.assertEqual(b10_init.username, None)
- self.assertIsNone(b10_init._socket_cache)
-
- def __setgid(self, gid):
- self.__gid_set = gid
-
- def __setuid(self, uid):
- self.__uid_set = uid
-
- def test_change_user(self):
- init.posix.setgid = self.__setgid
- init.posix.setuid = self.__setuid
-
- self.__gid_set = None
- self.__uid_set = None
- b10_init = Init()
- b10_init.change_user()
- # No gid/uid set in init, nothing called.
- self.assertIsNone(self.__gid_set)
- self.assertIsNone(self.__uid_set)
-
- Init(setuid=42, setgid=4200).change_user()
- # This time, it get's called
- self.assertEqual(4200, self.__gid_set)
- self.assertEqual(42, self.__uid_set)
-
- def raising_set_xid(gid_or_uid):
- ex = OSError()
- ex.errno, ex.strerror = errno.EPERM, 'Operation not permitted'
- raise ex
-
- # Let setgid raise an exception
- init.posix.setgid = raising_set_xid
- init.posix.setuid = self.__setuid
- self.assertRaises(init.ChangeUserError,
- Init(setuid=42, setgid=4200).change_user)
-
- # Let setuid raise an exception
- init.posix.setgid = self.__setgid
- init.posix.setuid = raising_set_xid
- self.assertRaises(init.ChangeUserError,
- Init(setuid=42, setgid=4200).change_user)
-
- # Let initial log output after setuid raise an exception
- init.posix.setgid = self.__setgid
- init.posix.setuid = self.__setuid
- isc.log.Logger = raising_set_xid
- self.assertRaises(init.ChangeUserError,
- Init(setuid=42, setgid=4200).change_user)
-
- def test_set_creator(self):
- """
- Test the call to set_creator. First time, the cache is created
- with the passed creator. The next time, it throws an exception.
- """
- init = Init()
- # The cache doesn't use it at start, so just create an empty class
- class Creator: pass
- creator = Creator()
- init.set_creator(creator)
- self.assertTrue(isinstance(init._socket_cache,
- isc.bind10.socket_cache.Cache))
- self.assertEqual(creator, init._socket_cache._creator)
- self.assertRaises(ValueError, init.set_creator, creator)
-
- def test_socket_srv(self):
- """Tests init_socket_srv() and remove_socket_srv() work as expected."""
- init = Init()
-
- self.assertIsNone(init._srv_socket)
- self.assertIsNone(init._tmpdir)
- self.assertIsNone(init._socket_path)
-
- init.init_socket_srv()
-
- self.assertIsNotNone(init._srv_socket)
- self.assertNotEqual(-1, init._srv_socket.fileno())
- self.assertEqual(os.path.join(init._tmpdir, 'sockcreator'),
- init._srv_socket.getsockname())
-
- self.assertIsNotNone(init._tmpdir)
- self.assertTrue(os.path.isdir(init._tmpdir))
- self.assertIsNotNone(init._socket_path)
- self.assertTrue(os.path.exists(init._socket_path))
-
- # Check that it's possible to connect to the socket file (this
- # only works if the socket file exists and the server listens on
- # it).
- s = socket.socket(socket.AF_UNIX)
- try:
- s.connect(init._socket_path)
- can_connect = True
- s.close()
- except socket.error as e:
- can_connect = False
-
- self.assertTrue(can_connect)
-
- init.remove_socket_srv()
-
- self.assertEqual(-1, init._srv_socket.fileno())
- self.assertFalse(os.path.exists(init._socket_path))
- self.assertFalse(os.path.isdir(init._tmpdir))
-
- # These should not fail either:
-
- # second call
- init.remove_socket_srv()
-
- init._srv_socket = None
- init.remove_socket_srv()
-
- def test_init_alternate_socket(self):
- init = Init("alt_socket_file")
- self.assertEqual(init.verbose, False)
- self.assertEqual(init.msgq_socket_file, "alt_socket_file")
- self.assertEqual(init.cc_session, None)
- self.assertEqual(init.ccs, None)
- self.assertEqual(init.components, {})
- self.assertEqual(init.runnable, False)
- self.assertEqual(init.username, None)
-
- def test_command_handler(self):
- class DummySession():
- def group_sendmsg(self, msg, group):
- (self.msg, self.group) = (msg, group)
- def group_recvmsg(self, nonblock, seq): pass
- class DummyModuleCCSession():
- module_spec = isc.config.module_spec.ModuleSpec({
- "module_name": "Init",
- "statistics": [
- {
- "item_name": "boot_time",
- "item_type": "string",
- "item_optional": False,
- "item_default": "1970-01-01T00:00:00Z",
- "item_title": "Boot time",
- "item_description": "A date time when bind10 process starts initially",
- "item_format": "date-time"
- }
- ]
- })
- def get_module_spec(self):
- return self.module_spec
- init = Init()
- init.verbose = True
- init.cc_session = DummySession()
- init.ccs = DummyModuleCCSession()
- # a bad command
- self.assertEqual(init.command_handler(-1, None),
- isc.config.ccsession.create_answer(1, "bad command"))
- # "shutdown" command
- self.assertEqual(init.command_handler("shutdown", None),
- isc.config.ccsession.create_answer(0))
- self.assertFalse(init.runnable)
- # "getstats" command
- self.assertEqual(init.command_handler("getstats", None),
- isc.config.ccsession.create_answer(0,
- { 'boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME) }))
- # "ping" command
- self.assertEqual(init.command_handler("ping", None),
- isc.config.ccsession.create_answer(0, "pong"))
- # "show_processes" command
- self.assertEqual(init.command_handler("show_processes", None),
- isc.config.ccsession.create_answer(0,
- init.get_processes()))
- # an unknown command
- self.assertEqual(init.command_handler("__UNKNOWN__", None),
- isc.config.ccsession.create_answer(1, "Unknown command"))
-
- # Fake the get_token of cache and test the command works
- init._socket_path = '/socket/path'
- class cache:
- def get_token(self, protocol, addr, port, share_mode, share_name):
- return str(addr) + ':' + str(port)
- init._socket_cache = cache()
- args = {
- "port": 53,
- "address": "0.0.0.0",
- "protocol": "UDP",
- "share_mode": "ANY",
- "share_name": "app"
- }
- # at all and this is the easiest way to check.
- self.assertEqual({'result': [0, {'token': '0.0.0.0:53',
- 'path': '/socket/path'}]},
- init.command_handler("get_socket", args))
- # The drop_socket is not tested here, but in TestCacheCommands.
- # It needs the cache mocks to be in place and they are there.
-
- def test_stop_process(self):
- """
- Test checking the stop_process method sends the right message over
- the message bus.
- """
- class DummySession():
- def group_sendmsg(self, msg, group, instance="*"):
- (self.msg, self.group, self.instance) = (msg, group, instance)
- init = Init()
- init.cc_session = DummySession()
- init.stop_process('process', 'address', 42)
- self.assertEqual('address', init.cc_session.group)
- self.assertEqual('address', init.cc_session.instance)
- self.assertEqual({'command': ['shutdown', {'pid': 42}]},
- init.cc_session.msg)
-
-# Mock class for testing Init's usage of ProcessInfo
-class MockProcessInfo:
- def __init__(self, name, args, env={}, dev_null_stdout=False,
- dev_null_stderr=False):
- self.name = name
- self.args = args
- self.env = env
- self.dev_null_stdout = dev_null_stdout
- self.dev_null_stderr = dev_null_stderr
- self.process = None
- self.pid = None
-
- def spawn(self):
- # set some pid (only used for testing that it is not None anymore)
- self.pid = 42147
-
-# Class for testing the Init without actually starting processes.
-# This is used for testing the start/stop components routines and
-# the Init commands.
-#
-# Testing that external processes start is outside the scope
-# of the unit test, by overriding the process start methods we can check
-# that the right processes are started depending on the configuration
-# options.
-class MockInit(Init):
- def __init__(self):
- Init.__init__(self)
-
- # Set flags as to which of the overridden methods has been run.
- self.msgq = False
- self.cfgmgr = False
- self.ccsession = False
- self.auth = False
- self.resolver = False
- self.xfrout = False
- self.xfrin = False
- self.zonemgr = False
- self.stats = False
- self.stats_httpd = False
- self.cmdctl = False
- self.dhcp6 = False
- self.dhcp4 = False
- self.c_channel_env = {}
- self.components = { }
- self.creator = False
- self.get_process_exit_status_called = False
-
- class MockSockCreator(isc.bind10.component.Component):
- def __init__(self, process, b10_init, kind, address=None,
- params=None):
- isc.bind10.component.Component.__init__(self, process,
- b10_init, kind,
- 'SockCreator')
- self._start_func = b10_init.start_creator
-
- specials = isc.bind10.special_component.get_specials()
- specials['sockcreator'] = MockSockCreator
- self._component_configurator = \
- isc.bind10.component.Configurator(self, specials)
-
- def start_creator(self):
- self.creator = True
- procinfo = ProcessInfo('b10-sockcreator', ['/bin/false'])
- procinfo.pid = 1
- return procinfo
-
- def _read_bind10_config(self):
- # Configuration options are set directly
- pass
-
- def start_msgq(self):
- self.msgq = True
- procinfo = ProcessInfo('b10-msgq', ['/bin/false'])
- procinfo.pid = 2
- return procinfo
-
- def start_ccsession(self, c_channel_env):
- # this is not a process, don't have to do anything with procinfo
- self.ccsession = True
-
- def start_cfgmgr(self):
- self.cfgmgr = True
- procinfo = ProcessInfo('b10-cfgmgr', ['/bin/false'])
- procinfo.pid = 3
- return procinfo
-
- def start_auth(self):
- self.auth = True
- procinfo = ProcessInfo('b10-auth', ['/bin/false'])
- procinfo.pid = 5
- return procinfo
-
- def start_resolver(self):
- self.resolver = True
- procinfo = ProcessInfo('b10-resolver', ['/bin/false'])
- procinfo.pid = 6
- return procinfo
-
- def start_simple(self, name):
- procmap = { 'b10-zonemgr': self.start_zonemgr,
- 'b10-stats': self.start_stats,
- 'b10-stats-httpd': self.start_stats_httpd,
- 'b10-cmdctl': self.start_cmdctl,
- 'b10-dhcp6': self.start_dhcp6,
- 'b10-dhcp4': self.start_dhcp4,
- 'b10-xfrin': self.start_xfrin,
- 'b10-xfrout': self.start_xfrout }
- return procmap[name]()
-
- def start_xfrout(self):
- self.xfrout = True
- procinfo = ProcessInfo('b10-xfrout', ['/bin/false'])
- procinfo.pid = 7
- return procinfo
-
- def start_xfrin(self):
- self.xfrin = True
- procinfo = ProcessInfo('b10-xfrin', ['/bin/false'])
- procinfo.pid = 8
- return procinfo
-
- def start_zonemgr(self):
- self.zonemgr = True
- procinfo = ProcessInfo('b10-zonemgr', ['/bin/false'])
- procinfo.pid = 9
- return procinfo
-
- def start_stats(self):
- self.stats = True
- procinfo = ProcessInfo('b10-stats', ['/bin/false'])
- procinfo.pid = 10
- return procinfo
-
- def start_stats_httpd(self):
- self.stats_httpd = True
- procinfo = ProcessInfo('b10-stats-httpd', ['/bin/false'])
- procinfo.pid = 11
- return procinfo
-
- def start_cmdctl(self):
- self.cmdctl = True
- procinfo = ProcessInfo('b10-cmdctl', ['/bin/false'])
- procinfo.pid = 12
- return procinfo
-
- def start_dhcp6(self):
- self.dhcp6 = True
- procinfo = ProcessInfo('b10-dhcp6', ['/bin/false'])
- procinfo.pid = 13
- return procinfo
-
- def start_dhcp4(self):
- self.dhcp4 = True
- procinfo = ProcessInfo('b10-dhcp4', ['/bin/false'])
- procinfo.pid = 14
- return procinfo
-
- def stop_process(self, process, recipient, pid):
- procmap = { 'b10-auth': self.stop_auth,
- 'b10-resolver': self.stop_resolver,
- 'b10-xfrout': self.stop_xfrout,
- 'b10-xfrin': self.stop_xfrin,
- 'b10-zonemgr': self.stop_zonemgr,
- 'b10-stats': self.stop_stats,
- 'b10-stats-httpd': self.stop_stats_httpd,
- 'b10-cmdctl': self.stop_cmdctl }
- procmap[process]()
-
- # Some functions to pretend we stop processes, use by stop_process
- def stop_msgq(self):
- if self.msgq:
- del self.components[2]
- self.msgq = False
-
- def stop_cfgmgr(self):
- if self.cfgmgr:
- del self.components[3]
- self.cfgmgr = False
-
- def stop_auth(self):
- if self.auth:
- del self.components[5]
- self.auth = False
-
- def stop_resolver(self):
- if self.resolver:
- del self.components[6]
- self.resolver = False
-
- def stop_xfrout(self):
- if self.xfrout:
- del self.components[7]
- self.xfrout = False
-
- def stop_xfrin(self):
- if self.xfrin:
- del self.components[8]
- self.xfrin = False
-
- def stop_zonemgr(self):
- if self.zonemgr:
- del self.components[9]
- self.zonemgr = False
-
- def stop_stats(self):
- if self.stats:
- del self.components[10]
- self.stats = False
-
- def stop_stats_httpd(self):
- if self.stats_httpd:
- del self.components[11]
- self.stats_httpd = False
-
- def stop_cmdctl(self):
- if self.cmdctl:
- del self.components[12]
- self.cmdctl = False
-
- def _get_process_exit_status(self):
- if self.get_process_exit_status_called:
- return (0, 0)
- self.get_process_exit_status_called = True
- return (53, 0)
-
- def _get_process_exit_status_unknown_pid(self):
- if self.get_process_exit_status_called:
- return (0, 0)
- self.get_process_exit_status_called = True
- return (42, 0)
-
- def _get_process_exit_status_raises_oserror_echild(self):
- raise OSError(errno.ECHILD, 'Mock error')
-
- def _get_process_exit_status_raises_oserror_other(self):
- raise OSError(0, 'Mock error')
-
- def _get_process_exit_status_raises_other(self):
- raise Exception('Mock error')
-
- def _make_mock_process_info(self, name, args, c_channel_env,
- dev_null_stdout=False, dev_null_stderr=False):
- return MockProcessInfo(name, args, c_channel_env,
- dev_null_stdout, dev_null_stderr)
-
-class MockInitSimple(Init):
- def __init__(self):
- Init.__init__(self)
- # Set which process has been started
- self.started_process_name = None
- self.started_process_args = None
- self.started_process_env = None
-
- def _make_mock_process_info(self, name, args, c_channel_env,
- dev_null_stdout=False, dev_null_stderr=False):
- return MockProcessInfo(name, args, c_channel_env,
- dev_null_stdout, dev_null_stderr)
-
- def start_process(self, name, args, c_channel_env, port=None,
- address=None):
- self.started_process_name = name
- self.started_process_args = args
- self.started_process_env = c_channel_env
- return None
-
-class TestStartStopProcessesInit(unittest.TestCase):
- """
- Check that the start_all_components method starts the right combination
- of components and that the right components are started and stopped
- according to changes in configuration.
- """
- def check_environment_unchanged(self):
- # Check whether the environment has not been changed
- self.assertEqual(original_os_environ, os.environ)
-
- def check_started(self, init, core, auth, resolver):
- """
- Check that the right sets of services are started. The ones that
- should be running are specified by the core, auth and resolver parameters
- (they are groups of processes, eg. auth means b10-auth, -xfrout, -xfrin
- and -zonemgr).
- """
- self.assertEqual(init.msgq, core)
- self.assertEqual(init.cfgmgr, core)
- self.assertEqual(init.ccsession, core)
- self.assertEqual(init.creator, core)
- self.assertEqual(init.auth, auth)
- self.assertEqual(init.resolver, resolver)
- self.assertEqual(init.xfrout, auth)
- self.assertEqual(init.xfrin, auth)
- self.assertEqual(init.zonemgr, auth)
- self.assertEqual(init.stats, core)
- self.assertEqual(init.stats_httpd, core)
- self.assertEqual(init.cmdctl, core)
- self.check_environment_unchanged()
-
- def check_preconditions(self, init):
- self.check_started(init, False, False, False)
-
- def check_started_none(self, init):
- """
- Check that the situation is according to configuration where no servers
- should be started. Some components still need to be running.
- """
- self.check_started(init, True, False, False)
- self.check_environment_unchanged()
-
- def check_started_both(self, init):
- """
- Check the situation is according to configuration where both servers
- (auth and resolver) are enabled.
- """
- self.check_started(init, True, True, True)
- self.check_environment_unchanged()
-
- def check_started_auth(self, init):
- """
- Check the set of components needed to run auth only is started.
- """
- self.check_started(init, True, True, False)
- self.check_environment_unchanged()
-
- def check_started_resolver(self, init):
- """
- Check the set of components needed to run resolver only is started.
- """
- self.check_started(init, True, False, True)
- self.check_environment_unchanged()
-
- def check_started_dhcp(self, init, v4, v6):
- """
- Check if proper combinations of DHCPv4 and DHCpv6 can be started
- """
- self.assertEqual(v4, init.dhcp4)
- self.assertEqual(v6, init.dhcp6)
- self.check_environment_unchanged()
-
- def construct_config(self, start_auth, start_resolver):
- # The things that are common, not turned on an off
- config = {}
- config['b10-stats'] = { 'kind': 'dispensable', 'address': 'Stats' }
- config['b10-stats-httpd'] = { 'kind': 'dispensable',
- 'address': 'StatsHttpd' }
- config['b10-cmdctl'] = { 'kind': 'needed', 'special': 'cmdctl' }
- if start_auth:
- config['b10-auth'] = { 'kind': 'needed', 'special': 'auth' }
- config['b10-xfrout'] = { 'kind': 'dispensable',
- 'address': 'Xfrout' }
- config['b10-xfrin'] = { 'kind': 'dispensable',
- 'address': 'Xfrin' }
- config['b10-zonemgr'] = { 'kind': 'dispensable',
- 'address': 'Zonemgr' }
- if start_resolver:
- config['b10-resolver'] = { 'kind': 'needed',
- 'special': 'resolver' }
- return {'components': config}
-
- def config_start_init(self, start_auth, start_resolver):
- """
- Test the configuration is loaded at the startup.
- """
- init = MockInit()
- config = self.construct_config(start_auth, start_resolver)
- class CC:
- def get_full_config(self):
- return config
- # Provide the fake CC with data
- init.ccs = CC()
- # And make sure it's not overwritten
- def start_ccsession():
- init.ccsession = True
- init.start_ccsession = lambda _: start_ccsession()
- # We need to return the original _read_bind10_config
- init._read_bind10_config = lambda: Init._read_bind10_config(init)
- init.start_all_components()
- self.check_started(init, True, start_auth, start_resolver)
- self.check_environment_unchanged()
-
- def test_start_none(self):
- self.config_start_init(False, False)
-
- def test_start_resolver(self):
- self.config_start_init(False, True)
-
- def test_start_auth(self):
- self.config_start_init(True, False)
-
- def test_start_both(self):
- self.config_start_init(True, True)
-
- def test_config_start(self):
- """
- Test that the configuration starts and stops components according
- to configuration changes.
- """
-
- # Create Init and ensure correct initialization
- init = MockInit()
- self.check_preconditions(init)
-
- init.start_all_components()
- init.runnable = True
- init.config_handler(self.construct_config(False, False))
- self.check_started_none(init)
-
- # Enable both at once
- init.config_handler(self.construct_config(True, True))
- self.check_started_both(init)
-
- # Not touched by empty change
- init.config_handler({})
- self.check_started_both(init)
-
- # Not touched by change to the same configuration
- init.config_handler(self.construct_config(True, True))
- self.check_started_both(init)
-
- # Turn them both off again
- init.config_handler(self.construct_config(False, False))
- self.check_started_none(init)
-
- # Not touched by empty change
- init.config_handler({})
- self.check_started_none(init)
-
- # Not touched by change to the same configuration
- init.config_handler(self.construct_config(False, False))
- self.check_started_none(init)
-
- # Start and stop auth separately
- init.config_handler(self.construct_config(True, False))
- self.check_started_auth(init)
-
- init.config_handler(self.construct_config(False, False))
- self.check_started_none(init)
-
- # Start and stop resolver separately
- init.config_handler(self.construct_config(False, True))
- self.check_started_resolver(init)
-
- init.config_handler(self.construct_config(False, False))
- self.check_started_none(init)
-
- # Alternate
- init.config_handler(self.construct_config(True, False))
- self.check_started_auth(init)
-
- init.config_handler(self.construct_config(False, True))
- self.check_started_resolver(init)
-
- init.config_handler(self.construct_config(True, False))
- self.check_started_auth(init)
-
- def test_config_start_once(self):
- """
- Tests that a component is started only once.
- """
- # Create Init and ensure correct initialization
- init = MockInit()
- self.check_preconditions(init)
-
- init.start_all_components()
-
- init.runnable = True
- init.config_handler(self.construct_config(True, True))
- self.check_started_both(init)
-
- init.start_auth = lambda: self.fail("Started auth again")
- init.start_xfrout = lambda: self.fail("Started xfrout again")
- init.start_xfrin = lambda: self.fail("Started xfrin again")
- init.start_zonemgr = lambda: self.fail("Started zonemgr again")
- init.start_resolver = lambda: self.fail("Started resolver again")
-
- # Send again we want to start them. Should not do it, as they are.
- init.config_handler(self.construct_config(True, True))
-
- def test_config_not_started_early(self):
- """
- Test that components are not started by the config handler before
- startup.
- """
- init = MockInit()
- self.check_preconditions(init)
-
- init.start_auth = lambda: self.fail("Started auth again")
- init.start_xfrout = lambda: self.fail("Started xfrout again")
- init.start_xfrin = lambda: self.fail("Started xfrin again")
- init.start_zonemgr = lambda: self.fail("Started zonemgr again")
- init.start_resolver = lambda: self.fail("Started resolver again")
-
- init.config_handler({'start_auth': True, 'start_resolver': True})
-
- # Checks that DHCP (v4 and v6) components are started when expected
- def test_start_dhcp(self):
-
- # Create Init and ensure correct initialization
- init = MockInit()
- self.check_preconditions(init)
-
- init.start_all_components()
- init.config_handler(self.construct_config(False, False))
- self.check_started_dhcp(init, False, False)
-
- def test_start_dhcp_v6only(self):
- # Create Init and ensure correct initialization
- init = MockInit()
- self.check_preconditions(init)
- # v6 only enabled
- init.start_all_components()
- init.runnable = True
- init._Init_started = True
- config = self.construct_config(False, False)
- config['components']['b10-dhcp6'] = { 'kind': 'needed',
- 'address': 'Dhcp6' }
- init.config_handler(config)
- self.check_started_dhcp(init, False, True)
-
- # uncomment when dhcpv4 becomes implemented
- # v4 only enabled
- #init.cfg_start_dhcp6 = False
- #init.cfg_start_dhcp4 = True
- #self.check_started_dhcp(init, True, False)
-
- # both v4 and v6 enabled
- #init.cfg_start_dhcp6 = True
- #init.cfg_start_dhcp4 = True
- #self.check_started_dhcp(init, True, True)
-
-class MockComponent:
- def __init__(self, name, pid, address=None):
- self.name = lambda: name
- self.pid = lambda: pid
- self.address = lambda: address
- self.restarted = False
- self.forceful = False
- self.running = True
- self.has_failed = False
-
- def get_restart_time(self):
- return 0 # arbitrary dummy value
-
- def restart(self, now):
- self.restarted = True
- return True
-
- def is_running(self):
- return self.running
-
- def failed(self, status):
- return self.has_failed
-
- def kill(self, forceful):
- self.forceful = forceful
-
-class TestInitCmd(unittest.TestCase):
- def test_ping(self):
- """
- Confirm simple ping command works.
- """
- init = MockInit()
- answer = init.command_handler("ping", None)
- self.assertEqual(answer, {'result': [0, 'pong']})
-
- def test_show_processes_empty(self):
- """
- Confirm getting a list of processes works.
- """
- init = MockInit()
- answer = init.command_handler("show_processes", None)
- self.assertEqual(answer, {'result': [0, []]})
-
- def test_show_processes(self):
- """
- Confirm getting a list of processes works.
- """
- init = MockInit()
- init.register_process(1, MockComponent('first', 1))
- init.register_process(2, MockComponent('second', 2, 'Second'))
- answer = init.command_handler("show_processes", None)
- processes = [[1, 'first', None],
- [2, 'second', 'Second']]
- self.assertEqual(answer, {'result': [0, processes]})
-
-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_clear_config(self):
- options = parse_args([], TestOptParser)
- self.assertEqual(False, options.clear_config)
- options = parse_args(['--clear-config'], TestOptParser)
- self.assertEqual(True, options.clear_config)
-
- def test_nokill(self):
- options = parse_args([], TestOptParser)
- self.assertEqual(False, options.nokill)
- options = parse_args(['--no-kill'], TestOptParser)
- self.assertEqual(True, options.nokill)
- options = parse_args([], TestOptParser)
- self.assertEqual(False, options.nokill)
- options = parse_args(['-i'], TestOptParser)
- self.assertEqual(True, options.nokill)
-
- 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'
- if os.path.exists(self.pid_file):
- os.unlink(self.pid_file)
-
- def tearDown(self):
- if os.path.exists(self.pid_file):
- os.unlink(self.pid_file)
-
- def check_pid_file(self):
- # dump PID to the file, and confirm the content is correct
- dump_pid(self.pid_file)
- my_pid = os.getpid()
- with open(self.pid_file, "r") as f:
- self.assertEqual(my_pid, int(f.read()))
-
- def test_dump_pid(self):
- self.check_pid_file()
-
- # make sure any existing content will be removed
- with open(self.pid_file, "w") as f:
- f.write('dummy data\n')
- self.check_pid_file()
-
- def test_unlink_pid_file_notexist(self):
- dummy_data = 'dummy_data\n'
-
- with open(self.pid_file, "w") as f:
- f.write(dummy_data)
-
- unlink_pid_file("no_such_pid_file")
-
- # the file specified for unlink_pid_file doesn't exist,
- # and the original content of the file should be intact.
- with open(self.pid_file, "r") as f:
- self.assertEqual(dummy_data, f.read())
-
- def test_dump_pid_with_none(self):
- # Check the behavior of dump_pid() and unlink_pid_file() with None.
- # This should be no-op.
- dump_pid(None)
- self.assertFalse(os.path.exists(self.pid_file))
-
- dummy_data = 'dummy_data\n'
-
- with open(self.pid_file, "w") as f:
- f.write(dummy_data)
-
- unlink_pid_file(None)
-
- with open(self.pid_file, "r") as f:
- self.assertEqual(dummy_data, f.read())
-
- def test_dump_pid_failure(self):
- # the attempt to open file will fail, which should result in exception.
- self.assertRaises(IOError, dump_pid,
- 'nonexistent_dir' + os.sep + 'bind10.pid')
-
-class TestInitComponents(unittest.TestCase):
- """
- Test b10-init propagates component configuration properly to the
- component configurator and acts sane.
- """
- def setUp(self):
- self.__param = None
- self.__called = False
- self.__compconfig = {
- 'comp': {
- 'kind': 'needed',
- 'process': 'cat'
- }
- }
- self._tmp_time = None
- self._tmp_sleep = None
- self._tmp_module_cc_session = None
- self._tmp_cc_session = None
-
- def tearDown(self):
- if self._tmp_time is not None:
- time.time = self._tmp_time
- if self._tmp_sleep is not None:
- time.sleep = self._tmp_sleep
- if self._tmp_module_cc_session is not None:
- isc.config.ModuleCCSession = self._tmp_module_cc_session
- if self._tmp_cc_session is not None:
- isc.cc.Session = self._tmp_cc_session
-
- def __unary_hook(self, param):
- """
- A hook function that stores the parameter for later examination.
- """
- self.__param = param
-
- def __nullary_hook(self):
- """
- A hook function that notes down it was called.
- """
- self.__called = True
-
- def __check_core(self, config):
- """
- A function checking that the config contains parts for the valid
- core component configuration.
- """
- self.assertIsNotNone(config)
- for component in ['sockcreator', 'msgq', 'cfgmgr']:
- self.assertTrue(component in config)
- self.assertEqual(component, config[component]['special'])
- self.assertEqual('core', config[component]['kind'])
-
- def __check_extended(self, config):
- """
- This checks that the config contains the core and one more component.
- """
- self.__check_core(config)
- self.assertTrue('comp' in config)
- self.assertEqual('cat', config['comp']['process'])
- self.assertEqual('needed', config['comp']['kind'])
- self.assertEqual(4, len(config))
-
- def test_correct_run(self):
- """
- Test the situation when we run in usual scenario, nothing fails,
- we just start, reconfigure and then stop peacefully.
- """
- init = MockInit()
- # Start it
- orig = init._component_configurator.startup
- init._component_configurator.startup = self.__unary_hook
- init.start_all_components()
- init._component_configurator.startup = orig
- self.__check_core(self.__param)
- self.assertEqual(3, len(self.__param))
-
- # Reconfigure it
- self.__param = None
- orig = init._component_configurator.reconfigure
- init._component_configurator.reconfigure = self.__unary_hook
- # Otherwise it does not work
- init.runnable = True
- init.config_handler({'components': self.__compconfig})
- self.__check_extended(self.__param)
- currconfig = self.__param
- # If we reconfigure it, but it does not contain the components part,
- # nothing is called
- init.config_handler({})
- self.assertEqual(self.__param, currconfig)
- self.__param = None
- init._component_configurator.reconfigure = orig
- # Check a configuration that messes up the core components is rejected.
- compconf = dict(self.__compconfig)
- compconf['msgq'] = { 'process': 'echo' }
- result = init.config_handler({'components': compconf})
- # Check it rejected it
- self.assertEqual(1, result['result'][0])
-
- # We can't call shutdown, that one relies on the stuff in main
- # We check somewhere else that the shutdown is actually called
- # from there (the test_kills).
-
- def __real_test_kill(self, nokill=False, ex_on_kill=None):
- """
- Helper function that does the actual kill functionality testing.
- """
- init = MockInit()
- init.nokill = nokill
-
- killed = []
- class ImmortalComponent:
- """
- An immortal component. It does not stop when it is told so
- (anyway it is not told so). It does not die if it is killed
- the first time. It dies only when killed forcefully.
- """
- def __init__(self):
- # number of kill() calls, preventing infinite loop.
- self.__call_count = 0
-
- def kill(self, forceful=False):
- self.__call_count += 1
- if self.__call_count > 2:
- raise Exception('Too many calls to ImmortalComponent.kill')
-
- killed.append(forceful)
- if ex_on_kill is not None:
- # If exception is given by the test, raise it here.
- # In the case of ESRCH, the process should have gone
- # somehow, so we clear the components.
- if ex_on_kill.errno == errno.ESRCH:
- init.components = {}
- raise ex_on_kill
- if forceful:
- init.components = {}
- def pid(self):
- return 1
- def name(self):
- return "Immortal"
- init.components = {}
- init.register_process(1, ImmortalComponent())
-
- # While at it, we check the configurator shutdown is actually called
- orig = init._component_configurator.shutdown
- init._component_configurator.shutdown = self.__nullary_hook
- self.__called = False
-
- init.ccs = MockModuleCCSession()
- self.assertFalse(init.ccs.stopped)
-
- init.shutdown()
-
- self.assertTrue(init.ccs.stopped)
-
- # Here, killed is an array where False is added if SIGTERM
- # should be sent, or True if SIGKILL should be sent, in order in
- # which they're sent.
- if nokill:
- self.assertEqual([], killed)
- else:
- if ex_on_kill is not None:
- self.assertEqual([False], killed)
- else:
- self.assertEqual([False, True], killed)
-
- self.assertTrue(self.__called)
-
- init._component_configurator.shutdown = orig
-
- def test_kills(self):
- """
- Test that b10-init kills components which don't want to stop.
- """
- self.__real_test_kill()
-
- def test_kill_fail(self):
- """Test cases where kill() results in an exception due to OS error.
-
- The behavior should be different for EPERM, so we test two cases.
-
- """
-
- ex = OSError()
- ex.errno, ex.strerror = errno.ESRCH, 'No such process'
- self.__real_test_kill(ex_on_kill=ex)
-
- ex.errno, ex.strerror = errno.EPERM, 'Operation not permitted'
- self.__real_test_kill(ex_on_kill=ex)
-
- def test_nokill(self):
- """
- Test that b10-init *doesn't* kill components which don't want to
- stop, when asked not to (by passing the --no-kill option which
- sets init.nokill to True).
- """
- self.__real_test_kill(True)
-
- def test_component_shutdown(self):
- """
- Test the component_shutdown sets all variables accordingly.
- """
- init = MockInit()
- self.assertRaises(Exception, init.component_shutdown, 1)
- self.assertEqual(1, init.exitcode)
- init._Init__started = True
- init.component_shutdown(2)
- self.assertEqual(2, init.exitcode)
- self.assertFalse(init.runnable)
-
- def test_init_config(self):
- """
- Test initial configuration is loaded.
- """
- init = MockInit()
- # Start it
- init._component_configurator.reconfigure = self.__unary_hook
- # We need to return the original read_bind10_config
- init._read_bind10_config = lambda: Init._read_bind10_config(init)
- # And provide a session to read the data from
- class CC:
- pass
- init.ccs = CC()
- init.ccs.get_full_config = lambda: {'components': self.__compconfig}
- init.start_all_components()
- self.__check_extended(self.__param)
-
- def __setup_restart(self, init, component):
- '''Common procedure for restarting a component used below.'''
- init.components_to_restart = { component }
- component.restarted = False
- init.restart_processes()
-
- def test_restart_processes(self):
- '''Check some behavior on restarting processes.'''
- init = MockInit()
- init.runnable = True
- component = MockComponent('test', 53)
-
- # A component to be restarted will actually be restarted iff it's
- # in the configurator's configuration.
- # We bruteforce the configurator internal below; ugly, but the easiest
- # way for the test.
- init._component_configurator._components['test'] = (None, component)
- self.__setup_restart(init, component)
- self.assertTrue(component.restarted)
- self.assertNotIn(component, init.components_to_restart)
-
- # Remove the component from the configuration. It won't be restarted
- # even if scheduled, nor will remain in the to-be-restarted list.
- del init._component_configurator._components['test']
- self.__setup_restart(init, component)
- self.assertFalse(component.restarted)
- self.assertNotIn(component, init.components_to_restart)
-
- def test_get_processes(self):
- '''Test that procsses are returned correctly, sorted by pid.'''
- init = MockInit()
-
- pids = list(range(0, 20))
- random.shuffle(pids)
-
- for i in range(0, 20):
- pid = pids[i]
- component = MockComponent('test' + str(pid), pid,
- 'Test' + str(pid))
- init.components[pid] = component
-
- process_list = init.get_processes()
- self.assertEqual(20, len(process_list))
-
- last_pid = -1
- for process in process_list:
- pid = process[0]
- self.assertLessEqual(last_pid, pid)
- last_pid = pid
- self.assertEqual([pid, 'test' + str(pid), 'Test' + str(pid)],
- process)
-
- def _test_reap_children_helper(self, runnable, is_running, failed):
- '''Construct a Init instance, set various data in it according to
- passed args and check if the component was added to the list of
- components to restart.'''
- init = MockInit()
- init.runnable = runnable
-
- component = MockComponent('test', 53)
- component.running = is_running
- component.has_failed = failed
- init.components[53] = component
-
- self.assertNotIn(component, init.components_to_restart)
-
- init.reap_children()
-
- if runnable and is_running and not failed:
- self.assertIn(component, init.components_to_restart)
- else:
- self.assertEqual([], init.components_to_restart)
-
- def test_reap_children(self):
- '''Test that children are queued to be restarted when they ask for it.'''
- # test various combinations of 3 booleans
- # (Init.runnable, component.is_running(), component.failed())
- self._test_reap_children_helper(False, False, False)
- self._test_reap_children_helper(False, False, True)
- self._test_reap_children_helper(False, True, False)
- self._test_reap_children_helper(False, True, True)
- self._test_reap_children_helper(True, False, False)
- self._test_reap_children_helper(True, False, True)
- self._test_reap_children_helper(True, True, False)
- self._test_reap_children_helper(True, True, True)
-
- # setup for more tests below
- init = MockInit()
- init.runnable = True
- component = MockComponent('test', 53)
- init.components[53] = component
-
- # case where the returned pid is unknown to us. nothing should
- # happpen then.
- init.get_process_exit_status_called = False
- init._get_process_exit_status = init._get_process_exit_status_unknown_pid
- init.components_to_restart = []
- # this should do nothing as the pid is unknown
- init.reap_children()
- self.assertEqual([], init.components_to_restart)
-
- # case where init._get_process_exit_status() raises OSError with
- # errno.ECHILD
- init._get_process_exit_status = \
- init._get_process_exit_status_raises_oserror_echild
- init.components_to_restart = []
- # this should catch and handle the OSError
- init.reap_children()
- self.assertEqual([], init.components_to_restart)
-
- # case where init._get_process_exit_status() raises OSError with
- # errno other than ECHILD
- init._get_process_exit_status = \
- init._get_process_exit_status_raises_oserror_other
- with self.assertRaises(OSError):
- init.reap_children()
-
- # case where init._get_process_exit_status() raises something
- # other than OSError
- init._get_process_exit_status = \
- init._get_process_exit_status_raises_other
- with self.assertRaises(Exception):
- init.reap_children()
-
- def test_kill_started_components(self):
- '''Test that started components are killed.'''
- init = MockInit()
-
- component = MockComponent('test', 53, 'Test')
- init.components[53] = component
-
- self.assertEqual([[53, 'test', 'Test']], init.get_processes())
- init.kill_started_components()
- self.assertEqual([], init.get_processes())
- self.assertTrue(component.forceful)
-
- def _start_msgq_helper(self, init, verbose):
- init.verbose = verbose
- pi = init.start_msgq()
- self.assertEqual('b10-msgq', pi.name)
- self.assertEqual(['b10-msgq'], pi.args)
- self.assertTrue(pi.dev_null_stdout)
- self.assertEqual(pi.dev_null_stderr, not verbose)
- self.assertEqual({'FOO': 'an env string'}, pi.env)
-
- # this is set by ProcessInfo.spawn()
- self.assertEqual(42147, pi.pid)
-
- def test_start_msgq(self):
- '''Test that b10-msgq is started.'''
- init = MockInitSimple()
- init.c_channel_env = {'FOO': 'an env string'}
- init._run_under_unittests = True
-
- # use the MockProcessInfo creator
- init._make_process_info = init._make_mock_process_info
-
- # non-verbose case
- self._start_msgq_helper(init, False)
-
- # verbose case
- self._start_msgq_helper(init, True)
-
- def test_start_msgq_timeout(self):
- '''Test that b10-msgq startup attempts connections several times
- and times out eventually.'''
- b10_init = MockInitSimple()
- b10_init.c_channel_env = {}
- # set the timeout to an arbitrary pre-determined value (which
- # code below depends on)
- b10_init.msgq_timeout = 1
- b10_init._run_under_unittests = False
-
- # use the MockProcessInfo creator
- b10_init._make_process_info = b10_init._make_mock_process_info
-
- global attempts
- global tsec
- attempts = 0
- tsec = 0
- self._tmp_time = time.time
- self._tmp_sleep = time.sleep
- def _my_time():
- global attempts
- global tsec
- attempts += 1
- return tsec
- def _my_sleep(nsec):
- global tsec
- tsec += nsec
- time.time = _my_time
- time.sleep = _my_sleep
-
- global cc_sub
- cc_sub = None
- class DummySessionAlwaysFails():
- def __init__(self, socket_file):
- raise isc.cc.session.SessionError('Connection fails')
- def group_subscribe(self, s):
- global cc_sub
- cc_sub = s
-
- isc.cc.Session = DummySessionAlwaysFails
-
- with self.assertRaises(init.CChannelConnectError):
- # An exception will be thrown here when it eventually times
- # out.
- pi = b10_init.start_msgq()
-
- # time.time() should be called 12 times within the while loop:
- # starting from 0, and 11 more times from 0.1 to 1.1. There's
- # another call to time.time() outside the loop, which makes it
- # 13.
- self.assertEqual(attempts, 13)
-
- # group_subscribe() should not have been called here.
- self.assertIsNone(cc_sub)
-
- global cc_socket_file
- cc_socket_file = None
- cc_sub = None
- class DummySession():
- def __init__(self, socket_file):
- global cc_socket_file
- cc_socket_file = socket_file
- def group_subscribe(self, s):
- global cc_sub
- cc_sub = s
-
- isc.cc.Session = DummySession
-
- # reset values
- attempts = 0
- tsec = 0
-
- pi = b10_init.start_msgq()
-
- # just one attempt, but 2 calls to time.time()
- self.assertEqual(attempts, 2)
-
- self.assertEqual(cc_socket_file, b10_init.msgq_socket_file)
- self.assertEqual(cc_sub, 'Init')
-
- # isc.cc.Session, time.time() and time.sleep() are restored
- # during tearDown().
-
- def _start_cfgmgr_helper(self, init, data_path, filename, clear_config):
- expect_args = ['b10-cfgmgr']
- if data_path is not None:
- init.data_path = data_path
- expect_args.append('--data-path=' + data_path)
- if filename is not None:
- init.config_filename = filename
- expect_args.append('--config-filename=' + filename)
- if clear_config:
- init.clear_config = clear_config
- expect_args.append('--clear-config')
-
- pi = init.start_cfgmgr()
- self.assertEqual('b10-cfgmgr', pi.name)
- self.assertEqual(expect_args, pi.args)
- self.assertEqual({'TESTENV': 'A test string'}, pi.env)
-
- # this is set by ProcessInfo.spawn()
- self.assertEqual(42147, pi.pid)
-
- def test_start_cfgmgr(self):
- '''Test that b10-cfgmgr is started.'''
- class DummySession():
- def __init__(self):
- self._tries = 0
- def group_recvmsg(self):
- self._tries += 1
- # return running on the 3rd try onwards
- if self._tries >= 3:
- return ({'running': 'ConfigManager'}, None)
- else:
- return ({}, None)
-
- init = MockInitSimple()
- init.c_channel_env = {'TESTENV': 'A test string'}
- init.cc_session = DummySession()
- init.wait_time = 5
-
- # use the MockProcessInfo creator
- init._make_process_info = init._make_mock_process_info
-
- global attempts
- attempts = 0
- self._tmp_sleep = time.sleep
- def _my_sleep(nsec):
- global attempts
- attempts += 1
- time.sleep = _my_sleep
-
- # defaults
- self._start_cfgmgr_helper(init, None, None, False)
-
- # check that 2 attempts were made. on the 3rd attempt,
- # process_running() returns that ConfigManager is running.
- self.assertEqual(attempts, 2)
-
- # data_path is specified
- self._start_cfgmgr_helper(init, '/var/lib/test', None, False)
-
- # config_filename is specified. Because `init` is not
- # reconstructed, data_path is retained from the last call to
- # _start_cfgmgr_helper().
- self._start_cfgmgr_helper(init, '/var/lib/test', 'foo.cfg', False)
-
- # clear_config is specified. Because `init` is not reconstructed,
- # data_path and config_filename are retained from the last call
- # to _start_cfgmgr_helper().
- self._start_cfgmgr_helper(init, '/var/lib/test', 'foo.cfg', True)
-
- def test_start_cfgmgr_timeout(self):
- '''Test that b10-cfgmgr startup attempts connections several times
- and times out eventually.'''
- class DummySession():
- def group_recvmsg(self):
- return (None, None)
- b10_init = MockInitSimple()
- b10_init.c_channel_env = {}
- b10_init.cc_session = DummySession()
- # set wait_time to an arbitrary pre-determined value (which code
- # below depends on)
- b10_init.wait_time = 2
-
- # use the MockProcessInfo creator
- b10_init._make_process_info = b10_init._make_mock_process_info
-
- global attempts
- attempts = 0
- self._tmp_sleep = time.sleep
- def _my_sleep(nsec):
- global attempts
- attempts += 1
- time.sleep = _my_sleep
-
- # We just check that an exception was thrown, and that several
- # attempts were made to connect.
- with self.assertRaises(init.ProcessStartError):
- pi = b10_init.start_cfgmgr()
-
- # 2 seconds of attempts every 1 second should result in 2 attempts
- self.assertEqual(attempts, 2)
-
- # time.sleep() is restored during tearDown().
-
- def test_start_ccsession(self):
- '''Test that CC session is started.'''
- class DummySession():
- def __init__(self, specfile, config_handler, command_handler,
- socket_file):
- self.specfile = specfile
- self.config_handler = config_handler
- self.command_handler = command_handler
- self.socket_file = socket_file
- self.started = False
- def start(self):
- self.started = True
- b10_init = MockInitSimple()
- self._tmp_module_cc_session = isc.config.ModuleCCSession
- isc.config.ModuleCCSession = DummySession
-
- b10_init.start_ccsession({})
- self.assertEqual(init.SPECFILE_LOCATION, b10_init.ccs.specfile)
- self.assertEqual(b10_init.config_handler, b10_init.ccs.config_handler)
- self.assertEqual(b10_init.command_handler,
- b10_init.ccs.command_handler)
- self.assertEqual(b10_init.msgq_socket_file, b10_init.ccs.socket_file)
- self.assertTrue(b10_init.ccs.started)
-
- # isc.config.ModuleCCSession is restored during tearDown().
-
- def test_start_process(self):
- '''Test that processes can be started.'''
- init = MockInit()
-
- # use the MockProcessInfo creator
- init._make_process_info = init._make_mock_process_info
-
- pi = init.start_process('Test Process', ['/bin/true'], {})
- self.assertEqual('Test Process', pi.name)
- self.assertEqual(['/bin/true'], pi.args)
- self.assertEqual({}, pi.env)
-
- # this is set by ProcessInfo.spawn()
- self.assertEqual(42147, pi.pid)
-
- def test_register_process(self):
- '''Test that processes can be registered with Init.'''
- init = MockInit()
- component = MockComponent('test', 53, 'Test')
-
- self.assertFalse(53 in init.components)
- init.register_process(53, component)
- self.assertTrue(53 in init.components)
- self.assertEqual(init.components[53].name(), 'test')
- self.assertEqual(init.components[53].pid(), 53)
- self.assertEqual(init.components[53].address(), 'Test')
-
- def _start_simple_helper(self, init, verbose):
- init.verbose = verbose
-
- args = ['/bin/true']
- if verbose:
- args.append('-v')
-
- init.start_simple('/bin/true')
- self.assertEqual('/bin/true', init.started_process_name)
- self.assertEqual(args, init.started_process_args)
- self.assertEqual({'TESTENV': 'A test string'}, init.started_process_env)
-
- def test_start_simple(self):
- '''Test simple process startup.'''
- init = MockInitSimple()
- init.c_channel_env = {'TESTENV': 'A test string'}
-
- # non-verbose case
- self._start_simple_helper(init, False)
-
- # verbose case
- self._start_simple_helper(init, True)
-
- def _start_auth_helper(self, init, verbose):
- init.verbose = verbose
-
- args = ['b10-auth']
- if verbose:
- args.append('-v')
-
- init.start_auth()
- self.assertEqual('b10-auth', init.started_process_name)
- self.assertEqual(args, init.started_process_args)
- self.assertEqual({'FOO': 'an env string'}, init.started_process_env)
-
- def test_start_auth(self):
- '''Test that b10-auth is started.'''
- init = MockInitSimple()
- init.c_channel_env = {'FOO': 'an env string'}
-
- # non-verbose case
- self._start_auth_helper(init, False)
-
- # verbose case
- self._start_auth_helper(init, True)
-
- def _start_resolver_helper(self, init, verbose):
- init.verbose = verbose
-
- args = ['b10-resolver']
- if verbose:
- args.append('-v')
-
- init.start_resolver()
- self.assertEqual('b10-resolver', init.started_process_name)
- self.assertEqual(args, init.started_process_args)
- self.assertEqual({'BAR': 'an env string'}, init.started_process_env)
-
- def test_start_resolver(self):
- '''Test that b10-resolver is started.'''
- init = MockInitSimple()
- init.c_channel_env = {'BAR': 'an env string'}
-
- # non-verbose case
- self._start_resolver_helper(init, False)
-
- # verbose case
- self._start_resolver_helper(init, True)
-
- def _start_cmdctl_helper(self, init, verbose, port = None):
- init.verbose = verbose
-
- args = ['b10-cmdctl']
-
- if port is not None:
- init.cmdctl_port = port
- args.append('--port=9353')
-
- if verbose:
- args.append('-v')
-
- init.start_cmdctl()
- self.assertEqual('b10-cmdctl', init.started_process_name)
- self.assertEqual(args, init.started_process_args)
- self.assertEqual({'BAZ': 'an env string'}, init.started_process_env)
-
- def test_start_cmdctl(self):
- '''Test that b10-cmdctl is started.'''
- init = MockInitSimple()
- init.c_channel_env = {'BAZ': 'an env string'}
-
- # non-verbose case
- self._start_cmdctl_helper(init, False)
-
- # verbose case
- self._start_cmdctl_helper(init, True)
-
- # with port, non-verbose case
- self._start_cmdctl_helper(init, False, 9353)
-
- # with port, verbose case
- self._start_cmdctl_helper(init, True, 9353)
-
- def test_socket_data(self):
- '''Test that Init._socket_data works as expected.'''
- class MockSock:
- def __init__(self, fd, throw):
- self.fd = fd
- self.throw = throw
- self.buf = b'Hello World.\nYou are so nice today.\nXX'
- self.i = 0
-
- def recv(self, bufsize, flags = 0):
- if bufsize != 1:
- raise Exception('bufsize != 1')
- if flags != socket.MSG_DONTWAIT:
- raise Exception('flags != socket.MSG_DONTWAIT')
- # after 15 recv()s, throw a socket.error with EAGAIN to
- # get _socket_data() to save back what's been read. The
- # number 15 is arbitrarily chosen, but the checks then
- # depend on this being 15, i.e., if you adjust this
- # number, you may have to adjust the checks below too.
- if self.throw and self.i > 15:
- raise socket.error(errno.EAGAIN, 'Try again')
- if self.i >= len(self.buf):
- return b'';
- t = self.i
- self.i += 1
- return self.buf[t:t+1]
-
- def close(self):
- return
-
- class MockInitSocketData(Init):
- def __init__(self, throw):
- self._unix_sockets = {42: (MockSock(42, throw), b'')}
- self.requests = []
- self.dead = []
-
- def socket_request_handler(self, previous, sock):
- self.requests.append({sock.fd: previous})
-
- def socket_consumer_dead(self, sock):
- self.dead.append(sock.fd)
-
- # Case where we get data every time we call recv()
- init = MockInitSocketData(False)
- init._socket_data(42)
- self.assertEqual(init.requests,
- [{42: b'Hello World.'},
- {42: b'You are so nice today.'}])
- self.assertEqual(init.dead, [42])
- self.assertEqual({}, init._unix_sockets)
-
- # Case where socket.recv() raises EAGAIN. In this case, the
- # routine is supposed to save what it has back to
- # Init._unix_sockets.
- init = MockInitSocketData(True)
- init._socket_data(42)
- self.assertEqual(init.requests, [{42: b'Hello World.'}])
- self.assertFalse(init.dead)
- self.assertEqual(len(init._unix_sockets), 1)
- self.assertEqual(init._unix_sockets[42][1], b'You')
-
- def test_startup(self):
- '''Test that Init.startup() handles failures properly.'''
- class MockInitStartup(Init):
- def __init__(self, throw):
- self.throw = throw
- self.started = False
- self.killed = False
- self.msgq_socket_file = None
- self.curproc = 'myproc'
- self.runnable = False
-
- def start_all_components(self):
- self.started = True
- if self.throw is True:
- raise Exception('Assume starting components has failed.')
- elif self.throw:
- raise self.throw
-
- def kill_started_components(self):
- self.killed = True
-
- class DummySession():
- def __init__(self, socket_file):
- raise isc.cc.session.SessionError('This is the expected case.')
-
- class DummySessionSocketExists():
- def __init__(self, socket_file):
- # simulate that connect passes
- return
-
- isc.cc.Session = DummySession
-
- # All is well case, where all components are started
- # successfully. We check that the actual call to
- # start_all_components() is made, and Init.runnable is true.
- b10_init = MockInitStartup(False)
- r = b10_init.startup()
- self.assertIsNone(r)
- self.assertTrue(b10_init.started)
- self.assertFalse(b10_init.killed)
- self.assertTrue(b10_init.runnable)
- self.assertEqual({}, b10_init.c_channel_env)
-
- # Case where starting components fails. We check that
- # kill_started_components() is called right after, and
- # Init.runnable is not modified.
- b10_init = MockInitStartup(True)
- r = b10_init.startup()
- # r contains an error message
- self.assertEqual(r, 'Unable to start myproc: Assume starting components has failed.')
- self.assertTrue(b10_init.started)
- self.assertTrue(b10_init.killed)
- self.assertFalse(b10_init.runnable)
- self.assertEqual({}, b10_init.c_channel_env)
-
- # Check if msgq_socket_file is carried over
- b10_init = MockInitStartup(False)
- b10_init.msgq_socket_file = 'foo'
- r = b10_init.startup()
- self.assertEqual({'BIND10_MSGQ_SOCKET_FILE': 'foo'},
- b10_init.c_channel_env)
-
- # Check failure of changing user results in a different message
- b10_init = MockInitStartup(init.ChangeUserError('failed to chusr'))
- r = b10_init.startup()
- self.assertIn('failed to chusr', r)
- self.assertTrue(b10_init.killed)
-
- # Check the case when socket file already exists
- isc.cc.Session = DummySessionSocketExists
- b10_init = MockInitStartup(False)
- r = b10_init.startup()
- self.assertIn('already running', r)
-
- # isc.cc.Session is restored during tearDown().
-
-class SocketSrvTest(unittest.TestCase):
- """
- This tests some methods of b10-init related to the unix domain sockets
- used to transfer other sockets to applications.
- """
- def setUp(self):
- """
- Create the b10-init to test, testdata and backup some functions.
- """
- self.__b10_init = Init()
- self.__select_backup = init.select.select
- self.__select_called = None
- self.__socket_data_called = None
- self.__consumer_dead_called = None
- self.__socket_request_handler_called = None
-
- def tearDown(self):
- """
- Restore functions.
- """
- init.select.select = self.__select_backup
-
- class __FalseSocket:
- """
- A mock socket for the select and accept and stuff like that.
- """
- def __init__(self, owner, fileno=42):
- self.__owner = owner
- self.__fileno = fileno
- self.data = None
- self.closed = False
-
- def fileno(self):
- return self.__fileno
-
- def accept(self):
- return (self.__class__(self.__owner, 13), "/path/to/socket")
-
- def recv(self, bufsize, flags=0):
- self.__owner.assertEqual(1, bufsize)
- self.__owner.assertEqual(socket.MSG_DONTWAIT, flags)
- if isinstance(self.data, socket.error):
- raise self.data
- elif self.data is not None:
- if len(self.data):
- result = self.data[0:1]
- self.data = self.data[1:]
- return result
- else:
- raise socket.error(errno.EAGAIN, "Would block")
- else:
- return b''
-
- def close(self):
- self.closed = True
-
- class __CCS:
- """
- A mock CCS, just to provide the socket file number.
- """
- class __Socket:
- def fileno(self):
- return 1
- def get_socket(self):
- return self.__Socket()
-
- def __select_accept(self, r, w, x, t):
- self.__select_called = (r, w, x, t)
- return ([42], [], [])
-
- def __select_data(self, r, w, x, t):
- self.__select_called = (r, w, x, t)
- return ([13], [], [])
-
- def __accept(self):
- """
- Hijack the accept method of the b10-init.
-
- Notes down it was called and stops b10-init.
- """
- self.__accept_called = True
- self.__b10_init.runnable = False
-
- def test_srv_accept_called(self):
- """
- Test that the _srv_accept method of b10-init is called when the
- listening socket is readable.
- """
- self.__b10_init.runnable = True
- self.__b10_init._srv_socket = self.__FalseSocket(self)
- self.__b10_init._srv_accept = self.__accept
- self.__b10_init.ccs = self.__CCS()
- init.select.select = self.__select_accept
- self.__b10_init.run(2)
- # It called the accept
- self.assertTrue(self.__accept_called)
- # And the select had the right parameters
- self.assertEqual(([2, 1, 42], [], [], None), self.__select_called)
-
- def test_srv_accept(self):
- """
- Test how the _srv_accept method works.
- """
- self.__b10_init._srv_socket = self.__FalseSocket(self)
- self.__b10_init._srv_accept()
- # After we accepted, a new socket is added there
- socket = self.__b10_init._unix_sockets[13][0]
- # The socket is properly stored there
- self.assertTrue(isinstance(socket, self.__FalseSocket))
- # And the buffer (yet empty) is there
- self.assertEqual({13: (socket, b'')}, self.__b10_init._unix_sockets)
-
- def __socket_data(self, socket):
- self.__b10_init.runnable = False
- self.__socket_data_called = socket
-
- def test_socket_data(self):
- """
- Test that a socket that wants attention gets it.
- """
- self.__b10_init._srv_socket = self.__FalseSocket(self)
- self.__b10_init._socket_data = self.__socket_data
- self.__b10_init.ccs = self.__CCS()
- self.__b10_init._unix_sockets = {13: (self.__FalseSocket(self, 13), b'')}
- self.__b10_init.runnable = True
- init.select.select = self.__select_data
- self.__b10_init.run(2)
- self.assertEqual(13, self.__socket_data_called)
- self.assertEqual(([2, 1, 42, 13], [], [], None), self.__select_called)
-
- def __prepare_data(self, data):
- socket = self.__FalseSocket(self, 13)
- self.__b10_init._unix_sockets = {13: (socket, b'')}
- socket.data = data
- self.__b10_init.socket_consumer_dead = self.__consumer_dead
- self.__b10_init.socket_request_handler = self.__socket_request_handler
- return socket
-
- def __consumer_dead(self, socket):
- self.__consumer_dead_called = socket
-
- def __socket_request_handler(self, token, socket):
- self.__socket_request_handler_called = (token, socket)
-
- def test_socket_closed(self):
- """
- Test that a socket is removed and the socket_consumer_dead is called
- when it is closed.
- """
- socket = self.__prepare_data(None)
- self.__b10_init._socket_data(13)
- self.assertEqual(socket, self.__consumer_dead_called)
- self.assertEqual({}, self.__b10_init._unix_sockets)
- self.assertTrue(socket.closed)
-
- def test_socket_short(self):
- """
- Test that if there's not enough data to get the whole socket, it is
- kept there, but nothing is called.
- """
- socket = self.__prepare_data(b'tok')
- self.__b10_init._socket_data(13)
- self.assertEqual({13: (socket, b'tok')}, self.__b10_init._unix_sockets)
- self.assertFalse(socket.closed)
- self.assertIsNone(self.__consumer_dead_called)
- self.assertIsNone(self.__socket_request_handler_called)
-
- def test_socket_continue(self):
- """
- Test that we call the token handling function when the whole token
- comes. This test pretends to continue reading where the previous one
- stopped.
- """
- socket = self.__prepare_data(b"en\nanothe")
- # The data to finish
- self.__b10_init._unix_sockets[13] = (socket, b'tok')
- self.__b10_init._socket_data(13)
- self.assertEqual({13: (socket, b'anothe')}, self.__b10_init._unix_sockets)
- self.assertFalse(socket.closed)
- self.assertIsNone(self.__consumer_dead_called)
- self.assertEqual((b'token', socket),
- self.__socket_request_handler_called)
-
- def test_broken_socket(self):
- """
- If the socket raises an exception during the read other than EAGAIN,
- it is broken and we remove it.
- """
- sock = self.__prepare_data(socket.error(errno.ENOMEM,
- "There's more memory available, but not for you"))
- self.__b10_init._socket_data(13)
- self.assertEqual(sock, self.__consumer_dead_called)
- self.assertEqual({}, self.__b10_init._unix_sockets)
- self.assertTrue(sock.closed)
-
-class TestFunctions(unittest.TestCase):
- def setUp(self):
- self.lockfile_testpath = \
- "@abs_top_builddir@/src/bin/bind10/tests/lockfile_test"
- self.assertFalse(os.path.exists(self.lockfile_testpath))
- os.mkdir(self.lockfile_testpath)
- self.assertTrue(os.path.isdir(self.lockfile_testpath))
- self.__isfile_orig = init.os.path.isfile
- self.__unlink_orig = init.os.unlink
-
- def tearDown(self):
- os.rmdir(self.lockfile_testpath)
- self.assertFalse(os.path.isdir(self.lockfile_testpath))
- os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = "@abs_top_builddir@"
- init.os.path.isfile = self.__isfile_orig
- init.os.unlink = self.__unlink_orig
-
- def test_remove_lock_files(self):
- os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = self.lockfile_testpath
-
- # create lockfiles for the testcase
- lockfiles = ["logger_lockfile"]
- for f in lockfiles:
- fname = os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] + '/' + f
- self.assertFalse(os.path.exists(fname))
- open(fname, "w").close()
- self.assertTrue(os.path.isfile(fname))
-
- # first call should clear up all the lockfiles
- init.remove_lock_files()
-
- # check if the lockfiles exist
- for f in lockfiles:
- fname = os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] + '/' + f
- self.assertFalse(os.path.isfile(fname))
-
- # second call should not assert anyway
- init.remove_lock_files()
-
- def test_remove_lock_files_fail(self):
- # Permission error on unlink is ignored; other exceptions are really
- # unexpected and propagated.
- def __raising_unlink(unused, ex):
- raise ex
-
- init.os.path.isfile = lambda _: True
- os_error = OSError()
- init.os.unlink = lambda f: __raising_unlink(f, os_error)
-
- os_error.errno = errno.EPERM
- init.remove_lock_files() # no disruption
-
- os_error.errno = errno.EACCES
- init.remove_lock_files() # no disruption
-
- os_error.errno = errno.ENOENT
- self.assertRaises(OSError, init.remove_lock_files)
-
- init.os.unlink = lambda f: __raising_unlink(f, Exception('bad'))
- self.assertRaises(Exception, init.remove_lock_files)
-
- def test_get_signame(self):
- # just test with some samples
- signame = init.get_signame(signal.SIGTERM)
- self.assertEqual('SIGTERM', signame)
- signame = init.get_signame(signal.SIGKILL)
- self.assertEqual('SIGKILL', signame)
- # 59426 is hopefully an unused signal on most platforms
- signame = init.get_signame(59426)
- self.assertEqual('Unknown signal 59426', signame)
-
- def test_fatal_signal(self):
- self.assertIsNone(init.b10_init)
- init.b10_init = Init()
- init.b10_init.runnable = True
- init.fatal_signal(signal.SIGTERM, None)
- # Now, runnable must be False
- self.assertFalse(init.b10_init.runnable)
- init.b10_init = None
-
-if __name__ == '__main__':
- # store os.environ for test_unchanged_environment
- original_os_environ = copy.deepcopy(os.environ)
- isc.log.resetUnitTestRootLogger()
- unittest.main()
diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py
index f382e2a..af599a4 100644
--- a/src/bin/bindctl/bindcmd.py
+++ b/src/bin/bindctl/bindcmd.py
@@ -25,7 +25,7 @@ from bindctl.moduleinfo import *
from bindctl.cmdparse import BindCmdParser
from bindctl import command_sets
from xml.dom import minidom
-import isc.config
+import isc
import isc.cc.data
import http.client
import json
diff --git a/src/bin/bindctl/run_bindctl.sh.in b/src/bin/bindctl/run_bindctl.sh.in
index 8a5d00b..999d7ee 100755
--- a/src/bin/bindctl/run_bindctl.sh.in
+++ b/src/bin/bindctl/run_bindctl.sh.in
@@ -23,7 +23,7 @@ BINDCTL_PATH=@abs_top_builddir@/src/bin/bindctl
# Note: lib/dns/python/.libs is necessary because __init__.py of isc package
# automatically imports isc.datasrc, which then requires the DNS loadable
# module. #2145 should eliminate the need for it.
-PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python
+PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
export PYTHONPATH
# If necessary (rare cases), explicitly specify paths to dynamic libraries
diff --git a/src/bin/stats/stats.py.in b/src/bin/stats/stats.py.in
index 0af0933..17d2f74 100755
--- a/src/bin/stats/stats.py.in
+++ b/src/bin/stats/stats.py.in
@@ -26,8 +26,7 @@ from optparse import OptionParser, OptionValueError
import errno
import select
-import isc.cc
-import isc.config
+import isc
import isc.util.process
import isc.log
from isc.log_messages.stats_messages import *
diff --git a/src/bin/sysinfo/run_sysinfo.sh.in b/src/bin/sysinfo/run_sysinfo.sh.in
index b5593b9..6459c2d 100755
--- a/src/bin/sysinfo/run_sysinfo.sh.in
+++ b/src/bin/sysinfo/run_sysinfo.sh.in
@@ -20,8 +20,20 @@ export PYTHON_EXEC
SYSINFO_PATH=@abs_top_builddir@/src/bin/sysinfo
-PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python
+# Note: we shouldn't need log_messages and lib/dns except for the seemingly
+# necessary dependency due to the automatic import in the isc package (its
+# __init__.py imports some other modules)
+# #2145 should eliminate the need for them.
+PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/dns/python/.libs
export PYTHONPATH
+# Likewise, we need only because isc.log requires some loadable modules.
+# sysinfo itself shouldn't need any of them.
+SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
+if test $SET_ENV_LIBRARY_PATH = yes; then
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ export @ENV_LIBRARY_PATH@
+fi
+
cd ${SYSINFO_PATH}
exec ${PYTHON_EXEC} -O sysinfo.py "$@"
diff --git a/src/lib/python/isc/__init__.py b/src/lib/python/isc/__init__.py
index 37138a2..029f110 100644
--- a/src/lib/python/isc/__init__.py
+++ b/src/lib/python/isc/__init__.py
@@ -1,3 +1,7 @@
-"""
-This is the top directory for common BIND 10 Python modules and packages.
-"""
+# On some systems, it appears the dynamic linker gets
+# confused if the order is not right here
+# There is probably a solution for this, but for now:
+# order is important here!
+import isc.cc
+import isc.config
+import isc.datasrc
diff --git a/src/lib/python/isc/datasrc/tests/zone_loader_test.py b/src/lib/python/isc/datasrc/tests/zone_loader_test.py
index 4cd4879..759afd7 100644
--- a/src/lib/python/isc/datasrc/tests/zone_loader_test.py
+++ b/src/lib/python/isc/datasrc/tests/zone_loader_test.py
@@ -13,7 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-import isc.log
import isc.datasrc
import isc.dns
More information about the bind10-changes
mailing list