|
@@ -1,6 +1,6 @@
|
|
|
#!@PYTHON@
|
|
|
|
|
|
-# Copyright (C) 2010 Internet Systems Consortium.
|
|
|
+# Copyright (C) 2010-2013 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
|
|
@@ -40,6 +40,9 @@ from isc.config.ccsession import *
|
|
|
import isc.util.process
|
|
|
from isc.log_messages.zonemgr_messages import *
|
|
|
from isc.notify import notify_out
|
|
|
+from isc.server_common.datasrc_clients_mgr import DataSrcClientsMgr, ConfigError
|
|
|
+from isc.datasrc import DataSourceClient, ZoneFinder
|
|
|
+from isc.dns import *
|
|
|
|
|
|
# Initialize logging for called modules.
|
|
|
isc.log.init("b10-zonemgr", buffer=True)
|
|
@@ -105,19 +108,25 @@ class ZonemgrRefresh:
|
|
|
can be stopped by calling shutdown() in another thread.
|
|
|
"""
|
|
|
|
|
|
- def __init__(self, db_file, slave_socket, module_cc_session):
|
|
|
- self._mccs = module_cc_session
|
|
|
+ def __init__(self, slave_socket, module_cc):
|
|
|
+ self._module_cc = module_cc
|
|
|
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.update_config_data(module_cc.get_full_config(),
|
|
|
+ module_cc)
|
|
|
self._running = False
|
|
|
+ # This is essentially private, but we allow tests to customize it.
|
|
|
+ self._datasrc_clients_mgr = DataSrcClientsMgr()
|
|
|
+ # data_sources configuration should be ready with cfgmgr, so this
|
|
|
+ # shouldn't fail; if it ever does we simply propagate the exception
|
|
|
+ # to terminate the program.
|
|
|
+ self._module_cc.add_remote_config_by_name('data_sources',
|
|
|
+ self._datasrc_config_handler)
|
|
|
|
|
|
def _random_jitter(self, max, jitter):
|
|
|
"""Imposes some random jitters for refresh and
|
|
@@ -229,26 +238,22 @@ class ZonemgrRefresh:
|
|
|
|
|
|
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]
|
|
|
+ self._get_zone_soa(zone_name_class[0], zone_name_class[1])
|
|
|
|
|
|
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)
|
|
|
+ zone_soa = self._get_zone_soa(zone_name_class[0], zone_name_class[1])
|
|
|
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_soa_rdata"] = zone_soa
|
|
|
+ zone_reload_time = float(zone_soa.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
|
|
@@ -258,6 +263,34 @@ class ZonemgrRefresh:
|
|
|
self._set_zone_timer(zone_name_class, zone_reload_time,
|
|
|
self._reload_jitter * zone_reload_time)
|
|
|
|
|
|
+ def _get_zone_soa(self, zone_name, zone_class):
|
|
|
+ """Retrieve the current SOA RR of the zone to be transferred."""
|
|
|
+ # Identify the data source to which the zone content is transferred,
|
|
|
+ # and get the current zone SOA from the data source (if available).
|
|
|
+ # Note that we do this before spawning the zonemgr session thread.
|
|
|
+ # find() on the client list and use of ZoneFinder (in _get_zone_soa())
|
|
|
+ # should be completed within the same single thread.
|
|
|
+ datasrc_client = None
|
|
|
+ clist = self._datasrc_clients_mgr.get_client_list(zone_class)
|
|
|
+ if clist is None:
|
|
|
+ return None
|
|
|
+ try:
|
|
|
+ datasrc_client = clist.find(zone_name, True, False)[0]
|
|
|
+ if datasrc_client is None: # can happen, so log it separately.
|
|
|
+ logger.error(ZONEMGR_DATASRC_UNKNOWN,
|
|
|
+ format_zone_str(zone_name, zone_class))
|
|
|
+ return None
|
|
|
+ zone_soa = _get_zone_soa(datasrc_client, Name(zone_name), RRClass(zone_class))
|
|
|
+ if (zone_soa == None):
|
|
|
+ return None
|
|
|
+ else:
|
|
|
+ return zone_soa.get_rdata()[0].to_text()
|
|
|
+ except isc.datasrc.Error as ex:
|
|
|
+ # rare case error. re-raise as ZonemgrException so it'll be logged
|
|
|
+ # in command_handler().
|
|
|
+ raise ZonemgrException('unexpected failure in datasrc module: ' +
|
|
|
+ str(ex))
|
|
|
+
|
|
|
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).\
|
|
@@ -317,7 +350,7 @@ class ZonemgrRefresh:
|
|
|
def _send_command(self, module_name, command_name, params):
|
|
|
"""Send command between modules."""
|
|
|
try:
|
|
|
- self._mccs.rpc_call(command_name, module_name, params=params)
|
|
|
+ self._module_cc.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
|
|
@@ -454,6 +487,20 @@ class ZonemgrRefresh:
|
|
|
# Return the thread to anyone interested
|
|
|
return self._thread
|
|
|
|
|
|
+ def _datasrc_config_handler(self, new_config, config_data):
|
|
|
+ """Configuration handler of the 'data_sources' module.
|
|
|
+
|
|
|
+ The actual handling is delegated to the DataSrcClientsMgr class;
|
|
|
+ this method is a simple wrapper.
|
|
|
+
|
|
|
+ This is essentially private, but implemented as 'protected' so tests
|
|
|
+ can refer to it; other external use is prohibited.
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ self._datasrc_clients_mgr.reconfigure(new_config, config_data)
|
|
|
+ except isc.server_common.datasrc_clients_mgr.ConfigError as ex:
|
|
|
+ logger.error(ZONEMGR_DATASRC_CONFIG_ERROR, ex)
|
|
|
+
|
|
|
def shutdown(self):
|
|
|
"""
|
|
|
Stop the run_timer() thread. Block until it finished. This must be
|
|
@@ -475,7 +522,7 @@ class ZonemgrRefresh:
|
|
|
self._read_sock = None
|
|
|
self._write_sock = None
|
|
|
|
|
|
- def update_config_data(self, new_config, module_cc_session):
|
|
|
+ def update_config_data(self, new_config, module_cc):
|
|
|
""" 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
|
|
@@ -524,7 +571,7 @@ class ZonemgrRefresh:
|
|
|
# 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')
|
|
|
+ # module_cc.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
|
|
@@ -534,7 +581,7 @@ class ZonemgrRefresh:
|
|
|
if 'class' in secondary_zone:
|
|
|
rr_class = secondary_zone['class']
|
|
|
else:
|
|
|
- rr_class = module_cc_session.get_default_value(
|
|
|
+ rr_class = module_cc.get_default_value(
|
|
|
'secondary_zones/class')
|
|
|
# Convert rr_class to and from RRClass to check its value
|
|
|
try:
|
|
@@ -566,13 +613,11 @@ class Zonemgr:
|
|
|
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 = ZonemgrRefresh(self._slave_socket, self._module_cc)
|
|
|
self._zone_refresh.run_timer()
|
|
|
|
|
|
self._lock = threading.Lock()
|
|
@@ -592,17 +637,6 @@ class Zonemgr:
|
|
|
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.
|
|
@@ -743,6 +777,59 @@ class Zonemgr:
|
|
|
finally:
|
|
|
self._module_cc.send_stopping()
|
|
|
|
|
|
+# XXX copy from xfrin for now
|
|
|
+def format_zone_str(zone_name, zone_class):
|
|
|
+ """Helper function to format a zone name and class as a string of
|
|
|
+ the form '<name>/<class>'.
|
|
|
+ Parameters:
|
|
|
+ zone_name (isc.dns.Name) name to format
|
|
|
+ zone_class (isc.dns.RRClass) class to format
|
|
|
+ """
|
|
|
+ return zone_name.to_text(True) + '/' + str(zone_class)
|
|
|
+
|
|
|
+# XXX copy from xfrin for now
|
|
|
+def _get_zone_soa(datasrc_client, zone_name, zone_class):
|
|
|
+ """Retrieve the current SOA RR of the zone to be transferred.
|
|
|
+
|
|
|
+ This function is essentially private to the module, but will also
|
|
|
+ be called (or tweaked) from tests; no one else should use this
|
|
|
+ function directly.
|
|
|
+
|
|
|
+ The specified zone is expected to exist in the data source referenced
|
|
|
+ by the given datasrc_client at the point of the call to this function.
|
|
|
+ If this is not met ZonemgrException exception will be raised.
|
|
|
+
|
|
|
+ It will be used for various purposes in subsequent xfr protocol
|
|
|
+ processing. It is validly possible that the zone is currently
|
|
|
+ empty and therefore doesn't have an SOA, so this method doesn't
|
|
|
+ consider it an error and returns None in such a case. It may or
|
|
|
+ may not result in failure in the actual processing depending on
|
|
|
+ how the SOA is used.
|
|
|
+
|
|
|
+ When the zone has an SOA RR, this method makes sure that it's
|
|
|
+ valid, i.e., it has exactly one RDATA; if it is not the case
|
|
|
+ this method returns None.
|
|
|
+
|
|
|
+ """
|
|
|
+ # get the zone finder. this must be SUCCESS (not even
|
|
|
+ # PARTIALMATCH) because we are specifying the zone origin name.
|
|
|
+ result, finder = datasrc_client.find_zone(zone_name)
|
|
|
+ if result != DataSourceClient.SUCCESS:
|
|
|
+ # The data source doesn't know the zone. In the context of this
|
|
|
+ # function is called, this shouldn't happen.
|
|
|
+ raise ZonemgrException("unexpected result: zone %s doesn't exist" %
|
|
|
+ format_zone_str(zone_name, zone_class))
|
|
|
+ result, soa_rrset, _ = finder.find(zone_name, RRType.SOA)
|
|
|
+ if result != ZoneFinder.SUCCESS:
|
|
|
+ logger.info(ZONEMGR_NO_SOA, format_zone_str(zone_name, zone_class))
|
|
|
+ return None
|
|
|
+ if soa_rrset.get_rdata_count() != 1:
|
|
|
+ logger.warn(ZONEMGR_MULTIPLE_SOA,
|
|
|
+ format_zone_str(zone_name, zone_class),
|
|
|
+ soa_rrset.get_rdata_count())
|
|
|
+ return None
|
|
|
+ return soa_rrset
|
|
|
+
|
|
|
zonemgrd = None
|
|
|
|
|
|
def signal_handler(signal, frame):
|