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
45 from yaml import MarkedYAMLError
47 from osm_ro import httpserver, nfvo, nfvo_db
48 from osm_ro.openmano_schemas import config_schema
49 from osm_ro.db_base import db_base_Exception
50 from osm_ro.wim.engine import WimEngine
51 from osm_ro.wim.persistence import WimPersistence
54 __author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes"
55 __date__ = "$26-aug-2014 11:09:29$"
56 __version__ = "0.6.20"
57 version_date = "May 2019"
58 database_version = 39 # expected database schema version
64 class LoadConfigurationException(Exception):
68 def load_configuration(configuration_file):
69 default_tokens = {'http_port': 9090,
70 'http_host': 'localhost',
71 'http_console_proxy': True,
72 'http_console_host': None,
74 'log_socket_port': 9022,
75 'auto_push_VNF_to_VIMs': True,
76 'db_host': 'localhost',
77 'db_ovim_host': 'localhost'
80 # Check config file exists
81 with open(configuration_file, 'r') as f:
83 # Parse configuration file
84 config = yaml.load(config_str)
85 # Validate configuration file with the config_schema
86 js_v(config, config_schema)
88 # Add default values tokens
89 for k, v in default_tokens.items():
94 except yaml.YAMLError as e:
96 if isinstance(e, MarkedYAMLError):
98 error_pos = " at line:{} column:{}".format(mark.line + 1, mark.column + 1)
99 raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}: {message}".format(
100 file=configuration_file, pos=error_pos, message=e))
101 except js_e.ValidationError as e:
104 error_pos = " at '" + ":".join(map(str, e.path)) + "'"
105 raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format(
106 file=configuration_file, pos=error_pos, message=e))
107 except Exception as e:
108 raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format(
109 file=configuration_file, message=e))
112 def console_port_iterator():
114 this iterator deals with the http_console_ports
115 returning the ports one by one
118 while index < len(global_config["http_console_ports"]):
119 port = global_config["http_console_ports"][index]
120 if type(port) is int:
122 else: # this is dictionary with from to keys
124 while port2 <= port["to"]:
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")
136 " -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)")
138 " -P|--adminport [port_number]: changes admin port number and overrides the port number in the configuration file (default: 9095)")
139 print(" --log-socket-host HOST: send logs to this host")
140 print(" --log-socket-port PORT: send logs using this port (default: 9022)")
141 print(" --log-file FILE: send logs to this file")
143 " --create-tenant NAME: Try to creates this tenant name before starting, ignoring any errors as e.g. conflict")
147 def set_logging_file(log_file):
149 file_handler = logging.handlers.RotatingFileHandler(log_file, maxBytes=100e6, backupCount=9, delay=0)
150 file_handler.setFormatter(log_formatter_simple)
151 logger.addHandler(file_handler)
152 # remove initial stream handler
153 logging.root.removeHandler(logging.root.handlers[0])
154 print ("logging on '{}'".format(log_file))
156 raise LoadConfigurationException(
157 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(log_file, e))
160 if __name__ == "__main__":
161 # env2config contains environ variable names and the correspondence with configuration file openmanod.cfg keys.
162 # If this environ is defined, this value is taken instead of the one at at configuration file
164 'RO_DB_HOST': 'db_host',
165 'RO_DB_NAME': 'db_name',
166 'RO_DB_USER': 'db_user',
167 'RO_DB_PASSWORD': 'db_passwd',
168 'RO_DB_OVIM_HOST': 'db_ovim_host',
169 'RO_DB_OVIM_NAME': 'db_ovim_name',
170 'RO_DB_OVIM_USER': 'db_ovim_user',
171 'RO_DB_OVIM_PASSWORD': 'db_ovim_passwd',
172 'RO_LOG_LEVEL': 'log_level',
173 'RO_LOG_FILE': 'log_file',
175 # Configure logging step 1
176 hostname = socket.gethostname()
177 log_formatter_str = '%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s severity:%(levelname)s logger:%(name)s log:%(message)s'
178 log_formatter_complete = logging.Formatter(log_formatter_str.format(host=hostname), datefmt='%Y-%m-%dT%H:%M:%S')
179 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(thread)d %(filename)s:%(lineno)s %(message)s"
180 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')
183 logger.setLevel(logging.DEBUG)
184 socket_handler = None
185 # Read parameters and configuration file
188 # 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="])
194 config_file = 'osm_ro/openmanod.cfg'
195 vnf_repository = None
197 log_socket_host = None
198 log_socket_port = None
202 if o in ("-v", "--version"):
203 print ("openmanod version " + __version__ + ' ' + version_date)
204 print ("(c) Copyright Telefonica")
206 elif o in ("-h", "--help"):
209 elif o in ("-V", "--vnf-repository"):
211 elif o in ("-c", "--config"):
213 elif o in ("-p", "--port"):
215 elif o in ("-P", "--adminport"):
217 elif o == "--log-socket-port":
219 elif o == "--log-socket-host":
221 elif o == "--log-file":
223 elif o == "--create-tenant":
226 assert False, "Unhandled option"
228 set_logging_file(log_file)
229 global_config = load_configuration(config_file)
230 global_config["version"] = __version__
231 global_config["version_date"] = version_date
232 # Override parameters obtained by command line on ENV
234 global_config['http_port'] = port
236 global_config['http_admin_port'] = port_admin
238 global_config['log_socket_host'] = log_socket_host
240 global_config['log_socket_port'] = log_socket_port
243 for env_k, env_v in environ.items():
245 if not env_k.startswith("RO_") or env_k not in env2config or not env_v:
247 global_config[env2config[env_k]] = env_v
248 if env_k.endswith("PORT"): # convert to int, skip if not possible
249 global_config[env2config[env_k]] = int(env_v)
250 except Exception as e:
251 logger.warn("skipping environ '{}={}' because exception '{}'".format(env_k, env_v, e))
253 global_config["console_port_iterator"] = console_port_iterator
254 global_config["console_thread"] = {}
255 global_config["console_ports"] = {}
256 if not global_config["http_console_host"]:
257 global_config["http_console_host"] = global_config["http_host"]
258 if global_config["http_host"] == "0.0.0.0":
259 global_config["http_console_host"] = socket.gethostname()
261 # Configure logging STEP 2
262 if "log_host" in global_config:
263 socket_handler = log_handlers.SocketHandler(global_config["log_socket_host"],
264 global_config["log_socket_port"])
265 socket_handler.setFormatter(log_formatter_complete)
266 if global_config.get("log_socket_level") \
267 and global_config["log_socket_level"] != global_config["log_level"]:
268 socket_handler.setLevel(global_config["log_socket_level"])
269 logger.addHandler(socket_handler)
272 global_config['log_file'] = log_file
273 elif global_config.get('log_file'):
274 set_logging_file(global_config['log_file'])
276 logger.setLevel(getattr(logging, global_config['log_level']))
277 logger.critical("Starting openmano server version: '%s %s' command: '%s'",
278 __version__, version_date, " ".join(sys.argv))
280 for log_module in ("nfvo", "http", "vim", "wim", "db", "console", "ovim"):
281 log_level_module = "log_level_" + log_module
282 log_file_module = "log_file_" + log_module
283 logger_module = logging.getLogger('openmano.' + log_module)
284 if log_level_module in global_config:
285 logger_module.setLevel(global_config[log_level_module])
286 if log_file_module in global_config:
288 file_handler = logging.handlers.RotatingFileHandler(global_config[log_file_module],
289 maxBytes=100e6, backupCount=9, delay=0)
290 file_handler.setFormatter(log_formatter_simple)
291 logger_module.addHandler(file_handler)
293 raise LoadConfigurationException(
294 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(
295 global_config[log_file_module], str(e)))
296 global_config["logger_" + log_module] = logger_module
298 # Initialize DB connection
299 mydb = nfvo_db.nfvo_db()
300 mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'],
301 global_config['db_name'])
302 db_path = osm_ro.__path__[0] + "/database_utils"
303 if not os_path.exists(db_path + "/migrate_mano_db.sh"):
304 db_path = osm_ro.__path__[0] + "/../database_utils"
306 r = mydb.get_db_version()
307 if r[0] != database_version:
308 logger.critical("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
309 " with '{db_path}/migrate_mano_db.sh {target}'".format(current=r[0],
310 target=database_version,
313 except db_base_Exception as e:
314 logger.critical("DATABASE is not valid. If you think it is corrupted, you can init it with"
315 " '{db_path}/init_mano_db.sh' script".format(db_path=db_path))
318 nfvo.global_config = global_config
321 nfvo.new_tenant(mydb, {"name": create_tenant})
322 except Exception as e:
323 if isinstance(e, nfvo.NfvoException) and e.http_code == 409:
324 pass # if tenant exist (NfvoException error 409), ignore
325 else: # otherwise print and error and continue
326 logger.error("Cannot create tenant '{}': {}".format(create_tenant, e))
329 wim_persistence = WimPersistence(mydb)
330 wim_engine = WimEngine(wim_persistence)
332 nfvo.start_service(mydb, wim_persistence, wim_engine)
334 httpthread = httpserver.httpserver(
336 global_config['http_host'], global_config['http_port'],
337 wim_persistence, wim_engine
341 if 'http_admin_port' in global_config:
342 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'],
343 global_config['http_admin_port'])
344 httpthreadadmin.start()
346 logger.info('Waiting for http clients')
347 print('Waiting for http clients')
348 print('openmanod ready')
349 print('====================')
353 # TODO: Interactive console must be implemented here instead of join or sleep
356 # if 'http_admin_port' in global_config:
357 # httpthreadadmin.join()
361 except KeyboardInterrupt as e:
365 except getopt.GetoptError as e:
366 logger.critical(str(e)) # will print something like "option -a not recognized"
368 except LoadConfigurationException as e:
369 logger.critical(str(e))
371 except db_base_Exception as e:
372 logger.critical(str(e))
374 except nfvo.NfvoException as e:
375 logger.critical(str(e), exc_info=True)