blob: f1ce0a7d1a53b380752b499d2a2e1fd802f2d603 [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
tierno11f81f62017-04-27 17:22:14 +020039import 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$"
51__version__ = "0.5.12-r522"
52version_date = "May 2017"
53database_version = 20 #expected database schema version
tierno99314902017-04-26 13:23:09 +020054
tierno7edb6752016-03-21 17:37:52 +010055global global_config
tiernof97fd272016-07-11 14:32:37 +020056global logger
tiernoae4a8d12016-07-08 12:30:39 +020057
58class LoadConfigurationException(Exception):
59 pass
tierno7edb6752016-03-21 17:37:52 +010060
61def load_configuration(configuration_file):
tierno639520f2017-04-05 19:55:36 +020062 default_tokens = {'http_port':9090,
63 'http_host':'localhost',
64 'http_console_proxy': True,
65 'http_console_host': None,
66 'log_level': 'DEBUG',
67 'log_socket_port': 9022,
68 'auto_push_VNF_to_VIMs': True,
69 'db_host': 'localhost',
70 'db_ovim_host': 'localhost'
71 }
tierno7edb6752016-03-21 17:37:52 +010072 try:
73 #Check config file exists
tierno72f35a52016-07-15 13:18:30 +020074 with open(configuration_file, 'r') as f:
75 config_str = f.read()
tierno7edb6752016-03-21 17:37:52 +010076 #Parse configuration file
tierno72f35a52016-07-15 13:18:30 +020077 config = yaml.load(config_str)
tierno7edb6752016-03-21 17:37:52 +010078 #Validate configuration file with the config_schema
tierno72f35a52016-07-15 13:18:30 +020079 js_v(config, config_schema)
tierno7edb6752016-03-21 17:37:52 +010080
tierno72f35a52016-07-15 13:18:30 +020081 #Add default values tokens
tierno7edb6752016-03-21 17:37:52 +010082 for k,v in default_tokens.items():
tierno72f35a52016-07-15 13:18:30 +020083 if k not in config:
84 config[k]=v
85 return config
tierno7edb6752016-03-21 17:37:52 +010086
tierno72f35a52016-07-15 13:18:30 +020087 except yaml.YAMLError as e:
88 error_pos = ""
89 if hasattr(e, 'problem_mark'):
90 mark = e.problem_mark
91 error_pos = " at line:{} column:{}".format(mark.line+1, mark.column+1)
92 raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}".format(file=configuration_file, pos=error_pos) )
93 except js_e.ValidationError as e:
94 error_pos = ""
95 if e.path:
96 error_pos=" at '" + ":".join(map(str, e.path))+"'"
97 raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format(file=configuration_file, pos=error_pos, message=str(e)) )
98 except Exception as e:
99 raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format(file=configuration_file, message=str(e) ) )
tierno7edb6752016-03-21 17:37:52 +0100100
tierno7edb6752016-03-21 17:37:52 +0100101
102def console_port_iterator():
103 '''this iterator deals with the http_console_ports
104 returning the ports one by one
105 '''
106 index = 0
107 while index < len(global_config["http_console_ports"]):
108 port = global_config["http_console_ports"][index]
tiernoae4a8d12016-07-08 12:30:39 +0200109 #print("ports -> ", port)
tierno7edb6752016-03-21 17:37:52 +0100110 if type(port) is int:
111 yield port
112 else: #this is dictionary with from to keys
113 port2 = port["from"]
tiernoae4a8d12016-07-08 12:30:39 +0200114 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100115 while port2 <= port["to"]:
tiernoae4a8d12016-07-08 12:30:39 +0200116 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100117 yield port2
118 port2 += 1
119 index += 1
120
121
122def usage():
tiernoae4a8d12016-07-08 12:30:39 +0200123 print("Usage: ", sys.argv[0], "[options]")
124 print( " -v|--version: prints current version")
125 print( " -c|--config [configuration_file]: loads the configuration file (default: openmanod.cfg)")
126 print( " -h|--help: shows this help")
127 print( " -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)")
128 print( " -P|--adminport [port_number]: changes admin port number and overrides the port number in the configuration file (default: 9095)")
tierno72f35a52016-07-15 13:18:30 +0200129 #print( " -V|--vnf-repository: changes the path of the vnf-repository and overrides the path in the configuration file")
tierno73ad9e42016-09-12 18:11:11 +0200130 print( " --log-socket-host HOST: send logs to this host")
131 print( " --log-socket-port PORT: send logs using this port (default: 9022)")
132 print( " --log-file FILE: send logs to this file")
tierno7edb6752016-03-21 17:37:52 +0100133 return
134
135if __name__=="__main__":
tierno72f35a52016-07-15 13:18:30 +0200136 #Configure logging step 1
137 hostname = socket.gethostname()
tiernoae4a8d12016-07-08 12:30:39 +0200138 #streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
tierno72f35a52016-07-15 13:18:30 +0200139 # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
140 log_formatter_complete = logging.Formatter(
141 '%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s severity:%(levelname)s logger:%(name)s log:%(message)s'.format(host=hostname),
142 datefmt='%Y-%m-%dT%H:%M:%S',
143 )
144 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
145 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
146 logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
147 logger = logging.getLogger('openmano')
tiernoae4a8d12016-07-08 12:30:39 +0200148 logger.setLevel(logging.DEBUG)
tierno72f35a52016-07-15 13:18:30 +0200149 socket_handler = None
tiernof97fd272016-07-11 14:32:37 +0200150 file_handler = None
tierno42026a02017-02-10 15:13:40 +0100151 # Read parameters and configuration file
152 httpthread = None
tierno7edb6752016-03-21 17:37:52 +0100153 try:
tierno72f35a52016-07-15 13:18:30 +0200154 #load parameters and configuration
tierno73ad9e42016-09-12 18:11:11 +0200155 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 +0200156 port=None
157 port_admin = None
garciadeblas4b6216b2017-04-20 16:41:52 +0200158 config_file = 'osm_ro/openmanod.cfg'
tiernoae4a8d12016-07-08 12:30:39 +0200159 vnf_repository = None
tierno205d1022016-07-21 11:26:22 +0200160 log_file = None
tierno72f35a52016-07-15 13:18:30 +0200161 log_socket_host = None
162 log_socket_port = None
tiernoae4a8d12016-07-08 12:30:39 +0200163
164 for o, a in opts:
165 if o in ("-v", "--version"):
tierno72f35a52016-07-15 13:18:30 +0200166 print ("openmanod version " + __version__ + ' ' + version_date)
167 print ("(c) Copyright Telefonica")
tiernoae4a8d12016-07-08 12:30:39 +0200168 sys.exit()
169 elif o in ("-h", "--help"):
170 usage()
171 sys.exit()
172 elif o in ("-V", "--vnf-repository"):
173 vnf_repository = a
174 elif o in ("-c", "--config"):
175 config_file = a
176 elif o in ("-p", "--port"):
177 port = a
178 elif o in ("-P", "--adminport"):
179 port_admin = a
tierno72f35a52016-07-15 13:18:30 +0200180 elif o == "--log-socket-port":
181 log_socket_port = a
tiernoa9a7e622016-09-30 08:43:32 +0000182 elif o == "--log-socket-host":
tierno72f35a52016-07-15 13:18:30 +0200183 log_socket_host = a
tierno205d1022016-07-21 11:26:22 +0200184 elif o == "--log-file":
185 log_file = a
tiernoae4a8d12016-07-08 12:30:39 +0200186 else:
187 assert False, "Unhandled option"
tiernoae4a8d12016-07-08 12:30:39 +0200188 global_config = load_configuration(config_file)
tierno7edb6752016-03-21 17:37:52 +0100189 #print global_config
tierno72f35a52016-07-15 13:18:30 +0200190 # Override parameters obtained by command line
191 if port:
192 global_config['http_port'] = port
193 if port_admin:
194 global_config['http_admin_port'] = port_admin
195 if log_socket_host:
196 global_config['log_socket_host'] = log_socket_host
197 if log_socket_port:
198 global_config['log_socket_port'] = log_socket_port
tierno205d1022016-07-21 11:26:22 +0200199 if log_file:
200 global_config['log_file'] = log_file
tierno72f35a52016-07-15 13:18:30 +0200201# if vnf_repository is not None:
202# global_config['vnf_repository'] = vnf_repository
203# else:
204# if not 'vnf_repository' in global_config:
205# logger.error( os.getcwd() )
206# global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
207# #print global_config
208# if not os.path.exists(global_config['vnf_repository']):
209# logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
210# try:
211# os.makedirs(global_config['vnf_repository'])
212# except Exception as e:
213# logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
214# exit(-1)
215
216 global_config["console_port_iterator"] = console_port_iterator
217 global_config["console_thread"]={}
218 global_config["console_ports"]={}
tierno20fc2a22016-08-19 17:02:35 +0200219 if not global_config["http_console_host"]:
220 global_config["http_console_host"] = global_config["http_host"]
221 if global_config["http_host"]=="0.0.0.0":
222 global_config["http_console_host"] = socket.gethostname()
tierno72f35a52016-07-15 13:18:30 +0200223
224 #Configure logging STEP 2
tiernof97fd272016-07-11 14:32:37 +0200225 if "log_host" in global_config:
tierno72f35a52016-07-15 13:18:30 +0200226 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
227 socket_handler.setFormatter(log_formatter_complete)
228 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
229 socket_handler.setLevel(global_config["log_socket_level"])
tiernof97fd272016-07-11 14:32:37 +0200230 logger.addHandler(socket_handler)
tierno205d1022016-07-21 11:26:22 +0200231 #logger.addHandler(log_handlers.SysLogHandler())
tiernof97fd272016-07-11 14:32:37 +0200232 if "log_file" in global_config:
233 try:
234 file_handler= logging.handlers.RotatingFileHandler(global_config["log_file"], maxBytes=100e6, backupCount=9, delay=0)
tierno72f35a52016-07-15 13:18:30 +0200235 file_handler.setFormatter(log_formatter_simple)
tiernof97fd272016-07-11 14:32:37 +0200236 logger.addHandler(file_handler)
tierno45a52852016-08-26 14:39:42 +0200237 #logger.debug("moving logs to '%s'", global_config["log_file"])
238 #remove initial stream handler
tierno205d1022016-07-21 11:26:22 +0200239 logging.root.removeHandler(logging.root.handlers[0])
tierno45a52852016-08-26 14:39:42 +0200240 print ("logging on '{}'".format(global_config["log_file"]))
tiernof97fd272016-07-11 14:32:37 +0200241 except IOError as e:
tierno72f35a52016-07-15 13:18:30 +0200242 raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(global_config["log_file"], str(e)) )
tierno205d1022016-07-21 11:26:22 +0200243 #logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
244 logger.setLevel(getattr(logging, global_config['log_level']))
tiernoaceb4642016-10-20 15:12:30 +0000245 logger.critical("Starting openmano server version: '%s %s' command: '%s'",
246 __version__, version_date, " ".join(sys.argv))
tierno7edb6752016-03-21 17:37:52 +0100247
tierno639520f2017-04-05 19:55:36 +0200248 for log_module in ("nfvo", "http", "vim", "db", "console", "ovim"):
tierno73ad9e42016-09-12 18:11:11 +0200249 log_level_module = "log_level_" + log_module
250 log_file_module = "log_file_" + log_module
251 logger_module = logging.getLogger('openmano.' + log_module)
252 if log_level_module in global_config:
253 logger_module.setLevel(global_config[log_level_module])
254 if log_file_module in global_config:
255 try:
256 file_handler= logging.handlers.RotatingFileHandler(global_config[log_file_module], maxBytes=100e6, backupCount=9, delay=0)
257 file_handler.setFormatter(log_formatter_simple)
258 logger_module.addHandler(file_handler)
259 except IOError as e:
260 raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(global_config[log_file_module], str(e)) )
261 global_config["logger_"+log_module] = logger_module
262 #httpserver.logger = global_config["logger_http"]
263 #nfvo.logger = global_config["logger_nfvo"]
264
tierno7edb6752016-03-21 17:37:52 +0100265 # Initialize DB connection
tiernob13f3cc2016-09-26 10:14:44 +0200266 mydb = nfvo_db.nfvo_db();
tierno44528e42016-10-11 12:06:25 +0000267 mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
tierno11f81f62017-04-27 17:22:14 +0200268 db_path = osm_ro.__path__[0] + "/database_utils"
269 if not os.path.exists(db_path + "/migrate_mano_db.sh"):
270 db_path = osm_ro.__path__[0] + "/../database_utils"
tierno44528e42016-10-11 12:06:25 +0000271 try:
272 r = mydb.get_db_version()
tierno11f81f62017-04-27 17:22:14 +0200273 if r[0] != database_version:
274 logger.critical("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
275 " with '{db_path}/migrate_mano_db.sh {target}'".format(
276 current=r[0], target=database_version, db_path=db_path))
tierno44528e42016-10-11 12:06:25 +0000277 exit(-1)
278 except db_base_Exception as e:
tierno11f81f62017-04-27 17:22:14 +0200279 logger.critical("DATABASE is not valid. If you think it is corrupted, you can init it with"
280 " '{db_path}/init_mano_db.sh' script".format(db_path=db_path))
tierno7edb6752016-03-21 17:37:52 +0100281 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000282
tierno7edb6752016-03-21 17:37:52 +0100283 nfvo.global_config=global_config
tierno42026a02017-02-10 15:13:40 +0100284 nfvo.start_service(mydb)
tierno7edb6752016-03-21 17:37:52 +0100285
286 httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
287
288 httpthread.start()
289 if 'http_admin_port' in global_config:
290 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
291 httpthreadadmin.start()
292 time.sleep(1)
tiernoae4a8d12016-07-08 12:30:39 +0200293 logger.info('Waiting for http clients')
tierno205d1022016-07-21 11:26:22 +0200294 print('Waiting for http clients')
tiernoae4a8d12016-07-08 12:30:39 +0200295 print('openmanod ready')
296 print('====================')
tierno7edb6752016-03-21 17:37:52 +0100297 time.sleep(20)
298 sys.stdout.flush()
299
300 #TODO: Interactive console must be implemented here instead of join or sleep
301
302 #httpthread.join()
tierno42026a02017-02-10 15:13:40 +0100303 #if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100304 # httpthreadadmin.join()
305 while True:
306 time.sleep(86400)
tierno7edb6752016-03-21 17:37:52 +0100307
tierno72f35a52016-07-15 13:18:30 +0200308 except KeyboardInterrupt as e:
309 logger.info(str(e))
tierno809a7802016-07-08 13:31:24 +0200310 except SystemExit:
311 pass
tiernoae4a8d12016-07-08 12:30:39 +0200312 except getopt.GetoptError as e:
tierno72f35a52016-07-15 13:18:30 +0200313 logger.critical(str(e)) # will print something like "option -a not recognized"
tiernoae4a8d12016-07-08 12:30:39 +0200314 #usage()
315 exit(-1)
316 except LoadConfigurationException as e:
tierno72f35a52016-07-15 13:18:30 +0200317 logger.critical(str(e))
tiernoae4a8d12016-07-08 12:30:39 +0200318 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000319 except db_base_Exception as e:
320 logger.critical(str(e))
321 exit(-1)
tierno42026a02017-02-10 15:13:40 +0100322 nfvo.stop_service()
323 if httpthread:
324 httpthread.join(1)
tierno7edb6752016-03-21 17:37:52 +0100325