2 # -*- coding: utf-8 -*-
5 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
6 # This file is part of openmano
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
13 # http://www.apache.org/licenses/LICENSE-2.0
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
21 # For those usages not covered by the Apache License, Version 2.0 please
22 # contact with: nfvlabs@tid.es
27 Main program that implements a reference NFVO (Network Functions Virtualisation Orchestrator).
28 It interfaces with an NFV VIM through its API and offers a northbound interface, based on REST (openmano API),
29 where NFV services are offered including the creation and deletion of VNF templates, VNF instances,
30 network service templates and network service instances.
32 It loads the configuration file and launches the http_server thread that will listen requests using openmano API.
39 from os import getenv as os_getenv, path as os_path
40 from jsonschema import validate as js_v, exceptions as js_e
42 import logging.handlers as log_handlers
44 from osm_ro import httpserver, nfvo, nfvo_db
45 from osm_ro.openmano_schemas import config_schema
46 from osm_ro.db_base import db_base_Exception
47 from osm_ro.wim.engine import WimEngine
48 from osm_ro.wim.persistence import WimPersistence
51 __author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes"
52 __date__ = "$26-aug-2014 11:09:29$"
53 __version__ = "0.6.00"
54 version_date = "Sep 2018"
55 database_version = 34 # expected database schema version
62 class LoadConfigurationException(Exception):
66 def load_configuration(configuration_file):
67 default_tokens = {'http_port': 9090,
68 'http_host': 'localhost',
69 'http_console_proxy': True,
70 'http_console_host': None,
72 'log_socket_port': 9022,
73 'auto_push_VNF_to_VIMs': True,
74 'db_host': 'localhost',
75 'db_ovim_host': 'localhost'
78 #Check config file exists
79 with open(configuration_file, 'r') as f:
81 #Parse configuration file
82 config = yaml.load(config_str)
83 #Validate configuration file with the config_schema
84 js_v(config, config_schema)
86 #Add default values tokens
87 for k,v in default_tokens.items():
92 except yaml.YAMLError as e:
94 if hasattr(e, 'problem_mark'):
96 error_pos = " at line:{} column:{}".format(mark.line+1, mark.column+1)
97 raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}: {message}".format(
98 file=configuration_file, pos=error_pos, message=e))
99 except js_e.ValidationError as e:
102 error_pos=" at '" + ":".join(map(str, e.path))+"'"
103 raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format(
104 file=configuration_file, pos=error_pos, message=e))
105 except Exception as e:
106 raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format(
107 file=configuration_file, message=e))
110 def console_port_iterator():
111 '''this iterator deals with the http_console_ports
112 returning the ports one by one
115 while index < len(global_config["http_console_ports"]):
116 port = global_config["http_console_ports"][index]
117 #print("ports -> ", port)
118 if type(port) is int:
120 else: #this is dictionary with from to keys
122 #print("ports -> ", port, port2)
123 while port2 <= port["to"]:
124 #print("ports -> ", port, port2)
131 print("Usage: ", sys.argv[0], "[options]")
132 print(" -v|--version: prints current version")
133 print(" -c|--config [configuration_file]: loads the configuration file (default: openmanod.cfg)")
134 print(" -h|--help: shows this help")
135 print(" -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)")
136 print(" -P|--adminport [port_number]: changes admin port number and overrides the port number in the configuration file (default: 9095)")
137 # print( " -V|--vnf-repository: changes the path of the vnf-repository and overrides the path in the configuration file")
138 print(" --log-socket-host HOST: send logs to this host")
139 print(" --log-socket-port PORT: send logs using this port (default: 9022)")
140 print(" --log-file FILE: send logs to this file")
141 print(" --create-tenant NAME: Try to creates this tenant name before starting, ignoring any errors as e.g. conflict")
145 def set_logging_file(log_file):
147 file_handler = logging.handlers.RotatingFileHandler(log_file, maxBytes=100e6, backupCount=9, delay=0)
148 file_handler.setFormatter(log_formatter_simple)
149 logger.addHandler(file_handler)
150 # logger.debug("moving logs to '%s'", global_config["log_file"])
151 # remove initial stream handler
152 logging.root.removeHandler(logging.root.handlers[0])
153 print ("logging on '{}'".format(log_file))
155 raise LoadConfigurationException(
156 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(log_file, e))
159 if __name__=="__main__":
161 'db_host': 'RO_DB_HOST',
162 'db_name': 'RO_DB_NAME',
163 'db_user': 'RO_DB_USER',
164 'db_passwd': 'RO_DB_PASSWORD',
165 'db_ovim_host': 'RO_DB_OVIM_HOST',
166 'db_ovim_name': 'RO_DB_OVIM_NAME',
167 'db_ovim_user': 'RO_DB_OVIM_USER',
168 'db_ovim_passwd': 'RO_DB_OVIM_PASSWORD',
169 'db_port': 'RO_DB_PORT',
170 'db_port': 'RO_DB_PORT',
172 # Configure logging step 1
173 hostname = socket.gethostname()
174 # streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
175 # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
176 log_formatter_complete = logging.Formatter('%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s '
177 'severity:%(levelname)s logger:%(name)s log:%(message)s'.format(
179 datefmt='%Y-%m-%dT%H:%M:%S')
180 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
181 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
182 logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
183 logger = logging.getLogger('openmano')
184 logger.setLevel(logging.DEBUG)
185 socket_handler = None
186 # Read parameters and configuration file
189 # load parameters and configuration
190 opts, args = getopt.getopt(sys.argv[1:], "hvc:V:p:P:",
191 ["config=", "help", "version", "port=", "vnf-repository=", "adminport=",
192 "log-socket-host=", "log-socket-port=", "log-file=", "create-tenant="])
195 config_file = 'osm_ro/openmanod.cfg'
196 vnf_repository = None
198 log_socket_host = None
199 log_socket_port = None
203 if o in ("-v", "--version"):
204 print ("openmanod version " + __version__ + ' ' + version_date)
205 print ("(c) Copyright Telefonica")
207 elif o in ("-h", "--help"):
210 elif o in ("-V", "--vnf-repository"):
212 elif o in ("-c", "--config"):
214 elif o in ("-p", "--port"):
216 elif o in ("-P", "--adminport"):
218 elif o == "--log-socket-port":
220 elif o == "--log-socket-host":
222 elif o == "--log-file":
224 elif o == "--create-tenant":
227 assert False, "Unhandled option"
229 set_logging_file(log_file)
230 global_config = load_configuration(config_file)
231 global_config["version"] = __version__
232 global_config["version_date"] = version_date
234 # Override parameters obtained by command line on ENV
236 global_config['http_port'] = port
238 global_config['http_admin_port'] = port_admin
240 global_config['log_socket_host'] = log_socket_host
242 global_config['log_socket_port'] = log_socket_port
245 for config_key, env_var in env_config.items():
246 if os_getenv(env_var):
247 global_config[config_key] = os_getenv(env_var)
250 # if vnf_repository is not None:
251 # global_config['vnf_repository'] = vnf_repository
253 # if not 'vnf_repository' in global_config:
254 # logger.error( os.getcwd() )
255 # global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
256 # #print global_config
257 # if not os.path.exists(global_config['vnf_repository']):
258 # logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
260 # os.makedirs(global_config['vnf_repository'])
261 # except Exception as e:
262 # logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
265 global_config["console_port_iterator"] = console_port_iterator
266 global_config["console_thread"]={}
267 global_config["console_ports"]={}
268 if not global_config["http_console_host"]:
269 global_config["http_console_host"] = global_config["http_host"]
270 if global_config["http_host"]=="0.0.0.0":
271 global_config["http_console_host"] = socket.gethostname()
273 # Configure logging STEP 2
274 if "log_host" in global_config:
275 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
276 socket_handler.setFormatter(log_formatter_complete)
277 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
278 socket_handler.setLevel(global_config["log_socket_level"])
279 logger.addHandler(socket_handler)
281 # logger.addHandler(log_handlers.SysLogHandler())
283 global_config['log_file'] = log_file
284 elif global_config.get('log_file'):
285 set_logging_file(global_config['log_file'])
287 # logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
288 logger.setLevel(getattr(logging, global_config['log_level']))
289 logger.critical("Starting openmano server version: '%s %s' command: '%s'",
290 __version__, version_date, " ".join(sys.argv))
292 for log_module in ("nfvo", "http", "vim", "wim", "db", "console", "ovim"):
293 log_level_module = "log_level_" + log_module
294 log_file_module = "log_file_" + log_module
295 logger_module = logging.getLogger('openmano.' + log_module)
296 if log_level_module in global_config:
297 logger_module.setLevel(global_config[log_level_module])
298 if log_file_module in global_config:
300 file_handler = logging.handlers.RotatingFileHandler(global_config[log_file_module],
301 maxBytes=100e6, backupCount=9, delay=0)
302 file_handler.setFormatter(log_formatter_simple)
303 logger_module.addHandler(file_handler)
305 raise LoadConfigurationException(
306 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(
307 global_config[log_file_module], str(e)) )
308 global_config["logger_"+log_module] = logger_module
309 #httpserver.logger = global_config["logger_http"]
310 #nfvo.logger = global_config["logger_nfvo"]
312 # Initialize DB connection
313 mydb = nfvo_db.nfvo_db();
314 mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
315 db_path = osm_ro.__path__[0] + "/database_utils"
316 if not os_path.exists(db_path + "/migrate_mano_db.sh"):
317 db_path = osm_ro.__path__[0] + "/../database_utils"
319 r = mydb.get_db_version()
320 if r[0] != database_version:
321 logger.critical("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
322 " with '{db_path}/migrate_mano_db.sh {target}'".format(
323 current=r[0], target=database_version, db_path=db_path))
325 except db_base_Exception as e:
326 logger.critical("DATABASE is not valid. If you think it is corrupted, you can init it with"
327 " '{db_path}/init_mano_db.sh' script".format(db_path=db_path))
330 nfvo.global_config=global_config
333 nfvo.new_tenant(mydb, {"name": create_tenant})
334 except Exception as e:
335 if isinstance(e, nfvo.NfvoException) and e.http_code == 409:
336 pass # if tenant exist (NfvoException error 409), ignore
337 else: # otherwise print and error and continue
338 logger.error("Cannot create tenant '{}': {}".format(create_tenant, e))
341 wim_persistence = WimPersistence(mydb)
342 wim_engine = WimEngine(wim_persistence)
344 nfvo.start_service(mydb, wim_persistence, wim_engine)
346 httpthread = httpserver.httpserver(
348 global_config['http_host'], global_config['http_port'],
349 wim_persistence, wim_engine
353 if 'http_admin_port' in global_config:
354 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
355 httpthreadadmin.start()
357 logger.info('Waiting for http clients')
358 print('Waiting for http clients')
359 print('openmanod ready')
360 print('====================')
364 #TODO: Interactive console must be implemented here instead of join or sleep
367 #if 'http_admin_port' in global_config:
368 # httpthreadadmin.join()
372 except KeyboardInterrupt as e:
376 except getopt.GetoptError as e:
377 logger.critical(str(e)) # will print something like "option -a not recognized"
380 except LoadConfigurationException as e:
381 logger.critical(str(e))
383 except db_base_Exception as e:
384 logger.critical(str(e))
386 except nfvo.NfvoException as e:
387 logger.critical(str(e), exc_info=True)