BIND 10 trac2145, updated. 64fb39c963cd9e6494f71dfe14d9dabdf869fdc8 [2145] (unrelated) cleanup: rename bind10_test.py to init_test.py.
BIND 10 source code commits
bind10-changes at lists.isc.org
Wed Feb 6 03:53:34 UTC 2013
The branch, trac2145 has been updated
via 64fb39c963cd9e6494f71dfe14d9dabdf869fdc8 (commit)
via 3a185f59245200be3c6b2e86340ac1c2ae464efb (commit)
via b43c93c8cb4e0256677c01d5f649093fc27c998a (commit)
from 0749f9e194505698031990eb7c544e8ec076fe10 (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 64fb39c963cd9e6494f71dfe14d9dabdf869fdc8
Author: JINMEI Tatuya <jinmei at isc.org>
Date: Tue Feb 5 19:52:38 2013 -0800
[2145] (unrelated) cleanup: rename bind10_test.py to init_test.py.
simply because it makes more sense in terms of what's tested.
commit 3a185f59245200be3c6b2e86340ac1c2ae464efb
Author: JINMEI Tatuya <jinmei at isc.org>
Date: Tue Feb 5 19:03:17 2013 -0800
[2145] removed now-unecessary python paths from some run-xxx wrapper scripts
commit b43c93c8cb4e0256677c01d5f649093fc27c998a
Author: JINMEI Tatuya <jinmei at isc.org>
Date: Tue Feb 5 18:50:56 2013 -0800
[2145] added some comments about in-source test setup for bind10 script
-----------------------------------------------------------------------
Summary of changes:
configure.ac | 2 +-
src/bin/bind10/bind10.in | 2 ++
src/bin/bind10/tests/Makefile.am | 2 +-
.../tests/{bind10_test.py.in => init_test.py.in} | 0
src/bin/bindctl/run_bindctl.sh.in | 2 +-
src/bin/sysinfo/run_sysinfo.sh.in | 14 +-------------
6 files changed, 6 insertions(+), 16 deletions(-)
rename src/bin/bind10/tests/{bind10_test.py.in => init_test.py.in} (100%)
-----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index 2075a74..b1de7be 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1319,7 +1319,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/bind10_test.py
+ src/bin/bind10/tests/init_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 6421bab..88c45c9 100755
--- a/src/bin/bind10/bind10.in
+++ b/src/bin/bind10/bind10.in
@@ -1,5 +1,7 @@
#!/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/tests/Makefile.am b/src/bin/bind10/tests/Makefile.am
index a5e3fab..6d59dbd 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 = bind10_test.py
+PYTESTS = init_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
deleted file mode 100644
index 9a591ef..0000000
--- a/src/bin/bind10/tests/bind10_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/bind10/tests/init_test.py.in b/src/bin/bind10/tests/init_test.py.in
new file mode 100644
index 0000000..9a591ef
--- /dev/null
+++ b/src/bin/bind10/tests/init_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.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/run_bindctl.sh.in b/src/bin/bindctl/run_bindctl.sh.in
index 999d7ee..8a5d00b 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:@abs_top_builddir@/src/lib/dns/python/.libs
+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
export PYTHONPATH
# If necessary (rare cases), explicitly specify paths to dynamic libraries
diff --git a/src/bin/sysinfo/run_sysinfo.sh.in b/src/bin/sysinfo/run_sysinfo.sh.in
index 6459c2d..b5593b9 100755
--- a/src/bin/sysinfo/run_sysinfo.sh.in
+++ b/src/bin/sysinfo/run_sysinfo.sh.in
@@ -20,20 +20,8 @@ export PYTHON_EXEC
SYSINFO_PATH=@abs_top_builddir@/src/bin/sysinfo
-# 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
+PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python
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 "$@"
More information about the bind10-changes
mailing list