changes at migrate_mano_db to use database version as a parameter and not use openman...
[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         #print global_config
190         # Override parameters obtained by command line
191         if port:
192             global_config['http_port'] = port
193         if port_admin:
194             global_config['http_admin_port'] = port_admin
195         if log_socket_host:
196             global_config['log_socket_host'] = log_socket_host
197         if log_socket_port:
198             global_config['log_socket_port'] = log_socket_port
199         if log_file:
200             global_config['log_file'] = log_file
201 #         if vnf_repository is not None:
202 #             global_config['vnf_repository'] = vnf_repository
203 #         else:
204 #             if not 'vnf_repository' in global_config:  
205 #                 logger.error( os.getcwd() )
206 #                 global_config['vnf_repository'] = os.getcwd()+'/vnfrepo'
207 #         #print global_config
208 #         if not os.path.exists(global_config['vnf_repository']):
209 #             logger.error( "Creating folder vnf_repository folder: '%s'.", global_config['vnf_repository'])
210 #             try:
211 #                 os.makedirs(global_config['vnf_repository'])
212 #             except Exception as e:
213 #                 logger.error( "Error '%s'. Ensure the path 'vnf_repository' is properly set at %s",e.args[1], config_file)
214 #                 exit(-1)
215         
216         global_config["console_port_iterator"] = console_port_iterator
217         global_config["console_thread"]={}
218         global_config["console_ports"]={}
219         if not global_config["http_console_host"]:
220             global_config["http_console_host"] = global_config["http_host"]
221             if global_config["http_host"]=="0.0.0.0":
222                 global_config["http_console_host"] = socket.gethostname()
223         
224         #Configure logging STEP 2
225         if "log_host" in global_config:
226             socket_handler= log_handlers.SocketHandler(global_config["log_socket_host"], global_config["log_socket_port"])
227             socket_handler.setFormatter(log_formatter_complete)
228             if global_config.get("log_socket_level") and global_config["log_socket_level"] != global_config["log_level"]: 
229                 socket_handler.setLevel(global_config["log_socket_level"])
230             logger.addHandler(socket_handler)
231         #logger.addHandler(log_handlers.SysLogHandler())
232         if "log_file" in global_config:
233             try:
234                 file_handler= logging.handlers.RotatingFileHandler(global_config["log_file"], maxBytes=100e6, backupCount=9, delay=0)
235                 file_handler.setFormatter(log_formatter_simple)
236                 logger.addHandler(file_handler)
237                 #logger.debug("moving logs to '%s'", global_config["log_file"])
238                 #remove initial stream handler
239                 logging.root.removeHandler(logging.root.handlers[0])
240                 print ("logging on '{}'".format(global_config["log_file"]))
241             except IOError as e:
242                 raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(global_config["log_file"], str(e)) ) 
243         #logging.basicConfig(level = getattr(logging, global_config.get('log_level',"debug")))
244         logger.setLevel(getattr(logging, global_config['log_level']))
245         logger.critical("Starting openmano server version: '%s %s' command: '%s'",  
246                          __version__, version_date, " ".join(sys.argv))
247         
248         for log_module in ("nfvo", "http", "vim", "db", "console", "ovim"):
249             log_level_module = "log_level_" + log_module
250             log_file_module = "log_file_" + log_module
251             logger_module = logging.getLogger('openmano.' + log_module)
252             if log_level_module in global_config:
253                 logger_module.setLevel(global_config[log_level_module])
254             if log_file_module in global_config:
255                 try:
256                     file_handler= logging.handlers.RotatingFileHandler(global_config[log_file_module], maxBytes=100e6, backupCount=9, delay=0)
257                     file_handler.setFormatter(log_formatter_simple)
258                     logger_module.addHandler(file_handler)
259                 except IOError as e:
260                     raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(global_config[log_file_module], str(e)) ) 
261             global_config["logger_"+log_module] = logger_module
262         #httpserver.logger = global_config["logger_http"]
263         #nfvo.logger = global_config["logger_nfvo"]
264         
265         # Initialize DB connection
266         mydb = nfvo_db.nfvo_db();
267         mydb.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
268         db_path = osm_ro.__path__[0] + "/database_utils"
269         if not os.path.exists(db_path + "/migrate_mano_db.sh"):
270             db_path = osm_ro.__path__[0] + "/../database_utils"
271         try:
272             r = mydb.get_db_version()
273             if r[0] != database_version:
274                 logger.critical("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
275                                 " with '{db_path}/migrate_mano_db.sh {target}'".format(
276                                 current=r[0], target=database_version,  db_path=db_path))
277                 exit(-1)
278         except db_base_Exception as e:
279             logger.critical("DATABASE is not valid. If you think it is corrupted, you can init it with"
280                             " '{db_path}/init_mano_db.sh' script".format(db_path=db_path))
281             exit(-1)
282
283         nfvo.global_config=global_config
284         nfvo.start_service(mydb)
285         
286         httpthread = httpserver.httpserver(mydb, False, global_config['http_host'], global_config['http_port'])
287         
288         httpthread.start()
289         if 'http_admin_port' in global_config: 
290             httpthreadadmin = httpserver.httpserver(mydb, True, global_config['http_host'], global_config['http_admin_port'])
291             httpthreadadmin.start()
292         time.sleep(1)      
293         logger.info('Waiting for http clients')
294         print('Waiting for http clients')
295         print('openmanod ready')
296         print('====================')
297         time.sleep(20)
298         sys.stdout.flush()
299
300         #TODO: Interactive console must be implemented here instead of join or sleep
301
302         #httpthread.join()
303         #if 'http_admin_port' in global_config:
304         #    httpthreadadmin.join()
305         while True:
306             time.sleep(86400)
307
308     except KeyboardInterrupt as e:
309         logger.info(str(e))
310     except SystemExit:
311         pass
312     except getopt.GetoptError as e:
313         logger.critical(str(e)) # will print something like "option -a not recognized"
314         #usage()
315         exit(-1)
316     except LoadConfigurationException as e:
317         logger.critical(str(e))
318         exit(-1)
319     except db_base_Exception as e:
320         logger.critical(str(e))
321         exit(-1)
322     nfvo.stop_service()
323     if httpthread:
324         httpthread.join(1)
325