blob: 55c7e8c78ec8442ec60b9f44595538542b6ae1dc [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):
tierno639520f2017-04-05 19:55:36 +020061 default_tokens = {'http_port':9090,
62 'http_host':'localhost',
63 'http_console_proxy': True,
64 'http_console_host': None,
65 'log_level': 'DEBUG',
66 'log_socket_port': 9022,
67 'auto_push_VNF_to_VIMs': True,
68 'db_host': 'localhost',
69 'db_ovim_host': 'localhost'
70 }
tierno7edb6752016-03-21 17:37:52 +010071 try:
72 #Check config file exists
tierno72f35a52016-07-15 13:18:30 +020073 with open(configuration_file, 'r') as f:
74 config_str = f.read()
tierno7edb6752016-03-21 17:37:52 +010075 #Parse configuration file
tierno72f35a52016-07-15 13:18:30 +020076 config = yaml.load(config_str)
tierno7edb6752016-03-21 17:37:52 +010077 #Validate configuration file with the config_schema
tierno72f35a52016-07-15 13:18:30 +020078 js_v(config, config_schema)
tierno7edb6752016-03-21 17:37:52 +010079
tierno72f35a52016-07-15 13:18:30 +020080 #Add default values tokens
tierno7edb6752016-03-21 17:37:52 +010081 for k,v in default_tokens.items():
tierno72f35a52016-07-15 13:18:30 +020082 if k not in config:
83 config[k]=v
84 return config
tierno7edb6752016-03-21 17:37:52 +010085
tierno72f35a52016-07-15 13:18:30 +020086 except yaml.YAMLError as e:
87 error_pos = ""
88 if hasattr(e, 'problem_mark'):
89 mark = e.problem_mark
90 error_pos = " at line:{} column:{}".format(mark.line+1, mark.column+1)
91 raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}".format(file=configuration_file, pos=error_pos) )
92 except js_e.ValidationError as e:
93 error_pos = ""
94 if e.path:
95 error_pos=" at '" + ":".join(map(str, e.path))+"'"
96 raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format(file=configuration_file, pos=error_pos, message=str(e)) )
97 except Exception as e:
98 raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format(file=configuration_file, message=str(e) ) )
tierno7edb6752016-03-21 17:37:52 +010099
tierno7edb6752016-03-21 17:37:52 +0100100
101def console_port_iterator():
102 '''this iterator deals with the http_console_ports
103 returning the ports one by one
104 '''
105 index = 0
106 while index < len(global_config["http_console_ports"]):
107 port = global_config["http_console_ports"][index]
tiernoae4a8d12016-07-08 12:30:39 +0200108 #print("ports -> ", port)
tierno7edb6752016-03-21 17:37:52 +0100109 if type(port) is int:
110 yield port
111 else: #this is dictionary with from to keys
112 port2 = port["from"]
tiernoae4a8d12016-07-08 12:30:39 +0200113 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100114 while port2 <= port["to"]:
tiernoae4a8d12016-07-08 12:30:39 +0200115 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100116 yield port2
117 port2 += 1
118 index += 1
119
120
121def usage():
tiernoae4a8d12016-07-08 12:30:39 +0200122 print("Usage: ", sys.argv[0], "[options]")
123 print( " -v|--version: prints current version")
124 print( " -c|--config [configuration_file]: loads the configuration file (default: openmanod.cfg)")
125 print( " -h|--help: shows this help")
126 print( " -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)")
127 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 +0200128 #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 +0200129 print( " --log-socket-host HOST: send logs to this host")
130 print( " --log-socket-port PORT: send logs using this port (default: 9022)")
131 print( " --log-file FILE: send logs to this file")
tierno7edb6752016-03-21 17:37:52 +0100132 return
133
134if __name__=="__main__":
tierno72f35a52016-07-15 13:18:30 +0200135 #Configure logging step 1
136 hostname = socket.gethostname()
tiernoae4a8d12016-07-08 12:30:39 +0200137 #streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
tierno72f35a52016-07-15 13:18:30 +0200138 # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
139 log_formatter_complete = logging.Formatter(
140 '%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s severity:%(levelname)s logger:%(name)s log:%(message)s'.format(host=hostname),
141 datefmt='%Y-%m-%dT%H:%M:%S',
142 )
143 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
144 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
145 logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
146 logger = logging.getLogger('openmano')
tiernoae4a8d12016-07-08 12:30:39 +0200147 logger.setLevel(logging.DEBUG)
tierno72f35a52016-07-15 13:18:30 +0200148 socket_handler = None
tiernof97fd272016-07-11 14:32:37 +0200149 file_handler = None
tierno42026a02017-02-10 15:13:40 +0100150 # Read parameters and configuration file
151 httpthread = None
tierno7edb6752016-03-21 17:37:52 +0100152 try:
tierno72f35a52016-07-15 13:18:30 +0200153 #load parameters and configuration
tierno73ad9e42016-09-12 18:11:11 +0200154 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 +0200155 port=None
156 port_admin = None
157 config_file = 'openmanod.cfg'
158 vnf_repository = None
tierno205d1022016-07-21 11:26:22 +0200159 log_file = None
tierno72f35a52016-07-15 13:18:30 +0200160 log_socket_host = None
161 log_socket_port = None
tiernoae4a8d12016-07-08 12:30:39 +0200162
163 for o, a in opts:
164 if o in ("-v", "--version"):
tierno72f35a52016-07-15 13:18:30 +0200165 print ("openmanod version " + __version__ + ' ' + version_date)
166 print ("(c) Copyright Telefonica")
tiernoae4a8d12016-07-08 12:30:39 +0200167 sys.exit()
168 elif o in ("-h", "--help"):
169 usage()
170 sys.exit()
171 elif o in ("-V", "--vnf-repository"):
172 vnf_repository = a
173 elif o in ("-c", "--config"):
174 config_file = a
175 elif o in ("-p", "--port"):
176 port = a
177 elif o in ("-P", "--adminport"):
178 port_admin = a
tierno72f35a52016-07-15 13:18:30 +0200179 elif o == "--log-socket-port":
180 log_socket_port = a
tiernoa9a7e622016-09-30 08:43:32 +0000181 elif o == "--log-socket-host":
tierno72f35a52016-07-15 13:18:30 +0200182 log_socket_host = a
tierno205d1022016-07-21 11:26:22 +0200183 elif o == "--log-file":
184 log_file = a
tiernoae4a8d12016-07-08 12:30:39 +0200185 else:
186 assert False, "Unhandled option"
tiernoae4a8d12016-07-08 12:30:39 +0200187 global_config = load_configuration(config_file)
tierno7edb6752016-03-21 17:37:52 +0100188 #print global_config
tierno72f35a52016-07-15 13:18:30 +0200189 # Override parameters obtained by command line
190 if port:
191 global_config['http_port'] = port
192 if port_admin:
193 global_config['http_admin_port'] = port_admin
194 if log_socket_host:
195 global_config['log_socket_host'] = log_socket_host
196 if log_socket_port:
197 global_config['log_socket_port'] = log_socket_port
tierno205d1022016-07-21 11:26:22 +0200198 if log_file:
199 global_config['log_file'] = log_file
tierno72f35a52016-07-15 13:18:30 +0200200# if vnf_repository is not None:
201# global_config['vnf_repository'] = vnf_repository
202# else:
203# if not 'vnf_repository' in global_config:
204# logger.error( os.getcwd() )
205# global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
206# #print global_config
207# if not os.path.exists(global_config['vnf_repository']):
208# logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
209# try:
210# os.makedirs(global_config['vnf_repository'])
211# except Exception as e:
212# logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
213# exit(-1)
214
215 global_config["console_port_iterator"] = console_port_iterator
216 global_config["console_thread"]={}
217 global_config["console_ports"]={}
tierno20fc2a22016-08-19 17:02:35 +0200218 if not global_config["http_console_host"]:
219 global_config["http_console_host"] = global_config["http_host"]
220 if global_config["http_host"]=="0.0.0.0":
221 global_config["http_console_host"] = socket.gethostname()
tierno72f35a52016-07-15 13:18:30 +0200222
223 #Configure logging STEP 2
tiernof97fd272016-07-11 14:32:37 +0200224 if "log_host" in global_config:
tierno72f35a52016-07-15 13:18:30 +0200225 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
226 socket_handler.setFormatter(log_formatter_complete)
227 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
228 socket_handler.setLevel(global_config["log_socket_level"])
tiernof97fd272016-07-11 14:32:37 +0200229 logger.addHandler(socket_handler)
tierno205d1022016-07-21 11:26:22 +0200230 #logger.addHandler(log_handlers.SysLogHandler())
tiernof97fd272016-07-11 14:32:37 +0200231 if "log_file" in global_config:
232 try:
233 file_handler= logging.handlers.RotatingFileHandler(global_config["log_file"], maxBytes=100e6, backupCount=9, delay=0)
tierno72f35a52016-07-15 13:18:30 +0200234 file_handler.setFormatter(log_formatter_simple)
tiernof97fd272016-07-11 14:32:37 +0200235 logger.addHandler(file_handler)
tierno45a52852016-08-26 14:39:42 +0200236 #logger.debug("moving logs to '%s'", global_config["log_file"])
237 #remove initial stream handler
tierno205d1022016-07-21 11:26:22 +0200238 logging.root.removeHandler(logging.root.handlers[0])
tierno45a52852016-08-26 14:39:42 +0200239 print ("logging on '{}'".format(global_config["log_file"]))
tiernof97fd272016-07-11 14:32:37 +0200240 except IOError as e:
tierno72f35a52016-07-15 13:18:30 +0200241 raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(global_config["log_file"], str(e)) )
tierno205d1022016-07-21 11:26:22 +0200242 #logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
243 logger.setLevel(getattr(logging, global_config['log_level']))
tiernoaceb4642016-10-20 15:12:30 +0000244 logger.critical("Starting openmano server version: '%s %s' command: '%s'",
245 __version__, version_date, " ".join(sys.argv))
tierno7edb6752016-03-21 17:37:52 +0100246
tierno639520f2017-04-05 19:55:36 +0200247 for log_module in ("nfvo", "http", "vim", "db", "console", "ovim"):
tierno73ad9e42016-09-12 18:11:11 +0200248 log_level_module = "log_level_" + log_module
249 log_file_module = "log_file_" + log_module
250 logger_module = logging.getLogger('openmano.' + log_module)
251 if log_level_module in global_config:
252 logger_module.setLevel(global_config[log_level_module])
253 if log_file_module in global_config:
254 try:
255 file_handler= logging.handlers.RotatingFileHandler(global_config[log_file_module], maxBytes=100e6, backupCount=9, delay=0)
256 file_handler.setFormatter(log_formatter_simple)
257 logger_module.addHandler(file_handler)
258 except IOError as e:
259 raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(global_config[log_file_module], str(e)) )
260 global_config["logger_"+log_module] = logger_module
261 #httpserver.logger = global_config["logger_http"]
262 #nfvo.logger = global_config["logger_nfvo"]
263
tierno7edb6752016-03-21 17:37:52 +0100264 # Initialize DB connection
tiernob13f3cc2016-09-26 10:14:44 +0200265 mydb = nfvo_db.nfvo_db();
tierno44528e42016-10-11 12:06:25 +0000266 mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
267 try:
268 r = mydb.get_db_version()
269 if r[1] != database_version:
270 logger.critical("DATABASE wrong version '%s'. \
271 Try to upgrade/downgrade to version '%s' with './database_utils/migrate_mano_db.sh'",
272 r[1], database_version)
273 exit(-1)
274 except db_base_Exception as e:
275 logger.critical("DATABASE is not a MANO one or it is a '0.0' version. Try to upgrade to version '%s' with \
276 './database_utils/migrate_mano_db.sh'", database_version)
tierno7edb6752016-03-21 17:37:52 +0100277 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000278
tierno7edb6752016-03-21 17:37:52 +0100279 nfvo.global_config=global_config
tierno42026a02017-02-10 15:13:40 +0100280 nfvo.start_service(mydb)
tierno7edb6752016-03-21 17:37:52 +0100281
282 httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
283
284 httpthread.start()
285 if 'http_admin_port' in global_config:
286 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
287 httpthreadadmin.start()
288 time.sleep(1)
tiernoae4a8d12016-07-08 12:30:39 +0200289 logger.info('Waiting for http clients')
tierno205d1022016-07-21 11:26:22 +0200290 print('Waiting for http clients')
tiernoae4a8d12016-07-08 12:30:39 +0200291 print('openmanod ready')
292 print('====================')
tierno7edb6752016-03-21 17:37:52 +0100293 time.sleep(20)
294 sys.stdout.flush()
295
296 #TODO: Interactive console must be implemented here instead of join or sleep
297
298 #httpthread.join()
tierno42026a02017-02-10 15:13:40 +0100299 #if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100300 # httpthreadadmin.join()
301 while True:
302 time.sleep(86400)
tierno7edb6752016-03-21 17:37:52 +0100303
tierno72f35a52016-07-15 13:18:30 +0200304 except KeyboardInterrupt as e:
305 logger.info(str(e))
tierno809a7802016-07-08 13:31:24 +0200306 except SystemExit:
307 pass
tiernoae4a8d12016-07-08 12:30:39 +0200308 except getopt.GetoptError as e:
tierno72f35a52016-07-15 13:18:30 +0200309 logger.critical(str(e)) # will print something like "option -a not recognized"
tiernoae4a8d12016-07-08 12:30:39 +0200310 #usage()
311 exit(-1)
312 except LoadConfigurationException as e:
tierno72f35a52016-07-15 13:18:30 +0200313 logger.critical(str(e))
tiernoae4a8d12016-07-08 12:30:39 +0200314 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000315 except db_base_Exception as e:
316 logger.critical(str(e))
317 exit(-1)
tierno42026a02017-02-10 15:13:40 +0100318 nfvo.stop_service()
319 if httpthread:
320 httpthread.join(1)
tierno7edb6752016-03-21 17:37:52 +0100321