Fix bug 350 - Sensitive information recorded in openmano.log
[osm/RO.git] / openmanod
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 ##
5 # Copyright 2015 Telefónica Investigación 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 import 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 import osm_ro
48
49 __author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes"
50 __date__ = "$26-aug-2014 11:09:29$"
51 __version__ = "0.5.31-r541"
52 version_date = "Oct 2017"
53 database_version = 27      # expected database schema version
54
55
56 global global_config
57 global logger
58
59
60 class LoadConfigurationException(Exception):
61     pass
62
63 def load_configuration(configuration_file):
64     default_tokens = {'http_port':9090,
65                       'http_host':'localhost',
66                       'http_console_proxy': True,
67                       'http_console_host': None,
68                       'log_level': 'DEBUG',
69                       'log_socket_port': 9022,
70                       'auto_push_VNF_to_VIMs': True,
71                       'db_host': 'localhost',
72                       'db_ovim_host': 'localhost'
73     }
74     try:
75         #Check config file exists
76         with open(configuration_file, 'r') as f:
77             config_str = f.read()
78         #Parse configuration file
79         config = yaml.load(config_str)
80         #Validate configuration file with the config_schema
81         js_v(config, config_schema)
82
83         #Add default values tokens
84         for k,v in default_tokens.items():
85             if k not in config:
86                 config[k]=v
87         return config
88
89     except yaml.YAMLError as e:
90         error_pos = ""
91         if hasattr(e, 'problem_mark'):
92             mark = e.problem_mark
93             error_pos = " at line:{} column:{}".format(mark.line+1, mark.column+1)
94         raise LoadConfigurationException("Bad YAML format at configuration file '{file}'{pos}: {message}".format(
95             file=configuration_file, pos=error_pos, message=e))
96     except js_e.ValidationError as e:
97         error_pos = ""
98         if e.path:
99             error_pos=" at '" + ":".join(map(str, e.path))+"'"
100         raise LoadConfigurationException("Invalid field at configuration file '{file}'{pos} {message}".format(
101             file=configuration_file, pos=error_pos, message=e))
102     except Exception as e:
103         raise LoadConfigurationException("Cannot load configuration file '{file}' {message}".format(
104             file=configuration_file, message=e))
105
106
107 def console_port_iterator():
108     '''this iterator deals with the http_console_ports 
109     returning the ports one by one
110     '''
111     index = 0
112     while index < len(global_config["http_console_ports"]):
113         port = global_config["http_console_ports"][index]
114         #print("ports -> ", port)
115         if type(port) is int:
116             yield port
117         else: #this is dictionary with from to keys
118             port2 = port["from"]
119             #print("ports -> ", port, port2)
120             while port2 <= port["to"]:
121                 #print("ports -> ", port, port2)
122                 yield port2
123                 port2 += 1
124         index += 1
125
126
127 def usage():
128     print("Usage: ", sys.argv[0], "[options]")
129     print( "      -v|--version: prints current version")
130     print( "      -c|--config [configuration_file]: loads the configuration file (default: openmanod.cfg)")
131     print( "      -h|--help: shows this help")
132     print( "      -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)")
133     print( "      -P|--adminport [port_number]: changes admin port number and overrides the port number in the configuration file (default: 9095)")
134     #print( "      -V|--vnf-repository: changes the path of the vnf-repository and overrides the path in the configuration file")
135     print( "      --log-socket-host HOST: send logs to this host")
136     print( "      --log-socket-port PORT: send logs using this port (default: 9022)")
137     print( "      --log-file FILE: send logs to this file")
138     return
139
140
141 def set_logging_file(log_file):
142     try:
143         file_handler = logging.handlers.RotatingFileHandler(log_file, maxBytes=100e6, backupCount=9, delay=0)
144         file_handler.setFormatter(log_formatter_simple)
145         logger.addHandler(file_handler)
146         # logger.debug("moving logs to '%s'", global_config["log_file"])
147         # remove initial stream handler
148         logging.root.removeHandler(logging.root.handlers[0])
149         print ("logging on '{}'".format(log_file))
150     except IOError as e:
151         raise LoadConfigurationException(
152             "Cannot open logging file '{}': {}. Check folder exist and permissions".format(log_file, e))
153
154
155 if __name__=="__main__":
156     # Configure logging step 1
157     hostname = socket.gethostname()
158     # streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
159     # "%(asctime)s %(name)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s %(process)d: %(message)s"
160     log_formatter_complete = logging.Formatter('%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s '
161                                                'severity:%(levelname)s logger:%(name)s log:%(message)s'.format(
162                                                     host=hostname),
163                                                datefmt='%Y-%m-%dT%H:%M:%S')
164     log_format_simple =  "%(asctime)s %(levelname)s  %(name)s %(filename)s:%(lineno)s %(message)s"
165     log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
166     logging.basicConfig(format=log_format_simple, level= logging.DEBUG)
167     logger = logging.getLogger('openmano')
168     logger.setLevel(logging.DEBUG)
169     socket_handler = None
170     # Read parameters and configuration file
171     httpthread = None
172     try:
173         #load parameters and configuration
174         opts, args = getopt.getopt(sys.argv[1:], "hvc:V:p:P:", ["config=", "help", "version", "port=", "vnf-repository=", "adminport=", "log-socket-host=", "log-socket-port=", "log-file="])
175         port=None
176         port_admin = None
177         config_file = 'osm_ro/openmanod.cfg'
178         vnf_repository = None
179         log_file = None
180         log_socket_host = None
181         log_socket_port = None
182
183         for o, a in opts:
184             if o in ("-v", "--version"):
185                 print ("openmanod version " + __version__ + ' ' + version_date)
186                 print ("(c) Copyright Telefonica")
187                 sys.exit()
188             elif o in ("-h", "--help"):
189                 usage()
190                 sys.exit()
191             elif o in ("-V", "--vnf-repository"):
192                 vnf_repository = a
193             elif o in ("-c", "--config"):
194                 config_file = a
195             elif o in ("-p", "--port"):
196                 port = a
197             elif o in ("-P", "--adminport"):
198                 port_admin = a
199             elif o == "--log-socket-port":
200                 log_socket_port = a
201             elif o == "--log-socket-host":
202                 log_socket_host = a
203             elif o == "--log-file":
204                 log_file = a
205             else:
206                 assert False, "Unhandled option"
207         if log_file:
208             set_logging_file(log_file)
209         global_config = load_configuration(config_file)
210         global_config["version"] = __version__
211         global_config["version_date"] = version_date
212         #print global_config
213         # Override parameters obtained by command line
214         if port:
215             global_config['http_port'] = port
216         if port_admin:
217             global_config['http_admin_port'] = port_admin
218         if log_socket_host:
219             global_config['log_socket_host'] = log_socket_host
220         if log_socket_port:
221             global_config['log_socket_port'] = log_socket_port
222 #         if vnf_repository is not None:
223 #             global_config['vnf_repository'] = vnf_repository
224 #         else:
225 #             if not 'vnf_repository' in global_config:
226 #                 logger.error( os.getcwd() )
227 #                 global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
228 #         #print global_config
229 #         if not os.path.exists(global_config['vnf_repository']):
230 #             logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
231 #             try:
232 #                 os.makedirs(global_config['vnf_repository'])
233 #             except Exception as e:
234 #                 logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
235 #                 exit(-1)
236
237         global_config["console_port_iterator"] = console_port_iterator
238         global_config["console_thread"]={}
239         global_config["console_ports"]={}
240         if not global_config["http_console_host"]:
241             global_config["http_console_host"] = global_config["http_host"]
242             if global_config["http_host"]=="0.0.0.0":
243                 global_config["http_console_host"] = socket.gethostname()
244
245         # Configure logging STEP 2
246         if "log_host" in global_config:
247             socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
248             socket_handler.setFormatter(log_formatter_complete)
249             if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]:
250                 socket_handler.setLevel(global_config["log_socket_level"])
251             logger.addHandler(socket_handler)
252
253         # logger.addHandler(log_handlers.SysLogHandler())
254         if log_file:
255             global_config['log_file'] = log_file
256         elif global_config.get('log_file'):
257             set_logging_file(global_config['log_file'])
258
259         # logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
260         logger.setLevel(getattr(logging, global_config['log_level']))
261         logger.critical("Starting openmano server version: '%s %s' command: '%s'",
262                          __version__, version_date, " ".join(sys.argv))
263
264         for log_module in ("nfvo", "http", "vim", "db", "console", "ovim"):
265             log_level_module = "log_level_" + log_module
266             log_file_module = "log_file_" + log_module
267             logger_module = logging.getLogger('openmano.' + log_module)
268             if log_level_module in global_config:
269                 logger_module.setLevel(global_config[log_level_module])
270             if log_file_module in global_config:
271                 try:
272                     file_handler = logging.handlers.RotatingFileHandler(global_config[log_file_module],
273                                                                         maxBytes=100e6, backupCount=9, delay=0)
274                     file_handler.setFormatter(log_formatter_simple)
275                     logger_module.addHandler(file_handler)
276                 except IOError as e:
277                     raise LoadConfigurationException(
278                         "Cannot open logging file '{}': {}. Check folder exist and permissions".format(
279                             global_config[log_file_module], str(e)) )
280             global_config["logger_"+log_module] = logger_module
281         #httpserver.logger = global_config["logger_http"]
282         #nfvo.logger = global_config["logger_nfvo"]
283
284         # Initialize DB connection
285         mydb = nfvo_db.nfvo_db();
286         mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
287         db_path = osm_ro.__path__[0] + "/database_utils"
288         if not os.path.exists(db_path + "/migrate_mano_db.sh"):
289             db_path = osm_ro.__path__[0] + "/../database_utils"
290         try:
291             r = mydb.get_db_version()
292             if r[0] != database_version:
293                 logger.critical("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
294                                 " with '{db_path}/migrate_mano_db.sh {target}'".format(
295                                 current=r[0], target=database_version,  db_path=db_path))
296                 exit(-1)
297         except db_base_Exception as e:
298             logger.critical("DATABASE is not valid. If you think it is corrupted, you can init it with"
299                             " '{db_path}/init_mano_db.sh' script".format(db_path=db_path))
300             exit(-1)
301
302         nfvo.global_config=global_config
303         nfvo.start_service(mydb)
304
305         httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
306
307         httpthread.start()
308         if 'http_admin_port' in global_config:
309             httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
310             httpthreadadmin.start()
311         time.sleep(1)
312         logger.info('Waiting for http clients')
313         print('Waiting for http clients')
314         print('openmanod ready')
315         print('====================')
316         time.sleep(20)
317         sys.stdout.flush()
318
319         #TODO: Interactive console must be implemented here instead of join or sleep
320
321         #httpthread.join()
322         #if 'http_admin_port' in global_config:
323         #    httpthreadadmin.join()
324         while True:
325             time.sleep(86400)
326
327     except KeyboardInterrupt as e:
328         logger.info(str(e))
329     except SystemExit:
330         pass
331     except getopt.GetoptError as e:
332         logger.critical(str(e)) # will print something like "option -a not recognized"
333         #usage()
334         exit(-1)
335     except LoadConfigurationException as e:
336         logger.critical(str(e))
337         exit(-1)
338     except db_base_Exception as e:
339         logger.critical(str(e))
340         exit(-1)
341     except nfvo.NfvoException as e:
342         logger.critical(str(e), exc_info=True)
343         exit(-1)
344     nfvo.stop_service()
345     if httpthread:
346         httpthread.join(1)
347