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