Parcourir la source

merge secondary manager and notifyout

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac289@2621 e5f2f494-b856-4b98-b285-d166d9295462
Jerry il y a 14 ans
Parent
commit
999d53dce9

+ 8 - 0
configure.ac

@@ -411,6 +411,8 @@ AC_CONFIG_FILES([Makefile
                  src/bin/xfrin/tests/Makefile
                  src/bin/xfrout/Makefile
                  src/bin/xfrout/tests/Makefile
+                 src/bin/zonemgr/Makefile
+                 src/bin/zonemgr/tests/Makefile
                  src/bin/usermgr/Makefile
                  src/lib/Makefile
                  src/lib/cc/Makefile
@@ -450,6 +452,10 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
            src/bin/xfrout/xfrout.spec.pre
            src/bin/xfrout/tests/xfrout_test
            src/bin/xfrout/run_b10-xfrout.sh
+           src/bin/zonemgr/zonemgr.py
+           src/bin/zonemgr/zonemgr.spec.pre
+           src/bin/zonemgr/tests/zonemgr_test
+           src/bin/zonemgr/run_b10-zonemgr.sh
            src/bin/bind10/bind10.py
            src/bin/bind10/tests/bind10_test
            src/bin/bind10/run_bind10.sh
@@ -480,10 +486,12 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
            chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
            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/run_bind10.sh
            chmod +x src/bin/cmdctl/tests/cmdctl_test
            chmod +x src/bin/xfrin/tests/xfrin_test
            chmod +x src/bin/xfrout/tests/xfrout_test
+           chmod +x src/bin/zonemgr/tests/zonemgr_test
            chmod +x src/bin/bindctl/tests/bindctl_test
            chmod +x src/bin/bindctl/run_bindctl.sh
            chmod +x src/bin/loadzone/run_loadzone.sh

+ 1 - 1
src/bin/Makefile.am

@@ -1 +1 @@
-SUBDIRS = bind10 bindctl cfgmgr loadzone msgq host cmdctl auth xfrin xfrout usermgr
+SUBDIRS = bind10 bindctl cfgmgr loadzone msgq host cmdctl auth xfrin xfrout usermgr zonemgr

+ 22 - 0
src/bin/bind10/bind10.py.in

@@ -396,6 +396,26 @@ class BoB:
             sys.stdout.write("[bind10] Started b10-xfrin (PID %d)\n" % 
                              xfrind.pid)
 
+        # start b10-zonemgr
+        zonemgr_args = ['b10-zonemgr']
+        if self.verbose:
+            sys.stdout.write("[bind10] Starting b10-zonemgr\n")
+            zonemgr_args += ['-v']
+        try:
+            zonemgr = ProcessInfo("b10-zonemgr", zonemgr_args,
+                                 c_channel_env)
+        except Exception as e:
+            c_channel.process.kill()
+            bind_cfgd.process.kill()
+            xfrout.process.kill()
+            auth.process.kill()
+            xfrind.process.kill()
+            return "Unable to start b10-zonemgr; " + str(e)
+        self.processes[zonemgr.pid] = zonemgr 
+        if self.verbose:
+            sys.stdout.write("[bind10] Started b10-zonemgr(PID %d)\n" % 
+                             zonemgr.pid)
+
         # start the b10-cmdctl
         # XXX: we hardcode port 8080
         cmdctl_args = ['b10-cmdctl']
@@ -411,6 +431,7 @@ class BoB:
             xfrout.process.kill()
             auth.process.kill()
             xfrind.process.kill()
+            zonemgr.process.kill()
             return "Unable to start b10-cmdctl; " + str(e)
         self.processes[cmd_ctrld.pid] = cmd_ctrld
         if self.verbose:
@@ -429,6 +450,7 @@ class BoB:
         self.cc_session.group_sendmsg(cmd, "Boss", "Auth")
         self.cc_session.group_sendmsg(cmd, "Boss", "Xfrout")
         self.cc_session.group_sendmsg(cmd, "Boss", "Xfrin")
+        self.cc_session.group_sendmsg(cmd, "Boss", "Zonemgr")
 
     def stop_process(self, process):
         """Stop the given process, friendly-like."""

+ 1 - 1
src/bin/bind10/run_bind10.sh.in

@@ -20,7 +20,7 @@ export PYTHON_EXEC
 
 BIND10_PATH=@abs_top_builddir@/src/bin/bind10
 
-PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:$PATH
+PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:$PATH
 export PATH
 
 PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs

+ 1 - 1
src/bin/xfrin/xfrin.py.in

@@ -432,7 +432,7 @@ a separate method for the convenience of unit tests.
                 # it to mount zone poisoning or DoS attacks.  We should
                 # locally identify the appropriate set of master servers.
                 # For now, we disable the code below.
-                master_is_valid = False
+                master_is_valid = False 
 
                 if master_is_valid:
                     ret = self.xfrin_start(zone_name, rrclass, db_file,

+ 18 - 0
src/bin/zonemgr/Makefile.am

@@ -0,0 +1,18 @@
+SUBDIRS = tests
+
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+
+pkglibexec_SCRIPTS = b10-zonemgr
+
+b10_zonemgrdir = $(DESTDIR)$(pkgdatadir)
+b10_zonemgr_DATA = zonemgr.spec
+
+CLEANFILES = b10-zonemgr zonemgr.pyc zonemgr.spec
+
+zonemgr.spec: zonemgr.spec.pre
+	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" zonemgr.spec.pre >$@
+
+b10-zonemgr: zonemgr.py
+	$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
+	       -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" zonemgr.py >$@
+	chmod a+x $@

+ 27 - 0
src/bin/zonemgr/run_b10-zonemgr.sh.in

@@ -0,0 +1,27 @@
+#! /bin/sh
+
+# 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.
+
+PYTHON_EXEC=${PYTHON_EXEC:-@PYTHON@}
+export PYTHON_EXEC
+
+MYPATH_PATH=@abs_top_builddir@/src/bin/zonemgr
+PYTHONPATH=@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/.libs
+export PYTHONPATH
+
+cd ${MYPATH_PATH}
+${PYTHON_EXEC} b10-zonemgr
+

+ 12 - 0
src/bin/zonemgr/tests/Makefile.am

@@ -0,0 +1,12 @@
+PYTESTS = zonemgr_test.py
+EXTRA_DIST = $(PYTESTS)
+
+# later will have configure option to choose this, like: coverage run --branch
+PYCOVERAGE = $(PYTHON)
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+	for pytest in $(PYTESTS) ; do \
+	echo Running test: $$pytest ; \
+	env PYTHONPATH=$(abs_top_builddir)/src/bin/zonemgr:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/xfr/.libs \
+	$(PYCOVERAGE) $(abs_srcdir)/$$pytest ; \
+	done

+ 27 - 0
src/bin/zonemgr/tests/zonemgr_test.in

@@ -0,0 +1,27 @@
+#! /bin/sh
+
+# 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.
+
+PYTHON_EXEC=${PYTHON_EXEC:-@PYTHON@}
+export PYTHON_EXEC
+
+TEST_PATH=@abs_top_srcdir@/src/bin/zonemgr/tests
+PYTHONPATH=@abs_top_srcdir@/src/bin/zonemgr:@abs_top_srcdir@/src/lib/python
+export PYTHONPATH
+
+cd ${TEST_PATH}
+exec ${PYTHON_EXEC} -O zonemgr_test.py $*
+

+ 426 - 0
src/bin/zonemgr/tests/zonemgr_test.py

@@ -0,0 +1,426 @@
+# 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.
+
+'''Tests for the ZoneRefreshInfo and ZoneMgr classes '''
+
+
+import unittest
+import os
+import tempfile
+from zonemgr import *
+
+class ZonemgrTestException(Exception):
+    pass
+
+class MySocket():
+    def __init__(self, family, type):
+        self.family = family
+        self.type = type
+
+    def recv(self, len):
+        data = struct.pack('s', " ")
+        return data
+
+    def send(self, data):
+        pass
+
+    def connect(self):
+        pass
+
+    def close(self):
+        pass
+
+class MySession():
+    def __init__(self):
+        pass
+
+    def group_sendmsg(self, msg, module_name):
+        if module_name not in ("Auth", "Xfrin"):
+            raise ZonemgrTestException("module name not exist")
+
+class MyZoneRefreshInfo(ZoneRefreshInfo):
+    def __init__(self):
+        self._cc = MySession()
+        self._sock_file = UNIX_SOCKET_FILE
+        self._db_file = "initdb.file"
+        self._zone_name_list = ['sd.cn.', 'tw.cn']
+        self._zones_refresh_info = [
+        {'last_refresh_time': 1280474398.822142,
+         'timeout': 1280481598.822153, 
+         'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600', 
+         'zone_state': 0},
+        {'last_refresh_time': 1280474399.116421, 
+         'timeout': 1280481599.116433, 
+         'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073112 7200 3600 2419200 21600', 
+         'zone_state': 0}
+        ]
+
+class TestZoneRefreshInfo(unittest.TestCase):
+    def setUp(self):
+        self.stdout_backup = sys.stdout
+        sys.stdout = open(os.devnull, 'w')
+        self.zoneinfo = MyZoneRefreshInfo()
+
+    def test_random_jitter(self):
+        max = 100025.120
+        jitter = 0
+        self.assertEqual(max, self.zoneinfo._random_jitter(max, jitter))
+        jitter = max / 4
+        self.assertTrue((3 * max / 4) <= self.zoneinfo._random_jitter(max, jitter)) 
+        self.assertTrue(self.zoneinfo._random_jitter(max, jitter) <= max) 
+
+    def test_get_current_time(self):
+        pass
+
+    def test_set_timer(self):
+        max = 3600
+        jitter = 900
+        time1 = time.time()
+        self.zoneinfo._set_timer(0, 3600, 900)
+        time2 = time.time()
+        zone_timeout = float(self.zoneinfo._zones_refresh_info[0]["timeout"])
+        self.assertTrue((3600 - 900) <= (zone_timeout - time1))
+        self.assertTrue((zone_timeout - time2) <= 3600)
+
+    def test_set_timer_refresh(self):
+        time1 = time.time()
+        self.zoneinfo._set_timer_refresh(0)
+        zone_timeout = self.zoneinfo._zones_refresh_info[0]["timeout"]
+        time2 = time.time()
+        self.assertTrue((time1 + 7200 * 3 / 4) <= zone_timeout)
+        self.assertTrue(zone_timeout <= time2 + 7200)
+        
+    def test_set_timer_retry(self):
+        time1 = time.time()
+        self.zoneinfo._set_timer_retry(0)
+        zone_timeout = self.zoneinfo._zones_refresh_info[0]["timeout"]
+        time2 = time.time()
+        self.assertTrue((time1 + 3600 * 3 / 4) <= zone_timeout)
+        self.assertTrue(zone_timeout <= time2 + 3600)
+
+    def test_set_timer_notify(self):
+        time1 = time.time()
+        self.zoneinfo._set_timer_notify(0)
+        zone_timeout = self.zoneinfo._zones_refresh_info[0]["timeout"]
+        time2 = time.time()
+        self.assertTrue(time1 <= zone_timeout)
+        self.assertTrue(zone_timeout <= time2)
+
+    def test_get_zone_index(self):
+        self.assertTrue(-1 == self.zoneinfo._get_zone_index("org.cn"))
+        self.assertTrue(0 == self.zoneinfo._get_zone_index("sd.cn."))
+        self.assertTrue(1 == self.zoneinfo._get_zone_index("tw.cn"))
+
+    def test_zone_is_expired(self):
+        current_time = time.time()
+        zone_expired_time = 2419200
+        self.zoneinfo._zones_refresh_info[0]["last_refresh_time"] = current_time - zone_expired_time - 1
+        self.assertTrue(self.zoneinfo._zone_is_expired(0))
+        self.zoneinfo._zones_refresh_info[0]["last_refresh_time"] = current_time - zone_expired_time + 1
+        self.assertFalse(self.zoneinfo._zone_is_expired(0))
+        self.zoneinfo._zones_refresh_info[0]["zone_state"] = ZONE_EXPIRED
+        self.assertTrue(self.zoneinfo._zone_is_expired(0))
+
+    def test_get_zone_soa_rdata(self):
+        soa_rdata1  = 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600' 
+        soa_rdata2  = 'a.dns.cn. root.cnnic.cn. 2009073112 7200 3600 2419200 21600' 
+        self.assertEqual(soa_rdata1, self.zoneinfo._get_zone_soa_rdata(0))
+        self.assertEqual(soa_rdata2, self.zoneinfo._get_zone_soa_rdata(1))
+         
+    def test_zonemgr_reload_zone(self):
+        soa_rdata = 'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600'
+        def get_zone_soa(zone_name, db_file):
+            return (1, 2, 'sd.cn.', 'cn.sd.', 21600, 'SOA', None, 
+                    'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600')
+        sqlite3_ds.get_zone_soa = get_zone_soa
+
+        self.zoneinfo._zonemgr_reload_zone(0)
+        self.assertEqual(soa_rdata, self.zoneinfo._zones_refresh_info[0]["zone_soa_rdata"])
+
+    def test_get_zone_notifier_master(self):
+        notify_master = ["192.168.1.1", 53]
+        self.assertEqual(None, self.zoneinfo._get_zone_notifier_master(0))
+        self.zoneinfo._zones_refresh_info[0]["notify_master"] = notify_master
+        self.assertEqual(notify_master, self.zoneinfo._get_zone_notifier_master(0))
+
+    def test_set_zone_notifier_master(self):
+        notify_master = ["192.168.1.1", 53]
+        self.zoneinfo._set_zone_notifier_master(0, notify_master)
+        self.assertEqual(self.zoneinfo._zones_refresh_info[0]["notify_master"], notify_master)
+
+    def test_clear_zone_notifier_master(self):
+        notify_master = ["192.168.1.1", 53]
+        self.zoneinfo._zones_refresh_info[0]["notify_master"] = notify_master
+        self.zoneinfo._clear_zone_notifier_master(0)
+        self.assertFalse("notify_master" in self.zoneinfo._zones_refresh_info[0].keys())
+        self.zoneinfo._clear_zone_notifier_master(1)
+        self.assertFalse("notify_master" in self.zoneinfo._zones_refresh_info[1].keys())
+
+    def test_get_zone_state(self):
+        self.assertEqual(ZONE_OK, self.zoneinfo._get_zone_state(0))
+        self.assertEqual(ZONE_OK, self.zoneinfo._get_zone_state(1))
+
+    def test_set_zone_state(self):
+        self.zoneinfo._set_zone_state(0, ZONE_REFRESHING)
+        self.zoneinfo._set_zone_state(1, ZONE_EXPIRED)
+        self.assertEqual(ZONE_REFRESHING, self.zoneinfo._zones_refresh_info[0]["zone_state"])
+        self.assertEqual(ZONE_EXPIRED, self.zoneinfo._zones_refresh_info[1]["zone_state"])
+
+    def test_get_zone_refresh_timeout(self):
+        current_time = time.time()
+        self.assertFalse("fresh_timeout" in self.zoneinfo._zones_refresh_info[0].keys())
+        self.zoneinfo._zones_refresh_info[0]["fresh_timeout"] = current_time
+        self.assertEqual(current_time, self.zoneinfo._get_zone_refresh_timeout(0))
+
+    def test_set_zone_refresh_timeout(self):
+        current_time = time.time()
+        self.zoneinfo._set_zone_refresh_timeout(0, current_time)
+        self.assertEqual(current_time, self.zoneinfo._zones_refresh_info[0]["fresh_timeout"])
+
+    def test_get_zone_timeout(self):
+        current_time = time.time()
+        self.zoneinfo._zones_refresh_info[0]["timeout"] = current_time
+        self.assertEqual(current_time, self.zoneinfo._get_zone_timeout(0))
+
+    def test_set_zone_timeout(self):
+        current_time = time.time()
+        self.zoneinfo._set_zone_timeout(0, current_time)
+        self.assertEqual(current_time, self.zoneinfo._zones_refresh_info[0]["timeout"])
+
+    def test_get_zone_last_refresh_time(self):
+        current_time = time.time()
+        self.zoneinfo._zones_refresh_info[0]["last_refresh_time"] = current_time
+        self.assertEqual(current_time, self.zoneinfo._get_zone_last_refresh_time(0))
+
+    def test_set_zone_last_refresh_time(self):
+        current_time = time.time()
+        self.zoneinfo._set_zone_last_refresh_time(0, current_time)
+        self.assertEqual(current_time, self.zoneinfo._zones_refresh_info[0]["last_refresh_time"])
+
+    def test_send_command(self):
+        self.assertRaises(ZonemgrTestException, self.zoneinfo._send_command, "Unknown", "Notify", None)
+
+    def test_zone_mgr_is_empty(self):
+        self.assertFalse(self.zoneinfo._zone_mgr_is_empty())
+        self.zoneinfo._zones_refresh_info = []
+        self.assertTrue(self.zoneinfo._zone_mgr_is_empty())
+
+    def test_build_zonemgr_refresh_info(self):
+        zone_name_list = [("sd.cn.", "IN")]
+        soa_rdata = 'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600'
+
+        def get_zones_info(db_file):
+            return zone_name_list
+
+        def get_zone_soa(zone_name, db_file):
+            return (1, 2, 'sd.cn.', 'cn.sd.', 21600, 'SOA', None, 
+                    'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600')
+
+        sqlite3_ds.get_zones_info = get_zones_info
+        sqlite3_ds.get_zone_soa = get_zone_soa
+
+        self.zoneinfo._zones_refresh_info = []
+        self.zoneinfo._build_zonemgr_refresh_info()
+        self.assertEqual(1, len(self.zoneinfo._zones_refresh_info))
+        self.assertEqual(soa_rdata, self.zoneinfo._zones_refresh_info[0]["zone_soa_rdata"])
+        self.assertEqual(ZONE_OK, self.zoneinfo._zones_refresh_info[0]["zone_state"])
+        self.assertTrue("last_refresh_time" in self.zoneinfo._zones_refresh_info[0].keys())
+        self.assertTrue("timeout" in self.zoneinfo._zones_refresh_info[0].keys())
+
+    def test_zone_handle_notify(self):
+        self.zoneinfo.zone_handle_notify("sd.cn.", "127.0.0.1", 53)
+        self.assertEqual(["127.0.0.1", 53], self.zoneinfo._zones_refresh_info[0]["notify_master"])
+        zone_timeout = float(self.zoneinfo._zones_refresh_info[0]["timeout"])
+        current_time = time.time()
+        self.assertTrue(zone_timeout <= current_time)
+        self.assertRaises(ZonemgrException, self.zoneinfo.zone_handle_notify,
+                          "org.cn.", "127.0.0.1", 53)
+
+    def test_zone_refresh_success(self):
+        soa_rdata = 'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600'
+        def get_zone_soa(zone_name, db_file):
+            return (1, 2, 'sd.cn.', 'cn.sd.', 21600, 'SOA', None, 
+                    'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600')
+        sqlite3_ds.get_zone_soa = get_zone_soa
+        time1 = time.time()
+        self.zoneinfo._zones_refresh_info[0]["zone_state"] = ZONE_REFRESHING
+        self.zoneinfo.zone_refresh_success("sd.cn.")
+        time2 = time.time()
+        self.assertEqual(soa_rdata, self.zoneinfo._zones_refresh_info[0]["zone_soa_rdata"])
+        self.assertTrue((time1 + 3 * 1800 / 4) <= self.zoneinfo._zones_refresh_info[0]["timeout"])
+        self.assertTrue(self.zoneinfo._zones_refresh_info[0]["timeout"] <= time2 + 1800)
+        self.assertEqual(ZONE_OK, self.zoneinfo._zones_refresh_info[0]["zone_state"])
+        self.assertTrue(time1 <= self.zoneinfo._zones_refresh_info[0]["last_refresh_time"])
+        self.assertTrue(self.zoneinfo._zones_refresh_info[0]["last_refresh_time"] <= time2)
+        self.assertRaises(ZonemgrException, self.zoneinfo.zone_refresh_success, "org.cn.")
+
+    def test_zone_refresh_fail(self):
+        soa_rdata = 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600' 
+        time1 = time.time()
+        self.zoneinfo._zones_refresh_info[0]["zone_state"] = ZONE_REFRESHING
+        self.zoneinfo.zone_refresh_fail("sd.cn.")
+        time2 = time.time()
+        self.assertEqual(soa_rdata, self.zoneinfo._zones_refresh_info[0]["zone_soa_rdata"])
+        self.assertTrue((time1 + 3 * 3600 / 4) <= self.zoneinfo._zones_refresh_info[0]["timeout"])
+        self.assertTrue(self.zoneinfo._zones_refresh_info[0]["timeout"] <= time2 + 3600)
+        self.assertEqual(ZONE_OK, self.zoneinfo._zones_refresh_info[0]["zone_state"])
+        self.assertRaises(ZonemgrException, self.zoneinfo.zone_refresh_success, "org.cn.")
+
+    def test_find_minimum_timeout_zone(self):
+        time1 = time.time()
+        self.zoneinfo._zones_refresh_info = [
+        {'last_refresh_time': time1,
+         'timeout': time1 + 7200, 
+         'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600', 
+         'zone_state': ZONE_OK},
+        {'last_refresh_time': time1 - 7200, 
+         'timeout': time1, 
+         'fresh_timeout': time1 + MAX_TRANSFER_TIMEOUT, 
+         'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073112 7200 3600 2419200 21600', 
+         'zone_state': ZONE_REFRESHING}
+        ]
+        zone_index = self.zoneinfo._find_minimum_timeout_zone()
+        self.assertEqual(0, zone_index)
+
+        self.zoneinfo._zones_refresh_info[0]["last_refresh_time"] = time1 - 2419200
+        self.zoneinfo._zones_refresh_info[0]["zone_state"] = ZONE_EXPIRED
+        zone_index = self.zoneinfo._find_minimum_timeout_zone()
+        self.assertEqual(-1, zone_index)
+
+        self.zoneinfo._zones_refresh_info[0]["zone_state"] = ZONE_REFRESHING
+        self.zoneinfo._zones_refresh_info[0]["notify_master"] = ["192.168.0.1", 6363]
+        zone_index = self.zoneinfo._find_minimum_timeout_zone()
+        self.assertEqual(0, zone_index)
+        self.assertEqual(ZONE_EXPIRED, self.zoneinfo._zones_refresh_info[0]["zone_state"])
+
+        self.zoneinfo._zones_refresh_info[1]["fresh_timeout"] = time1 
+        zone_index = self.zoneinfo._find_minimum_timeout_zone()
+        self.assertEqual(1, zone_index)
+
+    def test_do_refresh(self):
+        time1 = time.time()
+        self.zoneinfo._zones_refresh_info = [
+        {'last_refresh_time': time1 - 7200,
+         'timeout': time1 - 1, 
+         'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600', 
+         'zone_state': ZONE_OK}
+        ]
+        self.zoneinfo._do_refresh(0)
+        time2 = time.time()
+        self.assertEqual(ZONE_REFRESHING, self.zoneinfo._zones_refresh_info[0]["zone_state"])
+        self.assertTrue(time1 + MAX_TRANSFER_TIMEOUT <= 
+                        self.zoneinfo._zones_refresh_info[0]["fresh_timeout"])
+        self.assertTrue(time2 + MAX_TRANSFER_TIMEOUT >= 
+                        self.zoneinfo._zones_refresh_info[0]["fresh_timeout"])
+        self.zoneinfo._zones_refresh_info[0]["notify_master"] = ["127.0.0.1", 53]
+        self.zoneinfo._do_refresh(0)
+        time2 = time.time()
+        self.assertEqual(ZONE_REFRESHING, self.zoneinfo._zones_refresh_info[0]["zone_state"])
+        self.assertTrue(time1 + MAX_TRANSFER_TIMEOUT <= 
+                        self.zoneinfo._zones_refresh_info[0]["fresh_timeout"])
+        self.assertTrue(time2 + MAX_TRANSFER_TIMEOUT >= 
+                        self.zoneinfo._zones_refresh_info[0]["fresh_timeout"])
+        self.assertFalse("notify_master" in self.zoneinfo._zones_refresh_info[0].keys())
+
+    def test_connect_server(self):
+        self.assertRaises(ZonemgrException, self.zoneinfo._connect_server)
+
+    def test_shutdown(self):
+        pass
+
+    def tearDown(self):
+        sys.stdout = self.stdout_backup
+
+
+class MyCCSession():
+    def __init__(self):
+        pass
+                    
+    def get_remote_config_value(self, module_name, identifier):
+        if module_name == "Auth" and identifier == "database_file":
+            return "initdb.file", False
+        else:
+            return "unknown", False
+
+
+class MyZonemgr(Zonemgr):
+
+    def __init__(self):
+        self._db_file = "initdb.file"
+        self._conn, addr = (None, None)
+        self._shutdown_event = threading.Event()
+        self._cc = MySession()
+        self._module_cc = MyCCSession()
+        self._config_data = {"zone_name" : "org.cn", "master" : "127.0.0.1", "port" : 53}
+
+    def _start_zone_refresh_timer(self):
+        pass
+
+class TestZonemgr(unittest.TestCase):
+
+    def setUp(self):
+        self.zonemgr = MyZonemgr()
+
+    def test_config_handler(self):
+        config_data = {"zone_name" : "sd.cn.", "master" : "192.168.1.1", "port" : 53}
+        self.zonemgr.config_handler(config_data)
+        self.assertEqual(config_data, self.zonemgr._config_data)
+
+    def test_get_db_file(self):
+        self.assertEqual("initdb.file", self.zonemgr.get_db_file())
+    
+    def test_sock_file_in_use(self):
+        sock_file = tempfile.NamedTemporaryFile(mode='w',
+                                                prefix="b10",
+                                                delete=True)
+        sock_file_name = sock_file.name
+        if (os.path.exists(sock_file_name)):
+            os.unlink(sock_file_name)
+        self.assertFalse(self.zonemgr._sock_file_in_use(sock_file_name))
+        self.zonemgr._create_notify_socket(sock_file_name)
+        self.assertTrue(self.zonemgr._sock_file_in_use(sock_file_name))
+        sock_file.close()
+
+    def test_parse_cmd_params(self):
+        params1 = {"zone_name" : "org.cn", "master" : "127.0.0.1", "port" : "53"}
+        answer = ("org.cn", "127.0.0.1", "53")
+        self.assertEqual(answer, self.zonemgr._parse_cmd_params(params1))
+        params2 = {"zone_name" : "org.cn", "master" : "127.0.0.1"}
+        self.assertEqual(answer, self.zonemgr._parse_cmd_params(params2))
+
+    def test_remove_unused_sock_file(self):
+        sock_file = tempfile.NamedTemporaryFile(mode='w',
+                                                prefix="b10",
+                                                delete=True)
+        sock_file_name = sock_file.name
+        self.assertFalse(self.zonemgr._sock_file_in_use(sock_file_name))
+
+    def tearDown(self):
+        pass
+
+class TestAddr(unittest.TestCase):
+
+    def test_check_port(self):
+        self.assertRaises(ZonemgrException, check_port, "-1")
+        self.assertRaises(ZonemgrException, check_port, "65536")
+        self.assertRaises(ZonemgrException, check_port, "OK")
+
+    def test_check_addr(self):
+        self.assertRaises(ZonemgrException, check_addr, "192.168.256.222")
+        self.assertRaises(ZonemgrException, check_addr, "ff:00:00::ge")
+        self.assertRaises(ZonemgrException, check_addr, "OK")
+
+if __name__== "__main__":
+    unittest.main()

+ 529 - 0
src/bin/zonemgr/zonemgr.py.in

@@ -0,0 +1,529 @@
+#!@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 struct
+import random
+import threading
+import select
+import socket
+from isc.datasrc import sqlite3_ds
+from optparse import OptionParser, OptionValueError
+from isc.config.ccsession import *
+
+# 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"
+    UNIX_SOCKET_FILE= os.environ["B10_FROM_BUILD"] + "/zonemgr_conn"
+else:
+    PREFIX = "@prefix@"
+    DATAROOTDIR = "@datarootdir@"
+    SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+    AUTH_SPECFILE_PATH = SPECFILE_PATH
+    UNIX_SOCKET_FILE = "@@LOCALSTATEDIR@@/zonemgr_conn"
+
+SPECFILE_LOCATION = SPECFILE_PATH + "/zonemgr.spec"
+AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + "/auth.spec"
+
+__version__ = "BIND10"
+
+#default master port
+DEFAULT_MASTER_PORT = "53"
+
+# define zone state
+ZONE_OK = 0
+ZONE_REFRESHING = 1
+ZONE_EXPIRED = 2
+
+# smallest refresh timeout
+LOWERBOUND_REFRESH = 1
+# smallest retry timeout
+LOWERBOUND_RETRY = 1
+# max zone transfer timeout
+MAX_TRANSFER_TIMEOUT = 14400
+
+class ZonemgrException(Exception):
+    pass
+
+class ZoneRefreshInfo:
+    """This class will maintain and manage zone refresh info"""
+
+    def __init__(self, cc, db_file, sock_file):
+        self._cc = cc
+        self._sock_file = sock_file
+        self._db_file = db_file
+        self._zone_name_list = []
+        self._zones_refresh_info = []
+    
+    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."""
+        if 0 == jitter:
+            return max
+        return max - random.normalvariate(max, jitter) % jitter
+
+    def _get_current_time(self):
+        return time.time()
+
+    def _set_timer(self, zone_index, max, jitter):
+        self._zones_refresh_info[zone_index]["timeout"] = self._get_current_time() + \
+                                                          self._random_jitter(max, jitter)
+
+    def _set_timer_refresh(self, zone_index):
+        """Set timer for zone refresh timeout after zone refresh success."""
+        zone_refresh_time = float(self._get_zone_soa_rdata(zone_index).split(" ")[3])
+        if (zone_refresh_time < LOWERBOUND_REFRESH):
+            zone_refresh_time = LOWERBOUND_REFRESH
+        self._set_timer(zone_index, zone_refresh_time, (1 * zone_refresh_time) / 4)
+
+    def _set_timer_retry(self, zone_index):
+        """Set timer for zone retry timeout after zone refresh fail."""
+        zone_retry_time = float(self._get_zone_soa_rdata(zone_index).split(" ")[4])
+        if (zone_retry_time < LOWERBOUND_RETRY):
+            zone_retry_time = LOWERBOUND_RETRY
+        self._set_timer(zone_index, zone_retry_time, (1 * zone_retry_time) / 4)
+
+    def _set_timer_notify(self, zone_index):
+        """Set timer for a zone after receiving notify"""
+        self._set_timer(zone_index, 0, 0)
+
+    def zone_refresh_success(self, zone_name):
+        """Update zone update info after zone refresh success"""
+        zone_index = self._get_zone_index(zone_name)
+        if (-1 == zone_index):
+            raise ZonemgrException("[b10-zonemgr] Zone %s doesn't belong to zonemgr" % zone_name)
+            return
+        self._zonemgr_reload_zone(zone_index)
+        self._set_timer_refresh(zone_index)
+        self._set_zone_state(zone_index, ZONE_OK)
+        self._set_zone_last_refresh_time(zone_index, self._get_current_time())
+
+    def zone_refresh_fail(self, zone_name):
+        """Update zone update info after zone refresh fail"""
+        zone_index = self._get_zone_index(zone_name)
+        if (-1 == zone_index):
+            raise ZonemgrException("[b10-zonemgr] Zone %s doesn't belong to zonemgr" % zone_name)
+            return
+        self._set_zone_state(zone_index, ZONE_OK)
+        self._set_timer_retry(zone_index)
+
+    def zone_handle_notify(self, zone_name, master, port):
+        """Handle zone notify"""
+        zone_index = self._get_zone_index(zone_name)
+        if (-1 == zone_index):
+            raise ZonemgrException("[b10-zonemgr] Notified zone %s doesn't belong to zonemgr" % zone_name)
+            return
+        self._set_zone_notifier_master(zone_index, [master, port])
+        self._set_timer_notify(zone_index)
+
+    def _build_zonemgr_refresh_info(self):
+        for zone_name, zone_class in sqlite3_ds.get_zones_info(self._db_file):
+            zone_info = {}
+            self._zone_name_list.append(str(zone_name))
+            zone_soa = sqlite3_ds.get_zone_soa(str(zone_name), self._db_file)
+            zone_info["zone_soa_rdata"] = zone_soa[7]
+            zone_info["zone_state"] = ZONE_OK
+            zone_info["last_refresh_time"] = self._get_current_time() 
+            zone_info["timeout"] = self._get_current_time() + float(zone_soa[7].split(" ")[3])
+            self._zones_refresh_info.append(zone_info)
+
+    def _get_zone_index(self, zone_name):
+        """Get zone index in zone_refresh_list by zone name."""
+        if (str(zone_name) in self._zone_name_list):
+            zone_index = self._zone_name_list.index(str(zone_name))
+            return zone_index
+
+        return -1
+
+    def _zone_is_expired(self, zone_index):
+        zone_expired_time = float(self._get_zone_soa_rdata(zone_index).split(" ")[5])
+        zone_last_refresh_time = self._get_zone_last_refresh_time(zone_index)
+        if (ZONE_EXPIRED == self._get_zone_state(zone_index) or
+            zone_last_refresh_time + zone_expired_time <= self._get_current_time()):
+            return True
+
+        return False
+
+    def _get_zone_soa_rdata(self, zone_index):
+        return self._zones_refresh_info[zone_index]["zone_soa_rdata"]
+
+    def _zonemgr_reload_zone(self, zone_index):
+        zone_name = self._zone_name_list[zone_index]
+        zone_soa = sqlite3_ds.get_zone_soa(str(zone_name), self._db_file)
+        self._zones_refresh_info[zone_index]["zone_soa_rdata"] = zone_soa[7]
+
+    def _get_zone_last_refresh_time(self, zone_index):
+        return self._zones_refresh_info[zone_index]["last_refresh_time"]
+
+    def _set_zone_last_refresh_time(self, zone_index, time):
+        self._zones_refresh_info[zone_index]["last_refresh_time"] = time
+
+    def _get_zone_notifier_master(self, zone_index):
+        if ("notify_master" in self._zones_refresh_info[zone_index].keys()):
+            return self._zones_refresh_info[zone_index]["notify_master"] 
+
+        return None
+
+    def _set_zone_notifier_master(self, zone_index, master_addr):
+        self._zones_refresh_info[zone_index]["notify_master"] = master_addr
+
+    def _clear_zone_notifier_master(self, zone_index):
+        if ("notify_master" in self._zones_refresh_info[zone_index].keys()):
+            del self._zones_refresh_info[zone_index]["notify_master"]
+
+    def _get_zone_state(self, zone_index):
+        return self._zones_refresh_info[zone_index]["zone_state"]
+
+    def _set_zone_state(self, zone_index, zone_state):
+        self._zones_refresh_info[zone_index]["zone_state"] = zone_state 
+
+    def _get_zone_refresh_timeout(self, zone_index):
+        return self._zones_refresh_info[zone_index]["fresh_timeout"]
+
+    def _set_zone_refresh_timeout(self, zone_index, time):
+        self._zones_refresh_info[zone_index]["fresh_timeout"] = time
+
+    def _get_zone_timeout(self, zone_index):
+        return self._zones_refresh_info[zone_index]["timeout"]
+
+    def _set_zone_timeout(self, zone_index, timeout):
+        self._zones_refresh_info[zone_index]["timeout"] = timeout
+
+    def _send_command(self, module_name, command_name, params):
+        msg = create_command(command_name, params)
+        self._cc.group_sendmsg(msg, module_name)
+
+    def _find_minimum_timeout_zone(self):
+        minimum_index = -1
+        for i in range(0, len(self._zones_refresh_info)):
+            # Does the zone expired?
+            if (ZONE_EXPIRED != self._get_zone_state(i) and 
+                self._zone_is_expired(i)):
+                self._set_zone_state(i, ZONE_EXPIRED)
+
+            zone_state = self._get_zone_state(i)
+            # If zone is expired and doesn't receive notify, skip the zone
+            if (ZONE_EXPIRED == zone_state and 
+                (not self._get_zone_notifier_master(i))):
+                continue
+
+            # If hasn't received xfr response within xfr timeout, skip the zone
+            if (ZONE_REFRESHING == zone_state and
+                (self._get_zone_refresh_timeout(i) > self._get_current_time())):
+                continue
+                    
+            # Get the zone with minimum timeout
+            if ((-1 == minimum_index) or 
+                (self._get_zone_timeout(i) < self._get_zone_timeout(minimum_index))):
+                minimum_index = i
+
+        return minimum_index
+
+    
+    def _do_refresh(self, zone_index):
+        """Do zone refresh"""
+        zone_name = self._zone_name_list[zone_index]
+        self._set_zone_state(zone_index, ZONE_REFRESHING)
+        self._set_zone_refresh_timeout(zone_index, self._get_current_time() + MAX_TRANSFER_TIMEOUT) 
+        notify_master = self._get_zone_notifier_master(zone_index)
+        # If has notify master, send notify command to xfrin module
+        if notify_master:
+            param = {"zone_name" : zone_name,
+                     "master" : notify_master[0],
+                     "port" : notify_master[1]
+                     }
+            self._send_command("Xfrin", "notify", param) 
+            self._clear_zone_notifier_master(zone_index)
+        # Send refresh command to xfrin module
+        else:
+            param = {"zone_name" : zone_name}
+            self._send_command("Xfrin", "refresh", param)
+
+    def _connect_server(self):
+        """Connect to unix domain socket"""
+        try:
+            self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+            self._socket.connect(self._sock_file)
+        except socket.error as err:
+            raise ZonemgrException("Can't connect to socket")
+
+    def _zone_mgr_is_empty(self):
+        """Does zone manager has no zone?"""
+        if not len(self._zones_refresh_info):
+            return True
+
+        return False
+
+    def run_timer(self):
+        """Keep track of zone timers"""
+        self._build_zonemgr_refresh_info()
+        self._connect_server()
+        while True:
+            if self._zone_mgr_is_empty():
+                continue
+
+            minimum_index = self._find_minimum_timeout_zone()
+            # If don't get zone with minimum timeout, timer will wait LOWERBOUND_REFRESH
+            if (-1 == minimum_index):
+                timeout = LOWERBOUND_REFRESH
+            else:
+                timeout = self._get_zone_timeout(minimum_index) - self._get_current_time()
+                if (timeout < 0):
+                    self._do_refresh(minimum_index)
+                    continue
+
+            # Start timer, wait for timeout if not received notification
+            try:
+                (rlist, wlist, xlist) = select.select([self._socket], [], [], timeout)
+                if rlist:
+                    self._socket.recv(32)
+            except ValueError as e:
+                sys.stderr.write("[b10-zonemgr] Socket has been closed\n")
+                break
+            except select.error as e:
+                if e.args[0] == errno.EINTR:
+                    (rlist, wlist, xlist) = ([], [], [])
+                else:
+                    sys.stderr.write("[b10-zonemgr] Error with select(): %s\n" % err)
+                    break
+
+
+    def shutdown(self):
+        """Close socket"""
+        self._socket.close()
+
+
+def start_timer(zone_refresh_info):
+    """Keep track of zone timers"""
+    zone_refresh_info.run_timer()
+
+
+class Zonemgr:
+
+    def __init__(self, verbose = False):
+        self._setup_session()
+        self._db_file = self.get_db_file()
+        self._sock_file = UNIX_SOCKET_FILE 
+
+        self._create_notify_socket(self._sock_file)
+        self._start_zone_refresh_timer()
+
+        self._conn, addr = self._socket.accept()
+        self._shutdown_event = threading.Event()
+        self._verbose = verbose
+
+    def _create_notify_socket(self, sock_file):
+        """Create a unix domain socket to inform timer a new notify has arrived"""
+        self._remove_unused_sock_file(sock_file)
+        self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+        self._socket.bind(sock_file)
+        self._socket.listen(2)
+
+    def _start_zone_refresh_timer(self):
+        """Start a new thread to run zone refresh timer"""
+        self._zone_refresh_info = ZoneRefreshInfo(self._cc, self._db_file, self._sock_file)
+        listener = threading.Thread(target = start_timer, args = (self._zone_refresh_info,))
+        listener.start()
+
+    def _setup_session(self):
+        self._cc = isc.cc.Session()
+        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._module_cc.start()
+
+    def get_db_file(self):
+        db_file, is_default = self._module_cc.get_remote_config_value("Auth", "database_file")
+        if is_default and "B10_FROM_BUILD" in os.environ:
+            db_file = os.environ["B10_FROM_BUILD"] + "/bind10_zones.sqlite3"
+        return db_file
+
+    def _remove_unused_sock_file(self, sock_file):
+        if self._sock_file_in_use(sock_file):
+            sys.stderr.write("[b10-zonemgr] Fail to start zonemgr process, unix socket" 
+                  " file '%s' is being used by another zonemgr process" % sock_file)
+            sys.exit(0)
+        else:
+            if not os.path.exists(sock_file):
+                return
+            try:
+                os.unlink(sock_file)
+            except OSError as err:
+                sys.stderr.write("[b10-zonemgr] Fail to remove file " + self._sock_file, err)
+                sys.exit(0)
+   
+    def _sock_file_in_use(self, sock_file):
+        try:
+            sock = socket.socket(socket.AF_UNIX)
+            sock.connect(sock_file)
+        except socket.error as err:
+            return False
+        
+        return True 
+
+    def shutdown(self):
+
+        self._zone_refresh_info.shutdown()
+        try:
+            if (os.path.exists(self._sock_file)):
+                os.unlink(self._sock_file)
+            self._conn.close()
+        except Exception as e:
+            sys.stderr.write(str(e))
+
+        self._shutdown_event.set()
+        main_thread = threading.currentThread()
+        for th in threading.enumerate():
+            if th is main_thread:
+                continue
+            th.join()
+
+    def config_handler(self, new_config):
+        answer = create_answer(0)
+        for key in new_config:
+            if key not in self._config_data:
+                answer = create_answer(1, "Unknown config data: " + str(key))
+                continue
+            self._config_data[key] = new_config[key]
+        return answer
+
+    def _parse_cmd_params(self, args):
+        zone_name = args.get("zone_name")
+        if not zone_name:
+            sys.stderr.write("zone name should be provided")
+
+        master = args.get("master")
+        if not master:
+            sys.stderr.write("master address should be provided")
+        check_addr(master)
+
+        port_str = args.get("port")
+        if not port_str:
+            port_str = DEFAULT_MASTER_PORT
+        check_port(port_str)
+
+        return (zone_name, master, port_str)
+
+
+    def command_handler(self, command, args):
+        answer = create_answer(0)
+        if command == "notify":
+            zone_name, master, port = self._parse_cmd_params(args)
+            self._zone_refresh_info.zone_handle_notify(zone_name, master ,port)
+            data = struct.pack('s', " ")
+            self._conn.send(data)
+
+        elif command == "zone_new_data_ready":
+            zone_name = args.get("zone_name")
+            if not zone_name:
+                sys.stderr.write("zone name should be provided")
+            self._zone_refresh_info.zone_refresh_success(zone_name)
+
+        elif command == "zone_xfr_fail":
+            zone_name = args.get("zone_name")
+            if not zone_name:
+                sys.stderr.write("zone name should be provided")
+            self._zone_refresh_info.zone_refresh_fail(zone_name)
+
+        elif command == "shutdown":
+            self.shutdown()
+
+        else:
+            answer = create_answer(1, "Unknown command:" + str(command))
+
+        return answer
+
+    def run(self):
+        while not self._shutdown_event.is_set():
+            self._module_cc.check_command()
+
+zonemgrd = None
+
+def check_port(portstr):
+    try:
+        portnum = int(portstr)
+        if portnum < 0 or portnum > 65535:
+            raise ValueError("invalid port number (out of range): " + portstr)
+    except ValueError as err:
+        raise ZonemgrException("failed to resolve master addr=%s: %s" %
+                             ( portstr, str(err)))
+
+def check_addr(addrstr):
+    try:
+        addr = socket.inet_pton(socket.AF_INET, addrstr)
+        return
+    except:
+        pass
+
+    try:
+        addr = socket.inet_pton(socket.AF_INET6, addrstr)
+    except Exception as err:
+        raise ZonemgrException("failed to resolve master addr=%s: %s" %
+                             ( addrstr, str(err)))
+
+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:
+        parser = OptionParser()
+        set_cmd_options(parser)
+        (options, args) = parser.parse_args()
+        VERBOSE_MODE = options.verbose
+
+        set_signal_handler()
+        zonemgrd = Zonemgr()
+        zonemgrd.run()
+    except KeyboardInterrupt:
+        sys.stderr.write("[b10-zonemgr] exit zonemgr process")
+    except isc.cc.session.SessionError as e:
+        sys.stderr.write("[b10-zonemgr] Error creating ,zonemgr" 
+                           "is the command channel daemon running?")
+    except isc.config.ModuleCCSessionError as e:
+        sys.stderr.write("info", "[b10-zonemgr] exit zonemgr process:", e)
+
+    if zonemgrd:
+        zonemgrd.shutdown()
+

+ 21 - 0
src/bin/zonemgr/zonemgr.spec.pre.in

@@ -0,0 +1,21 @@
+{
+  "module_spec": {
+     "module_name": "Zonemgr",
+      "config_data":[
+      {
+        "item_name": "transfers_in",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 10
+      }
+      ],
+      "commands": [
+        {
+          "command_name": "shutdown",
+          "command_description": "Shut down Zonemgr",
+          "command_args": []
+        }
+      ]
+  }
+}
+     

+ 1 - 1
src/lib/python/isc/datasrc/sqlite3_ds.py

@@ -165,7 +165,7 @@ def get_zoneid(zone, cur):
         return row[0]
     else:
         return ''
-
+    
 #########################################################################
 # reverse_name:
 #   reverse the labels of a DNS name.  (for example,