blob: 14f07779a21dff9d12906c15df6222d442227719 [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$"
tierno98e909c2017-10-14 13:27:03 +020051__version__ = "0.5.39-r549"
tiernoe2ff1ce2017-11-02 17:01:10 +010052version_date = "Nov 2017"
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
63def load_configuration(configuration_file):
tierno639520f2017-04-05 19:55:36 +020064 default_tokens = {'http_port':9090,
65 'http_host':'localhost',
66 'http_console_proxy': True,
67 'http_console_host': None,
68 'log_level': 'DEBUG',
69 'log_socket_port': 9022,
70 'auto_push_VNF_to_VIMs': True,
71 'db_host': 'localhost',
72 'db_ovim_host': 'localhost'
73 }
tierno7edb6752016-03-21 17:37:52 +010074 try:
75 #Check config file exists
tierno72f35a52016-07-15 13:18:30 +020076 with open(configuration_file, 'r') as f:
77 config_str = f.read()
tierno7edb6752016-03-21 17:37:52 +010078 #Parse configuration file
tierno72f35a52016-07-15 13:18:30 +020079 config = yaml.load(config_str)
tierno7edb6752016-03-21 17:37:52 +010080 #Validate configuration file with the config_schema
tierno72f35a52016-07-15 13:18:30 +020081 js_v(config, config_schema)
tierno46df9672017-05-26 13:12:21 +020082
tierno72f35a52016-07-15 13:18:30 +020083 #Add default values tokens
tierno7edb6752016-03-21 17:37:52 +010084 for k,v in default_tokens.items():
tierno72f35a52016-07-15 13:18:30 +020085 if k not in config:
86 config[k]=v
87 return config
tierno46df9672017-05-26 13:12:21 +020088
tierno72f35a52016-07-15 13:18:30 +020089 except yaml.YAMLError as e:
90 error_pos = ""
91 if hasattr(e, 'problem_mark'):
92 mark = e.problem_mark
93 error_pos = " at line:{} column:{}".format(mark.line+1, mark.column+1)
tierno46df9672017-05-26 13:12:21 +020094 raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}: {message}".format(
95 file=configuration_file, pos=error_pos, message=e))
tierno72f35a52016-07-15 13:18:30 +020096 except js_e.ValidationError as e:
97 error_pos = ""
98 if e.path:
99 error_pos=" at '" + ":".join(map(str, e.path))+"'"
tierno46df9672017-05-26 13:12:21 +0200100 raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format(
101 file=configuration_file, pos=error_pos, message=e))
tierno72f35a52016-07-15 13:18:30 +0200102 except Exception as e:
tierno46df9672017-05-26 13:12:21 +0200103 raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format(
104 file=configuration_file, message=e))
105
tierno7edb6752016-03-21 17:37:52 +0100106
107def console_port_iterator():
108 '''this iterator deals with the http_console_ports
109 returning the ports one by one
110 '''
111 index = 0
112 while index < len(global_config["http_console_ports"]):
113 port = global_config["http_console_ports"][index]
tiernoae4a8d12016-07-08 12:30:39 +0200114 #print("ports -> ", port)
tierno7edb6752016-03-21 17:37:52 +0100115 if type(port) is int:
116 yield port
117 else: #this is dictionary with from to keys
118 port2 = port["from"]
tiernoae4a8d12016-07-08 12:30:39 +0200119 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100120 while port2 <= port["to"]:
tiernoae4a8d12016-07-08 12:30:39 +0200121 #print("ports -> ", port, port2)
tierno7edb6752016-03-21 17:37:52 +0100122 yield port2
123 port2 += 1
124 index += 1
tierno46df9672017-05-26 13:12:21 +0200125
126
tierno7edb6752016-03-21 17:37:52 +0100127def usage():
tiernoae4a8d12016-07-08 12:30:39 +0200128 print("Usage: ", sys.argv[0], "[options]")
129 print( " -v|--version: prints current version")
130 print( " -c|--config [configuration_file]: loads the configuration file (default: openmanod.cfg)")
131 print( " -h|--help: shows this help")
132 print( " -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)")
133 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 +0200134 #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 +0200135 print( " --log-socket-host HOST: send logs to this host")
136 print( " --log-socket-port PORT: send logs using this port (default: 9022)")
137 print( " --log-file FILE: send logs to this file")
tierno7edb6752016-03-21 17:37:52 +0100138 return
tierno46df9672017-05-26 13:12:21 +0200139
140
141def set_logging_file(log_file):
142 try:
143 file_handler = logging.handlers.RotatingFileHandler(log_file, maxBytes=100e6, backupCount=9, delay=0)
144 file_handler.setFormatter(log_formatter_simple)
145 logger.addHandler(file_handler)
146 # logger.debug("moving logs to '%s'", global_config["log_file"])
147 # remove initial stream handler
148 logging.root.removeHandler(logging.root.handlers[0])
149 print ("logging on '{}'".format(log_file))
150 except IOError as e:
151 raise LoadConfigurationException(
152 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(log_file, e))
153
154
tierno7edb6752016-03-21 17:37:52 +0100155if __name__=="__main__":
tierno46df9672017-05-26 13:12:21 +0200156 # Configure logging step 1
tierno72f35a52016-07-15 13:18:30 +0200157 hostname = socket.gethostname()
tierno46df9672017-05-26 13:12:21 +0200158 # streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
tierno72f35a52016-07-15 13:18:30 +0200159 # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
tierno46df9672017-05-26 13:12:21 +0200160 log_formatter_complete = logging.Formatter('%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s '
161 'severity:%(levelname)s logger:%(name)s log:%(message)s'.format(
162 host=hostname),
163 datefmt='%Y-%m-%dT%H:%M:%S')
tierno72f35a52016-07-15 13:18:30 +0200164 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
165 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
166 logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
167 logger = logging.getLogger('openmano')
tiernoae4a8d12016-07-08 12:30:39 +0200168 logger.setLevel(logging.DEBUG)
tierno72f35a52016-07-15 13:18:30 +0200169 socket_handler = None
tierno42026a02017-02-10 15:13:40 +0100170 # Read parameters and configuration file
171 httpthread = None
tierno7edb6752016-03-21 17:37:52 +0100172 try:
tierno72f35a52016-07-15 13:18:30 +0200173 #load parameters and configuration
tierno73ad9e42016-09-12 18:11:11 +0200174 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 +0200175 port=None
176 port_admin = None
garciadeblas4b6216b2017-04-20 16:41:52 +0200177 config_file = 'osm_ro/openmanod.cfg'
tiernoae4a8d12016-07-08 12:30:39 +0200178 vnf_repository = None
tierno205d1022016-07-21 11:26:22 +0200179 log_file = None
tierno72f35a52016-07-15 13:18:30 +0200180 log_socket_host = None
181 log_socket_port = None
tierno46df9672017-05-26 13:12:21 +0200182
tiernoae4a8d12016-07-08 12:30:39 +0200183 for o, a in opts:
184 if o in ("-v", "--version"):
tierno72f35a52016-07-15 13:18:30 +0200185 print ("openmanod version " + __version__ + ' ' + version_date)
186 print ("(c) Copyright Telefonica")
tiernoae4a8d12016-07-08 12:30:39 +0200187 sys.exit()
188 elif o in ("-h", "--help"):
189 usage()
190 sys.exit()
191 elif o in ("-V", "--vnf-repository"):
192 vnf_repository = a
193 elif o in ("-c", "--config"):
194 config_file = a
195 elif o in ("-p", "--port"):
196 port = a
197 elif o in ("-P", "--adminport"):
198 port_admin = a
tierno72f35a52016-07-15 13:18:30 +0200199 elif o == "--log-socket-port":
200 log_socket_port = a
tiernoa9a7e622016-09-30 08:43:32 +0000201 elif o == "--log-socket-host":
tierno72f35a52016-07-15 13:18:30 +0200202 log_socket_host = a
tierno205d1022016-07-21 11:26:22 +0200203 elif o == "--log-file":
204 log_file = a
tiernoae4a8d12016-07-08 12:30:39 +0200205 else:
206 assert False, "Unhandled option"
tierno46df9672017-05-26 13:12:21 +0200207 if log_file:
208 set_logging_file(log_file)
tiernoae4a8d12016-07-08 12:30:39 +0200209 global_config = load_configuration(config_file)
tierno6ddeded2017-05-16 15:40:26 +0200210 global_config["version"] = __version__
211 global_config["version_date"] = version_date
tierno7edb6752016-03-21 17:37:52 +0100212 #print global_config
tierno72f35a52016-07-15 13:18:30 +0200213 # Override parameters obtained by command line
214 if port:
215 global_config['http_port'] = port
216 if port_admin:
217 global_config['http_admin_port'] = port_admin
218 if log_socket_host:
219 global_config['log_socket_host'] = log_socket_host
220 if log_socket_port:
221 global_config['log_socket_port'] = log_socket_port
222# if vnf_repository is not None:
223# global_config['vnf_repository'] = vnf_repository
224# else:
tierno46df9672017-05-26 13:12:21 +0200225# if not 'vnf_repository' in global_config:
tierno72f35a52016-07-15 13:18:30 +0200226# logger.error( os.getcwd() )
227# global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
228# #print global_config
229# if not os.path.exists(global_config['vnf_repository']):
230# logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
231# try:
232# os.makedirs(global_config['vnf_repository'])
233# except Exception as e:
234# logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
235# exit(-1)
tierno46df9672017-05-26 13:12:21 +0200236
tierno72f35a52016-07-15 13:18:30 +0200237 global_config["console_port_iterator"] = console_port_iterator
238 global_config["console_thread"]={}
239 global_config["console_ports"]={}
tierno20fc2a22016-08-19 17:02:35 +0200240 if not global_config["http_console_host"]:
241 global_config["http_console_host"] = global_config["http_host"]
242 if global_config["http_host"]=="0.0.0.0":
243 global_config["http_console_host"] = socket.gethostname()
tierno46df9672017-05-26 13:12:21 +0200244
245 # Configure logging STEP 2
tiernof97fd272016-07-11 14:32:37 +0200246 if "log_host" in global_config:
tierno72f35a52016-07-15 13:18:30 +0200247 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
248 socket_handler.setFormatter(log_formatter_complete)
tierno46df9672017-05-26 13:12:21 +0200249 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
tierno72f35a52016-07-15 13:18:30 +0200250 socket_handler.setLevel(global_config["log_socket_level"])
tiernof97fd272016-07-11 14:32:37 +0200251 logger.addHandler(socket_handler)
tierno46df9672017-05-26 13:12:21 +0200252
253 # logger.addHandler(log_handlers.SysLogHandler())
254 if log_file:
255 global_config['log_file'] = log_file
256 elif global_config.get('log_file'):
257 set_logging_file(global_config['log_file'])
258
259 # logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
tierno205d1022016-07-21 11:26:22 +0200260 logger.setLevel(getattr(logging, global_config['log_level']))
tierno46df9672017-05-26 13:12:21 +0200261 logger.critical("Starting openmano server version: '%s %s' command: '%s'",
tiernoaceb4642016-10-20 15:12:30 +0000262 __version__, version_date, " ".join(sys.argv))
tierno46df9672017-05-26 13:12:21 +0200263
tierno639520f2017-04-05 19:55:36 +0200264 for log_module in ("nfvo", "http", "vim", "db", "console", "ovim"):
tierno73ad9e42016-09-12 18:11:11 +0200265 log_level_module = "log_level_" + log_module
266 log_file_module = "log_file_" + log_module
267 logger_module = logging.getLogger('openmano.' + log_module)
268 if log_level_module in global_config:
269 logger_module.setLevel(global_config[log_level_module])
270 if log_file_module in global_config:
271 try:
tierno46df9672017-05-26 13:12:21 +0200272 file_handler = logging.handlers.RotatingFileHandler(global_config[log_file_module],
273 maxBytes=100e6, backupCount=9, delay=0)
tierno73ad9e42016-09-12 18:11:11 +0200274 file_handler.setFormatter(log_formatter_simple)
275 logger_module.addHandler(file_handler)
276 except IOError as e:
tierno46df9672017-05-26 13:12:21 +0200277 raise LoadConfigurationException(
278 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(
279 global_config[log_file_module], str(e)) )
tierno73ad9e42016-09-12 18:11:11 +0200280 global_config["logger_"+log_module] = logger_module
281 #httpserver.logger = global_config["logger_http"]
282 #nfvo.logger = global_config["logger_nfvo"]
tierno46df9672017-05-26 13:12:21 +0200283
tierno7edb6752016-03-21 17:37:52 +0100284 # Initialize DB connection
tiernob13f3cc2016-09-26 10:14:44 +0200285 mydb = nfvo_db.nfvo_db();
tierno44528e42016-10-11 12:06:25 +0000286 mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
tierno11f81f62017-04-27 17:22:14 +0200287 db_path = osm_ro.__path__[0] + "/database_utils"
288 if not os.path.exists(db_path + "/migrate_mano_db.sh"):
289 db_path = osm_ro.__path__[0] + "/../database_utils"
tierno44528e42016-10-11 12:06:25 +0000290 try:
291 r = mydb.get_db_version()
tierno11f81f62017-04-27 17:22:14 +0200292 if r[0] != database_version:
293 logger.critical("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
294 " with '{db_path}/migrate_mano_db.sh {target}'".format(
295 current=r[0], target=database_version, db_path=db_path))
tierno44528e42016-10-11 12:06:25 +0000296 exit(-1)
297 except db_base_Exception as e:
tierno11f81f62017-04-27 17:22:14 +0200298 logger.critical("DATABASE is not valid. If you think it is corrupted, you can init it with"
299 " '{db_path}/init_mano_db.sh' script".format(db_path=db_path))
tierno7edb6752016-03-21 17:37:52 +0100300 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000301
tierno7edb6752016-03-21 17:37:52 +0100302 nfvo.global_config=global_config
tierno42026a02017-02-10 15:13:40 +0100303 nfvo.start_service(mydb)
tierno46df9672017-05-26 13:12:21 +0200304
tierno7edb6752016-03-21 17:37:52 +0100305 httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
tierno46df9672017-05-26 13:12:21 +0200306
tierno7edb6752016-03-21 17:37:52 +0100307 httpthread.start()
tierno46df9672017-05-26 13:12:21 +0200308 if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100309 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
310 httpthreadadmin.start()
tierno46df9672017-05-26 13:12:21 +0200311 time.sleep(1)
tiernoae4a8d12016-07-08 12:30:39 +0200312 logger.info('Waiting for http clients')
tierno205d1022016-07-21 11:26:22 +0200313 print('Waiting for http clients')
tiernoae4a8d12016-07-08 12:30:39 +0200314 print('openmanod ready')
315 print('====================')
tierno7edb6752016-03-21 17:37:52 +0100316 time.sleep(20)
317 sys.stdout.flush()
318
319 #TODO: Interactive console must be implemented here instead of join or sleep
320
321 #httpthread.join()
tierno42026a02017-02-10 15:13:40 +0100322 #if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100323 # httpthreadadmin.join()
324 while True:
325 time.sleep(86400)
tierno7edb6752016-03-21 17:37:52 +0100326
tierno72f35a52016-07-15 13:18:30 +0200327 except KeyboardInterrupt as e:
328 logger.info(str(e))
tierno809a7802016-07-08 13:31:24 +0200329 except SystemExit:
330 pass
tiernoae4a8d12016-07-08 12:30:39 +0200331 except getopt.GetoptError as e:
tierno72f35a52016-07-15 13:18:30 +0200332 logger.critical(str(e)) # will print something like "option -a not recognized"
tiernoae4a8d12016-07-08 12:30:39 +0200333 #usage()
334 exit(-1)
335 except LoadConfigurationException as e:
tierno72f35a52016-07-15 13:18:30 +0200336 logger.critical(str(e))
tiernoae4a8d12016-07-08 12:30:39 +0200337 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000338 except db_base_Exception as e:
339 logger.critical(str(e))
340 exit(-1)
tierno46df9672017-05-26 13:12:21 +0200341 except nfvo.NfvoException as e:
342 logger.critical(str(e), exc_info=True)
343 exit(-1)
tierno42026a02017-02-10 15:13:40 +0100344 nfvo.stop_service()
345 if httpthread:
346 httpthread.join(1)
tierno7edb6752016-03-21 17:37:52 +0100347