blob: b9858b4718069f2a2b04b0d1426eda3c50b7b480 [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$"
tiernoad6bdd42018-01-10 10:43:46 +010051__version__ = "0.5.47-r557"
52version_date = "Jan 2018"
gcalvinoe580c7d2017-09-22 14:09:51 +020053database_version = 27 # expected database schema version
54
tierno99314902017-04-26 13:23:09 +020055
tierno7edb6752016-03-21 17:37:52 +010056global global_config
tiernof97fd272016-07-11 14:32:37 +020057global logger
tiernoae4a8d12016-07-08 12:30:39 +020058
mirabal29356312017-07-27 12:21:22 +020059
tiernoae4a8d12016-07-08 12:30:39 +020060class LoadConfigurationException(Exception):
61 pass
tierno7edb6752016-03-21 17:37:52 +010062
tiernoad6bdd42018-01-10 10:43:46 +010063
tierno7edb6752016-03-21 17:37:52 +010064def load_configuration(configuration_file):
tiernoad6bdd42018-01-10 10:43:46 +010065 default_tokens = {'http_port': 9090,
66 'http_host': 'localhost',
tierno639520f2017-04-05 19:55:36 +020067 'http_console_proxy': True,
68 'http_console_host': None,
69 'log_level': 'DEBUG',
70 'log_socket_port': 9022,
71 'auto_push_VNF_to_VIMs': True,
72 'db_host': 'localhost',
73 'db_ovim_host': 'localhost'
74 }
tierno7edb6752016-03-21 17:37:52 +010075 try:
76 #Check config file exists
tierno72f35a52016-07-15 13:18:30 +020077 with open(configuration_file, 'r') as f:
78 config_str = f.read()
tierno7edb6752016-03-21 17:37:52 +010079 #Parse configuration file
tierno72f35a52016-07-15 13:18:30 +020080 config = yaml.load(config_str)
tierno7edb6752016-03-21 17:37:52 +010081 #Validate configuration file with the config_schema
tierno72f35a52016-07-15 13:18:30 +020082 js_v(config, config_schema)
tierno46df9672017-05-26 13:12:21 +020083
tierno72f35a52016-07-15 13:18:30 +020084 #Add default values tokens
tierno7edb6752016-03-21 17:37:52 +010085 for k,v in default_tokens.items():
tierno72f35a52016-07-15 13:18:30 +020086 if k not in config:
87 config[k]=v
88 return config
tierno46df9672017-05-26 13:12:21 +020089
tierno72f35a52016-07-15 13:18:30 +020090 except yaml.YAMLError as e:
91 error_pos = ""
92 if hasattr(e, 'problem_mark'):
93 mark = e.problem_mark
94 error_pos = " at line:{} column:{}".format(mark.line+1, mark.column+1)
tierno46df9672017-05-26 13:12:21 +020095 raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}: {message}".format(
96 file=configuration_file, pos=error_pos, message=e))
tierno72f35a52016-07-15 13:18:30 +020097 except js_e.ValidationError as e:
98 error_pos = ""
99 if e.path:
100 error_pos=" at '" + ":".join(map(str, e.path))+"'"
tierno46df9672017-05-26 13:12:21 +0200101 raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format(
102 file=configuration_file, pos=error_pos, message=e))
tierno72f35a52016-07-15 13:18:30 +0200103 except Exception as e:
tierno46df9672017-05-26 13:12:21 +0200104 raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format(
105 file=configuration_file, message=e))
106
tierno7edb6752016-03-21 17:37:52 +0100107
108def console_port_iterator():
109 '''this iterator deals with the http_console_ports
110 returning the ports one by one
111 '''
112 index = 0
113 while index < len(global_config["http_console_ports"]):
114 port = global_config["http_console_ports"][index]
tiernoae4a8d12016-07-08 12:30:39 +0200115 #print("ports -> ", port)
tierno7edb6752016-03-21 17:37:52 +0100116 if type(port) is int:
117 yield port
118 else: #this is dictionary with from to keys
119 port2 = port["from"]
tiernoae4a8d12016-07-08 12:30:39 +0200120 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100121 while port2 <= port["to"]:
tiernoae4a8d12016-07-08 12:30:39 +0200122 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100123 yield port2
124 port2 += 1
125 index += 1
tierno46df9672017-05-26 13:12:21 +0200126
127
tierno7edb6752016-03-21 17:37:52 +0100128def usage():
tiernoae4a8d12016-07-08 12:30:39 +0200129 print("Usage: ", sys.argv[0], "[options]")
130 print( " -v|--version: prints current version")
131 print( " -c|--config [configuration_file]: loads the configuration file (default: openmanod.cfg)")
132 print( " -h|--help: shows this help")
133 print( " -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)")
134 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 +0200135 #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 +0200136 print( " --log-socket-host HOST: send logs to this host")
137 print( " --log-socket-port PORT: send logs using this port (default: 9022)")
138 print( " --log-file FILE: send logs to this file")
tierno7edb6752016-03-21 17:37:52 +0100139 return
tierno46df9672017-05-26 13:12:21 +0200140
141
142def set_logging_file(log_file):
143 try:
144 file_handler = logging.handlers.RotatingFileHandler(log_file, maxBytes=100e6, backupCount=9, delay=0)
145 file_handler.setFormatter(log_formatter_simple)
146 logger.addHandler(file_handler)
147 # logger.debug("moving logs to '%s'", global_config["log_file"])
148 # remove initial stream handler
149 logging.root.removeHandler(logging.root.handlers[0])
150 print ("logging on '{}'".format(log_file))
151 except IOError as e:
152 raise LoadConfigurationException(
153 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(log_file, e))
154
155
tierno7edb6752016-03-21 17:37:52 +0100156if __name__=="__main__":
tierno46df9672017-05-26 13:12:21 +0200157 # Configure logging step 1
tierno72f35a52016-07-15 13:18:30 +0200158 hostname = socket.gethostname()
tierno46df9672017-05-26 13:12:21 +0200159 # streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
tierno72f35a52016-07-15 13:18:30 +0200160 # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
tierno46df9672017-05-26 13:12:21 +0200161 log_formatter_complete = logging.Formatter('%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s '
162 'severity:%(levelname)s logger:%(name)s log:%(message)s'.format(
163 host=hostname),
164 datefmt='%Y-%m-%dT%H:%M:%S')
tierno72f35a52016-07-15 13:18:30 +0200165 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
166 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
167 logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
168 logger = logging.getLogger('openmano')
tiernoae4a8d12016-07-08 12:30:39 +0200169 logger.setLevel(logging.DEBUG)
tierno72f35a52016-07-15 13:18:30 +0200170 socket_handler = None
tierno42026a02017-02-10 15:13:40 +0100171 # Read parameters and configuration file
172 httpthread = None
tierno7edb6752016-03-21 17:37:52 +0100173 try:
tierno72f35a52016-07-15 13:18:30 +0200174 #load parameters and configuration
tierno73ad9e42016-09-12 18:11:11 +0200175 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 +0200176 port=None
177 port_admin = None
garciadeblas4b6216b2017-04-20 16:41:52 +0200178 config_file = 'osm_ro/openmanod.cfg'
tiernoae4a8d12016-07-08 12:30:39 +0200179 vnf_repository = None
tierno205d1022016-07-21 11:26:22 +0200180 log_file = None
tierno72f35a52016-07-15 13:18:30 +0200181 log_socket_host = None
182 log_socket_port = None
tierno46df9672017-05-26 13:12:21 +0200183
tiernoae4a8d12016-07-08 12:30:39 +0200184 for o, a in opts:
185 if o in ("-v", "--version"):
tierno72f35a52016-07-15 13:18:30 +0200186 print ("openmanod version " + __version__ + ' ' + version_date)
187 print ("(c) Copyright Telefonica")
tiernoae4a8d12016-07-08 12:30:39 +0200188 sys.exit()
189 elif o in ("-h", "--help"):
190 usage()
191 sys.exit()
192 elif o in ("-V", "--vnf-repository"):
193 vnf_repository = a
194 elif o in ("-c", "--config"):
195 config_file = a
196 elif o in ("-p", "--port"):
197 port = a
198 elif o in ("-P", "--adminport"):
199 port_admin = a
tierno72f35a52016-07-15 13:18:30 +0200200 elif o == "--log-socket-port":
201 log_socket_port = a
tiernoa9a7e622016-09-30 08:43:32 +0000202 elif o == "--log-socket-host":
tierno72f35a52016-07-15 13:18:30 +0200203 log_socket_host = a
tierno205d1022016-07-21 11:26:22 +0200204 elif o == "--log-file":
205 log_file = a
tiernoae4a8d12016-07-08 12:30:39 +0200206 else:
207 assert False, "Unhandled option"
tierno46df9672017-05-26 13:12:21 +0200208 if log_file:
209 set_logging_file(log_file)
tiernoae4a8d12016-07-08 12:30:39 +0200210 global_config = load_configuration(config_file)
tierno6ddeded2017-05-16 15:40:26 +0200211 global_config["version"] = __version__
212 global_config["version_date"] = version_date
tierno7edb6752016-03-21 17:37:52 +0100213 #print global_config
tierno72f35a52016-07-15 13:18:30 +0200214 # Override parameters obtained by command line
215 if port:
216 global_config['http_port'] = port
217 if port_admin:
218 global_config['http_admin_port'] = port_admin
219 if log_socket_host:
220 global_config['log_socket_host'] = log_socket_host
221 if log_socket_port:
222 global_config['log_socket_port'] = log_socket_port
223# if vnf_repository is not None:
224# global_config['vnf_repository'] = vnf_repository
225# else:
tierno46df9672017-05-26 13:12:21 +0200226# if not 'vnf_repository' in global_config:
tierno72f35a52016-07-15 13:18:30 +0200227# logger.error( os.getcwd() )
228# global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
229# #print global_config
230# if not os.path.exists(global_config['vnf_repository']):
231# logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
232# try:
233# os.makedirs(global_config['vnf_repository'])
234# except Exception as e:
235# logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
236# exit(-1)
tierno46df9672017-05-26 13:12:21 +0200237
tierno72f35a52016-07-15 13:18:30 +0200238 global_config["console_port_iterator"] = console_port_iterator
239 global_config["console_thread"]={}
240 global_config["console_ports"]={}
tierno20fc2a22016-08-19 17:02:35 +0200241 if not global_config["http_console_host"]:
242 global_config["http_console_host"] = global_config["http_host"]
243 if global_config["http_host"]=="0.0.0.0":
244 global_config["http_console_host"] = socket.gethostname()
tierno46df9672017-05-26 13:12:21 +0200245
246 # Configure logging STEP 2
tiernof97fd272016-07-11 14:32:37 +0200247 if "log_host" in global_config:
tierno72f35a52016-07-15 13:18:30 +0200248 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
249 socket_handler.setFormatter(log_formatter_complete)
tierno46df9672017-05-26 13:12:21 +0200250 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
tierno72f35a52016-07-15 13:18:30 +0200251 socket_handler.setLevel(global_config["log_socket_level"])
tiernof97fd272016-07-11 14:32:37 +0200252 logger.addHandler(socket_handler)
tierno46df9672017-05-26 13:12:21 +0200253
254 # logger.addHandler(log_handlers.SysLogHandler())
255 if log_file:
256 global_config['log_file'] = log_file
257 elif global_config.get('log_file'):
258 set_logging_file(global_config['log_file'])
259
260 # logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
tierno205d1022016-07-21 11:26:22 +0200261 logger.setLevel(getattr(logging, global_config['log_level']))
tierno46df9672017-05-26 13:12:21 +0200262 logger.critical("Starting openmano server version: '%s %s' command: '%s'",
tiernoaceb4642016-10-20 15:12:30 +0000263 __version__, version_date, " ".join(sys.argv))
tierno46df9672017-05-26 13:12:21 +0200264
tierno639520f2017-04-05 19:55:36 +0200265 for log_module in ("nfvo", "http", "vim", "db", "console", "ovim"):
tierno73ad9e42016-09-12 18:11:11 +0200266 log_level_module = "log_level_" + log_module
267 log_file_module = "log_file_" + log_module
268 logger_module = logging.getLogger('openmano.' + log_module)
269 if log_level_module in global_config:
270 logger_module.setLevel(global_config[log_level_module])
271 if log_file_module in global_config:
272 try:
tierno46df9672017-05-26 13:12:21 +0200273 file_handler = logging.handlers.RotatingFileHandler(global_config[log_file_module],
274 maxBytes=100e6, backupCount=9, delay=0)
tierno73ad9e42016-09-12 18:11:11 +0200275 file_handler.setFormatter(log_formatter_simple)
276 logger_module.addHandler(file_handler)
277 except IOError as e:
tierno46df9672017-05-26 13:12:21 +0200278 raise LoadConfigurationException(
279 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(
280 global_config[log_file_module], str(e)) )
tierno73ad9e42016-09-12 18:11:11 +0200281 global_config["logger_"+log_module] = logger_module
282 #httpserver.logger = global_config["logger_http"]
283 #nfvo.logger = global_config["logger_nfvo"]
tierno46df9672017-05-26 13:12:21 +0200284
tierno7edb6752016-03-21 17:37:52 +0100285 # Initialize DB connection
tiernob13f3cc2016-09-26 10:14:44 +0200286 mydb = nfvo_db.nfvo_db();
tierno44528e42016-10-11 12:06:25 +0000287 mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
tierno11f81f62017-04-27 17:22:14 +0200288 db_path = osm_ro.__path__[0] + "/database_utils"
289 if not os.path.exists(db_path + "/migrate_mano_db.sh"):
290 db_path = osm_ro.__path__[0] + "/../database_utils"
tierno44528e42016-10-11 12:06:25 +0000291 try:
292 r = mydb.get_db_version()
tierno11f81f62017-04-27 17:22:14 +0200293 if r[0] != database_version:
294 logger.critical("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
295 " with '{db_path}/migrate_mano_db.sh {target}'".format(
296 current=r[0], target=database_version, db_path=db_path))
tierno44528e42016-10-11 12:06:25 +0000297 exit(-1)
298 except db_base_Exception as e:
tierno11f81f62017-04-27 17:22:14 +0200299 logger.critical("DATABASE is not valid. If you think it is corrupted, you can init it with"
300 " '{db_path}/init_mano_db.sh' script".format(db_path=db_path))
tierno7edb6752016-03-21 17:37:52 +0100301 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000302
tierno7edb6752016-03-21 17:37:52 +0100303 nfvo.global_config=global_config
tierno42026a02017-02-10 15:13:40 +0100304 nfvo.start_service(mydb)
tierno46df9672017-05-26 13:12:21 +0200305
tierno7edb6752016-03-21 17:37:52 +0100306 httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
tierno46df9672017-05-26 13:12:21 +0200307
tierno7edb6752016-03-21 17:37:52 +0100308 httpthread.start()
tierno46df9672017-05-26 13:12:21 +0200309 if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100310 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
311 httpthreadadmin.start()
tierno46df9672017-05-26 13:12:21 +0200312 time.sleep(1)
tiernoae4a8d12016-07-08 12:30:39 +0200313 logger.info('Waiting for http clients')
tierno205d1022016-07-21 11:26:22 +0200314 print('Waiting for http clients')
tiernoae4a8d12016-07-08 12:30:39 +0200315 print('openmanod ready')
316 print('====================')
tierno7edb6752016-03-21 17:37:52 +0100317 time.sleep(20)
318 sys.stdout.flush()
319
320 #TODO: Interactive console must be implemented here instead of join or sleep
321
322 #httpthread.join()
tierno42026a02017-02-10 15:13:40 +0100323 #if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100324 # httpthreadadmin.join()
325 while True:
326 time.sleep(86400)
tierno7edb6752016-03-21 17:37:52 +0100327
tierno72f35a52016-07-15 13:18:30 +0200328 except KeyboardInterrupt as e:
329 logger.info(str(e))
tierno809a7802016-07-08 13:31:24 +0200330 except SystemExit:
331 pass
tiernoae4a8d12016-07-08 12:30:39 +0200332 except getopt.GetoptError as e:
tierno72f35a52016-07-15 13:18:30 +0200333 logger.critical(str(e)) # will print something like "option -a not recognized"
tiernoae4a8d12016-07-08 12:30:39 +0200334 #usage()
335 exit(-1)
336 except LoadConfigurationException as e:
tierno72f35a52016-07-15 13:18:30 +0200337 logger.critical(str(e))
tiernoae4a8d12016-07-08 12:30:39 +0200338 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000339 except db_base_Exception as e:
340 logger.critical(str(e))
341 exit(-1)
tierno46df9672017-05-26 13:12:21 +0200342 except nfvo.NfvoException as e:
343 logger.critical(str(e), exc_info=True)
344 exit(-1)
tierno42026a02017-02-10 15:13:40 +0100345 nfvo.stop_service()
346 if httpthread:
347 httpthread.join(1)
tierno7edb6752016-03-21 17:37:52 +0100348