blob: 190f7e3bc4121e5b60d22f1f85eb708070048b3e [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
25'''
26openmano 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.
33'''
34__author__="Alfonso Tierno, Gerardo Garcia, Pablo Montes"
35__date__ ="$26-aug-2014 11:09:29$"
tiernoee272b92017-04-24 00:08:14 +020036__version__="0.5.10-r520"
37version_date="Apr 2017"
Pablo Montes Moreno3fbff9b2017-03-08 11:28:15 +010038database_version="0.20" #expected database schema version
tierno7edb6752016-03-21 17:37:52 +010039
tierno7edb6752016-03-21 17:37:52 +010040import time
tierno7edb6752016-03-21 17:37:52 +010041import sys
42import getopt
43import yaml
tierno7edb6752016-03-21 17:37:52 +010044from jsonschema import validate as js_v, exceptions as js_e
tiernoae4a8d12016-07-08 12:30:39 +020045import logging
tiernof97fd272016-07-11 14:32:37 +020046import logging.handlers as log_handlers
tierno72f35a52016-07-15 13:18:30 +020047import socket
garciadeblas2c290ca2017-04-06 03:12:51 +020048from osm_ro import httpserver, nfvo, nfvo_db
49from osm_ro.openmano_schemas import config_schema
50from osm_ro.db_base import db_base_Exception
garciadeblas4b6216b2017-04-20 16:41:52 +020051import osm_ro
tierno7edb6752016-03-21 17:37:52 +010052
53global global_config
tiernof97fd272016-07-11 14:32:37 +020054global logger
tiernoae4a8d12016-07-08 12:30:39 +020055
56class LoadConfigurationException(Exception):
57 pass
tierno7edb6752016-03-21 17:37:52 +010058
59def load_configuration(configuration_file):
tierno639520f2017-04-05 19:55:36 +020060 default_tokens = {'http_port':9090,
61 'http_host':'localhost',
62 'http_console_proxy': True,
63 'http_console_host': None,
64 'log_level': 'DEBUG',
65 'log_socket_port': 9022,
66 'auto_push_VNF_to_VIMs': True,
67 'db_host': 'localhost',
68 'db_ovim_host': 'localhost'
69 }
tierno7edb6752016-03-21 17:37:52 +010070 try:
71 #Check config file exists
tierno72f35a52016-07-15 13:18:30 +020072 with open(configuration_file, 'r') as f:
73 config_str = f.read()
tierno7edb6752016-03-21 17:37:52 +010074 #Parse configuration file
tierno72f35a52016-07-15 13:18:30 +020075 config = yaml.load(config_str)
tierno7edb6752016-03-21 17:37:52 +010076 #Validate configuration file with the config_schema
tierno72f35a52016-07-15 13:18:30 +020077 js_v(config, config_schema)
tierno7edb6752016-03-21 17:37:52 +010078
tierno72f35a52016-07-15 13:18:30 +020079 #Add default values tokens
tierno7edb6752016-03-21 17:37:52 +010080 for k,v in default_tokens.items():
tierno72f35a52016-07-15 13:18:30 +020081 if k not in config:
82 config[k]=v
83 return config
tierno7edb6752016-03-21 17:37:52 +010084
tierno72f35a52016-07-15 13:18:30 +020085 except yaml.YAMLError as e:
86 error_pos = ""
87 if hasattr(e, 'problem_mark'):
88 mark = e.problem_mark
89 error_pos = " at line:{} column:{}".format(mark.line+1, mark.column+1)
90 raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}".format(file=configuration_file, pos=error_pos) )
91 except js_e.ValidationError as e:
92 error_pos = ""
93 if e.path:
94 error_pos=" at '" + ":".join(map(str, e.path))+"'"
95 raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format(file=configuration_file, pos=error_pos, message=str(e)) )
96 except Exception as e:
97 raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format(file=configuration_file, message=str(e) ) )
tierno7edb6752016-03-21 17:37:52 +010098
tierno7edb6752016-03-21 17:37:52 +010099
100def console_port_iterator():
101 '''this iterator deals with the http_console_ports
102 returning the ports one by one
103 '''
104 index = 0
105 while index < len(global_config["http_console_ports"]):
106 port = global_config["http_console_ports"][index]
tiernoae4a8d12016-07-08 12:30:39 +0200107 #print("ports -> ", port)
tierno7edb6752016-03-21 17:37:52 +0100108 if type(port) is int:
109 yield port
110 else: #this is dictionary with from to keys
111 port2 = port["from"]
tiernoae4a8d12016-07-08 12:30:39 +0200112 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100113 while port2 <= port["to"]:
tiernoae4a8d12016-07-08 12:30:39 +0200114 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100115 yield port2
116 port2 += 1
117 index += 1
118
119
120def usage():
tiernoae4a8d12016-07-08 12:30:39 +0200121 print("Usage: ", sys.argv[0], "[options]")
122 print( " -v|--version: prints current version")
123 print( " -c|--config [configuration_file]: loads the configuration file (default: openmanod.cfg)")
124 print( " -h|--help: shows this help")
125 print( " -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)")
126 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 +0200127 #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 +0200128 print( " --log-socket-host HOST: send logs to this host")
129 print( " --log-socket-port PORT: send logs using this port (default: 9022)")
130 print( " --log-file FILE: send logs to this file")
tierno7edb6752016-03-21 17:37:52 +0100131 return
132
133if __name__=="__main__":
tierno72f35a52016-07-15 13:18:30 +0200134 #Configure logging step 1
135 hostname = socket.gethostname()
tiernoae4a8d12016-07-08 12:30:39 +0200136 #streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
tierno72f35a52016-07-15 13:18:30 +0200137 # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
138 log_formatter_complete = logging.Formatter(
139 '%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s severity:%(levelname)s logger:%(name)s log:%(message)s'.format(host=hostname),
140 datefmt='%Y-%m-%dT%H:%M:%S',
141 )
142 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
143 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
144 logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
145 logger = logging.getLogger('openmano')
tiernoae4a8d12016-07-08 12:30:39 +0200146 logger.setLevel(logging.DEBUG)
tierno72f35a52016-07-15 13:18:30 +0200147 socket_handler = None
tiernof97fd272016-07-11 14:32:37 +0200148 file_handler = None
tierno42026a02017-02-10 15:13:40 +0100149 # Read parameters and configuration file
150 httpthread = None
tierno7edb6752016-03-21 17:37:52 +0100151 try:
tierno72f35a52016-07-15 13:18:30 +0200152 #load parameters and configuration
tierno73ad9e42016-09-12 18:11:11 +0200153 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 +0200154 port=None
155 port_admin = None
garciadeblas4b6216b2017-04-20 16:41:52 +0200156 config_file = 'osm_ro/openmanod.cfg'
tiernoae4a8d12016-07-08 12:30:39 +0200157 vnf_repository = None
tierno205d1022016-07-21 11:26:22 +0200158 log_file = None
tierno72f35a52016-07-15 13:18:30 +0200159 log_socket_host = None
160 log_socket_port = None
tiernoae4a8d12016-07-08 12:30:39 +0200161
162 for o, a in opts:
163 if o in ("-v", "--version"):
tierno72f35a52016-07-15 13:18:30 +0200164 print ("openmanod version " + __version__ + ' ' + version_date)
165 print ("(c) Copyright Telefonica")
tiernoae4a8d12016-07-08 12:30:39 +0200166 sys.exit()
167 elif o in ("-h", "--help"):
168 usage()
169 sys.exit()
170 elif o in ("-V", "--vnf-repository"):
171 vnf_repository = a
172 elif o in ("-c", "--config"):
173 config_file = a
174 elif o in ("-p", "--port"):
175 port = a
176 elif o in ("-P", "--adminport"):
177 port_admin = a
tierno72f35a52016-07-15 13:18:30 +0200178 elif o == "--log-socket-port":
179 log_socket_port = a
tiernoa9a7e622016-09-30 08:43:32 +0000180 elif o == "--log-socket-host":
tierno72f35a52016-07-15 13:18:30 +0200181 log_socket_host = a
tierno205d1022016-07-21 11:26:22 +0200182 elif o == "--log-file":
183 log_file = a
tiernoae4a8d12016-07-08 12:30:39 +0200184 else:
185 assert False, "Unhandled option"
tiernoae4a8d12016-07-08 12:30:39 +0200186 global_config = load_configuration(config_file)
tierno7edb6752016-03-21 17:37:52 +0100187 #print global_config
tierno72f35a52016-07-15 13:18:30 +0200188 # Override parameters obtained by command line
189 if port:
190 global_config['http_port'] = port
191 if port_admin:
192 global_config['http_admin_port'] = port_admin
193 if log_socket_host:
194 global_config['log_socket_host'] = log_socket_host
195 if log_socket_port:
196 global_config['log_socket_port'] = log_socket_port
tierno205d1022016-07-21 11:26:22 +0200197 if log_file:
198 global_config['log_file'] = log_file
tierno72f35a52016-07-15 13:18:30 +0200199# if vnf_repository is not None:
200# global_config['vnf_repository'] = vnf_repository
201# else:
202# if not 'vnf_repository' in global_config:
203# logger.error( os.getcwd() )
204# global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
205# #print global_config
206# if not os.path.exists(global_config['vnf_repository']):
207# logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
208# try:
209# os.makedirs(global_config['vnf_repository'])
210# except Exception as e:
211# logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
212# exit(-1)
213
214 global_config["console_port_iterator"] = console_port_iterator
215 global_config["console_thread"]={}
216 global_config["console_ports"]={}
tierno20fc2a22016-08-19 17:02:35 +0200217 if not global_config["http_console_host"]:
218 global_config["http_console_host"] = global_config["http_host"]
219 if global_config["http_host"]=="0.0.0.0":
220 global_config["http_console_host"] = socket.gethostname()
tierno72f35a52016-07-15 13:18:30 +0200221
222 #Configure logging STEP 2
tiernof97fd272016-07-11 14:32:37 +0200223 if "log_host" in global_config:
tierno72f35a52016-07-15 13:18:30 +0200224 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
225 socket_handler.setFormatter(log_formatter_complete)
226 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
227 socket_handler.setLevel(global_config["log_socket_level"])
tiernof97fd272016-07-11 14:32:37 +0200228 logger.addHandler(socket_handler)
tierno205d1022016-07-21 11:26:22 +0200229 #logger.addHandler(log_handlers.SysLogHandler())
tiernof97fd272016-07-11 14:32:37 +0200230 if "log_file" in global_config:
231 try:
232 file_handler= logging.handlers.RotatingFileHandler(global_config["log_file"], maxBytes=100e6, backupCount=9, delay=0)
tierno72f35a52016-07-15 13:18:30 +0200233 file_handler.setFormatter(log_formatter_simple)
tiernof97fd272016-07-11 14:32:37 +0200234 logger.addHandler(file_handler)
tierno45a52852016-08-26 14:39:42 +0200235 #logger.debug("moving logs to '%s'", global_config["log_file"])
236 #remove initial stream handler
tierno205d1022016-07-21 11:26:22 +0200237 logging.root.removeHandler(logging.root.handlers[0])
tierno45a52852016-08-26 14:39:42 +0200238 print ("logging on '{}'".format(global_config["log_file"]))
tiernof97fd272016-07-11 14:32:37 +0200239 except IOError as e:
tierno72f35a52016-07-15 13:18:30 +0200240 raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(global_config["log_file"], str(e)) )
tierno205d1022016-07-21 11:26:22 +0200241 #logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
242 logger.setLevel(getattr(logging, global_config['log_level']))
tiernoaceb4642016-10-20 15:12:30 +0000243 logger.critical("Starting openmano server version: '%s %s' command: '%s'",
244 __version__, version_date, " ".join(sys.argv))
tierno7edb6752016-03-21 17:37:52 +0100245
tierno639520f2017-04-05 19:55:36 +0200246 for log_module in ("nfvo", "http", "vim", "db", "console", "ovim"):
tierno73ad9e42016-09-12 18:11:11 +0200247 log_level_module = "log_level_" + log_module
248 log_file_module = "log_file_" + log_module
249 logger_module = logging.getLogger('openmano.' + log_module)
250 if log_level_module in global_config:
251 logger_module.setLevel(global_config[log_level_module])
252 if log_file_module in global_config:
253 try:
254 file_handler= logging.handlers.RotatingFileHandler(global_config[log_file_module], maxBytes=100e6, backupCount=9, delay=0)
255 file_handler.setFormatter(log_formatter_simple)
256 logger_module.addHandler(file_handler)
257 except IOError as e:
258 raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(global_config[log_file_module], str(e)) )
259 global_config["logger_"+log_module] = logger_module
260 #httpserver.logger = global_config["logger_http"]
261 #nfvo.logger = global_config["logger_nfvo"]
262
tierno7edb6752016-03-21 17:37:52 +0100263 # Initialize DB connection
tiernob13f3cc2016-09-26 10:14:44 +0200264 mydb = nfvo_db.nfvo_db();
tierno44528e42016-10-11 12:06:25 +0000265 mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
266 try:
267 r = mydb.get_db_version()
268 if r[1] != database_version:
269 logger.critical("DATABASE wrong version '%s'. \
garciadeblas4b6216b2017-04-20 16:41:52 +0200270 Try to upgrade/downgrade to version '%s' with '%s/database_utils/migrate_mano_db.sh'",
271 r[1], database_version, osm_ro.__path__[0])
tierno44528e42016-10-11 12:06:25 +0000272 exit(-1)
273 except db_base_Exception as e:
274 logger.critical("DATABASE is not a MANO one or it is a '0.0' version. Try to upgrade to version '%s' with \
275 './database_utils/migrate_mano_db.sh'", database_version)
tierno7edb6752016-03-21 17:37:52 +0100276 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000277
tierno7edb6752016-03-21 17:37:52 +0100278 nfvo.global_config=global_config
tierno42026a02017-02-10 15:13:40 +0100279 nfvo.start_service(mydb)
tierno7edb6752016-03-21 17:37:52 +0100280
281 httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
282
283 httpthread.start()
284 if 'http_admin_port' in global_config:
285 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
286 httpthreadadmin.start()
287 time.sleep(1)
tiernoae4a8d12016-07-08 12:30:39 +0200288 logger.info('Waiting for http clients')
tierno205d1022016-07-21 11:26:22 +0200289 print('Waiting for http clients')
tiernoae4a8d12016-07-08 12:30:39 +0200290 print('openmanod ready')
291 print('====================')
tierno7edb6752016-03-21 17:37:52 +0100292 time.sleep(20)
293 sys.stdout.flush()
294
295 #TODO: Interactive console must be implemented here instead of join or sleep
296
297 #httpthread.join()
tierno42026a02017-02-10 15:13:40 +0100298 #if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100299 # httpthreadadmin.join()
300 while True:
301 time.sleep(86400)
tierno7edb6752016-03-21 17:37:52 +0100302
tierno72f35a52016-07-15 13:18:30 +0200303 except KeyboardInterrupt as e:
304 logger.info(str(e))
tierno809a7802016-07-08 13:31:24 +0200305 except SystemExit:
306 pass
tiernoae4a8d12016-07-08 12:30:39 +0200307 except getopt.GetoptError as e:
tierno72f35a52016-07-15 13:18:30 +0200308 logger.critical(str(e)) # will print something like "option -a not recognized"
tiernoae4a8d12016-07-08 12:30:39 +0200309 #usage()
310 exit(-1)
311 except LoadConfigurationException as e:
tierno72f35a52016-07-15 13:18:30 +0200312 logger.critical(str(e))
tiernoae4a8d12016-07-08 12:30:39 +0200313 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000314 except db_base_Exception as e:
315 logger.critical(str(e))
316 exit(-1)
tierno42026a02017-02-10 15:13:40 +0100317 nfvo.stop_service()
318 if httpthread:
319 httpthread.join(1)
tierno7edb6752016-03-21 17:37:52 +0100320