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
49 __author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes"
50 __date__ = "$26-aug-2014 11:09:29$"
51 __version__ = "0.5.80-r590"
52 version_date = "Oct 2018"
53 database_version = 32 # expected database schema version
60 class LoadConfigurationException(Exception):
64 def load_configuration(configuration_file):
65 default_tokens = {'http_port': 9090,
66 'http_host': 'localhost',
67 'http_console_proxy': True,
68 'http_console_host': None,
70 'log_socket_port': 9022,
71 'auto_push_VNF_to_VIMs': True,
72 'db_host': 'localhost',
73 'db_ovim_host': 'localhost'
76 #Check config file exists
77 with open(configuration_file, 'r') as f:
79 #Parse configuration file
80 config = yaml.load(config_str)
81 #Validate configuration file with the config_schema
82 js_v(config, config_schema)
84 #Add default values tokens
85 for k,v in default_tokens.items():
90 except yaml.YAMLError as e:
92 if hasattr(e, 'problem_mark'):
94 error_pos = " at line:{} column:{}".format(mark.line+1, mark.column+1)
95 raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}: {message}".format(
96 file=configuration_file, pos=error_pos, message=e))
97 except js_e.ValidationError as e:
100 error_pos=" at '" + ":".join(map(str, e.path))+"'"
101 raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format(
102 file=configuration_file, pos=error_pos, message=e))
103 except Exception as e:
104 raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format(
105 file=configuration_file, message=e))
108 def console_port_iterator():
109 '''this iterator deals with the http_console_ports
110 returning the ports one by one
113 while index < len(global_config["http_console_ports"]):
114 port = global_config["http_console_ports"][index]
115 #print("ports -> ", port)
116 if type(port) is int:
118 else: #this is dictionary with from to keys
120 #print("ports -> ", port, port2)
121 while port2 <= port["to"]:
122 #print("ports -> ", port, port2)
129 print("Usage: ", sys.argv[0], "[options]")
130 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")
139 print(" --create-tenant NAME: Try to creates this tenant name before starting, ignoring any errors as e.g. conflict")
143 def set_logging_file(log_file):
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))
153 raise LoadConfigurationException(
154 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(log_file, e))
157 if __name__=="__main__":
158 # env2config contains envioron variable names and the correspondence with configuration file openmanod.cfg keys.
159 # If this environ is defined, this value is taken instead of the one at at configuration file
161 'RO_DB_HOST': 'db_host',
162 'RO_DB_NAME': 'db_name',
163 'RO_DB_USER': 'db_user',
164 'RO_DB_PASSWORD': 'db_passwd',
165 # 'RO_DB_PORT': 'db_port',
166 'RO_DB_OVIM_HOST': 'db_ovim_host',
167 'RO_DB_OVIM_NAME': 'db_ovim_name',
168 'RO_DB_OVIM_USER': 'db_ovim_user',
169 'RO_DB_OVIM_PASSWORD': 'db_ovim_passwd',
170 # 'RO_DB_OVIM_PORT': 'db_ovim_port',
171 'RO_LOG_LEVEL': 'log_level',
172 'RO_LOG_FILE': 'log_file',
174 # Configure logging step 1
175 hostname = socket.gethostname()
176 # streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
177 # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
178 log_formatter_complete = logging.Formatter('%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s '
179 'severity:%(levelname)s logger:%(name)s log:%(message)s'.format(
181 datefmt='%Y-%m-%dT%H:%M:%S')
182 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(thread)d %(filename)s:%(lineno)s %(message)s"
183 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
184 logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
185 logger = logging.getLogger('openmano')
186 logger.setLevel(logging.DEBUG)
187 socket_handler = None
188 # Read parameters and configuration file
191 # load parameters and configuration
192 opts, args = getopt.getopt(sys.argv[1:], "hvc:V:p:P:",
193 ["config=", "help", "version", "port=", "vnf-repository=", "adminport=",
194 "log-socket-host=", "log-socket-port=", "log-file=", "create-tenant="])
197 config_file = 'osm_ro/openmanod.cfg'
198 vnf_repository = None
200 log_socket_host = None
201 log_socket_port = None
205 if o in ("-v", "--version"):
206 print ("openmanod version " + __version__ + ' ' + version_date)
207 print ("(c) Copyright Telefonica")
209 elif o in ("-h", "--help"):
212 elif o in ("-V", "--vnf-repository"):
214 elif o in ("-c", "--config"):
216 elif o in ("-p", "--port"):
218 elif o in ("-P", "--adminport"):
220 elif o == "--log-socket-port":
222 elif o == "--log-socket-host":
224 elif o == "--log-file":
226 elif o == "--create-tenant":
229 assert False, "Unhandled option"
231 set_logging_file(log_file)
232 global_config = load_configuration(config_file)
233 global_config["version"] = __version__
234 global_config["version_date"] = version_date
236 # Override parameters obtained by command line on ENV
238 global_config['http_port'] = port
240 global_config['http_admin_port'] = port_admin
242 global_config['log_socket_host'] = log_socket_host
244 global_config['log_socket_port'] = log_socket_port
247 for env_k, env_v in environ.items():
249 if not env_k.startswith("RO_") or env_k not in env2config or not env_v:
251 global_config[env2config[env_k]] = env_v
252 if env_k.endswith("PORT"): # convert to int, skip if not possible
253 global_config[env2config[env_k]] = int(env_v)
254 except Exception as e:
255 logger.warn("skipping environ '{}={}' because exception '{}'".format(env_k, env_v, e))
257 # if vnf_repository is not None:
258 # global_config['vnf_repository'] = vnf_repository
260 # if not 'vnf_repository' in global_config:
261 # logger.error( os.getcwd() )
262 # global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
263 # #print global_config
264 # if not os.path.exists(global_config['vnf_repository']):
265 # logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
267 # os.makedirs(global_config['vnf_repository'])
268 # except Exception as e:
269 # logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
272 global_config["console_port_iterator"] = console_port_iterator
273 global_config["console_thread"]={}
274 global_config["console_ports"]={}
275 if not global_config["http_console_host"]:
276 global_config["http_console_host"] = global_config["http_host"]
277 if global_config["http_host"]=="0.0.0.0":
278 global_config["http_console_host"] = socket.gethostname()
280 # Configure logging STEP 2
281 if "log_host" in global_config:
282 socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
283 socket_handler.setFormatter(log_formatter_complete)
284 if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
285 socket_handler.setLevel(global_config["log_socket_level"])
286 logger.addHandler(socket_handler)
288 # logger.addHandler(log_handlers.SysLogHandler())
290 global_config['log_file'] = log_file
291 elif global_config.get('log_file'):
292 set_logging_file(global_config['log_file'])
294 # logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
295 logger.setLevel(getattr(logging, global_config['log_level']))
296 logger.critical("Starting openmano server version: '%s %s' command: '%s'",
297 __version__, version_date, " ".join(sys.argv))
299 for log_module in ("nfvo", "http", "vim", "db", "console", "ovim"):
300 log_level_module = "log_level_" + log_module
301 log_file_module = "log_file_" + log_module
302 logger_module = logging.getLogger('openmano.' + log_module)
303 if log_level_module in global_config:
304 logger_module.setLevel(global_config[log_level_module])
305 if log_file_module in global_config:
307 file_handler = logging.handlers.RotatingFileHandler(global_config[log_file_module],
308 maxBytes=100e6, backupCount=9, delay=0)
309 file_handler.setFormatter(log_formatter_simple)
310 logger_module.addHandler(file_handler)
312 raise LoadConfigurationException(
313 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(
314 global_config[log_file_module], str(e)) )
315 global_config["logger_"+log_module] = logger_module
316 #httpserver.logger = global_config["logger_http"]
317 #nfvo.logger = global_config["logger_nfvo"]
319 # Initialize DB connection
320 mydb = nfvo_db.nfvo_db();
321 mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
322 db_path = osm_ro.__path__[0] + "/database_utils"
323 if not os_path.exists(db_path + "/migrate_mano_db.sh"):
324 db_path = osm_ro.__path__[0] + "/../database_utils"
326 r = mydb.get_db_version()
327 if r[0] != database_version:
328 logger.critical("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
329 " with '{db_path}/migrate_mano_db.sh {target}'".format(
330 current=r[0], target=database_version, db_path=db_path))
332 except db_base_Exception as e:
333 logger.critical("DATABASE is not valid. If you think it is corrupted, you can init it with"
334 " '{db_path}/init_mano_db.sh' script".format(db_path=db_path))
337 nfvo.global_config=global_config
340 nfvo.new_tenant(mydb, {"name": create_tenant})
341 except Exception as e:
342 if isinstance(e, nfvo.NfvoException) and e.http_code == 409:
343 pass # if tenant exist (NfvoException error 409), ignore
344 else: # otherwise print and error and continue
345 logger.error("Cannot create tenant '{}': {}".format(create_tenant, e))
346 nfvo.start_service(mydb)
348 httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
351 if 'http_admin_port' in global_config:
352 httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
353 httpthreadadmin.start()
355 logger.info('Waiting for http clients')
356 print('Waiting for http clients')
357 print('openmanod ready')
358 print('====================')
362 #TODO: Interactive console must be implemented here instead of join or sleep
365 #if 'http_admin_port' in global_config:
366 # httpthreadadmin.join()
370 except KeyboardInterrupt as e:
374 except getopt.GetoptError as e:
375 logger.critical(str(e)) # will print something like "option -a not recognized"
378 except LoadConfigurationException as e:
379 logger.critical(str(e))
381 except db_base_Exception as e:
382 logger.critical(str(e))
384 except nfvo.NfvoException as e:
385 logger.critical(str(e), exc_info=True)