2 # -*- coding: utf-8 -*-
5 # Copyright 2015 Telefonica Investigacion 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 environ, 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.09"
54 version_date = "Feb 2019"
55 database_version = 37 # expected database schema version
61 class LoadConfigurationException(Exception):
65 def load_configuration(configuration_file):
66 default_tokens = {'http_port': 9090,
67 'http_host': 'localhost',
68 'http_console_proxy': True,
69 'http_console_host': None,
71 'log_socket_port': 9022,
72 'auto_push_VNF_to_VIMs': True,
73 'db_host': 'localhost',
74 'db_ovim_host': 'localhost'
77 #Check config file exists
78 with open(configuration_file, 'r') as f:
80 #Parse configuration file
81 config = yaml.load(config_str)
82 #Validate configuration file with the config_schema
83 js_v(config, config_schema)
85 #Add default values tokens
86 for k,v in default_tokens.items():
91 except yaml.YAMLError as e:
93 if hasattr(e, 'problem_mark'):
95 error_pos = " at line:{} column:{}".format(mark.line+1, mark.column+1)
96 raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}: {message}".format(
97 file=configuration_file, pos=error_pos, message=e))
98 except js_e.ValidationError as e:
101 error_pos=" at '" + ":".join(map(str, e.path))+"'"
102 raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format(
103 file=configuration_file, pos=error_pos, message=e))
104 except Exception as e:
105 raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format(
106 file=configuration_file, message=e))
109 def console_port_iterator():
110 '''this iterator deals with the http_console_ports
111 returning the ports one by one
114 while index < len(global_config["http_console_ports"]):
115 port = global_config["http_console_ports"][index]
116 #print("ports -> ", port)
117 if type(port) is int:
119 else: #this is dictionary with from to keys
121 #print("ports -> ", port, port2)
122 while port2 <= port["to"]:
123 #print("ports -> ", port, port2)
130 print("Usage: ", sys.argv[0], "[options]")
131 print(" -v|--version: prints current version")
132 print(" -c|--config [configuration_file]: loads the configuration file (default: openmanod.cfg)")
133 print(" -h|--help: shows this help")
134 print(" -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)")
135 print(" -P|--adminport [port_number]: changes admin port number and overrides the port number in the configuration file (default: 9095)")
136 # print( " -V|--vnf-repository: changes the path of the vnf-repository and overrides the path in the configuration file")
137 print(" --log-socket-host HOST: send logs to this host")
138 print(" --log-socket-port PORT: send logs using this port (default: 9022)")
139 print(" --log-file FILE: send logs to this file")
140 print(" --create-tenant NAME: Try to creates this tenant name before starting, ignoring any errors as e.g. conflict")
144 def set_logging_file(log_file):
146 file_handler = logging.handlers.RotatingFileHandler(log_file, maxBytes=100e6, backupCount=9, delay=0)
147 file_handler.setFormatter(log_formatter_simple)
148 logger.addHandler(file_handler)
149 # logger.debug("moving logs to '%s'", global_config["log_file"])
150 # remove initial stream handler
151 logging.root.removeHandler(logging.root.handlers[0])
152 print ("logging on '{}'".format(log_file))
154 raise LoadConfigurationException(
155 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(log_file, e))
158 if __name__=="__main__":
159 # env2config contains envioron variable names and the correspondence with configuration file openmanod.cfg keys.
160 # If this environ is defined, this value is taken instead of the one at at configuration file
162 'RO_DB_HOST': 'db_host',
163 'RO_DB_NAME': 'db_name',
164 'RO_DB_USER': 'db_user',
165 'RO_DB_PASSWORD': 'db_passwd',
166 # 'RO_DB_PORT': 'db_port',
167 'RO_DB_OVIM_HOST': 'db_ovim_host',
168 'RO_DB_OVIM_NAME': 'db_ovim_name',
169 'RO_DB_OVIM_USER': 'db_ovim_user',
170 'RO_DB_OVIM_PASSWORD': 'db_ovim_passwd',
171 # 'RO_DB_OVIM_PORT': 'db_ovim_port',
172 'RO_LOG_LEVEL': 'log_level',
173 'RO_LOG_FILE': 'log_file',
175 # Configure logging step 1
176 hostname = socket.gethostname()
177 # streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
178 # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
179 log_formatter_complete = logging.Formatter('%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s '
180 'severity:%(levelname)s logger:%(name)s log:%(message)s'.format(
182 datefmt='%Y-%m-%dT%H:%M:%S')
183 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(thread)d %(filename)s:%(lineno)s %(message)s"
184 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
185 logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
186 logger = logging.getLogger('openmano')
187 logger.setLevel(logging.DEBUG)
188 socket_handler = None
189 # Read parameters and configuration file
192 # load parameters and configuration
193 opts, args = getopt.getopt(sys.argv[1:], "hvc:V:p:P:",
194 ["config=", "help", "version", "port=", "vnf-repository=", "adminport=",
195 "log-socket-host=", "log-socket-port=", "log-file=", "create-tenant="])
198 config_file = 'osm_ro/openmanod.cfg'
199 vnf_repository = None
201 log_socket_host = None
202 log_socket_port = None
206 if o in ("-v", "--version"):
207 print ("openmanod version " + __version__ + ' ' + version_date)
208 print ("(c) Copyright Telefonica")
210 elif o in ("-h", "--help"):
213 elif o in ("-V", "--vnf-repository"):
215 elif o in ("-c", "--config"):
217 elif o in ("-p", "--port"):
219 elif o in ("-P", "--adminport"):
221 elif o == "--log-socket-port":
223 elif o == "--log-socket-host":
225 elif o == "--log-file":
227 elif o == "--create-tenant":
230 assert False, "Unhandled option"
232 set_logging_file(log_file)
233 global_config = load_configuration(config_file)
234 global_config["version"] = __version__
235 global_config["version_date"] = version_date
237 # Override parameters obtained by command line on ENV
239 global_config['http_port'] = port
241 global_config['http_admin_port'] = port_admin
243 global_config['log_socket_host'] = log_socket_host
245 global_config['log_socket_port'] = log_socket_port
248 for env_k, env_v in environ.items():
250 if not env_k.startswith("RO_") or env_k not in env2config or not env_v:
252 global_config[env2config[env_k]] = env_v
253 if env_k.endswith("PORT"): # convert to int, skip if not possible
254 global_config[env2config[env_k]] = int(env_v)
255 except Exception as e:
256 logger.warn("skipping environ '{}={}' because exception '{}'".format(env_k, env_v, e))
258 # if vnf_repository is not None:
259 # global_config['vnf_repository'] = vnf_repository
261 # if not 'vnf_repository' in global_config:
262 # logger.error( os.getcwd() )
263 # global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
264 # #print global_config
265 # if not os.path.exists(global_config['vnf_repository']):
266 # logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
268 # os.makedirs(global_config['vnf_repository'])
269 # except Exception as e:
270 # logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
273 global_config["console_port_iterator"] = console_port_iterator
274 global_config["console_thread"]={}
275 global_config["console_ports"]={}
276 if not global_config["http_console_host"]:
277 global_config["http_console_host"] = global_config["http_host"]
278 if global_config["http_host"]=="0.0.0.0":
279 global_config["http_console_host"] = socket.gethostname()
281 # Configure logging STEP 2
282 if "log_host" in global_config:
283 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
284 socket_handler.setFormatter(log_formatter_complete)
285 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
286 socket_handler.setLevel(global_config["log_socket_level"])
287 logger.addHandler(socket_handler)
289 # logger.addHandler(log_handlers.SysLogHandler())
291 global_config['log_file'] = log_file
292 elif global_config.get('log_file'):
293 set_logging_file(global_config['log_file'])
295 # logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
296 logger.setLevel(getattr(logging, global_config['log_level']))
297 logger.critical("Starting openmano server version: '%s %s' command: '%s'",
298 __version__, version_date, " ".join(sys.argv))
300 for log_module in ("nfvo", "http", "vim", "wim", "db", "console", "ovim"):
301 log_level_module = "log_level_" + log_module
302 log_file_module = "log_file_" + log_module
303 logger_module = logging.getLogger('openmano.' + log_module)
304 if log_level_module in global_config:
305 logger_module.setLevel(global_config[log_level_module])
306 if log_file_module in global_config:
308 file_handler = logging.handlers.RotatingFileHandler(global_config[log_file_module],
309 maxBytes=100e6, backupCount=9, delay=0)
310 file_handler.setFormatter(log_formatter_simple)
311 logger_module.addHandler(file_handler)
313 raise LoadConfigurationException(
314 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(
315 global_config[log_file_module], str(e)) )
316 global_config["logger_"+log_module] = logger_module
317 #httpserver.logger = global_config["logger_http"]
318 #nfvo.logger = global_config["logger_nfvo"]
320 # Initialize DB connection
321 mydb = nfvo_db.nfvo_db();
322 mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
323 db_path = osm_ro.__path__[0] + "/database_utils"
324 if not os_path.exists(db_path + "/migrate_mano_db.sh"):
325 db_path = osm_ro.__path__[0] + "/../database_utils"
327 r = mydb.get_db_version()
328 if r[0] != database_version:
329 logger.critical("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
330 " with '{db_path}/migrate_mano_db.sh {target}'".format(
331 current=r[0], target=database_version, db_path=db_path))
333 except db_base_Exception as e:
334 logger.critical("DATABASE is not valid. If you think it is corrupted, you can init it with"
335 " '{db_path}/init_mano_db.sh' script".format(db_path=db_path))
338 nfvo.global_config=global_config
341 nfvo.new_tenant(mydb, {"name": create_tenant})
342 except Exception as e:
343 if isinstance(e, nfvo.NfvoException) and e.http_code == 409:
344 pass # if tenant exist (NfvoException error 409), ignore
345 else: # otherwise print and error and continue
346 logger.error("Cannot create tenant '{}': {}".format(create_tenant, e))
349 wim_persistence = WimPersistence(mydb)
350 wim_engine = WimEngine(wim_persistence)
352 nfvo.start_service(mydb, wim_persistence, wim_engine)
354 httpthread = httpserver.httpserver(
356 global_config['http_host'], global_config['http_port'],
357 wim_persistence, wim_engine
361 if 'http_admin_port' in global_config:
362 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
363 httpthreadadmin.start()
365 logger.info('Waiting for http clients')
366 print('Waiting for http clients')
367 print('openmanod ready')
368 print('====================')
372 #TODO: Interactive console must be implemented here instead of join or sleep
375 #if 'http_admin_port' in global_config:
376 # httpthreadadmin.join()
380 except KeyboardInterrupt as e:
384 except getopt.GetoptError as e:
385 logger.critical(str(e)) # will print something like "option -a not recognized"
388 except LoadConfigurationException as e:
389 logger.critical(str(e))
391 except db_base_Exception as e:
392 logger.critical(str(e))
394 except nfvo.NfvoException as e:
395 logger.critical(str(e), exc_info=True)