blob: 0611d4ec2ac0ff27015315614d175ee3625b2d96 [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$"
tiernod29b1d32017-01-25 11:02:52 +010036__version__="0.5.8-r518"
montesmoreno2a1fc4e2017-01-09 16:46:04 +000037version_date="Jan 2017"
tierno36c0b172017-01-12 18:32:28 +010038database_version="0.19" #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):
tiernoae4a8d12016-07-08 12:30:39 +020060 default_tokens ={'http_port':9090,
61 'http_host':'localhost',
tierno20fc2a22016-08-19 17:02:35 +020062 'http_console_proxy': True,
63 'http_console_host': None,
tiernoae4a8d12016-07-08 12:30:39 +020064 'log_level': 'DEBUG',
tierno72f35a52016-07-15 13:18:30 +020065 'log_socket_port': 9022,
tiernod29b1d32017-01-25 11:02:52 +010066 'auto_push_VNF_to_VIMs': True
tiernoae4a8d12016-07-08 12:30:39 +020067 }
tierno7edb6752016-03-21 17:37:52 +010068 try:
69 #Check config file exists
tierno72f35a52016-07-15 13:18:30 +020070 with open(configuration_file, 'r') as f:
71 config_str = f.read()
tierno7edb6752016-03-21 17:37:52 +010072 #Parse configuration file
tierno72f35a52016-07-15 13:18:30 +020073 config = yaml.load(config_str)
tierno7edb6752016-03-21 17:37:52 +010074 #Validate configuration file with the config_schema
tierno72f35a52016-07-15 13:18:30 +020075 js_v(config, config_schema)
tierno7edb6752016-03-21 17:37:52 +010076
tierno72f35a52016-07-15 13:18:30 +020077 #Add default values tokens
tierno7edb6752016-03-21 17:37:52 +010078 for k,v in default_tokens.items():
tierno72f35a52016-07-15 13:18:30 +020079 if k not in config:
80 config[k]=v
81 return config
tierno7edb6752016-03-21 17:37:52 +010082
tierno72f35a52016-07-15 13:18:30 +020083 except yaml.YAMLError as e:
84 error_pos = ""
85 if hasattr(e, 'problem_mark'):
86 mark = e.problem_mark
87 error_pos = " at line:{} column:{}".format(mark.line+1, mark.column+1)
88 raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}".format(file=configuration_file, pos=error_pos) )
89 except js_e.ValidationError as e:
90 error_pos = ""
91 if e.path:
92 error_pos=" at '" + ":".join(map(str, e.path))+"'"
93 raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format(file=configuration_file, pos=error_pos, message=str(e)) )
94 except Exception as e:
95 raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format(file=configuration_file, message=str(e) ) )
tierno7edb6752016-03-21 17:37:52 +010096
tierno7edb6752016-03-21 17:37:52 +010097
98def console_port_iterator():
99 '''this iterator deals with the http_console_ports
100 returning the ports one by one
101 '''
102 index = 0
103 while index < len(global_config["http_console_ports"]):
104 port = global_config["http_console_ports"][index]
tiernoae4a8d12016-07-08 12:30:39 +0200105 #print("ports -> ", port)
tierno7edb6752016-03-21 17:37:52 +0100106 if type(port) is int:
107 yield port
108 else: #this is dictionary with from to keys
109 port2 = port["from"]
tiernoae4a8d12016-07-08 12:30:39 +0200110 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100111 while port2 <= port["to"]:
tiernoae4a8d12016-07-08 12:30:39 +0200112 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100113 yield port2
114 port2 += 1
115 index += 1
116
117
118def usage():
tiernoae4a8d12016-07-08 12:30:39 +0200119 print("Usage: ", sys.argv[0], "[options]")
120 print( " -v|--version: prints current version")
121 print( " -c|--config [configuration_file]: loads the configuration file (default: openmanod.cfg)")
122 print( " -h|--help: shows this help")
123 print( " -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)")
124 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 +0200125 #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 +0200126 print( " --log-socket-host HOST: send logs to this host")
127 print( " --log-socket-port PORT: send logs using this port (default: 9022)")
128 print( " --log-file FILE: send logs to this file")
tierno7edb6752016-03-21 17:37:52 +0100129 return
130
131if __name__=="__main__":
tierno72f35a52016-07-15 13:18:30 +0200132 #Configure logging step 1
133 hostname = socket.gethostname()
tiernoae4a8d12016-07-08 12:30:39 +0200134 #streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
tierno72f35a52016-07-15 13:18:30 +0200135 # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
136 log_formatter_complete = logging.Formatter(
137 '%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s severity:%(levelname)s logger:%(name)s log:%(message)s'.format(host=hostname),
138 datefmt='%Y-%m-%dT%H:%M:%S',
139 )
140 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
141 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
142 logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
143 logger = logging.getLogger('openmano')
tiernoae4a8d12016-07-08 12:30:39 +0200144 logger.setLevel(logging.DEBUG)
tierno72f35a52016-07-15 13:18:30 +0200145 socket_handler = None
tiernof97fd272016-07-11 14:32:37 +0200146 file_handler = None
tierno42026a02017-02-10 15:13:40 +0100147 # Read parameters and configuration file
148 httpthread = None
tierno7edb6752016-03-21 17:37:52 +0100149 try:
tierno72f35a52016-07-15 13:18:30 +0200150 #load parameters and configuration
tierno73ad9e42016-09-12 18:11:11 +0200151 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 +0200152 port=None
153 port_admin = None
garciadeblas4b6216b2017-04-20 16:41:52 +0200154 config_file = 'osm_ro/openmanod.cfg'
tiernoae4a8d12016-07-08 12:30:39 +0200155 vnf_repository = None
tierno205d1022016-07-21 11:26:22 +0200156 log_file = None
tierno72f35a52016-07-15 13:18:30 +0200157 log_socket_host = None
158 log_socket_port = None
tiernoae4a8d12016-07-08 12:30:39 +0200159
160 for o, a in opts:
161 if o in ("-v", "--version"):
tierno72f35a52016-07-15 13:18:30 +0200162 print ("openmanod version " + __version__ + ' ' + version_date)
163 print ("(c) Copyright Telefonica")
tiernoae4a8d12016-07-08 12:30:39 +0200164 sys.exit()
165 elif o in ("-h", "--help"):
166 usage()
167 sys.exit()
168 elif o in ("-V", "--vnf-repository"):
169 vnf_repository = a
170 elif o in ("-c", "--config"):
171 config_file = a
172 elif o in ("-p", "--port"):
173 port = a
174 elif o in ("-P", "--adminport"):
175 port_admin = a
tierno72f35a52016-07-15 13:18:30 +0200176 elif o == "--log-socket-port":
177 log_socket_port = a
tiernoa9a7e622016-09-30 08:43:32 +0000178 elif o == "--log-socket-host":
tierno72f35a52016-07-15 13:18:30 +0200179 log_socket_host = a
tierno205d1022016-07-21 11:26:22 +0200180 elif o == "--log-file":
181 log_file = a
tiernoae4a8d12016-07-08 12:30:39 +0200182 else:
183 assert False, "Unhandled option"
tiernoae4a8d12016-07-08 12:30:39 +0200184 global_config = load_configuration(config_file)
tierno7edb6752016-03-21 17:37:52 +0100185 #print global_config
tierno72f35a52016-07-15 13:18:30 +0200186 # Override parameters obtained by command line
187 if port:
188 global_config['http_port'] = port
189 if port_admin:
190 global_config['http_admin_port'] = port_admin
191 if log_socket_host:
192 global_config['log_socket_host'] = log_socket_host
193 if log_socket_port:
194 global_config['log_socket_port'] = log_socket_port
tierno205d1022016-07-21 11:26:22 +0200195 if log_file:
196 global_config['log_file'] = log_file
tierno72f35a52016-07-15 13:18:30 +0200197# if vnf_repository is not None:
198# global_config['vnf_repository'] = vnf_repository
199# else:
200# if not 'vnf_repository' in global_config:
201# logger.error( os.getcwd() )
202# global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
203# #print global_config
204# if not os.path.exists(global_config['vnf_repository']):
205# logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
206# try:
207# os.makedirs(global_config['vnf_repository'])
208# except Exception as e:
209# logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
210# exit(-1)
211
212 global_config["console_port_iterator"] = console_port_iterator
213 global_config["console_thread"]={}
214 global_config["console_ports"]={}
tierno20fc2a22016-08-19 17:02:35 +0200215 if not global_config["http_console_host"]:
216 global_config["http_console_host"] = global_config["http_host"]
217 if global_config["http_host"]=="0.0.0.0":
218 global_config["http_console_host"] = socket.gethostname()
tierno72f35a52016-07-15 13:18:30 +0200219
220 #Configure logging STEP 2
tiernof97fd272016-07-11 14:32:37 +0200221 if "log_host" in global_config:
tierno72f35a52016-07-15 13:18:30 +0200222 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
223 socket_handler.setFormatter(log_formatter_complete)
224 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
225 socket_handler.setLevel(global_config["log_socket_level"])
tiernof97fd272016-07-11 14:32:37 +0200226 logger.addHandler(socket_handler)
tierno205d1022016-07-21 11:26:22 +0200227 #logger.addHandler(log_handlers.SysLogHandler())
tiernof97fd272016-07-11 14:32:37 +0200228 if "log_file" in global_config:
229 try:
230 file_handler= logging.handlers.RotatingFileHandler(global_config["log_file"], maxBytes=100e6, backupCount=9, delay=0)
tierno72f35a52016-07-15 13:18:30 +0200231 file_handler.setFormatter(log_formatter_simple)
tiernof97fd272016-07-11 14:32:37 +0200232 logger.addHandler(file_handler)
tierno45a52852016-08-26 14:39:42 +0200233 #logger.debug("moving logs to '%s'", global_config["log_file"])
234 #remove initial stream handler
tierno205d1022016-07-21 11:26:22 +0200235 logging.root.removeHandler(logging.root.handlers[0])
tierno45a52852016-08-26 14:39:42 +0200236 print ("logging on '{}'".format(global_config["log_file"]))
tiernof97fd272016-07-11 14:32:37 +0200237 except IOError as e:
tierno72f35a52016-07-15 13:18:30 +0200238 raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(global_config["log_file"], str(e)) )
tierno205d1022016-07-21 11:26:22 +0200239 #logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
240 logger.setLevel(getattr(logging, global_config['log_level']))
tiernoaceb4642016-10-20 15:12:30 +0000241 logger.critical("Starting openmano server version: '%s %s' command: '%s'",
242 __version__, version_date, " ".join(sys.argv))
tierno7edb6752016-03-21 17:37:52 +0100243
tierno1ae51342017-01-16 12:48:30 +0000244 for log_module in ("nfvo", "http", "vim", "db", "console"):
tierno73ad9e42016-09-12 18:11:11 +0200245 log_level_module = "log_level_" + log_module
246 log_file_module = "log_file_" + log_module
247 logger_module = logging.getLogger('openmano.' + log_module)
248 if log_level_module in global_config:
249 logger_module.setLevel(global_config[log_level_module])
250 if log_file_module in global_config:
251 try:
252 file_handler= logging.handlers.RotatingFileHandler(global_config[log_file_module], maxBytes=100e6, backupCount=9, delay=0)
253 file_handler.setFormatter(log_formatter_simple)
254 logger_module.addHandler(file_handler)
255 except IOError as e:
256 raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(global_config[log_file_module], str(e)) )
257 global_config["logger_"+log_module] = logger_module
258 #httpserver.logger = global_config["logger_http"]
259 #nfvo.logger = global_config["logger_nfvo"]
260
tierno7edb6752016-03-21 17:37:52 +0100261 # Initialize DB connection
tiernob13f3cc2016-09-26 10:14:44 +0200262 mydb = nfvo_db.nfvo_db();
tierno44528e42016-10-11 12:06:25 +0000263 mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
264 try:
265 r = mydb.get_db_version()
266 if r[1] != database_version:
267 logger.critical("DATABASE wrong version '%s'. \
garciadeblas4b6216b2017-04-20 16:41:52 +0200268 Try to upgrade/downgrade to version '%s' with '%s/database_utils/migrate_mano_db.sh'",
269 r[1], database_version, osm_ro.__path__[0])
tierno44528e42016-10-11 12:06:25 +0000270 exit(-1)
271 except db_base_Exception as e:
272 logger.critical("DATABASE is not a MANO one or it is a '0.0' version. Try to upgrade to version '%s' with \
273 './database_utils/migrate_mano_db.sh'", database_version)
tierno7edb6752016-03-21 17:37:52 +0100274 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000275
tierno7edb6752016-03-21 17:37:52 +0100276 nfvo.global_config=global_config
tierno42026a02017-02-10 15:13:40 +0100277 nfvo.start_service(mydb)
tierno7edb6752016-03-21 17:37:52 +0100278
279 httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
280
281 httpthread.start()
282 if 'http_admin_port' in global_config:
283 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
284 httpthreadadmin.start()
285 time.sleep(1)
tiernoae4a8d12016-07-08 12:30:39 +0200286 logger.info('Waiting for http clients')
tierno205d1022016-07-21 11:26:22 +0200287 print('Waiting for http clients')
tiernoae4a8d12016-07-08 12:30:39 +0200288 print('openmanod ready')
289 print('====================')
tierno7edb6752016-03-21 17:37:52 +0100290 time.sleep(20)
291 sys.stdout.flush()
292
293 #TODO: Interactive console must be implemented here instead of join or sleep
294
295 #httpthread.join()
tierno42026a02017-02-10 15:13:40 +0100296 #if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100297 # httpthreadadmin.join()
298 while True:
299 time.sleep(86400)
tierno7edb6752016-03-21 17:37:52 +0100300
tierno72f35a52016-07-15 13:18:30 +0200301 except KeyboardInterrupt as e:
302 logger.info(str(e))
tierno809a7802016-07-08 13:31:24 +0200303 except SystemExit:
304 pass
tiernoae4a8d12016-07-08 12:30:39 +0200305 except getopt.GetoptError as e:
tierno72f35a52016-07-15 13:18:30 +0200306 logger.critical(str(e)) # will print something like "option -a not recognized"
tiernoae4a8d12016-07-08 12:30:39 +0200307 #usage()
308 exit(-1)
309 except LoadConfigurationException as e:
tierno72f35a52016-07-15 13:18:30 +0200310 logger.critical(str(e))
tiernoae4a8d12016-07-08 12:30:39 +0200311 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000312 except db_base_Exception as e:
313 logger.critical(str(e))
314 exit(-1)
tierno42026a02017-02-10 15:13:40 +0100315 nfvo.stop_service()
316 if httpthread:
317 httpthread.join(1)
318 for thread in global_config["console_thread"]:
319 thread.terminate = True
tierno7edb6752016-03-21 17:37:52 +0100320