blob: e8bd2da4bc515f308276aeb669c9b5c948076294 [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$"
tierno46df9672017-05-26 13:12:21 +020051__version__ = "0.5.14-r523"
tierno11f81f62017-04-27 17:22:14 +020052version_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)
tierno46df9672017-05-26 13:12:21 +020080
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
tierno46df9672017-05-26 13:12:21 +020086
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)
tierno46df9672017-05-26 13:12:21 +020092 raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}: {message}".format(
93 file=configuration_file, pos=error_pos, message=e))
tierno72f35a52016-07-15 13:18:30 +020094 except js_e.ValidationError as e:
95 error_pos = ""
96 if e.path:
97 error_pos=" at '" + ":".join(map(str, e.path))+"'"
tierno46df9672017-05-26 13:12:21 +020098 raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format(
99 file=configuration_file, pos=error_pos, message=e))
tierno72f35a52016-07-15 13:18:30 +0200100 except Exception as e:
tierno46df9672017-05-26 13:12:21 +0200101 raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format(
102 file=configuration_file, message=e))
103
tierno7edb6752016-03-21 17:37:52 +0100104
105def console_port_iterator():
106 '''this iterator deals with the http_console_ports
107 returning the ports one by one
108 '''
109 index = 0
110 while index < len(global_config["http_console_ports"]):
111 port = global_config["http_console_ports"][index]
tiernoae4a8d12016-07-08 12:30:39 +0200112 #print("ports -> ", port)
tierno7edb6752016-03-21 17:37:52 +0100113 if type(port) is int:
114 yield port
115 else: #this is dictionary with from to keys
116 port2 = port["from"]
tiernoae4a8d12016-07-08 12:30:39 +0200117 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100118 while port2 <= port["to"]:
tiernoae4a8d12016-07-08 12:30:39 +0200119 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100120 yield port2
121 port2 += 1
122 index += 1
tierno46df9672017-05-26 13:12:21 +0200123
124
tierno7edb6752016-03-21 17:37:52 +0100125def usage():
tiernoae4a8d12016-07-08 12:30:39 +0200126 print("Usage: ", sys.argv[0], "[options]")
127 print( " -v|--version: prints current version")
128 print( " -c|--config [configuration_file]: loads the configuration file (default: openmanod.cfg)")
129 print( " -h|--help: shows this help")
130 print( " -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)")
131 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 +0200132 #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 +0200133 print( " --log-socket-host HOST: send logs to this host")
134 print( " --log-socket-port PORT: send logs using this port (default: 9022)")
135 print( " --log-file FILE: send logs to this file")
tierno7edb6752016-03-21 17:37:52 +0100136 return
tierno46df9672017-05-26 13:12:21 +0200137
138
139def set_logging_file(log_file):
140 try:
141 file_handler = logging.handlers.RotatingFileHandler(log_file, maxBytes=100e6, backupCount=9, delay=0)
142 file_handler.setFormatter(log_formatter_simple)
143 logger.addHandler(file_handler)
144 # logger.debug("moving logs to '%s'", global_config["log_file"])
145 # remove initial stream handler
146 logging.root.removeHandler(logging.root.handlers[0])
147 print ("logging on '{}'".format(log_file))
148 except IOError as e:
149 raise LoadConfigurationException(
150 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(log_file, e))
151
152
tierno7edb6752016-03-21 17:37:52 +0100153if __name__=="__main__":
tierno46df9672017-05-26 13:12:21 +0200154 # Configure logging step 1
tierno72f35a52016-07-15 13:18:30 +0200155 hostname = socket.gethostname()
tierno46df9672017-05-26 13:12:21 +0200156 # streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
tierno72f35a52016-07-15 13:18:30 +0200157 # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
tierno46df9672017-05-26 13:12:21 +0200158 log_formatter_complete = logging.Formatter('%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s '
159 'severity:%(levelname)s logger:%(name)s log:%(message)s'.format(
160 host=hostname),
161 datefmt='%Y-%m-%dT%H:%M:%S')
tierno72f35a52016-07-15 13:18:30 +0200162 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
163 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
164 logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
165 logger = logging.getLogger('openmano')
tiernoae4a8d12016-07-08 12:30:39 +0200166 logger.setLevel(logging.DEBUG)
tierno72f35a52016-07-15 13:18:30 +0200167 socket_handler = None
tierno42026a02017-02-10 15:13:40 +0100168 # Read parameters and configuration file
169 httpthread = None
tierno7edb6752016-03-21 17:37:52 +0100170 try:
tierno72f35a52016-07-15 13:18:30 +0200171 #load parameters and configuration
tierno73ad9e42016-09-12 18:11:11 +0200172 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 +0200173 port=None
174 port_admin = None
garciadeblas4b6216b2017-04-20 16:41:52 +0200175 config_file = 'osm_ro/openmanod.cfg'
tiernoae4a8d12016-07-08 12:30:39 +0200176 vnf_repository = None
tierno205d1022016-07-21 11:26:22 +0200177 log_file = None
tierno72f35a52016-07-15 13:18:30 +0200178 log_socket_host = None
179 log_socket_port = None
tierno46df9672017-05-26 13:12:21 +0200180
tiernoae4a8d12016-07-08 12:30:39 +0200181 for o, a in opts:
182 if o in ("-v", "--version"):
tierno72f35a52016-07-15 13:18:30 +0200183 print ("openmanod version " + __version__ + ' ' + version_date)
184 print ("(c) Copyright Telefonica")
tiernoae4a8d12016-07-08 12:30:39 +0200185 sys.exit()
186 elif o in ("-h", "--help"):
187 usage()
188 sys.exit()
189 elif o in ("-V", "--vnf-repository"):
190 vnf_repository = a
191 elif o in ("-c", "--config"):
192 config_file = a
193 elif o in ("-p", "--port"):
194 port = a
195 elif o in ("-P", "--adminport"):
196 port_admin = a
tierno72f35a52016-07-15 13:18:30 +0200197 elif o == "--log-socket-port":
198 log_socket_port = a
tiernoa9a7e622016-09-30 08:43:32 +0000199 elif o == "--log-socket-host":
tierno72f35a52016-07-15 13:18:30 +0200200 log_socket_host = a
tierno205d1022016-07-21 11:26:22 +0200201 elif o == "--log-file":
202 log_file = a
tiernoae4a8d12016-07-08 12:30:39 +0200203 else:
204 assert False, "Unhandled option"
tierno46df9672017-05-26 13:12:21 +0200205 if log_file:
206 set_logging_file(log_file)
tiernoae4a8d12016-07-08 12:30:39 +0200207 global_config = load_configuration(config_file)
tierno6ddeded2017-05-16 15:40:26 +0200208 global_config["version"] = __version__
209 global_config["version_date"] = version_date
tierno7edb6752016-03-21 17:37:52 +0100210 #print global_config
tierno72f35a52016-07-15 13:18:30 +0200211 # Override parameters obtained by command line
212 if port:
213 global_config['http_port'] = port
214 if port_admin:
215 global_config['http_admin_port'] = port_admin
216 if log_socket_host:
217 global_config['log_socket_host'] = log_socket_host
218 if log_socket_port:
219 global_config['log_socket_port'] = log_socket_port
220# if vnf_repository is not None:
221# global_config['vnf_repository'] = vnf_repository
222# else:
tierno46df9672017-05-26 13:12:21 +0200223# if not 'vnf_repository' in global_config:
tierno72f35a52016-07-15 13:18:30 +0200224# logger.error( os.getcwd() )
225# global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
226# #print global_config
227# if not os.path.exists(global_config['vnf_repository']):
228# logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
229# try:
230# os.makedirs(global_config['vnf_repository'])
231# except Exception as e:
232# logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
233# exit(-1)
tierno46df9672017-05-26 13:12:21 +0200234
tierno72f35a52016-07-15 13:18:30 +0200235 global_config["console_port_iterator"] = console_port_iterator
236 global_config["console_thread"]={}
237 global_config["console_ports"]={}
tierno20fc2a22016-08-19 17:02:35 +0200238 if not global_config["http_console_host"]:
239 global_config["http_console_host"] = global_config["http_host"]
240 if global_config["http_host"]=="0.0.0.0":
241 global_config["http_console_host"] = socket.gethostname()
tierno46df9672017-05-26 13:12:21 +0200242
243 # Configure logging STEP 2
tiernof97fd272016-07-11 14:32:37 +0200244 if "log_host" in global_config:
tierno72f35a52016-07-15 13:18:30 +0200245 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
246 socket_handler.setFormatter(log_formatter_complete)
tierno46df9672017-05-26 13:12:21 +0200247 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
tierno72f35a52016-07-15 13:18:30 +0200248 socket_handler.setLevel(global_config["log_socket_level"])
tiernof97fd272016-07-11 14:32:37 +0200249 logger.addHandler(socket_handler)
tierno46df9672017-05-26 13:12:21 +0200250
251 # logger.addHandler(log_handlers.SysLogHandler())
252 if log_file:
253 global_config['log_file'] = log_file
254 elif global_config.get('log_file'):
255 set_logging_file(global_config['log_file'])
256
257 # logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
tierno205d1022016-07-21 11:26:22 +0200258 logger.setLevel(getattr(logging, global_config['log_level']))
tierno46df9672017-05-26 13:12:21 +0200259 logger.critical("Starting openmano server version: '%s %s' command: '%s'",
tiernoaceb4642016-10-20 15:12:30 +0000260 __version__, version_date, " ".join(sys.argv))
tierno46df9672017-05-26 13:12:21 +0200261
tierno639520f2017-04-05 19:55:36 +0200262 for log_module in ("nfvo", "http", "vim", "db", "console", "ovim"):
tierno73ad9e42016-09-12 18:11:11 +0200263 log_level_module = "log_level_" + log_module
264 log_file_module = "log_file_" + log_module
265 logger_module = logging.getLogger('openmano.' + log_module)
266 if log_level_module in global_config:
267 logger_module.setLevel(global_config[log_level_module])
268 if log_file_module in global_config:
269 try:
tierno46df9672017-05-26 13:12:21 +0200270 file_handler = logging.handlers.RotatingFileHandler(global_config[log_file_module],
271 maxBytes=100e6, backupCount=9, delay=0)
tierno73ad9e42016-09-12 18:11:11 +0200272 file_handler.setFormatter(log_formatter_simple)
273 logger_module.addHandler(file_handler)
274 except IOError as e:
tierno46df9672017-05-26 13:12:21 +0200275 raise LoadConfigurationException(
276 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(
277 global_config[log_file_module], str(e)) )
tierno73ad9e42016-09-12 18:11:11 +0200278 global_config["logger_"+log_module] = logger_module
279 #httpserver.logger = global_config["logger_http"]
280 #nfvo.logger = global_config["logger_nfvo"]
tierno46df9672017-05-26 13:12:21 +0200281
tierno7edb6752016-03-21 17:37:52 +0100282 # Initialize DB connection
tiernob13f3cc2016-09-26 10:14:44 +0200283 mydb = nfvo_db.nfvo_db();
tierno44528e42016-10-11 12:06:25 +0000284 mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
tierno11f81f62017-04-27 17:22:14 +0200285 db_path = osm_ro.__path__[0] + "/database_utils"
286 if not os.path.exists(db_path + "/migrate_mano_db.sh"):
287 db_path = osm_ro.__path__[0] + "/../database_utils"
tierno44528e42016-10-11 12:06:25 +0000288 try:
289 r = mydb.get_db_version()
tierno11f81f62017-04-27 17:22:14 +0200290 if r[0] != database_version:
291 logger.critical("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
292 " with '{db_path}/migrate_mano_db.sh {target}'".format(
293 current=r[0], target=database_version, db_path=db_path))
tierno44528e42016-10-11 12:06:25 +0000294 exit(-1)
295 except db_base_Exception as e:
tierno11f81f62017-04-27 17:22:14 +0200296 logger.critical("DATABASE is not valid. If you think it is corrupted, you can init it with"
297 " '{db_path}/init_mano_db.sh' script".format(db_path=db_path))
tierno7edb6752016-03-21 17:37:52 +0100298 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000299
tierno7edb6752016-03-21 17:37:52 +0100300 nfvo.global_config=global_config
tierno42026a02017-02-10 15:13:40 +0100301 nfvo.start_service(mydb)
tierno46df9672017-05-26 13:12:21 +0200302
tierno7edb6752016-03-21 17:37:52 +0100303 httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
tierno46df9672017-05-26 13:12:21 +0200304
tierno7edb6752016-03-21 17:37:52 +0100305 httpthread.start()
tierno46df9672017-05-26 13:12:21 +0200306 if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100307 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
308 httpthreadadmin.start()
tierno46df9672017-05-26 13:12:21 +0200309 time.sleep(1)
tiernoae4a8d12016-07-08 12:30:39 +0200310 logger.info('Waiting for http clients')
tierno205d1022016-07-21 11:26:22 +0200311 print('Waiting for http clients')
tiernoae4a8d12016-07-08 12:30:39 +0200312 print('openmanod ready')
313 print('====================')
tierno7edb6752016-03-21 17:37:52 +0100314 time.sleep(20)
315 sys.stdout.flush()
316
317 #TODO: Interactive console must be implemented here instead of join or sleep
318
319 #httpthread.join()
tierno42026a02017-02-10 15:13:40 +0100320 #if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100321 # httpthreadadmin.join()
322 while True:
323 time.sleep(86400)
tierno7edb6752016-03-21 17:37:52 +0100324
tierno72f35a52016-07-15 13:18:30 +0200325 except KeyboardInterrupt as e:
326 logger.info(str(e))
tierno809a7802016-07-08 13:31:24 +0200327 except SystemExit:
328 pass
tiernoae4a8d12016-07-08 12:30:39 +0200329 except getopt.GetoptError as e:
tierno72f35a52016-07-15 13:18:30 +0200330 logger.critical(str(e)) # will print something like "option -a not recognized"
tiernoae4a8d12016-07-08 12:30:39 +0200331 #usage()
332 exit(-1)
333 except LoadConfigurationException as e:
tierno72f35a52016-07-15 13:18:30 +0200334 logger.critical(str(e))
tiernoae4a8d12016-07-08 12:30:39 +0200335 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000336 except db_base_Exception as e:
337 logger.critical(str(e))
338 exit(-1)
tierno46df9672017-05-26 13:12:21 +0200339 except nfvo.NfvoException as e:
340 logger.critical(str(e), exc_info=True)
341 exit(-1)
tierno42026a02017-02-10 15:13:40 +0100342 nfvo.stop_service()
343 if httpthread:
344 httpthread.join(1)
tierno7edb6752016-03-21 17:37:52 +0100345