blob: dbfd5054d0abf4166ecfe3bbcb130c2a47d8ba89 [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$"
tierno72f35a52016-07-15 13:18:30 +020036__version__="0.4.42-r479"
tiernof97fd272016-07-11 14:32:37 +020037version_date="Jul 2016"
tierno7edb6752016-03-21 17:37:52 +010038database_version="0.10" #expected database schema version
39
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',
62 'log_level': 'DEBUG',
63 'log_level_db': 'ERROR',
64 'log_level_vimconn': 'DEBUG',
tiernof97fd272016-07-11 14:32:37 +020065 'log_level_nfvo': 'DEBUG',
tierno72f35a52016-07-15 13:18:30 +020066 'log_socket_port': 9022,
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")
126 print( " --log-socket-host: send logs to this host")
127 print( " --log-socket-port: send logs using this port (default: 9022)")
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
149 opts, args = getopt.getopt(sys.argv[1:], "hvc:V:p:P:", ["config", "help", "version", "port", "vnf-repository", "adminport", "log-socket-host"])
tiernoae4a8d12016-07-08 12:30:39 +0200150 port=None
151 port_admin = None
152 config_file = 'openmanod.cfg'
153 vnf_repository = None
tierno72f35a52016-07-15 13:18:30 +0200154 log_socket_host = None
155 log_socket_port = None
tiernoae4a8d12016-07-08 12:30:39 +0200156
157 for o, a in opts:
158 if o in ("-v", "--version"):
tierno72f35a52016-07-15 13:18:30 +0200159 print ("openmanod version " + __version__ + ' ' + version_date)
160 print ("(c) Copyright Telefonica")
tiernoae4a8d12016-07-08 12:30:39 +0200161 sys.exit()
162 elif o in ("-h", "--help"):
163 usage()
164 sys.exit()
165 elif o in ("-V", "--vnf-repository"):
166 vnf_repository = a
167 elif o in ("-c", "--config"):
168 config_file = a
169 elif o in ("-p", "--port"):
170 port = a
171 elif o in ("-P", "--adminport"):
172 port_admin = a
tierno72f35a52016-07-15 13:18:30 +0200173 elif o == "--log-socket-port":
174 log_socket_port = a
175 elif o == "--log-socket-port":
176 log_socket_host = a
tiernoae4a8d12016-07-08 12:30:39 +0200177 else:
178 assert False, "Unhandled option"
tiernoae4a8d12016-07-08 12:30:39 +0200179 global_config = load_configuration(config_file)
tierno7edb6752016-03-21 17:37:52 +0100180 #print global_config
tierno72f35a52016-07-15 13:18:30 +0200181 # Override parameters obtained by command line
182 if port:
183 global_config['http_port'] = port
184 if port_admin:
185 global_config['http_admin_port'] = port_admin
186 if log_socket_host:
187 global_config['log_socket_host'] = log_socket_host
188 if log_socket_port:
189 global_config['log_socket_port'] = log_socket_port
190# if vnf_repository is not None:
191# global_config['vnf_repository'] = vnf_repository
192# else:
193# if not 'vnf_repository' in global_config:
194# logger.error( os.getcwd() )
195# global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
196# #print global_config
197# if not os.path.exists(global_config['vnf_repository']):
198# logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
199# try:
200# os.makedirs(global_config['vnf_repository'])
201# except Exception as e:
202# logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
203# exit(-1)
204
205 global_config["console_port_iterator"] = console_port_iterator
206 global_config["console_thread"]={}
207 global_config["console_ports"]={}
208
209 #Configure logging STEP 2
tiernoae4a8d12016-07-08 12:30:39 +0200210 logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
211 logger.setLevel(getattr(logging, global_config['log_level']))
tiernof97fd272016-07-11 14:32:37 +0200212 if "log_host" in global_config:
tierno72f35a52016-07-15 13:18:30 +0200213 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
214 socket_handler.setFormatter(log_formatter_complete)
215 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
216 socket_handler.setLevel(global_config["log_socket_level"])
tiernof97fd272016-07-11 14:32:37 +0200217 logger.addHandler(socket_handler)
218 logger.addHandler(log_handlers.SysLogHandler())
219 if "log_file" in global_config:
220 try:
221 file_handler= logging.handlers.RotatingFileHandler(global_config["log_file"], maxBytes=100e6, backupCount=9, delay=0)
tierno72f35a52016-07-15 13:18:30 +0200222 file_handler.setFormatter(log_formatter_simple)
tiernof97fd272016-07-11 14:32:37 +0200223 logger.addHandler(file_handler)
224 except IOError as e:
tierno72f35a52016-07-15 13:18:30 +0200225 raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(global_config["log_file"], str(e)) )
tierno7edb6752016-03-21 17:37:52 +0100226
tierno7edb6752016-03-21 17:37:52 +0100227 # Initialize DB connection
tiernof97fd272016-07-11 14:32:37 +0200228 mydb = nfvo_db.nfvo_db(log_level=global_config["log_level_db"]);
tierno7edb6752016-03-21 17:37:52 +0100229 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 +0200230 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 +0100231 exit(-1)
232 r = mydb.get_db_version()
233 if r[0]<0:
tierno72f35a52016-07-15 13:18:30 +0200234 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 +0100235 exit(-1)
236 elif r[1]!=database_version:
tierno72f35a52016-07-15 13:18:30 +0200237 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 +0100238 exit(-1)
239
240 nfvo.global_config=global_config
241
242 httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
243
244 httpthread.start()
245 if 'http_admin_port' in global_config:
246 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
247 httpthreadadmin.start()
248 time.sleep(1)
tiernoae4a8d12016-07-08 12:30:39 +0200249 logger.info('Waiting for http clients')
250 print('openmanod ready')
251 print('====================')
tierno7edb6752016-03-21 17:37:52 +0100252 time.sleep(20)
253 sys.stdout.flush()
254
255 #TODO: Interactive console must be implemented here instead of join or sleep
256
257 #httpthread.join()
258 #if 'http_admin_port' in global_config:
259 # httpthreadadmin.join()
260 while True:
261 time.sleep(86400)
262 for thread in global_config["console_thread"]:
263 thread.terminate = True
264
tierno72f35a52016-07-15 13:18:30 +0200265 except KeyboardInterrupt as e:
266 logger.info(str(e))
tierno809a7802016-07-08 13:31:24 +0200267 except SystemExit:
268 pass
tiernoae4a8d12016-07-08 12:30:39 +0200269 except getopt.GetoptError as e:
tierno72f35a52016-07-15 13:18:30 +0200270 logger.critical(str(e)) # will print something like "option -a not recognized"
tiernoae4a8d12016-07-08 12:30:39 +0200271 #usage()
272 exit(-1)
273 except LoadConfigurationException as e:
tierno72f35a52016-07-15 13:18:30 +0200274 logger.critical(str(e))
tiernoae4a8d12016-07-08 12:30:39 +0200275 exit(-1)
tierno7edb6752016-03-21 17:37:52 +0100276