BIND 10 trac1901, updated. 2dffde2cef71ec693097f59f81fe40ada8035975 [1901] Rename source file for b10-init to init.py
BIND 10 source code commits
bind10-changes at lists.isc.org
Mon Feb 4 17:31:22 UTC 2013
The branch, trac1901 has been updated
via 2dffde2cef71ec693097f59f81fe40ada8035975 (commit)
via 19a4de9fd0738a4332169f432035fd4734311f53 (commit)
via 8404133f0a362cede4819322da6506b83968e79b (commit)
via 10d63236169056c2c14cf4807d05017599c0a3bd (commit)
from f95627b501f5bf720e74de1d2cdb17219c826f02 (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 2dffde2cef71ec693097f59f81fe40ada8035975
Author: Jelte Jansen <jelte at isc.org>
Date: Mon Feb 4 18:13:54 2013 +0100
[1901] Rename source file for b10-init to init.py
commit 19a4de9fd0738a4332169f432035fd4734311f53
Author: Jelte Jansen <jelte at isc.org>
Date: Mon Feb 4 15:41:42 2013 +0100
[1901] Update version of lettuce test configs
commit 8404133f0a362cede4819322da6506b83968e79b
Author: Jelte Jansen <jelte at isc.org>
Date: Mon Feb 4 15:12:10 2013 +0100
[1901] Update manpages, log on certain update warnings
Second part of review as found in http://bind10.isc.org/ticket/1901#comment:11
commit 10d63236169056c2c14cf4807d05017599c0a3bd
Author: Jelte Jansen <jelte at isc.org>
Date: Mon Feb 4 12:53:25 2013 +0100
[1901] Fix run script, add manpage, revert ChangeLog
According to the first part of the review in http://bind10.isc.org/ticket/1901#comment:11
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 22 +-
configure.ac | 4 +-
src/bin/bind10/Makefile.am | 15 +-
src/bin/bind10/README | 3 +-
src/bin/bind10/b10-init.xml | 4 +-
src/bin/bind10/bind10 | 2 -
src/bin/bind10/bind10.in | 4 +
.../b10-sockcreator.xml => bind10/bind10.xml} | 52 ++---
src/bin/bind10/{b10-init.py.in => init.py.in} | 0
src/bin/bind10/init.spec | 2 +-
src/bin/bind10/init_messages.mes | 2 +-
src/bin/bind10/run_bind10.sh.in | 2 +-
src/bin/bind10/tests/bind10_test.py.in | 214 ++++++++++----------
src/bin/bindctl/bindctl_main.py.in | 2 +-
src/bin/ddns/b10-ddns.xml | 2 +-
src/bin/dhcp4/tests/dhcp4_test.py | 10 +-
src/bin/dhcp6/tests/dhcp6_test.py | 10 +-
src/bin/stats/b10-stats-httpd.xml | 4 +-
src/bin/stats/b10-stats.xml | 8 +-
src/lib/python/isc/config/cfgmgr.py | 11 +-
src/lib/python/isc/config/cfgmgr_messages.mes | 10 +
.../configurations/auth/auth_badzone.config.orig | 2 +-
.../configurations/auth/auth_basic.config.orig | 2 +-
.../configurations/bindctl/bindctl.config.orig | 2 +-
.../configurations/bindctl_commands.config.orig | 2 +-
tests/lettuce/configurations/ddns/ddns.config.orig | 2 +-
.../lettuce/configurations/ddns/noddns.config.orig | 2 +-
tests/lettuce/configurations/default.config | 2 +-
.../lettuce/configurations/example.org.config.orig | 2 +-
.../configurations/example.org.inmem.config | 2 +-
tests/lettuce/configurations/example2.org.config | 2 +-
.../inmemory_over_sqlite3/secondary.conf | 2 +-
.../configurations/ixfr-out/testset1-config.db | 2 +-
.../multi_instance/multi_auth.config.orig | 2 +-
tests/lettuce/configurations/no_db_file.config | 2 +-
.../lettuce/configurations/nsec3/nsec3_auth.config | 2 +-
.../resolver/resolver_basic.config.orig | 32 ++-
.../lettuce/configurations/xfrin/inmem_slave.conf | 2 +-
.../xfrin/retransfer_master.conf.orig | 2 +-
.../xfrin/retransfer_master_nons.conf.orig | 2 +-
.../xfrin/retransfer_slave.conf.orig | 2 +-
.../xfrin/retransfer_slave_notify.conf | 2 +-
42 files changed, 233 insertions(+), 222 deletions(-)
delete mode 100755 src/bin/bind10/bind10
create mode 100755 src/bin/bind10/bind10.in
copy src/bin/{sockcreator/b10-sockcreator.xml => bind10/bind10.xml} (56%)
rename src/bin/bind10/{b10-init.py.in => init.py.in} (100%)
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index dfbe7d4..32a9108 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -575,7 +575,7 @@ bind10-devel-20121115 released on November 15, 2012
487. [bug] jinmei
The bind10 process now terminates a component (subprocess) by the
- "config remove Init/components" bindctl command even if the
+ "config remove Boss/components" bindctl command even if the
process crashes immediately before the command is sent to bind10.
Previously this led to an inconsistent state between the
configuration and an internal component list of bind10, and bind10
@@ -599,7 +599,7 @@ bind10-devel-20121115 released on November 15, 2012
completion added a part twice has been solved, and it no longer
suggests the confusing value 'argument' as a completion-hint for
configuration items. Additionally, bindctl no longer crashes upon
- input like 'config remove Init'.
+ input like 'config remove Boss'.
(Trac #2254, git 9047de5e8f973e12e536f7180738e6b515439448)
484. [func] tomek
@@ -1462,7 +1462,7 @@ bind10-devel-20120119 released on January 19, 2012
the -u flag for switching the user after initialization.
Note: this change broke backward compatibility to boss component
configuration. If your b10-config.db contains "setuid" for
- Init.components, you'll need to remove that entry by hand before
+ Boss.components, you'll need to remove that entry by hand before
starting BIND 10.
(Trac #1508, #1509, #1510,
git edc5b3c12eb45437361484c843794416ad86bb00)
@@ -1785,7 +1785,7 @@ bind10-devel-20111128 released on November 28, 2011
316. [func]* vorner
The configuration of what parts of the system run is more
flexible now. Everything that should run must have an
- entry in Init/components.
+ entry in Boss/components.
(Trac #213, git 08e1873a3593b4fa06754654d22d99771aa388a6)
315. [func] tomek
@@ -1856,9 +1856,9 @@ bind10-devel-20111128 released on November 28, 2011
(Trac #1279, git cd3588c9020d0310f949bfd053c4d3a4bd84ef88)
306. [bug] stephen
- Init process now waits for the configuration manager to initialize
+ Boss process now waits for the configuration manager to initialize
itself before continuing with startup. This fixes a race condition
- whereby the Init could start the configuration manager and then
+ whereby the Boss could start the configuration manager and then
immediately start components that depended on that component being
fully initialized.
(Trac #1271, git 607cbae949553adac7e2a684fa25bda804658f61)
@@ -2521,7 +2521,7 @@ bind10-devel-20110322 released on March 22, 2011
206. [func] shane
Add the ability to list the running BIND 10 processes using the
- command channel. To try this, use "Init show_processes".
+ command channel. To try this, use "Boss show_processes".
(Trac #648, git 451bbb67c2b5d544db2f7deca4315165245d2b3b)
205. [bug] jinmei
@@ -2685,7 +2685,7 @@ bind10-devel-20110224 released on February 24, 2011
179. [func] vorner
It is possible to start and stop resolver and authoritative
server without restart of the whole system. Change of the
- configuration (Init/start_auth and Init/start_resolver) is
+ configuration (Boss/start_auth and Boss/start_resolver) is
enough.
(Trac #565, git 0ac0b4602fa30852b0d86cc3c0b4730deb1a58fe)
@@ -2751,7 +2751,7 @@ bind10-devel-20110224 released on February 24, 2011
(Trac #449, git 8aa3b2246ae095bbe7f855fd11656ae3bdb98986)
168. [bug] vorner
- Init no longer has the -f argument, which was undocumented and
+ Boss no longer has the -f argument, which was undocumented and
stayed as a relict of previous versions, currently causing only
strange behaviour.
(Trac #572, git 17f237478961005707d649a661cc72a4a0d612d4)
@@ -3062,7 +3062,7 @@ bind10-devel-20101201 released on December 01, 2010
Output changed to JSON format for consistency. (svn r3694)
122. [func] stephen
- src/bin/bind10: Added configuration options to Init to determine
+ src/bin/bind10: Added configuration options to Boss to determine
whether to start the authoritative server, recursive server (or
both). A dummy program has been provided for test purposes.
(Trac #412, svn r3676)
@@ -3131,7 +3131,7 @@ bind10-devel-20101201 released on December 01, 2010
111. [bug]* Vaner
Make sure process xfrin/xfrout/zonemgr/cmdctl can be stopped
- properly when user enter "ctrl+c" or 'Init shutdown' command
+ properly when user enter "ctrl+c" or 'Boss shutdown' command
through bindctl. The ZonemgrRefresh.run_timer and
NotifyOut.dispatcher spawn a thread themselves.
(Trac #335, svn r3273)
diff --git a/configure.ac b/configure.ac
index 9d9d372..1798bd9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1127,6 +1127,7 @@ AC_CONFIG_FILES([Makefile
compatcheck/Makefile
src/Makefile
src/bin/Makefile
+ src/bin/bind10/bind10
src/bin/bind10/Makefile
src/bin/bind10/tests/Makefile
src/bin/cmdctl/Makefile
@@ -1310,7 +1311,7 @@ AC_OUTPUT([doc/version.ent
src/bin/sysinfo/run_sysinfo.sh
src/bin/stats/stats.py
src/bin/stats/stats_httpd.py
- src/bin/bind10/b10-init.py
+ src/bin/bind10/init.py
src/bin/bind10/run_bind10.sh
src/bin/bind10/tests/bind10_test.py
src/bin/bindctl/run_bindctl.sh
@@ -1376,6 +1377,7 @@ AC_OUTPUT([doc/version.ent
chmod +x src/bin/xfrin/run_b10-xfrin.sh
chmod +x src/bin/xfrout/run_b10-xfrout.sh
chmod +x src/bin/zonemgr/run_b10-zonemgr.sh
+ chmod +x src/bin/bind10/bind10
chmod +x src/bin/bind10/run_bind10.sh
chmod +x src/bin/cmdctl/tests/cmdctl_test
chmod +x src/bin/dbutil/run_dbutil.sh
diff --git a/src/bin/bind10/Makefile.am b/src/bin/bind10/Makefile.am
index 162d6c9..728fc4a 100644
--- a/src/bin/bind10/Makefile.am
+++ b/src/bin/bind10/Makefile.am
@@ -15,14 +15,17 @@ noinst_SCRIPTS = run_bind10.sh
bind10dir = $(pkgdatadir)
bind10_DATA = init.spec
-EXTRA_DIST = init.spec
+EXTRA_DIST = init.spec bind10.in
-man_MANS = b10-init.8
-DISTCLEANFILES = $(man_MANS)
-EXTRA_DIST += $(man_MANS) b10-init.xml init_messages.mes
+man_MANS = b10-init.8 bind10.8
+DISTCLEANFILES = $(man_MANS) bind10
+EXTRA_DIST += $(man_MANS) b10-init.xml bind10.xml init_messages.mes
if GENERATE_DOCS
+bind10.8: bind10.xml
+ @XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/bind10.xml
+
b10-init.8: b10-init.xml
@XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-init.xml
@@ -46,10 +49,10 @@ $(PYTHON_LOGMSGPKG_DIR)/work/init_messages.py : init_messages.mes
-d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/init_messages.mes
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
-b10-init: b10-init.py $(PYTHON_LOGMSGPKG_DIR)/work/init_messages.py
+b10-init: init.py $(PYTHON_LOGMSGPKG_DIR)/work/init_messages.py
$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
-e "s|@@LIBDIR@@|$(libdir)|" \
- -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" b10-init.py >$@
+ -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" init.py >$@
chmod a+x $@
pytest:
diff --git a/src/bin/bind10/README b/src/bin/bind10/README
index d0098c3..d75c0cd 100644
--- a/src/bin/bind10/README
+++ b/src/bin/bind10/README
@@ -1,4 +1,5 @@
-This directory contains the source for the "Init of Bind" program.
+This directory contains the source for the "b10-init" program, as well as
+the "bind10" script that runs it.
Files:
Makefile.am - build information
diff --git a/src/bin/bind10/b10-init.py.in b/src/bin/bind10/b10-init.py.in
deleted file mode 100755
index 799aafd..0000000
--- a/src/bin/bind10/b10-init.py.in
+++ /dev/null
@@ -1,1320 +0,0 @@
-#!@PYTHON@
-
-# Copyright (C) 2010,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.
-
-"""
-This file implements the b10-init program.
-
-Its purpose is to start up the BIND 10 system, and then manage the
-processes, by starting and stopping processes, plus restarting
-processes that exit.
-
-To start the system, it first runs the c-channel program (msgq), then
-connects to that. It then runs the configuration manager, and reads
-its own configuration. Then it proceeds to starting other modules.
-
-The Python subprocess module is used for starting processes, but
-because this is not efficient for managing groups of processes,
-SIGCHLD signals are caught and processed using the signal module.
-
-Most of the logic is contained in the Init class. However, since Python
-requires that signal processing happen in the main thread, we do
-signal handling outside of that class, in the code running for
-__main__.
-"""
-
-import sys; sys.path.append ('@@PYTHONPATH@@')
-import os
-
-# If B10_FROM_SOURCE is set in the environment, we use data files
-# from a directory relative to that, otherwise we use the ones
-# installed on the system
-if "B10_FROM_SOURCE" in os.environ:
- SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] +\
- "/src/bin/bind10/init.spec"
-else:
- PREFIX = "@prefix@"
- DATAROOTDIR = "@datarootdir@"
- SPECFILE_LOCATION = "@datadir@/@PACKAGE@/init.spec"\
- .replace("${datarootdir}", DATAROOTDIR)\
- .replace("${prefix}", PREFIX)
-
-import subprocess
-import signal
-import re
-import errno
-import time
-import select
-import random
-import socket
-from optparse import OptionParser, OptionValueError
-import io
-import pwd
-import posix
-import copy
-
-from bind10_config import LIBEXECPATH
-import bind10_config
-import isc.cc
-import isc.util.process
-import isc.net.parse
-import isc.log
-from isc.log_messages.init_messages import *
-import isc.bind10.component
-import isc.bind10.special_component
-import isc.bind10.socket_cache
-import libutil_io_python
-import tempfile
-
-isc.log.init("b10-init", buffer=True)
-logger = isc.log.Logger("init")
-
-# Pending system-wide debug level definitions, the ones we
-# use here are hardcoded for now
-DBG_PROCESS = logger.DBGLVL_TRACE_BASIC
-DBG_COMMANDS = logger.DBGLVL_TRACE_DETAIL
-
-# Messages sent over the unix domain socket to indicate if it is followed by a real socket
-CREATOR_SOCKET_OK = b"1\n"
-CREATOR_SOCKET_UNAVAILABLE = b"0\n"
-
-# RCodes of known exceptions for the get_token command
-CREATOR_SOCKET_ERROR = 2
-CREATOR_SHARE_ERROR = 3
-
-# Assign this process some longer name
-isc.util.process.rename(sys.argv[0])
-
-# This is the version that gets displayed to the user.
-# The VERSION string consists of the module name, the module version
-# number, and the overall BIND 10 version number (set in configure.ac).
-VERSION = "bind10 20110223 (BIND 10 @PACKAGE_VERSION@)"
-
-# This is for boot_time of Init
-_BASETIME = time.gmtime()
-
-# Detailed error message commonly used on startup failure, possibly due to
-# permission issue regarding log lock file. We dump verbose message because
-# it may not be clear exactly what to do if it simply says
-# "failed to open <filename>: permission denied"
-NOTE_ON_LOCK_FILE = """\
-TIP: if this is about permission error for a lock file, check if the directory
-of the file is writable for the user of the bind10 process; often you need
-to start bind10 as a super user. Also, if you specify the -u option to
-change the user and group, the directory must be writable for the group,
-and the created lock file must be writable for that user. Finally, make sure
-the lock file is not left in the directly before restarting.
-"""
-
-class ProcessInfoError(Exception): pass
-
-class ChangeUserError(Exception):
- '''Exception raised when setuid/setgid fails.
-
- When raised, it's expected to be propagated via underlying component
- management modules to the top level so that it will help provide useful
- fatal error message.
-
- '''
- pass
-
-class ProcessInfo:
- """Information about a process"""
-
- dev_null = open(os.devnull, "w")
-
- 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 _preexec_work(self):
- """Function used before running a program that needs to run as a
- different user."""
- # First, put us into a separate process group so we don't get
- # SIGINT signals on Ctrl-C (b10-init will shut everthing down by
- # other means).
- os.setpgrp()
-
- def _spawn(self):
- if self.dev_null_stdout:
- spawn_stdout = self.dev_null
- else:
- spawn_stdout = None
- if self.dev_null_stderr:
- spawn_stderr = self.dev_null
- else:
- spawn_stderr = None
- # Environment variables for the child process will be a copy of those
- # of the b10-init process with any additional specific variables given
- # on construction (self.env).
- spawn_env = copy.deepcopy(os.environ)
- spawn_env.update(self.env)
- spawn_env['PATH'] = LIBEXECPATH + ':' + spawn_env['PATH']
- self.process = subprocess.Popen(self.args,
- stdin=subprocess.PIPE,
- stdout=spawn_stdout,
- stderr=spawn_stderr,
- close_fds=True,
- env=spawn_env,
- preexec_fn=self._preexec_work)
- self.pid = self.process.pid
-
- # spawn() and respawn() are the same for now, but in the future they
- # may have different functionality
- def spawn(self):
- self._spawn()
-
- def respawn(self):
- self._spawn()
-
-class CChannelConnectError(Exception): pass
-
-class ProcessStartError(Exception): pass
-
-class Init:
- """Init of BIND class."""
-
- def __init__(self, msgq_socket_file=None, data_path=None,
- config_filename=None, clear_config=False,
- verbose=False, nokill=False, setuid=None, setgid=None,
- username=None, cmdctl_port=None, wait_time=10):
- """
- Initialize the Init of BIND. This is a singleton (only one can run).
-
- The msgq_socket_file specifies the UNIX domain socket file that the
- msgq process listens on. If verbose is True, then b10-init reports
- what it is doing.
-
- Data path and config filename are passed through to config manager
- (if provided) and specify the config file to be used.
-
- The cmdctl_port is passed to cmdctl and specify on which port it
- should listen.
-
- wait_time controls the amount of time (in seconds) that Init waits
- for selected processes to initialize before continuing with the
- initialization. Currently this is only the configuration manager.
- """
- self.cc_session = None
- self.ccs = None
- self.curproc = None
- self.msgq_socket_file = msgq_socket_file
- self.component_config = {}
- # Some time in future, it may happen that a single component has
- # multple processes (like a pipeline-like component). If so happens,
- # name "components" may be inapropriate. But as the code isn't probably
- # completely ready for it, we leave it at components for now. We also
- # want to support multiple instances of a single component. If it turns
- # out that we'll have a single component with multiple same processes
- # or if we start multiple components with the same configuration (we do
- # this now, but it might change) is an open question.
- self.components = {}
- # Simply list of components that died and need to wait for a
- # restart. Components manage their own restart schedule now
- self.components_to_restart = []
- self.runnable = False
- self.__uid = setuid
- self.__gid = setgid
- self.username = username
- self.verbose = verbose
- self.nokill = nokill
- self.data_path = data_path
- self.config_filename = config_filename
- self.clear_config = clear_config
- self.cmdctl_port = cmdctl_port
- self.wait_time = wait_time
- self.msgq_timeout = 5
-
- # _run_under_unittests is only meant to be used when testing. It
- # bypasses execution of some code to help with testing.
- self._run_under_unittests = False
-
- self._component_configurator = isc.bind10.component.Configurator(self,
- isc.bind10.special_component.get_specials())
- # The priorities here make them start in the correct order. First
- # the socket creator (which would drop root privileges by then),
- # then message queue and after that the config manager (which uses
- # the config manager)
- self.__core_components = {
- 'sockcreator': {
- 'kind': 'core',
- 'special': 'sockcreator',
- 'priority': 200
- },
- 'msgq': {
- 'kind': 'core',
- 'special': 'msgq',
- 'priority': 199
- },
- 'cfgmgr': {
- 'kind': 'core',
- 'special': 'cfgmgr',
- 'priority': 198
- }
- }
- self.__started = False
- self.exitcode = 0
-
- # If -v was set, enable full debug logging.
- if self.verbose:
- logger.set_severity("DEBUG", 99)
- # This is set in init_socket_srv
- self._socket_path = None
- self._socket_cache = None
- self._tmpdir = None
- self._srv_socket = None
- self._unix_sockets = {}
-
- def __propagate_component_config(self, config):
- comps = dict(config)
- # Fill in the core components, so they stay alive
- for comp in self.__core_components:
- if comp in comps:
- raise Exception(comp + " is core component managed by " +
- "b10-init, do not set it")
- comps[comp] = self.__core_components[comp]
- # Update the configuration
- self._component_configurator.reconfigure(comps)
-
- def change_user(self):
- '''Change the user and group to those specified on construction.
-
- This method is expected to be called by a component on initial
- startup when the system is ready to switch the user and group
- (i.e., once all components that need the privilege of the original
- user have started).
- '''
- try:
- if self.__gid is not None:
- logger.info(BIND10_SETGID, self.__gid)
- posix.setgid(self.__gid)
- except Exception as ex:
- raise ChangeUserError('failed to change group: ' + str(ex))
-
- try:
- if self.__uid is not None:
- posix.setuid(self.__uid)
- # We use one-shot logger after setuid here. This will
- # detect any permission issue regarding logging due to the
- # result of setuid at the earliest opportunity.
- isc.log.Logger("b10-init").info(BIND10_SETUID, self.__uid)
- except Exception as ex:
- raise ChangeUserError('failed to change user: ' + str(ex))
-
- def config_handler(self, new_config):
- # If this is initial update, don't do anything now, leave it to startup
- if not self.runnable:
- return
- logger.debug(DBG_COMMANDS, BIND10_RECEIVED_NEW_CONFIGURATION,
- new_config)
- try:
- if 'components' in new_config:
- self.__propagate_component_config(new_config['components'])
- return isc.config.ccsession.create_answer(0)
- except Exception as e:
- return isc.config.ccsession.create_answer(1, str(e))
-
- def get_processes(self):
- pids = list(self.components.keys())
- pids.sort()
- process_list = [ ]
- for pid in pids:
- process_list.append([pid, self.components[pid].name(),
- self.components[pid].address()])
- return process_list
-
- def _get_stats_data(self):
- return { 'boot_time':
- time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME)
- }
-
- def command_handler(self, command, args):
- logger.debug(DBG_COMMANDS, BIND10_RECEIVED_COMMAND, command)
- answer = isc.config.ccsession.create_answer(1, "command not implemented")
- if type(command) != str:
- answer = isc.config.ccsession.create_answer(1, "bad command")
- else:
- if command == "shutdown":
- self.runnable = False
- answer = isc.config.ccsession.create_answer(0)
- elif command == "getstats":
- answer = isc.config.ccsession.create_answer(
- 0, self._get_stats_data())
- elif command == "ping":
- answer = isc.config.ccsession.create_answer(0, "pong")
- elif command == "show_processes":
- answer = isc.config.ccsession. \
- create_answer(0, self.get_processes())
- elif command == "get_socket":
- answer = self._get_socket(args)
- elif command == "drop_socket":
- if "token" not in args:
- answer = isc.config.ccsession. \
- create_answer(1, "Missing token parameter")
- else:
- try:
- self._socket_cache.drop_socket(args["token"])
- answer = isc.config.ccsession.create_answer(0)
- except Exception as e:
- answer = isc.config.ccsession.create_answer(1, str(e))
- else:
- answer = isc.config.ccsession.create_answer(1,
- "Unknown command")
- return answer
-
- def kill_started_components(self):
- """
- Called as part of the exception handling when a process fails to
- start, this runs through the list of started processes, killing
- each one. It then clears that list.
- """
- logger.info(BIND10_KILLING_ALL_PROCESSES)
- self.__kill_children(True)
- self.components = {}
-
- def _read_bind10_config(self):
- """
- Reads the parameters associated with the Init module itself.
-
- This means the list of components we should start now.
-
- This could easily be combined into start_all_processes, but
- it stays because of historical reasons and because the tests
- replace the method sometimes.
- """
- logger.info(BIND10_READING_INIT_CONFIGURATION)
-
- config_data = self.ccs.get_full_config()
- self.__propagate_component_config(config_data['components'])
-
- def log_starting(self, process, port = None, address = None):
- """
- A convenience function to output a "Starting xxx" message if the
- logging is set to DEBUG with debuglevel DBG_PROCESS or higher.
- Putting this into a separate method ensures
- that the output form is consistent across all processes.
-
- The process name (passed as the first argument) is put into
- self.curproc, and is used to indicate which process failed to
- start if there is an error (and is used in the "Started" message
- on success). The optional port and address information are
- appended to the message (if present).
- """
- self.curproc = process
- if port is None and address is None:
- logger.info(BIND10_STARTING_PROCESS, self.curproc)
- elif address is None:
- logger.info(BIND10_STARTING_PROCESS_PORT, self.curproc,
- port)
- else:
- logger.info(BIND10_STARTING_PROCESS_PORT_ADDRESS,
- self.curproc, address, port)
-
- def log_started(self, pid = None):
- """
- A convenience function to output a 'Started xxxx (PID yyyy)'
- message. As with starting_message(), this ensures a consistent
- format.
- """
- if pid is None:
- logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS, self.curproc)
- else:
- logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS_PID, self.curproc, pid)
-
- def process_running(self, msg, who):
- """
- Some processes return a message to the Init after they have
- started to indicate that they are running. The form of the
- message is a dictionary with contents {"running:", "<process>"}.
- This method checks the passed message and returns True if the
- "who" process is contained in the message (so is presumably
- running). It returns False for all other conditions and will
- log an error if appropriate.
- """
- if msg is not None:
- try:
- if msg["running"] == who:
- return True
- else:
- logger.error(BIND10_STARTUP_UNEXPECTED_MESSAGE, msg)
- except:
- logger.error(BIND10_STARTUP_UNRECOGNISED_MESSAGE, msg)
-
- return False
-
- # The next few methods start the individual processes of BIND-10. They
- # are called via start_all_processes(). If any fail, an exception is
- # raised which is caught by the caller of start_all_processes(); this kills
- # processes started up to that point before terminating the program.
-
- def _make_process_info(self, name, args, env,
- dev_null_stdout=False, dev_null_stderr=False):
- """
- Wrapper around ProcessInfo(), useful to override
- ProcessInfo() creation during testing.
- """
- return ProcessInfo(name, args, env, dev_null_stdout, dev_null_stderr)
-
- def start_msgq(self):
- """
- Start the message queue and connect to the command channel.
- """
- self.log_starting("b10-msgq")
- msgq_proc = self._make_process_info("b10-msgq", ["b10-msgq"],
- self.c_channel_env,
- True, not self.verbose)
- msgq_proc.spawn()
- self.log_started(msgq_proc.pid)
-
- # Now connect to the c-channel
- cc_connect_start = time.time()
- while self.cc_session is None:
- # if we are run under unittests, break
- if self._run_under_unittests:
- break
-
- # if we have been trying for "a while" give up
- if (time.time() - cc_connect_start) > self.msgq_timeout:
- if msgq_proc.process:
- msgq_proc.process.kill()
- logger.error(BIND10_CONNECTING_TO_CC_FAIL)
- raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
-
- # try to connect, and if we can't wait a short while
- try:
- self.cc_session = isc.cc.Session(self.msgq_socket_file)
- except isc.cc.session.SessionError:
- time.sleep(0.1)
-
- # Subscribe to the message queue. The only messages we expect to receive
- # on this channel are once relating to process startup.
- if self.cc_session is not None:
- self.cc_session.group_subscribe("Init")
-
- return msgq_proc
-
- def start_cfgmgr(self):
- """
- Starts the configuration manager process
- """
- self.log_starting("b10-cfgmgr")
- args = ["b10-cfgmgr"]
- if self.data_path is not None:
- args.append("--data-path=" + self.data_path)
- if self.config_filename is not None:
- args.append("--config-filename=" + self.config_filename)
- if self.clear_config:
- args.append("--clear-config")
- bind_cfgd = self._make_process_info("b10-cfgmgr", args,
- self.c_channel_env)
- bind_cfgd.spawn()
- self.log_started(bind_cfgd.pid)
-
- # Wait for the configuration manager to start up as
- # subsequent initialization cannot proceed without it. The
- # time to wait can be set on the command line.
- time_remaining = self.wait_time
- msg, env = self.cc_session.group_recvmsg()
- while time_remaining > 0 and not self.process_running(msg, "ConfigManager"):
- logger.debug(DBG_PROCESS, BIND10_WAIT_CFGMGR)
- time.sleep(1)
- time_remaining = time_remaining - 1
- msg, env = self.cc_session.group_recvmsg()
-
- if not self.process_running(msg, "ConfigManager"):
- raise ProcessStartError("Configuration manager process has not started")
-
- return bind_cfgd
-
- def start_ccsession(self, c_channel_env):
- """
- Start the CC Session
-
- The argument c_channel_env is unused but is supplied to keep the
- argument list the same for all start_xxx methods.
-
- With regards to logging, note that as the CC session is not a
- process, the log_starting/log_started methods are not used.
- """
- logger.info(BIND10_STARTING_CC)
- self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
- self.config_handler,
- self.command_handler,
- socket_file = self.msgq_socket_file)
- self.ccs.start()
- logger.debug(DBG_PROCESS, BIND10_STARTED_CC)
-
- # A couple of utility methods for starting processes...
-
- def start_process(self, name, args, c_channel_env, port=None, address=None):
- """
- Given a set of command arguments, start the process and output
- appropriate log messages. If the start is successful, the process
- is added to the list of started processes.
-
- The port and address arguments are for log messages only.
- """
- self.log_starting(name, port, address)
- newproc = self._make_process_info(name, args, c_channel_env)
- newproc.spawn()
- self.log_started(newproc.pid)
- return newproc
-
- def register_process(self, pid, component):
- """
- Put another process into b10-init to watch over it. When the process
- dies, the component.failed() is called with the exit code.
-
- It is expected the info is a isc.bind10.component.BaseComponent
- subclass (or anything having the same interface).
- """
- self.components[pid] = component
-
- def start_simple(self, name):
- """
- Most of the BIND-10 processes are started with the command:
-
- <process-name> [-v]
-
- ... where -v is appended if verbose is enabled. This method
- generates the arguments from the name and starts the process.
-
- The port and address arguments are for log messages only.
- """
- # Set up the command arguments.
- args = [name]
- if self.verbose:
- args += ['-v']
-
- # ... and start the process
- return self.start_process(name, args, self.c_channel_env)
-
- # The next few methods start up the rest of the BIND-10 processes.
- # Although many of these methods are little more than a call to
- # start_simple, they are retained (a) for testing reasons and (b) as a place
- # where modifications can be made if the process start-up sequence changes
- # for a given process.
-
- def start_auth(self):
- """
- Start the Authoritative server
- """
- authargs = ['b10-auth']
- if self.verbose:
- authargs += ['-v']
-
- # ... and start
- return self.start_process("b10-auth", authargs, self.c_channel_env)
-
- def start_resolver(self):
- """
- Start the Resolver. At present, all these arguments and switches
- are pure speculation. As with the auth daemon, they should be
- read from the configuration database.
- """
- self.curproc = "b10-resolver"
- # XXX: this must be read from the configuration manager in the future
- resargs = ['b10-resolver']
- if self.verbose:
- resargs += ['-v']
-
- # ... and start
- return self.start_process("b10-resolver", resargs, self.c_channel_env)
-
- def start_cmdctl(self):
- """
- Starts the command control process
- """
- args = ["b10-cmdctl"]
- if self.cmdctl_port is not None:
- args.append("--port=" + str(self.cmdctl_port))
- if self.verbose:
- args.append("-v")
- return self.start_process("b10-cmdctl", args, self.c_channel_env,
- self.cmdctl_port)
-
- def start_all_components(self):
- """
- Starts up all the components. Any exception generated during the
- starting of the components is handled by the caller.
- """
- # Start the real core (sockcreator, msgq, cfgmgr)
- self._component_configurator.startup(self.__core_components)
-
- # Connect to the msgq. This is not a process, so it's not handled
- # inside the configurator.
- self.start_ccsession(self.c_channel_env)
-
- # Extract the parameters associated with Init. This can only be
- # done after the CC Session is started. Note that the logging
- # configuration may override the "-v" switch set on the command line.
- self._read_bind10_config()
-
- # TODO: Return the dropping of privileges
-
- def startup(self):
- """
- Start the Init instance.
-
- Returns None if successful, otherwise an string describing the
- problem.
- """
- # Try to connect to the c-channel daemon, to see if it is already
- # running
- c_channel_env = {}
- if self.msgq_socket_file is not None:
- c_channel_env["BIND10_MSGQ_SOCKET_FILE"] = self.msgq_socket_file
- logger.debug(DBG_PROCESS, BIND10_CHECK_MSGQ_ALREADY_RUNNING)
- try:
- self.cc_session = isc.cc.Session(self.msgq_socket_file)
- logger.fatal(BIND10_MSGQ_ALREADY_RUNNING)
- return "b10-msgq already running, or socket file not cleaned , cannot start"
- except isc.cc.session.SessionError:
- # this is the case we want, where the msgq is not running
- pass
-
- # Start all components. If any one fails to start, kill all started
- # components and exit with an error indication.
- try:
- self.c_channel_env = c_channel_env
- self.start_all_components()
- except ChangeUserError as e:
- self.kill_started_components()
- return str(e) + '; ' + NOTE_ON_LOCK_FILE.replace('\n', ' ')
- except Exception as e:
- self.kill_started_components()
- return "Unable to start " + self.curproc + ": " + str(e)
-
- # Started successfully
- self.runnable = True
- self.__started = True
- return None
-
- def stop_process(self, process, recipient, pid):
- """
- Stop the given process, friendly-like. The process is the name it has
- (in logs, etc), the recipient is the address on msgq. The pid is the
- pid of the process (if we have multiple processes of the same name,
- it might want to choose if it is for this one).
- """
- logger.info(BIND10_STOP_PROCESS, process)
- self.cc_session.group_sendmsg(isc.config.ccsession.
- create_command('shutdown', {'pid': pid}),
- recipient, recipient)
-
- def component_shutdown(self, exitcode=0):
- """
- Stop the Init instance from a components' request. The exitcode
- indicates the desired exit code.
-
- If we did not start yet, it raises an exception, which is meant
- to propagate through the component and configurator to the startup
- routine and abort the startup immediately. If it is started up already,
- we just mark it so we terminate soon.
-
- It does set the exit code in both cases.
- """
- self.exitcode = exitcode
- if not self.__started:
- raise Exception("Component failed during startup");
- else:
- self.runnable = False
-
- def shutdown(self):
- """Stop the Init instance."""
- logger.info(BIND10_SHUTDOWN)
- # If ccsession is still there, inform rest of the system this module
- # is stopping. Since everything will be stopped shortly, this is not
- # really necessary, but this is done to reflect that b10-init is also
- # 'just' a module.
- self.ccs.send_stopping()
-
- # try using the BIND 10 request to stop
- try:
- self._component_configurator.shutdown()
- except:
- pass
- # XXX: some delay probably useful... how much is uncertain
- # I have changed the delay from 0.5 to 1, but sometime it's
- # still not enough.
- time.sleep(1)
- self.reap_children()
-
- # Send TERM and KILL signals to modules if we're not prevented
- # from doing so
- if not self.nokill:
- # next try sending a SIGTERM
- self.__kill_children(False)
- # finally, send SIGKILL (unmaskable termination) until everybody
- # dies
- while self.components:
- # XXX: some delay probably useful... how much is uncertain
- time.sleep(0.1)
- self.reap_children()
- self.__kill_children(True)
- logger.info(BIND10_SHUTDOWN_COMPLETE)
-
- def __kill_children(self, forceful):
- '''Terminate remaining subprocesses by sending a signal.
-
- The forceful paramter will be passed Component.kill().
- This is a dedicated subroutine of shutdown(), just to unify two
- similar cases.
-
- '''
- logmsg = BIND10_SEND_SIGKILL if forceful else BIND10_SEND_SIGTERM
- # We need to make a copy of values as the components may be modified
- # in the loop.
- for component in list(self.components.values()):
- logger.info(logmsg, component.name(), component.pid())
- try:
- component.kill(forceful)
- except OSError as ex:
- # If kill() failed due to EPERM, it doesn't make sense to
- # keep trying, so we just log the fact and forget that
- # component. Ignore other OSErrors (usually ESRCH because
- # the child finally exited)
- signame = "SIGKILL" if forceful else "SIGTERM"
- logger.info(BIND10_SEND_SIGNAL_FAIL, signame,
- component.name(), component.pid(), ex)
- if ex.errno == errno.EPERM:
- del self.components[component.pid()]
-
- def _get_process_exit_status(self):
- return os.waitpid(-1, os.WNOHANG)
-
- def reap_children(self):
- """Check to see if any of our child processes have exited,
- and note this for later handling.
- """
- while True:
- try:
- (pid, exit_status) = self._get_process_exit_status()
- except OSError as o:
- if o.errno == errno.ECHILD:
- break
- # XXX: should be impossible to get any other error here
- raise
- if pid == 0:
- break
- if pid in self.components:
- # One of the components we know about. Get information on it.
- component = self.components.pop(pid)
- logger.info(BIND10_PROCESS_ENDED, component.name(), pid,
- exit_status)
- if component.is_running() and self.runnable:
- # Tell it it failed. But only if it matters (we are
- # not shutting down and the component considers itself
- # to be running.
- component_restarted = component.failed(exit_status);
- # if the process wants to be restarted, but not just yet,
- # it returns False
- if not component_restarted:
- self.components_to_restart.append(component)
- else:
- logger.info(BIND10_UNKNOWN_CHILD_PROCESS_ENDED, pid)
-
- def restart_processes(self):
- """
- Restart any dead processes:
-
- * Returns the time when the next process is ready to be restarted.
- * If the server is shutting down, returns 0.
- * If there are no processes, returns None.
-
- The values returned can be safely passed into select() as the
- timeout value.
-
- """
- if not self.runnable:
- return 0
- still_dead = []
- # keep track of the first time we need to check this queue again,
- # if at all
- next_restart_time = None
- now = time.time()
- for component in self.components_to_restart:
- # If the component was removed from the configurator between since
- # scheduled to restart, just ignore it. The object will just be
- # dropped here.
- if not self._component_configurator.has_component(component):
- logger.info(BIND10_RESTART_COMPONENT_SKIPPED, component.name())
- elif not component.restart(now):
- still_dead.append(component)
- if next_restart_time is None or\
- next_restart_time > component.get_restart_time():
- next_restart_time = component.get_restart_time()
- self.components_to_restart = still_dead
-
- return next_restart_time
-
- def _get_socket(self, args):
- """
- Implementation of the get_socket CC command. It asks the cache
- to provide the token and sends the information back.
- """
- try:
- try:
- addr = isc.net.parse.addr_parse(args['address'])
- port = isc.net.parse.port_parse(args['port'])
- protocol = args['protocol']
- if protocol not in ['UDP', 'TCP']:
- raise ValueError("Protocol must be either UDP or TCP")
- share_mode = args['share_mode']
- if share_mode not in ['ANY', 'SAMEAPP', 'NO']:
- raise ValueError("Share mode must be one of ANY, SAMEAPP" +
- " or NO")
- share_name = args['share_name']
- except KeyError as ke:
- return \
- isc.config.ccsession.create_answer(1,
- "Missing parameter " +
- str(ke))
-
- # FIXME: This call contains blocking IPC. It is expected to be
- # short, but if it turns out to be problem, we'll need to do
- # something about it.
- token = self._socket_cache.get_token(protocol, addr, port,
- share_mode, share_name)
- return isc.config.ccsession.create_answer(0, {
- 'token': token,
- 'path': self._socket_path
- })
- except isc.bind10.socket_cache.SocketError as e:
- return isc.config.ccsession.create_answer(CREATOR_SOCKET_ERROR,
- str(e))
- except isc.bind10.socket_cache.ShareError as e:
- return isc.config.ccsession.create_answer(CREATOR_SHARE_ERROR,
- str(e))
- except Exception as e:
- return isc.config.ccsession.create_answer(1, str(e))
-
- def socket_request_handler(self, token, unix_socket):
- """
- This function handles a token that comes over a unix_domain socket.
- The function looks into the _socket_cache and sends the socket
- identified by the token back over the unix_socket.
- """
- try:
- token = str(token, 'ASCII') # Convert from bytes to str
- fd = self._socket_cache.get_socket(token, unix_socket.fileno())
- # FIXME: These two calls are blocking in their nature. An OS-level
- # buffer is likely to be large enough to hold all these data, but
- # if it wasn't and the remote application got stuck, we would have
- # a problem. If there appear such problems, we should do something
- # about it.
- unix_socket.sendall(CREATOR_SOCKET_OK)
- libutil_io_python.send_fd(unix_socket.fileno(), fd)
- except Exception as e:
- logger.info(BIND10_NO_SOCKET, token, e)
- unix_socket.sendall(CREATOR_SOCKET_UNAVAILABLE)
-
- def socket_consumer_dead(self, unix_socket):
- """
- This function handles when a unix_socket closes. This means all
- sockets sent to it are to be considered closed. This function signals
- so to the _socket_cache.
- """
- logger.info(BIND10_LOST_SOCKET_CONSUMER, unix_socket.fileno())
- try:
- self._socket_cache.drop_application(unix_socket.fileno())
- except ValueError:
- # This means the application holds no sockets. It's harmless, as it
- # can happen in real life - for example, it requests a socket, but
- # get_socket doesn't find it, so the application dies. It should be
- # rare, though.
- pass
-
- def set_creator(self, creator):
- """
- Registeres a socket creator into the b10-init. The socket creator is not
- used directly, but through a cache. The cache is created in this
- method.
-
- If called more than once, it raises a ValueError.
- """
- if self._socket_cache is not None:
- raise ValueError("A creator was inserted previously")
- self._socket_cache = isc.bind10.socket_cache.Cache(creator)
-
- def init_socket_srv(self):
- """
- Creates and listens on a unix-domain socket to be able to send out
- the sockets.
-
- This method should be called after switching user, or the switched
- applications won't be able to access the socket.
- """
- self._srv_socket = socket.socket(socket.AF_UNIX)
- # We create a temporary directory somewhere safe and unique, to avoid
- # the need to find the place ourself or bother users. Also, this
- # secures the socket on some platforms, as it creates a private
- # directory.
- self._tmpdir = tempfile.mkdtemp(prefix='sockcreator-')
- # Get the name
- self._socket_path = os.path.join(self._tmpdir, "sockcreator")
- # And bind the socket to the name
- self._srv_socket.bind(self._socket_path)
- self._srv_socket.listen(5)
-
- def remove_socket_srv(self):
- """
- Closes and removes the listening socket and the directory where it
- lives, as we created both.
-
- It does nothing if the _srv_socket is not set (eg. it was not yet
- initialized).
- """
- if self._srv_socket is not None:
- self._srv_socket.close()
- if os.path.exists(self._socket_path):
- os.remove(self._socket_path)
- if os.path.isdir(self._tmpdir):
- os.rmdir(self._tmpdir)
-
- def _srv_accept(self):
- """
- Accept a socket from the unix domain socket server and put it to the
- others we care about.
- """
- (socket, conn) = self._srv_socket.accept()
- self._unix_sockets[socket.fileno()] = (socket, b'')
-
- def _socket_data(self, socket_fileno):
- """
- This is called when a socket identified by the socket_fileno needs
- attention. We try to read data from there. If it is closed, we remove
- it.
- """
- (sock, previous) = self._unix_sockets[socket_fileno]
- while True:
- try:
- data = sock.recv(1, socket.MSG_DONTWAIT)
- except socket.error as se:
- # These two might be different on some systems
- if se.errno == errno.EAGAIN or se.errno == errno.EWOULDBLOCK:
- # No more data now. Oh, well, just store what we have.
- self._unix_sockets[socket_fileno] = (sock, previous)
- return
- else:
- data = b'' # Pretend it got closed
- if len(data) == 0: # The socket got to it's end
- del self._unix_sockets[socket_fileno]
- self.socket_consumer_dead(sock)
- sock.close()
- return
- else:
- if data == b"\n":
- # Handle this token and clear it
- self.socket_request_handler(previous, sock)
- previous = b''
- else:
- previous += data
-
- def run(self, wakeup_fd):
- """
- The main loop, waiting for sockets, commands and dead processes.
- Runs as long as the runnable is true.
-
- The wakeup_fd descriptor is the read end of pipe where CHLD signal
- handler writes.
- """
- ccs_fd = self.ccs.get_socket().fileno()
- while self.runnable:
- # clean up any processes that exited
- self.reap_children()
- next_restart = self.restart_processes()
- if next_restart is None:
- wait_time = None
- else:
- wait_time = max(next_restart - time.time(), 0)
-
- # select() can raise EINTR when a signal arrives,
- # even if they are resumable, so we have to catch
- # the exception
- try:
- (rlist, wlist, xlist) = \
- select.select([wakeup_fd, ccs_fd,
- self._srv_socket.fileno()] +
- list(self._unix_sockets.keys()), [], [],
- wait_time)
- except select.error as err:
- if err.args[0] == errno.EINTR:
- (rlist, wlist, xlist) = ([], [], [])
- else:
- logger.fatal(BIND10_SELECT_ERROR, err)
- break
-
- for fd in rlist + xlist:
- if fd == ccs_fd:
- try:
- self.ccs.check_command()
- except isc.cc.session.ProtocolError:
- logger.fatal(BIND10_MSGQ_DISAPPEARED)
- self.runnable = False
- break
- elif fd == wakeup_fd:
- os.read(wakeup_fd, 32)
- elif fd == self._srv_socket.fileno():
- self._srv_accept()
- elif fd in self._unix_sockets:
- self._socket_data(fd)
-
-# global variables, needed for signal handlers
-options = None
-b10_init = None
-
-def reaper(signal_number, stack_frame):
- """A child process has died (SIGCHLD received)."""
- # don't do anything...
- # the Python signal handler has been set up to write
- # down a pipe, waking up our select() bit
- pass
-
-def get_signame(signal_number):
- """Return the symbolic name for a signal."""
- for sig in dir(signal):
- if sig.startswith("SIG") and sig[3].isalnum():
- if getattr(signal, sig) == signal_number:
- return sig
- return "Unknown signal %d" % signal_number
-
-# XXX: perhaps register atexit() function and invoke that instead
-def fatal_signal(signal_number, stack_frame):
- """We need to exit (SIGINT or SIGTERM received)."""
- global options
- global b10_init
- logger.info(BIND10_RECEIVED_SIGNAL, get_signame(signal_number))
- signal.signal(signal.SIGCHLD, signal.SIG_DFL)
- b10_init.runnable = False
-
-def process_rename(option, opt_str, value, parser):
- """Function that renames the process if it is requested by a option."""
- isc.util.process.rename(value)
-
-def parse_args(args=sys.argv[1:], Parser=OptionParser):
- """
- Function for parsing command line arguments. Returns the
- options object from OptionParser.
- """
- parser = Parser(version=VERSION)
- parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
- type="string", default=None,
- help="UNIX domain socket file the b10-msgq daemon will use")
- parser.add_option("-i", "--no-kill", action="store_true", dest="nokill",
- default=False, help="do not send SIGTERM and SIGKILL signals to modules during shutdown")
- parser.add_option("-u", "--user", dest="user", type="string", default=None,
- help="Change user after startup (must run as root)")
- parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
- help="display more about what is going on")
- parser.add_option("--pretty-name", type="string", action="callback",
- callback=process_rename,
- help="Set the process name (displayed in ps, top, ...)")
- parser.add_option("-c", "--config-file", action="store",
- dest="config_file", default=None,
- help="Configuration database filename")
- parser.add_option("--clear-config", action="store_true",
- dest="clear_config", default=False,
- help="Create backup of the configuration file and " +
- "start with a clean configuration")
- parser.add_option("-p", "--data-path", dest="data_path",
- help="Directory to search for configuration files",
- default=None)
- parser.add_option("--cmdctl-port", dest="cmdctl_port", type="int",
- default=None, help="Port of command control")
- parser.add_option("--pid-file", dest="pid_file", type="string",
- default=None,
- help="file to dump the PID of the BIND 10 process")
- parser.add_option("-w", "--wait", dest="wait_time", type="int",
- default=10, help="Time (in seconds) to wait for config manager to start up")
-
- (options, args) = parser.parse_args(args)
-
- if options.cmdctl_port is not None:
- try:
- isc.net.parse.port_parse(options.cmdctl_port)
- except ValueError as e:
- parser.error(e)
-
- if args:
- parser.print_help()
- sys.exit(1)
-
- return options
-
-def dump_pid(pid_file):
- """
- Dump the PID of the current process to the specified file. If the given
- file is None this function does nothing. If the file already exists,
- the existing content will be removed. If a system error happens in
- creating or writing to the file, the corresponding exception will be
- propagated to the caller.
- """
- if pid_file is None:
- return
- f = open(pid_file, "w")
- f.write('%d\n' % os.getpid())
- f.close()
-
-def unlink_pid_file(pid_file):
- """
- Remove the given file, which is basically expected to be the PID file
- created by dump_pid(). The specified may or may not exist; if it
- doesn't this function does nothing. Other system level errors in removing
- the file will be propagated as the corresponding exception.
- """
- if pid_file is None:
- return
- try:
- os.unlink(pid_file)
- except OSError as error:
- if error.errno is not errno.ENOENT:
- raise
-
-def remove_lock_files():
- """
- Remove various lock files which were created by code such as in the
- logger. This function should be called after BIND 10 shutdown.
- """
-
- lockfiles = ["logger_lockfile"]
-
- lpath = bind10_config.DATA_PATH
- if "B10_FROM_BUILD" in os.environ:
- lpath = os.environ["B10_FROM_BUILD"]
- if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
- lpath = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
- if "B10_LOCKFILE_DIR_FROM_BUILD" in os.environ:
- lpath = os.environ["B10_LOCKFILE_DIR_FROM_BUILD"]
-
- for f in lockfiles:
- fname = lpath + '/' + f
- if os.path.isfile(fname):
- try:
- os.unlink(fname)
- except OSError as e:
- # We catch and ignore permission related error on unlink.
- # This can happen if bind10 started with -u, created a lock
- # file as a privileged user, but the directory is not writable
- # for the changed user. This setup will cause immediate
- # start failure, and we leave verbose error message including
- # the leftover lock file, so it should be acceptable to ignore
- # it (note that it doesn't make sense to log this event at
- # this poitn)
- if e.errno != errno.EPERM and e.errno != errno.EACCES:
- raise
-
- return
-
-def main():
- global options
- global b10_init
- # Enforce line buffering on stdout, even when not a TTY
- sys.stdout = io.TextIOWrapper(sys.stdout.detach(), line_buffering=True)
-
- options = parse_args()
-
- # Announce startup. Making this is the first log message.
- try:
- logger.info(BIND10_STARTING, VERSION)
- except RuntimeError as e:
- sys.stderr.write('ERROR: failed to write the initial log: %s\n' %
- str(e))
- sys.stderr.write(NOTE_ON_LOCK_FILE)
- sys.exit(1)
-
- # Check user ID.
- setuid = None
- setgid = None
- username = None
- if options.user:
- # Try getting information about the user, assuming UID passed.
- try:
- pw_ent = pwd.getpwuid(int(options.user))
- setuid = pw_ent.pw_uid
- setgid = pw_ent.pw_gid
- username = pw_ent.pw_name
- except ValueError:
- pass
- except KeyError:
- pass
-
- # Next try getting information about the user, assuming user name
- # passed.
- # If the information is both a valid user name and user number, we
- # prefer the name because we try it second. A minor point, hopefully.
- try:
- pw_ent = pwd.getpwnam(options.user)
- setuid = pw_ent.pw_uid
- setgid = pw_ent.pw_gid
- username = pw_ent.pw_name
- except KeyError:
- pass
-
- if setuid is None:
- logger.fatal(BIND10_INVALID_USER, options.user)
- sys.exit(1)
-
- # Create wakeup pipe for signal handlers
- wakeup_pipe = os.pipe()
- signal.set_wakeup_fd(wakeup_pipe[1])
-
- # Set signal handlers for catching child termination, as well
- # as our own demise.
- signal.signal(signal.SIGCHLD, reaper)
- signal.siginterrupt(signal.SIGCHLD, False)
- signal.signal(signal.SIGINT, fatal_signal)
- signal.signal(signal.SIGTERM, fatal_signal)
-
- # Block SIGPIPE, as we don't want it to end this process
- signal.signal(signal.SIGPIPE, signal.SIG_IGN)
-
- try:
- b10_init = Init(options.msgq_socket_file, options.data_path,
- options.config_file, options.clear_config,
- options.verbose, options.nokill,
- setuid, setgid, username, options.cmdctl_port,
- options.wait_time)
- startup_result = b10_init.startup()
- if startup_result:
- logger.fatal(BIND10_STARTUP_ERROR, startup_result)
- sys.exit(1)
- b10_init.init_socket_srv()
- logger.info(BIND10_STARTUP_COMPLETE)
- dump_pid(options.pid_file)
-
- # Let it run
- b10_init.run(wakeup_pipe[0])
-
- # shutdown
- signal.signal(signal.SIGCHLD, signal.SIG_DFL)
- b10_init.shutdown()
- finally:
- # Clean up the filesystem
- unlink_pid_file(options.pid_file)
- remove_lock_files()
- if b10_init is not None:
- b10_init.remove_socket_srv()
- sys.exit(b10_init.exitcode)
-
-if __name__ == "__main__":
- main()
diff --git a/src/bin/bind10/b10-init.xml b/src/bin/bind10/b10-init.xml
index f7fb0a5..dc8cf7f 100644
--- a/src/bin/bind10/b10-init.xml
+++ b/src/bin/bind10/b10-init.xml
@@ -31,7 +31,7 @@
<refnamediv>
<refname>b10-init</refname>
- <refpurpose>BIND 10 init process</refpurpose>
+ <refpurpose>BIND 10 Init process</refpurpose>
</refnamediv>
<docinfo>
@@ -368,7 +368,7 @@ xfrin
<!-- TODO: formating -->
<para>
- The <varname>init</varname> configuration commands are:
+ The <varname>Init</varname> configuration commands are:
</para>
<!-- TODO -->
diff --git a/src/bin/bind10/bind10 b/src/bin/bind10/bind10
deleted file mode 100755
index ee2964a..0000000
--- a/src/bin/bind10/bind10
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-exec b10-init $*
diff --git a/src/bin/bind10/bind10.in b/src/bin/bind10/bind10.in
new file mode 100755
index 0000000..fe8c0cc
--- /dev/null
+++ b/src/bin/bind10/bind10.in
@@ -0,0 +1,4 @@
+#!/bin/sh
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+exec @libexecdir@/@PACKAGE@/b10-init $*
diff --git a/src/bin/bind10/bind10.xml b/src/bin/bind10/bind10.xml
new file mode 100644
index 0000000..f366554
--- /dev/null
+++ b/src/bin/bind10/bind10.xml
@@ -0,0 +1,76 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+ [<!ENTITY mdash "—">]>
+<!--
+ - Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+ -
+ - Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
+ - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ - AND FITNESS. IN NO EVENT SHALL ISC 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.
+-->
+
+<refentry>
+
+ <refentryinfo>
+ <date>April 12, 2012</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>bind10</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo>BIND10</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>bind10</refname>
+ <refpurpose>BIND 10 start script</refpurpose>
+ </refnamediv>
+
+ <docinfo>
+ <copyright>
+ <year>2013</year>
+ <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+ </copyright>
+ </docinfo>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>bind10</command>
+ <arg><option>options</option></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para>The <command>bind10</command> script is a simple wrapper that
+ starts BIND10 by running the <command>b10-init</command> daemon. All
+ options passed to <command>bind10</command> are directly passed on to
+ <command>b10-init</command>.</para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>b10-init</refentrytitle><manvolnum>1</manvolnum>
+ </citerefentry>,
+ <citetitle>BIND 10 Guide</citetitle>.
+ </para>
+ </refsect1>
+
+ <refsect1 id='history'><title>HISTORY</title>
+ <para>The <command>bind10</command> script was added in Januari 2013
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/src/bin/bind10/init.py.in b/src/bin/bind10/init.py.in
new file mode 100755
index 0000000..799aafd
--- /dev/null
+++ b/src/bin/bind10/init.py.in
@@ -0,0 +1,1320 @@
+#!@PYTHON@
+
+# Copyright (C) 2010,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.
+
+"""
+This file implements the b10-init program.
+
+Its purpose is to start up the BIND 10 system, and then manage the
+processes, by starting and stopping processes, plus restarting
+processes that exit.
+
+To start the system, it first runs the c-channel program (msgq), then
+connects to that. It then runs the configuration manager, and reads
+its own configuration. Then it proceeds to starting other modules.
+
+The Python subprocess module is used for starting processes, but
+because this is not efficient for managing groups of processes,
+SIGCHLD signals are caught and processed using the signal module.
+
+Most of the logic is contained in the Init class. However, since Python
+requires that signal processing happen in the main thread, we do
+signal handling outside of that class, in the code running for
+__main__.
+"""
+
+import sys; sys.path.append ('@@PYTHONPATH@@')
+import os
+
+# If B10_FROM_SOURCE is set in the environment, we use data files
+# from a directory relative to that, otherwise we use the ones
+# installed on the system
+if "B10_FROM_SOURCE" in os.environ:
+ SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] +\
+ "/src/bin/bind10/init.spec"
+else:
+ PREFIX = "@prefix@"
+ DATAROOTDIR = "@datarootdir@"
+ SPECFILE_LOCATION = "@datadir@/@PACKAGE@/init.spec"\
+ .replace("${datarootdir}", DATAROOTDIR)\
+ .replace("${prefix}", PREFIX)
+
+import subprocess
+import signal
+import re
+import errno
+import time
+import select
+import random
+import socket
+from optparse import OptionParser, OptionValueError
+import io
+import pwd
+import posix
+import copy
+
+from bind10_config import LIBEXECPATH
+import bind10_config
+import isc.cc
+import isc.util.process
+import isc.net.parse
+import isc.log
+from isc.log_messages.init_messages import *
+import isc.bind10.component
+import isc.bind10.special_component
+import isc.bind10.socket_cache
+import libutil_io_python
+import tempfile
+
+isc.log.init("b10-init", buffer=True)
+logger = isc.log.Logger("init")
+
+# Pending system-wide debug level definitions, the ones we
+# use here are hardcoded for now
+DBG_PROCESS = logger.DBGLVL_TRACE_BASIC
+DBG_COMMANDS = logger.DBGLVL_TRACE_DETAIL
+
+# Messages sent over the unix domain socket to indicate if it is followed by a real socket
+CREATOR_SOCKET_OK = b"1\n"
+CREATOR_SOCKET_UNAVAILABLE = b"0\n"
+
+# RCodes of known exceptions for the get_token command
+CREATOR_SOCKET_ERROR = 2
+CREATOR_SHARE_ERROR = 3
+
+# Assign this process some longer name
+isc.util.process.rename(sys.argv[0])
+
+# This is the version that gets displayed to the user.
+# The VERSION string consists of the module name, the module version
+# number, and the overall BIND 10 version number (set in configure.ac).
+VERSION = "bind10 20110223 (BIND 10 @PACKAGE_VERSION@)"
+
+# This is for boot_time of Init
+_BASETIME = time.gmtime()
+
+# Detailed error message commonly used on startup failure, possibly due to
+# permission issue regarding log lock file. We dump verbose message because
+# it may not be clear exactly what to do if it simply says
+# "failed to open <filename>: permission denied"
+NOTE_ON_LOCK_FILE = """\
+TIP: if this is about permission error for a lock file, check if the directory
+of the file is writable for the user of the bind10 process; often you need
+to start bind10 as a super user. Also, if you specify the -u option to
+change the user and group, the directory must be writable for the group,
+and the created lock file must be writable for that user. Finally, make sure
+the lock file is not left in the directly before restarting.
+"""
+
+class ProcessInfoError(Exception): pass
+
+class ChangeUserError(Exception):
+ '''Exception raised when setuid/setgid fails.
+
+ When raised, it's expected to be propagated via underlying component
+ management modules to the top level so that it will help provide useful
+ fatal error message.
+
+ '''
+ pass
+
+class ProcessInfo:
+ """Information about a process"""
+
+ dev_null = open(os.devnull, "w")
+
+ 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 _preexec_work(self):
+ """Function used before running a program that needs to run as a
+ different user."""
+ # First, put us into a separate process group so we don't get
+ # SIGINT signals on Ctrl-C (b10-init will shut everthing down by
+ # other means).
+ os.setpgrp()
+
+ def _spawn(self):
+ if self.dev_null_stdout:
+ spawn_stdout = self.dev_null
+ else:
+ spawn_stdout = None
+ if self.dev_null_stderr:
+ spawn_stderr = self.dev_null
+ else:
+ spawn_stderr = None
+ # Environment variables for the child process will be a copy of those
+ # of the b10-init process with any additional specific variables given
+ # on construction (self.env).
+ spawn_env = copy.deepcopy(os.environ)
+ spawn_env.update(self.env)
+ spawn_env['PATH'] = LIBEXECPATH + ':' + spawn_env['PATH']
+ self.process = subprocess.Popen(self.args,
+ stdin=subprocess.PIPE,
+ stdout=spawn_stdout,
+ stderr=spawn_stderr,
+ close_fds=True,
+ env=spawn_env,
+ preexec_fn=self._preexec_work)
+ self.pid = self.process.pid
+
+ # spawn() and respawn() are the same for now, but in the future they
+ # may have different functionality
+ def spawn(self):
+ self._spawn()
+
+ def respawn(self):
+ self._spawn()
+
+class CChannelConnectError(Exception): pass
+
+class ProcessStartError(Exception): pass
+
+class Init:
+ """Init of BIND class."""
+
+ def __init__(self, msgq_socket_file=None, data_path=None,
+ config_filename=None, clear_config=False,
+ verbose=False, nokill=False, setuid=None, setgid=None,
+ username=None, cmdctl_port=None, wait_time=10):
+ """
+ Initialize the Init of BIND. This is a singleton (only one can run).
+
+ The msgq_socket_file specifies the UNIX domain socket file that the
+ msgq process listens on. If verbose is True, then b10-init reports
+ what it is doing.
+
+ Data path and config filename are passed through to config manager
+ (if provided) and specify the config file to be used.
+
+ The cmdctl_port is passed to cmdctl and specify on which port it
+ should listen.
+
+ wait_time controls the amount of time (in seconds) that Init waits
+ for selected processes to initialize before continuing with the
+ initialization. Currently this is only the configuration manager.
+ """
+ self.cc_session = None
+ self.ccs = None
+ self.curproc = None
+ self.msgq_socket_file = msgq_socket_file
+ self.component_config = {}
+ # Some time in future, it may happen that a single component has
+ # multple processes (like a pipeline-like component). If so happens,
+ # name "components" may be inapropriate. But as the code isn't probably
+ # completely ready for it, we leave it at components for now. We also
+ # want to support multiple instances of a single component. If it turns
+ # out that we'll have a single component with multiple same processes
+ # or if we start multiple components with the same configuration (we do
+ # this now, but it might change) is an open question.
+ self.components = {}
+ # Simply list of components that died and need to wait for a
+ # restart. Components manage their own restart schedule now
+ self.components_to_restart = []
+ self.runnable = False
+ self.__uid = setuid
+ self.__gid = setgid
+ self.username = username
+ self.verbose = verbose
+ self.nokill = nokill
+ self.data_path = data_path
+ self.config_filename = config_filename
+ self.clear_config = clear_config
+ self.cmdctl_port = cmdctl_port
+ self.wait_time = wait_time
+ self.msgq_timeout = 5
+
+ # _run_under_unittests is only meant to be used when testing. It
+ # bypasses execution of some code to help with testing.
+ self._run_under_unittests = False
+
+ self._component_configurator = isc.bind10.component.Configurator(self,
+ isc.bind10.special_component.get_specials())
+ # The priorities here make them start in the correct order. First
+ # the socket creator (which would drop root privileges by then),
+ # then message queue and after that the config manager (which uses
+ # the config manager)
+ self.__core_components = {
+ 'sockcreator': {
+ 'kind': 'core',
+ 'special': 'sockcreator',
+ 'priority': 200
+ },
+ 'msgq': {
+ 'kind': 'core',
+ 'special': 'msgq',
+ 'priority': 199
+ },
+ 'cfgmgr': {
+ 'kind': 'core',
+ 'special': 'cfgmgr',
+ 'priority': 198
+ }
+ }
+ self.__started = False
+ self.exitcode = 0
+
+ # If -v was set, enable full debug logging.
+ if self.verbose:
+ logger.set_severity("DEBUG", 99)
+ # This is set in init_socket_srv
+ self._socket_path = None
+ self._socket_cache = None
+ self._tmpdir = None
+ self._srv_socket = None
+ self._unix_sockets = {}
+
+ def __propagate_component_config(self, config):
+ comps = dict(config)
+ # Fill in the core components, so they stay alive
+ for comp in self.__core_components:
+ if comp in comps:
+ raise Exception(comp + " is core component managed by " +
+ "b10-init, do not set it")
+ comps[comp] = self.__core_components[comp]
+ # Update the configuration
+ self._component_configurator.reconfigure(comps)
+
+ def change_user(self):
+ '''Change the user and group to those specified on construction.
+
+ This method is expected to be called by a component on initial
+ startup when the system is ready to switch the user and group
+ (i.e., once all components that need the privilege of the original
+ user have started).
+ '''
+ try:
+ if self.__gid is not None:
+ logger.info(BIND10_SETGID, self.__gid)
+ posix.setgid(self.__gid)
+ except Exception as ex:
+ raise ChangeUserError('failed to change group: ' + str(ex))
+
+ try:
+ if self.__uid is not None:
+ posix.setuid(self.__uid)
+ # We use one-shot logger after setuid here. This will
+ # detect any permission issue regarding logging due to the
+ # result of setuid at the earliest opportunity.
+ isc.log.Logger("b10-init").info(BIND10_SETUID, self.__uid)
+ except Exception as ex:
+ raise ChangeUserError('failed to change user: ' + str(ex))
+
+ def config_handler(self, new_config):
+ # If this is initial update, don't do anything now, leave it to startup
+ if not self.runnable:
+ return
+ logger.debug(DBG_COMMANDS, BIND10_RECEIVED_NEW_CONFIGURATION,
+ new_config)
+ try:
+ if 'components' in new_config:
+ self.__propagate_component_config(new_config['components'])
+ return isc.config.ccsession.create_answer(0)
+ except Exception as e:
+ return isc.config.ccsession.create_answer(1, str(e))
+
+ def get_processes(self):
+ pids = list(self.components.keys())
+ pids.sort()
+ process_list = [ ]
+ for pid in pids:
+ process_list.append([pid, self.components[pid].name(),
+ self.components[pid].address()])
+ return process_list
+
+ def _get_stats_data(self):
+ return { 'boot_time':
+ time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME)
+ }
+
+ def command_handler(self, command, args):
+ logger.debug(DBG_COMMANDS, BIND10_RECEIVED_COMMAND, command)
+ answer = isc.config.ccsession.create_answer(1, "command not implemented")
+ if type(command) != str:
+ answer = isc.config.ccsession.create_answer(1, "bad command")
+ else:
+ if command == "shutdown":
+ self.runnable = False
+ answer = isc.config.ccsession.create_answer(0)
+ elif command == "getstats":
+ answer = isc.config.ccsession.create_answer(
+ 0, self._get_stats_data())
+ elif command == "ping":
+ answer = isc.config.ccsession.create_answer(0, "pong")
+ elif command == "show_processes":
+ answer = isc.config.ccsession. \
+ create_answer(0, self.get_processes())
+ elif command == "get_socket":
+ answer = self._get_socket(args)
+ elif command == "drop_socket":
+ if "token" not in args:
+ answer = isc.config.ccsession. \
+ create_answer(1, "Missing token parameter")
+ else:
+ try:
+ self._socket_cache.drop_socket(args["token"])
+ answer = isc.config.ccsession.create_answer(0)
+ except Exception as e:
+ answer = isc.config.ccsession.create_answer(1, str(e))
+ else:
+ answer = isc.config.ccsession.create_answer(1,
+ "Unknown command")
+ return answer
+
+ def kill_started_components(self):
+ """
+ Called as part of the exception handling when a process fails to
+ start, this runs through the list of started processes, killing
+ each one. It then clears that list.
+ """
+ logger.info(BIND10_KILLING_ALL_PROCESSES)
+ self.__kill_children(True)
+ self.components = {}
+
+ def _read_bind10_config(self):
+ """
+ Reads the parameters associated with the Init module itself.
+
+ This means the list of components we should start now.
+
+ This could easily be combined into start_all_processes, but
+ it stays because of historical reasons and because the tests
+ replace the method sometimes.
+ """
+ logger.info(BIND10_READING_INIT_CONFIGURATION)
+
+ config_data = self.ccs.get_full_config()
+ self.__propagate_component_config(config_data['components'])
+
+ def log_starting(self, process, port = None, address = None):
+ """
+ A convenience function to output a "Starting xxx" message if the
+ logging is set to DEBUG with debuglevel DBG_PROCESS or higher.
+ Putting this into a separate method ensures
+ that the output form is consistent across all processes.
+
+ The process name (passed as the first argument) is put into
+ self.curproc, and is used to indicate which process failed to
+ start if there is an error (and is used in the "Started" message
+ on success). The optional port and address information are
+ appended to the message (if present).
+ """
+ self.curproc = process
+ if port is None and address is None:
+ logger.info(BIND10_STARTING_PROCESS, self.curproc)
+ elif address is None:
+ logger.info(BIND10_STARTING_PROCESS_PORT, self.curproc,
+ port)
+ else:
+ logger.info(BIND10_STARTING_PROCESS_PORT_ADDRESS,
+ self.curproc, address, port)
+
+ def log_started(self, pid = None):
+ """
+ A convenience function to output a 'Started xxxx (PID yyyy)'
+ message. As with starting_message(), this ensures a consistent
+ format.
+ """
+ if pid is None:
+ logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS, self.curproc)
+ else:
+ logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS_PID, self.curproc, pid)
+
+ def process_running(self, msg, who):
+ """
+ Some processes return a message to the Init after they have
+ started to indicate that they are running. The form of the
+ message is a dictionary with contents {"running:", "<process>"}.
+ This method checks the passed message and returns True if the
+ "who" process is contained in the message (so is presumably
+ running). It returns False for all other conditions and will
+ log an error if appropriate.
+ """
+ if msg is not None:
+ try:
+ if msg["running"] == who:
+ return True
+ else:
+ logger.error(BIND10_STARTUP_UNEXPECTED_MESSAGE, msg)
+ except:
+ logger.error(BIND10_STARTUP_UNRECOGNISED_MESSAGE, msg)
+
+ return False
+
+ # The next few methods start the individual processes of BIND-10. They
+ # are called via start_all_processes(). If any fail, an exception is
+ # raised which is caught by the caller of start_all_processes(); this kills
+ # processes started up to that point before terminating the program.
+
+ def _make_process_info(self, name, args, env,
+ dev_null_stdout=False, dev_null_stderr=False):
+ """
+ Wrapper around ProcessInfo(), useful to override
+ ProcessInfo() creation during testing.
+ """
+ return ProcessInfo(name, args, env, dev_null_stdout, dev_null_stderr)
+
+ def start_msgq(self):
+ """
+ Start the message queue and connect to the command channel.
+ """
+ self.log_starting("b10-msgq")
+ msgq_proc = self._make_process_info("b10-msgq", ["b10-msgq"],
+ self.c_channel_env,
+ True, not self.verbose)
+ msgq_proc.spawn()
+ self.log_started(msgq_proc.pid)
+
+ # Now connect to the c-channel
+ cc_connect_start = time.time()
+ while self.cc_session is None:
+ # if we are run under unittests, break
+ if self._run_under_unittests:
+ break
+
+ # if we have been trying for "a while" give up
+ if (time.time() - cc_connect_start) > self.msgq_timeout:
+ if msgq_proc.process:
+ msgq_proc.process.kill()
+ logger.error(BIND10_CONNECTING_TO_CC_FAIL)
+ raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
+
+ # try to connect, and if we can't wait a short while
+ try:
+ self.cc_session = isc.cc.Session(self.msgq_socket_file)
+ except isc.cc.session.SessionError:
+ time.sleep(0.1)
+
+ # Subscribe to the message queue. The only messages we expect to receive
+ # on this channel are once relating to process startup.
+ if self.cc_session is not None:
+ self.cc_session.group_subscribe("Init")
+
+ return msgq_proc
+
+ def start_cfgmgr(self):
+ """
+ Starts the configuration manager process
+ """
+ self.log_starting("b10-cfgmgr")
+ args = ["b10-cfgmgr"]
+ if self.data_path is not None:
+ args.append("--data-path=" + self.data_path)
+ if self.config_filename is not None:
+ args.append("--config-filename=" + self.config_filename)
+ if self.clear_config:
+ args.append("--clear-config")
+ bind_cfgd = self._make_process_info("b10-cfgmgr", args,
+ self.c_channel_env)
+ bind_cfgd.spawn()
+ self.log_started(bind_cfgd.pid)
+
+ # Wait for the configuration manager to start up as
+ # subsequent initialization cannot proceed without it. The
+ # time to wait can be set on the command line.
+ time_remaining = self.wait_time
+ msg, env = self.cc_session.group_recvmsg()
+ while time_remaining > 0 and not self.process_running(msg, "ConfigManager"):
+ logger.debug(DBG_PROCESS, BIND10_WAIT_CFGMGR)
+ time.sleep(1)
+ time_remaining = time_remaining - 1
+ msg, env = self.cc_session.group_recvmsg()
+
+ if not self.process_running(msg, "ConfigManager"):
+ raise ProcessStartError("Configuration manager process has not started")
+
+ return bind_cfgd
+
+ def start_ccsession(self, c_channel_env):
+ """
+ Start the CC Session
+
+ The argument c_channel_env is unused but is supplied to keep the
+ argument list the same for all start_xxx methods.
+
+ With regards to logging, note that as the CC session is not a
+ process, the log_starting/log_started methods are not used.
+ """
+ logger.info(BIND10_STARTING_CC)
+ self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
+ self.config_handler,
+ self.command_handler,
+ socket_file = self.msgq_socket_file)
+ self.ccs.start()
+ logger.debug(DBG_PROCESS, BIND10_STARTED_CC)
+
+ # A couple of utility methods for starting processes...
+
+ def start_process(self, name, args, c_channel_env, port=None, address=None):
+ """
+ Given a set of command arguments, start the process and output
+ appropriate log messages. If the start is successful, the process
+ is added to the list of started processes.
+
+ The port and address arguments are for log messages only.
+ """
+ self.log_starting(name, port, address)
+ newproc = self._make_process_info(name, args, c_channel_env)
+ newproc.spawn()
+ self.log_started(newproc.pid)
+ return newproc
+
+ def register_process(self, pid, component):
+ """
+ Put another process into b10-init to watch over it. When the process
+ dies, the component.failed() is called with the exit code.
+
+ It is expected the info is a isc.bind10.component.BaseComponent
+ subclass (or anything having the same interface).
+ """
+ self.components[pid] = component
+
+ def start_simple(self, name):
+ """
+ Most of the BIND-10 processes are started with the command:
+
+ <process-name> [-v]
+
+ ... where -v is appended if verbose is enabled. This method
+ generates the arguments from the name and starts the process.
+
+ The port and address arguments are for log messages only.
+ """
+ # Set up the command arguments.
+ args = [name]
+ if self.verbose:
+ args += ['-v']
+
+ # ... and start the process
+ return self.start_process(name, args, self.c_channel_env)
+
+ # The next few methods start up the rest of the BIND-10 processes.
+ # Although many of these methods are little more than a call to
+ # start_simple, they are retained (a) for testing reasons and (b) as a place
+ # where modifications can be made if the process start-up sequence changes
+ # for a given process.
+
+ def start_auth(self):
+ """
+ Start the Authoritative server
+ """
+ authargs = ['b10-auth']
+ if self.verbose:
+ authargs += ['-v']
+
+ # ... and start
+ return self.start_process("b10-auth", authargs, self.c_channel_env)
+
+ def start_resolver(self):
+ """
+ Start the Resolver. At present, all these arguments and switches
+ are pure speculation. As with the auth daemon, they should be
+ read from the configuration database.
+ """
+ self.curproc = "b10-resolver"
+ # XXX: this must be read from the configuration manager in the future
+ resargs = ['b10-resolver']
+ if self.verbose:
+ resargs += ['-v']
+
+ # ... and start
+ return self.start_process("b10-resolver", resargs, self.c_channel_env)
+
+ def start_cmdctl(self):
+ """
+ Starts the command control process
+ """
+ args = ["b10-cmdctl"]
+ if self.cmdctl_port is not None:
+ args.append("--port=" + str(self.cmdctl_port))
+ if self.verbose:
+ args.append("-v")
+ return self.start_process("b10-cmdctl", args, self.c_channel_env,
+ self.cmdctl_port)
+
+ def start_all_components(self):
+ """
+ Starts up all the components. Any exception generated during the
+ starting of the components is handled by the caller.
+ """
+ # Start the real core (sockcreator, msgq, cfgmgr)
+ self._component_configurator.startup(self.__core_components)
+
+ # Connect to the msgq. This is not a process, so it's not handled
+ # inside the configurator.
+ self.start_ccsession(self.c_channel_env)
+
+ # Extract the parameters associated with Init. This can only be
+ # done after the CC Session is started. Note that the logging
+ # configuration may override the "-v" switch set on the command line.
+ self._read_bind10_config()
+
+ # TODO: Return the dropping of privileges
+
+ def startup(self):
+ """
+ Start the Init instance.
+
+ Returns None if successful, otherwise an string describing the
+ problem.
+ """
+ # Try to connect to the c-channel daemon, to see if it is already
+ # running
+ c_channel_env = {}
+ if self.msgq_socket_file is not None:
+ c_channel_env["BIND10_MSGQ_SOCKET_FILE"] = self.msgq_socket_file
+ logger.debug(DBG_PROCESS, BIND10_CHECK_MSGQ_ALREADY_RUNNING)
+ try:
+ self.cc_session = isc.cc.Session(self.msgq_socket_file)
+ logger.fatal(BIND10_MSGQ_ALREADY_RUNNING)
+ return "b10-msgq already running, or socket file not cleaned , cannot start"
+ except isc.cc.session.SessionError:
+ # this is the case we want, where the msgq is not running
+ pass
+
+ # Start all components. If any one fails to start, kill all started
+ # components and exit with an error indication.
+ try:
+ self.c_channel_env = c_channel_env
+ self.start_all_components()
+ except ChangeUserError as e:
+ self.kill_started_components()
+ return str(e) + '; ' + NOTE_ON_LOCK_FILE.replace('\n', ' ')
+ except Exception as e:
+ self.kill_started_components()
+ return "Unable to start " + self.curproc + ": " + str(e)
+
+ # Started successfully
+ self.runnable = True
+ self.__started = True
+ return None
+
+ def stop_process(self, process, recipient, pid):
+ """
+ Stop the given process, friendly-like. The process is the name it has
+ (in logs, etc), the recipient is the address on msgq. The pid is the
+ pid of the process (if we have multiple processes of the same name,
+ it might want to choose if it is for this one).
+ """
+ logger.info(BIND10_STOP_PROCESS, process)
+ self.cc_session.group_sendmsg(isc.config.ccsession.
+ create_command('shutdown', {'pid': pid}),
+ recipient, recipient)
+
+ def component_shutdown(self, exitcode=0):
+ """
+ Stop the Init instance from a components' request. The exitcode
+ indicates the desired exit code.
+
+ If we did not start yet, it raises an exception, which is meant
+ to propagate through the component and configurator to the startup
+ routine and abort the startup immediately. If it is started up already,
+ we just mark it so we terminate soon.
+
+ It does set the exit code in both cases.
+ """
+ self.exitcode = exitcode
+ if not self.__started:
+ raise Exception("Component failed during startup");
+ else:
+ self.runnable = False
+
+ def shutdown(self):
+ """Stop the Init instance."""
+ logger.info(BIND10_SHUTDOWN)
+ # If ccsession is still there, inform rest of the system this module
+ # is stopping. Since everything will be stopped shortly, this is not
+ # really necessary, but this is done to reflect that b10-init is also
+ # 'just' a module.
+ self.ccs.send_stopping()
+
+ # try using the BIND 10 request to stop
+ try:
+ self._component_configurator.shutdown()
+ except:
+ pass
+ # XXX: some delay probably useful... how much is uncertain
+ # I have changed the delay from 0.5 to 1, but sometime it's
+ # still not enough.
+ time.sleep(1)
+ self.reap_children()
+
+ # Send TERM and KILL signals to modules if we're not prevented
+ # from doing so
+ if not self.nokill:
+ # next try sending a SIGTERM
+ self.__kill_children(False)
+ # finally, send SIGKILL (unmaskable termination) until everybody
+ # dies
+ while self.components:
+ # XXX: some delay probably useful... how much is uncertain
+ time.sleep(0.1)
+ self.reap_children()
+ self.__kill_children(True)
+ logger.info(BIND10_SHUTDOWN_COMPLETE)
+
+ def __kill_children(self, forceful):
+ '''Terminate remaining subprocesses by sending a signal.
+
+ The forceful paramter will be passed Component.kill().
+ This is a dedicated subroutine of shutdown(), just to unify two
+ similar cases.
+
+ '''
+ logmsg = BIND10_SEND_SIGKILL if forceful else BIND10_SEND_SIGTERM
+ # We need to make a copy of values as the components may be modified
+ # in the loop.
+ for component in list(self.components.values()):
+ logger.info(logmsg, component.name(), component.pid())
+ try:
+ component.kill(forceful)
+ except OSError as ex:
+ # If kill() failed due to EPERM, it doesn't make sense to
+ # keep trying, so we just log the fact and forget that
+ # component. Ignore other OSErrors (usually ESRCH because
+ # the child finally exited)
+ signame = "SIGKILL" if forceful else "SIGTERM"
+ logger.info(BIND10_SEND_SIGNAL_FAIL, signame,
+ component.name(), component.pid(), ex)
+ if ex.errno == errno.EPERM:
+ del self.components[component.pid()]
+
+ def _get_process_exit_status(self):
+ return os.waitpid(-1, os.WNOHANG)
+
+ def reap_children(self):
+ """Check to see if any of our child processes have exited,
+ and note this for later handling.
+ """
+ while True:
+ try:
+ (pid, exit_status) = self._get_process_exit_status()
+ except OSError as o:
+ if o.errno == errno.ECHILD:
+ break
+ # XXX: should be impossible to get any other error here
+ raise
+ if pid == 0:
+ break
+ if pid in self.components:
+ # One of the components we know about. Get information on it.
+ component = self.components.pop(pid)
+ logger.info(BIND10_PROCESS_ENDED, component.name(), pid,
+ exit_status)
+ if component.is_running() and self.runnable:
+ # Tell it it failed. But only if it matters (we are
+ # not shutting down and the component considers itself
+ # to be running.
+ component_restarted = component.failed(exit_status);
+ # if the process wants to be restarted, but not just yet,
+ # it returns False
+ if not component_restarted:
+ self.components_to_restart.append(component)
+ else:
+ logger.info(BIND10_UNKNOWN_CHILD_PROCESS_ENDED, pid)
+
+ def restart_processes(self):
+ """
+ Restart any dead processes:
+
+ * Returns the time when the next process is ready to be restarted.
+ * If the server is shutting down, returns 0.
+ * If there are no processes, returns None.
+
+ The values returned can be safely passed into select() as the
+ timeout value.
+
+ """
+ if not self.runnable:
+ return 0
+ still_dead = []
+ # keep track of the first time we need to check this queue again,
+ # if at all
+ next_restart_time = None
+ now = time.time()
+ for component in self.components_to_restart:
+ # If the component was removed from the configurator between since
+ # scheduled to restart, just ignore it. The object will just be
+ # dropped here.
+ if not self._component_configurator.has_component(component):
+ logger.info(BIND10_RESTART_COMPONENT_SKIPPED, component.name())
+ elif not component.restart(now):
+ still_dead.append(component)
+ if next_restart_time is None or\
+ next_restart_time > component.get_restart_time():
+ next_restart_time = component.get_restart_time()
+ self.components_to_restart = still_dead
+
+ return next_restart_time
+
+ def _get_socket(self, args):
+ """
+ Implementation of the get_socket CC command. It asks the cache
+ to provide the token and sends the information back.
+ """
+ try:
+ try:
+ addr = isc.net.parse.addr_parse(args['address'])
+ port = isc.net.parse.port_parse(args['port'])
+ protocol = args['protocol']
+ if protocol not in ['UDP', 'TCP']:
+ raise ValueError("Protocol must be either UDP or TCP")
+ share_mode = args['share_mode']
+ if share_mode not in ['ANY', 'SAMEAPP', 'NO']:
+ raise ValueError("Share mode must be one of ANY, SAMEAPP" +
+ " or NO")
+ share_name = args['share_name']
+ except KeyError as ke:
+ return \
+ isc.config.ccsession.create_answer(1,
+ "Missing parameter " +
+ str(ke))
+
+ # FIXME: This call contains blocking IPC. It is expected to be
+ # short, but if it turns out to be problem, we'll need to do
+ # something about it.
+ token = self._socket_cache.get_token(protocol, addr, port,
+ share_mode, share_name)
+ return isc.config.ccsession.create_answer(0, {
+ 'token': token,
+ 'path': self._socket_path
+ })
+ except isc.bind10.socket_cache.SocketError as e:
+ return isc.config.ccsession.create_answer(CREATOR_SOCKET_ERROR,
+ str(e))
+ except isc.bind10.socket_cache.ShareError as e:
+ return isc.config.ccsession.create_answer(CREATOR_SHARE_ERROR,
+ str(e))
+ except Exception as e:
+ return isc.config.ccsession.create_answer(1, str(e))
+
+ def socket_request_handler(self, token, unix_socket):
+ """
+ This function handles a token that comes over a unix_domain socket.
+ The function looks into the _socket_cache and sends the socket
+ identified by the token back over the unix_socket.
+ """
+ try:
+ token = str(token, 'ASCII') # Convert from bytes to str
+ fd = self._socket_cache.get_socket(token, unix_socket.fileno())
+ # FIXME: These two calls are blocking in their nature. An OS-level
+ # buffer is likely to be large enough to hold all these data, but
+ # if it wasn't and the remote application got stuck, we would have
+ # a problem. If there appear such problems, we should do something
+ # about it.
+ unix_socket.sendall(CREATOR_SOCKET_OK)
+ libutil_io_python.send_fd(unix_socket.fileno(), fd)
+ except Exception as e:
+ logger.info(BIND10_NO_SOCKET, token, e)
+ unix_socket.sendall(CREATOR_SOCKET_UNAVAILABLE)
+
+ def socket_consumer_dead(self, unix_socket):
+ """
+ This function handles when a unix_socket closes. This means all
+ sockets sent to it are to be considered closed. This function signals
+ so to the _socket_cache.
+ """
+ logger.info(BIND10_LOST_SOCKET_CONSUMER, unix_socket.fileno())
+ try:
+ self._socket_cache.drop_application(unix_socket.fileno())
+ except ValueError:
+ # This means the application holds no sockets. It's harmless, as it
+ # can happen in real life - for example, it requests a socket, but
+ # get_socket doesn't find it, so the application dies. It should be
+ # rare, though.
+ pass
+
+ def set_creator(self, creator):
+ """
+ Registeres a socket creator into the b10-init. The socket creator is not
+ used directly, but through a cache. The cache is created in this
+ method.
+
+ If called more than once, it raises a ValueError.
+ """
+ if self._socket_cache is not None:
+ raise ValueError("A creator was inserted previously")
+ self._socket_cache = isc.bind10.socket_cache.Cache(creator)
+
+ def init_socket_srv(self):
+ """
+ Creates and listens on a unix-domain socket to be able to send out
+ the sockets.
+
+ This method should be called after switching user, or the switched
+ applications won't be able to access the socket.
+ """
+ self._srv_socket = socket.socket(socket.AF_UNIX)
+ # We create a temporary directory somewhere safe and unique, to avoid
+ # the need to find the place ourself or bother users. Also, this
+ # secures the socket on some platforms, as it creates a private
+ # directory.
+ self._tmpdir = tempfile.mkdtemp(prefix='sockcreator-')
+ # Get the name
+ self._socket_path = os.path.join(self._tmpdir, "sockcreator")
+ # And bind the socket to the name
+ self._srv_socket.bind(self._socket_path)
+ self._srv_socket.listen(5)
+
+ def remove_socket_srv(self):
+ """
+ Closes and removes the listening socket and the directory where it
+ lives, as we created both.
+
+ It does nothing if the _srv_socket is not set (eg. it was not yet
+ initialized).
+ """
+ if self._srv_socket is not None:
+ self._srv_socket.close()
+ if os.path.exists(self._socket_path):
+ os.remove(self._socket_path)
+ if os.path.isdir(self._tmpdir):
+ os.rmdir(self._tmpdir)
+
+ def _srv_accept(self):
+ """
+ Accept a socket from the unix domain socket server and put it to the
+ others we care about.
+ """
+ (socket, conn) = self._srv_socket.accept()
+ self._unix_sockets[socket.fileno()] = (socket, b'')
+
+ def _socket_data(self, socket_fileno):
+ """
+ This is called when a socket identified by the socket_fileno needs
+ attention. We try to read data from there. If it is closed, we remove
+ it.
+ """
+ (sock, previous) = self._unix_sockets[socket_fileno]
+ while True:
+ try:
+ data = sock.recv(1, socket.MSG_DONTWAIT)
+ except socket.error as se:
+ # These two might be different on some systems
+ if se.errno == errno.EAGAIN or se.errno == errno.EWOULDBLOCK:
+ # No more data now. Oh, well, just store what we have.
+ self._unix_sockets[socket_fileno] = (sock, previous)
+ return
+ else:
+ data = b'' # Pretend it got closed
+ if len(data) == 0: # The socket got to it's end
+ del self._unix_sockets[socket_fileno]
+ self.socket_consumer_dead(sock)
+ sock.close()
+ return
+ else:
+ if data == b"\n":
+ # Handle this token and clear it
+ self.socket_request_handler(previous, sock)
+ previous = b''
+ else:
+ previous += data
+
+ def run(self, wakeup_fd):
+ """
+ The main loop, waiting for sockets, commands and dead processes.
+ Runs as long as the runnable is true.
+
+ The wakeup_fd descriptor is the read end of pipe where CHLD signal
+ handler writes.
+ """
+ ccs_fd = self.ccs.get_socket().fileno()
+ while self.runnable:
+ # clean up any processes that exited
+ self.reap_children()
+ next_restart = self.restart_processes()
+ if next_restart is None:
+ wait_time = None
+ else:
+ wait_time = max(next_restart - time.time(), 0)
+
+ # select() can raise EINTR when a signal arrives,
+ # even if they are resumable, so we have to catch
+ # the exception
+ try:
+ (rlist, wlist, xlist) = \
+ select.select([wakeup_fd, ccs_fd,
+ self._srv_socket.fileno()] +
+ list(self._unix_sockets.keys()), [], [],
+ wait_time)
+ except select.error as err:
+ if err.args[0] == errno.EINTR:
+ (rlist, wlist, xlist) = ([], [], [])
+ else:
+ logger.fatal(BIND10_SELECT_ERROR, err)
+ break
+
+ for fd in rlist + xlist:
+ if fd == ccs_fd:
+ try:
+ self.ccs.check_command()
+ except isc.cc.session.ProtocolError:
+ logger.fatal(BIND10_MSGQ_DISAPPEARED)
+ self.runnable = False
+ break
+ elif fd == wakeup_fd:
+ os.read(wakeup_fd, 32)
+ elif fd == self._srv_socket.fileno():
+ self._srv_accept()
+ elif fd in self._unix_sockets:
+ self._socket_data(fd)
+
+# global variables, needed for signal handlers
+options = None
+b10_init = None
+
+def reaper(signal_number, stack_frame):
+ """A child process has died (SIGCHLD received)."""
+ # don't do anything...
+ # the Python signal handler has been set up to write
+ # down a pipe, waking up our select() bit
+ pass
+
+def get_signame(signal_number):
+ """Return the symbolic name for a signal."""
+ for sig in dir(signal):
+ if sig.startswith("SIG") and sig[3].isalnum():
+ if getattr(signal, sig) == signal_number:
+ return sig
+ return "Unknown signal %d" % signal_number
+
+# XXX: perhaps register atexit() function and invoke that instead
+def fatal_signal(signal_number, stack_frame):
+ """We need to exit (SIGINT or SIGTERM received)."""
+ global options
+ global b10_init
+ logger.info(BIND10_RECEIVED_SIGNAL, get_signame(signal_number))
+ signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+ b10_init.runnable = False
+
+def process_rename(option, opt_str, value, parser):
+ """Function that renames the process if it is requested by a option."""
+ isc.util.process.rename(value)
+
+def parse_args(args=sys.argv[1:], Parser=OptionParser):
+ """
+ Function for parsing command line arguments. Returns the
+ options object from OptionParser.
+ """
+ parser = Parser(version=VERSION)
+ parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
+ type="string", default=None,
+ help="UNIX domain socket file the b10-msgq daemon will use")
+ parser.add_option("-i", "--no-kill", action="store_true", dest="nokill",
+ default=False, help="do not send SIGTERM and SIGKILL signals to modules during shutdown")
+ parser.add_option("-u", "--user", dest="user", type="string", default=None,
+ help="Change user after startup (must run as root)")
+ parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
+ help="display more about what is going on")
+ parser.add_option("--pretty-name", type="string", action="callback",
+ callback=process_rename,
+ help="Set the process name (displayed in ps, top, ...)")
+ parser.add_option("-c", "--config-file", action="store",
+ dest="config_file", default=None,
+ help="Configuration database filename")
+ parser.add_option("--clear-config", action="store_true",
+ dest="clear_config", default=False,
+ help="Create backup of the configuration file and " +
+ "start with a clean configuration")
+ parser.add_option("-p", "--data-path", dest="data_path",
+ help="Directory to search for configuration files",
+ default=None)
+ parser.add_option("--cmdctl-port", dest="cmdctl_port", type="int",
+ default=None, help="Port of command control")
+ parser.add_option("--pid-file", dest="pid_file", type="string",
+ default=None,
+ help="file to dump the PID of the BIND 10 process")
+ parser.add_option("-w", "--wait", dest="wait_time", type="int",
+ default=10, help="Time (in seconds) to wait for config manager to start up")
+
+ (options, args) = parser.parse_args(args)
+
+ if options.cmdctl_port is not None:
+ try:
+ isc.net.parse.port_parse(options.cmdctl_port)
+ except ValueError as e:
+ parser.error(e)
+
+ if args:
+ parser.print_help()
+ sys.exit(1)
+
+ return options
+
+def dump_pid(pid_file):
+ """
+ Dump the PID of the current process to the specified file. If the given
+ file is None this function does nothing. If the file already exists,
+ the existing content will be removed. If a system error happens in
+ creating or writing to the file, the corresponding exception will be
+ propagated to the caller.
+ """
+ if pid_file is None:
+ return
+ f = open(pid_file, "w")
+ f.write('%d\n' % os.getpid())
+ f.close()
+
+def unlink_pid_file(pid_file):
+ """
+ Remove the given file, which is basically expected to be the PID file
+ created by dump_pid(). The specified may or may not exist; if it
+ doesn't this function does nothing. Other system level errors in removing
+ the file will be propagated as the corresponding exception.
+ """
+ if pid_file is None:
+ return
+ try:
+ os.unlink(pid_file)
+ except OSError as error:
+ if error.errno is not errno.ENOENT:
+ raise
+
+def remove_lock_files():
+ """
+ Remove various lock files which were created by code such as in the
+ logger. This function should be called after BIND 10 shutdown.
+ """
+
+ lockfiles = ["logger_lockfile"]
+
+ lpath = bind10_config.DATA_PATH
+ if "B10_FROM_BUILD" in os.environ:
+ lpath = os.environ["B10_FROM_BUILD"]
+ if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
+ lpath = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
+ if "B10_LOCKFILE_DIR_FROM_BUILD" in os.environ:
+ lpath = os.environ["B10_LOCKFILE_DIR_FROM_BUILD"]
+
+ for f in lockfiles:
+ fname = lpath + '/' + f
+ if os.path.isfile(fname):
+ try:
+ os.unlink(fname)
+ except OSError as e:
+ # We catch and ignore permission related error on unlink.
+ # This can happen if bind10 started with -u, created a lock
+ # file as a privileged user, but the directory is not writable
+ # for the changed user. This setup will cause immediate
+ # start failure, and we leave verbose error message including
+ # the leftover lock file, so it should be acceptable to ignore
+ # it (note that it doesn't make sense to log this event at
+ # this poitn)
+ if e.errno != errno.EPERM and e.errno != errno.EACCES:
+ raise
+
+ return
+
+def main():
+ global options
+ global b10_init
+ # Enforce line buffering on stdout, even when not a TTY
+ sys.stdout = io.TextIOWrapper(sys.stdout.detach(), line_buffering=True)
+
+ options = parse_args()
+
+ # Announce startup. Making this is the first log message.
+ try:
+ logger.info(BIND10_STARTING, VERSION)
+ except RuntimeError as e:
+ sys.stderr.write('ERROR: failed to write the initial log: %s\n' %
+ str(e))
+ sys.stderr.write(NOTE_ON_LOCK_FILE)
+ sys.exit(1)
+
+ # Check user ID.
+ setuid = None
+ setgid = None
+ username = None
+ if options.user:
+ # Try getting information about the user, assuming UID passed.
+ try:
+ pw_ent = pwd.getpwuid(int(options.user))
+ setuid = pw_ent.pw_uid
+ setgid = pw_ent.pw_gid
+ username = pw_ent.pw_name
+ except ValueError:
+ pass
+ except KeyError:
+ pass
+
+ # Next try getting information about the user, assuming user name
+ # passed.
+ # If the information is both a valid user name and user number, we
+ # prefer the name because we try it second. A minor point, hopefully.
+ try:
+ pw_ent = pwd.getpwnam(options.user)
+ setuid = pw_ent.pw_uid
+ setgid = pw_ent.pw_gid
+ username = pw_ent.pw_name
+ except KeyError:
+ pass
+
+ if setuid is None:
+ logger.fatal(BIND10_INVALID_USER, options.user)
+ sys.exit(1)
+
+ # Create wakeup pipe for signal handlers
+ wakeup_pipe = os.pipe()
+ signal.set_wakeup_fd(wakeup_pipe[1])
+
+ # Set signal handlers for catching child termination, as well
+ # as our own demise.
+ signal.signal(signal.SIGCHLD, reaper)
+ signal.siginterrupt(signal.SIGCHLD, False)
+ signal.signal(signal.SIGINT, fatal_signal)
+ signal.signal(signal.SIGTERM, fatal_signal)
+
+ # Block SIGPIPE, as we don't want it to end this process
+ signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+
+ try:
+ b10_init = Init(options.msgq_socket_file, options.data_path,
+ options.config_file, options.clear_config,
+ options.verbose, options.nokill,
+ setuid, setgid, username, options.cmdctl_port,
+ options.wait_time)
+ startup_result = b10_init.startup()
+ if startup_result:
+ logger.fatal(BIND10_STARTUP_ERROR, startup_result)
+ sys.exit(1)
+ b10_init.init_socket_srv()
+ logger.info(BIND10_STARTUP_COMPLETE)
+ dump_pid(options.pid_file)
+
+ # Let it run
+ b10_init.run(wakeup_pipe[0])
+
+ # shutdown
+ signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+ b10_init.shutdown()
+ finally:
+ # Clean up the filesystem
+ unlink_pid_file(options.pid_file)
+ remove_lock_files()
+ if b10_init is not None:
+ b10_init.remove_socket_srv()
+ sys.exit(b10_init.exitcode)
+
+if __name__ == "__main__":
+ main()
diff --git a/src/bin/bind10/init.spec b/src/bin/bind10/init.spec
index de4480d..62c6f09 100644
--- a/src/bin/bind10/init.spec
+++ b/src/bin/bind10/init.spec
@@ -1,7 +1,7 @@
{
"module_spec": {
"module_name": "Init",
- "module_description": "Master process",
+ "module_description": "Init process",
"config_data": [
{
"item_name": "components",
diff --git a/src/bin/bind10/init_messages.mes b/src/bin/bind10/init_messages.mes
index 1a2d10e..9cdb7ef 100644
--- a/src/bin/bind10/init_messages.mes
+++ b/src/bin/bind10/init_messages.mes
@@ -1,4 +1,4 @@
-IN# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
diff --git a/src/bin/bind10/run_bind10.sh.in b/src/bin/bind10/run_bind10.sh.in
index 9adc3b3..8121eba 100755
--- a/src/bin/bind10/run_bind10.sh.in
+++ b/src/bin/bind10/run_bind10.sh.in
@@ -45,5 +45,5 @@ export B10_FROM_BUILD
BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
export BIND10_MSGQ_SOCKET_FILE
-exec ${BIND10_PATH}/bind10 "$@"
+exec ${BIND10_PATH}/b10-init "$@"
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index 704a48a..36b98fe 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -13,19 +13,11 @@
# 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 "b10-init" for brevity. Sometimes,
+# 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.
-# Because the file has a hyphen in its name, we need to work around
-# the standard syntax here
-b10_init = __import__('b10-init')
-# emulate the 'from X import'
-Init = b10_init.Init
-ProcessInfo = b10_init.ProcessInfo
-parse_args = b10_init.parse_args
-dump_pid = b10_init.dump_pid
-unlink_pid_file = b10_init.unlink_pid_file
-_BASETIME = b10_init._BASETIME
+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
@@ -143,7 +135,7 @@ class TestCacheCommands(unittest.TestCase):
self.__send_fd_called = None
self.__get_token_called = None
self.__drop_socket_called = None
- b10_init.libutil_io_python.send_fd = self.__send_fd
+ init.libutil_io_python.send_fd = self.__send_fd
def __send_fd(self, to, socket):
"""
@@ -351,26 +343,26 @@ class TestCacheCommands(unittest.TestCase):
class TestInit(unittest.TestCase):
def setUp(self):
# Save original values that may be tweaked in some tests
- self.__orig_setgid = b10_init.posix.setgid
- self.__orig_setuid = b10_init.posix.setuid
+ 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()
- b10_init.posix.setgid = self.__orig_setgid
- b10_init.posix.setuid = self.__orig_setuid
+ init.posix.setgid = self.__orig_setgid
+ init.posix.setuid = self.__orig_setuid
isc.log.Logger = self.__orig_logger_class
def test_init(self):
- init = Init()
- self.assertEqual(init.verbose, False)
- self.assertEqual(init.msgq_socket_file, None)
- 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)
- self.assertIsNone(init._socket_cache)
+ 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
@@ -379,14 +371,14 @@ class TestInit(unittest.TestCase):
self.__uid_set = uid
def test_change_user(self):
- b10_init.posix.setgid = self.__setgid
- b10_init.posix.setuid = self.__setuid
+ init.posix.setgid = self.__setgid
+ init.posix.setuid = self.__setuid
self.__gid_set = None
self.__uid_set = None
- init = Init()
- init.change_user()
- # No gid/uid set in b10_init, nothing called.
+ 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)
@@ -401,22 +393,22 @@ class TestInit(unittest.TestCase):
raise ex
# Let setgid raise an exception
- b10_init.posix.setgid = raising_set_xid
- b10_init.posix.setuid = self.__setuid
- self.assertRaises(b10_init.ChangeUserError,
+ 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
- b10_init.posix.setgid = self.__setgid
- b10_init.posix.setuid = raising_set_xid
- self.assertRaises(b10_init.ChangeUserError,
+ 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
- b10_init.posix.setgid = self.__setgid
- b10_init.posix.setuid = self.__setuid
+ init.posix.setgid = self.__setgid
+ init.posix.setuid = self.__setuid
isc.log.Logger = raising_set_xid
- self.assertRaises(b10_init.ChangeUserError,
+ self.assertRaises(init.ChangeUserError,
Init(setuid=42, setgid=4200).change_user)
def test_set_creator(self):
@@ -1676,15 +1668,15 @@ class TestInitComponents(unittest.TestCase):
def test_start_msgq_timeout(self):
'''Test that b10-msgq startup attempts connections several times
and times out eventually.'''
- init = MockInitSimple()
- init.c_channel_env = {}
+ b10_init = MockInitSimple()
+ b10_init.c_channel_env = {}
# set the timeout to an arbitrary pre-determined value (which
# code below depends on)
- init.msgq_timeout = 1
- init._run_under_unittests = False
+ b10_init.msgq_timeout = 1
+ b10_init._run_under_unittests = False
# use the MockProcessInfo creator
- init._make_process_info = init._make_mock_process_info
+ b10_init._make_process_info = b10_init._make_mock_process_info
global attempts
global tsec
@@ -1714,10 +1706,10 @@ class TestInitComponents(unittest.TestCase):
isc.cc.Session = DummySessionAlwaysFails
- with self.assertRaises(b10_init.CChannelConnectError):
+ with self.assertRaises(init.CChannelConnectError):
# An exception will be thrown here when it eventually times
# out.
- pi = init.start_msgq()
+ 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
@@ -1745,12 +1737,12 @@ class TestInitComponents(unittest.TestCase):
attempts = 0
tsec = 0
- pi = init.start_msgq()
+ pi = b10_init.start_msgq()
# just one attempt, but 2 calls to time.time()
self.assertEqual(attempts, 2)
- self.assertEqual(cc_socket_file, init.msgq_socket_file)
+ 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
@@ -1831,15 +1823,15 @@ class TestInitComponents(unittest.TestCase):
class DummySession():
def group_recvmsg(self):
return (None, None)
- init = MockInitSimple()
- init.c_channel_env = {}
- init.cc_session = DummySession()
+ 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)
- init.wait_time = 2
+ b10_init.wait_time = 2
# use the MockProcessInfo creator
- init._make_process_info = init._make_mock_process_info
+ b10_init._make_process_info = b10_init._make_mock_process_info
global attempts
attempts = 0
@@ -1851,8 +1843,8 @@ class TestInitComponents(unittest.TestCase):
# We just check that an exception was thrown, and that several
# attempts were made to connect.
- with self.assertRaises(b10_init.ProcessStartError):
- pi = init.start_cfgmgr()
+ 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)
@@ -1871,16 +1863,17 @@ class TestInitComponents(unittest.TestCase):
self.started = False
def start(self):
self.started = True
- init = MockInitSimple()
+ b10_init = MockInitSimple()
self._tmp_module_cc_session = isc.config.ModuleCCSession
isc.config.ModuleCCSession = DummySession
- init.start_ccsession({})
- self.assertEqual(b10_init.SPECFILE_LOCATION, init.ccs.specfile)
- self.assertEqual(init.config_handler, init.ccs.config_handler)
- self.assertEqual(init.command_handler, init.ccs.command_handler)
- self.assertEqual(init.msgq_socket_file, init.ccs.socket_file)
- self.assertTrue(init.ccs.started)
+ 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().
@@ -2110,42 +2103,43 @@ class TestInitComponents(unittest.TestCase):
# 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.
- init = MockInitStartup(False)
- r = init.startup()
+ b10_init = MockInitStartup(False)
+ r = b10_init.startup()
self.assertIsNone(r)
- self.assertTrue(init.started)
- self.assertFalse(init.killed)
- self.assertTrue(init.runnable)
- self.assertEqual({}, init.c_channel_env)
+ 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.
- init = MockInitStartup(True)
- r = init.startup()
+ 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(init.started)
- self.assertTrue(init.killed)
- self.assertFalse(init.runnable)
- self.assertEqual({}, init.c_channel_env)
+ 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
- init = MockInitStartup(False)
- init.msgq_socket_file = 'foo'
- r = init.startup()
- self.assertEqual({'BIND10_MSGQ_SOCKET_FILE': 'foo'}, init.c_channel_env)
+ 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
- init = MockInitStartup(b10_init.ChangeUserError('failed to chusr'))
- r = init.startup()
+ b10_init = MockInitStartup(init.ChangeUserError('failed to chusr'))
+ r = b10_init.startup()
self.assertIn('failed to chusr', r)
- self.assertTrue(init.killed)
+ self.assertTrue(b10_init.killed)
# Check the case when socket file already exists
isc.cc.Session = DummySessionSocketExists
- init = MockInitStartup(False)
- r = init.startup()
+ b10_init = MockInitStartup(False)
+ r = b10_init.startup()
self.assertIn('already running', r)
# isc.cc.Session is restored during tearDown().
@@ -2160,7 +2154,7 @@ class SocketSrvTest(unittest.TestCase):
Create the b10-init to test, testdata and backup some functions.
"""
self.__b10_init = Init()
- self.__select_backup = b10_init.select.select
+ self.__select_backup = init.select.select
self.__select_called = None
self.__socket_data_called = None
self.__consumer_dead_called = None
@@ -2170,7 +2164,7 @@ class SocketSrvTest(unittest.TestCase):
"""
Restore functions.
"""
- b10_init.select.select = self.__select_backup
+ init.select.select = self.__select_backup
class __FalseSocket:
"""
@@ -2226,7 +2220,7 @@ class SocketSrvTest(unittest.TestCase):
def __accept(self):
"""
- Hijact the accept method of the b10-init.
+ Hijack the accept method of the b10-init.
Notes down it was called and stops b10-init.
"""
@@ -2242,7 +2236,7 @@ class SocketSrvTest(unittest.TestCase):
self.__b10_init._srv_socket = self.__FalseSocket(self)
self.__b10_init._srv_accept = self.__accept
self.__b10_init.ccs = self.__CCS()
- b10_init.select.select = self.__select_accept
+ init.select.select = self.__select_accept
self.__b10_init.run(2)
# It called the accept
self.assertTrue(self.__accept_called)
@@ -2275,7 +2269,7 @@ class SocketSrvTest(unittest.TestCase):
self.__b10_init.ccs = self.__CCS()
self.__b10_init._unix_sockets = {13: (self.__FalseSocket(self, 13), b'')}
self.__b10_init.runnable = True
- b10_init.select.select = self.__select_data
+ 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)
@@ -2352,15 +2346,15 @@ class TestFunctions(unittest.TestCase):
self.assertFalse(os.path.exists(self.lockfile_testpath))
os.mkdir(self.lockfile_testpath)
self.assertTrue(os.path.isdir(self.lockfile_testpath))
- self.__isfile_orig = b10_init.os.path.isfile
- self.__unlink_orig = b10_init.os.unlink
+ 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@"
- b10_init.os.path.isfile = self.__isfile_orig
- b10_init.os.unlink = self.__unlink_orig
+ 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
@@ -2374,7 +2368,7 @@ class TestFunctions(unittest.TestCase):
self.assertTrue(os.path.isfile(fname))
# first call should clear up all the lockfiles
- b10_init.remove_lock_files()
+ init.remove_lock_files()
# check if the lockfiles exist
for f in lockfiles:
@@ -2382,7 +2376,7 @@ class TestFunctions(unittest.TestCase):
self.assertFalse(os.path.isfile(fname))
# second call should not assert anyway
- b10_init.remove_lock_files()
+ init.remove_lock_files()
def test_remove_lock_files_fail(self):
# Permission error on unlink is ignored; other exceptions are really
@@ -2390,40 +2384,40 @@ class TestFunctions(unittest.TestCase):
def __raising_unlink(unused, ex):
raise ex
- b10_init.os.path.isfile = lambda _: True
+ init.os.path.isfile = lambda _: True
os_error = OSError()
- b10_init.os.unlink = lambda f: __raising_unlink(f, os_error)
+ init.os.unlink = lambda f: __raising_unlink(f, os_error)
os_error.errno = errno.EPERM
- b10_init.remove_lock_files() # no disruption
+ init.remove_lock_files() # no disruption
os_error.errno = errno.EACCES
- b10_init.remove_lock_files() # no disruption
+ init.remove_lock_files() # no disruption
os_error.errno = errno.ENOENT
- self.assertRaises(OSError, b10_init.remove_lock_files)
+ self.assertRaises(OSError, init.remove_lock_files)
- b10_init.os.unlink = lambda f: __raising_unlink(f, Exception('bad'))
- self.assertRaises(Exception, b10_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 = b10_init.get_signame(signal.SIGTERM)
+ signame = init.get_signame(signal.SIGTERM)
self.assertEqual('SIGTERM', signame)
- signame = b10_init.get_signame(signal.SIGKILL)
+ signame = init.get_signame(signal.SIGKILL)
self.assertEqual('SIGKILL', signame)
# 59426 is hopefully an unused signal on most platforms
- signame = b10_init.get_signame(59426)
+ signame = init.get_signame(59426)
self.assertEqual('Unknown signal 59426', signame)
def test_fatal_signal(self):
- self.assertIsNone(b10_init.b10_init)
- b10_init.b10_init = Init()
- b10_init.b10_init.runnable = True
- b10_init.fatal_signal(signal.SIGTERM, None)
+ 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(b10_init.b10_init.runnable)
- b10_init.b10_init = None
+ self.assertFalse(init.b10_init.runnable)
+ init.b10_init = None
if __name__ == '__main__':
# store os.environ for test_unchanged_environment
diff --git a/src/bin/bindctl/bindctl_main.py.in b/src/bin/bindctl/bindctl_main.py.in
index 9b75648..875b06e 100755
--- a/src/bin/bindctl/bindctl_main.py.in
+++ b/src/bin/bindctl/bindctl_main.py.in
@@ -34,7 +34,7 @@ isc.util.process.rename()
# number, and the overall BIND 10 version number (set in configure.ac).
VERSION = "bindctl 20110217 (BIND 10 @PACKAGE_VERSION@)"
-DEFAULT_IDENTIFIER_DESC = "The identifier specifies the config item. Child elements are separated with the '/' character. List indices can be specified with '[i]', where i is an integer specifying the index, starting with 0. Examples: 'Init/start_auth', 'Recurse/listen_on[0]/address'. If no identifier is given, shows the item at the current location."
+DEFAULT_IDENTIFIER_DESC = "The identifier specifies the config item. Child elements are separated with the '/' character. List indices can be specified with '[i]', where i is an integer specifying the index, starting with 0. Examples: 'Init/start_auth', 'Auth/listen_on[0]/address'. If no identifier is given, shows the item at the current location."
def prepare_config_commands(tool):
'''Prepare fixed commands for local configuration editing'''
diff --git a/src/bin/ddns/b10-ddns.xml b/src/bin/ddns/b10-ddns.xml
index 1373e14..7935482 100644
--- a/src/bin/ddns/b10-ddns.xml
+++ b/src/bin/ddns/b10-ddns.xml
@@ -119,7 +119,7 @@
<listitem>
<para>
This value is ignored at this moment, but is provided for
- compatibility with the <command>bind10</command> Init process.
+ compatibility with the <command>b10-init</command> process.
</para>
</listitem>
</varlistentry>
diff --git a/src/bin/dhcp4/tests/dhcp4_test.py b/src/bin/dhcp4/tests/dhcp4_test.py
index 9585464..276456e 100644
--- a/src/bin/dhcp4/tests/dhcp4_test.py
+++ b/src/bin/dhcp4/tests/dhcp4_test.py
@@ -13,15 +13,7 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# Because the file has a hyphen in its name, we need to work around
-# the standard syntax here
-b10_init = __import__('b10-init')
-# emulate the 'from X import'
-ProcessInfo = b10_init.ProcessInfo
-parse_args = b10_init.parse_args
-dump_pid = b10_init.dump_pid
-unlink_pid_file = b10_init.unlink_pid_file
-_BASETIME = b10_init._BASETIME
+from init import ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
import unittest
import sys
diff --git a/src/bin/dhcp6/tests/dhcp6_test.py b/src/bin/dhcp6/tests/dhcp6_test.py
index 348d95c..3333111 100644
--- a/src/bin/dhcp6/tests/dhcp6_test.py
+++ b/src/bin/dhcp6/tests/dhcp6_test.py
@@ -13,15 +13,7 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# Because the file has a hyphen in its name, we need to work around
-# the standard syntax here
-b10_init = __import__('b10-init')
-# emulate the 'from X import'
-ProcessInfo = b10_init.ProcessInfo
-parse_args = b10_init.parse_args
-dump_pid = b10_init.dump_pid
-unlink_pid_file = b10_init.unlink_pid_file
-_BASETIME = b10_init._BASETIME
+from init import ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
import unittest
import sys
diff --git a/src/bin/stats/b10-stats-httpd.xml b/src/bin/stats/b10-stats-httpd.xml
index 85ea7ef..be91737 100644
--- a/src/bin/stats/b10-stats-httpd.xml
+++ b/src/bin/stats/b10-stats-httpd.xml
@@ -54,7 +54,7 @@
intended for HTTP/XML interface for statistics module. This server
process runs as a process separated from the process of the BIND 10 Stats
daemon (<command>b10-stats</command>). The server is initially executed
- by the b10-init process (<command>bind10</command>) and eventually
+ by the b10-init process and eventually
exited by it. The server is intended to serve requests by HTTP
clients like web browsers and third-party modules. When the server is
asked, it requests BIND 10 statistics data or its schema from
@@ -74,7 +74,7 @@
10 statistics. The server uses CC session in communication
with <command>b10-stats</command>. CC session is provided
by <command>b10-msgq</command> which is started
- by <command>bind10</command> in advance. The server is implemented by
+ by <command>b10-init</command> in advance. The server is implemented by
HTTP-server libraries included in Python 3. The server obtains the
configuration from the config manager (<command>b10-cfgmgr</command>) in
runtime. Please see below for more details about this spec file and
diff --git a/src/bin/stats/b10-stats.xml b/src/bin/stats/b10-stats.xml
index 280f79c..bbdb96e 100644
--- a/src/bin/stats/b10-stats.xml
+++ b/src/bin/stats/b10-stats.xml
@@ -56,18 +56,18 @@
from each BIND 10 module. Its statistics information may be
reported via <command>bindctl</command> or
<command>b10-stats-httpd</command>. It is started by
- <command>bind10</command> and communicates by using the
+ <command>b10-init</command> and communicates by using the
Command Channel by <command>b10-msgq</command> with other
- modules like <command>bind10</command>, <command>b10-auth</command>
+ modules like <command>b10-init</command>, <command>b10-auth</command>
and so on. <command>b10-stats</command> periodically requests statistics
data from each module. The interval time can be configured
via <command>bindctl</command>. <command>b10-stats</command> cannot
accept any command from other modules for updating statistics data. The
stats module collects data and
aggregates it. <command>b10-stats</command> invokes an internal
- command for <command>bind10</command> after its initial
+ command for <command>b10-init</command> after its initial
starting to make sure it collects statistics data from
- <command>bind10</command>.
+ <command>b10-init</command>.
</para>
</refsect1>
diff --git a/src/lib/python/isc/config/cfgmgr.py b/src/lib/python/isc/config/cfgmgr.py
index 4e71030..421c371 100644
--- a/src/lib/python/isc/config/cfgmgr.py
+++ b/src/lib/python/isc/config/cfgmgr.py
@@ -109,9 +109,14 @@ class ConfigManagerData:
if new_data_version == 2:
# 'Boss' got changed to 'Init'; If for some reason both are
# present, simply ignore the old one
- if 'Boss' in config and not 'Init' in config:
- config['Init'] = config['Boss']
- del config['Boss']
+ if 'Boss' in config:
+ if not 'Init' in config:
+ config['Init'] = config['Boss']
+ del config['Boss']
+ else:
+ # This should not happen, but we don't want to overwrite
+ # any config in this case, so warn about it
+ logger.warn(CFGMGR_CONFIG_UPDATE_BOSS_AND_INIT_FOUND)
new_data_version = 3
config['version'] = new_data_version
diff --git a/src/lib/python/isc/config/cfgmgr_messages.mes b/src/lib/python/isc/config/cfgmgr_messages.mes
index 8701db3..4b12bfd 100644
--- a/src/lib/python/isc/config/cfgmgr_messages.mes
+++ b/src/lib/python/isc/config/cfgmgr_messages.mes
@@ -41,6 +41,16 @@ system. The most likely cause is that msgq is not running.
The configuration manager is starting, reading and saving the configuration
settings to the shown file.
+% CFGMGR_CONFIG_UPDATE_BOSS_AND_INIT_FOUND Configuration found for both 'Boss' and 'Init', ignoring 'Boss'
+In the process of updating the configuration from version 2 to version 3,
+the configuration manager has found that there are existing configurations
+for both the old value 'Boss' and the new value 'Init'. This should in
+theory not happen, as in older versions 'Init' does not exist, and in newer
+versions 'Boss' does not exist. The configuration manager will continue
+with the update process, leaving the values for both as they are, so as not
+to overwrite any settings. However, the values for 'Boss' are ignored by
+BIND 10, and it is probably wise to check the configuration file manually.
+
% CFGMGR_DATA_READ_ERROR error reading configuration database from disk: %1
There was a problem reading the persistent configuration data as stored
on disk. The file may be corrupted, or it is of a version from where
diff --git a/tests/lettuce/configurations/auth/auth_badzone.config.orig b/tests/lettuce/configurations/auth/auth_badzone.config.orig
index 79e9410..f86882a 100644
--- a/tests/lettuce/configurations/auth/auth_badzone.config.orig
+++ b/tests/lettuce/configurations/auth/auth_badzone.config.orig
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [{
"severity": "DEBUG",
diff --git a/tests/lettuce/configurations/auth/auth_basic.config.orig b/tests/lettuce/configurations/auth/auth_basic.config.orig
index 5a6eb50..24f615c 100644
--- a/tests/lettuce/configurations/auth/auth_basic.config.orig
+++ b/tests/lettuce/configurations/auth/auth_basic.config.orig
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [ {
"debuglevel": 99,
diff --git a/tests/lettuce/configurations/bindctl/bindctl.config.orig b/tests/lettuce/configurations/bindctl/bindctl.config.orig
index 78de0e7..ef0e8e2 100644
--- a/tests/lettuce/configurations/bindctl/bindctl.config.orig
+++ b/tests/lettuce/configurations/bindctl/bindctl.config.orig
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [ {
"debuglevel": 99,
diff --git a/tests/lettuce/configurations/bindctl_commands.config.orig b/tests/lettuce/configurations/bindctl_commands.config.orig
index fd3f056..980262b 100644
--- a/tests/lettuce/configurations/bindctl_commands.config.orig
+++ b/tests/lettuce/configurations/bindctl_commands.config.orig
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [ {
"debuglevel": 99,
diff --git a/tests/lettuce/configurations/ddns/ddns.config.orig b/tests/lettuce/configurations/ddns/ddns.config.orig
index 1497f9f..02978be 100644
--- a/tests/lettuce/configurations/ddns/ddns.config.orig
+++ b/tests/lettuce/configurations/ddns/ddns.config.orig
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [
{
diff --git a/tests/lettuce/configurations/ddns/noddns.config.orig b/tests/lettuce/configurations/ddns/noddns.config.orig
index 8df0297..d075924 100644
--- a/tests/lettuce/configurations/ddns/noddns.config.orig
+++ b/tests/lettuce/configurations/ddns/noddns.config.orig
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [
{
diff --git a/tests/lettuce/configurations/default.config b/tests/lettuce/configurations/default.config
index 9e1d3d1..2713def 100644
--- a/tests/lettuce/configurations/default.config
+++ b/tests/lettuce/configurations/default.config
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [ {
"debuglevel": 99,
diff --git a/tests/lettuce/configurations/example.org.config.orig b/tests/lettuce/configurations/example.org.config.orig
index af6f948..7da6304 100644
--- a/tests/lettuce/configurations/example.org.config.orig
+++ b/tests/lettuce/configurations/example.org.config.orig
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [ {
"debuglevel": 99,
diff --git a/tests/lettuce/configurations/example.org.inmem.config b/tests/lettuce/configurations/example.org.inmem.config
index 620964f..7ec921d 100644
--- a/tests/lettuce/configurations/example.org.inmem.config
+++ b/tests/lettuce/configurations/example.org.inmem.config
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [{
"severity": "DEBUG",
diff --git a/tests/lettuce/configurations/example2.org.config b/tests/lettuce/configurations/example2.org.config
index 72f2f9c..3bb3330 100644
--- a/tests/lettuce/configurations/example2.org.config
+++ b/tests/lettuce/configurations/example2.org.config
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [ {
"severity": "DEBUG",
diff --git a/tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf b/tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf
index 5776ccc..d93a8c6 100644
--- a/tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf
+++ b/tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [ {
"debuglevel": 99,
diff --git a/tests/lettuce/configurations/ixfr-out/testset1-config.db b/tests/lettuce/configurations/ixfr-out/testset1-config.db
index 2f9acf8..d5eaf83 100644
--- a/tests/lettuce/configurations/ixfr-out/testset1-config.db
+++ b/tests/lettuce/configurations/ixfr-out/testset1-config.db
@@ -9,7 +9,7 @@
}
]
},
- "version": 2,
+ "version": 3,
"Logging": {
"loggers":
[
diff --git a/tests/lettuce/configurations/multi_instance/multi_auth.config.orig b/tests/lettuce/configurations/multi_instance/multi_auth.config.orig
index 68b3d3c..96e25d8 100644
--- a/tests/lettuce/configurations/multi_instance/multi_auth.config.orig
+++ b/tests/lettuce/configurations/multi_instance/multi_auth.config.orig
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [ {
"debuglevel": 99,
diff --git a/tests/lettuce/configurations/no_db_file.config b/tests/lettuce/configurations/no_db_file.config
index 3552802..9e6c168 100644
--- a/tests/lettuce/configurations/no_db_file.config
+++ b/tests/lettuce/configurations/no_db_file.config
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [ {
"severity": "DEBUG",
diff --git a/tests/lettuce/configurations/nsec3/nsec3_auth.config b/tests/lettuce/configurations/nsec3/nsec3_auth.config
index fa43c41..5dfffa1 100644
--- a/tests/lettuce/configurations/nsec3/nsec3_auth.config
+++ b/tests/lettuce/configurations/nsec3/nsec3_auth.config
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [
{
diff --git a/tests/lettuce/configurations/resolver/resolver_basic.config.orig b/tests/lettuce/configurations/resolver/resolver_basic.config.orig
index 537fb28..fe5ddd0 100644
--- a/tests/lettuce/configurations/resolver/resolver_basic.config.orig
+++ b/tests/lettuce/configurations/resolver/resolver_basic.config.orig
@@ -1 +1,31 @@
-{"version": 2, "Logging": {"loggers": [{"severity": "DEBUG", "name": "*", "debuglevel": 99}]}, "Resolver": {"query_acl": [{"action": "REJECT", "from": "127.0.0.1"}], "listen_on": [{"port": 47806, "address": "127.0.0.1"}]}, "Init": {"components": {"b10-resolver": {"kind": "needed"}, "b10-cmdctl": {"kind": "needed", "special": "cmdctl"}}}}
+{
+ "version": 3,
+ "Logging": {
+ "loggers": [ {
+ "severity": "DEBUG",
+ "name": "*",
+ "debuglevel": 99
+ } ]
+ },
+ "Resolver": {
+ "query_acl": [ {
+ "action": "REJECT",
+ "from": "127.0.0.1"
+ } ],
+ "listen_on": [ {
+ "port": 47806,
+ "address": "127.0.0.1"
+ } ]
+ },
+ "Init": {
+ "components": {
+ "b10-resolver": {
+ "kind": "needed"
+ },
+ "b10-cmdctl": {
+ "kind": "needed",
+ "special": "cmdctl"
+ }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/xfrin/inmem_slave.conf b/tests/lettuce/configurations/xfrin/inmem_slave.conf
index 6342d39..fedf372 100644
--- a/tests/lettuce/configurations/xfrin/inmem_slave.conf
+++ b/tests/lettuce/configurations/xfrin/inmem_slave.conf
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [ {
"debuglevel": 99,
diff --git a/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
index d6c2ce1..1b2953d 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
+++ b/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [ {
"debuglevel": 99,
diff --git a/tests/lettuce/configurations/xfrin/retransfer_master_nons.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_master_nons.conf.orig
index 988a114..bccadf7 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_master_nons.conf.orig
+++ b/tests/lettuce/configurations/xfrin/retransfer_master_nons.conf.orig
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [ {
"debuglevel": 99,
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_slave.conf.orig
index 6ad48c3..2e6b17f 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_slave.conf.orig
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave.conf.orig
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [ {
"debuglevel": 99,
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf b/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf
index b1fcfe5..a5c22b1 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf
@@ -1,5 +1,5 @@
{
- "version": 2,
+ "version": 3,
"Logging": {
"loggers": [ {
"debuglevel": 99,
More information about the bind10-changes
mailing list