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__
= "8.0.0.post1"
57 version_date
= "Jul 2020"
58 database_version
= 41 # 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
, Loader
=yaml
.SafeLoader
)
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
))
162 Try to get version from package using pkg_resources (available with setuptools)
165 ro_version
= __version__
167 from pkg_resources
import get_distribution
168 ro_version
= get_distribution("osm_ro").version
174 if __name__
== "__main__":
175 # env2config contains environ variable names and the correspondence with configuration file openmanod.cfg keys.
176 # If this environ is defined, this value is taken instead of the one at at configuration file
178 'RO_DB_HOST': 'db_host',
179 'RO_DB_NAME': 'db_name',
180 'RO_DB_USER': 'db_user',
181 'RO_DB_PASSWORD': 'db_passwd',
182 'RO_DB_OVIM_HOST': 'db_ovim_host',
183 'RO_DB_OVIM_NAME': 'db_ovim_name',
184 'RO_DB_OVIM_USER': 'db_ovim_user',
185 'RO_DB_OVIM_PASSWORD': 'db_ovim_passwd',
186 'RO_LOG_LEVEL': 'log_level',
187 'RO_LOG_FILE': 'log_file',
189 ro_version
= _get_version()
190 # Configure logging step 1
191 hostname
= socket
.gethostname()
192 log_formatter_str
= '%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s severity:%(levelname)s logger:%(name)s log:%(message)s'
193 log_formatter_complete
= logging
.Formatter(log_formatter_str
.format(host
=hostname
), datefmt
='%Y-%m-%dT%H:%M:%S')
194 log_format_simple
= "%(asctime)s %(levelname)s %(name)s %(thread)d %(filename)s:%(lineno)s %(message)s"
195 log_formatter_simple
= logging
.Formatter(log_format_simple
, datefmt
='%Y-%m-%dT%H:%M:%S')
196 logging
.basicConfig(format
=log_format_simple
, level
=logging
.DEBUG
)
197 logger
= logging
.getLogger('openmano')
198 logger
.setLevel(logging
.DEBUG
)
199 socket_handler
= None
200 # Read parameters and configuration file
203 # load parameters and configuration
204 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:V:p:P:",
205 ["config=", "help", "version", "port=", "vnf-repository=", "adminport=",
206 "log-socket-host=", "log-socket-port=", "log-file=", "create-tenant="])
209 config_file
= 'openmanod.cfg'
210 vnf_repository
= None
212 log_socket_host
= None
213 log_socket_port
= None
217 if o
in ("-v", "--version"):
218 print ("openmanod version {} {}".format(ro_version
, version_date
))
219 print ("(c) Copyright Telefonica")
221 elif o
in ("-h", "--help"):
224 elif o
in ("-V", "--vnf-repository"):
226 elif o
in ("-c", "--config"):
228 elif o
in ("-p", "--port"):
230 elif o
in ("-P", "--adminport"):
232 elif o
== "--log-socket-port":
234 elif o
== "--log-socket-host":
236 elif o
== "--log-file":
238 elif o
== "--create-tenant":
241 assert False, "Unhandled option"
243 set_logging_file(log_file
)
244 global_config
= load_configuration(config_file
)
245 global_config
["version"] = ro_version
246 global_config
["version_date"] = version_date
247 # Override parameters obtained by command line on ENV
249 global_config
['http_port'] = port
251 global_config
['http_admin_port'] = port_admin
253 global_config
['log_socket_host'] = log_socket_host
255 global_config
['log_socket_port'] = log_socket_port
258 for env_k
, env_v
in environ
.items():
260 if not env_k
.startswith("RO_") or env_k
not in env2config
or not env_v
:
262 global_config
[env2config
[env_k
]] = env_v
263 if env_k
.endswith("PORT"): # convert to int, skip if not possible
264 global_config
[env2config
[env_k
]] = int(env_v
)
265 except Exception as e
:
266 logger
.warn("skipping environ '{}={}' because exception '{}'".format(env_k
, env_v
, e
))
268 global_config
["console_port_iterator"] = console_port_iterator
269 global_config
["console_thread"] = {}
270 global_config
["console_ports"] = {}
271 if not global_config
["http_console_host"]:
272 global_config
["http_console_host"] = global_config
["http_host"]
273 if global_config
["http_host"] == "0.0.0.0":
274 global_config
["http_console_host"] = socket
.gethostname()
276 # Configure logging STEP 2
277 if "log_host" in global_config
:
278 socket_handler
= log_handlers
.SocketHandler(global_config
["log_socket_host"],
279 global_config
["log_socket_port"])
280 socket_handler
.setFormatter(log_formatter_complete
)
281 if global_config
.get("log_socket_level") \
282 and global_config
["log_socket_level"] != global_config
["log_level"]:
283 socket_handler
.setLevel(global_config
["log_socket_level"])
284 logger
.addHandler(socket_handler
)
287 global_config
['log_file'] = log_file
288 elif global_config
.get('log_file'):
289 set_logging_file(global_config
['log_file'])
291 logger
.setLevel(getattr(logging
, global_config
['log_level']))
292 logger
.critical("Starting openmano server version: '%s %s' command: '%s'",
293 ro_version
, version_date
, " ".join(sys
.argv
))
295 for log_module
in ("nfvo", "http", "vim", "wim", "db", "console", "ovim", "sdn", "sdnconn"):
296 log_level_module
= "log_level_" + log_module
297 log_file_module
= "log_file_" + log_module
298 logger_module
= logging
.getLogger('openmano.' + log_module
)
299 if log_level_module
in global_config
:
300 logger_module
.setLevel(global_config
[log_level_module
])
301 if log_file_module
in global_config
:
303 file_handler
= logging
.handlers
.RotatingFileHandler(global_config
[log_file_module
],
304 maxBytes
=100e6
, backupCount
=9, delay
=0)
305 file_handler
.setFormatter(log_formatter_simple
)
306 logger_module
.addHandler(file_handler
)
308 raise LoadConfigurationException(
309 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(
310 global_config
[log_file_module
], str(e
)))
311 global_config
["logger_" + log_module
] = logger_module
313 # Initialize DB connection
314 mydb
= nfvo_db
.nfvo_db()
315 mydb
.connect(global_config
['db_host'], global_config
['db_user'], global_config
['db_passwd'],
316 global_config
['db_name'])
317 db_path
= osm_ro
.__path
__[0] + "/database_utils"
318 if not os_path
.exists(db_path
+ "/migrate_mano_db.sh"):
319 db_path
= osm_ro
.__path
__[0] + "/../database_utils"
321 r
= mydb
.get_db_version()
322 if r
[0] != database_version
:
323 logger
.critical("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
324 " with '{db_path}/migrate_mano_db.sh {target}'".format(current
=r
[0],
325 target
=database_version
,
328 except db_base_Exception
as e
:
329 logger
.critical("DATABASE is not valid. If you think it is corrupted, you can init it with"
330 " '{db_path}/init_mano_db.sh' script".format(db_path
=db_path
))
333 nfvo
.global_config
= global_config
336 nfvo
.new_tenant(mydb
, {"name": create_tenant
})
337 except Exception as e
:
338 if isinstance(e
, nfvo
.NfvoException
) and e
.http_code
== 409:
339 pass # if tenant exist (NfvoException error 409), ignore
340 else: # otherwise print and error and continue
341 logger
.error("Cannot create tenant '{}': {}".format(create_tenant
, e
))
344 wim_persistence
= WimPersistence(mydb
)
345 wim_engine
= WimEngine(wim_persistence
, nfvo
.plugins
)
347 nfvo
.start_service(mydb
, wim_persistence
, wim_engine
)
349 httpthread
= httpserver
.httpserver(
351 global_config
['http_host'], global_config
['http_port'],
352 wim_persistence
, wim_engine
356 if 'http_admin_port' in global_config
:
357 httpthreadadmin
= httpserver
.httpserver(mydb
, True, global_config
['http_host'],
358 global_config
['http_admin_port'])
359 httpthreadadmin
.start()
361 logger
.info('Waiting for http clients')
362 print('Waiting for http clients')
363 print('openmanod ready')
364 print('====================')
368 # TODO: Interactive console must be implemented here instead of join or sleep
371 # if 'http_admin_port' in global_config:
372 # httpthreadadmin.join()
376 except KeyboardInterrupt as e
:
380 except getopt
.GetoptError
as e
:
381 logger
.critical(str(e
)) # will print something like "option -a not recognized"
383 except LoadConfigurationException
as e
:
384 logger
.critical(str(e
))
386 except db_base_Exception
as e
:
387 logger
.critical(str(e
))
389 except nfvo
.NfvoException
as e
:
390 logger
.critical(str(e
), exc_info
=True)