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