Support for schema node callbacks

parent 6e9f3f34
Pipeline #161 skipped
......@@ -8,27 +8,41 @@ from . import op_handlers
from .rest_server import RestServer
from .config import load_config, print_config
from .nacm import NacmConfig
from .data import JsonDatastore
from .data import JsonDatastore, BaseDataListener, SchemaNode, InstanceIdentifier
from .helpers import DataHelpers
from .handler_list import OP_HANDLERS
class MyInfoDataListener(BaseDataListener):
def process(self, sn: SchemaNode, ii: InstanceIdentifier):
print("Change at sn \"{}\", dn \"{}\"".format(sn.name, ii))
def main():
# Load configuration
load_config("jetconf/config.yaml")
print_config()
# Load data model
datamodel = DataHelpers.load_data_model("data/", "data/yang-library-data.json")
# NACM init
nacm_data = JsonDatastore("./data", "./data/yang-library-data.json", "NACM data")
nacm_data = JsonDatastore(datamodel, "NACM data")
nacm_data.load("jetconf/example-data-nacm.json")
nacmc = NacmConfig(nacm_data)
# Datastore init
ex_datastore = JsonDatastore("./data", "./data/yang-library-data.json", "DNS data")
ex_datastore = JsonDatastore(datamodel, "DNS data")
ex_datastore.load("jetconf/example-data.json")
ex_datastore.register_nacm(nacmc)
nacmc.set_ds(ex_datastore)
# Register schema listeners
zone_listener1 = MyInfoDataListener(ex_datastore)
zone_listener1.add_schema_node("/dns-server:dns-server/zones/zone")
zone_listener1.add_schema_node("/ietf-netconf-acm:nacm/rule-list/rule")
# Register op handlers
OP_HANDLERS.register_handler("generate-key", op_handlers.sign_op_handler)
......@@ -45,9 +59,9 @@ if __name__ == "__main__":
opts, args = (None, None)
colorlog.basicConfig(
format="%(asctime)s %(log_color)s%(levelname)-8s%(reset)s %(message)s",
level=logging.INFO,
stream=sys.stdout
format="%(asctime)s %(log_color)s%(levelname)-8s%(reset)s %(message)s",
level=logging.INFO,
stream=sys.stdout
)
test_module = None
......@@ -68,8 +82,6 @@ if __name__ == "__main__":
tm.test()
except ImportError as e:
print(e.msg)
# except AttributeError:
# print("Module \"{}\" has no test() function".format(test_module))
else:
main()
......@@ -7,7 +7,9 @@ from threading import Lock
from enum import Enum
from colorlog import error, warning as warn, info, debug
from typing import List, Any, Dict, TypeVar, Tuple, Set
from pydispatch import dispatcher
from yangson.schema import SchemaRoute, SchemaNode, NonexistentSchemaNode
from yangson.context import Context
from yangson.datamodel import InstanceIdentifier, DataModel
from yangson.instance import \
......@@ -55,6 +57,23 @@ class NoHandlerForOpError(Exception):
return self.msg
class BaseDataListener:
def __init__(self, ds: "BaseDatastore"):
self._ds = ds
self.schema_paths = []
def add_schema_node(self, sch_pth: str):
sn = self._ds.get_schema_node(sch_pth)
self.schema_paths.append(sch_pth)
dispatcher.connect(self.process, str(id(sn)))
def process(self, sn: SchemaNode, ii: InstanceIdentifier):
raise NotImplementedError("Not implemented in base class")
def __str__(self):
return self.__class__.__name__ + ": listening at " + str(self.schema_paths)
class Rpc:
def __init__(self):
self.username = None # type: str
......@@ -67,18 +86,14 @@ class Rpc:
class BaseDatastore:
def __init__(self, module_dir: str, yang_library_file: str, name: str=""):
def __init__(self, dm: DataModel, name: str=""):
self.name = name
self.nacm = None # type: NacmConfig
self._data = None # type: Instance
self._dm = None # type: DataModel
self._dm = dm # type: DataModel
self._data_lock = Lock()
self._lock_username = None # type: str
with open(yang_library_file) as ylfile:
yl = ylfile.read()
self._dm = DataModel.from_yang_library(yl, module_dir)
# Register NACM module to datastore
def register_nacm(self, nacm_config: "NacmConfig"):
self.nacm = nacm_config
......@@ -87,6 +102,19 @@ class BaseDatastore:
def get_data_root(self) -> Instance:
return self._data
# Get schema node with particular schema address
def get_schema_node(self, sch_pth: str) -> SchemaNode:
schema_addr = Context.path2route(sch_pth)
sn = self._dm.schema.get_schema_descendant(schema_addr)
if sn is None:
raise NonexistentSchemaNode(sch_pth)
return sn
# Get schema node for particular data node
def get_schema_node_ii(self, ii: InstanceIdentifier) -> SchemaNode:
sn = self._dm.schema.get_data_descendant(ii)
return sn
# Parse Instance Identifier from string
def parse_ii(self, path: str, path_format: PathFormat) -> InstanceIdentifier:
if path_format == PathFormat.URL:
......@@ -96,6 +124,13 @@ class BaseDatastore:
return ii
# Notify data observers about change in datastore
def notify_edit(self, ii: InstanceIdentifier):
sn = self.get_schema_node_ii(ii)
while sn is not None:
dispatcher.send(str(id(sn)), **{'sn': sn, 'ii': ii})
sn = sn.parent
# Just get the node, do not evaluate NACM (for testing purposes)
def get_node(self, ii: InstanceIdentifier) -> Instance:
n = self._data.goto(ii)
......@@ -173,6 +208,8 @@ class BaseDatastore:
else:
raise InstanceTypeError(n, "Child node can only be appended to Object or Array")
self.notify_edit(ii)
def put_node_rpc(self, rpc: Rpc, value: Any):
ii = self.parse_ii(rpc.path, rpc.path_format)
n = self._data.goto(ii)
......@@ -189,6 +226,8 @@ class BaseDatastore:
new_n = n.update(new_value)
self._data = new_n.top()
self.notify_edit(ii)
def delete_node_rpc(self, rpc: Rpc, insert=None, point=None):
ii = self.parse_ii(rpc.path, rpc.path_format)
n = self._data.goto(ii)
......@@ -219,7 +258,7 @@ class BaseDatastore:
if op_handler is None:
raise NoHandlerForOpError()
schema_addr = Context.path2address(rpc.path)
schema_addr = Context.path2route(rpc.path)
sn = self._dm.schema.get_schema_descendant(schema_addr)
sn_input = sn.get_child("input")
if sn_input is not None:
......@@ -229,8 +268,6 @@ class BaseDatastore:
ret_data = op_handler(rpc.op_input_args)
return ret_data
# Locks datastore data
def lock_data(self, username: str = None, blocking: bool=True):
ret = self._data_lock.acquire(blocking=blocking, timeout=1)
......@@ -239,11 +276,11 @@ class BaseDatastore:
debug("Acquired lock in datastore \"{}\" for user \"{}\"".format(self.name, username))
else:
raise DataLockError(
"Failed to acquire lock in datastore \"{}\" for user \"{}\", already locked by \"{}\"".format(
self.name,
username,
self._lock_username
)
"Failed to acquire lock in datastore \"{}\" for user \"{}\", already locked by \"{}\"".format(
self.name,
username,
self._lock_username
)
)
# Unlocks datastore data
......@@ -275,7 +312,8 @@ class JsonDatastore(BaseDatastore):
def test():
data = JsonDatastore("./data", "./data/yang-library-data.json")
datamodel = DataHelpers.load_data_model("./data", "./data/yang-library-data.json")
data = JsonDatastore(datamodel)
data.load("jetconf/example-data.json")
rpc = Rpc()
......
......@@ -74,7 +74,7 @@
"path": "/dns-server:dns-server/zones/zone[domain='example.com']",
"access-operations": "create update delete",
"comment": "Users cannot write example.com.",
"action": "deny"
"action": "permit"
},
{
"name": "no-writes-on-example.com",
......
......@@ -2,6 +2,7 @@ from typing import Dict, Any
from datetime import datetime
from pytz import timezone
from yangson.instance import InstanceIdentifier, MemberName, EntryKeys
from yangson.datamodel import DataModel
class CertHelpers:
......@@ -32,6 +33,13 @@ class DataHelpers:
def path_first_ns(api_pth: str) -> str:
return api_pth[1:].split("/", maxsplit=1)[0].split(":", maxsplit=1)[0]
@staticmethod
def load_data_model(module_dir: str, yang_library_file: str) -> DataModel:
with open(yang_library_file) as ylfile:
yl = ylfile.read()
dm = DataModel.from_yang_library(yl, module_dir)
return dm
class DateTimeHelpers:
@staticmethod
......
......@@ -6,7 +6,6 @@ from enum import Enum
from colorlog import error, warning as warn, info, debug
from typing import List, Set
from yangson.schema import SchemaAddress
from yangson.instance import \
Instance, \
NonexistentInstance, \
......@@ -18,6 +17,8 @@ from yangson.instance import \
EntryIndex, \
EntryKeys
from .helpers import DataHelpers
class Action(Enum):
PERMIT = True
......@@ -423,12 +424,13 @@ class UserNacm:
def test():
nacm_data = JsonDatastore("./data", "./data/yang-library-data.json")
datamodel = DataHelpers.load_data_model("./data", "./data/yang-library-data.json")
nacm_data = JsonDatastore(datamodel)
nacm_data.load("jetconf/example-data-nacm.json")
nacm = NacmConfig(nacm_data)
data = JsonDatastore("./data", "./data/yang-library-data.json")
data = JsonDatastore(datamodel)
data.load("jetconf/example-data.json")
data.register_nacm(nacm)
......
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