Hi,

I'm trying to spin a nice transaction model around libknot, intended for
better automation, as attached.  I'm running into problems.

First and foremost, a lack of understanding what should go into what
fields when doing other commands than in the "stats" demos.  Are there
more advanced explanations of what goes into what fields?  I tried
looking into knotc, but it is rather long.  Perhaps there is a log
somewhere with the stuff going into knotd?

Secondly, the model by which transactions abort is not always logical.
Like a commit() that fails but does not turn into an abort() when
semantic errors are found.  I will run zone-check beforehand, to ensure
that this does not happen.

Thirdly, I found that the library freezes when supplied with unknown
commands or apparently funny data...

>>> import knotcontrol
>>> kc = knotcontrol.KnotControl()
>>> kc = knotcontrol.KnotControl ()
>>> kc.knot (cmd='stats')
KnotControl send {'cmd': 'stats'}
KnotControl recv {'server': {'zone-count': ['1']}}
{'server': {'zone-count': ['1']}}
>>> kc.knot (cmd='zone-stats',item='vanrein.org')
KnotControl send {'item': 'vanrein.org', 'cmd': 'zone-stats'}
KnotControl recv {}
{}
>>> kc.knot (cmd='zone-recv',item='vanrein.org')
KnotControl send {'item': 'vanrein.org', 'cmd': 'zone-recv'}

...and at this point it freezes, even to ^C -- before and after this
sequence, "kdig @localhost vanrein.org soa" worked to query the zone in
Knot DNS.


Sorry to be testing this early :-S but I'm eager to use it this way.


-Rick
# knotcontrol -- Class wrapper around libknot.control
#
# Higher-level transaction management, collecting succes/failure.
#
# From: Rick van Rein <[email protected]>


import libknot.control

import sys
import string
import json


#
# This uses the Python library to control Knot DNS.
# Its document includes a decent example:
#
#     Example:
#         import json
#         from libknot.control import *
#     
#         #load_lib("/usr/lib/libknot.so")
#     
#         ctl = KnotCtl()
#         ctl.connect("/var/run/knot/knot.sock")
#     
#         try:
#             ctl.send_block(cmd="conf-begin")
#             resp = ctl.receive_block()
#     
#             ctl.send_block(cmd="conf-set", section="zone", item="domain", data="test")
#             resp = ctl.receive_block()
#     
#             ctl.send_block(cmd="conf-commit")
#             resp = ctl.receive_block()
#     
#             ctl.send_block(cmd="conf-read", section="zone", item="domain")
#             resp = ctl.receive_block()
#             print(json.dumps(resp, indent=4))
#         finally:
#             ctl.send(KnotCtlType.END)
#             ctl.close()
#



class KnotControl:

	"""This class maintains state about Knot transactions:

	   **txn_success** is a boolean that indicates that the
	   current transaction has been successful up to here.

	   **txn_conf** is a boolean indicating if a global
	   configuration transaction has been assigned.

	   **txn_zone** is a set of zones for which a transaction
	   has been opened.
	"""

	def __init__ (self, socketpath='/var/run/knot/knot.sock'):
		"""Connect to knotd
		"""
		self.ctl = None
		self.txn_conf = False
		self.txn_zone = set ()
		self.txn_success = True
		self.ctl = libknot.control.KnotCtl ()
		self.ctl.connect (socketpath)
		self.ctl.set_timeout (3600)

	def __del__ (self):
		"""Cleanup the object.  We started __init__ with
		   setup of variables without external calls, so we
		   should be fine there.
		"""
		self.close ()

	def close (self):
		"""Disconnect from knotd; this involves aborting
		   any transactions that may still be open.
		"""
		self.force_abort ()
		if self.ctl is not None:
			self.ctl.send (libknot.control.KnotCtlType.END)
			self.ctl.close ()
		self.ctl = None

	def knot (self, **block):
		"""Run a command block and return the results.
		   This is skipped when the current transaction
		   has already failed.  The result from the
		   operation is returned.  Exceptions thrown by
		   libknot are caught and made into transaction
		   failures, and in such cases None is returned.
		"""
		if not self.txn_success:
			# Already failed; discontinue
			return None
		try:
			print 'KnotControl send', block
			self.ctl.send_block (**block)
			resp = self.ctl.receive_block ()
			print 'KnotControl recv', resp
		except libknot.control.KnotCtlError as lck:
			self.txn_success = False
			sys.stderr.write ('KnotControl exception: %s\n' % (str (lck),))
			resp = None
		return resp

	def try_commit (self):
		"""Attempt to commit the current transactions run by
		   knot.  If there was a failure, abort instead.  The
		   function returns the (overall) success of all
		   actions run through knot().
		"""
		if self.txn_success:
			# Success
			conf_cmd = 'conf-commit'
			zone_cmd = 'zone-commit'
		else:
			# Failure
			conf_cmd = 'conf-abort'
			zone_cmd = 'zone-abort'
			self.txn_success = True
		for zone in self.txn_zone:
			self.knot (cmd=zone_cmd, data=zone)
		self.txn_zone = set ()
		if self.txn_conf:
			self.knot (cmd=conf_cmd)
			self.txn_conf = False
		if not self.txn_success:
			raise InternalError ('Unexpected failure during %s and/or %s' % (conf_cmd, zone_cmd))
		return self.txn_success

	def force_abort (self):
		"""Deliberately abort all transactions run against knot.
		   The cause can be anything outside of DNS.
		   
		   After this procedure, the state has been reset, and
		   new transactions may be opened.  With no transactions
		   open, this has no effect.
		   
		   This function also runs when the object is closed,
		   to ensure giving up any locks that were held, and to
		   avoid implicitly committing unfinished data from an
		   unfinished program.
		"""
		self.txn_success = False
		self.try_commit ()

	def have_conf (self):
		"""Lock the conf transaction; this should always
		   be done before any of the zone locks.
		"""
		if self.txn_conf:
			return
		if len (self.txn_zone) > 0:
			raise Exception ('Allocate a configuration lock with or before the first zone lock')
		self.knot (cmd='conf-begin')
		self.txn_conf = True

	def have_zones (self, zones, conf=False):
		"""Use this method to claim locks on a set (or list)
		   of zones.  A single zone may be used too, supplied
		   as a string.  Under the assumption that other knot
		   clients use the same order, namely alphabetic
		   order of lowercase-mapped zone names, there should
		   be no risk of deadlock.  That is under the
		   assumption that any configuration lock is obtained
		   either before or with the first zone.
		  
		   To prevent deadlock, you cannot call this function
		   more than once; you need to supply all the zones
		   at the same time.
		  
		   #TODO# We could allow sequencing for sub-zones?
		"""
		if len (self.txn_zone) > 0:
			raise Exception ('Lock all zones at once to stave off deadlocks')
		if type (zones) == type (''):
			ordered_zones = [zones]
		else:
			ordered_zones = map (string.lower, zones)
			ordered_zones.sort ()
		if conf:
			self.have_conf ()
		for zone in ordered_zones:
			self.knot (cmd='zone-begin', data=zone)
			self.txn_zone.add (zone)



if True:

	#TODO# BE LAZY, BOOT KNOTD FROM HERE
	import os
	os.system ("/etc/init.d/knot start")
	import time
	# time.sleep (2)


	#TODO# BE LAZY, START THINGS FROM HERE

	knot = None
	try:
		knot = KnotControl ()

		# time.sleep (5)

		knot.have_conf ()
		knot.knot (cmd='conf-set', section='zone', item='domain', data='orvelte.nep')
		knot.force_abort ()

		# time.sleep (4)

		knot.have_conf ()
		knot.knot (cmd='conf-unset', section='zone', item='domain', data='orvelte.nep')
		knot.try_commit ()

	except Exception as e:
		print 'EXCEPTION:', e

	finally:
		if knot is not None:
			knot.close ()
			knot = None

-- 
https://lists.nic.cz/cgi-bin/mailman/listinfo/knot-dns-users

Reply via email to