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