Adding flake8 tests over some files
[osm/RO.git] / openmanod
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 ##
5 # Copyright 2015 Telefonica Investigacion y Desarrollo, S.A.U.
6 # This file is part of openmano
7 # All Rights Reserved.
8 #
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
12 #
13 #         http://www.apache.org/licenses/LICENSE-2.0
14 #
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
19 # under the License.
20 #
21 # For those usages not covered by the Apache License, Version 2.0 please
22 # contact with: nfvlabs@tid.es
23 ##
24
25 """
26 openmano server.
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.
31
32 It loads the configuration file and launches the http_server thread that will listen requests using openmano API.
33 """
34
35 import time
36 import sys
37 import getopt
38 import yaml
39 from os import environ, path as os_path
40 from jsonschema import validate as js_v, exceptions as js_e
41 import logging
42 import logging.handlers as log_handlers
43 import socket
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
49 import osm_ro
50
51 __author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes"
52 __date__ = "$26-aug-2014 11:09:29$"
53 __version__ = "0.6.12"
54 version_date = "Apr 2019"
55 database_version = 38      # expected database schema version
56
57 global global_config
58 global logger
59
60
61 class LoadConfigurationException(Exception):
62     pass
63
64
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,
70                       'log_level': 'DEBUG',
71                       'log_socket_port': 9022,
72                       'auto_push_VNF_to_VIMs': True,
73                       'db_host': 'localhost',
74                       'db_ovim_host': 'localhost'
75     }
76     try:
77         #Check config file exists
78         with open(configuration_file, 'r') as f:
79             config_str = f.read()
80         #Parse configuration file
81         config = yaml.load(config_str)
82         #Validate configuration file with the config_schema
83         js_v(config, config_schema)
84
85         #Add default values tokens
86         for k,v in default_tokens.items():
87             if k not in config:
88                 config[k]=v
89         return config
90
91     except yaml.YAMLError as e:
92         error_pos = ""
93         if hasattr(e, 'problem_mark'):
94             mark = 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:
99         error_pos = ""
100         if e.path:
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))
107
108
109 def console_port_iterator():
110     '''this iterator deals with the http_console_ports
111     returning the ports one by one
112     '''
113     index = 0
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:
118             yield port
119         else: #this is dictionary with from to keys
120             port2 = port["from"]
121             #print("ports -> ", port, port2)
122             while port2 <= port["to"]:
123                 #print("ports -> ", port, port2)
124                 yield port2
125                 port2 += 1
126         index += 1
127
128
129 def usage():
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")
141     return
142
143
144 def set_logging_file(log_file):
145     try:
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))
153     except IOError as e:
154         raise LoadConfigurationException(
155             "Cannot open logging file '{}': {}. Check folder exist and permissions".format(log_file, e))
156
157
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
161     env2config = {
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',
174     }
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(
181                                                     host=hostname),
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
190     httpthread = None
191     try:
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="])
196         port=None
197         port_admin = None
198         config_file = 'osm_ro/openmanod.cfg'
199         vnf_repository = None
200         log_file = None
201         log_socket_host = None
202         log_socket_port = None
203         create_tenant = None
204
205         for o, a in opts:
206             if o in ("-v", "--version"):
207                 print ("openmanod version " + __version__ + ' ' + version_date)
208                 print ("(c) Copyright Telefonica")
209                 sys.exit()
210             elif o in ("-h", "--help"):
211                 usage()
212                 sys.exit()
213             elif o in ("-V", "--vnf-repository"):
214                 vnf_repository = a
215             elif o in ("-c", "--config"):
216                 config_file = a
217             elif o in ("-p", "--port"):
218                 port = a
219             elif o in ("-P", "--adminport"):
220                 port_admin = a
221             elif o == "--log-socket-port":
222                 log_socket_port = a
223             elif o == "--log-socket-host":
224                 log_socket_host = a
225             elif o == "--log-file":
226                 log_file = a
227             elif o == "--create-tenant":
228                 create_tenant = a
229             else:
230                 assert False, "Unhandled option"
231         if log_file:
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
236         #print global_config
237         # Override parameters obtained by command line on ENV
238         if port:
239             global_config['http_port'] = port
240         if port_admin:
241             global_config['http_admin_port'] = port_admin
242         if log_socket_host:
243             global_config['log_socket_host'] = log_socket_host
244         if log_socket_port:
245             global_config['log_socket_port'] = log_socket_port
246
247         # override with ENV
248         for env_k, env_v in environ.items():
249             try:
250                 if not env_k.startswith("RO_") or env_k not in env2config or not env_v:
251                     continue
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))
257
258 #         if vnf_repository is not None:
259 #             global_config['vnf_repository'] = vnf_repository
260 #         else:
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'])
267 #             try:
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)
271 #                 exit(-1)
272
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()
280
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)
288
289         # logger.addHandler(log_handlers.SysLogHandler())
290         if log_file:
291             global_config['log_file'] = log_file
292         elif global_config.get('log_file'):
293             set_logging_file(global_config['log_file'])
294
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))
299
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:
307                 try:
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)
312                 except IOError as e:
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"]
319
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"
326         try:
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))
332                 exit(-1)
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))
336             exit(-1)
337
338         nfvo.global_config=global_config
339         if create_tenant:
340             try:
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))
347
348         # WIM module
349         wim_persistence = WimPersistence(mydb)
350         wim_engine = WimEngine(wim_persistence)
351         # ---
352         nfvo.start_service(mydb, wim_persistence, wim_engine)
353
354         httpthread = httpserver.httpserver(
355             mydb, False,
356             global_config['http_host'], global_config['http_port'],
357             wim_persistence, wim_engine
358         )
359
360         httpthread.start()
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()
364         time.sleep(1)
365         logger.info('Waiting for http clients')
366         print('Waiting for http clients')
367         print('openmanod ready')
368         print('====================')
369         time.sleep(20)
370         sys.stdout.flush()
371
372         #TODO: Interactive console must be implemented here instead of join or sleep
373
374         #httpthread.join()
375         #if 'http_admin_port' in global_config:
376         #    httpthreadadmin.join()
377         while True:
378             time.sleep(86400)
379
380     except KeyboardInterrupt as e:
381         logger.info(str(e))
382     except SystemExit:
383         pass
384     except getopt.GetoptError as e:
385         logger.critical(str(e)) # will print something like "option -a not recognized"
386         #usage()
387         exit(-1)
388     except LoadConfigurationException as e:
389         logger.critical(str(e))
390         exit(-1)
391     except db_base_Exception as e:
392         logger.critical(str(e))
393         exit(-1)
394     except nfvo.NfvoException as e:
395         logger.critical(str(e), exc_info=True)
396         exit(-1)
397     nfvo.stop_service()
398     if httpthread:
399         httpthread.join(1)
400