blob: ee9b86217571bf559c4cb5b78fd9067efb9c0f19 [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$"
Pablo Montes Moreno3fbff9b2017-03-08 11:28:15 +010036__version__="0.5.9-r519"
37version_date="Mar 2017"
38database_version="0.20" #expected database schema version
tierno7edb6752016-03-21 17:37:52 +010039
40import httpserver
41import time
tierno7edb6752016-03-21 17:37:52 +010042import sys
43import getopt
44import yaml
45import nfvo_db
46from jsonschema import validate as js_v, exceptions as js_e
tierno7edb6752016-03-21 17:37:52 +010047from openmano_schemas import config_schema
tierno44528e42016-10-11 12:06:25 +000048from db_base import db_base_Exception
tierno7edb6752016-03-21 17:37:52 +010049import nfvo
tiernoae4a8d12016-07-08 12:30:39 +020050import logging
tiernof97fd272016-07-11 14:32:37 +020051import logging.handlers as log_handlers
tierno72f35a52016-07-15 13:18:30 +020052import socket
tierno7edb6752016-03-21 17:37:52 +010053
54global global_config
tiernof97fd272016-07-11 14:32:37 +020055global logger
tiernoae4a8d12016-07-08 12:30:39 +020056
57class LoadConfigurationException(Exception):
58 pass
tierno7edb6752016-03-21 17:37:52 +010059
60def load_configuration(configuration_file):
tiernoae4a8d12016-07-08 12:30:39 +020061 default_tokens ={'http_port':9090,
62 'http_host':'localhost',
tierno20fc2a22016-08-19 17:02:35 +020063 'http_console_proxy': True,
64 'http_console_host': None,
tiernoae4a8d12016-07-08 12:30:39 +020065 'log_level': 'DEBUG',
tierno72f35a52016-07-15 13:18:30 +020066 'log_socket_port': 9022,
tiernod29b1d32017-01-25 11:02:52 +010067 'auto_push_VNF_to_VIMs': True
tiernoae4a8d12016-07-08 12:30:39 +020068 }
tierno7edb6752016-03-21 17:37:52 +010069 try:
70 #Check config file exists
tierno72f35a52016-07-15 13:18:30 +020071 with open(configuration_file, 'r') as f:
72 config_str = f.read()
tierno7edb6752016-03-21 17:37:52 +010073 #Parse configuration file
tierno72f35a52016-07-15 13:18:30 +020074 config = yaml.load(config_str)
tierno7edb6752016-03-21 17:37:52 +010075 #Validate configuration file with the config_schema
tierno72f35a52016-07-15 13:18:30 +020076 js_v(config, config_schema)
tierno7edb6752016-03-21 17:37:52 +010077
tierno72f35a52016-07-15 13:18:30 +020078 #Add default values tokens
tierno7edb6752016-03-21 17:37:52 +010079 for k,v in default_tokens.items():
tierno72f35a52016-07-15 13:18:30 +020080 if k not in config:
81 config[k]=v
82 return config
tierno7edb6752016-03-21 17:37:52 +010083
tierno72f35a52016-07-15 13:18:30 +020084 except yaml.YAMLError as e:
85 error_pos = ""
86 if hasattr(e, 'problem_mark'):
87 mark = e.problem_mark
88 error_pos = " at line:{} column:{}".format(mark.line+1, mark.column+1)
89 raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}".format(file=configuration_file, pos=error_pos) )
90 except js_e.ValidationError as e:
91 error_pos = ""
92 if e.path:
93 error_pos=" at '" + ":".join(map(str, e.path))+"'"
94 raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format(file=configuration_file, pos=error_pos, message=str(e)) )
95 except Exception as e:
96 raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format(file=configuration_file, message=str(e) ) )
tierno7edb6752016-03-21 17:37:52 +010097
tierno7edb6752016-03-21 17:37:52 +010098
99def console_port_iterator():
100 '''this iterator deals with the http_console_ports
101 returning the ports one by one
102 '''
103 index = 0
104 while index < len(global_config["http_console_ports"]):
105 port = global_config["http_console_ports"][index]
tiernoae4a8d12016-07-08 12:30:39 +0200106 #print("ports -> ", port)
tierno7edb6752016-03-21 17:37:52 +0100107 if type(port) is int:
108 yield port
109 else: #this is dictionary with from to keys
110 port2 = port["from"]
tiernoae4a8d12016-07-08 12:30:39 +0200111 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100112 while port2 <= port["to"]:
tiernoae4a8d12016-07-08 12:30:39 +0200113 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100114 yield port2
115 port2 += 1
116 index += 1
117
118
119def usage():
tiernoae4a8d12016-07-08 12:30:39 +0200120 print("Usage: ", sys.argv[0], "[options]")
121 print( " -v|--version: prints current version")
122 print( " -c|--config [configuration_file]: loads the configuration file (default: openmanod.cfg)")
123 print( " -h|--help: shows this help")
124 print( " -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)")
125 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 +0200126 #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 +0200127 print( " --log-socket-host HOST: send logs to this host")
128 print( " --log-socket-port PORT: send logs using this port (default: 9022)")
129 print( " --log-file FILE: send logs to this file")
tierno7edb6752016-03-21 17:37:52 +0100130 return
131
132if __name__=="__main__":
tierno72f35a52016-07-15 13:18:30 +0200133 #Configure logging step 1
134 hostname = socket.gethostname()
tiernoae4a8d12016-07-08 12:30:39 +0200135 #streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
tierno72f35a52016-07-15 13:18:30 +0200136 # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
137 log_formatter_complete = logging.Formatter(
138 '%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s severity:%(levelname)s logger:%(name)s log:%(message)s'.format(host=hostname),
139 datefmt='%Y-%m-%dT%H:%M:%S',
140 )
141 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
142 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
143 logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
144 logger = logging.getLogger('openmano')
tiernoae4a8d12016-07-08 12:30:39 +0200145 logger.setLevel(logging.DEBUG)
tierno72f35a52016-07-15 13:18:30 +0200146 socket_handler = None
tiernof97fd272016-07-11 14:32:37 +0200147 file_handler = None
tierno42026a02017-02-10 15:13:40 +0100148 # Read parameters and configuration file
149 httpthread = None
tierno7edb6752016-03-21 17:37:52 +0100150 try:
tierno72f35a52016-07-15 13:18:30 +0200151 #load parameters and configuration
tierno73ad9e42016-09-12 18:11:11 +0200152 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 +0200153 port=None
154 port_admin = None
155 config_file = 'openmanod.cfg'
156 vnf_repository = None
tierno205d1022016-07-21 11:26:22 +0200157 log_file = None
tierno72f35a52016-07-15 13:18:30 +0200158 log_socket_host = None
159 log_socket_port = None
tiernoae4a8d12016-07-08 12:30:39 +0200160
161 for o, a in opts:
162 if o in ("-v", "--version"):
tierno72f35a52016-07-15 13:18:30 +0200163 print ("openmanod version " + __version__ + ' ' + version_date)
164 print ("(c) Copyright Telefonica")
tiernoae4a8d12016-07-08 12:30:39 +0200165 sys.exit()
166 elif o in ("-h", "--help"):
167 usage()
168 sys.exit()
169 elif o in ("-V", "--vnf-repository"):
170 vnf_repository = a
171 elif o in ("-c", "--config"):
172 config_file = a
173 elif o in ("-p", "--port"):
174 port = a
175 elif o in ("-P", "--adminport"):
176 port_admin = a
tierno72f35a52016-07-15 13:18:30 +0200177 elif o == "--log-socket-port":
178 log_socket_port = a
tiernoa9a7e622016-09-30 08:43:32 +0000179 elif o == "--log-socket-host":
tierno72f35a52016-07-15 13:18:30 +0200180 log_socket_host = a
tierno205d1022016-07-21 11:26:22 +0200181 elif o == "--log-file":
182 log_file = a
tiernoae4a8d12016-07-08 12:30:39 +0200183 else:
184 assert False, "Unhandled option"
tiernoae4a8d12016-07-08 12:30:39 +0200185 global_config = load_configuration(config_file)
tierno7edb6752016-03-21 17:37:52 +0100186 #print global_config
tierno72f35a52016-07-15 13:18:30 +0200187 # Override parameters obtained by command line
188 if port:
189 global_config['http_port'] = port
190 if port_admin:
191 global_config['http_admin_port'] = port_admin
192 if log_socket_host:
193 global_config['log_socket_host'] = log_socket_host
194 if log_socket_port:
195 global_config['log_socket_port'] = log_socket_port
tierno205d1022016-07-21 11:26:22 +0200196 if log_file:
197 global_config['log_file'] = log_file
tierno72f35a52016-07-15 13:18:30 +0200198# if vnf_repository is not None:
199# global_config['vnf_repository'] = vnf_repository
200# else:
201# if not 'vnf_repository' in global_config:
202# logger.error( os.getcwd() )
203# global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
204# #print global_config
205# if not os.path.exists(global_config['vnf_repository']):
206# logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
207# try:
208# os.makedirs(global_config['vnf_repository'])
209# except Exception as e:
210# logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
211# exit(-1)
212
213 global_config["console_port_iterator"] = console_port_iterator
214 global_config["console_thread"]={}
215 global_config["console_ports"]={}
tierno20fc2a22016-08-19 17:02:35 +0200216 if not global_config["http_console_host"]:
217 global_config["http_console_host"] = global_config["http_host"]
218 if global_config["http_host"]=="0.0.0.0":
219 global_config["http_console_host"] = socket.gethostname()
tierno72f35a52016-07-15 13:18:30 +0200220
221 #Configure logging STEP 2
tiernof97fd272016-07-11 14:32:37 +0200222 if "log_host" in global_config:
tierno72f35a52016-07-15 13:18:30 +0200223 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
224 socket_handler.setFormatter(log_formatter_complete)
225 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
226 socket_handler.setLevel(global_config["log_socket_level"])
tiernof97fd272016-07-11 14:32:37 +0200227 logger.addHandler(socket_handler)
tierno205d1022016-07-21 11:26:22 +0200228 #logger.addHandler(log_handlers.SysLogHandler())
tiernof97fd272016-07-11 14:32:37 +0200229 if "log_file" in global_config:
230 try:
231 file_handler= logging.handlers.RotatingFileHandler(global_config["log_file"], maxBytes=100e6, backupCount=9, delay=0)
tierno72f35a52016-07-15 13:18:30 +0200232 file_handler.setFormatter(log_formatter_simple)
tiernof97fd272016-07-11 14:32:37 +0200233 logger.addHandler(file_handler)
tierno45a52852016-08-26 14:39:42 +0200234 #logger.debug("moving logs to '%s'", global_config["log_file"])
235 #remove initial stream handler
tierno205d1022016-07-21 11:26:22 +0200236 logging.root.removeHandler(logging.root.handlers[0])
tierno45a52852016-08-26 14:39:42 +0200237 print ("logging on '{}'".format(global_config["log_file"]))
tiernof97fd272016-07-11 14:32:37 +0200238 except IOError as e:
tierno72f35a52016-07-15 13:18:30 +0200239 raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(global_config["log_file"], str(e)) )
tierno205d1022016-07-21 11:26:22 +0200240 #logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
241 logger.setLevel(getattr(logging, global_config['log_level']))
tiernoaceb4642016-10-20 15:12:30 +0000242 logger.critical("Starting openmano server version: '%s %s' command: '%s'",
243 __version__, version_date, " ".join(sys.argv))
tierno7edb6752016-03-21 17:37:52 +0100244
tierno1ae51342017-01-16 12:48:30 +0000245 for log_module in ("nfvo", "http", "vim", "db", "console"):
tierno73ad9e42016-09-12 18:11:11 +0200246 log_level_module = "log_level_" + log_module
247 log_file_module = "log_file_" + log_module
248 logger_module = logging.getLogger('openmano.' + log_module)
249 if log_level_module in global_config:
250 logger_module.setLevel(global_config[log_level_module])
251 if log_file_module in global_config:
252 try:
253 file_handler= logging.handlers.RotatingFileHandler(global_config[log_file_module], maxBytes=100e6, backupCount=9, delay=0)
254 file_handler.setFormatter(log_formatter_simple)
255 logger_module.addHandler(file_handler)
256 except IOError as e:
257 raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(global_config[log_file_module], str(e)) )
258 global_config["logger_"+log_module] = logger_module
259 #httpserver.logger = global_config["logger_http"]
260 #nfvo.logger = global_config["logger_nfvo"]
261
tierno7edb6752016-03-21 17:37:52 +0100262 # Initialize DB connection
tiernob13f3cc2016-09-26 10:14:44 +0200263 mydb = nfvo_db.nfvo_db();
tierno44528e42016-10-11 12:06:25 +0000264 mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
265 try:
266 r = mydb.get_db_version()
267 if r[1] != database_version:
268 logger.critical("DATABASE wrong version '%s'. \
269 Try to upgrade/downgrade to version '%s' with './database_utils/migrate_mano_db.sh'",
270 r[1], database_version)
271 exit(-1)
272 except db_base_Exception as e:
273 logger.critical("DATABASE is not a MANO one or it is a '0.0' version. Try to upgrade to version '%s' with \
274 './database_utils/migrate_mano_db.sh'", database_version)
tierno7edb6752016-03-21 17:37:52 +0100275 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000276
tierno7edb6752016-03-21 17:37:52 +0100277 nfvo.global_config=global_config
tierno42026a02017-02-10 15:13:40 +0100278 nfvo.start_service(mydb)
tierno7edb6752016-03-21 17:37:52 +0100279
280 httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
281
282 httpthread.start()
283 if 'http_admin_port' in global_config:
284 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
285 httpthreadadmin.start()
286 time.sleep(1)
tiernoae4a8d12016-07-08 12:30:39 +0200287 logger.info('Waiting for http clients')
tierno205d1022016-07-21 11:26:22 +0200288 print('Waiting for http clients')
tiernoae4a8d12016-07-08 12:30:39 +0200289 print('openmanod ready')
290 print('====================')
tierno7edb6752016-03-21 17:37:52 +0100291 time.sleep(20)
292 sys.stdout.flush()
293
294 #TODO: Interactive console must be implemented here instead of join or sleep
295
296 #httpthread.join()
tierno42026a02017-02-10 15:13:40 +0100297 #if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100298 # httpthreadadmin.join()
299 while True:
300 time.sleep(86400)
tierno7edb6752016-03-21 17:37:52 +0100301
tierno72f35a52016-07-15 13:18:30 +0200302 except KeyboardInterrupt as e:
303 logger.info(str(e))
tierno809a7802016-07-08 13:31:24 +0200304 except SystemExit:
305 pass
tiernoae4a8d12016-07-08 12:30:39 +0200306 except getopt.GetoptError as e:
tierno72f35a52016-07-15 13:18:30 +0200307 logger.critical(str(e)) # will print something like "option -a not recognized"
tiernoae4a8d12016-07-08 12:30:39 +0200308 #usage()
309 exit(-1)
310 except LoadConfigurationException as e:
tierno72f35a52016-07-15 13:18:30 +0200311 logger.critical(str(e))
tiernoae4a8d12016-07-08 12:30:39 +0200312 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000313 except db_base_Exception as e:
314 logger.critical(str(e))
315 exit(-1)
tierno42026a02017-02-10 15:13:40 +0100316 nfvo.stop_service()
317 if httpthread:
318 httpthread.join(1)
tierno7edb6752016-03-21 17:37:52 +0100319