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