__main__.py 5.74 KB
Newer Older
Pavel Spirek's avatar
Pavel Spirek committed
1
import os
2 3 4 5
import colorlog
import getopt
import logging
import sys
Pavel Spirek's avatar
Pavel Spirek committed
6
import signal
7

8
from importlib import import_module
Pavel Spirek's avatar
Pavel Spirek committed
9
from colorlog import error, info
10
from yaml.parser import ParserError
11

12
from yangson.enumerations import ContentType, ValidationScope
13
from yangson.exceptions import YangsonException
14
from yangson.schemanode import SchemaError, SemanticError
15

16
from . import op_internal
17
from .rest_server import RestServer
18
from .config import CONFIG_GLOBAL, CONFIG_NACM, load_config, print_config
19
from .helpers import DataHelpers, ErrorHelpers
20

21 22 23
# from jetconf_jukebox import usr_state_data_handlers, usr_conf_data_handlers, usr_op_handlers
# from jetconf_jukebox.usr_datastore import UserDatastore

24

25
def main():
26
    config_file = "config.yaml"
27 28 29 30 31

    # Parse command line arguments
    try:
        opts, args = getopt.getopt(sys.argv[1:], "c:")
    except getopt.GetoptError:
32
        print("Invalid argument detected. Possible options are: -c (config file)")
33 34 35
        sys.exit(1)

    for opt, arg in opts:
36
        if opt == "-c":
37 38
            config_file = arg

39
    # Load configuration
40 41 42 43 44 45 46 47
    try:
        load_config(config_file)
    except FileNotFoundError:
        print("Configuration file does not exist")
        sys.exit(1)
    except ParserError as e:
        print("Configuration syntax error: " + str(e))
        sys.exit(1)
48

49
    # Set logging level
50 51 52 53 54
    log_level = {
        "error": logging.ERROR,
        "warning": logging.WARNING,
        "info": logging.INFO,
        "debug": logging.INFO
55
    }.get(CONFIG_GLOBAL["LOG_LEVEL"], logging.INFO)
56 57
    logging.root.handlers.clear()

Pavel Spirek's avatar
Pavel Spirek committed
58
    # Daemonize
59
    if CONFIG_GLOBAL["LOGFILE"] not in ("-", "stdout"):
60
        # Setup basic logging
61 62 63
        logging.basicConfig(
            format="%(asctime)s %(levelname)-8s %(message)s",
            level=log_level,
64
            filename=CONFIG_GLOBAL["LOGFILE"]
Pavel Spirek's avatar
Pavel Spirek committed
65 66
        )

67
        # Go to background
Pavel Spirek's avatar
Pavel Spirek committed
68 69 70 71 72 73 74 75 76
        pid = os.fork()
        if pid != 0:
            sys.exit(0)
        os.setsid()
        os.umask(0)
        pid = os.fork()
        if pid != 0:
            sys.exit(0)

77
        # Close standard file descriptors
Pavel Spirek's avatar
Pavel Spirek committed
78 79 80 81 82 83
        os.close(sys.stdin.fileno())
        os.close(sys.stdout.fileno())
        os.close(sys.stderr.fileno())
        fd_null = os.open("/dev/null", os.O_RDWR)
        os.dup(fd_null)
        os.dup(fd_null)
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
    else:
        # Setup color logging
        log_formatter = colorlog.ColoredFormatter(
            "%(asctime)s %(log_color)s%(levelname)-8s%(reset)s %(message)s",
            datefmt=None,
            reset=True,
            log_colors={
                'DEBUG': 'cyan',
                'INFO': 'green',
                'WARNING': 'yellow',
                'ERROR': 'red',
                'CRITICAL': 'red',
            },
            secondary_log_colors={},
            style='%'
        )

        log_handler = colorlog.StreamHandler()
        log_handler.setFormatter(log_formatter)
        log_handler.stream = sys.stdout

        logger = colorlog.getLogger()
        logger.addHandler(log_handler)
        logger.setLevel(log_level)
Pavel Spirek's avatar
Pavel Spirek committed
108

Pavel Spirek's avatar
Pavel Spirek committed
109 110 111
    # Print configuration
    print_config()

Pavel Spirek's avatar
Pavel Spirek committed
112
    # Create pidfile
113
    fl = os.open(CONFIG_GLOBAL["PIDFILE"], os.O_WRONLY + os.O_CREAT, 0o666)
Pavel Spirek's avatar
Pavel Spirek committed
114 115 116 117 118 119 120 121
    try:
        os.lockf(fl, os.F_TLOCK, 0)
        os.write(fl, str(os.getpid()).encode())
        os.fsync(fl)
    except BlockingIOError:
        error("Jetconf daemon already running (pidfile exists). Exiting.")
        sys.exit(1)

Pavel Spirek's avatar
Pavel Spirek committed
122 123 124
    # Set signal handlers
    def sig_exit_handler(signum, frame):
        os.close(fl)
125
        os.unlink(CONFIG_GLOBAL["PIDFILE"])
Pavel Spirek's avatar
Pavel Spirek committed
126 127 128 129 130 131
        info("Exiting.")
        sys.exit(0)

    signal.signal(signal.SIGTERM, sig_exit_handler)
    signal.signal(signal.SIGINT, sig_exit_handler)

132 133 134 135 136 137 138 139 140 141 142 143
    # Import backend modules
    backend_package = CONFIG_GLOBAL["BACKEND_PACKAGE"]
    try:
        usr_state_data_handlers = import_module(backend_package + ".usr_state_data_handlers")
        usr_conf_data_handlers = import_module(backend_package + ".usr_conf_data_handlers")
        usr_op_handlers = import_module(backend_package + ".usr_op_handlers")
        usr_datastore = import_module(backend_package + ".usr_datastore")
    except ImportError as e:
        error(ErrorHelpers.epretty(e))
        error("Cannot import backend package \"{}\". Exiting.".format(backend_package))
        sys.exit(1)

144
    # Load data model
145
    yang_lib_file = os.path.join(CONFIG_GLOBAL["YANG_LIB_DIR"], "yang-library-data.json")
146 147
    datamodel = DataHelpers.load_data_model(
        CONFIG_GLOBAL["YANG_LIB_DIR"],
148
        yang_lib_file
149
    )
150

151
    # Datastore init
152
    datastore = usr_datastore.UserDatastore(datamodel, CONFIG_GLOBAL["DATA_JSON_FILE"], with_nacm=CONFIG_NACM["ENABLED"])
153 154
    try:
        datastore.load()
155
        datastore.load_yl_data(yang_lib_file)
156 157 158 159
    except (FileNotFoundError, YangsonException) as e:
        error("Could not load JSON datastore " + CONFIG_GLOBAL["DATA_JSON_FILE"])
        error(ErrorHelpers.epretty(e))
        sig_exit_handler(0, None)
160

161
    # Validate datastore on startup
162 163 164
    try:
        datastore.get_data_root().validate(ValidationScope.all, ContentType.config)
    except (SchemaError, SemanticError) as e:
165
        error("Initial validation of datastore failed")
166 167
        error(ErrorHelpers.epretty(e))
        sig_exit_handler(0, None)
168

169 170
    # Register handlers for configuration data
    usr_conf_data_handlers.register_conf_handlers(datastore)
171

172 173
    # Register handlers for state data
    usr_state_data_handlers.register_state_handlers(datastore)
174

175
    # Register handlers for operations
176 177
    op_internal.register_op_handlers(datastore)
    usr_op_handlers.register_op_handlers(datastore)
178

179 180
    # Create HTTP server
    rest_srv = RestServer()
181
    rest_srv.register_api_handlers(datastore)
182 183 184 185 186 187 188
    rest_srv.register_static_handlers()

    # Run HTTP server
    rest_srv.run()


if __name__ == "__main__":
189
    main()