blob: f2dbd5386055ffa85e66dcc53e96d14053553b6b [file] [log] [blame]
tiernof7aa8c42016-09-06 16:43:04 +02001#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4##
5# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
tierno9a61c6b2016-09-08 10:57:02 +02006# This file is part of openvim
tiernof7aa8c42016-09-06 16:43:04 +02007# 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
tierno51068952017-04-26 15:09:48 +020025"""
tiernof7aa8c42016-09-06 16:43:04 +020026This is the main program of openvim, it reads the configuration
tierno1ff6c022017-05-26 14:37:32 +020027and launches the rest of threads: http clients, openflow controllers
28and host controllers, network controller
tierno51068952017-04-26 15:09:48 +020029"""
mirabal50a052f2017-03-27 18:08:07 +020030
mirabal9f657102017-04-10 20:05:40 +020031import osm_openvim.httpserver as httpserver
32import osm_openvim.auxiliary_functions as af
tiernof7aa8c42016-09-06 16:43:04 +020033import sys
34import getopt
35import time
tiernof7aa8c42016-09-06 16:43:04 +020036import yaml
37import os
38from jsonschema import validate as js_v, exceptions as js_e
tierno51068952017-04-26 15:09:48 +020039from osm_openvim.vim_schema import config_schema
tiernof7aa8c42016-09-06 16:43:04 +020040import logging
tiernof13617a2016-09-08 11:42:10 +020041import logging.handlers as log_handlers
tiernof13617a2016-09-08 11:42:10 +020042import socket
mirabal9f657102017-04-10 20:05:40 +020043import osm_openvim.ovim as ovim
tiernof7aa8c42016-09-06 16:43:04 +020044
tierno51068952017-04-26 15:09:48 +020045__author__ = "Alfonso Tierno"
46__date__ = "$10-jul-2014 12:07:15$"
47
tiernof7aa8c42016-09-06 16:43:04 +020048global config_dic
49global logger
50logger = logging.getLogger('vim')
51
mirabal9f657102017-04-10 20:05:40 +020052
tiernof13617a2016-09-08 11:42:10 +020053class LoadConfigurationException(Exception):
54 pass
55
mirabal9f657102017-04-10 20:05:40 +020056
tiernof7aa8c42016-09-06 16:43:04 +020057def load_configuration(configuration_file):
tierno86b8dd12017-05-26 13:16:40 +020058 default_tokens = {'http_port': 9080, 'http_host': 'localhost',
59 'of_controller_nets_with_same_vlan': True,
60 'host_ssh_keyfile': None,
61 'network_vlan_range_start': 1000,
62 'network_vlan_range_end': 4096,
63 'log_level': "DEBUG",
64 'log_level_db': "ERROR",
65 'log_level_of': 'ERROR',
66 'bridge_ifaces': {},
67 'network_type': 'ovs',
68 'ovs_controller_user': 'osm_dhcp',
69 'ovs_controller_file_path': '/var/lib/',
70 }
tiernof7aa8c42016-09-06 16:43:04 +020071 try:
tiernoa6933042017-05-24 16:54:33 +020072 # First load configuration from configuration file
73 # Check config file exists
tiernof7aa8c42016-09-06 16:43:04 +020074 if not os.path.isfile(configuration_file):
tiernoa6933042017-05-24 16:54:33 +020075 raise LoadConfigurationException("Configuration file '{}' does not exists".format(configuration_file))
tierno86b8dd12017-05-26 13:16:40 +020076
tiernoa6933042017-05-24 16:54:33 +020077 # Read and parse file
tiernof7aa8c42016-09-06 16:43:04 +020078 (return_status, code) = af.read_file(configuration_file)
79 if not return_status:
tiernoa6933042017-05-24 16:54:33 +020080 raise LoadConfigurationException("Error loading configuration file '{}': {}".format(
81 configuration_file, code))
82 config = yaml.load(code)
83 js_v(config, config_schema)
84 # Check default values tokens
85 for k, v in default_tokens.items():
86 if k not in config:
87 config[k] = v
88 # Check vlan ranges
tierno86b8dd12017-05-26 13:16:40 +020089 if config["network_vlan_range_start"] + 10 >= config["network_vlan_range_end"]:
90 raise LoadConfigurationException(
91 "Error at configuration file '{}'. Invalid network_vlan_range less than 10 elements".format(
92 configuration_file))
tiernoa6933042017-05-24 16:54:33 +020093 return config
94 except yaml.YAMLError as exc:
95 error_pos = ""
96 if hasattr(exc, 'problem_mark'):
97 mark = exc.problem_mark
98 error_pos = " at position: ({}:{})".format(mark.line + 1, mark.column + 1)
tierno86b8dd12017-05-26 13:16:40 +020099 raise LoadConfigurationException("Bad YAML format at configuration file '{}'{}: {}\n"
tiernoa6933042017-05-24 16:54:33 +0200100 "Use a valid yaml format. Indentation matters, "
101 "and tabs characters are not valid".format(
tierno86b8dd12017-05-26 13:16:40 +0200102 configuration_file, error_pos, exc))
tiernoa6933042017-05-24 16:54:33 +0200103 except js_e.ValidationError as exc:
104 error_pos = ""
105 if len(exc.path) > 0:
106 error_pos = " at '{}'".format(":".join(map(str, exc.path)))
tierno86b8dd12017-05-26 13:16:40 +0200107 raise LoadConfigurationException("Invalid field at configuration file '{}'{}: {}".format(
tiernoa6933042017-05-24 16:54:33 +0200108 configuration_file, error_pos, exc))
109
tierno86b8dd12017-05-26 13:16:40 +0200110 # except Exception as e:
111 # raise LoadConfigurationException("Error loading configuration file '{}': {}".format(configuration_file, e))
tiernoa6933042017-05-24 16:54:33 +0200112
tiernof7aa8c42016-09-06 16:43:04 +0200113
tiernof7aa8c42016-09-06 16:43:04 +0200114def usage():
tierno86b8dd12017-05-26 13:16:40 +0200115 print ("Usage: {} [options]".format(sys.argv[0]))
tiernoa6933042017-05-24 16:54:33 +0200116 print (" -v|--version: prints current version")
117 print (" -c|--config FILE: loads the configuration file (default: osm_openvim/openvimd.cfg)")
118 print (" -h|--help: shows this help")
119 print (" -p|--port PORT: changes port number and overrides the port number in the configuration file "
120 "(default: 908)")
121 print (" -P|--adminport PORT: changes admin port number and overrides the port number in the configuration "
122 "file (default: not listen)")
123 print (" --dbname NAME: changes db_name and overrides the db_name in the configuration file")
124 # print( " --log-socket-host HOST: send logs to this host")
125 # print( " --log-socket-port PORT: send logs using this port (default: 9022)")
126 print (" --log-file FILE: send logs to this file")
tiernof7aa8c42016-09-06 16:43:04 +0200127 return
128
129
tiernoa6933042017-05-24 16:54:33 +0200130def set_logging_file(log_file):
131 try:
132 file_handler = logging.handlers.RotatingFileHandler(log_file, maxBytes=100e6, backupCount=9, delay=0)
133 file_handler.setFormatter(log_formatter_simple)
134 logger.addHandler(file_handler)
135 # logger.debug("moving logs to '%s'", global_config["log_file"])
136 # remove initial stream handler
137 logging.root.removeHandler(logging.root.handlers[0])
138 print ("logging on '{}'".format(log_file))
139 except IOError as e:
140 raise LoadConfigurationException(
141 "Cannot open logging file '{}': {}. Check folder exist and permissions".format(log_file, e))
142
143
144if __name__ == "__main__":
tiernof13617a2016-09-08 11:42:10 +0200145 hostname = socket.gethostname()
tiernoa6933042017-05-24 16:54:33 +0200146 # streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
147 log_formatter_complete = logging.Formatter('%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s '
148 'severity:%(levelname)s logger:%(name)s log:%(message)s'.format(
tierno86b8dd12017-05-26 13:16:40 +0200149 host=hostname),
150 datefmt='%Y-%m-%dT%H:%M:%S')
tiernoa6933042017-05-24 16:54:33 +0200151 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
tiernof13617a2016-09-08 11:42:10 +0200152 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
tierno51068952017-04-26 15:09:48 +0200153 logging.basicConfig(format=log_format_simple, level=logging.DEBUG)
tierno57f7bda2017-02-09 12:01:55 +0100154 logger = logging.getLogger('openvim')
tiernof7aa8c42016-09-06 16:43:04 +0200155 logger.setLevel(logging.DEBUG)
156 try:
tiernoa6933042017-05-24 16:54:33 +0200157 opts, args = getopt.getopt(sys.argv[1:], "hvc:p:P:",
158 ["config=", "help", "version", "port=", "adminport=", "log-file=", "dbname="])
159 except getopt.GetoptError as err:
tiernof7aa8c42016-09-06 16:43:04 +0200160 # print help information and exit:
tiernoa6933042017-05-24 16:54:33 +0200161 logger.error("%s. Type -h for help", err) # will print something like "option -a not recognized"
162 # usage()
163 sys.exit(2)
tiernof7aa8c42016-09-06 16:43:04 +0200164
tiernoa6933042017-05-24 16:54:33 +0200165 port = None
tiernof7aa8c42016-09-06 16:43:04 +0200166 port_admin = None
tierno51068952017-04-26 15:09:48 +0200167 config_file = 'osm_openvim/openvimd.cfg'
tiernof13617a2016-09-08 11:42:10 +0200168 log_file = None
tiernoa36d64d2016-09-14 15:58:40 +0200169 db_name = None
tiernof7aa8c42016-09-06 16:43:04 +0200170
171 for o, a in opts:
172 if o in ("-v", "--version"):
tierno86b8dd12017-05-26 13:16:40 +0200173 print ("openvimd version {} {}".format(ovim.ovim.get_version(), ovim.ovim.get_version_date()))
tiernoa6933042017-05-24 16:54:33 +0200174 print ("(c) Copyright Telefonica")
tiernof7aa8c42016-09-06 16:43:04 +0200175 sys.exit(0)
176 elif o in ("-h", "--help"):
177 usage()
178 sys.exit(0)
179 elif o in ("-c", "--config"):
180 config_file = a
181 elif o in ("-p", "--port"):
182 port = a
183 elif o in ("-P", "--adminport"):
184 port_admin = a
tiernoa36d64d2016-09-14 15:58:40 +0200185 elif o in ("-P", "--dbname"):
186 db_name = a
tiernof13617a2016-09-08 11:42:10 +0200187 elif o == "--log-file":
188 log_file = a
tiernof7aa8c42016-09-06 16:43:04 +0200189 else:
190 assert False, "Unhandled option"
191
tierno57f7bda2017-02-09 12:01:55 +0100192 engine = None
tierno56c0c282017-02-10 14:52:55 +0100193 http_thread = None
194 http_thread_admin = None
195
tiernof7aa8c42016-09-06 16:43:04 +0200196 try:
tiernof13617a2016-09-08 11:42:10 +0200197 if log_file:
tiernoa6933042017-05-24 16:54:33 +0200198 set_logging_file(log_file)
199 # Load configuration file
200 config_dic = load_configuration(config_file)
201 if config_dic.get("dhcp_server"):
202 if config_dic["dhcp_server"].get("key"):
203 config_dic["dhcp_server"]["keyfile"] = config_dic["dhcp_server"].pop("key")
204 if config_dic.get("image_path"):
205 config_dic["host_image_path"] = config_dic.pop("image_path")
206 elif not config_dic.get("host_image_path"):
tierno86b8dd12017-05-26 13:16:40 +0200207 config_dic["host_image_path"] = '/opt/VNF/images' # default value
tiernoa6933042017-05-24 16:54:33 +0200208 # print config_dic
tiernof13617a2016-09-08 11:42:10 +0200209
tiernof7aa8c42016-09-06 16:43:04 +0200210 logger.setLevel(getattr(logging, config_dic['log_level']))
tiernof13617a2016-09-08 11:42:10 +0200211 logger.critical("Starting openvim server command: '%s'", sys.argv[0])
tiernoa6933042017-05-24 16:54:33 +0200212 # override parameters obtained by command line
tierno86b8dd12017-05-26 13:16:40 +0200213 if port:
tiernoa36d64d2016-09-14 15:58:40 +0200214 config_dic['http_port'] = port
215 if port_admin:
216 config_dic['http_admin_port'] = port_admin
tierno86b8dd12017-05-26 13:16:40 +0200217 if db_name:
tiernoa36d64d2016-09-14 15:58:40 +0200218 config_dic['db_name'] = db_name
tierno86b8dd12017-05-26 13:16:40 +0200219
tiernoa6933042017-05-24 16:54:33 +0200220 # check mode
tiernof7aa8c42016-09-06 16:43:04 +0200221 if 'mode' not in config_dic:
222 config_dic['mode'] = 'normal'
tiernoa6933042017-05-24 16:54:33 +0200223 # allow backward compatibility of test_mode option
tierno86b8dd12017-05-26 13:16:40 +0200224 if 'test_mode' in config_dic and config_dic['test_mode'] == True:
225 config_dic['mode'] = 'test'
Mirabal7256d6b2016-12-15 10:51:19 +0000226 if config_dic['mode'] == 'development' and config_dic['network_type'] == 'bridge' and \
tiernoa6933042017-05-24 16:54:33 +0200227 ('development_bridge' not in config_dic or
tierno86b8dd12017-05-26 13:16:40 +0200228 config_dic['development_bridge'] not in config_dic.get("bridge_ifaces", None)):
tiernoa6933042017-05-24 16:54:33 +0200229 error_msg = "'{}' is not a valid 'development_bridge', not one of the 'bridge_ifaces'".format(config_file)
230 print (error_msg)
231 logger.error(error_msg)
232 exit(1)
Mirabale9317ff2017-01-18 16:10:58 +0000233
tiernof7aa8c42016-09-06 16:43:04 +0200234 if config_dic['mode'] != 'normal':
tiernoa6933042017-05-24 16:54:33 +0200235 print ('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
236 print ("!! Warning, openvimd in TEST mode '{}'".format(config_dic['mode']))
237 print ('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
tierno2db743b2017-03-28 17:23:15 +0200238 config_dic['version'] = ovim.ovim.get_version()
tierno57f7bda2017-02-09 12:01:55 +0100239 config_dic["logger_name"] = "openvim"
tiernof7aa8c42016-09-06 16:43:04 +0200240
tierno57f7bda2017-02-09 12:01:55 +0100241 engine = ovim.ovim(config_dic)
242 engine.start_service()
tiernof7aa8c42016-09-06 16:43:04 +0200243
tierno86b8dd12017-05-26 13:16:40 +0200244 # Create thread to listen to web requests
tiernoa6933042017-05-24 16:54:33 +0200245 http_thread = httpserver.httpserver(engine, 'http', config_dic['http_host'], config_dic['http_port'],
246 False, config_dic)
tiernof7aa8c42016-09-06 16:43:04 +0200247 http_thread.start()
tiernoa6933042017-05-24 16:54:33 +0200248
tierno57f7bda2017-02-09 12:01:55 +0100249 if 'http_admin_port' in config_dic:
250 engine2 = ovim.ovim(config_dic)
tiernoa6933042017-05-24 16:54:33 +0200251 http_thread_admin = httpserver.httpserver(engine2, 'http-admin', config_dic['http_host'],
252 config_dic['http_admin_port'], True)
tiernof7aa8c42016-09-06 16:43:04 +0200253 http_thread_admin.start()
254 else:
255 http_thread_admin = None
tiernoa6933042017-05-24 16:54:33 +0200256 time.sleep(1)
tiernof7aa8c42016-09-06 16:43:04 +0200257 logger.info('Waiting for http clients')
258 print ('openvimd ready')
259 print ('====================')
260 sys.stdout.flush()
tiernoa6933042017-05-24 16:54:33 +0200261
tierno86b8dd12017-05-26 13:16:40 +0200262 # TODO: Interactive console would be nice here instead of join or sleep
tiernoa6933042017-05-24 16:54:33 +0200263
264 r = ""
tiernof7aa8c42016-09-06 16:43:04 +0200265 while True:
tiernoa6933042017-05-24 16:54:33 +0200266 if r == 'exit':
267 break
268 elif r != '':
tiernof7aa8c42016-09-06 16:43:04 +0200269 print "type 'exit' for terminate"
tiernoa6933042017-05-24 16:54:33 +0200270 try:
271 r = raw_input('> ')
272 except EOFError:
273 time.sleep(86400)
tiernof7aa8c42016-09-06 16:43:04 +0200274
275 except (KeyboardInterrupt, SystemExit):
276 pass
tiernoa6933042017-05-24 16:54:33 +0200277 except (getopt.GetoptError, LoadConfigurationException, ovim.ovimException) as e:
tierno86b8dd12017-05-26 13:16:40 +0200278 logger.critical(str(e)) # will print something like "option -a not recognized"
tiernoa6933042017-05-24 16:54:33 +0200279 exit(1)
tiernof7aa8c42016-09-06 16:43:04 +0200280
281 logger.info('Exiting openvimd')
tierno57f7bda2017-02-09 12:01:55 +0100282 if engine:
283 engine.stop_service()
tierno56c0c282017-02-10 14:52:55 +0100284 if http_thread:
285 http_thread.join(1)
286 if http_thread_admin:
287 http_thread_admin.join(1)
tiernoa6933042017-05-24 16:54:33 +0200288 logger.debug("bye!")
tiernof7aa8c42016-09-06 16:43:04 +0200289 exit()