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