zonemgr.py.in 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  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 isc.dns
  28. import random
  29. import threading
  30. import select
  31. import socket
  32. import errno
  33. from isc.datasrc import sqlite3_ds
  34. from optparse import OptionParser, OptionValueError
  35. from isc.config.ccsession import *
  36. import isc.util.process
  37. from isc.log_messages.zonemgr_messages import *
  38. from isc.notify import notify_out
  39. # Initialize logging for called modules.
  40. isc.log.init("b10-zonemgr", buffer=True)
  41. logger = isc.log.Logger("zonemgr")
  42. # Pending system-wide debug level definitions, the ones we
  43. # use here are hardcoded for now
  44. DBG_PROCESS = logger.DBGLVL_TRACE_BASIC
  45. DBG_COMMANDS = logger.DBGLVL_TRACE_DETAIL
  46. # Constants for debug levels.
  47. DBG_START_SHUT = logger.DBGLVL_START_SHUT
  48. DBG_ZONEMGR_COMMAND = logger.DBGLVL_COMMAND
  49. DBG_ZONEMGR_BASIC = logger.DBGLVL_TRACE_BASIC
  50. isc.util.process.rename()
  51. # If B10_FROM_BUILD is set in the environment, we use data files
  52. # from a directory relative to that, otherwise we use the ones
  53. # installed on the system
  54. if "B10_FROM_BUILD" in os.environ:
  55. SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/zonemgr"
  56. AUTH_SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/auth"
  57. else:
  58. PREFIX = "@prefix@"
  59. DATAROOTDIR = "@datarootdir@"
  60. SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
  61. AUTH_SPECFILE_PATH = SPECFILE_PATH
  62. SPECFILE_LOCATION = SPECFILE_PATH + "/zonemgr.spec"
  63. AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + "/auth.spec"
  64. __version__ = "BIND10"
  65. # define module name
  66. XFRIN_MODULE_NAME = 'Xfrin'
  67. AUTH_MODULE_NAME = 'Auth'
  68. # define command name
  69. ZONE_REFRESH_COMMAND = 'refresh_from_zonemgr'
  70. ZONE_NOTIFY_COMMAND = 'notify'
  71. # define zone state
  72. ZONE_OK = 0
  73. ZONE_REFRESHING = 1
  74. ZONE_EXPIRED = 2
  75. # offsets of fields in the SOA RDATA
  76. REFRESH_OFFSET = 3
  77. RETRY_OFFSET = 4
  78. EXPIRED_OFFSET = 5
  79. class ZonemgrException(Exception):
  80. pass
  81. class ZonemgrRefresh:
  82. """This class will maintain and manage zone refresh info.
  83. It also provides methods to keep track of zone timers and
  84. do zone refresh.
  85. Zone timers can be started by calling run_timer(), and it
  86. can be stopped by calling shutdown() in another thread.
  87. """
  88. def __init__(self, db_file, slave_socket, module_cc_session):
  89. self._mccs = module_cc_session
  90. self._check_sock = slave_socket
  91. self._db_file = db_file
  92. self._zonemgr_refresh_info = {}
  93. self._lowerbound_refresh = None
  94. self._lowerbound_retry = None
  95. self._max_transfer_timeout = None
  96. self._refresh_jitter = None
  97. self._reload_jitter = None
  98. self.update_config_data(module_cc_session.get_full_config(),
  99. module_cc_session)
  100. self._running = False
  101. def _random_jitter(self, max, jitter):
  102. """Imposes some random jitters for refresh and
  103. retry timers to avoid many zones need to do refresh
  104. at the same time.
  105. The value should be between (max - jitter) and max.
  106. """
  107. if 0 == jitter:
  108. return max
  109. return random.uniform(max - jitter, max)
  110. def _get_current_time(self):
  111. return time.time()
  112. def _set_zone_timer(self, zone_name_class, max, jitter):
  113. """Set zone next refresh time.
  114. jitter should not be bigger than half the original value."""
  115. self._set_zone_next_refresh_time(zone_name_class, self._get_current_time() + \
  116. self._random_jitter(max, jitter))
  117. def _set_zone_refresh_timer(self, zone_name_class):
  118. """Set zone next refresh time after zone refresh success.
  119. now + refresh - refresh_jitter <= next_refresh_time <= now + refresh
  120. """
  121. zone_refresh_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[REFRESH_OFFSET])
  122. zone_refresh_time = max(self._lowerbound_refresh, zone_refresh_time)
  123. self._set_zone_timer(zone_name_class, zone_refresh_time, self._refresh_jitter * zone_refresh_time)
  124. def _set_zone_retry_timer(self, zone_name_class):
  125. """Set zone next refresh time after zone refresh fail.
  126. now + retry - retry_jitter <= next_refresh_time <= now + retry
  127. """
  128. if (self._get_zone_soa_rdata(zone_name_class) is not None):
  129. zone_retry_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[RETRY_OFFSET])
  130. else:
  131. zone_retry_time = 0.0
  132. zone_retry_time = max(self._lowerbound_retry, zone_retry_time)
  133. self._set_zone_timer(zone_name_class, zone_retry_time, self._refresh_jitter * zone_retry_time)
  134. def _set_zone_notify_timer(self, zone_name_class):
  135. """Set zone next refresh time after receiving notify
  136. next_refresh_time = now
  137. """
  138. self._set_zone_timer(zone_name_class, 0, 0)
  139. def _zone_not_exist(self, zone_name_class):
  140. """ Zone doesn't belong to zonemgr"""
  141. return not zone_name_class in self._zonemgr_refresh_info
  142. def zone_refresh_success(self, zone_name_class):
  143. """Update zone info after zone refresh success"""
  144. if (self._zone_not_exist(zone_name_class)):
  145. logger.error(ZONEMGR_UNKNOWN_ZONE_SUCCESS, zone_name_class[0], zone_name_class[1])
  146. raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
  147. "belong to zonemgr" % zone_name_class)
  148. self.zonemgr_reload_zone(zone_name_class)
  149. self._set_zone_refresh_timer(zone_name_class)
  150. self._set_zone_state(zone_name_class, ZONE_OK)
  151. self._set_zone_last_refresh_time(zone_name_class, self._get_current_time())
  152. def zone_refresh_fail(self, zone_name_class):
  153. """Update zone info after zone refresh fail"""
  154. if (self._zone_not_exist(zone_name_class)):
  155. logger.error(ZONEMGR_UNKNOWN_ZONE_FAIL, zone_name_class[0], zone_name_class[1])
  156. raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
  157. "belong to zonemgr" % zone_name_class)
  158. # Is zone expired?
  159. if ((self._get_zone_soa_rdata(zone_name_class) is None) or
  160. self._zone_is_expired(zone_name_class)):
  161. self._set_zone_state(zone_name_class, ZONE_EXPIRED)
  162. else:
  163. self._set_zone_state(zone_name_class, ZONE_OK)
  164. self._set_zone_retry_timer(zone_name_class)
  165. def zone_handle_notify(self, zone_name_class, master):
  166. """Handle an incoming NOTIFY message via the Auth module.
  167. It returns True if the specified zone matches one of the locally
  168. configured list of secondary zones; otherwise returns False.
  169. In the latter case it assumes the server is a primary (master) of the
  170. zone; the Auth module should have rejected the case where it's not
  171. even authoritative for the zone.
  172. Note: to be more robust and less independent from other module's
  173. behavior, it's probably safer to check the authority condition here,
  174. too. But right now it uses SQLite3 specific API (to be deprecated),
  175. so we rather rely on Auth.
  176. Parameters:
  177. zone_name_class (Name, RRClass): the notified zone name and class.
  178. master (str): textual address of the NOTIFY sender.
  179. """
  180. if self._zone_not_exist(zone_name_class):
  181. logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_ZONE_NOTIFY_NOT_SECONDARY,
  182. zone_name_class[0], zone_name_class[1], master)
  183. return False
  184. self._set_zone_notifier_master(zone_name_class, master)
  185. self._set_zone_notify_timer(zone_name_class)
  186. return True
  187. def zonemgr_reload_zone(self, zone_name_class):
  188. """ Reload a zone."""
  189. zone_soa = sqlite3_ds.get_zone_soa(str(zone_name_class[0]), self._db_file)
  190. self._zonemgr_refresh_info[zone_name_class]["zone_soa_rdata"] = zone_soa[7]
  191. def zonemgr_add_zone(self, zone_name_class):
  192. """ Add a zone into zone manager."""
  193. logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_LOAD_ZONE, zone_name_class[0], zone_name_class[1])
  194. zone_info = {}
  195. zone_soa = sqlite3_ds.get_zone_soa(str(zone_name_class[0]), self._db_file)
  196. if zone_soa is None:
  197. logger.warn(ZONEMGR_NO_SOA, zone_name_class[0], zone_name_class[1])
  198. zone_info["zone_soa_rdata"] = None
  199. zone_reload_time = 0.0
  200. else:
  201. zone_info["zone_soa_rdata"] = zone_soa[7]
  202. zone_reload_time = float(zone_soa[7].split(" ")[RETRY_OFFSET])
  203. zone_info["zone_state"] = ZONE_OK
  204. zone_info["last_refresh_time"] = self._get_current_time()
  205. self._zonemgr_refresh_info[zone_name_class] = zone_info
  206. # Imposes some random jitters to avoid many zones need to do refresh at the same time.
  207. zone_reload_time = max(self._lowerbound_retry, zone_reload_time)
  208. self._set_zone_timer(zone_name_class, zone_reload_time, self._reload_jitter * zone_reload_time)
  209. def _zone_is_expired(self, zone_name_class):
  210. """Judge whether a zone is expired or not."""
  211. zone_expired_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[EXPIRED_OFFSET])
  212. zone_last_refresh_time = self._get_zone_last_refresh_time(zone_name_class)
  213. if (ZONE_EXPIRED == self._get_zone_state(zone_name_class) or
  214. zone_last_refresh_time + zone_expired_time <= self._get_current_time()):
  215. return True
  216. return False
  217. def _get_zone_soa_rdata(self, zone_name_class):
  218. return self._zonemgr_refresh_info[zone_name_class]["zone_soa_rdata"]
  219. def _get_zone_last_refresh_time(self, zone_name_class):
  220. return self._zonemgr_refresh_info[zone_name_class]["last_refresh_time"]
  221. def _set_zone_last_refresh_time(self, zone_name_class, time):
  222. self._zonemgr_refresh_info[zone_name_class]["last_refresh_time"] = time
  223. def _get_zone_notifier_master(self, zone_name_class):
  224. if ("notify_master" in self._zonemgr_refresh_info[zone_name_class].keys()):
  225. return self._zonemgr_refresh_info[zone_name_class]["notify_master"]
  226. return None
  227. def _set_zone_notifier_master(self, zone_name_class, master_addr):
  228. self._zonemgr_refresh_info[zone_name_class]["notify_master"] = master_addr
  229. def _clear_zone_notifier_master(self, zone_name_class):
  230. if ("notify_master" in self._zonemgr_refresh_info[zone_name_class].keys()):
  231. del self._zonemgr_refresh_info[zone_name_class]["notify_master"]
  232. def _get_zone_state(self, zone_name_class):
  233. return self._zonemgr_refresh_info[zone_name_class]["zone_state"]
  234. def _set_zone_state(self, zone_name_class, zone_state):
  235. self._zonemgr_refresh_info[zone_name_class]["zone_state"] = zone_state
  236. def _get_zone_refresh_timeout(self, zone_name_class):
  237. return self._zonemgr_refresh_info[zone_name_class]["refresh_timeout"]
  238. def _set_zone_refresh_timeout(self, zone_name_class, time):
  239. self._zonemgr_refresh_info[zone_name_class]["refresh_timeout"] = time
  240. def _get_zone_next_refresh_time(self, zone_name_class):
  241. return self._zonemgr_refresh_info[zone_name_class]["next_refresh_time"]
  242. def _set_zone_next_refresh_time(self, zone_name_class, time):
  243. self._zonemgr_refresh_info[zone_name_class]["next_refresh_time"] = time
  244. def _send_command(self, module_name, command_name, params):
  245. """Send command between modules."""
  246. try:
  247. self._mccs.rpc_call(command_name, module_name, params=params)
  248. except socket.error:
  249. # FIXME: WTF? Where does socket.error come from? And how do we ever
  250. # dare ignore such serious error? It can only be broken link to
  251. # msgq, we need to terminate then.
  252. logger.error(ZONEMGR_SEND_FAIL, module_name)
  253. except (isc.cc.session.SessionTimeout, isc.config.RPCError):
  254. pass # for now we just ignore the failure
  255. def _find_need_do_refresh_zone(self):
  256. """Find the first zone need do refresh, if no zone need
  257. do refresh, return the zone with minimum next_refresh_time.
  258. """
  259. zone_need_refresh = None
  260. for zone_name_class in self._zonemgr_refresh_info.keys():
  261. zone_state = self._get_zone_state(zone_name_class)
  262. # If hasn't received refresh response but are within refresh
  263. # timeout, skip the zone
  264. if (ZONE_REFRESHING == zone_state and
  265. (self._get_zone_refresh_timeout(zone_name_class) > self._get_current_time())):
  266. continue
  267. # Get the zone with minimum next_refresh_time
  268. if ((zone_need_refresh is None) or
  269. (self._get_zone_next_refresh_time(zone_name_class) <
  270. self._get_zone_next_refresh_time(zone_need_refresh))):
  271. zone_need_refresh = zone_name_class
  272. # Find the zone need do refresh
  273. if (self._get_zone_next_refresh_time(zone_need_refresh) < self._get_current_time()):
  274. break
  275. return zone_need_refresh
  276. def _do_refresh(self, zone_name_class):
  277. """Do zone refresh."""
  278. logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_REFRESH_ZONE, zone_name_class[0], zone_name_class[1])
  279. self._set_zone_state(zone_name_class, ZONE_REFRESHING)
  280. self._set_zone_refresh_timeout(zone_name_class, self._get_current_time() + self._max_transfer_timeout)
  281. notify_master = self._get_zone_notifier_master(zone_name_class)
  282. # If the zone has notify master, send notify command to xfrin module
  283. if notify_master:
  284. param = {"zone_name" : zone_name_class[0],
  285. "zone_class" : zone_name_class[1],
  286. "master" : notify_master
  287. }
  288. self._send_command(XFRIN_MODULE_NAME, ZONE_NOTIFY_COMMAND, param)
  289. self._clear_zone_notifier_master(zone_name_class)
  290. # Send refresh command to xfrin module
  291. else:
  292. param = {"zone_name" : zone_name_class[0],
  293. "zone_class" : zone_name_class[1]
  294. }
  295. self._send_command(XFRIN_MODULE_NAME, ZONE_REFRESH_COMMAND, param)
  296. def _zone_mgr_is_empty(self):
  297. """Does zone manager has no zone?"""
  298. if not len(self._zonemgr_refresh_info):
  299. return True
  300. return False
  301. def _run_timer(self, start_event):
  302. while self._running:
  303. # Notify run_timer that we already started and are inside the loop.
  304. # It is set only once, but when it was outside the loop, there was
  305. # a race condition and _running could be set to false before we
  306. # could enter it
  307. if start_event:
  308. start_event.set()
  309. start_event = None
  310. # If zonemgr has no zone, set timer timeout to self._lowerbound_retry.
  311. if self._zone_mgr_is_empty():
  312. timeout = self._lowerbound_retry
  313. else:
  314. zone_need_refresh = self._find_need_do_refresh_zone()
  315. # If don't get zone with minimum next refresh time, set timer timeout to self._lowerbound_retry.
  316. if not zone_need_refresh:
  317. timeout = self._lowerbound_retry
  318. else:
  319. timeout = self._get_zone_next_refresh_time(zone_need_refresh) - self._get_current_time()
  320. if (timeout < 0):
  321. self._do_refresh(zone_need_refresh)
  322. continue
  323. """ Wait for the socket notification for a maximum time of timeout
  324. in seconds (as float)."""
  325. try:
  326. rlist, wlist, xlist = select.select([self._check_sock, self._read_sock], [], [], timeout)
  327. except select.error as e:
  328. if e.args[0] == errno.EINTR:
  329. (rlist, wlist, xlist) = ([], [], [])
  330. else:
  331. logger.error(ZONEMGR_SELECT_ERROR, e);
  332. break
  333. for fd in rlist:
  334. if fd == self._read_sock: # awaken by shutdown socket
  335. # self._running will be False by now, if it is not a false
  336. # alarm (linux kernel is said to trigger spurious wakeup
  337. # on a filehandle that is not really readable).
  338. continue
  339. if fd == self._check_sock: # awaken by check socket
  340. self._check_sock.recv(32)
  341. def run_timer(self, daemon=False):
  342. """
  343. Keep track of zone timers. Spawns and starts a thread. The thread object
  344. is returned.
  345. You can stop it by calling shutdown().
  346. """
  347. # Small sanity check
  348. if self._running:
  349. logger.error(ZONEMGR_TIMER_THREAD_RUNNING)
  350. raise RuntimeError("Trying to run the timers twice at the same time")
  351. # Prepare the launch
  352. self._running = True
  353. (self._read_sock, self._write_sock) = socket.socketpair()
  354. start_event = threading.Event()
  355. # Start the thread
  356. self._thread = threading.Thread(target = self._run_timer,
  357. args = (start_event,))
  358. if daemon:
  359. self._thread.setDaemon(True)
  360. self._thread.start()
  361. start_event.wait()
  362. # Return the thread to anyone interested
  363. return self._thread
  364. def shutdown(self):
  365. """
  366. Stop the run_timer() thread. Block until it finished. This must be
  367. called from a different thread.
  368. """
  369. if not self._running:
  370. logger.error(ZONEMGR_NO_TIMER_THREAD)
  371. raise RuntimeError("Trying to shutdown, but not running")
  372. # Ask the thread to stop
  373. self._running = False
  374. self._write_sock.send(b'shutdown') # make self._read_sock readable
  375. # Wait for it to actually finnish
  376. self._thread.join()
  377. # Wipe out what we do not need
  378. self._thread = None
  379. self._read_sock.close()
  380. self._write_sock.close()
  381. self._read_sock = None
  382. self._write_sock = None
  383. def update_config_data(self, new_config, module_cc_session):
  384. """ update ZonemgrRefresh config """
  385. # Get a new value, but only if it is defined (commonly used below)
  386. # We don't use "value or default", because if value would be
  387. # 0, we would take default
  388. def val_or_default(value, default):
  389. if value is not None:
  390. return value
  391. else:
  392. return default
  393. self._lowerbound_refresh = val_or_default(
  394. new_config.get('lowerbound_refresh'), self._lowerbound_refresh)
  395. self._lowerbound_retry = val_or_default(
  396. new_config.get('lowerbound_retry'), self._lowerbound_retry)
  397. self._max_transfer_timeout = val_or_default(
  398. new_config.get('max_transfer_timeout'), self._max_transfer_timeout)
  399. self._refresh_jitter = val_or_default(
  400. new_config.get('refresh_jitter'), self._refresh_jitter)
  401. self._reload_jitter = val_or_default(
  402. new_config.get('reload_jitter'), self._reload_jitter)
  403. try:
  404. required = {}
  405. secondary_zones = new_config.get('secondary_zones')
  406. if secondary_zones is not None:
  407. # Add new zones
  408. for secondary_zone in new_config.get('secondary_zones'):
  409. if 'name' not in secondary_zone:
  410. raise ZonemgrException("Secondary zone specified "
  411. "without a name")
  412. name = secondary_zone['name']
  413. # Convert to Name and back (both to check and to normalize)
  414. try:
  415. name = isc.dns.Name(name, True).to_text()
  416. # Name() can raise a number of different exceptions, just
  417. # catch 'em all.
  418. except Exception as isce:
  419. raise ZonemgrException("Bad zone name '" + name +
  420. "': " + str(isce))
  421. # Currently we use an explicit get_default_value call
  422. # in case the class hasn't been set. Alternatively, we
  423. # could use
  424. # module_cc_session.get_value('secondary_zones[INDEX]/class')
  425. # To get either the value that was set, or the default if
  426. # it wasn't set.
  427. # But the real solution would be to make new_config a type
  428. # that contains default values itself
  429. # (then this entire method can be simplified a lot, and we
  430. # wouldn't need direct access to the ccsession object)
  431. if 'class' in secondary_zone:
  432. rr_class = secondary_zone['class']
  433. else:
  434. rr_class = module_cc_session.get_default_value(
  435. 'secondary_zones/class')
  436. # Convert rr_class to and from RRClass to check its value
  437. try:
  438. name_class = (name, isc.dns.RRClass(rr_class).to_text())
  439. except isc.dns.InvalidRRClass:
  440. raise ZonemgrException("Bad RR class '" +
  441. rr_class +
  442. "' for zone " + name)
  443. required[name_class] = True
  444. # Add it only if it isn't there already
  445. if not name_class in self._zonemgr_refresh_info:
  446. # If we are not able to find it in database, log an warning
  447. self.zonemgr_add_zone(name_class)
  448. # Drop the zones that are no longer there
  449. # Do it in two phases, python doesn't like deleting while iterating
  450. to_drop = []
  451. for old_zone in self._zonemgr_refresh_info:
  452. if not old_zone in required:
  453. to_drop.append(old_zone)
  454. for drop in to_drop:
  455. del self._zonemgr_refresh_info[drop]
  456. except:
  457. raise
  458. class Zonemgr:
  459. """Zone manager class."""
  460. def __init__(self):
  461. self._zone_refresh = None
  462. self._setup_session()
  463. self._db_file = self.get_db_file()
  464. # Create socket pair for communicating between main thread and zonemgr timer thread
  465. self._master_socket, self._slave_socket = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
  466. self._zone_refresh = ZonemgrRefresh(self._db_file, self._slave_socket, self._module_cc)
  467. self._zone_refresh.run_timer()
  468. self._lock = threading.Lock()
  469. self._shutdown_event = threading.Event()
  470. self.running = False
  471. def _setup_session(self):
  472. """Setup two sessions for zonemgr, one(self._module_cc) is used for receiving
  473. commands and config data sent from other modules, another one (self._cc)
  474. is used to send commands to proper modules."""
  475. self._module_cc = isc.config.ModuleCCSession(SPECFILE_LOCATION,
  476. self.config_handler,
  477. self.command_handler)
  478. self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION)
  479. self._config_data = self._module_cc.get_full_config()
  480. self._config_data_check(self._config_data)
  481. self._module_cc.start()
  482. def get_db_file(self):
  483. db_file, is_default = self._module_cc.get_remote_config_value(AUTH_MODULE_NAME, "database_file")
  484. # this too should be unnecessary, but currently the
  485. # 'from build' override isn't stored in the config
  486. # (and we don't have indirect python access to datasources yet)
  487. if is_default and "B10_FROM_BUILD" in os.environ:
  488. db_file = os.environ["B10_FROM_BUILD"] + "/bind10_zones.sqlite3"
  489. return db_file
  490. def shutdown(self):
  491. """Shutdown the zonemgr process. The thread which is keeping track of
  492. zone timers should be terminated.
  493. """
  494. self._zone_refresh.shutdown()
  495. self._slave_socket.close()
  496. self._master_socket.close()
  497. self._shutdown_event.set()
  498. self.running = False
  499. def config_handler(self, new_config):
  500. """ Update config data. """
  501. answer = create_answer(0)
  502. ok = True
  503. complete = self._config_data.copy()
  504. for key in new_config:
  505. if key not in complete:
  506. answer = create_answer(1, "Unknown config data: " + str(key))
  507. ok = False
  508. continue
  509. complete[key] = new_config[key]
  510. self._config_data_check(complete)
  511. if self._zone_refresh is not None:
  512. try:
  513. self._zone_refresh.update_config_data(complete, self._module_cc)
  514. except Exception as e:
  515. answer = create_answer(1, str(e))
  516. ok = False
  517. if ok:
  518. self._config_data = complete
  519. return answer
  520. def _config_data_check(self, config_data):
  521. """Check whether the new config data is valid or
  522. not. It contains only basic logic, not full check against
  523. database."""
  524. # jitter should not be bigger than half of the original value
  525. if config_data.get('refresh_jitter') > 0.5:
  526. config_data['refresh_jitter'] = 0.5
  527. logger.warn(ZONEMGR_JITTER_TOO_BIG)
  528. def _parse_cmd_params(self, args, command):
  529. zone_name = args.get("zone_name")
  530. if not zone_name:
  531. logger.error(ZONEMGR_NO_ZONE_NAME)
  532. raise ZonemgrException("zone name should be provided")
  533. zone_class = args.get("zone_class")
  534. if not zone_class:
  535. logger.error(ZONEMGR_NO_ZONE_CLASS)
  536. raise ZonemgrException("zone class should be provided")
  537. if (command != ZONE_NOTIFY_COMMAND):
  538. return (zone_name, zone_class)
  539. master_str = args.get("master")
  540. if not master_str:
  541. logger.error(ZONEMGR_NO_MASTER_ADDRESS)
  542. raise ZonemgrException("master address should be provided")
  543. return ((zone_name, zone_class), master_str)
  544. def command_handler(self, command, args):
  545. """Handle command receivd from command channel.
  546. ZONE_NOTIFY_COMMAND is issued by Auth process;
  547. ZONE_NEW_DATA_READY_CMD and ZONE_XFRIN_FAILED are issued by
  548. Xfrin process;
  549. shutdown is issued by a user or Init process. """
  550. answer = create_answer(0)
  551. if command == ZONE_NOTIFY_COMMAND:
  552. """ Handle Auth notify command"""
  553. # master is the source sender of the notify message.
  554. zone_name_class, master = self._parse_cmd_params(args, command)
  555. logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_NOTIFY,
  556. zone_name_class[0], zone_name_class[1])
  557. with self._lock:
  558. need_refresh = self._zone_refresh.zone_handle_notify(
  559. zone_name_class, master)
  560. if need_refresh:
  561. # Send notification to zonemgr timer thread by making
  562. # self._slave_socket readable.
  563. self._master_socket.send(b" ")
  564. elif command == notify_out.ZONE_NEW_DATA_READY_CMD:
  565. """ Handle xfrin success command"""
  566. zone_name_class = self._parse_cmd_params(args, command)
  567. logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_SUCCESS,
  568. zone_name_class[0], zone_name_class[1])
  569. with self._lock:
  570. self._zone_refresh.zone_refresh_success(zone_name_class)
  571. self._master_socket.send(b" ")# make self._slave_socket readable
  572. elif command == notify_out.ZONE_XFRIN_FAILED:
  573. """ Handle xfrin fail command"""
  574. zone_name_class = self._parse_cmd_params(args, command)
  575. logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_FAILED,
  576. zone_name_class[0], zone_name_class[1])
  577. with self._lock:
  578. self._zone_refresh.zone_refresh_fail(zone_name_class)
  579. self._master_socket.send(b" ")# make self._slave_socket readable
  580. elif command == "shutdown":
  581. logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_SHUTDOWN)
  582. self.shutdown()
  583. else:
  584. logger.warn(ZONEMGR_RECEIVE_UNKNOWN, str(command))
  585. answer = create_answer(1, "Unknown command:" + str(command))
  586. return answer
  587. def run(self):
  588. logger.debug(DBG_PROCESS, ZONEMGR_STARTED)
  589. self.running = True
  590. try:
  591. while not self._shutdown_event.is_set():
  592. fileno = self._module_cc.get_socket().fileno()
  593. # Wait with select() until there is something to read,
  594. # and then read it using a non-blocking read
  595. # This may or may not be relevant data for this loop,
  596. # but due to the way the zonemgr does threading, we
  597. # can't have a blocking read loop here.
  598. try:
  599. (reads, _, _) = select.select([fileno], [], [])
  600. except select.error as se:
  601. if se.args[0] != errno.EINTR:
  602. raise
  603. if fileno in reads:
  604. self._module_cc.check_command(True)
  605. finally:
  606. self._module_cc.send_stopping()
  607. zonemgrd = None
  608. def signal_handler(signal, frame):
  609. if zonemgrd:
  610. zonemgrd.shutdown()
  611. sys.exit(0)
  612. def set_signal_handler():
  613. signal.signal(signal.SIGTERM, signal_handler)
  614. signal.signal(signal.SIGINT, signal_handler)
  615. def set_cmd_options(parser):
  616. parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
  617. help="display more about what is going on")
  618. if '__main__' == __name__:
  619. try:
  620. logger.debug(DBG_START_SHUT, ZONEMGR_STARTING)
  621. parser = OptionParser()
  622. set_cmd_options(parser)
  623. (options, args) = parser.parse_args()
  624. if options.verbose:
  625. logger.set_severity("DEBUG", 99)
  626. set_signal_handler()
  627. zonemgrd = Zonemgr()
  628. zonemgrd.run()
  629. except KeyboardInterrupt:
  630. logger.info(ZONEMGR_KEYBOARD_INTERRUPT)
  631. except isc.cc.session.SessionError as e:
  632. logger.error(ZONEMGR_SESSION_ERROR)
  633. except isc.cc.session.SessionTimeout as e:
  634. logger.error(ZONEMGR_SESSION_TIMEOUT)
  635. except isc.config.ModuleCCSessionError as e:
  636. logger.error(ZONEMGR_CCSESSION_ERROR, str(e))
  637. if zonemgrd and zonemgrd.running:
  638. zonemgrd.shutdown()
  639. logger.info(ZONEMGR_SHUTDOWN)