blob: b935017ff92afa84e239c880900341f9680d39f1 [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
tiernod4d5e252018-01-23 17:07:46 +010039from os import getenv as os_getenv, path as 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$"
tiernoe4e80352018-08-29 10:56:13 +020051__version__ = "0.5.75-r585"
tiernoc4be16d2018-10-25 14:27:36 +020052version_date = "Oct 2018"
tiernofc5f80b2018-05-29 16:00:43 +020053database_version = 32 # expected database schema version
gcalvinoe580c7d2017-09-22 14:09:51 +020054
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]")
tiernod4d5e252018-01-23 17:07:46 +0100130 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)")
135 # print( " -V|--vnf-repository: changes the path of the vnf-repository and overrides the path in the configuration file")
136 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")
tierno9c5c8322018-03-23 15:44:03 +0100139 print(" --create-tenant NAME: Try to creates this tenant name before starting, ignoring any errors as e.g. conflict")
tierno7edb6752016-03-21 17:37:52 +0100140 return
tierno46df9672017-05-26 13:12:21 +0200141
142
143def set_logging_file(log_file):
144 try:
145 file_handler = logging.handlers.RotatingFileHandler(log_file, maxBytes=100e6, backupCount=9, delay=0)
146 file_handler.setFormatter(log_formatter_simple)
147 logger.addHandler(file_handler)
148 # logger.debug("moving logs to '%s'", global_config["log_file"])
149 # remove initial stream handler
150 logging.root.removeHandler(logging.root.handlers[0])
151 print ("logging on '{}'".format(log_file))
152 except IOError as e:
153 raise LoadConfigurationException(
154 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(log_file, e))
155
156
tierno7edb6752016-03-21 17:37:52 +0100157if __name__=="__main__":
tiernod4d5e252018-01-23 17:07:46 +0100158 env_config = {
159 'db_host': 'RO_DB_HOST',
160 'db_name': 'RO_DB_NAME',
161 'db_user': 'RO_DB_USER',
162 'db_passwd': 'RO_DB_PASSWORD',
163 'db_ovim_host': 'RO_DB_OVIM_HOST',
164 'db_ovim_name': 'RO_DB_OVIM_NAME',
165 'db_ovim_user': 'RO_DB_OVIM_USER',
166 'db_ovim_passwd': 'RO_DB_OVIM_PASSWORD',
167 'db_port': 'RO_DB_PORT',
tiernoc4be16d2018-10-25 14:27:36 +0200168 'log_level': 'RO_LOG_LEVEL',
169 'log_file': 'RO_LOG_FILE',
tiernod4d5e252018-01-23 17:07:46 +0100170 }
tierno46df9672017-05-26 13:12:21 +0200171 # Configure logging step 1
tierno72f35a52016-07-15 13:18:30 +0200172 hostname = socket.gethostname()
tierno46df9672017-05-26 13:12:21 +0200173 # streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
tierno72f35a52016-07-15 13:18:30 +0200174 # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
tierno46df9672017-05-26 13:12:21 +0200175 log_formatter_complete = logging.Formatter('%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s '
176 'severity:%(levelname)s logger:%(name)s log:%(message)s'.format(
177 host=hostname),
178 datefmt='%Y-%m-%dT%H:%M:%S')
Ravi Chamartye0e198a2018-10-29 12:07:03 -0400179 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(thread)d %(filename)s:%(lineno)s %(message)s"
tierno72f35a52016-07-15 13:18:30 +0200180 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
181 logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
182 logger = logging.getLogger('openmano')
tiernoae4a8d12016-07-08 12:30:39 +0200183 logger.setLevel(logging.DEBUG)
tierno72f35a52016-07-15 13:18:30 +0200184 socket_handler = None
tierno42026a02017-02-10 15:13:40 +0100185 # Read parameters and configuration file
186 httpthread = None
tierno7edb6752016-03-21 17:37:52 +0100187 try:
tierno9c5c8322018-03-23 15:44:03 +0100188 # load parameters and configuration
189 opts, args = getopt.getopt(sys.argv[1:], "hvc:V:p:P:",
190 ["config=", "help", "version", "port=", "vnf-repository=", "adminport=",
191 "log-socket-host=", "log-socket-port=", "log-file=", "create-tenant="])
tiernoae4a8d12016-07-08 12:30:39 +0200192 port=None
193 port_admin = None
garciadeblas4b6216b2017-04-20 16:41:52 +0200194 config_file = 'osm_ro/openmanod.cfg'
tiernoae4a8d12016-07-08 12:30:39 +0200195 vnf_repository = None
tierno205d1022016-07-21 11:26:22 +0200196 log_file = None
tierno72f35a52016-07-15 13:18:30 +0200197 log_socket_host = None
198 log_socket_port = None
tierno9c5c8322018-03-23 15:44:03 +0100199 create_tenant = None
tierno46df9672017-05-26 13:12:21 +0200200
tiernoae4a8d12016-07-08 12:30:39 +0200201 for o, a in opts:
202 if o in ("-v", "--version"):
tierno72f35a52016-07-15 13:18:30 +0200203 print ("openmanod version " + __version__ + ' ' + version_date)
204 print ("(c) Copyright Telefonica")
tiernoae4a8d12016-07-08 12:30:39 +0200205 sys.exit()
206 elif o in ("-h", "--help"):
207 usage()
208 sys.exit()
209 elif o in ("-V", "--vnf-repository"):
210 vnf_repository = a
211 elif o in ("-c", "--config"):
212 config_file = a
213 elif o in ("-p", "--port"):
214 port = a
215 elif o in ("-P", "--adminport"):
216 port_admin = a
tierno72f35a52016-07-15 13:18:30 +0200217 elif o == "--log-socket-port":
218 log_socket_port = a
tiernoa9a7e622016-09-30 08:43:32 +0000219 elif o == "--log-socket-host":
tierno72f35a52016-07-15 13:18:30 +0200220 log_socket_host = a
tierno205d1022016-07-21 11:26:22 +0200221 elif o == "--log-file":
222 log_file = a
tierno9c5c8322018-03-23 15:44:03 +0100223 elif o == "--create-tenant":
224 create_tenant = a
tiernoae4a8d12016-07-08 12:30:39 +0200225 else:
226 assert False, "Unhandled option"
tierno46df9672017-05-26 13:12:21 +0200227 if log_file:
228 set_logging_file(log_file)
tiernoae4a8d12016-07-08 12:30:39 +0200229 global_config = load_configuration(config_file)
tierno6ddeded2017-05-16 15:40:26 +0200230 global_config["version"] = __version__
231 global_config["version_date"] = version_date
tierno7edb6752016-03-21 17:37:52 +0100232 #print global_config
tiernod4d5e252018-01-23 17:07:46 +0100233 # Override parameters obtained by command line on ENV
tierno72f35a52016-07-15 13:18:30 +0200234 if port:
235 global_config['http_port'] = port
236 if port_admin:
237 global_config['http_admin_port'] = port_admin
238 if log_socket_host:
239 global_config['log_socket_host'] = log_socket_host
240 if log_socket_port:
241 global_config['log_socket_port'] = log_socket_port
tiernod4d5e252018-01-23 17:07:46 +0100242
243 # override with ENV
244 for config_key, env_var in env_config.items():
245 if os_getenv(env_var):
246 global_config[config_key] = os_getenv(env_var)
247
248
tierno72f35a52016-07-15 13:18:30 +0200249# if vnf_repository is not None:
250# global_config['vnf_repository'] = vnf_repository
251# else:
tierno46df9672017-05-26 13:12:21 +0200252# if not 'vnf_repository' in global_config:
tierno72f35a52016-07-15 13:18:30 +0200253# logger.error( os.getcwd() )
254# global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
255# #print global_config
256# if not os.path.exists(global_config['vnf_repository']):
257# logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
258# try:
259# os.makedirs(global_config['vnf_repository'])
260# except Exception as e:
261# logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
262# exit(-1)
tierno46df9672017-05-26 13:12:21 +0200263
tierno72f35a52016-07-15 13:18:30 +0200264 global_config["console_port_iterator"] = console_port_iterator
265 global_config["console_thread"]={}
266 global_config["console_ports"]={}
tierno20fc2a22016-08-19 17:02:35 +0200267 if not global_config["http_console_host"]:
268 global_config["http_console_host"] = global_config["http_host"]
269 if global_config["http_host"]=="0.0.0.0":
270 global_config["http_console_host"] = socket.gethostname()
tierno46df9672017-05-26 13:12:21 +0200271
272 # Configure logging STEP 2
tiernof97fd272016-07-11 14:32:37 +0200273 if "log_host" in global_config:
tierno72f35a52016-07-15 13:18:30 +0200274 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
275 socket_handler.setFormatter(log_formatter_complete)
tierno46df9672017-05-26 13:12:21 +0200276 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
tierno72f35a52016-07-15 13:18:30 +0200277 socket_handler.setLevel(global_config["log_socket_level"])
tiernof97fd272016-07-11 14:32:37 +0200278 logger.addHandler(socket_handler)
tierno46df9672017-05-26 13:12:21 +0200279
280 # logger.addHandler(log_handlers.SysLogHandler())
281 if log_file:
282 global_config['log_file'] = log_file
283 elif global_config.get('log_file'):
284 set_logging_file(global_config['log_file'])
285
286 # logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
tierno205d1022016-07-21 11:26:22 +0200287 logger.setLevel(getattr(logging, global_config['log_level']))
tierno46df9672017-05-26 13:12:21 +0200288 logger.critical("Starting openmano server version: '%s %s' command: '%s'",
tiernoaceb4642016-10-20 15:12:30 +0000289 __version__, version_date, " ".join(sys.argv))
tierno46df9672017-05-26 13:12:21 +0200290
tierno639520f2017-04-05 19:55:36 +0200291 for log_module in ("nfvo", "http", "vim", "db", "console", "ovim"):
tierno73ad9e42016-09-12 18:11:11 +0200292 log_level_module = "log_level_" + log_module
293 log_file_module = "log_file_" + log_module
294 logger_module = logging.getLogger('openmano.' + log_module)
295 if log_level_module in global_config:
296 logger_module.setLevel(global_config[log_level_module])
297 if log_file_module in global_config:
298 try:
tierno46df9672017-05-26 13:12:21 +0200299 file_handler = logging.handlers.RotatingFileHandler(global_config[log_file_module],
300 maxBytes=100e6, backupCount=9, delay=0)
tierno73ad9e42016-09-12 18:11:11 +0200301 file_handler.setFormatter(log_formatter_simple)
302 logger_module.addHandler(file_handler)
303 except IOError as e:
tierno46df9672017-05-26 13:12:21 +0200304 raise LoadConfigurationException(
305 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(
306 global_config[log_file_module], str(e)) )
tierno73ad9e42016-09-12 18:11:11 +0200307 global_config["logger_"+log_module] = logger_module
308 #httpserver.logger = global_config["logger_http"]
309 #nfvo.logger = global_config["logger_nfvo"]
tierno46df9672017-05-26 13:12:21 +0200310
tierno7edb6752016-03-21 17:37:52 +0100311 # Initialize DB connection
tiernob13f3cc2016-09-26 10:14:44 +0200312 mydb = nfvo_db.nfvo_db();
tierno44528e42016-10-11 12:06:25 +0000313 mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
tierno11f81f62017-04-27 17:22:14 +0200314 db_path = osm_ro.__path__[0] + "/database_utils"
tiernod4d5e252018-01-23 17:07:46 +0100315 if not os_path.exists(db_path + "/migrate_mano_db.sh"):
tierno11f81f62017-04-27 17:22:14 +0200316 db_path = osm_ro.__path__[0] + "/../database_utils"
tierno44528e42016-10-11 12:06:25 +0000317 try:
318 r = mydb.get_db_version()
tierno11f81f62017-04-27 17:22:14 +0200319 if r[0] != database_version:
320 logger.critical("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
321 " with '{db_path}/migrate_mano_db.sh {target}'".format(
322 current=r[0], target=database_version, db_path=db_path))
tierno44528e42016-10-11 12:06:25 +0000323 exit(-1)
324 except db_base_Exception as e:
tierno11f81f62017-04-27 17:22:14 +0200325 logger.critical("DATABASE is not valid. If you think it is corrupted, you can init it with"
326 " '{db_path}/init_mano_db.sh' script".format(db_path=db_path))
tierno7edb6752016-03-21 17:37:52 +0100327 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000328
tierno7edb6752016-03-21 17:37:52 +0100329 nfvo.global_config=global_config
tierno9c5c8322018-03-23 15:44:03 +0100330 if create_tenant:
331 try:
332 nfvo.new_tenant(mydb, {"name": create_tenant})
333 except Exception as e:
334 if isinstance(e, nfvo.NfvoException) and e.http_code == 409:
335 pass # if tenant exist (NfvoException error 409), ignore
336 else: # otherwise print and error and continue
337 logger.error("Cannot create tenant '{}': {}".format(create_tenant, e))
tierno42026a02017-02-10 15:13:40 +0100338 nfvo.start_service(mydb)
tierno46df9672017-05-26 13:12:21 +0200339
tierno7edb6752016-03-21 17:37:52 +0100340 httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
tierno46df9672017-05-26 13:12:21 +0200341
tierno7edb6752016-03-21 17:37:52 +0100342 httpthread.start()
tierno46df9672017-05-26 13:12:21 +0200343 if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100344 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
345 httpthreadadmin.start()
tierno46df9672017-05-26 13:12:21 +0200346 time.sleep(1)
tiernoae4a8d12016-07-08 12:30:39 +0200347 logger.info('Waiting for http clients')
tierno205d1022016-07-21 11:26:22 +0200348 print('Waiting for http clients')
tiernoae4a8d12016-07-08 12:30:39 +0200349 print('openmanod ready')
350 print('====================')
tierno7edb6752016-03-21 17:37:52 +0100351 time.sleep(20)
352 sys.stdout.flush()
353
354 #TODO: Interactive console must be implemented here instead of join or sleep
355
356 #httpthread.join()
tierno42026a02017-02-10 15:13:40 +0100357 #if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100358 # httpthreadadmin.join()
359 while True:
360 time.sleep(86400)
tierno7edb6752016-03-21 17:37:52 +0100361
tierno72f35a52016-07-15 13:18:30 +0200362 except KeyboardInterrupt as e:
363 logger.info(str(e))
tierno809a7802016-07-08 13:31:24 +0200364 except SystemExit:
365 pass
tiernoae4a8d12016-07-08 12:30:39 +0200366 except getopt.GetoptError as e:
tierno72f35a52016-07-15 13:18:30 +0200367 logger.critical(str(e)) # will print something like "option -a not recognized"
tiernoae4a8d12016-07-08 12:30:39 +0200368 #usage()
369 exit(-1)
370 except LoadConfigurationException as e:
tierno72f35a52016-07-15 13:18:30 +0200371 logger.critical(str(e))
tiernoae4a8d12016-07-08 12:30:39 +0200372 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000373 except db_base_Exception as e:
374 logger.critical(str(e))
375 exit(-1)
tierno46df9672017-05-26 13:12:21 +0200376 except nfvo.NfvoException as e:
377 logger.critical(str(e), exc_info=True)
378 exit(-1)
tierno42026a02017-02-10 15:13:40 +0100379 nfvo.stop_service()
380 if httpthread:
381 httpthread.join(1)
tierno7edb6752016-03-21 17:37:52 +0100382