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