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