X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=RO%2Fosm_ro%2Fopenmanod.py;fp=RO%2Fosm_ro%2Fopenmanod.py;h=cdf451a198b895c698f637ed1c273f5a417a82aa;hb=7d782eff123e5b44d41437377ccca66ad1e8b21b;hp=0000000000000000000000000000000000000000;hpb=5db670b68349fd1f00a5efc8c0ccd0ef9d073dca;p=osm%2FRO.git diff --git a/RO/osm_ro/openmanod.py b/RO/osm_ro/openmanod.py new file mode 100755 index 00000000..cdf451a1 --- /dev/null +++ b/RO/osm_ro/openmanod.py @@ -0,0 +1,379 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +## +# Copyright 2015 Telefonica Investigacion y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## + +""" +openmano server. +Main program that implements a reference NFVO (Network Functions Virtualisation Orchestrator). +It interfaces with an NFV VIM through its API and offers a northbound interface, based on REST (openmano API), +where NFV services are offered including the creation and deletion of VNF templates, VNF instances, +network service templates and network service instances. + +It loads the configuration file and launches the http_server thread that will listen requests using openmano API. +""" + +import time +import sys +import getopt +import yaml +from os import environ, path as os_path +from jsonschema import validate as js_v, exceptions as js_e +import logging +import logging.handlers as log_handlers +import socket + +from yaml import MarkedYAMLError + +from osm_ro import httpserver, nfvo, nfvo_db +from osm_ro.openmano_schemas import config_schema +from osm_ro.db_base import db_base_Exception +from osm_ro.wim.engine import WimEngine +from osm_ro.wim.persistence import WimPersistence +import osm_ro + +__author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes" +__date__ = "$26-aug-2014 11:09:29$" +__version__ = "6.0.3.post5" +version_date = "Oct 2019" +database_version = 39 # expected database schema version + +global global_config +global logger + + +class LoadConfigurationException(Exception): + pass + + +def load_configuration(configuration_file): + default_tokens = {'http_port': 9090, + 'http_host': 'localhost', + 'http_console_proxy': True, + 'http_console_host': None, + 'log_level': 'DEBUG', + 'log_socket_port': 9022, + 'auto_push_VNF_to_VIMs': True, + 'db_host': 'localhost', + 'db_ovim_host': 'localhost' + } + try: + # Check config file exists + with open(configuration_file, 'r') as f: + config_str = f.read() + # Parse configuration file + config = yaml.load(config_str, Loader=yaml.SafeLoader) + # Validate configuration file with the config_schema + js_v(config, config_schema) + + # Add default values tokens + for k, v in default_tokens.items(): + if k not in config: + config[k] = v + return config + + except yaml.YAMLError as e: + error_pos = "" + if isinstance(e, MarkedYAMLError): + mark = e.problem_mark + error_pos = " at line:{} column:{}".format(mark.line + 1, mark.column + 1) + raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}: {message}".format( + file=configuration_file, pos=error_pos, message=e)) + except js_e.ValidationError as e: + error_pos = "" + if e.path: + error_pos = " at '" + ":".join(map(str, e.path)) + "'" + raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format( + file=configuration_file, pos=error_pos, message=e)) + except Exception as e: + raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format( + file=configuration_file, message=e)) + + +def console_port_iterator(): + """ + this iterator deals with the http_console_ports + returning the ports one by one + """ + index = 0 + while index < len(global_config["http_console_ports"]): + port = global_config["http_console_ports"][index] + if type(port) is int: + yield port + else: # this is dictionary with from to keys + port2 = port["from"] + while port2 <= port["to"]: + yield port2 + port2 += 1 + index += 1 + + +def usage(): + print("Usage: ", sys.argv[0], "[options]") + print(" -v|--version: prints current version") + print(" -c|--config [configuration_file]: loads the configuration file (default: openmanod.cfg)") + print(" -h|--help: shows this help") + print( + " -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)") + print( + " -P|--adminport [port_number]: changes admin port number and overrides the port number in the configuration file (default: 9095)") + print(" --log-socket-host HOST: send logs to this host") + print(" --log-socket-port PORT: send logs using this port (default: 9022)") + print(" --log-file FILE: send logs to this file") + print( + " --create-tenant NAME: Try to creates this tenant name before starting, ignoring any errors as e.g. conflict") + return + + +def set_logging_file(log_file): + try: + file_handler = logging.handlers.RotatingFileHandler(log_file, maxBytes=100e6, backupCount=9, delay=0) + file_handler.setFormatter(log_formatter_simple) + logger.addHandler(file_handler) + # remove initial stream handler + logging.root.removeHandler(logging.root.handlers[0]) + print ("logging on '{}'".format(log_file)) + except IOError as e: + raise LoadConfigurationException( + "Cannot open logging file '{}': {}. Check folder exist and permissions".format(log_file, e)) + + +if __name__ == "__main__": + # env2config contains environ variable names and the correspondence with configuration file openmanod.cfg keys. + # If this environ is defined, this value is taken instead of the one at at configuration file + env2config = { + 'RO_DB_HOST': 'db_host', + 'RO_DB_NAME': 'db_name', + 'RO_DB_USER': 'db_user', + 'RO_DB_PASSWORD': 'db_passwd', + 'RO_DB_OVIM_HOST': 'db_ovim_host', + 'RO_DB_OVIM_NAME': 'db_ovim_name', + 'RO_DB_OVIM_USER': 'db_ovim_user', + 'RO_DB_OVIM_PASSWORD': 'db_ovim_passwd', + 'RO_LOG_LEVEL': 'log_level', + 'RO_LOG_FILE': 'log_file', + } + # Configure logging step 1 + hostname = socket.gethostname() + log_formatter_str = '%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s severity:%(levelname)s logger:%(name)s log:%(message)s' + log_formatter_complete = logging.Formatter(log_formatter_str.format(host=hostname), datefmt='%Y-%m-%dT%H:%M:%S') + log_format_simple = "%(asctime)s %(levelname)s %(name)s %(thread)d %(filename)s:%(lineno)s %(message)s" + log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S') + logging.basicConfig(format=log_format_simple, level=logging.DEBUG) + logger = logging.getLogger('openmano') + logger.setLevel(logging.DEBUG) + socket_handler = None + # Read parameters and configuration file + httpthread = None + try: + # load parameters and configuration + opts, args = getopt.getopt(sys.argv[1:], "hvc:V:p:P:", + ["config=", "help", "version", "port=", "vnf-repository=", "adminport=", + "log-socket-host=", "log-socket-port=", "log-file=", "create-tenant="]) + port = None + port_admin = None + config_file = 'openmanod.cfg' + vnf_repository = None + log_file = None + log_socket_host = None + log_socket_port = None + create_tenant = None + + for o, a in opts: + if o in ("-v", "--version"): + print ("openmanod version " + __version__ + ' ' + version_date) + print ("(c) Copyright Telefonica") + sys.exit() + elif o in ("-h", "--help"): + usage() + sys.exit() + elif o in ("-V", "--vnf-repository"): + vnf_repository = a + elif o in ("-c", "--config"): + config_file = a + elif o in ("-p", "--port"): + port = a + elif o in ("-P", "--adminport"): + port_admin = a + elif o == "--log-socket-port": + log_socket_port = a + elif o == "--log-socket-host": + log_socket_host = a + elif o == "--log-file": + log_file = a + elif o == "--create-tenant": + create_tenant = a + else: + assert False, "Unhandled option" + if log_file: + set_logging_file(log_file) + global_config = load_configuration(config_file) + global_config["version"] = __version__ + global_config["version_date"] = version_date + # Override parameters obtained by command line on ENV + if port: + global_config['http_port'] = port + if port_admin: + global_config['http_admin_port'] = port_admin + if log_socket_host: + global_config['log_socket_host'] = log_socket_host + if log_socket_port: + global_config['log_socket_port'] = log_socket_port + + # override with ENV + for env_k, env_v in environ.items(): + try: + if not env_k.startswith("RO_") or env_k not in env2config or not env_v: + continue + global_config[env2config[env_k]] = env_v + if env_k.endswith("PORT"): # convert to int, skip if not possible + global_config[env2config[env_k]] = int(env_v) + except Exception as e: + logger.warn("skipping environ '{}={}' because exception '{}'".format(env_k, env_v, e)) + + global_config["console_port_iterator"] = console_port_iterator + global_config["console_thread"] = {} + global_config["console_ports"] = {} + if not global_config["http_console_host"]: + global_config["http_console_host"] = global_config["http_host"] + if global_config["http_host"] == "0.0.0.0": + global_config["http_console_host"] = socket.gethostname() + + # Configure logging STEP 2 + if "log_host" in global_config: + socket_handler = log_handlers.SocketHandler(global_config["log_socket_host"], + global_config["log_socket_port"]) + socket_handler.setFormatter(log_formatter_complete) + if global_config.get("log_socket_level") \ + and global_config["log_socket_level"] != global_config["log_level"]: + socket_handler.setLevel(global_config["log_socket_level"]) + logger.addHandler(socket_handler) + + if log_file: + global_config['log_file'] = log_file + elif global_config.get('log_file'): + set_logging_file(global_config['log_file']) + + logger.setLevel(getattr(logging, global_config['log_level'])) + logger.critical("Starting openmano server version: '%s %s' command: '%s'", + __version__, version_date, " ".join(sys.argv)) + + for log_module in ("nfvo", "http", "vim", "wim", "db", "console", "ovim"): + log_level_module = "log_level_" + log_module + log_file_module = "log_file_" + log_module + logger_module = logging.getLogger('openmano.' + log_module) + if log_level_module in global_config: + logger_module.setLevel(global_config[log_level_module]) + if log_file_module in global_config: + try: + file_handler = logging.handlers.RotatingFileHandler(global_config[log_file_module], + maxBytes=100e6, backupCount=9, delay=0) + file_handler.setFormatter(log_formatter_simple) + logger_module.addHandler(file_handler) + except IOError as e: + raise LoadConfigurationException( + "Cannot open logging file '{}': {}. Check folder exist and permissions".format( + global_config[log_file_module], str(e))) + global_config["logger_" + log_module] = logger_module + + # Initialize DB connection + mydb = nfvo_db.nfvo_db() + mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], + global_config['db_name']) + db_path = osm_ro.__path__[0] + "/database_utils" + if not os_path.exists(db_path + "/migrate_mano_db.sh"): + db_path = osm_ro.__path__[0] + "/../database_utils" + try: + r = mydb.get_db_version() + if r[0] != database_version: + logger.critical("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'" + " with '{db_path}/migrate_mano_db.sh {target}'".format(current=r[0], + target=database_version, + db_path=db_path)) + exit(-1) + except db_base_Exception as e: + logger.critical("DATABASE is not valid. If you think it is corrupted, you can init it with" + " '{db_path}/init_mano_db.sh' script".format(db_path=db_path)) + exit(-1) + + nfvo.global_config = global_config + if create_tenant: + try: + nfvo.new_tenant(mydb, {"name": create_tenant}) + except Exception as e: + if isinstance(e, nfvo.NfvoException) and e.http_code == 409: + pass # if tenant exist (NfvoException error 409), ignore + else: # otherwise print and error and continue + logger.error("Cannot create tenant '{}': {}".format(create_tenant, e)) + + # WIM module + wim_persistence = WimPersistence(mydb) + wim_engine = WimEngine(wim_persistence) + # --- + nfvo.start_service(mydb, wim_persistence, wim_engine) + + httpthread = httpserver.httpserver( + mydb, False, + global_config['http_host'], global_config['http_port'], + wim_persistence, wim_engine + ) + + httpthread.start() + if 'http_admin_port' in global_config: + httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], + global_config['http_admin_port']) + httpthreadadmin.start() + time.sleep(1) + logger.info('Waiting for http clients') + print('Waiting for http clients') + print('openmanod ready') + print('====================') + time.sleep(20) + sys.stdout.flush() + + # TODO: Interactive console must be implemented here instead of join or sleep + + # httpthread.join() + # if 'http_admin_port' in global_config: + # httpthreadadmin.join() + while True: + time.sleep(86400) + + except KeyboardInterrupt as e: + logger.info(str(e)) + except SystemExit: + pass + except getopt.GetoptError as e: + logger.critical(str(e)) # will print something like "option -a not recognized" + exit(-1) + except LoadConfigurationException as e: + logger.critical(str(e)) + exit(-1) + except db_base_Exception as e: + logger.critical(str(e)) + exit(-1) + except nfvo.NfvoException as e: + logger.critical(str(e), exc_info=True) + exit(-1) + nfvo.stop_service() + if httpthread: + httpthread.join(1)