Unverified Commit aed22086 authored by Pavel Spirek's avatar Pavel Spirek
Browse files

Fixed zone data handling

parent 4fa5307b
module ietf-yang-library {
namespace "urn:ietf:params:xml:ns:yang:ietf-yang-library";
prefix "yanglib";
import ietf-yang-types {
prefix yang;
import ietf-inet-types {
prefix inet;
"IETF NETCONF (Network Configuration) Working Group";
"WG Web: <https://datatracker.ietf.org/wg/netconf/>
WG List: <mailto:netconf@ietf.org>
WG Chair: Mehmet Ersue
WG Chair: Mahesh Jethanandani
Editor: Andy Bierman
Editor: Martin Bjorklund
Editor: Kent Watsen
"This module contains monitoring information about the YANG
modules and submodules that are used within a YANG-based
Copyright (c) 2016 IETF Trust and the persons identified as
authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with or
without modification, is permitted pursuant to, and subject
to the license terms contained in, the Simplified BSD License
set forth in Section 4.c of the IETF Trust's Legal Provisions
Relating to IETF Documents
This version of this YANG module is part of RFC 7895; see
the RFC itself for full legal notices.";
revision 2016-06-21 {
"Initial revision.";
"RFC 7895: YANG Module Library.";
* Typedefs
typedef revision-identifier {
type string {
pattern '\d{4}-\d{2}-\d{2}';
"Represents a specific date in YYYY-MM-DD format.";
* Groupings
grouping module-list {
"The module data structure is represented as a grouping
so it can be reused in configuration or another monitoring
data structure.";
grouping common-leafs {
"Common parameters for YANG modules and submodules.";
leaf name {
type yang:yang-identifier;
"The YANG module or submodule name.";
leaf revision {
type union {
type revision-identifier;
type string { length 0; }
"The YANG module or submodule revision date.
A zero-length string is used if no revision statement
is present in the YANG module or submodule.";
grouping schema-leaf {
"Common schema leaf parameter for modules and submodules.";
leaf schema {
type inet:uri;
"Contains a URL that represents the YANG schema
resource for this module or submodule.
This leaf will only be present if there is a URL
available for retrieval of the schema for this entry.";
list module {
key "name revision";
"Each entry represents one revision of one module
currently supported by the server.";
uses common-leafs;
uses schema-leaf;
leaf namespace {
type inet:uri;
mandatory true;
"The XML namespace identifier for this module.";
leaf-list feature {
type yang:yang-identifier;
"List of YANG feature names from this module that are
supported by the server, regardless of whether they are
defined in the module or any included submodule.";
list deviation {
key "name revision";
"List of YANG deviation module names and revisions
used by this server to modify the conformance of
the module associated with this entry. Note that
the same module can be used for deviations for
multiple modules, so the same entry MAY appear
within multiple 'module' entries.
The deviation module MUST be present in the 'module'
list, with the same name and revision values.
The 'conformance-type' value will be 'implement' for
the deviation module.";
uses common-leafs;
leaf conformance-type {
type enumeration {
enum implement {
"Indicates that the server implements one or more
protocol-accessible objects defined in the YANG module
identified in this entry. This includes deviation
statements defined in the module.
For YANG version 1.1 modules, there is at most one
module entry with conformance type 'implement' for a
particular module name, since YANG 1.1 requires that,
at most, one revision of a module is implemented.
For YANG version 1 modules, there SHOULD NOT be more
than one module entry for a particular module name.";
enum import {
"Indicates that the server imports reusable definitions
from the specified revision of the module but does
not implement any protocol-accessible objects from
this revision.
Multiple module entries for the same module name MAY
exist. This can occur if multiple modules import the
same module but specify different revision dates in
the import statements.";
mandatory true;
"Indicates the type of conformance the server is claiming
for the YANG module identified by this entry.";
list submodule {
key "name revision";
"Each entry represents one submodule within the
parent module.";
uses common-leafs;
uses schema-leaf;
* Operational state data nodes
container modules-state {
config false;
"Contains YANG module monitoring information.";
leaf module-set-id {
type string;
mandatory true;
"Contains a server-specific identifier representing
the current set of modules and submodules. The
server MUST change the value of this leaf if the
information represented by the 'module' list instances
has changed.";
uses module-list;
* Notifications
notification yang-library-change {
"Generated when the set of modules and submodules supported
by the server has changed.";
leaf module-set-id {
type leafref {
path "/yanglib:modules-state/yanglib:module-set-id";
mandatory true;
"Contains the module-set-id value representing the
set of modules and submodules supported at the server at
the time the notification is generated.";
......@@ -204,7 +204,9 @@ class KnotConfAclListener(BaseDataListener):
class KnotZoneDataListener(BaseDataListener):
def process(self, sn: SchemaNode, ii: InstanceRoute, ch: DataChange):
base_ii_str = self.schema_path
print("zdChange at sn \"{}\", dn \"{}\"".format(sn.name, ii))
ii_str = "".join([str(seg) for seg in ii])
print("zdChange at sn \"{}\", dn \"{}\"".format(sn.name, ii_str))
base_ii = self._ds.parse_ii(base_ii_str, PathFormat.URL)
base_nv = self._ds.get_node(self._ds.get_data_root(), base_ii).value
......@@ -212,13 +214,13 @@ class KnotZoneDataListener(BaseDataListener):
name = ch.data["zone"]["name"]
print("--- Creating new zone \"{}\"".format(name))
knot_api.KNOT.set_item(section="zone", item="domain", data=name)
elif (len(ii) == (len(base_ii) + 2)) and isinstance(ii[len(base_ii) + 1], EntryKeys) and (ch.change_type == ChangeType.DELETE):
name = ii[len(base_ii) + 1].keys["name"]
print("--- Deleting zone \"{}\"".format(name))
# knot_api.KNOT.zone_new(name)
elif (len(ii) > len(base_ii)) and isinstance(ii[len(base_ii) + 1], EntryKeys):
zone_name = ii[len(base_ii) + 1].keys["name"]
......@@ -230,7 +232,7 @@ class KnotZoneDataListener(BaseDataListener):
print("writing soa {}".format(soa))
soarr = SOARecord(zone_name)
soarr = SOARecord()
soarr.mname = soa["mname"]
soarr.rname = soa["rname"]
soarr.serial = soa["serial"]
......@@ -17,4 +17,4 @@ NACM:
ALLOWED_USERS: ["lojza@mail.cz"]
SOCKET: "/tmp/knottest-1462525244-b67pmzm_/ddns/ttl/knot1/knot.sock"
SOCKET: "/home/pspirek/knot-conf/knot.sock"
......@@ -20,12 +20,7 @@ from yangson.instance import (
from .helpers import DataHelpers
class PathFormat(Enum):
URL = 0
from .helpers import PathFormat
class ChangeType(Enum):
......@@ -257,7 +252,8 @@ class BaseDatastore:
# n = self._data.goto(ii)
# sn = n.schema_node
sch_pth = str(InstanceRoute(filter(lambda n: isinstance(n, MemberName), ii)))
sch_pth_list = filter(lambda n: isinstance(n, MemberName), ii)
sch_pth = "".join([str(seg) for seg in sch_pth_list])
sn = self.get_schema_node(sch_pth)
while sn is not None:
......@@ -275,7 +275,8 @@
"name": "users",
"user-name": [
......@@ -304,18 +305,25 @@
"rule": [
"name": "no-writes-on-example.com",
"path": "/dnss:dns-server/dnss:zones/dnss:zone[dnss:domain='example.com']",
"path": "/dns-server:dns-server/zones/zone[domain='example.com']",
"access-operations": "create update delete",
"comment": "Users cannot write example.com.",
"action": "deny"
"name": "permit-zone-access",
"path": "/dnss:dns-server/dnss:zones/dnss:zone",
"path": "/dns-server:dns-server/zones/zone",
"access-operations": "*",
"comment": "Users can write other zones.",
"action": "permit"
"name": "permit-zone-data-access",
"path": "/dns-zones:zone-data",
"access-operations": "*",
"comment": "Users can edit zone data.",
"action": "permit"
"name": "permit-zone-reload",
"module-name": "dns-server",
from enum import Enum
from typing import Dict, Any
from datetime import datetime
from pytz import timezone
from yangson.instance import InstanceRoute, MemberName, EntryKeys
from yangson.instance import InstanceRoute, MemberName, EntryKeys, InstanceIdParser, ResourceIdParser
from yangson.datamodel import DataModel
class PathFormat(Enum):
URL = 0
class CertHelpers:
def get_field(cert: Dict[str, Any], key: str) -> str:
......@@ -40,6 +46,16 @@ class DataHelpers:
dm = DataModel(yl, [module_dir])
return dm
# Parse Instance Identifier from string
def parse_ii(path: str, path_format: PathFormat) -> InstanceRoute:
if path_format == PathFormat.URL:
ii = ResourceIdParser(path).parse()
ii = InstanceIdParser(path).parse()
return ii
class DateTimeHelpers:
......@@ -55,5 +71,9 @@ class DateTimeHelpers:
class ErrorHelpers:
def epretty(e: BaseException) -> str:
return e.__class__.__name__ + ": " + str(e)
def epretty(e: BaseException, module_name: str=None) -> str:
err_str = e.__class__.__name__ + ": " + str(e)
if module_name is not None:
return "In module " + module_name + ": " + err_str
return err_str
......@@ -72,7 +72,7 @@ def _get(prot: "H2Protocol", stream_id: int, ds: BaseDatastore, pth: str, yl_dat
n = ds.get_node_rpc(rpc1, yl_data)
response = json.dumps(n.value, indent=4) + "\n"
response = json.dumps(n.raw_value(), indent=4) + "\n"
response_bytes = response.encode()
response_headers = [
......@@ -90,7 +90,14 @@ def _get(prot: "H2Protocol", stream_id: int, ds: BaseDatastore, pth: str, yl_dat
response_headers.append(("content-length", len(response_bytes)))
prot.conn.send_headers(stream_id, response_headers)
prot.conn.send_data(stream_id, response_bytes, end_stream=True)
# prot.conn.send_data(stream_id, response_bytes, end_stream=True)
def split_arr(arr, chunk_size):
for i in range(0, len(arr), chunk_size):
yield arr[i:i + chunk_size]
for data_chunk in split_arr(response_bytes, prot.conn.max_outbound_frame_size):
prot.conn.send_data(stream_id, data_chunk, end_stream=False)
except DataLockError as e:
prot.send_empty(stream_id, "500", "Internal Server Error")
from enum import Enum
from typing import List, Union, Dict, Any
from threading import Lock
from colorlog import debug
from .libknot.control import KnotCtl, KnotCtlType
from .config import CONFIG
......@@ -46,8 +47,8 @@ class RRecordBase:
class SOARecord(RRecordBase):
def __init__(self, owner_name: str):
super().__init__(owner_name, "SOA")
def __init__(self):
super().__init__("@", "SOA")
self.mname = None # type: str
self.rname = None # type: str
self.serial = None # type: str
......@@ -109,19 +110,21 @@ class KnotConfig(KnotCtl):
if self.conf_state == KnotConfState.NONE:
print(">>> CONF BEGIN")
# print(">>> CONF BEGIN")
self.conf_state = KnotConfState.CONF
def begin_zone(self):
if self.conf_state == KnotConfState.NONE:
# print(">>> ZONE BEGIN")
self.conf_state = KnotConfState.ZONE
def commit(self):
if self.conf_state == KnotConfState.CONF:
# print(">>> CONF COMMIT")
self.conf_state = KnotConfState.NONE
raise KnotApiStateError()
......@@ -130,6 +133,7 @@ class KnotConfig(KnotCtl):
if self.conf_state == KnotConfState.ZONE:
# print(">>> ZONE COMMIT")
self.conf_state = KnotConfState.NONE
raise KnotApiStateError()
......@@ -190,9 +194,11 @@ class KnotConfig(KnotCtl):
raise KnotApiError("Knot socket is closed")
self.set_zone_item(zone=domain_name, owner=rr.owner, ttl=str(rr.ttl), rtype=rr.type, data=rr.rrdata_format())
res_data = rr.rrdata_format()
self.set_zone_item(zone=domain_name, owner=rr.owner, ttl=str(rr.ttl), rtype=rr.type, data=res_data)
debug("Inserting zone \"{}\" RR, type=\"{}\", owner=\"{}\", ttl=\"{}\", data=\"{}\"".format(
domain_name, rr.type, rr.owner, rr.ttl, res_data
resp = self.receive_block()
except Exception as e:
raise KnotInternalError(str(e))
import json
import collections
import copy
from threading import Lock
......@@ -6,19 +5,22 @@ from enum import Enum
from colorlog import error, warning as warn, info, debug
from typing import List, Set
from yangson.instance import \
InstanceNode, \
NonexistentInstance, \
ArrayValue, \
ObjectValue, \
InstanceSelector, \
InstanceIdentifier, \
InstanceRoute, \
MemberName, \
EntryIndex, \
from yangson.instance import (
from .helpers import DataHelpers
from .helpers import DataHelpers, PathFormat, ErrorHelpers
epretty = ErrorHelpers.epretty
class Action(Enum):
......@@ -105,10 +107,14 @@ class DataRuleTree:
self.root = [] # type: List[RuleTreeNode]
# Access to datastore needed for parsing IIs
def create_rule_tree(self, ds, rule_lists: List[NacmRuleList]):
def create_rule_tree(self, rule_lists: List[NacmRuleList]):
for rl in rule_lists:
for rule in filter(lambda r: r.type == NacmRuleType.NACM_RULE_DATA, rl.rules):
ii = ds.parse_ii(rule.type_data.path, PathFormat.XPATH)
ii = DataHelpers.parse_ii(rule.type_data.path, PathFormat.XPATH)
except NonexistentSchemaNode as e:
error(epretty(e, __name__))
ii = []
nl = self.root
node_match_prev = None
for isel in ii:
......@@ -307,7 +313,7 @@ class UserNacm:
user_groups_names = list(map(lambda x: x.name, user_groups))
self.rule_lists = list(filter(lambda x: (set(user_groups_names) & set(x.groups)), config.rule_lists))
self.rule_tree.create_rule_tree(self.data, self.rule_lists)
# self.rule_tree.print_rule_tree()
# No need to hold lock anymore
......@@ -83,7 +83,8 @@ class ZoneStateHandler(StateNodeHandlerBase):
self.schema_node = data_model.get_data_node(self.sch_pth)
def update_node(self, node_ii: InstanceRoute, data_root: InstanceNode, with_container: bool) -> InstanceNode:
print("zone_state_handler, ii = {}".format(node_ii))
node_ii_str = sch_pth = "".join([str(seg) for seg in node_ii])
print("zone_state_handler, ii = {}".format(node_ii_str))
# Request status of specific zone
if len(node_ii) > 2:
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment