blob: c79f86a493442a3904cc4de72f16aaa4775008bc [file] [log] [blame]
mirabald87877c2017-03-31 15:15:52 +02001#!/usr/bin/env python
tierno57f7bda2017-02-09 12:01:55 +01002# -*- coding: utf-8 -*-
3
4##
5# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
6# This file is part of openvim
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
tierno51068952017-04-26 15:09:48 +020025"""
tierno57f7bda2017-02-09 12:01:55 +010026This is the thread for the http server North API.
27Two thread will be launched, with normal and administrative permissions.
tierno51068952017-04-26 15:09:48 +020028"""
tierno57f7bda2017-02-09 12:01:55 +010029
30import threading
garciadeblas071df952017-04-21 11:12:50 +020031import vim_db
tierno57f7bda2017-02-09 12:01:55 +010032import logging
tierno29d80242017-04-25 18:07:08 +020033# import imp
tierno95a9e832017-04-27 18:49:37 +020034import os.path
mirabal9f657102017-04-10 20:05:40 +020035import argparse
mirabal65ba8f82017-02-15 12:36:33 +010036from netaddr import IPNetwork
37from jsonschema import validate as js_v, exceptions as js_e
garciadeblas071df952017-04-21 11:12:50 +020038import host_thread as ht
39import dhcp_thread as dt
40import openflow_thread as oft
41import openflow_conn
mirabal9f657102017-04-10 20:05:40 +020042
tierno51068952017-04-26 15:09:48 +020043__author__ = "Alfonso Tierno, Leonardo Mirabal"
44__date__ = "$06-Feb-2017 12:07:15$"
tiernoa290d8f2017-05-03 17:42:52 +020045__version__ = "0.5.13-r529"
tierno95a9e832017-04-27 18:49:37 +020046version_date = "May 2017"
tiernoa290d8f2017-05-03 17:42:52 +020047database_version = 18 #needed database schema version
tierno57f7bda2017-02-09 12:01:55 +010048
49HTTP_Bad_Request = 400
50HTTP_Unauthorized = 401
51HTTP_Not_Found = 404
52HTTP_Forbidden = 403
53HTTP_Method_Not_Allowed = 405
54HTTP_Not_Acceptable = 406
55HTTP_Request_Timeout = 408
56HTTP_Conflict = 409
57HTTP_Service_Unavailable = 503
58HTTP_Internal_Server_Error= 500
59
60
61def convert_boolean(data, items):
62 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
63 It assumes that bandwidth is well formed
64 Attributes:
65 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
66 'items': tuple of keys to convert
67 Return:
68 None
69 '''
70 if type(data) is dict:
71 for k in data.keys():
72 if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
73 convert_boolean(data[k], items)
74 if k in items:
75 if type(data[k]) is str:
76 if data[k] == "false":
77 data[k] = False
78 elif data[k] == "true":
79 data[k] = True
80 if type(data) is tuple or type(data) is list:
81 for k in data:
82 if type(k) is dict or type(k) is tuple or type(k) is list:
83 convert_boolean(k, items)
84
85
86
87class ovimException(Exception):
88 def __init__(self, message, http_code=HTTP_Bad_Request):
89 self.http_code = http_code
90 Exception.__init__(self, message)
91
92
93class ovim():
94 running_info = {} #TODO OVIM move the info of running threads from config_dic to this static variable
mirabal580435e2017-03-01 16:17:10 +010095 of_module = {}
96
tierno57f7bda2017-02-09 12:01:55 +010097 def __init__(self, configuration):
98 self.config = configuration
tierno46ca3a92017-04-05 19:49:24 +020099 self.logger_name = configuration.get("logger_name", "openvim")
100 self.logger = logging.getLogger(self.logger_name)
tierno57f7bda2017-02-09 12:01:55 +0100101 self.db = None
tierno46ca3a92017-04-05 19:49:24 +0200102 self.db = self._create_database_connection()
mirabal580435e2017-03-01 16:17:10 +0100103 self.db_lock = None
104 self.db_of = None
105 self.of_test_mode = False
tierno57f7bda2017-02-09 12:01:55 +0100106
107 def _create_database_connection(self):
108 db = vim_db.vim_db((self.config["network_vlan_range_start"], self.config["network_vlan_range_end"]),
tierno46ca3a92017-04-05 19:49:24 +0200109 self.logger_name + ".db", self.config.get('log_level_db'))
tierno57f7bda2017-02-09 12:01:55 +0100110 if db.connect(self.config['db_host'], self.config['db_user'], self.config['db_passwd'],
111 self.config['db_name']) == -1:
112 # self.logger.error("Cannot connect to database %s at %s@%s", self.config['db_name'], self.config['db_user'],
113 # self.config['db_host'])
114 raise ovimException("Cannot connect to database {} at {}@{}".format(self.config['db_name'],
115 self.config['db_user'],
116 self.config['db_host']) )
117 return db
118
mirabale9f6f1a2017-02-16 17:57:35 +0100119 @staticmethod
mirabal50a052f2017-03-27 18:08:07 +0200120 def get_version():
121 return __version__
122
123 @staticmethod
124 def get_version_date():
125 return version_date
126
127 @staticmethod
128 def get_database_version():
129 return database_version
130
131 @staticmethod
mirabale9f6f1a2017-02-16 17:57:35 +0100132 def _check_dhcp_data_integrity(network):
133 """
134 Check if all dhcp parameter for anet are valid, if not will be calculated from cidr value
135 :param network: list with user nets paramters
136 :return:
137 """
138 if "cidr" in network:
139 cidr = network["cidr"]
140 ip_tools = IPNetwork(cidr)
141 cidr_len = ip_tools.prefixlen
142 if cidr_len > 29:
143 return False
144
145 ips = IPNetwork(cidr)
146 if "dhcp_first_ip" not in network:
147 network["dhcp_first_ip"] = str(ips[2])
148 if "dhcp_last_ip" not in network:
149 network["dhcp_last_ip"] = str(ips[-2])
150 if "gateway_ip" not in network:
151 network["gateway_ip"] = str(ips[1])
152
153 return True
154 else:
155 return False
156
157 @staticmethod
158 def _check_valid_uuid(uuid):
159 id_schema = {"type": "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
160 try:
161 js_v(uuid, id_schema)
162 return True
163 except js_e.ValidationError:
164 return False
tierno57f7bda2017-02-09 12:01:55 +0100165
166 def start_service(self):
mirabal580435e2017-03-01 16:17:10 +0100167 """
168 Start ovim services
169 :return:
170 """
tierno2db743b2017-03-28 17:23:15 +0200171 global database_version
mirabal580435e2017-03-01 16:17:10 +0100172 # if self.running_info:
tierno57f7bda2017-02-09 12:01:55 +0100173 # return #TODO service can be checked and rebuild broken threads
174 r = self.db.get_db_version()
tierno95a9e832017-04-27 18:49:37 +0200175 db_path = __file__
176 db_path = db_path[:db_path.rfind("/")]
177 if os.path.exists(db_path + "/database_utils/migrate_vim_db.sh"):
178 db_path += "/database_utils"
179 else:
180 db_path += "/../database_utils"
181
mirabal580435e2017-03-01 16:17:10 +0100182 if r[0] < 0:
tierno95a9e832017-04-27 18:49:37 +0200183 raise ovimException("DATABASE is not valid. If you think it is corrupted, you can init it with"
184 " '{db_path}/init_vim_db.sh' script".format(db_path=db_path))
185 elif r[0] != database_version:
186 raise ovimException("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
187 " with '{db_path}/migrate_vim_db.sh {target}'".format(
188 current=r[0], target=database_version, db_path=db_path))
tierno46ca3a92017-04-05 19:49:24 +0200189 self.logger.critical("Starting ovim server version: '{} {}' database version '{}'".format(
190 self.get_version(), self.get_version_date(), self.get_database_version()))
tierno57f7bda2017-02-09 12:01:55 +0100191 # create database connection for openflow threads
mirabal580435e2017-03-01 16:17:10 +0100192 self.db_of = self._create_database_connection()
193 self.config["db"] = self.db_of
194 self.db_lock = threading.Lock()
195 self.config["db_lock"] = self.db_lock
tierno57f7bda2017-02-09 12:01:55 +0100196
mirabal580435e2017-03-01 16:17:10 +0100197 self.of_test_mode = False if self.config['mode'] == 'normal' or self.config['mode'] == "OF only" else True
198 # precreate interfaces; [bridge:<host_bridge_name>, VLAN used at Host, uuid of network camping in this bridge,
199 # speed in Gbit/s
200
tierno57f7bda2017-02-09 12:01:55 +0100201 self.config['dhcp_nets'] = []
202 self.config['bridge_nets'] = []
203 for bridge, vlan_speed in self.config["bridge_ifaces"].items():
mirabal580435e2017-03-01 16:17:10 +0100204 # skip 'development_bridge'
tierno57f7bda2017-02-09 12:01:55 +0100205 if self.config['mode'] == 'development' and self.config['development_bridge'] == bridge:
206 continue
207 self.config['bridge_nets'].append([bridge, vlan_speed[0], vlan_speed[1], None])
208
209 # check if this bridge is already used (present at database) for a network)
210 used_bridge_nets = []
211 for brnet in self.config['bridge_nets']:
tierno686b3952017-03-10 13:57:24 +0100212 r, nets = self.db.get_table(SELECT=('uuid',), FROM='nets', WHERE={'provider': "bridge:" + brnet[0]})
tierno57f7bda2017-02-09 12:01:55 +0100213 if r > 0:
214 brnet[3] = nets[0]['uuid']
215 used_bridge_nets.append(brnet[0])
216 if self.config.get("dhcp_server"):
217 if brnet[0] in self.config["dhcp_server"]["bridge_ifaces"]:
218 self.config['dhcp_nets'].append(nets[0]['uuid'])
219 if len(used_bridge_nets) > 0:
220 self.logger.info("found used bridge nets: " + ",".join(used_bridge_nets))
221 # get nets used by dhcp
222 if self.config.get("dhcp_server"):
223 for net in self.config["dhcp_server"].get("nets", ()):
tierno686b3952017-03-10 13:57:24 +0100224 r, nets = self.db.get_table(SELECT=('uuid',), FROM='nets', WHERE={'name': net})
tierno57f7bda2017-02-09 12:01:55 +0100225 if r > 0:
226 self.config['dhcp_nets'].append(nets[0]['uuid'])
227
mirabal580435e2017-03-01 16:17:10 +0100228 # OFC default
229 self._start_ofc_default_task()
tierno57f7bda2017-02-09 12:01:55 +0100230
mirabal580435e2017-03-01 16:17:10 +0100231 # OFC per tenant in DB
232 self._start_of_db_tasks()
tierno57f7bda2017-02-09 12:01:55 +0100233
234 # create dhcp_server thread
235 host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
236 dhcp_params = self.config.get("dhcp_server")
237 if dhcp_params:
238 thread = dt.dhcp_thread(dhcp_params=dhcp_params, test=host_test_mode, dhcp_nets=self.config["dhcp_nets"],
tierno43044b02017-04-06 18:58:24 +0200239 db=self.db_of, db_lock=self.db_lock, logger_name=self.logger_name + ".dhcp",
240 debug=self.config.get('log_level_of'))
tierno57f7bda2017-02-09 12:01:55 +0100241 thread.start()
242 self.config['dhcp_thread'] = thread
243
244 # Create one thread for each host
245 host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
246 host_develop_mode = True if self.config['mode'] == 'development' else False
247 host_develop_bridge_iface = self.config.get('development_bridge', None)
mirabal580435e2017-03-01 16:17:10 +0100248
249 # get host list from data base before starting threads
tierno686b3952017-03-10 13:57:24 +0100250 r, hosts = self.db.get_table(SELECT=('name', 'ip_name', 'user', 'uuid'), FROM='hosts', WHERE={'status': 'ok'})
mirabal580435e2017-03-01 16:17:10 +0100251 if r < 0:
252 raise ovimException("Cannot get hosts from database {}".format(hosts))
253
tierno57f7bda2017-02-09 12:01:55 +0100254 self.config['host_threads'] = {}
255 for host in hosts:
256 host['image_path'] = '/opt/VNF/images/openvim'
mirabal580435e2017-03-01 16:17:10 +0100257 thread = ht.host_thread(name=host['name'], user=host['user'], host=host['ip_name'], db=self.db_of,
258 db_lock=self.db_lock, test=host_test_mode, image_path=self.config['image_path'],
259 version=self.config['version'], host_id=host['uuid'], develop_mode=host_develop_mode,
tiernof135eff2017-04-19 19:11:53 +0200260 develop_bridge_iface=host_develop_bridge_iface,
261 logger_name=self.logger_name + ".host." + host['name'],
262 debug=self.config.get('log_level_host'))
tierno57f7bda2017-02-09 12:01:55 +0100263 thread.start()
264 self.config['host_threads'][host['uuid']] = thread
265
mirabalb716ac52017-02-10 14:47:53 +0100266 # create ovs dhcp thread
267 result, content = self.db.get_table(FROM='nets')
268 if result < 0:
269 self.logger.error("http_get_ports Error %d %s", result, content)
270 raise ovimException(str(content), -result)
271
272 for net in content:
273 net_type = net['type']
mirabale9f6f1a2017-02-16 17:57:35 +0100274 if (net_type == 'bridge_data' or net_type == 'bridge_man') \
275 and net["provider"][:4] == 'OVS:' and net["enable_dhcp"] == "true":
mirabal65ba8f82017-02-15 12:36:33 +0100276 self.launch_dhcp_server(net['vlan'],
277 net['dhcp_first_ip'],
278 net['dhcp_last_ip'],
279 net['cidr'],
280 net['gateway_ip'])
mirabalb716ac52017-02-10 14:47:53 +0100281
mirabal580435e2017-03-01 16:17:10 +0100282 def _start_of_db_tasks(self):
283 """
284 Start ofc task for existing ofcs in database
285 :param db_of:
286 :param db_lock:
287 :return:
288 """
289 ofcs = self.get_of_controllers()
290
291 for ofc in ofcs:
292 of_conn = self._load_of_module(ofc)
293 # create ofc thread per of controller
294 self._create_ofc_task(ofc['uuid'], ofc['dpid'], of_conn)
295
296 def _create_ofc_task(self, ofc_uuid, dpid, of_conn):
297 """
298 Create an ofc thread for handle each sdn controllers
299 :param ofc_uuid: sdn controller uuid
300 :param dpid: sdn controller dpid
301 :param of_conn: OF_conn module
302 :return:
303 """
304 if 'ofcs_thread' not in self.config and 'ofcs_thread_dpid' not in self.config:
305 ofcs_threads = {}
306 ofcs_thread_dpid = []
307 else:
308 ofcs_threads = self.config['ofcs_thread']
309 ofcs_thread_dpid = self.config['ofcs_thread_dpid']
310
311 if ofc_uuid not in ofcs_threads:
312 ofc_thread = self._create_ofc_thread(of_conn, ofc_uuid)
313 if ofc_uuid == "Default":
314 self.config['of_thread'] = ofc_thread
315
316 ofcs_threads[ofc_uuid] = ofc_thread
317 self.config['ofcs_thread'] = ofcs_threads
318
319 ofcs_thread_dpid.append({dpid: ofc_thread})
320 self.config['ofcs_thread_dpid'] = ofcs_thread_dpid
321
322 def _start_ofc_default_task(self):
323 """
324 Create default ofc thread
325 """
326 if 'of_controller' not in self.config \
327 and 'of_controller_ip' not in self.config \
328 and 'of_controller_port' not in self.config \
329 and 'of_controller_dpid' not in self.config:
330 return
331
332 # OF THREAD
333 db_config = {}
334 db_config['ip'] = self.config.get('of_controller_ip')
335 db_config['port'] = self.config.get('of_controller_port')
336 db_config['dpid'] = self.config.get('of_controller_dpid')
337 db_config['type'] = self.config.get('of_controller')
338 db_config['user'] = self.config.get('of_user')
339 db_config['password'] = self.config.get('of_password')
340
341 # create connector to the openflow controller
342 # load other parameters starting by of_ from config dict in a temporal dict
343
344 of_conn = self._load_of_module(db_config)
345 # create openflow thread
346 self._create_ofc_task("Default", db_config['dpid'], of_conn)
347
348 def _load_of_module(self, db_config):
349 """
350 import python module for each SDN controller supported
mirabalf9a1a8d2017-03-15 12:42:27 +0100351 :param db_config: SDN dn information
mirabal580435e2017-03-01 16:17:10 +0100352 :return: Module
353 """
354 if not db_config:
355 raise ovimException("No module found it", HTTP_Internal_Server_Error)
356
357 module_info = None
358
359 try:
360 if self.of_test_mode:
mirabal6c600652017-03-16 17:22:57 +0100361 return openflow_conn.OfTestConnector({"name": db_config['type'],
362 "dpid": db_config['dpid'],
363 "of_debug": self.config['log_level_of']})
mirabal580435e2017-03-01 16:17:10 +0100364 temp_dict = {}
365
366 if db_config:
367 temp_dict['of_ip'] = db_config['ip']
368 temp_dict['of_port'] = db_config['port']
369 temp_dict['of_dpid'] = db_config['dpid']
370 temp_dict['of_controller'] = db_config['type']
montesmorenoba1862b2017-04-06 10:07:44 +0000371 temp_dict['of_user'] = db_config.get('user')
372 temp_dict['of_password'] = db_config.get('password')
mirabal580435e2017-03-01 16:17:10 +0100373
374 temp_dict['of_debug'] = self.config['log_level_of']
375
376 if temp_dict['of_controller'] == 'opendaylight':
377 module = "ODL"
378 else:
379 module = temp_dict['of_controller']
380
381 if module not in ovim.of_module:
tierno29d80242017-04-25 18:07:08 +0200382 for base in ("", "osm_openvim.", "lib_osm_openvim."):
383 try:
384 pkg = __import__(base + module)
385 if base:
386 of_conn_module = getattr(pkg, module)
387 else:
388 of_conn_module = pkg
389 ovim.of_module[module] = of_conn_module
390 self.logger.debug("Module load from {}".format(base + module))
391 break
392 except Exception as e:
393 self.logger.warning("Module {} not found {}".format(base + module, e))
394 else:
395 self.logger.error("Cannot open openflow controller module of type '%s'", module)
396 raise ovimException("Cannot open openflow controller of type module '{}'"
397 "Revise it is installed".format(module),
398 HTTP_Internal_Server_Error)
mirabal580435e2017-03-01 16:17:10 +0100399 else:
400 of_conn_module = ovim.of_module[module]
tierno29d80242017-04-25 18:07:08 +0200401 return of_conn_module.OF_conn(temp_dict)
402 except Exception as e:
403 self.logger.error("Cannot open the Openflow controller '%s': %s", type(e).__name__, str(e))
404 raise ovimException("Cannot open the Openflow controller '{}': '{}'".format(type(e).__name__, str(e)),
mirabal580435e2017-03-01 16:17:10 +0100405 HTTP_Internal_Server_Error)
406
407 def _create_ofc_thread(self, of_conn, ofc_uuid="Default"):
408 """
409 Create and launch a of thread
410 :return: thread obj
411 """
412 # create openflow thread
413
montesmoreno92827552017-03-30 13:24:17 +0200414 #if 'of_controller_nets_with_same_vlan' in self.config:
415 # ofc_net_same_vlan = self.config['of_controller_nets_with_same_vlan']
416 #else:
417 # ofc_net_same_vlan = False
418 ofc_net_same_vlan = False
mirabal580435e2017-03-01 16:17:10 +0100419
420 thread = oft.openflow_thread(ofc_uuid, of_conn, of_test=self.of_test_mode, db=self.db_of, db_lock=self.db_lock,
421 pmp_with_same_vlan=ofc_net_same_vlan, debug=self.config['log_level_of'])
422 #r, c = thread.OF_connector.obtain_port_correspondence()
423 #if r < 0:
424 # raise ovimException("Cannot get openflow information %s", c)
425 thread.start()
426 return thread
427
tierno57f7bda2017-02-09 12:01:55 +0100428 def stop_service(self):
429 threads = self.config.get('host_threads', {})
430 if 'of_thread' in self.config:
431 threads['of'] = (self.config['of_thread'])
mirabal580435e2017-03-01 16:17:10 +0100432 if 'ofcs_thread' in self.config:
433 ofcs_thread = self.config['ofcs_thread']
434 for ofc in ofcs_thread:
435 threads[ofc] = ofcs_thread[ofc]
436
tierno57f7bda2017-02-09 12:01:55 +0100437 if 'dhcp_thread' in self.config:
438 threads['dhcp'] = (self.config['dhcp_thread'])
439
440 for thread in threads.values():
441 thread.insert_task("exit")
442 for thread in threads.values():
443 thread.join()
tierno57f7bda2017-02-09 12:01:55 +0100444
mirabal9e194592017-02-17 11:03:25 +0100445 def get_networks(self, columns=None, db_filter={}, limit=None):
mirabal65ba8f82017-02-15 12:36:33 +0100446 """
447 Retreive networks available
mirabale9f6f1a2017-02-16 17:57:35 +0100448 :param columns: List with select query parameters
mirabal9e194592017-02-17 11:03:25 +0100449 :param db_filter: List with where query parameters
mirabale9f6f1a2017-02-16 17:57:35 +0100450 :param limit: Query limit result
mirabal65ba8f82017-02-15 12:36:33 +0100451 :return:
452 """
mirabal9e194592017-02-17 11:03:25 +0100453 result, content = self.db.get_table(SELECT=columns, FROM='nets', WHERE=db_filter, LIMIT=limit)
mirabal65ba8f82017-02-15 12:36:33 +0100454
455 if result < 0:
456 raise ovimException(str(content), -result)
457
458 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
459
460 return content
461
mirabal9e194592017-02-17 11:03:25 +0100462 def show_network(self, network_id, db_filter={}):
mirabal65ba8f82017-02-15 12:36:33 +0100463 """
mirabale9f6f1a2017-02-16 17:57:35 +0100464 Get network from DB by id
465 :param network_id: net Id
mirabal9e194592017-02-17 11:03:25 +0100466 :param db_filter: List with where query parameters
mirabal65ba8f82017-02-15 12:36:33 +0100467 :return:
468 """
469 # obtain data
mirabale9f6f1a2017-02-16 17:57:35 +0100470 if not network_id:
471 raise ovimException("Not network id was not found")
mirabal9e194592017-02-17 11:03:25 +0100472 db_filter['uuid'] = network_id
mirabal65ba8f82017-02-15 12:36:33 +0100473
mirabal9e194592017-02-17 11:03:25 +0100474 result, content = self.db.get_table(FROM='nets', WHERE=db_filter, LIMIT=100)
mirabal65ba8f82017-02-15 12:36:33 +0100475
476 if result < 0:
477 raise ovimException(str(content), -result)
478 elif result == 0:
mirabale9f6f1a2017-02-16 17:57:35 +0100479 raise ovimException("show_network network '%s' not found" % network_id, -result)
mirabal65ba8f82017-02-15 12:36:33 +0100480 else:
481 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
mirabale9f6f1a2017-02-16 17:57:35 +0100482 # get ports from DB
mirabal65ba8f82017-02-15 12:36:33 +0100483 result, ports = self.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
mirabale9f6f1a2017-02-16 17:57:35 +0100484 WHERE={'net_id': network_id}, LIMIT=100)
mirabal65ba8f82017-02-15 12:36:33 +0100485 if len(ports) > 0:
486 content[0]['ports'] = ports
mirabal65ba8f82017-02-15 12:36:33 +0100487
mirabale9f6f1a2017-02-16 17:57:35 +0100488 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
489 return content[0]
490
491 def new_network(self, network):
mirabal65ba8f82017-02-15 12:36:33 +0100492 """
mirabale9f6f1a2017-02-16 17:57:35 +0100493 Create a net in DB
mirabal65ba8f82017-02-15 12:36:33 +0100494 :return:
495 """
496 tenant_id = network.get('tenant_id')
497
498 if tenant_id:
499 result, _ = self.db.get_table(FROM='tenants', SELECT=('uuid',), WHERE={'uuid': tenant_id, "enabled": True})
500 if result <= 0:
501 raise ovimException("set_network error, no tenant founded", -result)
502
503 bridge_net = None
504 # check valid params
505 net_provider = network.get('provider')
506 net_type = network.get('type')
mirabal65ba8f82017-02-15 12:36:33 +0100507 net_vlan = network.get("vlan")
508 net_bind_net = network.get("bind_net")
509 net_bind_type = network.get("bind_type")
tiernoa290d8f2017-05-03 17:42:52 +0200510 net_region = network.get("region")
mirabal65ba8f82017-02-15 12:36:33 +0100511 name = network["name"]
512
513 # check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
514 vlan_index = name.rfind(":")
515 if not net_bind_net and not net_bind_type and vlan_index > 1:
516 try:
517 vlan_tag = int(name[vlan_index + 1:])
518 if not vlan_tag and vlan_tag < 4096:
519 net_bind_net = name[:vlan_index]
520 net_bind_type = "vlan:" + name[vlan_index + 1:]
521 except:
522 pass
523
524 if net_bind_net:
525 # look for a valid net
526 if self._check_valid_uuid(net_bind_net):
527 net_bind_key = "uuid"
528 else:
529 net_bind_key = "name"
530 result, content = self.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net})
531 if result < 0:
532 raise ovimException(' getting nets from db ' + content, HTTP_Internal_Server_Error)
533 elif result == 0:
534 raise ovimException(" bind_net %s '%s'not found" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
535 elif result > 1:
536 raise ovimException(" more than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
537 network["bind_net"] = content[0]["uuid"]
538
539 if net_bind_type:
540 if net_bind_type[0:5] != "vlan:":
541 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>'", HTTP_Bad_Request)
542 if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:]) <= 0:
543 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095",
544 HTTP_Bad_Request)
545 network["bind_type"] = net_bind_type
546
547 if net_provider:
548 if net_provider[:9] == "openflow:":
549 if net_type:
550 if net_type != "ptp" and net_type != "data":
551 raise ovimException(" only 'ptp' or 'data' net types can be bound to 'openflow'",
552 HTTP_Bad_Request)
553 else:
554 net_type = 'data'
555 else:
556 if net_type:
557 if net_type != "bridge_man" and net_type != "bridge_data":
558 raise ovimException("Only 'bridge_man' or 'bridge_data' net types can be bound "
559 "to 'bridge', 'macvtap' or 'default", HTTP_Bad_Request)
560 else:
561 net_type = 'bridge_man'
562
563 if not net_type:
564 net_type = 'bridge_man'
565
566 if net_provider:
567 if net_provider[:7] == 'bridge:':
568 # check it is one of the pre-provisioned bridges
569 bridge_net_name = net_provider[7:]
570 for brnet in self.config['bridge_nets']:
571 if brnet[0] == bridge_net_name: # free
572 if not brnet[3]:
573 raise ovimException("invalid 'provider:physical', "
574 "bridge '%s' is already used" % bridge_net_name, HTTP_Conflict)
575 bridge_net = brnet
576 net_vlan = brnet[1]
577 break
mirabale9f6f1a2017-02-16 17:57:35 +0100578 # if bridge_net==None:
579 # bottle.abort(HTTP_Bad_Request, "invalid 'provider:physical', bridge '%s' is not one of the
580 # provisioned 'bridge_ifaces' in the configuration file" % bridge_net_name)
581 # return
582
mirabal65ba8f82017-02-15 12:36:33 +0100583 elif self.config['network_type'] == 'bridge' and (net_type == 'bridge_data' or net_type == 'bridge_man'):
584 # look for a free precreated nets
585 for brnet in self.config['bridge_nets']:
586 if not brnet[3]: # free
587 if not bridge_net:
588 if net_type == 'bridge_man': # look for the smaller speed
589 if brnet[2] < bridge_net[2]:
590 bridge_net = brnet
591 else: # look for the larger speed
592 if brnet[2] > bridge_net[2]:
593 bridge_net = brnet
594 else:
595 bridge_net = brnet
596 net_vlan = brnet[1]
597 if not bridge_net:
598 raise ovimException("Max limits of bridge networks reached. Future versions of VIM "
599 "will overcome this limit", HTTP_Bad_Request)
600 else:
mirabale9f6f1a2017-02-16 17:57:35 +0100601 self.logger.debug("using net " + bridge_net)
mirabal65ba8f82017-02-15 12:36:33 +0100602 net_provider = "bridge:" + bridge_net[0]
603 net_vlan = bridge_net[1]
604 elif net_type == 'bridge_data' or net_type == 'bridge_man' and self.config['network_type'] == 'ovs':
605 net_provider = 'OVS'
tiernoa290d8f2017-05-03 17:42:52 +0200606 if not net_region:
607 if net_type == "data" or net_type == "ptp":
608 net_region = "__DATA__"
609 elif net_provider == "OVS":
610 net_region = "__OVS__"
mirabal65ba8f82017-02-15 12:36:33 +0100611 if not net_vlan and (net_type == "data" or net_type == "ptp" or net_provider == "OVS"):
tiernoa290d8f2017-05-03 17:42:52 +0200612 net_vlan = self.db.get_free_net_vlan(net_region)
mirabal65ba8f82017-02-15 12:36:33 +0100613 if net_vlan < 0:
614 raise ovimException("Error getting an available vlan", HTTP_Internal_Server_Error)
615 if net_provider == 'OVS':
616 net_provider = 'OVS' + ":" + str(net_vlan)
617
618 network['provider'] = net_provider
619 network['type'] = net_type
620 network['vlan'] = net_vlan
tiernoa290d8f2017-05-03 17:42:52 +0200621 network['region'] = net_region
mirabal65ba8f82017-02-15 12:36:33 +0100622 dhcp_integrity = True
623 if 'enable_dhcp' in network and network['enable_dhcp']:
624 dhcp_integrity = self._check_dhcp_data_integrity(network)
625
626 result, content = self.db.new_row('nets', network, True, True)
627
628 if result >= 0 and dhcp_integrity:
629 if bridge_net:
630 bridge_net[3] = content
631 if self.config.get("dhcp_server") and self.config['network_type'] == 'bridge':
632 if network["name"] in self.config["dhcp_server"].get("nets", ()):
633 self.config["dhcp_nets"].append(content)
mirabale9f6f1a2017-02-16 17:57:35 +0100634 self.logger.debug("dhcp_server: add new net", content)
mirabal65ba8f82017-02-15 12:36:33 +0100635 elif not bridge_net and bridge_net[0] in self.config["dhcp_server"].get("bridge_ifaces", ()):
636 self.config["dhcp_nets"].append(content)
mirabale9f6f1a2017-02-16 17:57:35 +0100637 self.logger.debug("dhcp_server: add new net", content, content)
mirabal65ba8f82017-02-15 12:36:33 +0100638 return content
639 else:
mirabal65ba8f82017-02-15 12:36:33 +0100640 raise ovimException("Error posting network", HTTP_Internal_Server_Error)
mirabale9f6f1a2017-02-16 17:57:35 +0100641# TODO kei change update->edit
mirabal65ba8f82017-02-15 12:36:33 +0100642
mirabale9f6f1a2017-02-16 17:57:35 +0100643 def edit_network(self, network_id, network):
mirabal65ba8f82017-02-15 12:36:33 +0100644 """
mirabale9f6f1a2017-02-16 17:57:35 +0100645 Update entwork data byt id
mirabal65ba8f82017-02-15 12:36:33 +0100646 :return:
647 """
mirabale9f6f1a2017-02-16 17:57:35 +0100648 # Look for the previous data
649 where_ = {'uuid': network_id}
650 result, network_old = self.db.get_table(FROM='nets', WHERE=where_)
651 if result < 0:
652 raise ovimException("Error updating network %s" % network_old, HTTP_Internal_Server_Error)
653 elif result == 0:
654 raise ovimException('network %s not found' % network_id, HTTP_Not_Found)
655 # get ports
656 nbports, content = self.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
657 WHERE={'net_id': network_id}, LIMIT=100)
658 if result < 0:
659 raise ovimException("http_put_network_id error %d %s" % (result, network_old), HTTP_Internal_Server_Error)
660 if nbports > 0:
661 if 'type' in network and network['type'] != network_old[0]['type']:
662 raise ovimException("Can not change type of network while having ports attached",
663 HTTP_Method_Not_Allowed)
664 if 'vlan' in network and network['vlan'] != network_old[0]['vlan']:
665 raise ovimException("Can not change vlan of network while having ports attached",
666 HTTP_Method_Not_Allowed)
mirabal65ba8f82017-02-15 12:36:33 +0100667
mirabale9f6f1a2017-02-16 17:57:35 +0100668 # check valid params
669 net_provider = network.get('provider', network_old[0]['provider'])
670 net_type = network.get('type', network_old[0]['type'])
671 net_bind_net = network.get("bind_net")
672 net_bind_type = network.get("bind_type")
673 if net_bind_net:
674 # look for a valid net
675 if self._check_valid_uuid(net_bind_net):
676 net_bind_key = "uuid"
677 else:
678 net_bind_key = "name"
679 result, content = self.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net})
680 if result < 0:
681 raise ovimException('Getting nets from db ' + content, HTTP_Internal_Server_Error)
682 elif result == 0:
683 raise ovimException("bind_net %s '%s'not found" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
684 elif result > 1:
685 raise ovimException("More than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net),
686 HTTP_Bad_Request)
687 network["bind_net"] = content[0]["uuid"]
688 if net_bind_type:
689 if net_bind_type[0:5] != "vlan:":
690 raise ovimException("Bad format for 'bind_type', must be 'vlan:<tag>'", HTTP_Bad_Request)
691 if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:]) <= 0:
692 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095",
693 HTTP_Bad_Request)
694 if net_provider:
695 if net_provider[:9] == "openflow:":
696 if net_type != "ptp" and net_type != "data":
697 raise ovimException("Only 'ptp' or 'data' net types can be bound to 'openflow'", HTTP_Bad_Request)
698 else:
699 if net_type != "bridge_man" and net_type != "bridge_data":
700 raise ovimException("Only 'bridge_man' or 'bridge_data' net types can be bound to "
701 "'bridge', 'macvtap' or 'default", HTTP_Bad_Request)
mirabal65ba8f82017-02-15 12:36:33 +0100702
mirabale9f6f1a2017-02-16 17:57:35 +0100703 # insert in data base
704 result, content = self.db.update_rows('nets', network, WHERE={'uuid': network_id}, log=True)
705 if result >= 0:
706 # if result > 0 and nbports>0 and 'admin_state_up' in network
707 # and network['admin_state_up'] != network_old[0]['admin_state_up']:
708 if result > 0:
mirabal7bbf50e2017-03-13 15:15:18 +0100709
710 try:
tiernoaa941462017-03-29 15:10:28 +0200711 if nbports:
712 self.net_update_ofc_thread(network_id)
mirabal7bbf50e2017-03-13 15:15:18 +0100713 except ovimException as e:
714 raise ovimException("Error while launching openflow rules in network '{}' {}"
715 .format(network_id, str(e)), HTTP_Internal_Server_Error)
716 except Exception as e:
717 raise ovimException("Error while launching openflow rules in network '{}' {}"
718 .format(network_id, str(e)), HTTP_Internal_Server_Error)
719
mirabale9f6f1a2017-02-16 17:57:35 +0100720 if self.config.get("dhcp_server"):
721 if network_id in self.config["dhcp_nets"]:
722 self.config["dhcp_nets"].remove(network_id)
mirabal7bbf50e2017-03-13 15:15:18 +0100723 if network.get("name", network_old[0]["name"]) in self.config["dhcp_server"].get("nets", ()):
mirabale9f6f1a2017-02-16 17:57:35 +0100724 self.config["dhcp_nets"].append(network_id)
725 else:
mirabal7bbf50e2017-03-13 15:15:18 +0100726 net_bind = network.get("bind_type", network_old[0]["bind_type"])
727 if net_bind and net_bind and net_bind[:7] == "bridge:" and net_bind[7:] in self.config["dhcp_server"].get(
mirabale9f6f1a2017-02-16 17:57:35 +0100728 "bridge_ifaces", ()):
729 self.config["dhcp_nets"].append(network_id)
730 return network_id
mirabal65ba8f82017-02-15 12:36:33 +0100731 else:
mirabale9f6f1a2017-02-16 17:57:35 +0100732 raise ovimException(content, -result)
mirabal65ba8f82017-02-15 12:36:33 +0100733
mirabale9f6f1a2017-02-16 17:57:35 +0100734 def delete_network(self, network_id):
mirabal9e194592017-02-17 11:03:25 +0100735 """
736 Delete network by network id
737 :param network_id: network id
738 :return:
739 """
mirabale9f6f1a2017-02-16 17:57:35 +0100740
741 # delete from the data base
742 result, content = self.db.delete_row('nets', network_id)
743
744 if result == 0:
745 raise ovimException("Network %s not found " % network_id, HTTP_Not_Found)
746 elif result > 0:
747 for brnet in self.config['bridge_nets']:
748 if brnet[3] == network_id:
749 brnet[3] = None
750 break
751 if self.config.get("dhcp_server") and network_id in self.config["dhcp_nets"]:
752 self.config["dhcp_nets"].remove(network_id)
753 return content
754 else:
755 raise ovimException("Error deleting network %s" % network_id, HTTP_Internal_Server_Error)
mirabal65ba8f82017-02-15 12:36:33 +0100756
757 def get_openflow_rules(self, network_id=None):
758 """
759 Get openflow id from DB
760 :param network_id: Network id, if none all networks will be retrieved
761 :return: Return a list with Openflow rules per net
762 """
763 # ignore input data
764 if not network_id:
765 where_ = {}
766 else:
767 where_ = {"net_id": network_id}
mirabal65ba8f82017-02-15 12:36:33 +0100768 result, content = self.db.get_table(
mirabalf9a1a8d2017-03-15 12:42:27 +0100769 SELECT=("name", "net_id", "ofc_id", "priority", "vlan_id", "ingress_port", "src_mac", "dst_mac", "actions"),
mirabal65ba8f82017-02-15 12:36:33 +0100770 WHERE=where_, FROM='of_flows')
771
772 if result < 0:
773 raise ovimException(str(content), -result)
774 return content
775
mirabale9f6f1a2017-02-16 17:57:35 +0100776 def edit_openflow_rules(self, network_id=None):
mirabal65ba8f82017-02-15 12:36:33 +0100777
778 """
779 To make actions over the net. The action is to reinstall the openflow rules
780 network_id can be 'all'
781 :param network_id: Network id, if none all networks will be retrieved
782 :return : Number of nets updated
783 """
784
785 # ignore input data
786 if not network_id:
787 where_ = {}
788 else:
789 where_ = {"uuid": network_id}
790 result, content = self.db.get_table(SELECT=("uuid", "type"), WHERE=where_, FROM='nets')
791
792 if result < 0:
793 raise ovimException(str(content), -result)
794
795 for net in content:
796 if net["type"] != "ptp" and net["type"] != "data":
797 result -= 1
798 continue
mirabal7bbf50e2017-03-13 15:15:18 +0100799
800 try:
801 self.net_update_ofc_thread(net['uuid'])
802 except ovimException as e:
803 raise ovimException("Error updating network'{}' {}".format(net['uuid'], str(e)),
804 HTTP_Internal_Server_Error)
805 except Exception as e:
806 raise ovimException("Error updating network '{}' {}".format(net['uuid'], str(e)),
807 HTTP_Internal_Server_Error)
808
mirabal65ba8f82017-02-15 12:36:33 +0100809 return result
810
mirabalf9a1a8d2017-03-15 12:42:27 +0100811 def delete_openflow_rules(self, ofc_id=None):
mirabal65ba8f82017-02-15 12:36:33 +0100812 """
813 To make actions over the net. The action is to delete ALL openflow rules
814 :return: return operation result
815 """
mirabalf9a1a8d2017-03-15 12:42:27 +0100816
817 if not ofc_id:
818 if 'Default' in self.config['ofcs_thread']:
819 r, c = self.config['ofcs_thread']['Default'].insert_task("clear-all")
820 else:
821 raise ovimException("Default Openflow controller not not running", HTTP_Not_Found)
822
823 elif ofc_id in self.config['ofcs_thread']:
824 r, c = self.config['ofcs_thread'][ofc_id].insert_task("clear-all")
825
826 # ignore input data
827 if r < 0:
828 raise ovimException(str(c), -r)
829 else:
830 raise ovimException("Openflow controller not found with ofc_id={}".format(ofc_id), HTTP_Not_Found)
mirabal65ba8f82017-02-15 12:36:33 +0100831 return r
832
mirabalf9a1a8d2017-03-15 12:42:27 +0100833 def get_openflow_ports(self, ofc_id=None):
mirabal65ba8f82017-02-15 12:36:33 +0100834 """
835 Obtain switch ports names of openflow controller
836 :return: Return flow ports in DB
837 """
mirabalf9a1a8d2017-03-15 12:42:27 +0100838 if not ofc_id:
839 if 'Default' in self.config['ofcs_thread']:
840 conn = self.config['ofcs_thread']['Default'].OF_connector
841 else:
842 raise ovimException("Default Openflow controller not not running", HTTP_Not_Found)
843
844 if ofc_id in self.config['ofcs_thread']:
845 conn = self.config['ofcs_thread'][ofc_id].OF_connector
846 else:
847 raise ovimException("Openflow controller not found with ofc_id={}".format(ofc_id), HTTP_Not_Found)
848 return conn.pp2ofi
tierno57f7bda2017-02-09 12:01:55 +0100849
850 def get_ports(self, columns=None, filter={}, limit=None):
851 # result, content = my.db.get_ports(where_)
852 result, content = self.db.get_table(SELECT=columns, WHERE=filter, FROM='ports', LIMIT=limit)
853 if result < 0:
854 self.logger.error("http_get_ports Error %d %s", result, content)
855 raise ovimException(str(content), -result)
856 else:
857 convert_boolean(content, ('admin_state_up',))
858 return content
859
tierno57f7bda2017-02-09 12:01:55 +0100860 def new_port(self, port_data):
861 port_data['type'] = 'external'
862 if port_data.get('net_id'):
863 # check that new net has the correct type
864 result, new_net = self.db.check_target_net(port_data['net_id'], None, 'external')
865 if result < 0:
866 raise ovimException(str(new_net), -result)
867 # insert in data base
868 result, uuid = self.db.new_row('ports', port_data, True, True)
869 if result > 0:
870 if 'net_id' in port_data:
mirabal7bbf50e2017-03-13 15:15:18 +0100871 try:
872 self.net_update_ofc_thread(port_data['net_id'])
873 except ovimException as e:
874 raise ovimException("Cannot insert a task for updating network '{}' {}"
875 .format(port_data['net_id'], str(e)), HTTP_Internal_Server_Error)
876 except Exception as e:
877 raise ovimException("Cannot insert a task for updating network '{}' {}"
878 .format(port_data['net_id'], str(e)), HTTP_Internal_Server_Error)
879
tierno57f7bda2017-02-09 12:01:55 +0100880 return uuid
881 else:
882 raise ovimException(str(uuid), -result)
883
mirabal37829452017-03-09 14:41:21 +0100884 def new_external_port(self, port_data):
885 """
886 Create new external port and check port mapping correspondence
887 :param port_data: port_data = {
888 'region': 'datacenter region',
889 'compute_node': 'compute node id',
890 'pci': 'pci port address',
891 'vlan': 'net vlan',
892 'net_id': 'net id',
893 'tenant_id': 'tenant id',
894 'mac': 'switch mac',
895 'name': 'port name'
896 'ip_address': 'ip address - optional'}
897 :return:
898 """
899
900 port_data['type'] = 'external'
901
902 if port_data.get('net_id'):
903 # check that new net has the correct type
904 result, new_net = self.db.check_target_net(port_data['net_id'], None, 'external')
905 if result < 0:
906 raise ovimException(str(new_net), -result)
907 # insert in data base
908 db_filter = {}
909
910 if port_data.get('region'):
911 db_filter['region'] = port_data['region']
912 if port_data.get('pci'):
913 db_filter['pci'] = port_data['pci']
914 if port_data.get('compute_node'):
915 db_filter['compute_node'] = port_data['compute_node']
916
917 columns = ['ofc_id', 'switch_dpid', 'switch_port', 'switch_mac', 'pci']
918 port_mapping_data = self.get_of_port_mappings(columns, db_filter)
919
920 if not len(port_mapping_data):
tierno16007502017-03-27 16:48:32 +0200921 raise ovimException("No port mapping founded for '{}'".format(str(db_filter)),
mirabal37829452017-03-09 14:41:21 +0100922 HTTP_Not_Found)
923 elif len(port_mapping_data) > 1:
924 raise ovimException("Wrong port data was given, please check pci, region & compute id data",
925 HTTP_Conflict)
926
927 port_data['ofc_id'] = port_mapping_data[0]['ofc_id']
928 port_data['switch_dpid'] = port_mapping_data[0]['switch_dpid']
929 port_data['switch_port'] = port_mapping_data[0]['switch_port']
930 port_data['switch_mac'] = port_mapping_data[0]['switch_mac']
931
932 # remove from compute_node, region and pci of_port_data to adapt to 'ports' structure
montesmoreno275b1992017-03-28 15:45:02 +0200933 if 'region' in port_data:
934 del port_data['region']
935 if 'pci' in port_data:
936 del port_data['pci']
937 if 'compute_node' in port_data:
938 del port_data['compute_node']
mirabal37829452017-03-09 14:41:21 +0100939
940 result, uuid = self.db.new_row('ports', port_data, True, True)
941 if result > 0:
mirabal7bbf50e2017-03-13 15:15:18 +0100942 try:
943 self.net_update_ofc_thread(port_data['net_id'], port_data['ofc_id'])
944 except ovimException as e:
945 raise ovimException("Cannot insert a task for updating network '{}' {}".
946 format(port_data['net_id'], str(e)), HTTP_Internal_Server_Error)
947 except Exception as e:
948 raise ovimException("Cannot insert a task for updating network '{}' {}"
949 .format(port_data['net_id'], e), HTTP_Internal_Server_Error)
mirabal37829452017-03-09 14:41:21 +0100950 return uuid
951 else:
952 raise ovimException(str(uuid), -result)
953
tiernoaa941462017-03-29 15:10:28 +0200954 def net_update_ofc_thread(self, net_id, ofc_id=None, switch_dpid=None):
mirabal7bbf50e2017-03-13 15:15:18 +0100955 """
956 Insert a update net task by net id or ofc_id for each ofc thread
957 :param net_id: network id
958 :param ofc_id: openflow controller id
tiernoaa941462017-03-29 15:10:28 +0200959 :param switch_dpid: switch dpid
mirabal7bbf50e2017-03-13 15:15:18 +0100960 :return:
961 """
962 if not net_id:
963 raise ovimException("No net_id received", HTTP_Internal_Server_Error)
964
mirabal7bbf50e2017-03-13 15:15:18 +0100965 r = -1
966 c = 'No valid ofc_id or switch_dpid received'
967
968 if not ofc_id:
969 ports = self.get_ports(filter={"net_id": net_id})
970 for port in ports:
971 port_ofc_id = port.get('ofc_id', None)
972 if port_ofc_id:
973 ofc_id = port['ofc_id']
974 switch_dpid = port['switch_dpid']
975 break
tiernoaa941462017-03-29 15:10:28 +0200976 #TODO if not ofc_id: look at database table ofcs
977
mirabal7bbf50e2017-03-13 15:15:18 +0100978
979 # If no ofc_id found it, default ofc_id is used.
980 if not ofc_id and not switch_dpid:
981 ofc_id = "Default"
982
983 if ofc_id and ofc_id in self.config['ofcs_thread']:
984 r, c = self.config['ofcs_thread'][ofc_id].insert_task("update-net", net_id)
985 elif switch_dpid:
986
987 ofcs_dpid_list = self.config['ofcs_thread_dpid']
988 for ofc_t in ofcs_dpid_list:
989 if switch_dpid in ofc_t:
990 r, c = ofc_t[switch_dpid].insert_task("update-net", net_id)
991
992 if r < 0:
tierno82232582017-03-15 18:09:16 +0100993 message = "Cannot insert a task for updating network '{}', {}".format(net_id, c)
mirabal7bbf50e2017-03-13 15:15:18 +0100994 self.logger.error(message)
995 raise ovimException(message, HTTP_Internal_Server_Error)
996
tierno57f7bda2017-02-09 12:01:55 +0100997 def delete_port(self, port_id):
998 # Look for the previous port data
999 result, ports = self.db.get_table(WHERE={'uuid': port_id, "type": "external"}, FROM='ports')
1000 if result < 0:
1001 raise ovimException("Cannot get port info from database: {}".format(ports), http_code=-result)
1002 # delete from the data base
1003 result, content = self.db.delete_row('ports', port_id)
1004 if result == 0:
1005 raise ovimException("External port '{}' not found".format(port_id), http_code=HTTP_Not_Found)
1006 elif result < 0:
1007 raise ovimException("Cannot delete port from database: {}".format(content), http_code=-result)
1008 # update network
1009 network = ports[0].get('net_id', None)
1010 if network:
1011 # change of net.
mirabal7bbf50e2017-03-13 15:15:18 +01001012
1013 try:
tiernoaa941462017-03-29 15:10:28 +02001014 self.net_update_ofc_thread(network, ofc_id=ports[0]["ofc_id"], switch_dpid=ports[0]["switch_dpid"])
mirabal7bbf50e2017-03-13 15:15:18 +01001015 except ovimException as e:
1016 raise ovimException("Cannot insert a task for delete network '{}' {}".format(network, str(e)),
1017 HTTP_Internal_Server_Error)
1018 except Exception as e:
1019 raise ovimException("Cannot insert a task for delete network '{}' {}".format(network, str(e)),
1020 HTTP_Internal_Server_Error)
1021
tierno57f7bda2017-02-09 12:01:55 +01001022 return content
1023
tierno57f7bda2017-02-09 12:01:55 +01001024 def edit_port(self, port_id, port_data, admin=True):
1025 # Look for the previous port data
1026 result, content = self.db.get_table(FROM="ports", WHERE={'uuid': port_id})
1027 if result < 0:
1028 raise ovimException("Cannot get port info from database: {}".format(content), http_code=-result)
1029 elif result == 0:
1030 raise ovimException("Port '{}' not found".format(port_id), http_code=HTTP_Not_Found)
1031 port = content[0]
1032 nets = []
1033 host_id = None
1034 result = 1
1035 if 'net_id' in port_data:
1036 # change of net.
1037 old_net = port.get('net_id', None)
1038 new_net = port_data['net_id']
1039 if old_net != new_net:
1040
1041 if new_net:
1042 nets.append(new_net) # put first the new net, so that new openflow rules are created before removing the old ones
1043 if old_net:
1044 nets.append(old_net)
1045 if port['type'] == 'instance:bridge' or port['type'] == 'instance:ovs':
1046 raise ovimException("bridge interfaces cannot be attached to a different net", http_code=HTTP_Forbidden)
1047 elif port['type'] == 'external' and not admin:
1048 raise ovimException("Needed admin privileges",http_code=HTTP_Unauthorized)
1049 if new_net:
1050 # check that new net has the correct type
1051 result, new_net_dict = self.db.check_target_net(new_net, None, port['type'])
1052 if result < 0:
1053 raise ovimException("Error {}".format(new_net_dict), http_code=HTTP_Conflict)
1054 # change VLAN for SR-IOV ports
1055 if result >= 0 and port["type"] == "instance:data" and port["model"] == "VF": # TODO consider also VFnotShared
1056 if new_net:
1057 port_data["vlan"] = None
1058 else:
1059 port_data["vlan"] = new_net_dict["vlan"]
1060 # get host where this VM is allocated
1061 result, content = self.db.get_table(FROM="instances", WHERE={"uuid": port["instance_id"]})
1062 if result > 0:
1063 host_id = content[0]["host_id"]
1064
1065 # insert in data base
1066 if result >= 0:
1067 result, content = self.db.update_rows('ports', port_data, WHERE={'uuid': port_id}, log=False)
tiernoaa941462017-03-29 15:10:28 +02001068 port.update(port_data)
tierno57f7bda2017-02-09 12:01:55 +01001069
1070 # Insert task to complete actions
1071 if result > 0:
1072 for net_id in nets:
mirabal7bbf50e2017-03-13 15:15:18 +01001073 try:
tiernoaa941462017-03-29 15:10:28 +02001074 self.net_update_ofc_thread(net_id, port["ofc_id"], switch_dpid=port["switch_dpid"])
mirabal7bbf50e2017-03-13 15:15:18 +01001075 except ovimException as e:
1076 raise ovimException("Error updating network'{}' {}".format(net_id, str(e)),
1077 HTTP_Internal_Server_Error)
1078 except Exception as e:
1079 raise ovimException("Error updating network '{}' {}".format(net_id, str(e)),
1080 HTTP_Internal_Server_Error)
1081
tierno57f7bda2017-02-09 12:01:55 +01001082 if host_id:
1083 r, v = self.config['host_threads'][host_id].insert_task("edit-iface", port_id, old_net, new_net)
1084 if r < 0:
1085 self.logger.error("Error updating network '{}' {}".format(r,v))
1086 # TODO Do something if fails
1087 if result >= 0:
1088 return port_id
1089 else:
1090 raise ovimException("Error {}".format(content), http_code=-result)
mirabalb716ac52017-02-10 14:47:53 +01001091
mirabal9e194592017-02-17 11:03:25 +01001092 def new_of_controller(self, ofc_data):
1093 """
1094 Create a new openflow controller into DB
1095 :param ofc_data: Dict openflow controller data
1096 :return: openflow controller dpid
1097 """
1098
mirabal580435e2017-03-01 16:17:10 +01001099 result, ofc_uuid = self.db.new_row('ofcs', ofc_data, True, True)
mirabal9e194592017-02-17 11:03:25 +01001100 if result < 0:
mirabal580435e2017-03-01 16:17:10 +01001101 raise ovimException("New ofc Error %s" % ofc_uuid, HTTP_Internal_Server_Error)
1102
1103 ofc_data['uuid'] = ofc_uuid
1104 of_conn = self._load_of_module(ofc_data)
1105 self._create_ofc_task(ofc_uuid, ofc_data['dpid'], of_conn)
1106
1107 return ofc_uuid
mirabal9e194592017-02-17 11:03:25 +01001108
1109 def edit_of_controller(self, of_id, ofc_data):
1110 """
1111 Edit an openflow controller entry from DB
1112 :return:
1113 """
1114 if not ofc_data:
1115 raise ovimException("No data received during uptade OF contorller", http_code=HTTP_Internal_Server_Error)
1116
1117 old_of_controller = self.show_of_controller(of_id)
1118
1119 if old_of_controller:
1120 result, content = self.db.update_rows('ofcs', ofc_data, WHERE={'uuid': of_id}, log=False)
1121 if result >= 0:
1122 return ofc_data
1123 else:
1124 raise ovimException("Error uptating OF contorller with uuid {}".format(of_id),
1125 http_code=-result)
1126 else:
1127 raise ovimException("Error uptating OF contorller with uuid {}".format(of_id),
1128 http_code=HTTP_Internal_Server_Error)
1129
1130 def delete_of_controller(self, of_id):
1131 """
1132 Delete an openflow controller from DB.
1133 :param of_id: openflow controller dpid
1134 :return:
1135 """
1136
mirabal580435e2017-03-01 16:17:10 +01001137 ofc = self.show_of_controller(of_id)
1138
Pablo Montes Moreno5b6f7492017-03-02 16:18:36 +01001139 result, content = self.db.delete_row("ofcs", of_id)
mirabal9e194592017-02-17 11:03:25 +01001140 if result < 0:
1141 raise ovimException("Cannot delete ofc from database: {}".format(content), http_code=-result)
1142 elif result == 0:
1143 raise ovimException("ofc {} not found ".format(content), http_code=HTTP_Not_Found)
mirabal580435e2017-03-01 16:17:10 +01001144
1145 ofc_thread = self.config['ofcs_thread'][of_id]
1146 del self.config['ofcs_thread'][of_id]
1147 for ofc_th in self.config['ofcs_thread_dpid']:
1148 if ofc['dpid'] in ofc_th:
1149 self.config['ofcs_thread_dpid'].remove(ofc_th)
1150
1151 ofc_thread.insert_task("exit")
1152 #ofc_thread.join()
1153
mirabal9e194592017-02-17 11:03:25 +01001154 return content
1155
1156 def show_of_controller(self, uuid):
1157 """
1158 Show an openflow controller by dpid from DB.
1159 :param db_filter: List with where query parameters
1160 :return:
1161 """
1162
1163 result, content = self.db.get_table(FROM='ofcs', WHERE={"uuid": uuid}, LIMIT=100)
1164
1165 if result == 0:
1166 raise ovimException("Openflow controller with uuid '{}' not found".format(uuid),
1167 http_code=HTTP_Not_Found)
1168 elif result < 0:
1169 raise ovimException("Openflow controller with uuid '{}' error".format(uuid),
1170 http_code=HTTP_Internal_Server_Error)
Pablo Montes Moreno5b6f7492017-03-02 16:18:36 +01001171 return content[0]
mirabal9e194592017-02-17 11:03:25 +01001172
mirabalfbfb7972017-02-27 17:36:17 +01001173 def get_of_controllers(self, columns=None, db_filter={}, limit=None):
mirabal9e194592017-02-17 11:03:25 +01001174 """
1175 Show an openflow controllers from DB.
1176 :param columns: List with SELECT query parameters
1177 :param db_filter: List with where query parameters
mirabalfbfb7972017-02-27 17:36:17 +01001178 :param limit: result Limit
mirabal9e194592017-02-17 11:03:25 +01001179 :return:
1180 """
mirabalfbfb7972017-02-27 17:36:17 +01001181 result, content = self.db.get_table(SELECT=columns, FROM='ofcs', WHERE=db_filter, LIMIT=limit)
mirabal9e194592017-02-17 11:03:25 +01001182
1183 if result < 0:
1184 raise ovimException(str(content), -result)
1185
1186 return content
1187
mirabalfbfb7972017-02-27 17:36:17 +01001188 def get_tenants(self, columns=None, db_filter={}, limit=None):
1189 """
1190 Retrieve tenant list from DB
1191 :param columns: List with SELECT query parameters
1192 :param db_filter: List with where query parameters
1193 :param limit: result limit
1194 :return:
1195 """
1196 result, content = self.db.get_table(FROM='tenants', SELECT=columns, WHERE=db_filter, LIMIT=limit)
1197 if result < 0:
1198 raise ovimException('get_tenatns Error {}'.format(str(content)), -result)
1199 else:
1200 convert_boolean(content, ('enabled',))
1201 return content
1202
1203 def show_tenant_id(self, tenant_id):
1204 """
1205 Get tenant from DB by id
1206 :param tenant_id: tenant id
1207 :return:
1208 """
1209 result, content = self.db.get_table(FROM='tenants', SELECT=('uuid', 'name', 'description', 'enabled'),
1210 WHERE={"uuid": tenant_id})
1211 if result < 0:
1212 raise ovimException(str(content), -result)
1213 elif result == 0:
1214 raise ovimException("tenant with uuid='{}' not found".format(tenant_id), HTTP_Not_Found)
1215 else:
1216 convert_boolean(content, ('enabled',))
1217 return content[0]
1218
1219 def new_tentant(self, tenant):
1220 """
1221 Create a tenant and store in DB
1222 :param tenant: Dictionary with tenant data
1223 :return: the uuid of created tenant. Raise exception upon error
1224 """
1225
1226 # insert in data base
1227 result, tenant_uuid = self.db.new_tenant(tenant)
1228
1229 if result >= 0:
1230 return tenant_uuid
1231 else:
1232 raise ovimException(str(tenant_uuid), -result)
1233
1234 def delete_tentant(self, tenant_id):
1235 """
1236 Delete a tenant from the database.
1237 :param tenant_id: Tenant id
1238 :return: delete tenant id
1239 """
1240
1241 # check permissions
1242 r, tenants_flavors = self.db.get_table(FROM='tenants_flavors', SELECT=('flavor_id', 'tenant_id'),
1243 WHERE={'tenant_id': tenant_id})
1244 if r <= 0:
1245 tenants_flavors = ()
1246 r, tenants_images = self.db.get_table(FROM='tenants_images', SELECT=('image_id', 'tenant_id'),
1247 WHERE={'tenant_id': tenant_id})
1248 if r <= 0:
1249 tenants_images = ()
1250
1251 result, content = self.db.delete_row('tenants', tenant_id)
1252 if result == 0:
1253 raise ovimException("tenant '%s' not found" % tenant_id, HTTP_Not_Found)
1254 elif result > 0:
1255 for flavor in tenants_flavors:
1256 self.db.delete_row_by_key("flavors", "uuid", flavor['flavor_id'])
1257 for image in tenants_images:
1258 self.db.delete_row_by_key("images", "uuid", image['image_id'])
1259 return content
1260 else:
1261 raise ovimException("Error deleting tenant '%s' " % tenant_id, HTTP_Internal_Server_Error)
1262
1263 def edit_tenant(self, tenant_id, tenant_data):
1264 """
1265 Update a tenant data identified by tenant id
1266 :param tenant_id: tenant id
1267 :param tenant_data: Dictionary with tenant data
1268 :return:
1269 """
1270
1271 # Look for the previous data
1272 result, tenant_data_old = self.db.get_table(FROM='tenants', WHERE={'uuid': tenant_id})
1273 if result < 0:
1274 raise ovimException("Error updating tenant with uuid='{}': {}".format(tenant_id, tenant_data_old),
1275 HTTP_Internal_Server_Error)
1276 elif result == 0:
1277 raise ovimException("tenant with uuid='{}' not found".format(tenant_id), HTTP_Not_Found)
1278
1279 # insert in data base
1280 result, content = self.db.update_rows('tenants', tenant_data, WHERE={'uuid': tenant_id}, log=True)
1281 if result >= 0:
1282 return content
1283 else:
1284 raise ovimException(str(content), -result)
1285
mirabal6045a9d2017-03-06 11:36:55 +01001286 def set_of_port_mapping(self, of_maps, ofc_id=None, switch_dpid=None, region=None):
1287 """
1288 Create new port mapping entry
1289 :param of_maps: List with port mapping information
1290 # maps =[{"ofc_id": <ofc_id>,"region": datacenter region,"compute_node": compute uuid,"pci": pci adress,
1291 "switch_dpid": swith dpid,"switch_port": port name,"switch_mac": mac}]
1292 :param ofc_id: ofc id
1293 :param switch_dpid: switch dpid
1294 :param region: datacenter region id
1295 :return:
1296 """
1297
1298 for map in of_maps:
1299 if ofc_id:
1300 map['ofc_id'] = ofc_id
1301 if switch_dpid:
1302 map['switch_dpid'] = switch_dpid
1303 if region:
1304 map['region'] = region
1305
1306 for of_map in of_maps:
1307 result, uuid = self.db.new_row('of_port_mappings', of_map, True)
1308 if result > 0:
1309 of_map["uuid"] = uuid
1310 else:
1311 raise ovimException(str(uuid), -result)
1312 return of_maps
1313
1314 def clear_of_port_mapping(self, db_filter={}):
1315 """
1316 Clear port mapping filtering using db_filter dict
1317 :param db_filter: Parameter to filter during remove process
1318 :return:
1319 """
1320 result, content = self.db.delete_row_by_dict(FROM='of_port_mappings', WHERE=db_filter)
1321 # delete_row_by_key
1322 if result >= 0:
1323 return content
1324 else:
1325 raise ovimException("Error deleting of_port_mappings with filter='{}'".format(str(db_filter)),
1326 HTTP_Internal_Server_Error)
1327
1328 def get_of_port_mappings(self, column=None, db_filter=None, db_limit=None):
1329 """
1330 Retrive port mapping from DB
1331 :param column:
1332 :param db_filter:
1333 :return:
1334 """
1335 result, content = self.db.get_table(SELECT=column, WHERE=db_filter, FROM='of_port_mappings', LIMIT=db_limit)
1336
1337 if result < 0:
1338 self.logger.error("get_of_port_mappings Error %d %s", result, content)
1339 raise ovimException(str(content), -result)
1340 else:
1341 return content
1342
mirabalb716ac52017-02-10 14:47:53 +01001343 def get_dhcp_controller(self):
1344 """
1345 Create an host_thread object for manage openvim controller and not create a thread for itself
1346 :return: dhcp_host openvim controller object
1347 """
1348
1349 if 'openvim_controller' in self.config['host_threads']:
1350 return self.config['host_threads']['openvim_controller']
1351
1352 bridge_ifaces = []
1353 controller_ip = self.config['ovs_controller_ip']
1354 ovs_controller_user = self.config['ovs_controller_user']
1355
1356 host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
1357 host_develop_mode = True if self.config['mode'] == 'development' else False
1358
1359 dhcp_host = ht.host_thread(name='openvim_controller', user=ovs_controller_user, host=controller_ip,
tierno686b3952017-03-10 13:57:24 +01001360 db=self.db_of,
1361 db_lock=self.db_lock, test=host_test_mode,
mirabalb716ac52017-02-10 14:47:53 +01001362 image_path=self.config['image_path'], version=self.config['version'],
1363 host_id='openvim_controller', develop_mode=host_develop_mode,
tiernof135eff2017-04-19 19:11:53 +02001364 develop_bridge_iface=bridge_ifaces, logger_name=self.logger_name + ".host.controller",
1365 debug=self.config.get('log_level_host'))
mirabalb716ac52017-02-10 14:47:53 +01001366
1367 self.config['host_threads']['openvim_controller'] = dhcp_host
1368 if not host_test_mode:
1369 dhcp_host.ssh_connect()
1370 return dhcp_host
1371
mirabal18f5de32017-02-13 12:41:49 +01001372 def launch_dhcp_server(self, vlan, first_ip, last_ip, cidr, gateway):
mirabalb716ac52017-02-10 14:47:53 +01001373 """
1374 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
1375 :param vlan: vlan identifier
1376 :param first_ip: First dhcp range ip
1377 :param last_ip: Last dhcp range ip
1378 :param cidr: net cidr
mirabale9f6f1a2017-02-16 17:57:35 +01001379 :param gateway: net gateway
mirabalb716ac52017-02-10 14:47:53 +01001380 :return:
1381 """
1382 ip_tools = IPNetwork(cidr)
1383 dhcp_netmask = str(ip_tools.netmask)
1384 ip_range = [first_ip, last_ip]
1385
1386 dhcp_path = self.config['ovs_controller_file_path']
1387
1388 controller_host = self.get_dhcp_controller()
1389 controller_host.create_linux_bridge(vlan)
1390 controller_host.create_dhcp_interfaces(vlan, first_ip, dhcp_netmask)
mirabal18f5de32017-02-13 12:41:49 +01001391 controller_host.launch_dhcp_server(vlan, ip_range, dhcp_netmask, dhcp_path, gateway)
mirabalb716ac52017-02-10 14:47:53 +01001392
mirabald87877c2017-03-31 15:15:52 +02001393if __name__ == "__main__":
1394
1395 parser = argparse.ArgumentParser()
tierno46ca3a92017-04-05 19:49:24 +02001396 parser.add_argument("-v","--version", help="show ovim library version", action="store_true")
1397 parser.add_argument("--database-version", help="show required database version", action="store_true")
mirabald87877c2017-03-31 15:15:52 +02001398 args = parser.parse_args()
1399 if args.version:
1400 print ('openvimd version {} {}'.format(ovim.get_version(), ovim.get_version_date()))
1401 print ('(c) Copyright Telefonica')
tierno46ca3a92017-04-05 19:49:24 +02001402 elif args.database_version:
1403 print ('required database version: {}'.format(ovim.get_database_version()))
mirabalb716ac52017-02-10 14:47:53 +01001404