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