blob: 0ad7a9e363bb785d61542408a47a241379bc49f6 [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
tierno99314902017-04-26 13:23:09 +020025"""
tierno7edb6752016-03-21 17:37:52 +010026openmano 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.
tierno99314902017-04-26 13:23:09 +020033"""
tierno7edb6752016-03-21 17:37:52 +010034
tierno7edb6752016-03-21 17:37:52 +010035import time
tierno7edb6752016-03-21 17:37:52 +010036import sys
37import getopt
38import yaml
tierno11f81f62017-04-27 17:22:14 +020039import os.path
tierno7edb6752016-03-21 17:37:52 +010040from jsonschema import validate as js_v, exceptions as js_e
tiernoae4a8d12016-07-08 12:30:39 +020041import logging
tiernof97fd272016-07-11 14:32:37 +020042import logging.handlers as log_handlers
tierno72f35a52016-07-15 13:18:30 +020043import socket
garciadeblas2c290ca2017-04-06 03:12:51 +020044from osm_ro import httpserver, nfvo, nfvo_db
45from osm_ro.openmano_schemas import config_schema
46from osm_ro.db_base import db_base_Exception
garciadeblas4b6216b2017-04-20 16:41:52 +020047import osm_ro
tierno7edb6752016-03-21 17:37:52 +010048
tierno11f81f62017-04-27 17:22:14 +020049__author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes"
50__date__ = "$26-aug-2014 11:09:29$"
51__version__ = "0.5.12-r522"
52version_date = "May 2017"
53database_version = 20 #expected database schema version
tierno99314902017-04-26 13:23:09 +020054
tierno7edb6752016-03-21 17:37:52 +010055global global_config
tiernof97fd272016-07-11 14:32:37 +020056global logger
tiernoae4a8d12016-07-08 12:30:39 +020057
58class LoadConfigurationException(Exception):
59 pass
tierno7edb6752016-03-21 17:37:52 +010060
61def load_configuration(configuration_file):
tierno639520f2017-04-05 19:55:36 +020062 default_tokens = {'http_port':9090,
63 'http_host':'localhost',
64 'http_console_proxy': True,
65 'http_console_host': None,
66 'log_level': 'DEBUG',
67 'log_socket_port': 9022,
68 'auto_push_VNF_to_VIMs': True,
69 'db_host': 'localhost',
70 'db_ovim_host': 'localhost'
71 }
tierno7edb6752016-03-21 17:37:52 +010072 try:
73 #Check config file exists
tierno72f35a52016-07-15 13:18:30 +020074 with open(configuration_file, 'r') as f:
75 config_str = f.read()
tierno7edb6752016-03-21 17:37:52 +010076 #Parse configuration file
tierno72f35a52016-07-15 13:18:30 +020077 config = yaml.load(config_str)
tierno7edb6752016-03-21 17:37:52 +010078 #Validate configuration file with the config_schema
tierno72f35a52016-07-15 13:18:30 +020079 js_v(config, config_schema)
tierno7edb6752016-03-21 17:37:52 +010080
tierno72f35a52016-07-15 13:18:30 +020081 #Add default values tokens
tierno7edb6752016-03-21 17:37:52 +010082 for k,v in default_tokens.items():
tierno72f35a52016-07-15 13:18:30 +020083 if k not in config:
84 config[k]=v
85 return config
tierno7edb6752016-03-21 17:37:52 +010086
tierno72f35a52016-07-15 13:18:30 +020087 except yaml.YAMLError as e:
88 error_pos = ""
89 if hasattr(e, 'problem_mark'):
90 mark = e.problem_mark
91 error_pos = " at line:{} column:{}".format(mark.line+1, mark.column+1)
92 raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}".format(file=configuration_file, pos=error_pos) )
93 except js_e.ValidationError as e:
94 error_pos = ""
95 if e.path:
96 error_pos=" at '" + ":".join(map(str, e.path))+"'"
97 raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format(file=configuration_file, pos=error_pos, message=str(e)) )
98 except Exception as e:
99 raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format(file=configuration_file, message=str(e) ) )
tierno7edb6752016-03-21 17:37:52 +0100100
tierno7edb6752016-03-21 17:37:52 +0100101
102def console_port_iterator():
103 '''this iterator deals with the http_console_ports
104 returning the ports one by one
105 '''
106 index = 0
107 while index < len(global_config["http_console_ports"]):
108 port = global_config["http_console_ports"][index]
tiernoae4a8d12016-07-08 12:30:39 +0200109 #print("ports -> ", port)
tierno7edb6752016-03-21 17:37:52 +0100110 if type(port) is int:
111 yield port
112 else: #this is dictionary with from to keys
113 port2 = port["from"]
tiernoae4a8d12016-07-08 12:30:39 +0200114 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100115 while port2 <= port["to"]:
tiernoae4a8d12016-07-08 12:30:39 +0200116 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100117 yield port2
118 port2 += 1
119 index += 1
120
121
122def usage():
tiernoae4a8d12016-07-08 12:30:39 +0200123 print("Usage: ", sys.argv[0], "[options]")
124 print( " -v|--version: prints current version")
125 print( " -c|--config [configuration_file]: loads the configuration file (default: openmanod.cfg)")
126 print( " -h|--help: shows this help")
127 print( " -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)")
128 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 +0200129 #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 +0200130 print( " --log-socket-host HOST: send logs to this host")
131 print( " --log-socket-port PORT: send logs using this port (default: 9022)")
132 print( " --log-file FILE: send logs to this file")
tierno7edb6752016-03-21 17:37:52 +0100133 return
134
135if __name__=="__main__":
tierno72f35a52016-07-15 13:18:30 +0200136 #Configure logging step 1
137 hostname = socket.gethostname()
tiernoae4a8d12016-07-08 12:30:39 +0200138 #streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
tierno72f35a52016-07-15 13:18:30 +0200139 # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
140 log_formatter_complete = logging.Formatter(
141 '%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s severity:%(levelname)s logger:%(name)s log:%(message)s'.format(host=hostname),
142 datefmt='%Y-%m-%dT%H:%M:%S',
143 )
144 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
145 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
146 logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
147 logger = logging.getLogger('openmano')
tiernoae4a8d12016-07-08 12:30:39 +0200148 logger.setLevel(logging.DEBUG)
tierno72f35a52016-07-15 13:18:30 +0200149 socket_handler = None
tiernof97fd272016-07-11 14:32:37 +0200150 file_handler = None
tierno42026a02017-02-10 15:13:40 +0100151 # Read parameters and configuration file
152 httpthread = None
tierno7edb6752016-03-21 17:37:52 +0100153 try:
tierno72f35a52016-07-15 13:18:30 +0200154 #load parameters and configuration
tierno73ad9e42016-09-12 18:11:11 +0200155 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 +0200156 port=None
157 port_admin = None
garciadeblas4b6216b2017-04-20 16:41:52 +0200158 config_file = 'osm_ro/openmanod.cfg'
tiernoae4a8d12016-07-08 12:30:39 +0200159 vnf_repository = None
tierno205d1022016-07-21 11:26:22 +0200160 log_file = None
tierno72f35a52016-07-15 13:18:30 +0200161 log_socket_host = None
162 log_socket_port = None
tiernoae4a8d12016-07-08 12:30:39 +0200163
164 for o, a in opts:
165 if o in ("-v", "--version"):
tierno72f35a52016-07-15 13:18:30 +0200166 print ("openmanod version " + __version__ + ' ' + version_date)
167 print ("(c) Copyright Telefonica")
tiernoae4a8d12016-07-08 12:30:39 +0200168 sys.exit()
169 elif o in ("-h", "--help"):
170 usage()
171 sys.exit()
172 elif o in ("-V", "--vnf-repository"):
173 vnf_repository = a
174 elif o in ("-c", "--config"):
175 config_file = a
176 elif o in ("-p", "--port"):
177 port = a
178 elif o in ("-P", "--adminport"):
179 port_admin = a
tierno72f35a52016-07-15 13:18:30 +0200180 elif o == "--log-socket-port":
181 log_socket_port = a
tiernoa9a7e622016-09-30 08:43:32 +0000182 elif o == "--log-socket-host":
tierno72f35a52016-07-15 13:18:30 +0200183 log_socket_host = a
tierno205d1022016-07-21 11:26:22 +0200184 elif o == "--log-file":
185 log_file = a
tiernoae4a8d12016-07-08 12:30:39 +0200186 else:
187 assert False, "Unhandled option"
tiernoae4a8d12016-07-08 12:30:39 +0200188 global_config = load_configuration(config_file)
tierno6ddeded2017-05-16 15:40:26 +0200189 global_config["version"] = __version__
190 global_config["version_date"] = version_date
tierno7edb6752016-03-21 17:37:52 +0100191 #print global_config
tierno72f35a52016-07-15 13:18:30 +0200192 # Override parameters obtained by command line
193 if port:
194 global_config['http_port'] = port
195 if port_admin:
196 global_config['http_admin_port'] = port_admin
197 if log_socket_host:
198 global_config['log_socket_host'] = log_socket_host
199 if log_socket_port:
200 global_config['log_socket_port'] = log_socket_port
tierno205d1022016-07-21 11:26:22 +0200201 if log_file:
202 global_config['log_file'] = log_file
tierno72f35a52016-07-15 13:18:30 +0200203# if vnf_repository is not None:
204# global_config['vnf_repository'] = vnf_repository
205# else:
206# if not 'vnf_repository' in global_config:
207# logger.error( os.getcwd() )
208# global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
209# #print global_config
210# if not os.path.exists(global_config['vnf_repository']):
211# logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
212# try:
213# os.makedirs(global_config['vnf_repository'])
214# except Exception as e:
215# logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
216# exit(-1)
217
218 global_config["console_port_iterator"] = console_port_iterator
219 global_config["console_thread"]={}
220 global_config["console_ports"]={}
tierno20fc2a22016-08-19 17:02:35 +0200221 if not global_config["http_console_host"]:
222 global_config["http_console_host"] = global_config["http_host"]
223 if global_config["http_host"]=="0.0.0.0":
224 global_config["http_console_host"] = socket.gethostname()
tierno72f35a52016-07-15 13:18:30 +0200225
226 #Configure logging STEP 2
tiernof97fd272016-07-11 14:32:37 +0200227 if "log_host" in global_config:
tierno72f35a52016-07-15 13:18:30 +0200228 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
229 socket_handler.setFormatter(log_formatter_complete)
230 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
231 socket_handler.setLevel(global_config["log_socket_level"])
tiernof97fd272016-07-11 14:32:37 +0200232 logger.addHandler(socket_handler)
tierno205d1022016-07-21 11:26:22 +0200233 #logger.addHandler(log_handlers.SysLogHandler())
tiernof97fd272016-07-11 14:32:37 +0200234 if "log_file" in global_config:
235 try:
236 file_handler= logging.handlers.RotatingFileHandler(global_config["log_file"], maxBytes=100e6, backupCount=9, delay=0)
tierno72f35a52016-07-15 13:18:30 +0200237 file_handler.setFormatter(log_formatter_simple)
tiernof97fd272016-07-11 14:32:37 +0200238 logger.addHandler(file_handler)
tierno45a52852016-08-26 14:39:42 +0200239 #logger.debug("moving logs to '%s'", global_config["log_file"])
240 #remove initial stream handler
tierno205d1022016-07-21 11:26:22 +0200241 logging.root.removeHandler(logging.root.handlers[0])
tierno45a52852016-08-26 14:39:42 +0200242 print ("logging on '{}'".format(global_config["log_file"]))
tiernof97fd272016-07-11 14:32:37 +0200243 except IOError as e:
tierno72f35a52016-07-15 13:18:30 +0200244 raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(global_config["log_file"], str(e)) )
tierno205d1022016-07-21 11:26:22 +0200245 #logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
246 logger.setLevel(getattr(logging, global_config['log_level']))
tiernoaceb4642016-10-20 15:12:30 +0000247 logger.critical("Starting openmano server version: '%s %s' command: '%s'",
248 __version__, version_date, " ".join(sys.argv))
tierno7edb6752016-03-21 17:37:52 +0100249
tierno639520f2017-04-05 19:55:36 +0200250 for log_module in ("nfvo", "http", "vim", "db", "console", "ovim"):
tierno73ad9e42016-09-12 18:11:11 +0200251 log_level_module = "log_level_" + log_module
252 log_file_module = "log_file_" + log_module
253 logger_module = logging.getLogger('openmano.' + log_module)
254 if log_level_module in global_config:
255 logger_module.setLevel(global_config[log_level_module])
256 if log_file_module in global_config:
257 try:
258 file_handler= logging.handlers.RotatingFileHandler(global_config[log_file_module], maxBytes=100e6, backupCount=9, delay=0)
259 file_handler.setFormatter(log_formatter_simple)
260 logger_module.addHandler(file_handler)
261 except IOError as e:
262 raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(global_config[log_file_module], str(e)) )
263 global_config["logger_"+log_module] = logger_module
264 #httpserver.logger = global_config["logger_http"]
265 #nfvo.logger = global_config["logger_nfvo"]
266
tierno7edb6752016-03-21 17:37:52 +0100267 # Initialize DB connection
tiernob13f3cc2016-09-26 10:14:44 +0200268 mydb = nfvo_db.nfvo_db();
tierno44528e42016-10-11 12:06:25 +0000269 mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
tierno11f81f62017-04-27 17:22:14 +0200270 db_path = osm_ro.__path__[0] + "/database_utils"
271 if not os.path.exists(db_path + "/migrate_mano_db.sh"):
272 db_path = osm_ro.__path__[0] + "/../database_utils"
tierno44528e42016-10-11 12:06:25 +0000273 try:
274 r = mydb.get_db_version()
tierno11f81f62017-04-27 17:22:14 +0200275 if r[0] != database_version:
276 logger.critical("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
277 " with '{db_path}/migrate_mano_db.sh {target}'".format(
278 current=r[0], target=database_version, db_path=db_path))
tierno44528e42016-10-11 12:06:25 +0000279 exit(-1)
280 except db_base_Exception as e:
tierno11f81f62017-04-27 17:22:14 +0200281 logger.critical("DATABASE is not valid. If you think it is corrupted, you can init it with"
282 " '{db_path}/init_mano_db.sh' script".format(db_path=db_path))
tierno7edb6752016-03-21 17:37:52 +0100283 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000284
tierno7edb6752016-03-21 17:37:52 +0100285 nfvo.global_config=global_config
tierno42026a02017-02-10 15:13:40 +0100286 nfvo.start_service(mydb)
tierno7edb6752016-03-21 17:37:52 +0100287
288 httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
289
290 httpthread.start()
291 if 'http_admin_port' in global_config:
292 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
293 httpthreadadmin.start()
294 time.sleep(1)
tiernoae4a8d12016-07-08 12:30:39 +0200295 logger.info('Waiting for http clients')
tierno205d1022016-07-21 11:26:22 +0200296 print('Waiting for http clients')
tiernoae4a8d12016-07-08 12:30:39 +0200297 print('openmanod ready')
298 print('====================')
tierno7edb6752016-03-21 17:37:52 +0100299 time.sleep(20)
300 sys.stdout.flush()
301
302 #TODO: Interactive console must be implemented here instead of join or sleep
303
304 #httpthread.join()
tierno42026a02017-02-10 15:13:40 +0100305 #if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100306 # httpthreadadmin.join()
307 while True:
308 time.sleep(86400)
tierno7edb6752016-03-21 17:37:52 +0100309
tierno72f35a52016-07-15 13:18:30 +0200310 except KeyboardInterrupt as e:
311 logger.info(str(e))
tierno809a7802016-07-08 13:31:24 +0200312 except SystemExit:
313 pass
tiernoae4a8d12016-07-08 12:30:39 +0200314 except getopt.GetoptError as e:
tierno72f35a52016-07-15 13:18:30 +0200315 logger.critical(str(e)) # will print something like "option -a not recognized"
tiernoae4a8d12016-07-08 12:30:39 +0200316 #usage()
317 exit(-1)
318 except LoadConfigurationException as e:
tierno72f35a52016-07-15 13:18:30 +0200319 logger.critical(str(e))
tiernoae4a8d12016-07-08 12:30:39 +0200320 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000321 except db_base_Exception as e:
322 logger.critical(str(e))
323 exit(-1)
tierno42026a02017-02-10 15:13:40 +0100324 nfvo.stop_service()
325 if httpthread:
326 httpthread.join(1)
tierno7edb6752016-03-21 17:37:52 +0100327