zonemgr.py.in 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. #!@PYTHON@
  2. # Copyright (C) 2010 Internet Systems Consortium.
  3. #
  4. # Permission to use, copy, modify, and distribute this software for any
  5. # purpose with or without fee is hereby granted, provided that the above
  6. # copyright notice and this permission notice appear in all copies.
  7. #
  8. # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
  9. # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  10. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  11. # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  12. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  13. # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  14. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  15. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. """
  17. This file implements the Secondary Manager program.
  18. The secondary manager is one of the co-operating processes
  19. of BIND10, which keeps track of timers and other information
  20. necessary for BIND10 to act as a slave.
  21. """
  22. import sys; sys.path.append ('@@PYTHONPATH@@')
  23. import os
  24. import time
  25. import signal
  26. import isc
  27. import random
  28. import threading
  29. import select
  30. import socket
  31. import errno
  32. from isc.datasrc import sqlite3_ds
  33. from optparse import OptionParser, OptionValueError
  34. from isc.config.ccsession import *
  35. import isc.util.process
  36. from zonemgr_messages import *
  37. # Initialize logging for called modules.
  38. isc.log.init("b10-zonemgr")
  39. logger = isc.log.Logger("zonemgr")
  40. # Constants for debug levels, to be removed when we have #1074.
  41. DBG_START_SHUT = 0
  42. DBG_ZONEMGR_COMMAND = 10
  43. DBG_ZONEMGR_BASIC = 40
  44. isc.util.process.rename()
  45. # If B10_FROM_BUILD is set in the environment, we use data files
  46. # from a directory relative to that, otherwise we use the ones
  47. # installed on the system
  48. if "B10_FROM_BUILD" in os.environ:
  49. SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/zonemgr"
  50. AUTH_SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/auth"
  51. else:
  52. PREFIX = "@prefix@"
  53. DATAROOTDIR = "@datarootdir@"
  54. SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
  55. AUTH_SPECFILE_PATH = SPECFILE_PATH
  56. SPECFILE_LOCATION = SPECFILE_PATH + "/zonemgr.spec"
  57. AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + "/auth.spec"
  58. __version__ = "BIND10"
  59. # define module name
  60. XFRIN_MODULE_NAME = 'Xfrin'
  61. AUTH_MODULE_NAME = 'Auth'
  62. # define command name
  63. ZONE_XFRIN_FAILED_COMMAND = 'zone_xfrin_failed'
  64. ZONE_XFRIN_SUCCESS_COMMAND = 'zone_new_data_ready'
  65. ZONE_REFRESH_COMMAND = 'refresh_from_zonemgr'
  66. ZONE_NOTIFY_COMMAND = 'notify'
  67. # define zone state
  68. ZONE_OK = 0
  69. ZONE_REFRESHING = 1
  70. ZONE_EXPIRED = 2
  71. # offsets of fields in the SOA RDATA
  72. REFRESH_OFFSET = 3
  73. RETRY_OFFSET = 4
  74. EXPIRED_OFFSET = 5
  75. class ZonemgrException(Exception):
  76. pass
  77. class ZonemgrRefresh:
  78. """This class will maintain and manage zone refresh info.
  79. It also provides methods to keep track of zone timers and
  80. do zone refresh.
  81. Zone timers can be started by calling run_timer(), and it
  82. can be stopped by calling shutdown() in another thread.
  83. """
  84. def __init__(self, cc, db_file, slave_socket, config_data):
  85. self._cc = cc
  86. self._check_sock = slave_socket
  87. self._db_file = db_file
  88. self._zonemgr_refresh_info = {}
  89. self._lowerbound_refresh = None
  90. self._lowerbound_retry = None
  91. self._max_transfer_timeout = None
  92. self._refresh_jitter = None
  93. self._reload_jitter = None
  94. self.update_config_data(config_data)
  95. self._running = False
  96. def _random_jitter(self, max, jitter):
  97. """Imposes some random jitters for refresh and
  98. retry timers to avoid many zones need to do refresh
  99. at the same time.
  100. The value should be between (max - jitter) and max.
  101. """
  102. if 0 == jitter:
  103. return max
  104. return random.uniform(max - jitter, max)
  105. def _get_current_time(self):
  106. return time.time()
  107. def _set_zone_timer(self, zone_name_class, max, jitter):
  108. """Set zone next refresh time.
  109. jitter should not be bigger than half the original value."""
  110. self._set_zone_next_refresh_time(zone_name_class, self._get_current_time() + \
  111. self._random_jitter(max, jitter))
  112. def _set_zone_refresh_timer(self, zone_name_class):
  113. """Set zone next refresh time after zone refresh success.
  114. now + refresh - refresh_jitter <= next_refresh_time <= now + refresh
  115. """
  116. zone_refresh_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[REFRESH_OFFSET])
  117. zone_refresh_time = max(self._lowerbound_refresh, zone_refresh_time)
  118. self._set_zone_timer(zone_name_class, zone_refresh_time, self._refresh_jitter * zone_refresh_time)
  119. def _set_zone_retry_timer(self, zone_name_class):
  120. """Set zone next refresh time after zone refresh fail.
  121. now + retry - retry_jitter <= next_refresh_time <= now + retry
  122. """
  123. zone_retry_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[RETRY_OFFSET])
  124. zone_retry_time = max(self._lowerbound_retry, zone_retry_time)
  125. self._set_zone_timer(zone_name_class, zone_retry_time, self._refresh_jitter * zone_retry_time)
  126. def _set_zone_notify_timer(self, zone_name_class):
  127. """Set zone next refresh time after receiving notify
  128. next_refresh_time = now
  129. """
  130. self._set_zone_timer(zone_name_class, 0, 0)
  131. def _zone_not_exist(self, zone_name_class):
  132. """ Zone doesn't belong to zonemgr"""
  133. return not zone_name_class in self._zonemgr_refresh_info
  134. def zone_refresh_success(self, zone_name_class):
  135. """Update zone info after zone refresh success"""
  136. if (self._zone_not_exist(zone_name_class)):
  137. logger.error(ZONEMGR_UNKNOWN_ZONE_SUCCESS, zone_name_class[0], zone_name_class[1])
  138. raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
  139. "belong to zonemgr" % zone_name_class)
  140. self.zonemgr_reload_zone(zone_name_class)
  141. self._set_zone_refresh_timer(zone_name_class)
  142. self._set_zone_state(zone_name_class, ZONE_OK)
  143. self._set_zone_last_refresh_time(zone_name_class, self._get_current_time())
  144. def zone_refresh_fail(self, zone_name_class):
  145. """Update zone info after zone refresh fail"""
  146. if (self._zone_not_exist(zone_name_class)):
  147. logger.error(ZONEMGR_UNKNOWN_ZONE_FAIL, zone_name_class[0], zone_name_class[1])
  148. raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
  149. "belong to zonemgr" % zone_name_class)
  150. # Is zone expired?
  151. if (self._zone_is_expired(zone_name_class)):
  152. self._set_zone_state(zone_name_class, ZONE_EXPIRED)
  153. else:
  154. self._set_zone_state(zone_name_class, ZONE_OK)
  155. self._set_zone_retry_timer(zone_name_class)
  156. def zone_handle_notify(self, zone_name_class, master):
  157. """Handle zone notify"""
  158. if (self._zone_not_exist(zone_name_class)):
  159. logger.error(ZONEMGR_UNKNOWN_ZONE_NOTIFIED, zone_name_class[0], zone_name_class[1])
  160. raise ZonemgrException("[b10-zonemgr] Notified zone (%s, %s) "
  161. "doesn't belong to zonemgr" % zone_name_class)
  162. self._set_zone_notifier_master(zone_name_class, master)
  163. self._set_zone_notify_timer(zone_name_class)
  164. def zonemgr_reload_zone(self, zone_name_class):
  165. """ Reload a zone."""
  166. zone_soa = sqlite3_ds.get_zone_soa(str(zone_name_class[0]), self._db_file)
  167. self._zonemgr_refresh_info[zone_name_class]["zone_soa_rdata"] = zone_soa[7]
  168. def zonemgr_add_zone(self, zone_name_class):
  169. """ Add a zone into zone manager."""
  170. logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_LOAD_ZONE, zone_name_class[0], zone_name_class[1])
  171. zone_info = {}
  172. zone_soa = sqlite3_ds.get_zone_soa(str(zone_name_class[0]), self._db_file)
  173. if not zone_soa:
  174. logger.error(ZONEMGR_NO_SOA, zone_name_class[0], zone_name_class[1])
  175. raise ZonemgrException("[b10-zonemgr] zone (%s, %s) doesn't have soa." % zone_name_class)
  176. zone_info["zone_soa_rdata"] = zone_soa[7]
  177. zone_info["zone_state"] = ZONE_OK
  178. zone_info["last_refresh_time"] = self._get_current_time()
  179. self._zonemgr_refresh_info[zone_name_class] = zone_info
  180. # Imposes some random jitters to avoid many zones need to do refresh at the same time.
  181. zone_reload_jitter = float(zone_soa[7].split(" ")[RETRY_OFFSET])
  182. zone_reload_jitter = max(self._lowerbound_retry, zone_reload_jitter)
  183. self._set_zone_timer(zone_name_class, zone_reload_jitter, self._reload_jitter * zone_reload_jitter)
  184. def _zone_is_expired(self, zone_name_class):
  185. """Judge whether a zone is expired or not."""
  186. zone_expired_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[EXPIRED_OFFSET])
  187. zone_last_refresh_time = self._get_zone_last_refresh_time(zone_name_class)
  188. if (ZONE_EXPIRED == self._get_zone_state(zone_name_class) or
  189. zone_last_refresh_time + zone_expired_time <= self._get_current_time()):
  190. return True
  191. return False
  192. def _get_zone_soa_rdata(self, zone_name_class):
  193. return self._zonemgr_refresh_info[zone_name_class]["zone_soa_rdata"]
  194. def _get_zone_last_refresh_time(self, zone_name_class):
  195. return self._zonemgr_refresh_info[zone_name_class]["last_refresh_time"]
  196. def _set_zone_last_refresh_time(self, zone_name_class, time):
  197. self._zonemgr_refresh_info[zone_name_class]["last_refresh_time"] = time
  198. def _get_zone_notifier_master(self, zone_name_class):
  199. if ("notify_master" in self._zonemgr_refresh_info[zone_name_class].keys()):
  200. return self._zonemgr_refresh_info[zone_name_class]["notify_master"]
  201. return None
  202. def _set_zone_notifier_master(self, zone_name_class, master_addr):
  203. self._zonemgr_refresh_info[zone_name_class]["notify_master"] = master_addr
  204. def _clear_zone_notifier_master(self, zone_name_class):
  205. if ("notify_master" in self._zonemgr_refresh_info[zone_name_class].keys()):
  206. del self._zonemgr_refresh_info[zone_name_class]["notify_master"]
  207. def _get_zone_state(self, zone_name_class):
  208. return self._zonemgr_refresh_info[zone_name_class]["zone_state"]
  209. def _set_zone_state(self, zone_name_class, zone_state):
  210. self._zonemgr_refresh_info[zone_name_class]["zone_state"] = zone_state
  211. def _get_zone_refresh_timeout(self, zone_name_class):
  212. return self._zonemgr_refresh_info[zone_name_class]["refresh_timeout"]
  213. def _set_zone_refresh_timeout(self, zone_name_class, time):
  214. self._zonemgr_refresh_info[zone_name_class]["refresh_timeout"] = time
  215. def _get_zone_next_refresh_time(self, zone_name_class):
  216. return self._zonemgr_refresh_info[zone_name_class]["next_refresh_time"]
  217. def _set_zone_next_refresh_time(self, zone_name_class, time):
  218. self._zonemgr_refresh_info[zone_name_class]["next_refresh_time"] = time
  219. def _send_command(self, module_name, command_name, params):
  220. """Send command between modules."""
  221. msg = create_command(command_name, params)
  222. try:
  223. seq = self._cc.group_sendmsg(msg, module_name)
  224. try:
  225. answer, env = self._cc.group_recvmsg(False, seq)
  226. except isc.cc.session.SessionTimeout:
  227. pass # for now we just ignore the failure
  228. except socket.error:
  229. logger.error(ZONEMGR_SEND_FAIL, module_name)
  230. def _find_need_do_refresh_zone(self):
  231. """Find the first zone need do refresh, if no zone need
  232. do refresh, return the zone with minimum next_refresh_time.
  233. """
  234. zone_need_refresh = None
  235. for zone_name_class in self._zonemgr_refresh_info.keys():
  236. zone_state = self._get_zone_state(zone_name_class)
  237. # If hasn't received refresh response but are within refresh
  238. # timeout, skip the zone
  239. if (ZONE_REFRESHING == zone_state and
  240. (self._get_zone_refresh_timeout(zone_name_class) > self._get_current_time())):
  241. continue
  242. # Get the zone with minimum next_refresh_time
  243. if ((zone_need_refresh is None) or
  244. (self._get_zone_next_refresh_time(zone_name_class) <
  245. self._get_zone_next_refresh_time(zone_need_refresh))):
  246. zone_need_refresh = zone_name_class
  247. # Find the zone need do refresh
  248. if (self._get_zone_next_refresh_time(zone_need_refresh) < self._get_current_time()):
  249. break
  250. return zone_need_refresh
  251. def _do_refresh(self, zone_name_class):
  252. """Do zone refresh."""
  253. logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_REFRESH_ZONE, zone_name_class[0], zone_name_class[1])
  254. self._set_zone_state(zone_name_class, ZONE_REFRESHING)
  255. self._set_zone_refresh_timeout(zone_name_class, self._get_current_time() + self._max_transfer_timeout)
  256. notify_master = self._get_zone_notifier_master(zone_name_class)
  257. # If the zone has notify master, send notify command to xfrin module
  258. if notify_master:
  259. param = {"zone_name" : zone_name_class[0],
  260. "zone_class" : zone_name_class[1],
  261. "master" : notify_master
  262. }
  263. self._send_command(XFRIN_MODULE_NAME, ZONE_NOTIFY_COMMAND, param)
  264. self._clear_zone_notifier_master(zone_name_class)
  265. # Send refresh command to xfrin module
  266. else:
  267. param = {"zone_name" : zone_name_class[0],
  268. "zone_class" : zone_name_class[1]
  269. }
  270. self._send_command(XFRIN_MODULE_NAME, ZONE_REFRESH_COMMAND, param)
  271. def _zone_mgr_is_empty(self):
  272. """Does zone manager has no zone?"""
  273. if not len(self._zonemgr_refresh_info):
  274. return True
  275. return False
  276. def _run_timer(self, start_event):
  277. while self._running:
  278. # Notify run_timer that we already started and are inside the loop.
  279. # It is set only once, but when it was outside the loop, there was
  280. # a race condition and _running could be set to false before we
  281. # could enter it
  282. if start_event:
  283. start_event.set()
  284. start_event = None
  285. # If zonemgr has no zone, set timer timeout to self._lowerbound_retry.
  286. if self._zone_mgr_is_empty():
  287. timeout = self._lowerbound_retry
  288. else:
  289. zone_need_refresh = self._find_need_do_refresh_zone()
  290. # If don't get zone with minimum next refresh time, set timer timeout to self._lowerbound_retry.
  291. if not zone_need_refresh:
  292. timeout = self._lowerbound_retry
  293. else:
  294. timeout = self._get_zone_next_refresh_time(zone_need_refresh) - self._get_current_time()
  295. if (timeout < 0):
  296. self._do_refresh(zone_need_refresh)
  297. continue
  298. """ Wait for the socket notification for a maximum time of timeout
  299. in seconds (as float)."""
  300. try:
  301. rlist, wlist, xlist = select.select([self._check_sock, self._read_sock], [], [], timeout)
  302. except select.error as e:
  303. if e.args[0] == errno.EINTR:
  304. (rlist, wlist, xlist) = ([], [], [])
  305. else:
  306. logger.error(ZONEMGR_SELECT_ERROR, e);
  307. break
  308. for fd in rlist:
  309. if fd == self._read_sock: # awaken by shutdown socket
  310. # self._running will be False by now, if it is not a false
  311. # alarm (linux kernel is said to trigger spurious wakeup
  312. # on a filehandle that is not really readable).
  313. continue
  314. if fd == self._check_sock: # awaken by check socket
  315. self._check_sock.recv(32)
  316. def run_timer(self, daemon=False):
  317. """
  318. Keep track of zone timers. Spawns and starts a thread. The thread object
  319. is returned.
  320. You can stop it by calling shutdown().
  321. """
  322. # Small sanity check
  323. if self._running:
  324. logger.error(ZONEMGR_TIMER_THREAD_RUNNING)
  325. raise RuntimeError("Trying to run the timers twice at the same time")
  326. # Prepare the launch
  327. self._running = True
  328. (self._read_sock, self._write_sock) = socket.socketpair()
  329. start_event = threading.Event()
  330. # Start the thread
  331. self._thread = threading.Thread(target = self._run_timer,
  332. args = (start_event,))
  333. if daemon:
  334. self._thread.setDaemon(True)
  335. self._thread.start()
  336. start_event.wait()
  337. # Return the thread to anyone interested
  338. return self._thread
  339. def shutdown(self):
  340. """
  341. Stop the run_timer() thread. Block until it finished. This must be
  342. called from a different thread.
  343. """
  344. if not self._running:
  345. logger.error(ZONEMGR_NO_TIMER_THREAD)
  346. raise RuntimeError("Trying to shutdown, but not running")
  347. # Ask the thread to stop
  348. self._running = False
  349. self._write_sock.send(b'shutdown') # make self._read_sock readble
  350. # Wait for it to actually finnish
  351. self._thread.join()
  352. # Wipe out what we do not need
  353. self._thread = None
  354. self._read_sock = None
  355. self._write_sock = None
  356. def update_config_data(self, new_config):
  357. """ update ZonemgrRefresh config """
  358. # TODO: we probably want to store all this info in a nice
  359. # class, so that we don't have to backup and restore every
  360. # single value.
  361. # TODO2: We also don't use get_default_value yet
  362. backup = self._zonemgr_refresh_info.copy()
  363. # Get a new value, but only if it is defined (commonly used below)
  364. # We don't use "value or default", because if value would be
  365. # 0, we would take default
  366. def val_or_default(value, default):
  367. if value is not None:
  368. return value
  369. else:
  370. return default
  371. # store the values so we can restore them if there is a problem
  372. lowerbound_refresh_backup = self._lowerbound_refresh
  373. self._lowerbound_refresh = val_or_default(
  374. new_config.get('lowerbound_refresh'), self._lowerbound_refresh)
  375. lowerbound_retry_backup = self._lowerbound_retry
  376. self._lowerbound_retry = val_or_default(
  377. new_config.get('lowerbound_retry'), self._lowerbound_retry)
  378. max_transfer_timeout_backup = self._max_transfer_timeout
  379. self._max_transfer_timeout = val_or_default(
  380. new_config.get('max_transfer_timeout'), self._max_transfer_timeout)
  381. refresh_jitter_backup = self._refresh_jitter
  382. self._refresh_jitter = val_or_default(
  383. new_config.get('refresh_jitter'), self._refresh_jitter)
  384. reload_jitter_backup = self._reload_jitter
  385. self._reload_jitter = val_or_default(
  386. new_config.get('reload_jitter'), self._reload_jitter)
  387. try:
  388. required = {}
  389. secondary_zones = new_config.get('secondary_zones')
  390. if secondary_zones is not None:
  391. # Add new zones
  392. for secondary_zone in new_config.get('secondary_zones'):
  393. name = secondary_zone['name']
  394. # Be tolerant to sclerotic users who forget the final dot
  395. if name[-1] != '.':
  396. name = name + '.'
  397. name_class = (name, secondary_zone['class'])
  398. required[name_class] = True
  399. # Add it only if it isn't there already
  400. if not name_class in self._zonemgr_refresh_info:
  401. self.zonemgr_add_zone(name_class)
  402. # Drop the zones that are no longer there
  403. # Do it in two phases, python doesn't like deleting while iterating
  404. to_drop = []
  405. for old_zone in self._zonemgr_refresh_info:
  406. if not old_zone in required:
  407. to_drop.append(old_zone)
  408. for drop in to_drop:
  409. del self._zonemgr_refresh_info[drop]
  410. # If we are not able to find it in database, restore the original
  411. except:
  412. self._zonemgr_refresh_info = backup
  413. self._lowerbound_refresh = lowerbound_refresh_backup
  414. self._lowerbound_retry = lowerbound_retry_backup
  415. self._max_transfer_timeout = max_transfer_timeout_backup
  416. self._refresh_jitter = refresh_jitter_backup
  417. self._reload_jitter = reload_jitter_backup
  418. raise
  419. class Zonemgr:
  420. """Zone manager class."""
  421. def __init__(self):
  422. self._zone_refresh = None
  423. self._setup_session()
  424. self._db_file = self.get_db_file()
  425. # Create socket pair for communicating between main thread and zonemgr timer thread
  426. self._master_socket, self._slave_socket = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
  427. self._zone_refresh = ZonemgrRefresh(self._cc, self._db_file, self._slave_socket, self._config_data)
  428. self._zone_refresh.run_timer()
  429. self._lock = threading.Lock()
  430. self._shutdown_event = threading.Event()
  431. self.running = False
  432. def _setup_session(self):
  433. """Setup two sessions for zonemgr, one(self._module_cc) is used for receiving
  434. commands and config data sent from other modules, another one (self._cc)
  435. is used to send commands to proper modules."""
  436. self._cc = isc.cc.Session()
  437. self._module_cc = isc.config.ModuleCCSession(SPECFILE_LOCATION,
  438. self.config_handler,
  439. self.command_handler)
  440. self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION)
  441. self._config_data = self._module_cc.get_full_config()
  442. self._config_data_check(self._config_data)
  443. self._module_cc.start()
  444. def get_db_file(self):
  445. db_file, is_default = self._module_cc.get_remote_config_value(AUTH_MODULE_NAME, "database_file")
  446. # this too should be unnecessary, but currently the
  447. # 'from build' override isn't stored in the config
  448. # (and we don't have indirect python access to datasources yet)
  449. if is_default and "B10_FROM_BUILD" in os.environ:
  450. db_file = os.environ["B10_FROM_BUILD"] + "/bind10_zones.sqlite3"
  451. return db_file
  452. def shutdown(self):
  453. """Shutdown the zonemgr process. The thread which is keeping track of
  454. zone timers should be terminated.
  455. """
  456. self._zone_refresh.shutdown()
  457. self._slave_socket.close()
  458. self._master_socket.close()
  459. self._shutdown_event.set()
  460. self.running = False
  461. def config_handler(self, new_config):
  462. """ Update config data. """
  463. answer = create_answer(0)
  464. ok = True
  465. complete = self._config_data.copy()
  466. for key in new_config:
  467. if key not in complete:
  468. answer = create_answer(1, "Unknown config data: " + str(key))
  469. ok = False
  470. continue
  471. complete[key] = new_config[key]
  472. self._config_data_check(complete)
  473. if self._zone_refresh is not None:
  474. try:
  475. self._zone_refresh.update_config_data(complete)
  476. except Exception as e:
  477. answer = create_answer(1, str(e))
  478. ok = False
  479. if ok:
  480. self._config_data = complete
  481. return answer
  482. def _config_data_check(self, config_data):
  483. """Check whether the new config data is valid or
  484. not. It contains only basic logic, not full check against
  485. database."""
  486. # jitter should not be bigger than half of the original value
  487. if config_data.get('refresh_jitter') > 0.5:
  488. config_data['refresh_jitter'] = 0.5
  489. logger.warn(ZONEMGR_JITTER_TOO_BIG)
  490. def _parse_cmd_params(self, args, command):
  491. zone_name = args.get("zone_name")
  492. if not zone_name:
  493. logger.error(ZONEMGR_NO_ZONE_NAME)
  494. raise ZonemgrException("zone name should be provided")
  495. zone_class = args.get("zone_class")
  496. if not zone_class:
  497. logger.error(ZONEMGR_NO_ZONE_CLASS)
  498. raise ZonemgrException("zone class should be provided")
  499. if (command != ZONE_NOTIFY_COMMAND):
  500. return (zone_name, zone_class)
  501. master_str = args.get("master")
  502. if not master_str:
  503. logger.error(ZONEMGR_NO_MASTER_ADDRESS)
  504. raise ZonemgrException("master address should be provided")
  505. return ((zone_name, zone_class), master_str)
  506. def command_handler(self, command, args):
  507. """Handle command receivd from command channel.
  508. ZONE_NOTIFY_COMMAND is issued by Auth process;
  509. ZONE_XFRIN_SUCCESS_COMMAND and ZONE_XFRIN_FAILED_COMMAND are issued by
  510. Xfrin process;
  511. shutdown is issued by a user or Boss process. """
  512. answer = create_answer(0)
  513. if command == ZONE_NOTIFY_COMMAND:
  514. """ Handle Auth notify command"""
  515. # master is the source sender of the notify message.
  516. zone_name_class, master = self._parse_cmd_params(args, command)
  517. logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_NOTIFY, zone_name_class[0], zone_name_class[1])
  518. with self._lock:
  519. self._zone_refresh.zone_handle_notify(zone_name_class, master)
  520. # Send notification to zonemgr timer thread
  521. self._master_socket.send(b" ")# make self._slave_socket readble
  522. elif command == ZONE_XFRIN_SUCCESS_COMMAND:
  523. """ Handle xfrin success command"""
  524. zone_name_class = self._parse_cmd_params(args, command)
  525. logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_SUCCESS, zone_name_class[0], zone_name_class[1])
  526. with self._lock:
  527. self._zone_refresh.zone_refresh_success(zone_name_class)
  528. self._master_socket.send(b" ")# make self._slave_socket readble
  529. elif command == ZONE_XFRIN_FAILED_COMMAND:
  530. """ Handle xfrin fail command"""
  531. zone_name_class = self._parse_cmd_params(args, command)
  532. logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_FAILED, zone_name_class[0], zone_name_class[1])
  533. with self._lock:
  534. self._zone_refresh.zone_refresh_fail(zone_name_class)
  535. self._master_socket.send(b" ")# make self._slave_socket readble
  536. elif command == "shutdown":
  537. logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_SHUTDOWN)
  538. self.shutdown()
  539. else:
  540. logger.warn(ZONEMGR_RECEIVE_UNKNOWN, str(command))
  541. answer = create_answer(1, "Unknown command:" + str(command))
  542. return answer
  543. def run(self):
  544. self.running = True
  545. while not self._shutdown_event.is_set():
  546. self._module_cc.check_command(False)
  547. zonemgrd = None
  548. def signal_handler(signal, frame):
  549. if zonemgrd:
  550. zonemgrd.shutdown()
  551. sys.exit(0)
  552. def set_signal_handler():
  553. signal.signal(signal.SIGTERM, signal_handler)
  554. signal.signal(signal.SIGINT, signal_handler)
  555. def set_cmd_options(parser):
  556. parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
  557. help="display more about what is going on")
  558. if '__main__' == __name__:
  559. try:
  560. logger.debug(DBG_START_SHUT, ZONEMGR_STARTING)
  561. parser = OptionParser()
  562. set_cmd_options(parser)
  563. (options, args) = parser.parse_args()
  564. if options.verbose:
  565. logger.set_severity("DEBUG", 99)
  566. set_signal_handler()
  567. zonemgrd = Zonemgr()
  568. zonemgrd.run()
  569. except KeyboardInterrupt:
  570. logger.info(ZONEMGR_KEYBOARD_INTERRUPT)
  571. except isc.cc.session.SessionError as e:
  572. logger.error(ZONEMGR_SESSION_ERROR)
  573. except isc.cc.session.SessionTimeout as e:
  574. logger.error(ZONEMGR_SESSION_TIMEOUT)
  575. except isc.config.ModuleCCSessionError as e:
  576. logger.error(ZONEMGR_CCSESSION_ERROR, str(e))
  577. if zonemgrd and zonemgrd.running:
  578. zonemgrd.shutdown()
  579. logger.debug(DBG_START_SHUT, ZONEMGR_SHUTDOWN)