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