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