Commit 1a41483a authored by Pavel Spirek's avatar Pavel Spirek

Another code refactor, version bump

parent 24596fb2
PROJECT = jetconf
VERSION = 0.3.1
VERSION = 0.3.2
.PHONY = tags deps install-deps test
tags:
......
This diff is collapsed.
from typing import Callable, Union
from yangson.schemanode import SchemaNode
from yangson.instance import InstanceRoute
from .journal import DataChange, RpcInfo
from .helpers import JsonNodeT
# ---------- Base classes for conf data handlers ----------
class ConfDataHandlerBase:
def __init__(self, ds: "BaseDatastore", sch_pth: str):
self.ds = ds
self.schema_path = sch_pth # type: str
self.schema_node = ds.get_schema_node(sch_pth) # type: SchemaNode
class ConfDataObjectHandler(ConfDataHandlerBase):
def create(self, ii: InstanceRoute, ch: DataChange):
pass
def replace(self, ii: InstanceRoute, ch: DataChange):
pass
def delete(self, ii: InstanceRoute, ch: DataChange):
pass
def __str__(self):
return self.__class__.__name__ + ": listening at " + self.schema_path
class ConfDataListHandler(ConfDataHandlerBase):
def create_item(self, ii: InstanceRoute, ch: DataChange):
pass
def replace_item(self, ii: InstanceRoute, ch: DataChange):
pass
def delete_item(self, ii: InstanceRoute, ch: DataChange):
pass
def create_list(self, ii: InstanceRoute, ch: DataChange):
pass
def replace_list(self, ii: InstanceRoute, ch: DataChange):
pass
def delete_list(self, ii: InstanceRoute, ch: DataChange):
pass
def __str__(self):
return self.__class__.__name__ + ": listening at " + self.schema_path
# ---------- Base classes for state data handlers ----------
class StateDataHandlerBase:
def __init__(self, datastore: "BaseDatastore", schema_path: str):
self.ds = datastore
self.data_model = datastore.get_dm()
self.sch_pth = schema_path
self.schema_node = self.data_model.get_data_node(self.sch_pth)
class StateDataContainerHandler(StateDataHandlerBase):
def generate_node(self, node_ii: InstanceRoute, username: str, staging: bool) -> JsonNodeT:
pass
class StateDataListHandler(StateDataHandlerBase):
def generate_list(self, node_ii: InstanceRoute, username: str, staging: bool) -> JsonNodeT:
pass
def generate_item(self, node_ii: InstanceRoute, username: str, staging: bool) -> JsonNodeT:
pass
# ---------- Types ----------
ConfDataHandler = Union[ConfDataObjectHandler, ConfDataListHandler]
StateDataHandler = Union[StateDataContainerHandler, StateDataListHandler]
OpHandler = Callable[[RpcInfo], JsonNodeT]
from typing import Dict, Callable
from typing import List, Dict, Tuple, Callable
from yangson.schemanode import SchemaNode
from yangson.schemadata import SchemaData
from yangson.instance import InstanceRoute
from yangson.typealiases import SchemaRoute
from .helpers import JsonNodeT
# ---------- Base classes for conf data handlers ----------
class ConfDataHandlerBase:
def __init__(self, ds: "BaseDatastore", sch_pth: str):
self.ds = ds
self.schema_path = sch_pth # type: str
self.schema_node = ds.get_schema_node(sch_pth) # type: SchemaNode
class ConfDataObjectHandler(ConfDataHandlerBase):
def create(self, ii: InstanceRoute, ch: "DataChange"):
pass
def replace(self, ii: InstanceRoute, ch: "DataChange"):
pass
def delete(self, ii: InstanceRoute, ch: "DataChange"):
pass
def __str__(self):
return self.__class__.__name__ + ": listening at " + self.schema_path
class ConfDataListHandler(ConfDataHandlerBase):
def create_item(self, ii: InstanceRoute, ch: "DataChange"):
pass
def replace_item(self, ii: InstanceRoute, ch: "DataChange"):
pass
def delete_item(self, ii: InstanceRoute, ch: "DataChange"):
pass
def create_list(self, ii: InstanceRoute, ch: "DataChange"):
pass
def replace_list(self, ii: InstanceRoute, ch: "DataChange"):
pass
def delete_list(self, ii: InstanceRoute, ch: "DataChange"):
pass
def __str__(self):
return self.__class__.__name__ + ": listening at " + self.schema_path
# ---------- Base classes for state data handlers ----------
class StateDataHandlerBase:
def __init__(self, datastore: "BaseDatastore", schema_path: str):
self.ds = datastore
self.data_model = datastore.get_dm()
self.sch_pth = schema_path
self.schema_node = self.data_model.get_data_node(self.sch_pth)
class StateDataContainerHandler(StateDataHandlerBase):
def generate_node(self, node_ii: InstanceRoute, username: str, staging: bool) -> JsonNodeT:
pass
class StateDataListHandler(StateDataHandlerBase):
def generate_list(self, node_ii: InstanceRoute, username: str, staging: bool) -> JsonNodeT:
pass
def generate_item(self, node_ii: InstanceRoute, username: str, staging: bool) -> JsonNodeT:
pass
from .handler_base import ConfDataHandlerBase, StateDataHandlerBase, ConfDataHandler, StateDataHandler, OpHandler
# ---------- Handler lists ----------
......@@ -85,22 +17,22 @@ class ConfDataHandlerList:
self.handlers[sch_node_id] = handler
self.handlers_pth[handler.schema_path] = handler
def get_handler(self, sch_node_id: int) -> ConfDataHandlerBase:
def get_handler(self, sch_node_id: int) -> ConfDataHandler:
return self.handlers.get(sch_node_id)
def get_handler_by_pth(self, sch_pth: str) -> ConfDataHandlerBase:
def get_handler_by_pth(self, sch_pth: str) -> ConfDataHandler:
return self.handlers_pth.get(sch_pth)
class StateDataHandlerList:
def __init__(self):
self.handlers = []
self.handlers = [] # type: List[Tuple[SchemaRoute, StateDataHandlerBase]]
def register(self, handler: "StateDataHandlerBase"):
def register(self, handler: StateDataHandlerBase):
saddr = SchemaData.path2route(handler.sch_pth)
self.handlers.append((saddr, handler))
def get_handler(self, sch_pth: str, allow_superior: bool = True) -> Callable:
def get_handler(self, sch_pth: str, allow_superior: bool = True) -> StateDataHandler:
saddr = SchemaData.path2route(sch_pth)
if allow_superior:
while saddr:
......@@ -118,20 +50,10 @@ class StateDataHandlerList:
class OpHandlerList:
def __init__(self):
self.handlers = {} # type: Dict[str, Callable]
self.default_handler = None # type: Callable
self.handlers = {} # type: Dict[str, OpHandler]
def register(self, handler: Callable, op_name: str):
self.handlers[op_name] = handler
def register_default(self, handler: Callable):
self.default_handler = handler
def get_handler(self, op_name: str) -> Callable:
return self.handlers.get(op_name, self.default_handler)
# ---------- Handler list globals ----------
OP_HANDLERS = OpHandlerList()
STATE_DATA_HANDLES = StateDataHandlerList()
CONF_DATA_HANDLES = ConfDataHandlerList()
def get_handler(self, op_name: str) -> OpHandler:
return self.handlers.get(op_name)
This diff is collapsed.
......@@ -111,7 +111,6 @@ class Jetconf:
# Create HTTP server
self.rest_srv = RestServer()
self.rest_srv.register_api_handlers(datastore)
self.rest_srv.register_static_handlers()
def run(self):
# Set signal handlers
......
from enum import Enum
from colorlog import error, info
from typing import List, Dict
from yangson.enumerations import ContentType, ValidationScope
from yangson.schemanode import SchemaError, SemanticError
from yangson.instvalue import ObjectValue
from yangson.instance import InstanceNode
from . import config
from .helpers import ErrorHelpers, LogHelpers, PathFormat, JsonNodeT
from .errors import ConfHandlerFailedError
epretty = ErrorHelpers.epretty
debug_journal = LogHelpers.create_module_dbg_logger(__name__)
class ChangeType(Enum):
CREATE = 0,
REPLACE = 1,
DELETE = 2
class RpcInfo:
def __init__(self):
self.username = None # type: str
self.path = None # type: str
self.qs = None # type: Dict[str, List[str]]
self.path_format = PathFormat.URL # type: PathFormat
self.skip_nacm_check = False # type: bool
self.op_name = None # type: str
self.op_input_args = None # type: ObjectValue
class DataChange:
def __init__(self, change_type: ChangeType, rpc_info: RpcInfo, input_data: JsonNodeT, root_after_change: InstanceNode, nacm_modified: bool):
self.change_type = change_type
self.rpc_info = rpc_info
self.input_data = input_data
self.root_after_change = root_after_change
self.nacm_modified = nacm_modified
class UsrChangeJournal:
def __init__(self, root_origin: InstanceNode):
self._root_origin = root_origin
self._journal = [] # type: List[DataChange]
def get_root_head(self) -> InstanceNode:
if len(self._journal) > 0:
return self._journal[-1].root_after_change
else:
return self._root_origin
def get_root_origin(self) -> InstanceNode:
return self._root_origin
def add(self, change: DataChange):
self._journal.append(change)
def list(self) -> JsonNodeT:
changes_info = []
for ch in self._journal:
changes_info.append([ch.change_type.name, ch.rpc_info.path])
return changes_info
def commit(self, ds: "BaseDatastore") -> bool:
nacm_modified = False
if len(self._journal) == 0:
return False
if hash(ds.get_data_root()) == hash(self._root_origin):
info("Commiting new configuration (swapping roots)")
# Set new root
nr = self.get_root_head()
for change in self._journal:
nacm_modified = nacm_modified or change.nacm_modified
else:
info("Commiting new configuration (re-applying changes)")
nr = ds.get_data_root()
for change in self._journal:
nacm_modified = nacm_modified or change.nacm_modified
if change.change_type == ChangeType.CREATE:
nr = ds.create_node_rpc(nr, change.rpc_info, change.input_data)[0]
elif change.change_type == ChangeType.REPLACE:
nr = ds.update_node_rpc(nr, change.rpc_info, change.input_data)[0]
elif change.change_type == ChangeType.DELETE:
nr = ds.delete_node_rpc(nr, change.rpc_info)[0]
try:
# Validate syntax and semantics of new data
if config.CFG.glob["VALIDATE_TRANSACTIONS"] is True:
nr.validate(ValidationScope.all, ContentType.config)
except (SchemaError, SemanticError) as e:
error("Data validation error:")
error(epretty(e))
raise e
# Set new data root
ds.set_data_root(nr)
# Update NACM if NACM data has been affected by any edit
if nacm_modified and ds.nacm is not None:
ds.nacm.update()
# Call commit begin hook
begin_hook_failed = False
try:
ds.commit_begin_callback()
except Exception as e:
error("Exception occured in commit_begin handler: {}".format(epretty(e)))
begin_hook_failed = True
# Run schema node handlers
conf_handler_failed = False
if not begin_hook_failed:
try:
for change in self._journal:
ii = ds.parse_ii(change.rpc_info.path, change.rpc_info.path_format)
ds.run_conf_edit_handler(ii, change)
except Exception as e:
error("Exception occured in edit handler: {}".format(epretty(e)))
conf_handler_failed = True
# Call commit end hook
end_hook_failed = False
end_hook_abort_failed = False
if not (begin_hook_failed or conf_handler_failed):
try:
ds.commit_end_callback(failed=False)
except Exception as e:
error("Exception occured in commit_end handler: {}".format(epretty(e)))
end_hook_failed = True
if begin_hook_failed or conf_handler_failed or end_hook_failed:
try:
# Call commit_end callback again with "failed" argument set to True
ds.commit_end_callback(failed=True)
except Exception as e:
error("Exception occured in commit_end handler (abort): {}".format(epretty(e)))
end_hook_abort_failed = True
# Return to previous version of data and raise an exception if something went wrong
if begin_hook_failed or conf_handler_failed or end_hook_failed or end_hook_abort_failed:
ds.data_root_rollback(history_steps=1, store_current=False)
# Update NACM again after rollback
if nacm_modified and ds.nacm is not None:
ds.nacm.update()
raise ConfHandlerFailedError("(see logged)")
return True
from . import config
from .helpers import JsonNodeT
from .handler_list import OP_HANDLERS
from .data import BaseDatastore, RpcInfo, StagingDataException
......@@ -85,9 +84,9 @@ class OpHandlersContainer:
def register_op_handlers(ds: BaseDatastore):
op_handlers_obj = OpHandlersContainer(ds)
OP_HANDLERS.register(op_handlers_obj.jetconf_conf_status, "jetconf:conf-status")
OP_HANDLERS.register(op_handlers_obj.jetconf_conf_reset, "jetconf:conf-reset")
OP_HANDLERS.register(op_handlers_obj.jetconf_conf_commit, "jetconf:conf-commit")
OP_HANDLERS.register(op_handlers_obj.jetconf_get_schema_digest, "jetconf:get-schema-digest")
OP_HANDLERS.register(op_handlers_obj.jetconf_get_list_length, "jetconf:get-list-length")
ds.handlers.op_obj = OpHandlersContainer(ds)
ds.handlers.op.register(ds.handlers.op_obj.jetconf_conf_status, "jetconf:conf-status")
ds.handlers.op.register(ds.handlers.op_obj.jetconf_conf_reset, "jetconf:conf-reset")
ds.handlers.op.register(ds.handlers.op_obj.jetconf_conf_commit, "jetconf:conf-commit")
ds.handlers.op.register(ds.handlers.op_obj.jetconf_get_schema_digest, "jetconf:get-schema-digest")
ds.handlers.op.register(ds.handlers.op_obj.jetconf_get_list_length, "jetconf:get-list-length")
......@@ -16,6 +16,7 @@ from . import config, http_handlers as handlers
from .helpers import SSLCertT, LogHelpers
from .data import BaseDatastore
from .http_handlers import (
HttpHandlersImpl,
HttpResponse,
HttpStatus,
RestconfErrType,
......@@ -25,8 +26,6 @@ from .http_handlers import (
)
HandlerConditionT = Callable[[str, str], bool] # Function(method, path) -> bool
HttpHandlerT = Callable[[OrderedDict, Optional[str], SSLCertT], handlers.HttpResponse]
debug_srv = LogHelpers.create_module_dbg_logger(__name__)
......@@ -43,27 +42,8 @@ class ResponseData:
self.bytes_sent = 0
class HttpHandlerList:
def __init__(self):
self.handlers = [] # type: List[Tuple[HandlerConditionT, HttpHandlerT]]
self.default_handler = None # type: HttpHandlerT
def register(self, condition: HandlerConditionT, handler: HttpHandlerT):
self.handlers.append((condition, handler))
def register_default(self, handler: HttpHandlerT):
self.default_handler = handler
def get_handler(self, method: str, path: str) -> HttpHandlerT:
for h in self.handlers:
if h[0](method, path):
return h[1]
return self.default_handler
class H2Protocol(asyncio.Protocol):
HTTP_HANDLERS = None # type: HttpHandlerList
HTTP_HANDLERS = None # type: HttpHandlersImpl
def __init__(self):
self.conn = H2Connection(H2Configuration(client_side=False, header_encoding="utf-8"))
......@@ -174,9 +154,9 @@ class H2Protocol(asyncio.Protocol):
method = headers[":method"]
if method == "HEAD":
h = self.HTTP_HANDLERS.get_handler("GET", url_path)
h = self.HTTP_HANDLERS.list.get("GET", url_path)
else:
h = self.HTTP_HANDLERS.get_handler(method, url_path)
h = self.HTTP_HANDLERS.list.get(method, url_path)
if not h:
self.send_response(
......@@ -272,47 +252,11 @@ class RestServer:
ssl=ssl_context
)
self.server = self.loop.run_until_complete(listener)
# Set H2Protocol class variables
H2Protocol.HTTP_HANDLERS = HttpHandlerList()
# Register HTTP handlers
@staticmethod
def register_api_handlers(datastore: BaseDatastore):
api_get_root = handlers.api_root_handler
api_get_ylv = handlers.api_ylv_handler
api_get = handlers.create_get_api(datastore)
api_get_run = handlers.create_get_running_api(datastore)
api_post = handlers.create_post_api(datastore)
api_put = handlers.create_put_api(datastore)
api_delete = handlers.create_api_delete(datastore)
api_get_op = handlers.create_api_op(datastore)
api_op = handlers.create_api_op(datastore)
api_root = config.CFG.http["API_ROOT"]
api_root_data = config.CFG.api_root_data
api_root_running_data = config.CFG.api_root_running_data
api_root_ylv = config.CFG.api_root_ylv
api_root_ops = config.CFG.api_root_ops
H2Protocol.HTTP_HANDLERS.register(lambda m, p: (m == "GET") and (p.startswith(api_root_data)), api_get)
H2Protocol.HTTP_HANDLERS.register(lambda m, p: (m == "GET") and (p.startswith(api_root_running_data)), api_get_run)
H2Protocol.HTTP_HANDLERS.register(lambda m, p: (m == "GET") and (p == api_root_ylv), api_get_ylv)
H2Protocol.HTTP_HANDLERS.register(lambda m, p: (m == "GET") and (p == api_root), api_get_root)
H2Protocol.HTTP_HANDLERS.register(lambda m, p: (m == "POST") and (p.startswith(api_root_data)), api_post)
H2Protocol.HTTP_HANDLERS.register(lambda m, p: (m == "PUT") and (p.startswith(api_root_data)), api_put)
H2Protocol.HTTP_HANDLERS.register(lambda m, p: (m == "DELETE") and (p.startswith(api_root_data)), api_delete)
H2Protocol.HTTP_HANDLERS.register(lambda m, p: (m == "GET") and (p.startswith(api_root_ops)), api_get_op)
H2Protocol.HTTP_HANDLERS.register(lambda m, p: (m == "POST") and (p.startswith(api_root_ops)), api_op)
H2Protocol.HTTP_HANDLERS.register(lambda m, p: m == "OPTIONS", handlers.options_api)
# Register static HTTP handlers
@staticmethod
def register_static_handlers():
api_root = config.CFG.http["API_ROOT"]
H2Protocol.HTTP_HANDLERS.register(lambda m, p: (m == "GET") and not (p.startswith(api_root)), handlers.get_file)
H2Protocol.HTTP_HANDLERS.register_default(handlers.unknown_req_handler)
H2Protocol.HTTP_HANDLERS = HttpHandlersImpl(datastore)
# Start server event loop (this will block until shutdown)
def run(self):
......
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