blob: 061638915ef4b0d5099bc8343520e522339e6281 [file] [log] [blame]
tierno7edb6752016-03-21 17:37:52 +01001#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4##
5# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
6# This file is part of openmano
7# All Rights Reserved.
8#
9# Licensed under the Apache License, Version 2.0 (the "License"); you may
10# not use this file except in compliance with the License. You may obtain
11# a copy of the License at
12#
13# http://www.apache.org/licenses/LICENSE-2.0
14#
15# Unless required by applicable law or agreed to in writing, software
16# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
17# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
18# License for the specific language governing permissions and limitations
19# under the License.
20#
21# For those usages not covered by the Apache License, Version 2.0 please
22# contact with: nfvlabs@tid.es
23##
24
tierno99314902017-04-26 13:23:09 +020025"""
tierno7edb6752016-03-21 17:37:52 +010026openmano server.
27Main program that implements a reference NFVO (Network Functions Virtualisation Orchestrator).
28It interfaces with an NFV VIM through its API and offers a northbound interface, based on REST (openmano API),
29where NFV services are offered including the creation and deletion of VNF templates, VNF instances,
30network service templates and network service instances.
31
32It loads the configuration file and launches the http_server thread that will listen requests using openmano API.
tierno99314902017-04-26 13:23:09 +020033"""
tierno7edb6752016-03-21 17:37:52 +010034
tierno7edb6752016-03-21 17:37:52 +010035import time
tierno7edb6752016-03-21 17:37:52 +010036import sys
37import getopt
38import yaml
tiernod4d5e252018-01-23 17:07:46 +010039from os import getenv as os_getenv, path as os_path
tierno7edb6752016-03-21 17:37:52 +010040from jsonschema import validate as js_v, exceptions as js_e
tiernoae4a8d12016-07-08 12:30:39 +020041import logging
tiernof97fd272016-07-11 14:32:37 +020042import logging.handlers as log_handlers
tierno72f35a52016-07-15 13:18:30 +020043import socket
garciadeblas2c290ca2017-04-06 03:12:51 +020044from osm_ro import httpserver, nfvo, nfvo_db
45from osm_ro.openmano_schemas import config_schema
46from osm_ro.db_base import db_base_Exception
garciadeblas4b6216b2017-04-20 16:41:52 +020047import osm_ro
tierno7edb6752016-03-21 17:37:52 +010048
tierno11f81f62017-04-27 17:22:14 +020049__author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes"
50__date__ = "$26-aug-2014 11:09:29$"
tiernod4d5e252018-01-23 17:07:46 +010051__version__ = "0.5.51-r561"
tiernoad6bdd42018-01-10 10:43:46 +010052version_date = "Jan 2018"
gcalvinoe580c7d2017-09-22 14:09:51 +020053database_version = 27 # expected database schema version
54
tierno99314902017-04-26 13:23:09 +020055
tierno7edb6752016-03-21 17:37:52 +010056global global_config
tiernof97fd272016-07-11 14:32:37 +020057global logger
tiernoae4a8d12016-07-08 12:30:39 +020058
mirabal29356312017-07-27 12:21:22 +020059
tiernoae4a8d12016-07-08 12:30:39 +020060class LoadConfigurationException(Exception):
61 pass
tierno7edb6752016-03-21 17:37:52 +010062
tiernoad6bdd42018-01-10 10:43:46 +010063
tierno7edb6752016-03-21 17:37:52 +010064def load_configuration(configuration_file):
tiernoad6bdd42018-01-10 10:43:46 +010065 default_tokens = {'http_port': 9090,
66 'http_host': 'localhost',
tierno639520f2017-04-05 19:55:36 +020067 'http_console_proxy': True,
68 'http_console_host': None,
69 'log_level': 'DEBUG',
70 'log_socket_port': 9022,
71 'auto_push_VNF_to_VIMs': True,
72 'db_host': 'localhost',
73 'db_ovim_host': 'localhost'
74 }
tierno7edb6752016-03-21 17:37:52 +010075 try:
76 #Check config file exists
tierno72f35a52016-07-15 13:18:30 +020077 with open(configuration_file, 'r') as f:
78 config_str = f.read()
tierno7edb6752016-03-21 17:37:52 +010079 #Parse configuration file
tierno72f35a52016-07-15 13:18:30 +020080 config = yaml.load(config_str)
tierno7edb6752016-03-21 17:37:52 +010081 #Validate configuration file with the config_schema
tierno72f35a52016-07-15 13:18:30 +020082 js_v(config, config_schema)
tierno46df9672017-05-26 13:12:21 +020083
tierno72f35a52016-07-15 13:18:30 +020084 #Add default values tokens
tierno7edb6752016-03-21 17:37:52 +010085 for k,v in default_tokens.items():
tierno72f35a52016-07-15 13:18:30 +020086 if k not in config:
87 config[k]=v
88 return config
tierno46df9672017-05-26 13:12:21 +020089
tierno72f35a52016-07-15 13:18:30 +020090 except yaml.YAMLError as e:
91 error_pos = ""
92 if hasattr(e, 'problem_mark'):
93 mark = e.problem_mark
94 error_pos = " at line:{} column:{}".format(mark.line+1, mark.column+1)
tierno46df9672017-05-26 13:12:21 +020095 raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}: {message}".format(
96 file=configuration_file, pos=error_pos, message=e))
tierno72f35a52016-07-15 13:18:30 +020097 except js_e.ValidationError as e:
98 error_pos = ""
99 if e.path:
100 error_pos=" at '" + ":".join(map(str, e.path))+"'"
tierno46df9672017-05-26 13:12:21 +0200101 raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format(
102 file=configuration_file, pos=error_pos, message=e))
tierno72f35a52016-07-15 13:18:30 +0200103 except Exception as e:
tierno46df9672017-05-26 13:12:21 +0200104 raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format(
105 file=configuration_file, message=e))
106
tierno7edb6752016-03-21 17:37:52 +0100107
108def console_port_iterator():
109 '''this iterator deals with the http_console_ports
110 returning the ports one by one
111 '''
112 index = 0
113 while index < len(global_config["http_console_ports"]):
114 port = global_config["http_console_ports"][index]
tiernoae4a8d12016-07-08 12:30:39 +0200115 #print("ports -> ", port)
tierno7edb6752016-03-21 17:37:52 +0100116 if type(port) is int:
117 yield port
118 else: #this is dictionary with from to keys
119 port2 = port["from"]
tiernoae4a8d12016-07-08 12:30:39 +0200120 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100121 while port2 <= port["to"]:
tiernoae4a8d12016-07-08 12:30:39 +0200122 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100123 yield port2
124 port2 += 1
125 index += 1
tierno46df9672017-05-26 13:12:21 +0200126
127
tierno7edb6752016-03-21 17:37:52 +0100128def usage():
tiernoae4a8d12016-07-08 12:30:39 +0200129 print("Usage: ", sys.argv[0], "[options]")
tiernod4d5e252018-01-23 17:07:46 +0100130 print(" -v|--version: prints current version")
131 print(" -c|--config [configuration_file]: loads the configuration file (default: openmanod.cfg)")
132 print(" -h|--help: shows this help")
133 print(" -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)")
134 print(" -P|--adminport [port_number]: changes admin port number and overrides the port number in the configuration file (default: 9095)")
135 # print( " -V|--vnf-repository: changes the path of the vnf-repository and overrides the path in the configuration file")
136 print(" --log-socket-host HOST: send logs to this host")
137 print(" --log-socket-port PORT: send logs using this port (default: 9022)")
138 print(" --log-file FILE: send logs to this file")
tierno7edb6752016-03-21 17:37:52 +0100139 return
tierno46df9672017-05-26 13:12:21 +0200140
141
142def set_logging_file(log_file):
143 try:
144 file_handler = logging.handlers.RotatingFileHandler(log_file, maxBytes=100e6, backupCount=9, delay=0)
145 file_handler.setFormatter(log_formatter_simple)
146 logger.addHandler(file_handler)
147 # logger.debug("moving logs to '%s'", global_config["log_file"])
148 # remove initial stream handler
149 logging.root.removeHandler(logging.root.handlers[0])
150 print ("logging on '{}'".format(log_file))
151 except IOError as e:
152 raise LoadConfigurationException(
153 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(log_file, e))
154
155
tierno7edb6752016-03-21 17:37:52 +0100156if __name__=="__main__":
tiernod4d5e252018-01-23 17:07:46 +0100157 env_config = {
158 'db_host': 'RO_DB_HOST',
159 'db_name': 'RO_DB_NAME',
160 'db_user': 'RO_DB_USER',
161 'db_passwd': 'RO_DB_PASSWORD',
162 'db_ovim_host': 'RO_DB_OVIM_HOST',
163 'db_ovim_name': 'RO_DB_OVIM_NAME',
164 'db_ovim_user': 'RO_DB_OVIM_USER',
165 'db_ovim_passwd': 'RO_DB_OVIM_PASSWORD',
166 'db_port': 'RO_DB_PORT',
167 'db_port': 'RO_DB_PORT',
168 }
tierno46df9672017-05-26 13:12:21 +0200169 # Configure logging step 1
tierno72f35a52016-07-15 13:18:30 +0200170 hostname = socket.gethostname()
tierno46df9672017-05-26 13:12:21 +0200171 # streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
tierno72f35a52016-07-15 13:18:30 +0200172 # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
tierno46df9672017-05-26 13:12:21 +0200173 log_formatter_complete = logging.Formatter('%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s '
174 'severity:%(levelname)s logger:%(name)s log:%(message)s'.format(
175 host=hostname),
176 datefmt='%Y-%m-%dT%H:%M:%S')
tierno72f35a52016-07-15 13:18:30 +0200177 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
178 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
179 logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
180 logger = logging.getLogger('openmano')
tiernoae4a8d12016-07-08 12:30:39 +0200181 logger.setLevel(logging.DEBUG)
tierno72f35a52016-07-15 13:18:30 +0200182 socket_handler = None
tierno42026a02017-02-10 15:13:40 +0100183 # Read parameters and configuration file
184 httpthread = None
tierno7edb6752016-03-21 17:37:52 +0100185 try:
tierno72f35a52016-07-15 13:18:30 +0200186 #load parameters and configuration
tierno73ad9e42016-09-12 18:11:11 +0200187 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="])
tiernoae4a8d12016-07-08 12:30:39 +0200188 port=None
189 port_admin = None
garciadeblas4b6216b2017-04-20 16:41:52 +0200190 config_file = 'osm_ro/openmanod.cfg'
tiernoae4a8d12016-07-08 12:30:39 +0200191 vnf_repository = None
tierno205d1022016-07-21 11:26:22 +0200192 log_file = None
tierno72f35a52016-07-15 13:18:30 +0200193 log_socket_host = None
194 log_socket_port = None
tierno46df9672017-05-26 13:12:21 +0200195
tiernoae4a8d12016-07-08 12:30:39 +0200196 for o, a in opts:
197 if o in ("-v", "--version"):
tierno72f35a52016-07-15 13:18:30 +0200198 print ("openmanod version " + __version__ + ' ' + version_date)
199 print ("(c) Copyright Telefonica")
tiernoae4a8d12016-07-08 12:30:39 +0200200 sys.exit()
201 elif o in ("-h", "--help"):
202 usage()
203 sys.exit()
204 elif o in ("-V", "--vnf-repository"):
205 vnf_repository = a
206 elif o in ("-c", "--config"):
207 config_file = a
208 elif o in ("-p", "--port"):
209 port = a
210 elif o in ("-P", "--adminport"):
211 port_admin = a
tierno72f35a52016-07-15 13:18:30 +0200212 elif o == "--log-socket-port":
213 log_socket_port = a
tiernoa9a7e622016-09-30 08:43:32 +0000214 elif o == "--log-socket-host":
tierno72f35a52016-07-15 13:18:30 +0200215 log_socket_host = a
tierno205d1022016-07-21 11:26:22 +0200216 elif o == "--log-file":
217 log_file = a
tiernoae4a8d12016-07-08 12:30:39 +0200218 else:
219 assert False, "Unhandled option"
tierno46df9672017-05-26 13:12:21 +0200220 if log_file:
221 set_logging_file(log_file)
tiernoae4a8d12016-07-08 12:30:39 +0200222 global_config = load_configuration(config_file)
tierno6ddeded2017-05-16 15:40:26 +0200223 global_config["version"] = __version__
224 global_config["version_date"] = version_date
tierno7edb6752016-03-21 17:37:52 +0100225 #print global_config
tiernod4d5e252018-01-23 17:07:46 +0100226 # Override parameters obtained by command line on ENV
tierno72f35a52016-07-15 13:18:30 +0200227 if port:
228 global_config['http_port'] = port
229 if port_admin:
230 global_config['http_admin_port'] = port_admin
231 if log_socket_host:
232 global_config['log_socket_host'] = log_socket_host
233 if log_socket_port:
234 global_config['log_socket_port'] = log_socket_port
tiernod4d5e252018-01-23 17:07:46 +0100235
236 # override with ENV
237 for config_key, env_var in env_config.items():
238 if os_getenv(env_var):
239 global_config[config_key] = os_getenv(env_var)
240
241
tierno72f35a52016-07-15 13:18:30 +0200242# if vnf_repository is not None:
243# global_config['vnf_repository'] = vnf_repository
244# else:
tierno46df9672017-05-26 13:12:21 +0200245# if not 'vnf_repository' in global_config:
tierno72f35a52016-07-15 13:18:30 +0200246# logger.error( os.getcwd() )
247# global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
248# #print global_config
249# if not os.path.exists(global_config['vnf_repository']):
250# logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
251# try:
252# os.makedirs(global_config['vnf_repository'])
253# except Exception as e:
254# logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
255# exit(-1)
tierno46df9672017-05-26 13:12:21 +0200256
tierno72f35a52016-07-15 13:18:30 +0200257 global_config["console_port_iterator"] = console_port_iterator
258 global_config["console_thread"]={}
259 global_config["console_ports"]={}
tierno20fc2a22016-08-19 17:02:35 +0200260 if not global_config["http_console_host"]:
261 global_config["http_console_host"] = global_config["http_host"]
262 if global_config["http_host"]=="0.0.0.0":
263 global_config["http_console_host"] = socket.gethostname()
tierno46df9672017-05-26 13:12:21 +0200264
265 # Configure logging STEP 2
tiernof97fd272016-07-11 14:32:37 +0200266 if "log_host" in global_config:
tierno72f35a52016-07-15 13:18:30 +0200267 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
268 socket_handler.setFormatter(log_formatter_complete)
tierno46df9672017-05-26 13:12:21 +0200269 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
tierno72f35a52016-07-15 13:18:30 +0200270 socket_handler.setLevel(global_config["log_socket_level"])
tiernof97fd272016-07-11 14:32:37 +0200271 logger.addHandler(socket_handler)
tierno46df9672017-05-26 13:12:21 +0200272
273 # logger.addHandler(log_handlers.SysLogHandler())
274 if log_file:
275 global_config['log_file'] = log_file
276 elif global_config.get('log_file'):
277 set_logging_file(global_config['log_file'])
278
279 # logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
tierno205d1022016-07-21 11:26:22 +0200280 logger.setLevel(getattr(logging, global_config['log_level']))
tierno46df9672017-05-26 13:12:21 +0200281 logger.critical("Starting openmano server version: '%s %s' command: '%s'",
tiernoaceb4642016-10-20 15:12:30 +0000282 __version__, version_date, " ".join(sys.argv))
tierno46df9672017-05-26 13:12:21 +0200283
tierno639520f2017-04-05 19:55:36 +0200284 for log_module in ("nfvo", "http", "vim", "db", "console", "ovim"):
tierno73ad9e42016-09-12 18:11:11 +0200285 log_level_module = "log_level_" + log_module
286 log_file_module = "log_file_" + log_module
287 logger_module = logging.getLogger('openmano.' + log_module)
288 if log_level_module in global_config:
289 logger_module.setLevel(global_config[log_level_module])
290 if log_file_module in global_config:
291 try:
tierno46df9672017-05-26 13:12:21 +0200292 file_handler = logging.handlers.RotatingFileHandler(global_config[log_file_module],
293 maxBytes=100e6, backupCount=9, delay=0)
tierno73ad9e42016-09-12 18:11:11 +0200294 file_handler.setFormatter(log_formatter_simple)
295 logger_module.addHandler(file_handler)
296 except IOError as e:
tierno46df9672017-05-26 13:12:21 +0200297 raise LoadConfigurationException(
298 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(
299 global_config[log_file_module], str(e)) )
tierno73ad9e42016-09-12 18:11:11 +0200300 global_config["logger_"+log_module] = logger_module
301 #httpserver.logger = global_config["logger_http"]
302 #nfvo.logger = global_config["logger_nfvo"]
tierno46df9672017-05-26 13:12:21 +0200303
tierno7edb6752016-03-21 17:37:52 +0100304 # Initialize DB connection
tiernob13f3cc2016-09-26 10:14:44 +0200305 mydb = nfvo_db.nfvo_db();
tierno44528e42016-10-11 12:06:25 +0000306 mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
tierno11f81f62017-04-27 17:22:14 +0200307 db_path = osm_ro.__path__[0] + "/database_utils"
tiernod4d5e252018-01-23 17:07:46 +0100308 if not os_path.exists(db_path + "/migrate_mano_db.sh"):
tierno11f81f62017-04-27 17:22:14 +0200309 db_path = osm_ro.__path__[0] + "/../database_utils"
tierno44528e42016-10-11 12:06:25 +0000310 try:
311 r = mydb.get_db_version()
tierno11f81f62017-04-27 17:22:14 +0200312 if r[0] != database_version:
313 logger.critical("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
314 " with '{db_path}/migrate_mano_db.sh {target}'".format(
315 current=r[0], target=database_version, db_path=db_path))
tierno44528e42016-10-11 12:06:25 +0000316 exit(-1)
317 except db_base_Exception as e:
tierno11f81f62017-04-27 17:22:14 +0200318 logger.critical("DATABASE is not valid. If you think it is corrupted, you can init it with"
319 " '{db_path}/init_mano_db.sh' script".format(db_path=db_path))
tierno7edb6752016-03-21 17:37:52 +0100320 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000321
tierno7edb6752016-03-21 17:37:52 +0100322 nfvo.global_config=global_config
tierno42026a02017-02-10 15:13:40 +0100323 nfvo.start_service(mydb)
tierno46df9672017-05-26 13:12:21 +0200324
tierno7edb6752016-03-21 17:37:52 +0100325 httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
tierno46df9672017-05-26 13:12:21 +0200326
tierno7edb6752016-03-21 17:37:52 +0100327 httpthread.start()
tierno46df9672017-05-26 13:12:21 +0200328 if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100329 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
330 httpthreadadmin.start()
tierno46df9672017-05-26 13:12:21 +0200331 time.sleep(1)
tiernoae4a8d12016-07-08 12:30:39 +0200332 logger.info('Waiting for http clients')
tierno205d1022016-07-21 11:26:22 +0200333 print('Waiting for http clients')
tiernoae4a8d12016-07-08 12:30:39 +0200334 print('openmanod ready')
335 print('====================')
tierno7edb6752016-03-21 17:37:52 +0100336 time.sleep(20)
337 sys.stdout.flush()
338
339 #TODO: Interactive console must be implemented here instead of join or sleep
340
341 #httpthread.join()
tierno42026a02017-02-10 15:13:40 +0100342 #if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100343 # httpthreadadmin.join()
344 while True:
345 time.sleep(86400)
tierno7edb6752016-03-21 17:37:52 +0100346
tierno72f35a52016-07-15 13:18:30 +0200347 except KeyboardInterrupt as e:
348 logger.info(str(e))
tierno809a7802016-07-08 13:31:24 +0200349 except SystemExit:
350 pass
tiernoae4a8d12016-07-08 12:30:39 +0200351 except getopt.GetoptError as e:
tierno72f35a52016-07-15 13:18:30 +0200352 logger.critical(str(e)) # will print something like "option -a not recognized"
tiernoae4a8d12016-07-08 12:30:39 +0200353 #usage()
354 exit(-1)
355 except LoadConfigurationException as e:
tierno72f35a52016-07-15 13:18:30 +0200356 logger.critical(str(e))
tiernoae4a8d12016-07-08 12:30:39 +0200357 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000358 except db_base_Exception as e:
359 logger.critical(str(e))
360 exit(-1)
tierno46df9672017-05-26 13:12:21 +0200361 except nfvo.NfvoException as e:
362 logger.critical(str(e), exc_info=True)
363 exit(-1)
tierno42026a02017-02-10 15:13:40 +0100364 nfvo.stop_service()
365 if httpthread:
366 httpthread.join(1)
tierno7edb6752016-03-21 17:37:52 +0100367