Commit 78340a58 authored by Pavel Spirek's avatar Pavel Spirek

Better error reporting, minor fixes

parent a4357ea7
PROJECT = jetconf PROJECT = jetconf
VERSION = 0.3.3 VERSION = 0.3.4
.PHONY = tags deps install-deps test .PHONY = tags deps install-deps test
tags: tags:
......
...@@ -679,7 +679,7 @@ class BaseDatastore: ...@@ -679,7 +679,7 @@ class BaseDatastore:
# Unlock datastore data # Unlock datastore data
def unlock_data(self): def unlock_data(self):
self._data_lock.release() self._data_lock.release()
debug_data("Released datastore lockfor user \"{}\"".format(self._lock_username)) debug_data("Released datastore lock for user \"{}\"".format(self._lock_username))
self._lock_username = None self._lock_username = None
# Load data from persistent storage # Load data from persistent storage
......
import sys
import logging import logging
from colorlog import debug, getLogger from colorlog import debug, getLogger
...@@ -97,12 +98,12 @@ class DateTimeHelpers: ...@@ -97,12 +98,12 @@ class DateTimeHelpers:
class ErrorHelpers: class ErrorHelpers:
@staticmethod @staticmethod
def epretty(e: BaseException, module_name: str=None) -> str: def epretty(e: BaseException) -> str:
err_str = e.__class__.__name__ + ": " + str(e) ex_type, ex_val, ex_tb = sys.exc_info()
if module_name is not None: line_no = ex_tb.tb_lineno
return "In module " + module_name + ": " + err_str filename = ex_tb.tb_frame.f_code.co_filename
else:
return err_str return "{}: {} (file {}, line {})".format(e.__class__.__name__, str(e), filename, line_no)
class LogHelpers: class LogHelpers:
......
...@@ -6,12 +6,12 @@ from collections import OrderedDict ...@@ -6,12 +6,12 @@ from collections import OrderedDict
from enum import Enum from enum import Enum
from colorlog import error, warning as warn, info from colorlog import error, warning as warn, info
from urllib.parse import parse_qs from urllib.parse import parse_qs
from datetime import datetime
from typing import Dict, List, Tuple, Any, Optional, Callable from typing import Dict, List, Tuple, Any, Optional, Callable
from yangson.exceptions import YangsonException, NonexistentSchemaNode, SchemaError, SemanticError from yangson.exceptions import YangsonException, NonexistentSchemaNode, SchemaError, SemanticError
from yangson.schemanode import ContainerNode, ListNode, GroupNode, LeafNode from yangson.schemanode import ContainerNode, ListNode, GroupNode, LeafNode
from yangson.instance import NonexistentInstance, InstanceValueError, RootNode from yangson.instance import NonexistentInstance, InstanceValueError, RootNode
from yangson.instvalue import ArrayValue
from . import config from . import config
from .helpers import CertHelpers, DateTimeHelpers, ErrorHelpers, LogHelpers, SSLCertT from .helpers import CertHelpers, DateTimeHelpers, ErrorHelpers, LogHelpers, SSLCertT
...@@ -202,15 +202,16 @@ class HttpHandlersImpl: ...@@ -202,15 +202,16 @@ class HttpHandlersImpl:
"unknown_request" "unknown_request"
) )
@staticmethod def _get_yl_date(self) -> str:
def _get_yl_date() -> str: yl_modules = self.ds.get_yl_data_root()["ietf-yang-library:modules-state"]["module"].value # type: ArrayValue
try: revision_val = None
yang_lib_date_ts = os.path.getmtime(os.path.join(config.CFG.glob["YANG_LIB_DIR"], "yang-library-data.json"))
yang_lib_date = datetime.fromtimestamp(yang_lib_date_ts).strftime("%Y-%m-%d") for module in yl_modules:
except OSError: if module["name"] == "ietf-yang-library":
yang_lib_date = None revision_val = module["revision"]
break
return yang_lib_date return revision_val
def get_api_root(self, headers: OrderedDict, data: Optional[str], client_cert: SSLCertT): def get_api_root(self, headers: OrderedDict, data: Optional[str], client_cert: SSLCertT):
# Top level api resource (appendix B.1.1) # Top level api resource (appendix B.1.1)
...@@ -372,7 +373,7 @@ class HttpHandlersImpl: ...@@ -372,7 +373,7 @@ class HttpHandlersImpl:
api_pth = headers[":path"][len(config.CFG.api_root_ops):].rstrip("/") api_pth = headers[":path"][len(config.CFG.api_root_ops):].rstrip("/")
op_name_fq = api_pth[1:].split("/", maxsplit=1)[0] op_name_fq = api_pth[1:].split("/", maxsplit=1)[0]
op_names_dict = dict(map(lambda n: (n[0], None), self.ds.handlers.op.handlers)) op_names_dict = dict(map(lambda n: (n, None), self.ds.handlers.op.handlers))
if api_pth == "": if api_pth == "":
# GET root # GET root
......
...@@ -39,13 +39,6 @@ class Jetconf: ...@@ -39,13 +39,6 @@ class Jetconf:
self.fl = -1 self.fl = -1
raise JetconfInitError("Jetconf already running (pidfile exists)") raise JetconfInitError("Jetconf already running (pidfile exists)")
# Set signal handlers
# def sig_exit_handler(signum, frame):
# self.exit_clean(0)
#
# signal.signal(signal.SIGTERM, sig_exit_handler)
# signal.signal(signal.SIGINT, sig_exit_handler)
# Import backend modules # Import backend modules
backend_package = self.config.glob["BACKEND_PACKAGE"] backend_package = self.config.glob["BACKEND_PACKAGE"]
try: try:
......
...@@ -290,6 +290,10 @@ class UserRuleSet: ...@@ -290,6 +290,10 @@ class UserRuleSet:
self.default_write = config.default_write self.default_write = config.default_write
self.default_exec = config.default_exec self.default_exec = config.default_exec
self.rule_lists = [] self.rule_lists = []
self.rule_tree = None
if not self.nacm_enabled:
return
user_groups = list(filter(lambda x: username in x.users, config.nacm_groups)) user_groups = list(filter(lambda x: username in x.users, config.nacm_groups))
user_groups_names = list(map(lambda x: x.name, user_groups)) user_groups_names = list(map(lambda x: x.name, user_groups))
......
...@@ -4,15 +4,15 @@ import ssl ...@@ -4,15 +4,15 @@ import ssl
from io import BytesIO from io import BytesIO
from collections import OrderedDict from collections import OrderedDict
from colorlog import error, warning as warn, info from colorlog import error, warning as warn, info
from typing import List, Tuple, Dict, Callable, Optional from typing import Dict, Optional
from h2.config import H2Configuration from h2.config import H2Configuration
from h2.connection import H2Connection from h2.connection import H2Connection
from h2.errors import ErrorCodes as H2ErrorCodes from h2.errors import ErrorCodes as H2ErrorCodes
from h2.exceptions import ProtocolError from h2.exceptions import ProtocolError
from h2.events import DataReceived, RequestReceived, RemoteSettingsChanged, StreamEnded, WindowUpdated from h2.events import DataReceived, RequestReceived, RemoteSettingsChanged, StreamEnded, WindowUpdated, ConnectionTerminated
from . import config, http_handlers as handlers from . import config
from .helpers import SSLCertT, LogHelpers from .helpers import SSLCertT, LogHelpers
from .data import BaseDatastore from .data import BaseDatastore
from .http_handlers import ( from .http_handlers import (
...@@ -44,6 +44,7 @@ class ResponseData: ...@@ -44,6 +44,7 @@ class ResponseData:
class H2Protocol(asyncio.Protocol): class H2Protocol(asyncio.Protocol):
HTTP_HANDLERS = None # type: HttpHandlersImpl HTTP_HANDLERS = None # type: HttpHandlersImpl
LOOP = None # type: asyncio.BaseEventLoop
def __init__(self): def __init__(self):
self.conn = H2Connection(H2Configuration(client_side=False, header_encoding="utf-8")) self.conn = H2Connection(H2Configuration(client_side=False, header_encoding="utf-8"))
...@@ -64,7 +65,7 @@ class H2Protocol(asyncio.Protocol): ...@@ -64,7 +65,7 @@ class H2Protocol(asyncio.Protocol):
if agreed_protocol is None: if agreed_protocol is None:
error("Connection error, client does not support HTTP/2") error("Connection error, client does not support HTTP/2")
transport.close() self.transport.close()
else: else:
self.conn.initiate_connection() self.conn.initiate_connection()
...@@ -87,6 +88,7 @@ class H2Protocol(asyncio.Protocol): ...@@ -87,6 +88,7 @@ class H2Protocol(asyncio.Protocol):
# Check if incoming data are not excessively large # Check if incoming data are not excessively large
if (stream_data.data.tell() + len(event.data)) < (config.CFG.http["UPLOAD_SIZE_LIMIT"] * 1048576): if (stream_data.data.tell() + len(event.data)) < (config.CFG.http["UPLOAD_SIZE_LIMIT"] * 1048576):
stream_data.data.write(event.data) stream_data.data.write(event.data)
self.conn.acknowledge_received_data(len(event.data), event.stream_id)
else: else:
stream_data.data_overflow = True stream_data.data_overflow = True
self.conn.reset_stream(event.stream_id, error_code=H2ErrorCodes.ENHANCE_YOUR_CALM) self.conn.reset_stream(event.stream_id, error_code=H2ErrorCodes.ENHANCE_YOUR_CALM)
...@@ -131,15 +133,12 @@ class H2Protocol(asyncio.Protocol): ...@@ -131,15 +133,12 @@ class H2Protocol(asyncio.Protocol):
self.conn.update_settings(changed_settings) self.conn.update_settings(changed_settings)
elif isinstance(event, WindowUpdated): elif isinstance(event, WindowUpdated):
try: try:
debug_srv( debug_srv("str {} nw={}".format(event.stream_id, self.conn.local_flow_control_window(event.stream_id)))
"str {} nw={}".format(event.stream_id, self.conn.local_flow_control_window(event.stream_id))
)
self.send_response_continue(event.stream_id) self.send_response_continue(event.stream_id)
except (ProtocolError, KeyError) as e: except (ProtocolError, KeyError) as e:
# debug_srv("wupdexception strid={}: {}".format(event.stream_id, str(e))) debug_srv("wupexception strid={}: {}".format(event.stream_id, str(e)))
pass elif isinstance(event, ConnectionTerminated):
# else: self.transport.close()
# print(type(event))
dts = self.conn.data_to_send() dts = self.conn.data_to_send()
if dts: if dts:
...@@ -153,6 +152,29 @@ class H2Protocol(asyncio.Protocol): ...@@ -153,6 +152,29 @@ class H2Protocol(asyncio.Protocol):
url_path = headers[":path"].split("?")[0].rstrip("/") url_path = headers[":path"].split("?")[0].rstrip("/")
method = headers[":method"] method = headers[":method"]
################
# if url_path == "/evtest":
# self.ev_stream_start_response(stream_id)
# i = 0
#
# def cb():
# self.ev_stream_send_data("ahoj\ncau\n1", stream_id)
#
# nonlocal i
# i += 1
# if i < 5:
# self.LOOP.call_later(1, cb)
# elif stream_id in self.conn.streams.keys():
# self.conn.end_stream(stream_id)
#
# dts = self.conn.data_to_send()
# if dts:
# self.transport.write(dts)
#
# cb()
# return
###############
if method == "HEAD": if method == "HEAD":
h = self.HTTP_HANDLERS.list.get("GET", url_path) h = self.HTTP_HANDLERS.list.get("GET", url_path)
else: else:
...@@ -223,6 +245,30 @@ class H2Protocol(asyncio.Protocol): ...@@ -223,6 +245,30 @@ class H2Protocol(asyncio.Protocol):
self.conn.send_data(stream_id, bytes(), end_stream=True) self.conn.send_data(stream_id, bytes(), end_stream=True)
del self.resp_stream_data[stream_id] del self.resp_stream_data[stream_id]
def ev_stream_start_response(self, stream_id: int):
resp_headers = (
(":status", "200"),
("Transfer-Encoding", "Chunked"),
("Content-Type", "text/event-stream"),
("Server", config.CFG.http["SERVER_NAME"]),
("Cache-Control", "No-Cache"),
("Access-Control-Allow-Origin", "*"),
("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE"),
("Access-Control-Allow-Headers", "Content-Type")
)
self.conn.send_headers(stream_id, resp_headers)
def ev_stream_send_data(self, data: str, stream_id: int):
if stream_id not in self.conn.streams.keys():
return
data_lines = data.splitlines()
data_lines_pfxed = list(map(lambda l: "data: " + l + "\n", data_lines))
data_to_send = "".join(data_lines_pfxed) + "\n"
self.conn.send_data(stream_id, data_to_send.encode(), end_stream=False)
class RestServer: class RestServer:
def __init__(self): def __init__(self):
...@@ -253,6 +299,8 @@ class RestServer: ...@@ -253,6 +299,8 @@ class RestServer:
) )
self.server = self.loop.run_until_complete(listener) self.server = self.loop.run_until_complete(listener)
H2Protocol.LOOP = self.loop
# Register HTTP handlers # Register HTTP handlers
@staticmethod @staticmethod
def register_api_handlers(datastore: BaseDatastore): def register_api_handlers(datastore: BaseDatastore):
......
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