|
@@ -72,7 +72,7 @@ isc.util.process.rename(sys.argv[0])
|
|
# This is the version that gets displayed to the user.
|
|
# This is the version that gets displayed to the user.
|
|
# The VERSION string consists of the module name, the module version
|
|
# The VERSION string consists of the module name, the module version
|
|
# number, and the overall BIND 10 version number (set in configure.ac).
|
|
# number, and the overall BIND 10 version number (set in configure.ac).
|
|
-VERSION = "bind10 20101129 (BIND 10 @PACKAGE_VERSION@)"
|
|
+VERSION = "bind10 20110223 (BIND 10 @PACKAGE_VERSION@)"
|
|
|
|
|
|
# This is for bind10.boottime of stats module
|
|
# This is for bind10.boottime of stats module
|
|
_BASETIME = time.gmtime()
|
|
_BASETIME = time.gmtime()
|
|
@@ -194,8 +194,8 @@ class CChannelConnectError(Exception): pass
|
|
class BoB:
|
|
class BoB:
|
|
"""Boss of BIND class."""
|
|
"""Boss of BIND class."""
|
|
|
|
|
|
- def __init__(self, msgq_socket_file=None, dns_port=5300, address=None,
|
|
+ def __init__(self, msgq_socket_file=None, nocache=False, verbose=False,
|
|
- nocache=False, verbose=False, setuid=None, username=None):
|
|
+ setuid=None, username=None):
|
|
"""
|
|
"""
|
|
Initialize the Boss of BIND. This is a singleton (only one can run).
|
|
Initialize the Boss of BIND. This is a singleton (only one can run).
|
|
|
|
|
|
@@ -203,29 +203,72 @@ class BoB:
|
|
msgq process listens on. If verbose is True, then the boss reports
|
|
msgq process listens on. If verbose is True, then the boss reports
|
|
what it is doing.
|
|
what it is doing.
|
|
"""
|
|
"""
|
|
- self.address = address
|
|
|
|
- self.dns_port = dns_port
|
|
|
|
self.cc_session = None
|
|
self.cc_session = None
|
|
self.ccs = None
|
|
self.ccs = None
|
|
self.cfg_start_auth = True
|
|
self.cfg_start_auth = True
|
|
self.cfg_start_resolver = False
|
|
self.cfg_start_resolver = False
|
|
|
|
+ self.started_auth_family = False
|
|
|
|
+ self.started_resolver_family = False
|
|
self.curproc = None
|
|
self.curproc = None
|
|
self.dead_processes = {}
|
|
self.dead_processes = {}
|
|
self.msgq_socket_file = msgq_socket_file
|
|
self.msgq_socket_file = msgq_socket_file
|
|
self.nocache = nocache
|
|
self.nocache = nocache
|
|
self.processes = {}
|
|
self.processes = {}
|
|
|
|
+ self.expected_shutdowns = {}
|
|
self.runnable = False
|
|
self.runnable = False
|
|
self.uid = setuid
|
|
self.uid = setuid
|
|
self.username = username
|
|
self.username = username
|
|
self.verbose = verbose
|
|
self.verbose = verbose
|
|
|
|
|
|
def config_handler(self, new_config):
|
|
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
|
|
|
|
+ # Now we declare few functions used only internally here. Besides the
|
|
|
|
+ # benefit of not polluting the name space, they are closures, so we
|
|
|
|
+ # don't need to pass some variables
|
|
|
|
+ def start_stop(name, started, start, stop):
|
|
|
|
+ if not'start_' + name in new_config:
|
|
|
|
+ return
|
|
|
|
+ if new_config['start_' + name]:
|
|
|
|
+ if not started:
|
|
|
|
+ if self.uid is not None:
|
|
|
|
+ sys.stderr.write("[bind10] Starting " + name + " as " +
|
|
|
|
+ "a user, not root. This might fail.\n")
|
|
|
|
+ start()
|
|
|
|
+ else:
|
|
|
|
+ stop()
|
|
|
|
+ # These four functions are passed to start_stop (smells like functional
|
|
|
|
+ # programming little bit)
|
|
|
|
+ def resolver_on():
|
|
|
|
+ self.start_resolver(self.c_channel_env)
|
|
|
|
+ self.started_resolver_family = True
|
|
|
|
+ def resolver_off():
|
|
|
|
+ self.stop_resolver()
|
|
|
|
+ self.started_resolver_family = False
|
|
|
|
+ def auth_on():
|
|
|
|
+ self.start_auth(self.c_channel_env)
|
|
|
|
+ self.start_xfrout(self.c_channel_env)
|
|
|
|
+ self.start_xfrin(self.c_channel_env)
|
|
|
|
+ self.start_zonemgr(self.c_channel_env)
|
|
|
|
+ self.started_auth_family = True
|
|
|
|
+ def auth_off():
|
|
|
|
+ self.stop_zonemgr()
|
|
|
|
+ self.stop_xfrin()
|
|
|
|
+ self.stop_xfrout()
|
|
|
|
+ self.stop_auth()
|
|
|
|
+ self.started_auth_family = False
|
|
|
|
+
|
|
|
|
+ # The real code of the config handler function follows here
|
|
if self.verbose:
|
|
if self.verbose:
|
|
sys.stdout.write("[bind10] Handling new configuration: " +
|
|
sys.stdout.write("[bind10] Handling new configuration: " +
|
|
str(new_config) + "\n")
|
|
str(new_config) + "\n")
|
|
|
|
+ start_stop('resolver', self.started_resolver_family, resolver_on,
|
|
|
|
+ resolver_off)
|
|
|
|
+ start_stop('auth', self.started_auth_family, auth_on, auth_off)
|
|
|
|
+
|
|
answer = isc.config.ccsession.create_answer(0)
|
|
answer = isc.config.ccsession.create_answer(0)
|
|
return answer
|
|
return answer
|
|
- # TODO
|
|
|
|
|
|
|
|
def command_handler(self, command, args):
|
|
def command_handler(self, command, args):
|
|
if self.verbose:
|
|
if self.verbose:
|
|
@@ -314,8 +357,8 @@ class BoB:
|
|
sys.stdout.write("\n")
|
|
sys.stdout.write("\n")
|
|
|
|
|
|
# The next few methods start the individual processes of BIND-10. They
|
|
# The next few methods start the individual processes of BIND-10. They
|
|
- # are called via start_all_process(). If any fail, an exception is raised
|
|
+ # are called via start_all_processes(). If any fail, an exception is
|
|
- # which is caught by the caller of start_all_processes(); this kills
|
|
+ # raised which is caught by the caller of start_all_processes(); this kills
|
|
# processes started up to that point before terminating the program.
|
|
# processes started up to that point before terminating the program.
|
|
|
|
|
|
def start_msgq(self, c_channel_env):
|
|
def start_msgq(self, c_channel_env):
|
|
@@ -417,9 +460,6 @@ class BoB:
|
|
Start the Authoritative server
|
|
Start the Authoritative server
|
|
"""
|
|
"""
|
|
authargs = ['b10-auth']
|
|
authargs = ['b10-auth']
|
|
- authargs += ['-p', str(self.dns_port)]
|
|
|
|
- if self.address:
|
|
|
|
- authargs += ['-a', str(self.address)]
|
|
|
|
if self.nocache:
|
|
if self.nocache:
|
|
authargs += ['-n']
|
|
authargs += ['-n']
|
|
if self.uid:
|
|
if self.uid:
|
|
@@ -428,8 +468,7 @@ class BoB:
|
|
authargs += ['-v']
|
|
authargs += ['-v']
|
|
|
|
|
|
# ... and start
|
|
# ... and start
|
|
- self.start_process("b10-auth", authargs, c_channel_env,
|
|
+ self.start_process("b10-auth", authargs, c_channel_env)
|
|
- self.dns_port, self.address)
|
|
|
|
|
|
|
|
def start_resolver(self, c_channel_env):
|
|
def start_resolver(self, c_channel_env):
|
|
"""
|
|
"""
|
|
@@ -464,11 +503,12 @@ class BoB:
|
|
# XXX: we hardcode port 8080
|
|
# XXX: we hardcode port 8080
|
|
self.start_simple("b10-cmdctl", c_channel_env, 8080)
|
|
self.start_simple("b10-cmdctl", c_channel_env, 8080)
|
|
|
|
|
|
- def start_all_processes(self, c_channel_env):
|
|
+ def start_all_processes(self):
|
|
"""
|
|
"""
|
|
Starts up all the processes. Any exception generated during the
|
|
Starts up all the processes. Any exception generated during the
|
|
starting of the processes is handled by the caller.
|
|
starting of the processes is handled by the caller.
|
|
"""
|
|
"""
|
|
|
|
+ c_channel_env = self.c_channel_env
|
|
self.start_msgq(c_channel_env)
|
|
self.start_msgq(c_channel_env)
|
|
self.start_cfgmgr(c_channel_env)
|
|
self.start_cfgmgr(c_channel_env)
|
|
self.start_ccsession(c_channel_env)
|
|
self.start_ccsession(c_channel_env)
|
|
@@ -485,6 +525,7 @@ class BoB:
|
|
# ... and resolver (if selected):
|
|
# ... and resolver (if selected):
|
|
if self.cfg_start_resolver:
|
|
if self.cfg_start_resolver:
|
|
self.start_resolver(c_channel_env)
|
|
self.start_resolver(c_channel_env)
|
|
|
|
+ self.started_resolver_family = True
|
|
|
|
|
|
# Everything after the main components can run as non-root.
|
|
# Everything after the main components can run as non-root.
|
|
# TODO: this is only temporary - once the privileged socket creator is
|
|
# TODO: this is only temporary - once the privileged socket creator is
|
|
@@ -498,6 +539,7 @@ class BoB:
|
|
self.start_xfrout(c_channel_env)
|
|
self.start_xfrout(c_channel_env)
|
|
self.start_xfrin(c_channel_env)
|
|
self.start_xfrin(c_channel_env)
|
|
self.start_zonemgr(c_channel_env)
|
|
self.start_zonemgr(c_channel_env)
|
|
|
|
+ self.started_auth_family = True
|
|
|
|
|
|
# ... and finally start the remaining processes
|
|
# ... and finally start the remaining processes
|
|
self.start_stats(c_channel_env)
|
|
self.start_stats(c_channel_env)
|
|
@@ -528,7 +570,8 @@ class BoB:
|
|
# Start all processes. If any one fails to start, kill all started
|
|
# Start all processes. If any one fails to start, kill all started
|
|
# processes and exit with an error indication.
|
|
# processes and exit with an error indication.
|
|
try:
|
|
try:
|
|
- self.start_all_processes(c_channel_env)
|
|
+ self.c_channel_env = c_channel_env
|
|
|
|
+ self.start_all_processes()
|
|
except Exception as e:
|
|
except Exception as e:
|
|
self.kill_started_processes()
|
|
self.kill_started_processes()
|
|
return "Unable to start " + self.curproc + ": " + str(e)
|
|
return "Unable to start " + self.curproc + ": " + str(e)
|
|
@@ -550,10 +593,35 @@ class BoB:
|
|
self.cc_session.group_sendmsg(cmd, "Zonemgr", "Zonemgr")
|
|
self.cc_session.group_sendmsg(cmd, "Zonemgr", "Zonemgr")
|
|
self.cc_session.group_sendmsg(cmd, "Stats", "Stats")
|
|
self.cc_session.group_sendmsg(cmd, "Stats", "Stats")
|
|
|
|
|
|
- def stop_process(self, process):
|
|
+ def stop_process(self, process, recipient):
|
|
- """Stop the given process, friendly-like."""
|
|
+ """
|
|
- # XXX nothing yet
|
|
+ Stop the given process, friendly-like. The process is the name it has
|
|
- pass
|
|
+ (in logs, etc), the recipient is the address on msgq.
|
|
|
|
+ """
|
|
|
|
+ if self.verbose:
|
|
|
|
+ sys.stdout.write("[bind10] Asking %s to terminate\n" % process)
|
|
|
|
+ # TODO: Some timeout to solve processes that don't want to die would
|
|
|
|
+ # help. We can even store it in the dict, it is used only as a set
|
|
|
|
+ self.expected_shutdowns[process] = 1
|
|
|
|
+ # Ask the process to die willingly
|
|
|
|
+ self.cc_session.group_sendmsg({'command': ['shutdown']}, recipient,
|
|
|
|
+ recipient)
|
|
|
|
+
|
|
|
|
+ # Series of stop_process wrappers
|
|
|
|
+ def stop_resolver(self):
|
|
|
|
+ self.stop_process('b10-resolver', 'Resolver')
|
|
|
|
+
|
|
|
|
+ def stop_auth(self):
|
|
|
|
+ self.stop_process('b10-auth', 'Auth')
|
|
|
|
+
|
|
|
|
+ def stop_xfrout(self):
|
|
|
|
+ self.stop_process('b10-xfrout', 'Xfrout')
|
|
|
|
+
|
|
|
|
+ def stop_xfrin(self):
|
|
|
|
+ self.stop_process('b10-xfrin', 'Xfrin')
|
|
|
|
+
|
|
|
|
+ def stop_zonemgr(self):
|
|
|
|
+ self.stop_process('b10-zonemgr', 'Zonemgr')
|
|
|
|
|
|
def shutdown(self):
|
|
def shutdown(self):
|
|
"""Stop the BoB instance."""
|
|
"""Stop the BoB instance."""
|
|
@@ -659,6 +727,10 @@ class BoB:
|
|
still_dead = {}
|
|
still_dead = {}
|
|
now = time.time()
|
|
now = time.time()
|
|
for proc_info in self.dead_processes.values():
|
|
for proc_info in self.dead_processes.values():
|
|
|
|
+ if proc_info.name in self.expected_shutdowns:
|
|
|
|
+ # We don't restart, we wanted it to die
|
|
|
|
+ del self.expected_shutdowns[proc_info.name]
|
|
|
|
+ continue
|
|
restart_time = proc_info.restart_schedule.get_restart_time(now)
|
|
restart_time = proc_info.restart_schedule.get_restart_time(now)
|
|
if restart_time > now:
|
|
if restart_time > now:
|
|
if (next_restart is None) or (next_restart > restart_time):
|
|
if (next_restart is None) or (next_restart > restart_time):
|
|
@@ -709,32 +781,39 @@ def fatal_signal(signal_number, stack_frame):
|
|
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
|
|
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
|
|
boss_of_bind.runnable = False
|
|
boss_of_bind.runnable = False
|
|
|
|
|
|
-def check_port(option, opt_str, value, parser):
|
|
|
|
- """Function to insure that the port we are passed is actually
|
|
|
|
- a valid port number. Used by OptionParser() on startup."""
|
|
|
|
- try:
|
|
|
|
- if opt_str in ['-p', '--port']:
|
|
|
|
- parser.values.dns_port = isc.net.parse.port_parse(value)
|
|
|
|
- else:
|
|
|
|
- raise OptionValueError("Unknown option " + opt_str)
|
|
|
|
- except ValueError as e:
|
|
|
|
- raise OptionValueError(str(e))
|
|
|
|
-
|
|
|
|
-def check_addr(option, opt_str, value, parser):
|
|
|
|
- """Function to insure that the address we are passed is actually
|
|
|
|
- a valid address. Used by OptionParser() on startup."""
|
|
|
|
- try:
|
|
|
|
- if opt_str in ['-a', '--address']:
|
|
|
|
- parser.values.address = isc.net.parse.addr_parse(value)
|
|
|
|
- else:
|
|
|
|
- raise OptionValueError("Unknown option " + opt_str)
|
|
|
|
- except ValueError:
|
|
|
|
- raise OptionValueError("%s requires a valid IPv4 or IPv6 address" % opt_str)
|
|
|
|
-
|
|
|
|
def process_rename(option, opt_str, value, parser):
|
|
def process_rename(option, opt_str, value, parser):
|
|
"""Function that renames the process if it is requested by a option."""
|
|
"""Function that renames the process if it is requested by a option."""
|
|
isc.util.process.rename(value)
|
|
isc.util.process.rename(value)
|
|
|
|
|
|
|
|
+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 main():
|
|
def main():
|
|
global options
|
|
global options
|
|
global boss_of_bind
|
|
global boss_of_bind
|
|
@@ -743,17 +822,11 @@ def main():
|
|
|
|
|
|
# Parse any command-line options.
|
|
# Parse any command-line options.
|
|
parser = OptionParser(version=VERSION)
|
|
parser = OptionParser(version=VERSION)
|
|
- parser.add_option("-a", "--address", dest="address", type="string",
|
|
|
|
- action="callback", callback=check_addr, default=None,
|
|
|
|
- help="address the DNS server will use (default: listen on all addresses)")
|
|
|
|
parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
|
|
parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
|
|
type="string", default=None,
|
|
type="string", default=None,
|
|
help="UNIX domain socket file the b10-msgq daemon will use")
|
|
help="UNIX domain socket file the b10-msgq daemon will use")
|
|
parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
|
|
parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
|
|
default=False, help="disable hot-spot cache in authoritative DNS server")
|
|
default=False, help="disable hot-spot cache in authoritative DNS server")
|
|
- parser.add_option("-p", "--port", dest="dns_port", type="int",
|
|
|
|
- action="callback", callback=check_port, default=5300,
|
|
|
|
- help="port the DNS server will use (default 5300)")
|
|
|
|
parser.add_option("-u", "--user", dest="user", type="string", default=None,
|
|
parser.add_option("-u", "--user", dest="user", type="string", default=None,
|
|
help="Change user after startup (must run as root)")
|
|
help="Change user after startup (must run as root)")
|
|
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
|
|
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
|
|
@@ -761,6 +834,9 @@ def main():
|
|
parser.add_option("--pretty-name", type="string", action="callback",
|
|
parser.add_option("--pretty-name", type="string", action="callback",
|
|
callback=process_rename,
|
|
callback=process_rename,
|
|
help="Set the process name (displayed in ps, top, ...)")
|
|
help="Set the process name (displayed in ps, top, ...)")
|
|
|
|
+ parser.add_option("--pid-file", dest="pid_file", type="string",
|
|
|
|
+ default=None,
|
|
|
|
+ help="file to dump the PID of the BIND 10 process")
|
|
(options, args) = parser.parse_args()
|
|
(options, args) = parser.parse_args()
|
|
if args:
|
|
if args:
|
|
parser.print_help()
|
|
parser.print_help()
|
|
@@ -814,14 +890,14 @@ def main():
|
|
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
|
|
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
|
|
|
|
|
|
# Go bob!
|
|
# Go bob!
|
|
- boss_of_bind = BoB(options.msgq_socket_file, options.dns_port,
|
|
+ boss_of_bind = BoB(options.msgq_socket_file, options.nocache,
|
|
- options.address, options.nocache, options.verbose,
|
|
+ options.verbose, setuid, username)
|
|
- setuid, username)
|
|
|
|
startup_result = boss_of_bind.startup()
|
|
startup_result = boss_of_bind.startup()
|
|
if startup_result:
|
|
if startup_result:
|
|
sys.stderr.write("[bind10] Error on startup: %s\n" % startup_result)
|
|
sys.stderr.write("[bind10] Error on startup: %s\n" % startup_result)
|
|
sys.exit(1)
|
|
sys.exit(1)
|
|
sys.stdout.write("[bind10] BIND 10 started\n")
|
|
sys.stdout.write("[bind10] BIND 10 started\n")
|
|
|
|
+ dump_pid(options.pid_file)
|
|
|
|
|
|
# send "bind10.boot_time" to b10-stats
|
|
# send "bind10.boot_time" to b10-stats
|
|
time.sleep(1) # wait a second
|
|
time.sleep(1) # wait a second
|
|
@@ -875,6 +951,7 @@ def main():
|
|
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
|
|
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
|
|
boss_of_bind.shutdown()
|
|
boss_of_bind.shutdown()
|
|
sys.stdout.write("[bind10] BIND 10 exiting\n");
|
|
sys.stdout.write("[bind10] BIND 10 exiting\n");
|
|
|
|
+ unlink_pid_file(options.pid_file)
|
|
sys.exit(0)
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if __name__ == "__main__":
|