blob: f6436a6b9b3fd6198d0b6d565298909e2f9e44a8 [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
25'''
26openmano 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.
33'''
34__author__="Alfonso Tierno, Gerardo Garcia, Pablo Montes"
35__date__ ="$26-aug-2014 11:09:29$"
tiernof97fd272016-07-11 14:32:37 +020036__version__="0.4.41-r478"
37version_date="Jul 2016"
tierno7edb6752016-03-21 17:37:52 +010038database_version="0.10" #expected database schema version
39
40import httpserver
41import time
42import os
43import sys
44import getopt
45import yaml
46import nfvo_db
47from jsonschema import validate as js_v, exceptions as js_e
tierno42fcc3b2016-07-06 17:20:40 +020048import utils
tierno7edb6752016-03-21 17:37:52 +010049from openmano_schemas import config_schema
50import nfvo
tiernoae4a8d12016-07-08 12:30:39 +020051import logging
tiernof97fd272016-07-11 14:32:37 +020052import logging.handlers as log_handlers
tierno7edb6752016-03-21 17:37:52 +010053
54global global_config
tiernof97fd272016-07-11 14:32:37 +020055global logger
56logger = logging.getLogger('openmano')
tiernoae4a8d12016-07-08 12:30:39 +020057
58class LoadConfigurationException(Exception):
59 pass
tierno7edb6752016-03-21 17:37:52 +010060
61def load_configuration(configuration_file):
tiernoae4a8d12016-07-08 12:30:39 +020062 default_tokens ={'http_port':9090,
63 'http_host':'localhost',
64 'log_level': 'DEBUG',
65 'log_level_db': 'ERROR',
66 'log_level_vimconn': 'DEBUG',
tiernof97fd272016-07-11 14:32:37 +020067 'log_level_nfvo': 'DEBUG',
tiernoae4a8d12016-07-08 12:30:39 +020068 }
tierno7edb6752016-03-21 17:37:52 +010069 try:
70 #Check config file exists
71 if not os.path.isfile(configuration_file):
tiernoae4a8d12016-07-08 12:30:39 +020072 raise LoadConfigurationException("Error: Configuration file '"+configuration_file+"' does not exist.")
tierno7edb6752016-03-21 17:37:52 +010073
74 #Read file
tierno42fcc3b2016-07-06 17:20:40 +020075 (return_status, code) = utils.read_file(configuration_file)
tierno7edb6752016-03-21 17:37:52 +010076 if not return_status:
tiernoae4a8d12016-07-08 12:30:39 +020077 raise LoadConfigurationException("Error loading configuration file '"+configuration_file+"': "+code)
tierno7edb6752016-03-21 17:37:52 +010078 #Parse configuration file
79 try:
80 config = yaml.load(code)
81 except yaml.YAMLError, exc:
82 error_pos = ""
83 if hasattr(exc, 'problem_mark'):
84 mark = exc.problem_mark
85 error_pos = " at position: (%s:%s)" % (mark.line+1, mark.column+1)
tiernoae4a8d12016-07-08 12:30:39 +020086 raise LoadConfigurationException("Error loading configuration file '"+configuration_file+"'"+error_pos+": content format error: Failed to parse yaml format")
tierno7edb6752016-03-21 17:37:52 +010087
88 #Validate configuration file with the config_schema
89 try:
90 js_v(config, config_schema)
91 except js_e.ValidationError, exc:
92 error_pos = ""
93 if len(exc.path)>0: error_pos=" at '" + ":".join(map(str, exc.path))+"'"
tiernoae4a8d12016-07-08 12:30:39 +020094 raise LoadConfigurationException("Error loading configuration file '"+configuration_file+"'"+error_pos+": "+exc.message)
tierno7edb6752016-03-21 17:37:52 +010095
96 #Check default values tokens
97 for k,v in default_tokens.items():
98 if k not in config: config[k]=v
99
100 except Exception,e:
tiernoae4a8d12016-07-08 12:30:39 +0200101 raise LoadConfigurationException("Error loading configuration file '"+configuration_file+"': "+str(e))
tierno7edb6752016-03-21 17:37:52 +0100102
tiernoae4a8d12016-07-08 12:30:39 +0200103 return config
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
123
124
125def 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)")
132 print( " -V|--vnf-repository: changes the path of the vnf-repository and overrides the path in the configuration file")
tierno7edb6752016-03-21 17:37:52 +0100133 return
134
135if __name__=="__main__":
tiernoae4a8d12016-07-08 12:30:39 +0200136 #streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
tiernof97fd272016-07-11 14:32:37 +0200137 logging_local_format = "%(asctime)s %(name)s %(levelname)s: %(message)s"
138 logging_complete_format = "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
139 logging.basicConfig(format=logging_local_format, level= logging.DEBUG)
tiernoae4a8d12016-07-08 12:30:39 +0200140 logger.setLevel(logging.DEBUG)
tiernof97fd272016-07-11 14:32:37 +0200141 file_handler = None
tierno7edb6752016-03-21 17:37:52 +0100142 # Read parameters and configuration file
143 try:
144 opts, args = getopt.getopt(sys.argv[1:], "hvc:V:p:P:", ["config", "help", "version", "port", "vnf-repository", "adminport"])
tierno7edb6752016-03-21 17:37:52 +0100145
tiernoae4a8d12016-07-08 12:30:39 +0200146 port=None
147 port_admin = None
148 config_file = 'openmanod.cfg'
149 vnf_repository = None
150
151 for o, a in opts:
152 if o in ("-v", "--version"):
153 print "openmanod version", __version__, version_date
154 print "(c) Copyright Telefonica"
155 sys.exit()
156 elif o in ("-h", "--help"):
157 usage()
158 sys.exit()
159 elif o in ("-V", "--vnf-repository"):
160 vnf_repository = a
161 elif o in ("-c", "--config"):
162 config_file = a
163 elif o in ("-p", "--port"):
164 port = a
165 elif o in ("-P", "--adminport"):
166 port_admin = a
167 else:
168 assert False, "Unhandled option"
tierno7edb6752016-03-21 17:37:52 +0100169
tiernoae4a8d12016-07-08 12:30:39 +0200170 global_config = load_configuration(config_file)
tierno7edb6752016-03-21 17:37:52 +0100171 #print global_config
tiernoae4a8d12016-07-08 12:30:39 +0200172 logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
173 logger.setLevel(getattr(logging, global_config['log_level']))
tiernof97fd272016-07-11 14:32:37 +0200174 if "log_host" in global_config:
175 socket_handler= log_handlers.SocketHandler(global_config["log_host"], global_config["log_port"])
176 logger.addHandler(socket_handler)
177 logger.addHandler(log_handlers.SysLogHandler())
178 if "log_file" in global_config:
179 try:
180 file_handler= logging.handlers.RotatingFileHandler(global_config["log_file"], maxBytes=100e6, backupCount=9, delay=0)
181 file_handler.setFormatter(logging.Formatter(fmt=logging_complete_format))
182 logger.addHandler(file_handler)
183 except IOError as e:
184 print "Error opening logging file '{}': {}. Check folder exist and permissions".fomat(global_config["log_file"], str(e))
tierno7edb6752016-03-21 17:37:52 +0100185 # Override parameters obtained by command line
tiernof97fd272016-07-11 14:32:37 +0200186 print logger.handlers
tierno7edb6752016-03-21 17:37:52 +0100187 if port is not None: global_config['http_port'] = port
188 if port_admin is not None: global_config['http_admin_port'] = port_admin
189 if vnf_repository is not None:
190 global_config['vnf_repository'] = vnf_repository
191 else:
192 if not 'vnf_repository' in global_config:
tiernoae4a8d12016-07-08 12:30:39 +0200193 logger.error( os.getcwd() )
tierno7edb6752016-03-21 17:37:52 +0100194 global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
195 #print global_config
196
197 if not os.path.exists(global_config['vnf_repository']):
tiernoae4a8d12016-07-08 12:30:39 +0200198 logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
tierno7edb6752016-03-21 17:37:52 +0100199 try:
200 os.makedirs(global_config['vnf_repository'])
tiernoae4a8d12016-07-08 12:30:39 +0200201 except Exception as e:
202 logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
tierno7edb6752016-03-21 17:37:52 +0100203 exit(-1)
204
205 global_config["console_port_iterator"] = console_port_iterator
206 global_config["console_thread"]={}
207 global_config["console_ports"]={}
208 # Initialize DB connection
tiernof97fd272016-07-11 14:32:37 +0200209 mydb = nfvo_db.nfvo_db(log_level=global_config["log_level_db"]);
tierno7edb6752016-03-21 17:37:52 +0100210 if mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name']) == -1:
tiernoae4a8d12016-07-08 12:30:39 +0200211 logger.error("Error connecting to database %s at %s@%s", global_config['db_name'], global_config['db_user'], global_config['db_host'])
tierno7edb6752016-03-21 17:37:52 +0100212 exit(-1)
213 r = mydb.get_db_version()
214 if r[0]<0:
tiernoae4a8d12016-07-08 12:30:39 +0200215 logger.error("Error DATABASE is not a MANO one or it is a '0.0' version. Try to upgrade to version '%s' with './database_utils/migrate_mano_db.sh'", database_version)
tierno7edb6752016-03-21 17:37:52 +0100216 exit(-1)
217 elif r[1]!=database_version:
tiernoae4a8d12016-07-08 12:30:39 +0200218 logger.error("Error DATABASE wrong version '%s'. Try to upgrade/downgrade to version '%s' with './database_utils/migrate_mano_db.sh'", r[1], database_version)
tierno7edb6752016-03-21 17:37:52 +0100219 exit(-1)
220
221 nfvo.global_config=global_config
222
223 httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
224
225 httpthread.start()
226 if 'http_admin_port' in global_config:
227 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
228 httpthreadadmin.start()
229 time.sleep(1)
tiernoae4a8d12016-07-08 12:30:39 +0200230 logger.info('Waiting for http clients')
231 print('openmanod ready')
232 print('====================')
tierno7edb6752016-03-21 17:37:52 +0100233 time.sleep(20)
234 sys.stdout.flush()
235
236 #TODO: Interactive console must be implemented here instead of join or sleep
237
238 #httpthread.join()
239 #if 'http_admin_port' in global_config:
240 # httpthreadadmin.join()
241 while True:
242 time.sleep(86400)
243 for thread in global_config["console_thread"]:
244 thread.terminate = True
245
tierno809a7802016-07-08 13:31:24 +0200246 except KeyboardInterrupt:
247 logger.info('KyboardInterrupt')
248 except SystemExit:
249 pass
tiernoae4a8d12016-07-08 12:30:39 +0200250 except getopt.GetoptError as e:
tierno809a7802016-07-08 13:31:24 +0200251 logger.error("Error: %s", str(e)) # will print something like "option -a not recognized"
tiernoae4a8d12016-07-08 12:30:39 +0200252 #usage()
253 exit(-1)
254 except LoadConfigurationException as e:
tierno809a7802016-07-08 13:31:24 +0200255 logger.error("Error: %s", str(e))
tiernoae4a8d12016-07-08 12:30:39 +0200256 exit(-1)
tierno7edb6752016-03-21 17:37:52 +0100257