blob: 94924bd720ef4ee93948a4d585baf3ba1ae98faf [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$"
tierno8e690322017-08-10 15:58:50 +020051__version__ = "0.5.21-r531"
tierno56d73d22017-08-02 13:53:02 +020052version_date = "Aug 2017"
tierno8e690322017-08-10 15:58:50 +020053database_version = 24 # 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
mirabal29356312017-07-27 12:21:22 +020058
tiernoae4a8d12016-07-08 12:30:39 +020059class LoadConfigurationException(Exception):
60 pass
tierno7edb6752016-03-21 17:37:52 +010061
62def load_configuration(configuration_file):
tierno639520f2017-04-05 19:55:36 +020063 default_tokens = {'http_port':9090,
64 'http_host':'localhost',
65 'http_console_proxy': True,
66 'http_console_host': None,
67 'log_level': 'DEBUG',
68 'log_socket_port': 9022,
69 'auto_push_VNF_to_VIMs': True,
70 'db_host': 'localhost',
71 'db_ovim_host': 'localhost'
72 }
tierno7edb6752016-03-21 17:37:52 +010073 try:
74 #Check config file exists
tierno72f35a52016-07-15 13:18:30 +020075 with open(configuration_file, 'r') as f:
76 config_str = f.read()
tierno7edb6752016-03-21 17:37:52 +010077 #Parse configuration file
tierno72f35a52016-07-15 13:18:30 +020078 config = yaml.load(config_str)
tierno7edb6752016-03-21 17:37:52 +010079 #Validate configuration file with the config_schema
tierno72f35a52016-07-15 13:18:30 +020080 js_v(config, config_schema)
tierno46df9672017-05-26 13:12:21 +020081
tierno72f35a52016-07-15 13:18:30 +020082 #Add default values tokens
tierno7edb6752016-03-21 17:37:52 +010083 for k,v in default_tokens.items():
tierno72f35a52016-07-15 13:18:30 +020084 if k not in config:
85 config[k]=v
86 return config
tierno46df9672017-05-26 13:12:21 +020087
tierno72f35a52016-07-15 13:18:30 +020088 except yaml.YAMLError as e:
89 error_pos = ""
90 if hasattr(e, 'problem_mark'):
91 mark = e.problem_mark
92 error_pos = " at line:{} column:{}".format(mark.line+1, mark.column+1)
tierno46df9672017-05-26 13:12:21 +020093 raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}: {message}".format(
94 file=configuration_file, pos=error_pos, message=e))
tierno72f35a52016-07-15 13:18:30 +020095 except js_e.ValidationError as e:
96 error_pos = ""
97 if e.path:
98 error_pos=" at '" + ":".join(map(str, e.path))+"'"
tierno46df9672017-05-26 13:12:21 +020099 raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format(
100 file=configuration_file, pos=error_pos, message=e))
tierno72f35a52016-07-15 13:18:30 +0200101 except Exception as e:
tierno46df9672017-05-26 13:12:21 +0200102 raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format(
103 file=configuration_file, message=e))
104
tierno7edb6752016-03-21 17:37:52 +0100105
106def console_port_iterator():
107 '''this iterator deals with the http_console_ports
108 returning the ports one by one
109 '''
110 index = 0
111 while index < len(global_config["http_console_ports"]):
112 port = global_config["http_console_ports"][index]
tiernoae4a8d12016-07-08 12:30:39 +0200113 #print("ports -> ", port)
tierno7edb6752016-03-21 17:37:52 +0100114 if type(port) is int:
115 yield port
116 else: #this is dictionary with from to keys
117 port2 = port["from"]
tiernoae4a8d12016-07-08 12:30:39 +0200118 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100119 while port2 <= port["to"]:
tiernoae4a8d12016-07-08 12:30:39 +0200120 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100121 yield port2
122 port2 += 1
123 index += 1
tierno46df9672017-05-26 13:12:21 +0200124
125
tierno7edb6752016-03-21 17:37:52 +0100126def usage():
tiernoae4a8d12016-07-08 12:30:39 +0200127 print("Usage: ", sys.argv[0], "[options]")
128 print( " -v|--version: prints current version")
129 print( " -c|--config [configuration_file]: loads the configuration file (default: openmanod.cfg)")
130 print( " -h|--help: shows this help")
131 print( " -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)")
132 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 +0200133 #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 +0200134 print( " --log-socket-host HOST: send logs to this host")
135 print( " --log-socket-port PORT: send logs using this port (default: 9022)")
136 print( " --log-file FILE: send logs to this file")
tierno7edb6752016-03-21 17:37:52 +0100137 return
tierno46df9672017-05-26 13:12:21 +0200138
139
140def set_logging_file(log_file):
141 try:
142 file_handler = logging.handlers.RotatingFileHandler(log_file, maxBytes=100e6, backupCount=9, delay=0)
143 file_handler.setFormatter(log_formatter_simple)
144 logger.addHandler(file_handler)
145 # logger.debug("moving logs to '%s'", global_config["log_file"])
146 # remove initial stream handler
147 logging.root.removeHandler(logging.root.handlers[0])
148 print ("logging on '{}'".format(log_file))
149 except IOError as e:
150 raise LoadConfigurationException(
151 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(log_file, e))
152
153
tierno7edb6752016-03-21 17:37:52 +0100154if __name__=="__main__":
tierno46df9672017-05-26 13:12:21 +0200155 # Configure logging step 1
tierno72f35a52016-07-15 13:18:30 +0200156 hostname = socket.gethostname()
tierno46df9672017-05-26 13:12:21 +0200157 # streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
tierno72f35a52016-07-15 13:18:30 +0200158 # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
tierno46df9672017-05-26 13:12:21 +0200159 log_formatter_complete = logging.Formatter('%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s '
160 'severity:%(levelname)s logger:%(name)s log:%(message)s'.format(
161 host=hostname),
162 datefmt='%Y-%m-%dT%H:%M:%S')
tierno72f35a52016-07-15 13:18:30 +0200163 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
164 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
165 logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
166 logger = logging.getLogger('openmano')
tiernoae4a8d12016-07-08 12:30:39 +0200167 logger.setLevel(logging.DEBUG)
tierno72f35a52016-07-15 13:18:30 +0200168 socket_handler = None
tierno42026a02017-02-10 15:13:40 +0100169 # Read parameters and configuration file
170 httpthread = None
tierno7edb6752016-03-21 17:37:52 +0100171 try:
tierno72f35a52016-07-15 13:18:30 +0200172 #load parameters and configuration
tierno73ad9e42016-09-12 18:11:11 +0200173 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 +0200174 port=None
175 port_admin = None
garciadeblas4b6216b2017-04-20 16:41:52 +0200176 config_file = 'osm_ro/openmanod.cfg'
tiernoae4a8d12016-07-08 12:30:39 +0200177 vnf_repository = None
tierno205d1022016-07-21 11:26:22 +0200178 log_file = None
tierno72f35a52016-07-15 13:18:30 +0200179 log_socket_host = None
180 log_socket_port = None
tierno46df9672017-05-26 13:12:21 +0200181
tiernoae4a8d12016-07-08 12:30:39 +0200182 for o, a in opts:
183 if o in ("-v", "--version"):
tierno72f35a52016-07-15 13:18:30 +0200184 print ("openmanod version " + __version__ + ' ' + version_date)
185 print ("(c) Copyright Telefonica")
tiernoae4a8d12016-07-08 12:30:39 +0200186 sys.exit()
187 elif o in ("-h", "--help"):
188 usage()
189 sys.exit()
190 elif o in ("-V", "--vnf-repository"):
191 vnf_repository = a
192 elif o in ("-c", "--config"):
193 config_file = a
194 elif o in ("-p", "--port"):
195 port = a
196 elif o in ("-P", "--adminport"):
197 port_admin = a
tierno72f35a52016-07-15 13:18:30 +0200198 elif o == "--log-socket-port":
199 log_socket_port = a
tiernoa9a7e622016-09-30 08:43:32 +0000200 elif o == "--log-socket-host":
tierno72f35a52016-07-15 13:18:30 +0200201 log_socket_host = a
tierno205d1022016-07-21 11:26:22 +0200202 elif o == "--log-file":
203 log_file = a
tiernoae4a8d12016-07-08 12:30:39 +0200204 else:
205 assert False, "Unhandled option"
tierno46df9672017-05-26 13:12:21 +0200206 if log_file:
207 set_logging_file(log_file)
tiernoae4a8d12016-07-08 12:30:39 +0200208 global_config = load_configuration(config_file)
tierno6ddeded2017-05-16 15:40:26 +0200209 global_config["version"] = __version__
210 global_config["version_date"] = version_date
tierno7edb6752016-03-21 17:37:52 +0100211 #print global_config
tierno72f35a52016-07-15 13:18:30 +0200212 # Override parameters obtained by command line
213 if port:
214 global_config['http_port'] = port
215 if port_admin:
216 global_config['http_admin_port'] = port_admin
217 if log_socket_host:
218 global_config['log_socket_host'] = log_socket_host
219 if log_socket_port:
220 global_config['log_socket_port'] = log_socket_port
221# if vnf_repository is not None:
222# global_config['vnf_repository'] = vnf_repository
223# else:
tierno46df9672017-05-26 13:12:21 +0200224# if not 'vnf_repository' in global_config:
tierno72f35a52016-07-15 13:18:30 +0200225# logger.error( os.getcwd() )
226# global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
227# #print global_config
228# if not os.path.exists(global_config['vnf_repository']):
229# logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
230# try:
231# os.makedirs(global_config['vnf_repository'])
232# except Exception as e:
233# logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
234# exit(-1)
tierno46df9672017-05-26 13:12:21 +0200235
tierno72f35a52016-07-15 13:18:30 +0200236 global_config["console_port_iterator"] = console_port_iterator
237 global_config["console_thread"]={}
238 global_config["console_ports"]={}
tierno20fc2a22016-08-19 17:02:35 +0200239 if not global_config["http_console_host"]:
240 global_config["http_console_host"] = global_config["http_host"]
241 if global_config["http_host"]=="0.0.0.0":
242 global_config["http_console_host"] = socket.gethostname()
tierno46df9672017-05-26 13:12:21 +0200243
244 # Configure logging STEP 2
tiernof97fd272016-07-11 14:32:37 +0200245 if "log_host" in global_config:
tierno72f35a52016-07-15 13:18:30 +0200246 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
247 socket_handler.setFormatter(log_formatter_complete)
tierno46df9672017-05-26 13:12:21 +0200248 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
tierno72f35a52016-07-15 13:18:30 +0200249 socket_handler.setLevel(global_config["log_socket_level"])
tiernof97fd272016-07-11 14:32:37 +0200250 logger.addHandler(socket_handler)
tierno46df9672017-05-26 13:12:21 +0200251
252 # logger.addHandler(log_handlers.SysLogHandler())
253 if log_file:
254 global_config['log_file'] = log_file
255 elif global_config.get('log_file'):
256 set_logging_file(global_config['log_file'])
257
258 # logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
tierno205d1022016-07-21 11:26:22 +0200259 logger.setLevel(getattr(logging, global_config['log_level']))
tierno46df9672017-05-26 13:12:21 +0200260 logger.critical("Starting openmano server version: '%s %s' command: '%s'",
tiernoaceb4642016-10-20 15:12:30 +0000261 __version__, version_date, " ".join(sys.argv))
tierno46df9672017-05-26 13:12:21 +0200262
tierno639520f2017-04-05 19:55:36 +0200263 for log_module in ("nfvo", "http", "vim", "db", "console", "ovim"):
tierno73ad9e42016-09-12 18:11:11 +0200264 log_level_module = "log_level_" + log_module
265 log_file_module = "log_file_" + log_module
266 logger_module = logging.getLogger('openmano.' + log_module)
267 if log_level_module in global_config:
268 logger_module.setLevel(global_config[log_level_module])
269 if log_file_module in global_config:
270 try:
tierno46df9672017-05-26 13:12:21 +0200271 file_handler = logging.handlers.RotatingFileHandler(global_config[log_file_module],
272 maxBytes=100e6, backupCount=9, delay=0)
tierno73ad9e42016-09-12 18:11:11 +0200273 file_handler.setFormatter(log_formatter_simple)
274 logger_module.addHandler(file_handler)
275 except IOError as e:
tierno46df9672017-05-26 13:12:21 +0200276 raise LoadConfigurationException(
277 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(
278 global_config[log_file_module], str(e)) )
tierno73ad9e42016-09-12 18:11:11 +0200279 global_config["logger_"+log_module] = logger_module
280 #httpserver.logger = global_config["logger_http"]
281 #nfvo.logger = global_config["logger_nfvo"]
tierno46df9672017-05-26 13:12:21 +0200282
tierno7edb6752016-03-21 17:37:52 +0100283 # Initialize DB connection
tiernob13f3cc2016-09-26 10:14:44 +0200284 mydb = nfvo_db.nfvo_db();
tierno44528e42016-10-11 12:06:25 +0000285 mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
tierno11f81f62017-04-27 17:22:14 +0200286 db_path = osm_ro.__path__[0] + "/database_utils"
287 if not os.path.exists(db_path + "/migrate_mano_db.sh"):
288 db_path = osm_ro.__path__[0] + "/../database_utils"
tierno44528e42016-10-11 12:06:25 +0000289 try:
290 r = mydb.get_db_version()
tierno11f81f62017-04-27 17:22:14 +0200291 if r[0] != database_version:
292 logger.critical("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
293 " with '{db_path}/migrate_mano_db.sh {target}'".format(
294 current=r[0], target=database_version, db_path=db_path))
tierno44528e42016-10-11 12:06:25 +0000295 exit(-1)
296 except db_base_Exception as e:
tierno11f81f62017-04-27 17:22:14 +0200297 logger.critical("DATABASE is not valid. If you think it is corrupted, you can init it with"
298 " '{db_path}/init_mano_db.sh' script".format(db_path=db_path))
tierno7edb6752016-03-21 17:37:52 +0100299 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000300
tierno7edb6752016-03-21 17:37:52 +0100301 nfvo.global_config=global_config
tierno42026a02017-02-10 15:13:40 +0100302 nfvo.start_service(mydb)
tierno46df9672017-05-26 13:12:21 +0200303
tierno7edb6752016-03-21 17:37:52 +0100304 httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
tierno46df9672017-05-26 13:12:21 +0200305
tierno7edb6752016-03-21 17:37:52 +0100306 httpthread.start()
tierno46df9672017-05-26 13:12:21 +0200307 if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100308 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
309 httpthreadadmin.start()
tierno46df9672017-05-26 13:12:21 +0200310 time.sleep(1)
tiernoae4a8d12016-07-08 12:30:39 +0200311 logger.info('Waiting for http clients')
tierno205d1022016-07-21 11:26:22 +0200312 print('Waiting for http clients')
tiernoae4a8d12016-07-08 12:30:39 +0200313 print('openmanod ready')
314 print('====================')
tierno7edb6752016-03-21 17:37:52 +0100315 time.sleep(20)
316 sys.stdout.flush()
317
318 #TODO: Interactive console must be implemented here instead of join or sleep
319
320 #httpthread.join()
tierno42026a02017-02-10 15:13:40 +0100321 #if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100322 # httpthreadadmin.join()
323 while True:
324 time.sleep(86400)
tierno7edb6752016-03-21 17:37:52 +0100325
tierno72f35a52016-07-15 13:18:30 +0200326 except KeyboardInterrupt as e:
327 logger.info(str(e))
tierno809a7802016-07-08 13:31:24 +0200328 except SystemExit:
329 pass
tiernoae4a8d12016-07-08 12:30:39 +0200330 except getopt.GetoptError as e:
tierno72f35a52016-07-15 13:18:30 +0200331 logger.critical(str(e)) # will print something like "option -a not recognized"
tiernoae4a8d12016-07-08 12:30:39 +0200332 #usage()
333 exit(-1)
334 except LoadConfigurationException as e:
tierno72f35a52016-07-15 13:18:30 +0200335 logger.critical(str(e))
tiernoae4a8d12016-07-08 12:30:39 +0200336 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000337 except db_base_Exception as e:
338 logger.critical(str(e))
339 exit(-1)
tierno46df9672017-05-26 13:12:21 +0200340 except nfvo.NfvoException as e:
341 logger.critical(str(e), exc_info=True)
342 exit(-1)
tierno42026a02017-02-10 15:13:40 +0100343 nfvo.stop_service()
344 if httpthread:
345 httpthread.join(1)
tierno7edb6752016-03-21 17:37:52 +0100346