123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751 |
- #!@PYTHON@
- # Copyright (C) 2010 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 Secondary Manager program.
- The secondary manager is one of the co-operating processes
- of BIND10, which keeps track of timers and other information
- necessary for BIND10 to act as a slave.
- """
- import sys; sys.path.append ('@@PYTHONPATH@@')
- import os
- import time
- import signal
- import isc
- import isc.dns
- import random
- import threading
- import select
- import socket
- import errno
- from isc.datasrc import sqlite3_ds
- from optparse import OptionParser, OptionValueError
- from isc.config.ccsession import *
- import isc.util.process
- from isc.log_messages.zonemgr_messages import *
- from isc.notify import notify_out
- # Initialize logging for called modules.
- isc.log.init("b10-zonemgr", buffer=True)
- logger = isc.log.Logger("zonemgr")
- # 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
- # Constants for debug levels.
- DBG_START_SHUT = logger.DBGLVL_START_SHUT
- DBG_ZONEMGR_COMMAND = logger.DBGLVL_COMMAND
- DBG_ZONEMGR_BASIC = logger.DBGLVL_TRACE_BASIC
- isc.util.process.rename()
- # If B10_FROM_BUILD 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_BUILD" in os.environ:
- SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/zonemgr"
- AUTH_SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/auth"
- else:
- PREFIX = "@prefix@"
- DATAROOTDIR = "@datarootdir@"
- SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
- AUTH_SPECFILE_PATH = SPECFILE_PATH
- SPECFILE_LOCATION = SPECFILE_PATH + "/zonemgr.spec"
- AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + "/auth.spec"
- __version__ = "BIND10"
- # define module name
- XFRIN_MODULE_NAME = 'Xfrin'
- AUTH_MODULE_NAME = 'Auth'
- # define command name
- ZONE_REFRESH_COMMAND = 'refresh_from_zonemgr'
- ZONE_NOTIFY_COMMAND = 'notify'
- # define zone state
- ZONE_OK = 0
- ZONE_REFRESHING = 1
- ZONE_EXPIRED = 2
- # offsets of fields in the SOA RDATA
- REFRESH_OFFSET = 3
- RETRY_OFFSET = 4
- EXPIRED_OFFSET = 5
- class ZonemgrException(Exception):
- pass
- class ZonemgrRefresh:
- """This class will maintain and manage zone refresh info.
- It also provides methods to keep track of zone timers and
- do zone refresh.
- Zone timers can be started by calling run_timer(), and it
- can be stopped by calling shutdown() in another thread.
- """
- def __init__(self, db_file, slave_socket, module_cc_session):
- self._mccs = module_cc_session
- self._check_sock = slave_socket
- self._db_file = db_file
- self._zonemgr_refresh_info = {}
- self._lowerbound_refresh = None
- self._lowerbound_retry = None
- self._max_transfer_timeout = None
- self._refresh_jitter = None
- self._reload_jitter = None
- self.update_config_data(module_cc_session.get_full_config(),
- module_cc_session)
- self._running = False
- def _random_jitter(self, max, jitter):
- """Imposes some random jitters for refresh and
- retry timers to avoid many zones need to do refresh
- at the same time.
- The value should be between (max - jitter) and max.
- """
- if 0 == jitter:
- return max
- return random.uniform(max - jitter, max)
- def _get_current_time(self):
- return time.time()
- def _set_zone_timer(self, zone_name_class, max, jitter):
- """Set zone next refresh time.
- jitter should not be bigger than half the original value."""
- self._set_zone_next_refresh_time(zone_name_class, self._get_current_time() + \
- self._random_jitter(max, jitter))
- def _set_zone_refresh_timer(self, zone_name_class):
- """Set zone next refresh time after zone refresh success.
- now + refresh - refresh_jitter <= next_refresh_time <= now + refresh
- """
- zone_refresh_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[REFRESH_OFFSET])
- zone_refresh_time = max(self._lowerbound_refresh, zone_refresh_time)
- self._set_zone_timer(zone_name_class, zone_refresh_time, self._refresh_jitter * zone_refresh_time)
- def _set_zone_retry_timer(self, zone_name_class):
- """Set zone next refresh time after zone refresh fail.
- now + retry - retry_jitter <= next_refresh_time <= now + retry
- """
- if (self._get_zone_soa_rdata(zone_name_class) is not None):
- zone_retry_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[RETRY_OFFSET])
- else:
- zone_retry_time = 0.0
- zone_retry_time = max(self._lowerbound_retry, zone_retry_time)
- self._set_zone_timer(zone_name_class, zone_retry_time, self._refresh_jitter * zone_retry_time)
- def _set_zone_notify_timer(self, zone_name_class):
- """Set zone next refresh time after receiving notify
- next_refresh_time = now
- """
- self._set_zone_timer(zone_name_class, 0, 0)
- def _zone_not_exist(self, zone_name_class):
- """ Zone doesn't belong to zonemgr"""
- return not zone_name_class in self._zonemgr_refresh_info
- def zone_refresh_success(self, zone_name_class):
- """Update zone info after zone refresh success"""
- if (self._zone_not_exist(zone_name_class)):
- logger.error(ZONEMGR_UNKNOWN_ZONE_SUCCESS, zone_name_class[0], zone_name_class[1])
- raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
- "belong to zonemgr" % zone_name_class)
- self.zonemgr_reload_zone(zone_name_class)
- self._set_zone_refresh_timer(zone_name_class)
- self._set_zone_state(zone_name_class, ZONE_OK)
- self._set_zone_last_refresh_time(zone_name_class, self._get_current_time())
- def zone_refresh_fail(self, zone_name_class):
- """Update zone info after zone refresh fail"""
- if (self._zone_not_exist(zone_name_class)):
- logger.error(ZONEMGR_UNKNOWN_ZONE_FAIL, zone_name_class[0], zone_name_class[1])
- raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
- "belong to zonemgr" % zone_name_class)
- # Is zone expired?
- if ((self._get_zone_soa_rdata(zone_name_class) is None) or
- self._zone_is_expired(zone_name_class)):
- self._set_zone_state(zone_name_class, ZONE_EXPIRED)
- else:
- self._set_zone_state(zone_name_class, ZONE_OK)
- self._set_zone_retry_timer(zone_name_class)
- def zone_handle_notify(self, zone_name_class, master):
- """Handle an incoming NOTIFY message via the Auth module.
- It returns True if the specified zone matches one of the locally
- configured list of secondary zones; otherwise returns False.
- In the latter case it assumes the server is a primary (master) of the
- zone; the Auth module should have rejected the case where it's not
- even authoritative for the zone.
- Note: to be more robust and less independent from other module's
- behavior, it's probably safer to check the authority condition here,
- too. But right now it uses SQLite3 specific API (to be deprecated),
- so we rather rely on Auth.
- Parameters:
- zone_name_class (Name, RRClass): the notified zone name and class.
- master (str): textual address of the NOTIFY sender.
- """
- if self._zone_not_exist(zone_name_class):
- logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_ZONE_NOTIFY_NOT_SECONDARY,
- zone_name_class[0], zone_name_class[1], master)
- return False
- self._set_zone_notifier_master(zone_name_class, master)
- self._set_zone_notify_timer(zone_name_class)
- return True
- def zonemgr_reload_zone(self, zone_name_class):
- """ Reload a zone."""
- zone_soa = sqlite3_ds.get_zone_soa(str(zone_name_class[0]), self._db_file)
- self._zonemgr_refresh_info[zone_name_class]["zone_soa_rdata"] = zone_soa[7]
- def zonemgr_add_zone(self, zone_name_class):
- """ Add a zone into zone manager."""
- logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_LOAD_ZONE, zone_name_class[0], zone_name_class[1])
- zone_info = {}
- zone_soa = sqlite3_ds.get_zone_soa(str(zone_name_class[0]), self._db_file)
- if zone_soa is None:
- logger.warn(ZONEMGR_NO_SOA, zone_name_class[0], zone_name_class[1])
- zone_info["zone_soa_rdata"] = None
- zone_reload_time = 0.0
- else:
- zone_info["zone_soa_rdata"] = zone_soa[7]
- zone_reload_time = float(zone_soa[7].split(" ")[RETRY_OFFSET])
- zone_info["zone_state"] = ZONE_OK
- zone_info["last_refresh_time"] = self._get_current_time()
- self._zonemgr_refresh_info[zone_name_class] = zone_info
- # Imposes some random jitters to avoid many zones need to do refresh at the same time.
- zone_reload_time = max(self._lowerbound_retry, zone_reload_time)
- self._set_zone_timer(zone_name_class, zone_reload_time, self._reload_jitter * zone_reload_time)
- def _zone_is_expired(self, zone_name_class):
- """Judge whether a zone is expired or not."""
- zone_expired_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[EXPIRED_OFFSET])
- zone_last_refresh_time = self._get_zone_last_refresh_time(zone_name_class)
- if (ZONE_EXPIRED == self._get_zone_state(zone_name_class) or
- zone_last_refresh_time + zone_expired_time <= self._get_current_time()):
- return True
- return False
- def _get_zone_soa_rdata(self, zone_name_class):
- return self._zonemgr_refresh_info[zone_name_class]["zone_soa_rdata"]
- def _get_zone_last_refresh_time(self, zone_name_class):
- return self._zonemgr_refresh_info[zone_name_class]["last_refresh_time"]
- def _set_zone_last_refresh_time(self, zone_name_class, time):
- self._zonemgr_refresh_info[zone_name_class]["last_refresh_time"] = time
- def _get_zone_notifier_master(self, zone_name_class):
- if ("notify_master" in self._zonemgr_refresh_info[zone_name_class].keys()):
- return self._zonemgr_refresh_info[zone_name_class]["notify_master"]
- return None
- def _set_zone_notifier_master(self, zone_name_class, master_addr):
- self._zonemgr_refresh_info[zone_name_class]["notify_master"] = master_addr
- def _clear_zone_notifier_master(self, zone_name_class):
- if ("notify_master" in self._zonemgr_refresh_info[zone_name_class].keys()):
- del self._zonemgr_refresh_info[zone_name_class]["notify_master"]
- def _get_zone_state(self, zone_name_class):
- return self._zonemgr_refresh_info[zone_name_class]["zone_state"]
- def _set_zone_state(self, zone_name_class, zone_state):
- self._zonemgr_refresh_info[zone_name_class]["zone_state"] = zone_state
- def _get_zone_refresh_timeout(self, zone_name_class):
- return self._zonemgr_refresh_info[zone_name_class]["refresh_timeout"]
- def _set_zone_refresh_timeout(self, zone_name_class, time):
- self._zonemgr_refresh_info[zone_name_class]["refresh_timeout"] = time
- def _get_zone_next_refresh_time(self, zone_name_class):
- return self._zonemgr_refresh_info[zone_name_class]["next_refresh_time"]
- def _set_zone_next_refresh_time(self, zone_name_class, time):
- self._zonemgr_refresh_info[zone_name_class]["next_refresh_time"] = time
- def _send_command(self, module_name, command_name, params):
- """Send command between modules."""
- try:
- self._mccs.rpc_call(command_name, module_name, params=params)
- except socket.error:
- # FIXME: WTF? Where does socket.error come from? And how do we ever
- # dare ignore such serious error? It can only be broken link to
- # msgq, we need to terminate then.
- logger.error(ZONEMGR_SEND_FAIL, module_name)
- except (isc.cc.session.SessionTimeout, isc.config.RPCError):
- pass # for now we just ignore the failure
- def _find_need_do_refresh_zone(self):
- """Find the first zone need do refresh, if no zone need
- do refresh, return the zone with minimum next_refresh_time.
- """
- zone_need_refresh = None
- for zone_name_class in self._zonemgr_refresh_info.keys():
- zone_state = self._get_zone_state(zone_name_class)
- # If hasn't received refresh response but are within refresh
- # timeout, skip the zone
- if (ZONE_REFRESHING == zone_state and
- (self._get_zone_refresh_timeout(zone_name_class) > self._get_current_time())):
- continue
- # Get the zone with minimum next_refresh_time
- if ((zone_need_refresh is None) or
- (self._get_zone_next_refresh_time(zone_name_class) <
- self._get_zone_next_refresh_time(zone_need_refresh))):
- zone_need_refresh = zone_name_class
- # Find the zone need do refresh
- if (self._get_zone_next_refresh_time(zone_need_refresh) < self._get_current_time()):
- break
- return zone_need_refresh
- def _do_refresh(self, zone_name_class):
- """Do zone refresh."""
- logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_REFRESH_ZONE, zone_name_class[0], zone_name_class[1])
- self._set_zone_state(zone_name_class, ZONE_REFRESHING)
- self._set_zone_refresh_timeout(zone_name_class, self._get_current_time() + self._max_transfer_timeout)
- notify_master = self._get_zone_notifier_master(zone_name_class)
- # If the zone has notify master, send notify command to xfrin module
- if notify_master:
- param = {"zone_name" : zone_name_class[0],
- "zone_class" : zone_name_class[1],
- "master" : notify_master
- }
- self._send_command(XFRIN_MODULE_NAME, ZONE_NOTIFY_COMMAND, param)
- self._clear_zone_notifier_master(zone_name_class)
- # Send refresh command to xfrin module
- else:
- param = {"zone_name" : zone_name_class[0],
- "zone_class" : zone_name_class[1]
- }
- self._send_command(XFRIN_MODULE_NAME, ZONE_REFRESH_COMMAND, param)
- def _zone_mgr_is_empty(self):
- """Does zone manager has no zone?"""
- if not len(self._zonemgr_refresh_info):
- return True
- return False
- def _run_timer(self, start_event):
- while self._running:
- # Notify run_timer that we already started and are inside the loop.
- # It is set only once, but when it was outside the loop, there was
- # a race condition and _running could be set to false before we
- # could enter it
- if start_event:
- start_event.set()
- start_event = None
- # If zonemgr has no zone, set timer timeout to self._lowerbound_retry.
- if self._zone_mgr_is_empty():
- timeout = self._lowerbound_retry
- else:
- zone_need_refresh = self._find_need_do_refresh_zone()
- # If don't get zone with minimum next refresh time, set timer timeout to self._lowerbound_retry.
- if not zone_need_refresh:
- timeout = self._lowerbound_retry
- else:
- timeout = self._get_zone_next_refresh_time(zone_need_refresh) - self._get_current_time()
- if (timeout < 0):
- self._do_refresh(zone_need_refresh)
- continue
- """ Wait for the socket notification for a maximum time of timeout
- in seconds (as float)."""
- try:
- rlist, wlist, xlist = select.select([self._check_sock, self._read_sock], [], [], timeout)
- except select.error as e:
- if e.args[0] == errno.EINTR:
- (rlist, wlist, xlist) = ([], [], [])
- else:
- logger.error(ZONEMGR_SELECT_ERROR, e);
- break
- for fd in rlist:
- if fd == self._read_sock: # awaken by shutdown socket
- # self._running will be False by now, if it is not a false
- # alarm (linux kernel is said to trigger spurious wakeup
- # on a filehandle that is not really readable).
- continue
- if fd == self._check_sock: # awaken by check socket
- self._check_sock.recv(32)
- def run_timer(self, daemon=False):
- """
- Keep track of zone timers. Spawns and starts a thread. The thread object
- is returned.
- You can stop it by calling shutdown().
- """
- # Small sanity check
- if self._running:
- logger.error(ZONEMGR_TIMER_THREAD_RUNNING)
- raise RuntimeError("Trying to run the timers twice at the same time")
- # Prepare the launch
- self._running = True
- (self._read_sock, self._write_sock) = socket.socketpair()
- start_event = threading.Event()
- # Start the thread
- self._thread = threading.Thread(target = self._run_timer,
- args = (start_event,))
- if daemon:
- self._thread.setDaemon(True)
- self._thread.start()
- start_event.wait()
- # Return the thread to anyone interested
- return self._thread
- def shutdown(self):
- """
- Stop the run_timer() thread. Block until it finished. This must be
- called from a different thread.
- """
- if not self._running:
- logger.error(ZONEMGR_NO_TIMER_THREAD)
- raise RuntimeError("Trying to shutdown, but not running")
- # Ask the thread to stop
- self._running = False
- self._write_sock.send(b'shutdown') # make self._read_sock readable
- # Wait for it to actually finnish
- self._thread.join()
- # Wipe out what we do not need
- self._thread = None
- self._read_sock.close()
- self._write_sock.close()
- self._read_sock = None
- self._write_sock = None
- def update_config_data(self, new_config, module_cc_session):
- """ update ZonemgrRefresh config """
- # Get a new value, but only if it is defined (commonly used below)
- # We don't use "value or default", because if value would be
- # 0, we would take default
- def val_or_default(value, default):
- if value is not None:
- return value
- else:
- return default
- self._lowerbound_refresh = val_or_default(
- new_config.get('lowerbound_refresh'), self._lowerbound_refresh)
- self._lowerbound_retry = val_or_default(
- new_config.get('lowerbound_retry'), self._lowerbound_retry)
- self._max_transfer_timeout = val_or_default(
- new_config.get('max_transfer_timeout'), self._max_transfer_timeout)
- self._refresh_jitter = val_or_default(
- new_config.get('refresh_jitter'), self._refresh_jitter)
- self._reload_jitter = val_or_default(
- new_config.get('reload_jitter'), self._reload_jitter)
- try:
- required = {}
- secondary_zones = new_config.get('secondary_zones')
- if secondary_zones is not None:
- # Add new zones
- for secondary_zone in new_config.get('secondary_zones'):
- if 'name' not in secondary_zone:
- raise ZonemgrException("Secondary zone specified "
- "without a name")
- name = secondary_zone['name']
- # Convert to Name and back (both to check and to normalize)
- try:
- name = isc.dns.Name(name, True).to_text()
- # Name() can raise a number of different exceptions, just
- # catch 'em all.
- except Exception as isce:
- raise ZonemgrException("Bad zone name '" + name +
- "': " + str(isce))
- # Currently we use an explicit get_default_value call
- # in case the class hasn't been set. Alternatively, we
- # could use
- # module_cc_session.get_value('secondary_zones[INDEX]/class')
- # To get either the value that was set, or the default if
- # it wasn't set.
- # But the real solution would be to make new_config a type
- # that contains default values itself
- # (then this entire method can be simplified a lot, and we
- # wouldn't need direct access to the ccsession object)
- if 'class' in secondary_zone:
- rr_class = secondary_zone['class']
- else:
- rr_class = module_cc_session.get_default_value(
- 'secondary_zones/class')
- # Convert rr_class to and from RRClass to check its value
- try:
- name_class = (name, isc.dns.RRClass(rr_class).to_text())
- except isc.dns.InvalidRRClass:
- raise ZonemgrException("Bad RR class '" +
- rr_class +
- "' for zone " + name)
- required[name_class] = True
- # Add it only if it isn't there already
- if not name_class in self._zonemgr_refresh_info:
- # If we are not able to find it in database, log an warning
- self.zonemgr_add_zone(name_class)
- # Drop the zones that are no longer there
- # Do it in two phases, python doesn't like deleting while iterating
- to_drop = []
- for old_zone in self._zonemgr_refresh_info:
- if not old_zone in required:
- to_drop.append(old_zone)
- for drop in to_drop:
- del self._zonemgr_refresh_info[drop]
- except:
- raise
- class Zonemgr:
- """Zone manager class."""
- def __init__(self):
- self._zone_refresh = None
- self._setup_session()
- self._db_file = self.get_db_file()
- # Create socket pair for communicating between main thread and zonemgr timer thread
- self._master_socket, self._slave_socket = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
- self._zone_refresh = ZonemgrRefresh(self._db_file, self._slave_socket, self._module_cc)
- self._zone_refresh.run_timer()
- self._lock = threading.Lock()
- self._shutdown_event = threading.Event()
- self.running = False
- def _setup_session(self):
- """Setup two sessions for zonemgr, one(self._module_cc) is used for receiving
- commands and config data sent from other modules, another one (self._cc)
- is used to send commands to proper modules."""
- self._module_cc = isc.config.ModuleCCSession(SPECFILE_LOCATION,
- self.config_handler,
- self.command_handler)
- self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION)
- self._config_data = self._module_cc.get_full_config()
- self._config_data_check(self._config_data)
- self._module_cc.start()
- def get_db_file(self):
- db_file, is_default = self._module_cc.get_remote_config_value(AUTH_MODULE_NAME, "database_file")
- # this too should be unnecessary, but currently the
- # 'from build' override isn't stored in the config
- # (and we don't have indirect python access to datasources yet)
- if is_default and "B10_FROM_BUILD" in os.environ:
- db_file = os.environ["B10_FROM_BUILD"] + "/bind10_zones.sqlite3"
- return db_file
- def shutdown(self):
- """Shutdown the zonemgr process. The thread which is keeping track of
- zone timers should be terminated.
- """
- self._zone_refresh.shutdown()
- self._slave_socket.close()
- self._master_socket.close()
- self._shutdown_event.set()
- self.running = False
- def config_handler(self, new_config):
- """ Update config data. """
- answer = create_answer(0)
- ok = True
- complete = self._config_data.copy()
- for key in new_config:
- if key not in complete:
- answer = create_answer(1, "Unknown config data: " + str(key))
- ok = False
- continue
- complete[key] = new_config[key]
- self._config_data_check(complete)
- if self._zone_refresh is not None:
- try:
- self._zone_refresh.update_config_data(complete, self._module_cc)
- except Exception as e:
- answer = create_answer(1, str(e))
- ok = False
- if ok:
- self._config_data = complete
- return answer
- def _config_data_check(self, config_data):
- """Check whether the new config data is valid or
- not. It contains only basic logic, not full check against
- database."""
- # jitter should not be bigger than half of the original value
- if config_data.get('refresh_jitter') > 0.5:
- config_data['refresh_jitter'] = 0.5
- logger.warn(ZONEMGR_JITTER_TOO_BIG)
- def _parse_cmd_params(self, args, command):
- zone_name = args.get("zone_name")
- if not zone_name:
- logger.error(ZONEMGR_NO_ZONE_NAME)
- raise ZonemgrException("zone name should be provided")
- zone_class = args.get("zone_class")
- if not zone_class:
- logger.error(ZONEMGR_NO_ZONE_CLASS)
- raise ZonemgrException("zone class should be provided")
- if (command != ZONE_NOTIFY_COMMAND):
- return (zone_name, zone_class)
- master_str = args.get("master")
- if not master_str:
- logger.error(ZONEMGR_NO_MASTER_ADDRESS)
- raise ZonemgrException("master address should be provided")
- return ((zone_name, zone_class), master_str)
- def command_handler(self, command, args):
- """Handle command receivd from command channel.
- ZONE_NOTIFY_COMMAND is issued by Auth process;
- ZONE_NEW_DATA_READY_CMD and ZONE_XFRIN_FAILED are issued by
- Xfrin process;
- shutdown is issued by a user or Init process. """
- answer = create_answer(0)
- if command == ZONE_NOTIFY_COMMAND:
- """ Handle Auth notify command"""
- # master is the source sender of the notify message.
- zone_name_class, master = self._parse_cmd_params(args, command)
- logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_NOTIFY,
- zone_name_class[0], zone_name_class[1])
- with self._lock:
- need_refresh = self._zone_refresh.zone_handle_notify(
- zone_name_class, master)
- if need_refresh:
- # Send notification to zonemgr timer thread by making
- # self._slave_socket readable.
- self._master_socket.send(b" ")
- elif command == notify_out.ZONE_NEW_DATA_READY_CMD:
- """ Handle xfrin success command"""
- zone_name_class = self._parse_cmd_params(args, command)
- logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_SUCCESS,
- zone_name_class[0], zone_name_class[1])
- with self._lock:
- self._zone_refresh.zone_refresh_success(zone_name_class)
- self._master_socket.send(b" ")# make self._slave_socket readable
- elif command == notify_out.ZONE_XFRIN_FAILED:
- """ Handle xfrin fail command"""
- zone_name_class = self._parse_cmd_params(args, command)
- logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_FAILED,
- zone_name_class[0], zone_name_class[1])
- with self._lock:
- self._zone_refresh.zone_refresh_fail(zone_name_class)
- self._master_socket.send(b" ")# make self._slave_socket readable
- elif command == "shutdown":
- logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_SHUTDOWN)
- self.shutdown()
- else:
- logger.warn(ZONEMGR_RECEIVE_UNKNOWN, str(command))
- answer = create_answer(1, "Unknown command:" + str(command))
- return answer
- def run(self):
- logger.debug(DBG_PROCESS, ZONEMGR_STARTED)
- self.running = True
- try:
- while not self._shutdown_event.is_set():
- fileno = self._module_cc.get_socket().fileno()
- # Wait with select() until there is something to read,
- # and then read it using a non-blocking read
- # This may or may not be relevant data for this loop,
- # but due to the way the zonemgr does threading, we
- # can't have a blocking read loop here.
- try:
- (reads, _, _) = select.select([fileno], [], [])
- except select.error as se:
- if se.args[0] != errno.EINTR:
- raise
- if fileno in reads:
- self._module_cc.check_command(True)
- finally:
- self._module_cc.send_stopping()
- zonemgrd = None
- def signal_handler(signal, frame):
- if zonemgrd:
- zonemgrd.shutdown()
- sys.exit(0)
- def set_signal_handler():
- signal.signal(signal.SIGTERM, signal_handler)
- signal.signal(signal.SIGINT, signal_handler)
- def set_cmd_options(parser):
- parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
- help="display more about what is going on")
- if '__main__' == __name__:
- try:
- logger.debug(DBG_START_SHUT, ZONEMGR_STARTING)
- parser = OptionParser()
- set_cmd_options(parser)
- (options, args) = parser.parse_args()
- if options.verbose:
- logger.set_severity("DEBUG", 99)
- set_signal_handler()
- zonemgrd = Zonemgr()
- zonemgrd.run()
- except KeyboardInterrupt:
- logger.info(ZONEMGR_KEYBOARD_INTERRUPT)
- except isc.cc.session.SessionError as e:
- logger.error(ZONEMGR_SESSION_ERROR)
- except isc.cc.session.SessionTimeout as e:
- logger.error(ZONEMGR_SESSION_TIMEOUT)
- except isc.config.ModuleCCSessionError as e:
- logger.error(ZONEMGR_CCSESSION_ERROR, str(e))
- if zonemgrd and zonemgrd.running:
- zonemgrd.shutdown()
- logger.info(ZONEMGR_SHUTDOWN)
|