datasrc_info.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. # Copyright (C) 2013 Internet Systems Consortium.
  2. #
  3. # Permission to use, copy, modify, and distribute this software for any
  4. # purpose with or without fee is hereby granted, provided that the above
  5. # copyright notice and this permission notice appear in all copies.
  6. #
  7. # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
  8. # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  9. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  10. # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  11. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  12. # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  13. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  14. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. import os
  16. class SegmentInfoError(Exception):
  17. """An exception raised for general errors in the SegmentInfo class."""
  18. pass
  19. class SegmentInfo:
  20. """A base class to maintain information about memory segments.
  21. An instance of this class corresponds to the memory segment used
  22. for in-memory cache of a specific single data source. It manages
  23. information to set/reset the latest effective segment (such as
  24. path to a memory mapped file) and sets of other modules using the
  25. segment.
  26. Since there can be several different types of memory segments,
  27. the top level class provides abstract interfaces independent from
  28. segment-type specific details. Such details are expected to be
  29. delegated to subclasses corresponding to specific types of segments.
  30. A summarized (and simplified) state transition diagram (for __state)
  31. would be as follows:
  32. +--sync_reader()/remove_reader()
  33. | still have old readers
  34. | |
  35. UPDATING-----complete_--->SYNCHRONIZING<---+
  36. ^ update() |
  37. start_update()| | sync_reader()/remove_reader()
  38. events | V no more old reader
  39. exist READY<------complete_----------COPYING
  40. update()
  41. """
  42. # Common constants of user type: reader or writer
  43. READER = 0
  44. WRITER = 1
  45. # Enumerated values for state:
  46. UPDATING = 0 # the segment is being updated (by the builder thread,
  47. # although SegmentInfo won't care about this level of
  48. # details).
  49. SYNCHRONIZING = 1 # one pair of underlying segments has been
  50. # updated, and readers are now migrating to the
  51. # updated version of the segment.
  52. COPYING = 2 # all readers that used the old version of segment have
  53. # been migrated to the updated version, and the old
  54. # segment is now being updated.
  55. READY = 3 # both segments of the pair have been updated. it can now
  56. # handle further updates (e.g., from xfrin).
  57. def __init__(self):
  58. # Holds the state of SegmentInfo. See the class description
  59. # above for the state transition diagram.
  60. self.__state = self.READY
  61. # __readers is a set of 'reader_session_id' private to
  62. # SegmentInfo. It consists of the (ID of) reader modules that
  63. # are using the "current" reader version of the segment.
  64. self.__readers = set()
  65. # __old_readers is a set of 'reader_session_id' private to
  66. # SegmentInfo for write (update), but publicly readable. It can
  67. # be non empty only in the SYNCHRONIZING state, and consists of
  68. # (ID of) reader modules that are using the old version of the
  69. # segments (and have to migrate to the updated version).
  70. self.__old_readers = set()
  71. # __events is a FIFO queue of opaque data for pending update
  72. # events. Update events can come at any time (e.g., after
  73. # xfr-in), but can be only handled if SegmentInfo is in the
  74. # READY state. This maintains such pending events in the order
  75. # they arrived. SegmentInfo doesn't have to know the details of
  76. # the stored data; it only matters for the memmgr.
  77. self.__events = []
  78. def get_state(self):
  79. """Returns the state of SegmentInfo (UPDATING, SYNCHRONIZING,
  80. COPYING or READY)."""
  81. return self.__state
  82. def get_readers(self):
  83. """Returns a set of IDs of the reader modules that are using the
  84. "current" reader version of the segment. This method is mainly
  85. useful for testing purposes."""
  86. return self.__readers
  87. def get_old_readers(self):
  88. """Returns a set of IDs of reader modules that are using the old
  89. version of the segments and have to be migrated to the updated
  90. version."""
  91. return self.__old_readers
  92. def get_events(self):
  93. """Returns a list of pending events in the order they arrived."""
  94. return self.__events
  95. # Helper method used in complete_update(), sync_reader() and
  96. # remove_reader().
  97. def __sync_reader_helper(self, new_state):
  98. if not self.__old_readers:
  99. self.__state = new_state
  100. if self.__events:
  101. e = self.__events[0]
  102. del self.__events[0]
  103. return e
  104. return None
  105. def add_event(self, event_data):
  106. """Add an event to the end of the pending events queue. The
  107. event_data is not used internally by this class, and is returned
  108. as-is by other methods. The format of event_data only matters in
  109. the memmgr. This method must be called by memmgr when it
  110. receives a request for reloading a zone. No state transition
  111. happens."""
  112. self.__events.append(event_data)
  113. def add_reader(self, reader_session_id):
  114. """Add the reader module ID to an internal set of reader modules
  115. that are using the "current" reader version of the segment. It
  116. must be called by memmgr when it first gets the pre-existing
  117. readers or when it's notified of a new reader. No state
  118. transition happens."""
  119. if reader_session_id in self.__readers:
  120. raise SegmentInfoError('Reader session ID is already in readers set: ' +
  121. str(reader_session_id))
  122. self.__readers.add(reader_session_id)
  123. def start_update(self):
  124. """If the current state is READY and there are pending events,
  125. it changes the state to UPDATING and returns the head (oldest)
  126. event (without removing it from the pending events queue). This
  127. tells the caller (memmgr) that it should initiate the update
  128. process with the builder. In all other cases it returns None."""
  129. if self.__state == self.READY and self.__events:
  130. self.__state = self.UPDATING
  131. return self.__events[0]
  132. return None
  133. def complete_update(self):
  134. """This method should be called when memmgr is notified by the
  135. builder of the completion of segment update. It changes the
  136. state from UPDATING to SYNCHRONIZING, and COPYING to READY. In
  137. the former case, set of reader modules that are using the
  138. "current" reader version of the segment are moved to the set
  139. that are using an "old" version of segment. If there are no such
  140. readers using the "old" version of segment, it pops the head
  141. (oldest) event from the pending events queue and returns it. It
  142. is an error if this method is called in other states than
  143. UPDATING and COPYING."""
  144. if self.__state == self.UPDATING:
  145. self.__state = self.SYNCHRONIZING
  146. self.__old_readers.update(self.__readers)
  147. self.__readers.clear()
  148. return self.__sync_reader_helper(self.SYNCHRONIZING)
  149. elif self.__state == self.COPYING:
  150. self.__state = self.READY
  151. return None
  152. else:
  153. raise SegmentInfoError('complete_update() called in ' +
  154. 'incorrect state: ' + str(self.__state))
  155. def sync_reader(self, reader_session_id):
  156. """This method must only be called in the SYNCHRONIZING
  157. state. memmgr should call it when it receives the
  158. "segment_update_ack" message from a reader module. It moves the
  159. given ID from the set of reader modules that are using the "old"
  160. version of the segment to the set of reader modules that are
  161. using the "current" version of the segment, and if there are no
  162. reader modules using the "old" version of the segment, the state
  163. is changed to COPYING. If the state has changed to COPYING, it
  164. pops the head (oldest) event from the pending events queue and
  165. returns it; otherwise it returns None."""
  166. if self.__state != self.SYNCHRONIZING:
  167. raise SegmentInfoError('sync_reader() called in ' +
  168. 'incorrect state: ' + str(self.__state))
  169. if reader_session_id not in self.__old_readers:
  170. raise SegmentInfoError('Reader session ID is not in old readers set: ' +
  171. str(reader_session_id))
  172. if reader_session_id in self.__readers:
  173. raise SegmentInfoError('Reader session ID is already in readers set: ' +
  174. str(reader_session_id))
  175. self.__old_readers.remove(reader_session_id)
  176. self.__readers.add(reader_session_id)
  177. return self.__sync_reader_helper(self.COPYING)
  178. def remove_reader(self, reader_session_id):
  179. """This method must only be called in the SYNCHRONIZING
  180. state. memmgr should call it when it's notified that an existing
  181. reader has unsubscribed. It removes the given reader ID from
  182. either the set of readers that use the "current" version of the
  183. segment or the "old" version of the segment (wherever the reader
  184. belonged), and in the latter case, if there are no reader
  185. modules using the "old" version of the segment, the state is
  186. changed to COPYING. If the state has changed to COPYING, it pops
  187. the head (oldest) event from the pending events queue and
  188. returns it; otherwise it returns None."""
  189. if self.__state != self.SYNCHRONIZING:
  190. raise SegmentInfoError('remove_reader() called in ' +
  191. 'incorrect state: ' + str(self.__state))
  192. if reader_session_id in self.__old_readers:
  193. self.__old_readers.remove(reader_session_id)
  194. return self.__sync_reader_helper(self.COPYING)
  195. elif reader_session_id in self.__readers:
  196. self.__readers.remove(reader_session_id)
  197. return None
  198. else:
  199. raise SegmentInfoError('Reader session ID is not in current ' +
  200. 'readers or old readers set: ' +
  201. str(reader_session_id))
  202. def create(type, genid, rrclass, datasrc_name, mgr_config):
  203. """Factory of specific SegmentInfo subclass instance based on the
  204. segment type.
  205. This is specifically for the memmgr, and segments that are not of
  206. its interest will be ignored. This method returns None in these
  207. cases. At least 'local' type segments will be ignored this way.
  208. If an unknown type of segment is specified, this method throws an
  209. SegmentInfoError exception. The assumption is that this method
  210. is called after the corresponding data source configuration has been
  211. validated, at which point such unknown segments should have been
  212. rejected.
  213. Parameters:
  214. type (str or None): The type of memory segment; None if the segment
  215. isn't used.
  216. genid (int): The generation ID of the corresponding data source
  217. configuration.
  218. rrclass (isc.dns.RRClass): The RR class of the data source.
  219. datasrc_name (str): The name of the data source.
  220. mgr_config (dict): memmgr configuration related to memory segment
  221. information. The content of the dict is type specific; each
  222. subclass is expected to know which key is necessary and the
  223. semantics of its value.
  224. """
  225. if type == 'mapped':
  226. return MappedSegmentInfo(genid, rrclass, datasrc_name, mgr_config)
  227. elif type is None or type == 'local':
  228. return None
  229. raise SegmentInfoError('unknown segment type to create info: ' + type)
  230. def get_reset_param(self, user_type):
  231. """Return parameters to reset the zone table memory segment.
  232. It returns a dict object that consists of parameter mappings
  233. (string to parameter value) for the specified type of user to
  234. reset a zone table segment with
  235. isc.datasrc.ConfigurableClientList.reset_memory_segment(). It
  236. can also be passed to the user module as part of command
  237. parameters. Note that reset_memory_segment() takes a json
  238. expression encoded as a string, so the return value of this method
  239. will have to be converted with json.dumps().
  240. Each subclass must implement this method.
  241. Parameter:
  242. user_type (READER or WRITER): specifies the type of user to reset
  243. the segment.
  244. """
  245. raise SegmentInfoError('get_reset_param is not implemented')
  246. def switch_versions(self):
  247. """Switch internal information for the reader segment and writer
  248. segment.
  249. This method is expected to be called when the writer on one version
  250. of memory segment completes updates and the memmgr is going to
  251. have readers switch to the updated version. Details of the
  252. information to be switched would depend on the segment type, and
  253. are delegated to the specific subclass.
  254. Each subclass must implement this method.
  255. """
  256. raise SegmentInfoError('switch_versions is not implemented')
  257. class MappedSegmentInfo(SegmentInfo):
  258. """SegmentInfo implementation of 'mapped' type memory segments.
  259. It maintains paths to mapped files both readers and the writer.
  260. While objets of this class are expected to be shared by multiple
  261. threads, it assumes operations are serialized through message passing,
  262. so access to this class itself is not protected by any explicit
  263. synchronization mechanism.
  264. """
  265. def __init__(self, genid, rrclass, datasrc_name, mgr_config):
  266. super().__init__()
  267. # Something like "/var/bind10/zone-IN-1-sqlite3-mapped"
  268. self.__mapped_file_base = mgr_config['mapped_file_dir'] + os.sep + \
  269. 'zone-' + str(rrclass) + '-' + str(genid) + '-' + datasrc_name + \
  270. '-mapped'
  271. # Current versions (suffix of the mapped files) for readers and the
  272. # writer. In this initial implementation we assume that all possible
  273. # readers are waiting for a new version (not using pre-existing one),
  274. # and the writer is expected to build a new segment as version "0".
  275. self.__reader_ver = None # => 0 => 1 => 0 => 1 ...
  276. self.__writer_ver = 0 # => 1 => 0 => 1 => 0 ...
  277. def get_reset_param(self, user_type):
  278. ver = self.__reader_ver if user_type == self.READER else \
  279. self.__writer_ver
  280. if ver is None:
  281. return None
  282. mapped_file = self.__mapped_file_base + '.' + str(ver)
  283. return {'mapped-file': mapped_file}
  284. def switch_versions(self):
  285. # Swith the versions as noted in the constructor.
  286. self.__writer_ver = 1 - self.__writer_ver
  287. if self.__reader_ver is None:
  288. self.__reader_ver = 0
  289. else:
  290. self.__reader_ver = 1 - self.__reader_ver
  291. # Versions should be different
  292. assert(self.__reader_ver != self.__writer_ver)
  293. class DataSrcInfo:
  294. """A container for datasrc.ConfigurableClientLists and associated
  295. in-memory segment information corresponding to a given geration of
  296. configuration.
  297. This class maintains all datasrc.ConfigurableClientLists in a form
  298. of dict from RR classes corresponding to a given single generation
  299. of data source configuration, along with sets of memory segment
  300. information that needs to be used by memmgr.
  301. Once constructed, mappings do not change (different generation of
  302. configuration will result in another DataSrcInfo objects). Status
  303. of SegmentInfo objects stored in this class object may change over time.
  304. Attributes: these are all constant and read only. For dict objects,
  305. mapping shouldn't be modified either.
  306. gen_id (int): The corresponding configuration generation ID.
  307. clients_map (dict, isc.dns.RRClass=>isc.datasrc.ConfigurableClientList):
  308. The configured client lists for all RR classes of the generation.
  309. segment_info_map (dict, (isc.dns.RRClass, str)=>SegmentInfo):
  310. SegmentInfo objects managed in the DataSrcInfo objects. Can be
  311. retrieved by (RRClass, <data source name>).
  312. """
  313. def __init__(self, genid, clients_map, mgr_config):
  314. """Constructor.
  315. As long as given parameters are of valid type and have been
  316. validated, this constructor shouldn't raise an exception.
  317. Parameters:
  318. genid (int): see gen_id attribute
  319. clients_map (dict): see clients_map attribute
  320. mgr_config (dict, str=>key-dependent-value): A copy of the current
  321. memmgr configuration, in case it's needed to construct a specific
  322. type of SegmentInfo. The specific SegmentInfo class is expected
  323. to know the key-value mappings that it needs.
  324. """
  325. self.__gen_id = genid
  326. self.__clients_map = clients_map
  327. self.__segment_info_map = {}
  328. for (rrclass, client_list) in clients_map.items():
  329. for (name, sgmt_type, _) in client_list.get_status():
  330. sgmt_info = SegmentInfo.create(sgmt_type, genid, rrclass, name,
  331. mgr_config)
  332. if sgmt_info is not None:
  333. self.__segment_info_map[(rrclass, name)] = sgmt_info
  334. @property
  335. def gen_id(self):
  336. return self.__gen_id
  337. @property
  338. def clients_map(self):
  339. return self.__clients_map
  340. @property
  341. def segment_info_map(self):
  342. return self.__segment_info_map