blob: 6df29d116b50820d23c5262783d19335f81f9f5b [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$"
garciadeblasedca7b32016-09-29 14:01:52 +000036__version__="0.4.60-r503"
tierno327465f2016-09-07 09:54:47 +020037version_date="Sep 2016"
garciadeblasb69fa9f2016-09-28 12:04:10 +020038database_version="0.15" #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
48import nfvo
tiernoae4a8d12016-07-08 12:30:39 +020049import logging
tiernof97fd272016-07-11 14:32:37 +020050import logging.handlers as log_handlers
tierno72f35a52016-07-15 13:18:30 +020051import socket
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,
tiernoae4a8d12016-07-08 12:30:39 +020066 }
tierno7edb6752016-03-21 17:37:52 +010067 try:
68 #Check config file exists
tierno72f35a52016-07-15 13:18:30 +020069 with open(configuration_file, 'r') as f:
70 config_str = f.read()
tierno7edb6752016-03-21 17:37:52 +010071 #Parse configuration file
tierno72f35a52016-07-15 13:18:30 +020072 config = yaml.load(config_str)
tierno7edb6752016-03-21 17:37:52 +010073 #Validate configuration file with the config_schema
tierno72f35a52016-07-15 13:18:30 +020074 js_v(config, config_schema)
tierno7edb6752016-03-21 17:37:52 +010075
tierno72f35a52016-07-15 13:18:30 +020076 #Add default values tokens
tierno7edb6752016-03-21 17:37:52 +010077 for k,v in default_tokens.items():
tierno72f35a52016-07-15 13:18:30 +020078 if k not in config:
79 config[k]=v
80 return config
tierno7edb6752016-03-21 17:37:52 +010081
tierno72f35a52016-07-15 13:18:30 +020082 except yaml.YAMLError as e:
83 error_pos = ""
84 if hasattr(e, 'problem_mark'):
85 mark = e.problem_mark
86 error_pos = " at line:{} column:{}".format(mark.line+1, mark.column+1)
87 raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}".format(file=configuration_file, pos=error_pos) )
88 except js_e.ValidationError as e:
89 error_pos = ""
90 if e.path:
91 error_pos=" at '" + ":".join(map(str, e.path))+"'"
92 raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format(file=configuration_file, pos=error_pos, message=str(e)) )
93 except Exception as e:
94 raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format(file=configuration_file, message=str(e) ) )
tierno7edb6752016-03-21 17:37:52 +010095
tierno7edb6752016-03-21 17:37:52 +010096
97def console_port_iterator():
98 '''this iterator deals with the http_console_ports
99 returning the ports one by one
100 '''
101 index = 0
102 while index < len(global_config["http_console_ports"]):
103 port = global_config["http_console_ports"][index]
tiernoae4a8d12016-07-08 12:30:39 +0200104 #print("ports -> ", port)
tierno7edb6752016-03-21 17:37:52 +0100105 if type(port) is int:
106 yield port
107 else: #this is dictionary with from to keys
108 port2 = port["from"]
tiernoae4a8d12016-07-08 12:30:39 +0200109 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100110 while port2 <= port["to"]:
tiernoae4a8d12016-07-08 12:30:39 +0200111 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100112 yield port2
113 port2 += 1
114 index += 1
115
116
117def usage():
tiernoae4a8d12016-07-08 12:30:39 +0200118 print("Usage: ", sys.argv[0], "[options]")
119 print( " -v|--version: prints current version")
120 print( " -c|--config [configuration_file]: loads the configuration file (default: openmanod.cfg)")
121 print( " -h|--help: shows this help")
122 print( " -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)")
123 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 +0200124 #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 +0200125 print( " --log-socket-host HOST: send logs to this host")
126 print( " --log-socket-port PORT: send logs using this port (default: 9022)")
127 print( " --log-file FILE: send logs to this file")
tierno7edb6752016-03-21 17:37:52 +0100128 return
129
130if __name__=="__main__":
tierno72f35a52016-07-15 13:18:30 +0200131 #Configure logging step 1
132 hostname = socket.gethostname()
tiernoae4a8d12016-07-08 12:30:39 +0200133 #streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
tierno72f35a52016-07-15 13:18:30 +0200134 # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
135 log_formatter_complete = logging.Formatter(
136 '%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s severity:%(levelname)s logger:%(name)s log:%(message)s'.format(host=hostname),
137 datefmt='%Y-%m-%dT%H:%M:%S',
138 )
139 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
140 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
141 logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
142 logger = logging.getLogger('openmano')
tiernoae4a8d12016-07-08 12:30:39 +0200143 logger.setLevel(logging.DEBUG)
tierno72f35a52016-07-15 13:18:30 +0200144 socket_handler = None
tiernof97fd272016-07-11 14:32:37 +0200145 file_handler = None
tierno7edb6752016-03-21 17:37:52 +0100146 # Read parameters and configuration file
147 try:
tierno72f35a52016-07-15 13:18:30 +0200148 #load parameters and configuration
tierno73ad9e42016-09-12 18:11:11 +0200149 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 +0200150 port=None
151 port_admin = None
152 config_file = 'openmanod.cfg'
153 vnf_repository = None
tierno205d1022016-07-21 11:26:22 +0200154 log_file = None
tierno72f35a52016-07-15 13:18:30 +0200155 log_socket_host = None
156 log_socket_port = None
tiernoae4a8d12016-07-08 12:30:39 +0200157
158 for o, a in opts:
159 if o in ("-v", "--version"):
tierno72f35a52016-07-15 13:18:30 +0200160 print ("openmanod version " + __version__ + ' ' + version_date)
161 print ("(c) Copyright Telefonica")
tiernoae4a8d12016-07-08 12:30:39 +0200162 sys.exit()
163 elif o in ("-h", "--help"):
164 usage()
165 sys.exit()
166 elif o in ("-V", "--vnf-repository"):
167 vnf_repository = a
168 elif o in ("-c", "--config"):
169 config_file = a
170 elif o in ("-p", "--port"):
171 port = a
172 elif o in ("-P", "--adminport"):
173 port_admin = a
tierno72f35a52016-07-15 13:18:30 +0200174 elif o == "--log-socket-port":
175 log_socket_port = a
tiernoa9a7e622016-09-30 08:43:32 +0000176 elif o == "--log-socket-host":
tierno72f35a52016-07-15 13:18:30 +0200177 log_socket_host = a
tierno205d1022016-07-21 11:26:22 +0200178 elif o == "--log-file":
179 log_file = a
tiernoae4a8d12016-07-08 12:30:39 +0200180 else:
181 assert False, "Unhandled option"
tiernoae4a8d12016-07-08 12:30:39 +0200182 global_config = load_configuration(config_file)
tierno7edb6752016-03-21 17:37:52 +0100183 #print global_config
tierno72f35a52016-07-15 13:18:30 +0200184 # Override parameters obtained by command line
185 if port:
186 global_config['http_port'] = port
187 if port_admin:
188 global_config['http_admin_port'] = port_admin
189 if log_socket_host:
190 global_config['log_socket_host'] = log_socket_host
191 if log_socket_port:
192 global_config['log_socket_port'] = log_socket_port
tierno205d1022016-07-21 11:26:22 +0200193 if log_file:
194 global_config['log_file'] = log_file
tierno72f35a52016-07-15 13:18:30 +0200195# if vnf_repository is not None:
196# global_config['vnf_repository'] = vnf_repository
197# else:
198# if not 'vnf_repository' in global_config:
199# logger.error( os.getcwd() )
200# global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
201# #print global_config
202# if not os.path.exists(global_config['vnf_repository']):
203# logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
204# try:
205# os.makedirs(global_config['vnf_repository'])
206# except Exception as e:
207# logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
208# exit(-1)
209
210 global_config["console_port_iterator"] = console_port_iterator
211 global_config["console_thread"]={}
212 global_config["console_ports"]={}
tierno20fc2a22016-08-19 17:02:35 +0200213 if not global_config["http_console_host"]:
214 global_config["http_console_host"] = global_config["http_host"]
215 if global_config["http_host"]=="0.0.0.0":
216 global_config["http_console_host"] = socket.gethostname()
tierno72f35a52016-07-15 13:18:30 +0200217
218 #Configure logging STEP 2
tiernof97fd272016-07-11 14:32:37 +0200219 if "log_host" in global_config:
tierno72f35a52016-07-15 13:18:30 +0200220 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
221 socket_handler.setFormatter(log_formatter_complete)
222 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
223 socket_handler.setLevel(global_config["log_socket_level"])
tiernof97fd272016-07-11 14:32:37 +0200224 logger.addHandler(socket_handler)
tierno205d1022016-07-21 11:26:22 +0200225 #logger.addHandler(log_handlers.SysLogHandler())
tiernof97fd272016-07-11 14:32:37 +0200226 if "log_file" in global_config:
227 try:
228 file_handler= logging.handlers.RotatingFileHandler(global_config["log_file"], maxBytes=100e6, backupCount=9, delay=0)
tierno72f35a52016-07-15 13:18:30 +0200229 file_handler.setFormatter(log_formatter_simple)
tiernof97fd272016-07-11 14:32:37 +0200230 logger.addHandler(file_handler)
tierno45a52852016-08-26 14:39:42 +0200231 #logger.debug("moving logs to '%s'", global_config["log_file"])
232 #remove initial stream handler
tierno205d1022016-07-21 11:26:22 +0200233 logging.root.removeHandler(logging.root.handlers[0])
tierno45a52852016-08-26 14:39:42 +0200234 print ("logging on '{}'".format(global_config["log_file"]))
tiernof97fd272016-07-11 14:32:37 +0200235 except IOError as e:
tierno72f35a52016-07-15 13:18:30 +0200236 raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(global_config["log_file"], str(e)) )
tierno205d1022016-07-21 11:26:22 +0200237 #logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
238 logger.setLevel(getattr(logging, global_config['log_level']))
tierno45a52852016-08-26 14:39:42 +0200239 logger.critical("Starting openmano server command: '%s'", sys.argv[0])
tierno7edb6752016-03-21 17:37:52 +0100240
tierno73ad9e42016-09-12 18:11:11 +0200241 for log_module in ("nfvo", "http", "vim", "db"):
242 log_level_module = "log_level_" + log_module
243 log_file_module = "log_file_" + log_module
244 logger_module = logging.getLogger('openmano.' + log_module)
245 if log_level_module in global_config:
246 logger_module.setLevel(global_config[log_level_module])
247 if log_file_module in global_config:
248 try:
249 file_handler= logging.handlers.RotatingFileHandler(global_config[log_file_module], maxBytes=100e6, backupCount=9, delay=0)
250 file_handler.setFormatter(log_formatter_simple)
251 logger_module.addHandler(file_handler)
252 except IOError as e:
253 raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(global_config[log_file_module], str(e)) )
254 global_config["logger_"+log_module] = logger_module
255 #httpserver.logger = global_config["logger_http"]
256 #nfvo.logger = global_config["logger_nfvo"]
257
tierno7edb6752016-03-21 17:37:52 +0100258 # Initialize DB connection
tiernob13f3cc2016-09-26 10:14:44 +0200259 mydb = nfvo_db.nfvo_db();
tierno7edb6752016-03-21 17:37:52 +0100260 if mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name']) == -1:
tierno72f35a52016-07-15 13:18:30 +0200261 logger.critical("Cannot connect to database %s at %s@%s", global_config['db_name'], global_config['db_user'], global_config['db_host'])
tierno7edb6752016-03-21 17:37:52 +0100262 exit(-1)
263 r = mydb.get_db_version()
264 if r[0]<0:
tierno72f35a52016-07-15 13:18:30 +0200265 logger.critical("DATABASE is not a MANO one or it is a '0.0' version. Try to upgrade to version '%s' with './database_utils/migrate_mano_db.sh'", database_version)
tierno7edb6752016-03-21 17:37:52 +0100266 exit(-1)
267 elif r[1]!=database_version:
tierno72f35a52016-07-15 13:18:30 +0200268 logger.critical("DATABASE wrong version '%s'. Try to upgrade/downgrade to version '%s' with './database_utils/migrate_mano_db.sh'", r[1], database_version)
tierno7edb6752016-03-21 17:37:52 +0100269 exit(-1)
270
271 nfvo.global_config=global_config
272
273 httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
274
275 httpthread.start()
276 if 'http_admin_port' in global_config:
277 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
278 httpthreadadmin.start()
279 time.sleep(1)
tiernoae4a8d12016-07-08 12:30:39 +0200280 logger.info('Waiting for http clients')
tierno205d1022016-07-21 11:26:22 +0200281 print('Waiting for http clients')
tiernoae4a8d12016-07-08 12:30:39 +0200282 print('openmanod ready')
283 print('====================')
tierno7edb6752016-03-21 17:37:52 +0100284 time.sleep(20)
285 sys.stdout.flush()
286
287 #TODO: Interactive console must be implemented here instead of join or sleep
288
289 #httpthread.join()
290 #if 'http_admin_port' in global_config:
291 # httpthreadadmin.join()
292 while True:
293 time.sleep(86400)
294 for thread in global_config["console_thread"]:
295 thread.terminate = True
296
tierno72f35a52016-07-15 13:18:30 +0200297 except KeyboardInterrupt as e:
298 logger.info(str(e))
tierno809a7802016-07-08 13:31:24 +0200299 except SystemExit:
300 pass
tiernoae4a8d12016-07-08 12:30:39 +0200301 except getopt.GetoptError as e:
tierno72f35a52016-07-15 13:18:30 +0200302 logger.critical(str(e)) # will print something like "option -a not recognized"
tiernoae4a8d12016-07-08 12:30:39 +0200303 #usage()
304 exit(-1)
305 except LoadConfigurationException as e:
tierno72f35a52016-07-15 13:18:30 +0200306 logger.critical(str(e))
tiernoae4a8d12016-07-08 12:30:39 +0200307 exit(-1)
tierno7edb6752016-03-21 17:37:52 +0100308