blob: 6cccf0e45fd78d50c45f011938000f8eb0138c27 [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$"
tiernoe72710b2018-07-23 16:16:00 +020051__version__ = "0.5.74-r584"
tierno55d234c2018-07-04 18:29:21 +020052version_date = "Jul 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',
168 'db_port': 'RO_DB_PORT',
169 }
tierno46df9672017-05-26 13:12:21 +0200170 # Configure logging step 1
tierno72f35a52016-07-15 13:18:30 +0200171 hostname = socket.gethostname()
tierno46df9672017-05-26 13:12:21 +0200172 # streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
tierno72f35a52016-07-15 13:18:30 +0200173 # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
tierno46df9672017-05-26 13:12:21 +0200174 log_formatter_complete = logging.Formatter('%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s '
175 'severity:%(levelname)s logger:%(name)s log:%(message)s'.format(
176 host=hostname),
177 datefmt='%Y-%m-%dT%H:%M:%S')
tierno72f35a52016-07-15 13:18:30 +0200178 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
179 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
180 logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
181 logger = logging.getLogger('openmano')
tiernoae4a8d12016-07-08 12:30:39 +0200182 logger.setLevel(logging.DEBUG)
tierno72f35a52016-07-15 13:18:30 +0200183 socket_handler = None
tierno42026a02017-02-10 15:13:40 +0100184 # Read parameters and configuration file
185 httpthread = None
tierno7edb6752016-03-21 17:37:52 +0100186 try:
tierno9c5c8322018-03-23 15:44:03 +0100187 # load parameters and configuration
188 opts, args = getopt.getopt(sys.argv[1:], "hvc:V:p:P:",
189 ["config=", "help", "version", "port=", "vnf-repository=", "adminport=",
190 "log-socket-host=", "log-socket-port=", "log-file=", "create-tenant="])
tiernoae4a8d12016-07-08 12:30:39 +0200191 port=None
192 port_admin = None
garciadeblas4b6216b2017-04-20 16:41:52 +0200193 config_file = 'osm_ro/openmanod.cfg'
tiernoae4a8d12016-07-08 12:30:39 +0200194 vnf_repository = None
tierno205d1022016-07-21 11:26:22 +0200195 log_file = None
tierno72f35a52016-07-15 13:18:30 +0200196 log_socket_host = None
197 log_socket_port = None
tierno9c5c8322018-03-23 15:44:03 +0100198 create_tenant = None
tierno46df9672017-05-26 13:12:21 +0200199
tiernoae4a8d12016-07-08 12:30:39 +0200200 for o, a in opts:
201 if o in ("-v", "--version"):
tierno72f35a52016-07-15 13:18:30 +0200202 print ("openmanod version " + __version__ + ' ' + version_date)
203 print ("(c) Copyright Telefonica")
tiernoae4a8d12016-07-08 12:30:39 +0200204 sys.exit()
205 elif o in ("-h", "--help"):
206 usage()
207 sys.exit()
208 elif o in ("-V", "--vnf-repository"):
209 vnf_repository = a
210 elif o in ("-c", "--config"):
211 config_file = a
212 elif o in ("-p", "--port"):
213 port = a
214 elif o in ("-P", "--adminport"):
215 port_admin = a
tierno72f35a52016-07-15 13:18:30 +0200216 elif o == "--log-socket-port":
217 log_socket_port = a
tiernoa9a7e622016-09-30 08:43:32 +0000218 elif o == "--log-socket-host":
tierno72f35a52016-07-15 13:18:30 +0200219 log_socket_host = a
tierno205d1022016-07-21 11:26:22 +0200220 elif o == "--log-file":
221 log_file = a
tierno9c5c8322018-03-23 15:44:03 +0100222 elif o == "--create-tenant":
223 create_tenant = a
tiernoae4a8d12016-07-08 12:30:39 +0200224 else:
225 assert False, "Unhandled option"
tierno46df9672017-05-26 13:12:21 +0200226 if log_file:
227 set_logging_file(log_file)
tiernoae4a8d12016-07-08 12:30:39 +0200228 global_config = load_configuration(config_file)
tierno6ddeded2017-05-16 15:40:26 +0200229 global_config["version"] = __version__
230 global_config["version_date"] = version_date
tierno7edb6752016-03-21 17:37:52 +0100231 #print global_config
tiernod4d5e252018-01-23 17:07:46 +0100232 # Override parameters obtained by command line on ENV
tierno72f35a52016-07-15 13:18:30 +0200233 if port:
234 global_config['http_port'] = port
235 if port_admin:
236 global_config['http_admin_port'] = port_admin
237 if log_socket_host:
238 global_config['log_socket_host'] = log_socket_host
239 if log_socket_port:
240 global_config['log_socket_port'] = log_socket_port
tiernod4d5e252018-01-23 17:07:46 +0100241
242 # override with ENV
243 for config_key, env_var in env_config.items():
244 if os_getenv(env_var):
245 global_config[config_key] = os_getenv(env_var)
246
247
tierno72f35a52016-07-15 13:18:30 +0200248# if vnf_repository is not None:
249# global_config['vnf_repository'] = vnf_repository
250# else:
tierno46df9672017-05-26 13:12:21 +0200251# if not 'vnf_repository' in global_config:
tierno72f35a52016-07-15 13:18:30 +0200252# logger.error( os.getcwd() )
253# global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
254# #print global_config
255# if not os.path.exists(global_config['vnf_repository']):
256# logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
257# try:
258# os.makedirs(global_config['vnf_repository'])
259# except Exception as e:
260# logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
261# exit(-1)
tierno46df9672017-05-26 13:12:21 +0200262
tierno72f35a52016-07-15 13:18:30 +0200263 global_config["console_port_iterator"] = console_port_iterator
264 global_config["console_thread"]={}
265 global_config["console_ports"]={}
tierno20fc2a22016-08-19 17:02:35 +0200266 if not global_config["http_console_host"]:
267 global_config["http_console_host"] = global_config["http_host"]
268 if global_config["http_host"]=="0.0.0.0":
269 global_config["http_console_host"] = socket.gethostname()
tierno46df9672017-05-26 13:12:21 +0200270
271 # Configure logging STEP 2
tiernof97fd272016-07-11 14:32:37 +0200272 if "log_host" in global_config:
tierno72f35a52016-07-15 13:18:30 +0200273 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
274 socket_handler.setFormatter(log_formatter_complete)
tierno46df9672017-05-26 13:12:21 +0200275 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
tierno72f35a52016-07-15 13:18:30 +0200276 socket_handler.setLevel(global_config["log_socket_level"])
tiernof97fd272016-07-11 14:32:37 +0200277 logger.addHandler(socket_handler)
tierno46df9672017-05-26 13:12:21 +0200278
279 # logger.addHandler(log_handlers.SysLogHandler())
280 if log_file:
281 global_config['log_file'] = log_file
282 elif global_config.get('log_file'):
283 set_logging_file(global_config['log_file'])
284
285 # logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
tierno205d1022016-07-21 11:26:22 +0200286 logger.setLevel(getattr(logging, global_config['log_level']))
tierno46df9672017-05-26 13:12:21 +0200287 logger.critical("Starting openmano server version: '%s %s' command: '%s'",
tiernoaceb4642016-10-20 15:12:30 +0000288 __version__, version_date, " ".join(sys.argv))
tierno46df9672017-05-26 13:12:21 +0200289
tierno639520f2017-04-05 19:55:36 +0200290 for log_module in ("nfvo", "http", "vim", "db", "console", "ovim"):
tierno73ad9e42016-09-12 18:11:11 +0200291 log_level_module = "log_level_" + log_module
292 log_file_module = "log_file_" + log_module
293 logger_module = logging.getLogger('openmano.' + log_module)
294 if log_level_module in global_config:
295 logger_module.setLevel(global_config[log_level_module])
296 if log_file_module in global_config:
297 try:
tierno46df9672017-05-26 13:12:21 +0200298 file_handler = logging.handlers.RotatingFileHandler(global_config[log_file_module],
299 maxBytes=100e6, backupCount=9, delay=0)
tierno73ad9e42016-09-12 18:11:11 +0200300 file_handler.setFormatter(log_formatter_simple)
301 logger_module.addHandler(file_handler)
302 except IOError as e:
tierno46df9672017-05-26 13:12:21 +0200303 raise LoadConfigurationException(
304 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(
305 global_config[log_file_module], str(e)) )
tierno73ad9e42016-09-12 18:11:11 +0200306 global_config["logger_"+log_module] = logger_module
307 #httpserver.logger = global_config["logger_http"]
308 #nfvo.logger = global_config["logger_nfvo"]
tierno46df9672017-05-26 13:12:21 +0200309
tierno7edb6752016-03-21 17:37:52 +0100310 # Initialize DB connection
tiernob13f3cc2016-09-26 10:14:44 +0200311 mydb = nfvo_db.nfvo_db();
tierno44528e42016-10-11 12:06:25 +0000312 mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
tierno11f81f62017-04-27 17:22:14 +0200313 db_path = osm_ro.__path__[0] + "/database_utils"
tiernod4d5e252018-01-23 17:07:46 +0100314 if not os_path.exists(db_path + "/migrate_mano_db.sh"):
tierno11f81f62017-04-27 17:22:14 +0200315 db_path = osm_ro.__path__[0] + "/../database_utils"
tierno44528e42016-10-11 12:06:25 +0000316 try:
317 r = mydb.get_db_version()
tierno11f81f62017-04-27 17:22:14 +0200318 if r[0] != database_version:
319 logger.critical("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
320 " with '{db_path}/migrate_mano_db.sh {target}'".format(
321 current=r[0], target=database_version, db_path=db_path))
tierno44528e42016-10-11 12:06:25 +0000322 exit(-1)
323 except db_base_Exception as e:
tierno11f81f62017-04-27 17:22:14 +0200324 logger.critical("DATABASE is not valid. If you think it is corrupted, you can init it with"
325 " '{db_path}/init_mano_db.sh' script".format(db_path=db_path))
tierno7edb6752016-03-21 17:37:52 +0100326 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000327
tierno7edb6752016-03-21 17:37:52 +0100328 nfvo.global_config=global_config
tierno9c5c8322018-03-23 15:44:03 +0100329 if create_tenant:
330 try:
331 nfvo.new_tenant(mydb, {"name": create_tenant})
332 except Exception as e:
333 if isinstance(e, nfvo.NfvoException) and e.http_code == 409:
334 pass # if tenant exist (NfvoException error 409), ignore
335 else: # otherwise print and error and continue
336 logger.error("Cannot create tenant '{}': {}".format(create_tenant, e))
tierno42026a02017-02-10 15:13:40 +0100337 nfvo.start_service(mydb)
tierno46df9672017-05-26 13:12:21 +0200338
tierno7edb6752016-03-21 17:37:52 +0100339 httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
tierno46df9672017-05-26 13:12:21 +0200340
tierno7edb6752016-03-21 17:37:52 +0100341 httpthread.start()
tierno46df9672017-05-26 13:12:21 +0200342 if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100343 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
344 httpthreadadmin.start()
tierno46df9672017-05-26 13:12:21 +0200345 time.sleep(1)
tiernoae4a8d12016-07-08 12:30:39 +0200346 logger.info('Waiting for http clients')
tierno205d1022016-07-21 11:26:22 +0200347 print('Waiting for http clients')
tiernoae4a8d12016-07-08 12:30:39 +0200348 print('openmanod ready')
349 print('====================')
tierno7edb6752016-03-21 17:37:52 +0100350 time.sleep(20)
351 sys.stdout.flush()
352
353 #TODO: Interactive console must be implemented here instead of join or sleep
354
355 #httpthread.join()
tierno42026a02017-02-10 15:13:40 +0100356 #if 'http_admin_port' in global_config:
tierno7edb6752016-03-21 17:37:52 +0100357 # httpthreadadmin.join()
358 while True:
359 time.sleep(86400)
tierno7edb6752016-03-21 17:37:52 +0100360
tierno72f35a52016-07-15 13:18:30 +0200361 except KeyboardInterrupt as e:
362 logger.info(str(e))
tierno809a7802016-07-08 13:31:24 +0200363 except SystemExit:
364 pass
tiernoae4a8d12016-07-08 12:30:39 +0200365 except getopt.GetoptError as e:
tierno72f35a52016-07-15 13:18:30 +0200366 logger.critical(str(e)) # will print something like "option -a not recognized"
tiernoae4a8d12016-07-08 12:30:39 +0200367 #usage()
368 exit(-1)
369 except LoadConfigurationException as e:
tierno72f35a52016-07-15 13:18:30 +0200370 logger.critical(str(e))
tiernoae4a8d12016-07-08 12:30:39 +0200371 exit(-1)
tierno44528e42016-10-11 12:06:25 +0000372 except db_base_Exception as e:
373 logger.critical(str(e))
374 exit(-1)
tierno46df9672017-05-26 13:12:21 +0200375 except nfvo.NfvoException as e:
376 logger.critical(str(e), exc_info=True)
377 exit(-1)
tierno42026a02017-02-10 15:13:40 +0100378 nfvo.stop_service()
379 if httpthread:
380 httpthread.join(1)
tierno7edb6752016-03-21 17:37:52 +0100381