blob: a1eaffab87e20288e43182dbf12a2ed3c8ebe1ce [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
25'''
26This is the thread for the http server North API.
27Two thread will be launched, with normal and administrative permissions.
28'''
29
30__author__ = "Alfonso Tierno, Leonardo Mirabal"
31__date__ = "$06-Feb-2017 12:07:15$"
tierno46ca3a92017-04-05 19:49:24 +020032__version__ = "0.5.10-r526"
33version_date = "Apr 2017"
34database_version = "0.17" #expected database schema version
tierno57f7bda2017-02-09 12:01:55 +010035
36import threading
37import vim_db
38import logging
tierno57f7bda2017-02-09 12:01:55 +010039import imp
40import host_thread as ht
41import dhcp_thread as dt
42import openflow_thread as oft
mirabal65ba8f82017-02-15 12:36:33 +010043from netaddr import IPNetwork
44from jsonschema import validate as js_v, exceptions as js_e
mirabal6c600652017-03-16 17:22:57 +010045import openflow_conn
mirabald87877c2017-03-31 15:15:52 +020046import argparse
tierno57f7bda2017-02-09 12:01:55 +010047
48HTTP_Bad_Request = 400
49HTTP_Unauthorized = 401
50HTTP_Not_Found = 404
51HTTP_Forbidden = 403
52HTTP_Method_Not_Allowed = 405
53HTTP_Not_Acceptable = 406
54HTTP_Request_Timeout = 408
55HTTP_Conflict = 409
56HTTP_Service_Unavailable = 503
57HTTP_Internal_Server_Error= 500
58
59
60def convert_boolean(data, items):
61 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
62 It assumes that bandwidth is well formed
63 Attributes:
64 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
65 'items': tuple of keys to convert
66 Return:
67 None
68 '''
69 if type(data) is dict:
70 for k in data.keys():
71 if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
72 convert_boolean(data[k], items)
73 if k in items:
74 if type(data[k]) is str:
75 if data[k] == "false":
76 data[k] = False
77 elif data[k] == "true":
78 data[k] = True
79 if type(data) is tuple or type(data) is list:
80 for k in data:
81 if type(k) is dict or type(k) is tuple or type(k) is list:
82 convert_boolean(k, items)
83
84
85
86class ovimException(Exception):
87 def __init__(self, message, http_code=HTTP_Bad_Request):
88 self.http_code = http_code
89 Exception.__init__(self, message)
90
91
92class ovim():
93 running_info = {} #TODO OVIM move the info of running threads from config_dic to this static variable
mirabal580435e2017-03-01 16:17:10 +010094 of_module = {}
95
tierno57f7bda2017-02-09 12:01:55 +010096 def __init__(self, configuration):
97 self.config = configuration
tierno46ca3a92017-04-05 19:49:24 +020098 self.logger_name = configuration.get("logger_name", "openvim")
99 self.logger = logging.getLogger(self.logger_name)
tierno57f7bda2017-02-09 12:01:55 +0100100 self.db = None
tierno46ca3a92017-04-05 19:49:24 +0200101 self.db = self._create_database_connection()
mirabal580435e2017-03-01 16:17:10 +0100102 self.db_lock = None
103 self.db_of = None
104 self.of_test_mode = False
tierno57f7bda2017-02-09 12:01:55 +0100105
106 def _create_database_connection(self):
107 db = vim_db.vim_db((self.config["network_vlan_range_start"], self.config["network_vlan_range_end"]),
tierno46ca3a92017-04-05 19:49:24 +0200108 self.logger_name + ".db", self.config.get('log_level_db'))
tierno57f7bda2017-02-09 12:01:55 +0100109 if db.connect(self.config['db_host'], self.config['db_user'], self.config['db_passwd'],
110 self.config['db_name']) == -1:
111 # self.logger.error("Cannot connect to database %s at %s@%s", self.config['db_name'], self.config['db_user'],
112 # self.config['db_host'])
113 raise ovimException("Cannot connect to database {} at {}@{}".format(self.config['db_name'],
114 self.config['db_user'],
115 self.config['db_host']) )
116 return db
117
mirabale9f6f1a2017-02-16 17:57:35 +0100118 @staticmethod
mirabal50a052f2017-03-27 18:08:07 +0200119 def get_version():
120 return __version__
121
122 @staticmethod
123 def get_version_date():
124 return version_date
125
126 @staticmethod
127 def get_database_version():
128 return database_version
129
130 @staticmethod
mirabale9f6f1a2017-02-16 17:57:35 +0100131 def _check_dhcp_data_integrity(network):
132 """
133 Check if all dhcp parameter for anet are valid, if not will be calculated from cidr value
134 :param network: list with user nets paramters
135 :return:
136 """
137 if "cidr" in network:
138 cidr = network["cidr"]
139 ip_tools = IPNetwork(cidr)
140 cidr_len = ip_tools.prefixlen
141 if cidr_len > 29:
142 return False
143
144 ips = IPNetwork(cidr)
145 if "dhcp_first_ip" not in network:
146 network["dhcp_first_ip"] = str(ips[2])
147 if "dhcp_last_ip" not in network:
148 network["dhcp_last_ip"] = str(ips[-2])
149 if "gateway_ip" not in network:
150 network["gateway_ip"] = str(ips[1])
151
152 return True
153 else:
154 return False
155
156 @staticmethod
157 def _check_valid_uuid(uuid):
158 id_schema = {"type": "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
159 try:
160 js_v(uuid, id_schema)
161 return True
162 except js_e.ValidationError:
163 return False
tierno57f7bda2017-02-09 12:01:55 +0100164
165 def start_service(self):
mirabal580435e2017-03-01 16:17:10 +0100166 """
167 Start ovim services
168 :return:
169 """
tierno2db743b2017-03-28 17:23:15 +0200170 global database_version
mirabal580435e2017-03-01 16:17:10 +0100171 # if self.running_info:
tierno57f7bda2017-02-09 12:01:55 +0100172 # return #TODO service can be checked and rebuild broken threads
173 r = self.db.get_db_version()
mirabal580435e2017-03-01 16:17:10 +0100174 if r[0] < 0:
tierno57f7bda2017-02-09 12:01:55 +0100175 raise ovimException("DATABASE is not a VIM one or it is a '0.0' version. Try to upgrade to version '{}' with "\
tierno2db743b2017-03-28 17:23:15 +0200176 "'./database_utils/migrate_vim_db.sh'".format(database_version) )
177 elif r[1] != database_version:
tierno57f7bda2017-02-09 12:01:55 +0100178 raise ovimException("DATABASE wrong version '{}'. Try to upgrade/downgrade to version '{}' with "\
tierno2db743b2017-03-28 17:23:15 +0200179 "'./database_utils/migrate_vim_db.sh'".format(r[1], database_version) )
tierno46ca3a92017-04-05 19:49:24 +0200180 self.logger.critical("Starting ovim server version: '{} {}' database version '{}'".format(
181 self.get_version(), self.get_version_date(), self.get_database_version()))
tierno57f7bda2017-02-09 12:01:55 +0100182 # create database connection for openflow threads
mirabal580435e2017-03-01 16:17:10 +0100183 self.db_of = self._create_database_connection()
184 self.config["db"] = self.db_of
185 self.db_lock = threading.Lock()
186 self.config["db_lock"] = self.db_lock
tierno57f7bda2017-02-09 12:01:55 +0100187
mirabal580435e2017-03-01 16:17:10 +0100188 self.of_test_mode = False if self.config['mode'] == 'normal' or self.config['mode'] == "OF only" else True
189 # precreate interfaces; [bridge:<host_bridge_name>, VLAN used at Host, uuid of network camping in this bridge,
190 # speed in Gbit/s
191
tierno57f7bda2017-02-09 12:01:55 +0100192 self.config['dhcp_nets'] = []
193 self.config['bridge_nets'] = []
194 for bridge, vlan_speed in self.config["bridge_ifaces"].items():
mirabal580435e2017-03-01 16:17:10 +0100195 # skip 'development_bridge'
tierno57f7bda2017-02-09 12:01:55 +0100196 if self.config['mode'] == 'development' and self.config['development_bridge'] == bridge:
197 continue
198 self.config['bridge_nets'].append([bridge, vlan_speed[0], vlan_speed[1], None])
199
200 # check if this bridge is already used (present at database) for a network)
201 used_bridge_nets = []
202 for brnet in self.config['bridge_nets']:
tierno686b3952017-03-10 13:57:24 +0100203 r, nets = self.db.get_table(SELECT=('uuid',), FROM='nets', WHERE={'provider': "bridge:" + brnet[0]})
tierno57f7bda2017-02-09 12:01:55 +0100204 if r > 0:
205 brnet[3] = nets[0]['uuid']
206 used_bridge_nets.append(brnet[0])
207 if self.config.get("dhcp_server"):
208 if brnet[0] in self.config["dhcp_server"]["bridge_ifaces"]:
209 self.config['dhcp_nets'].append(nets[0]['uuid'])
210 if len(used_bridge_nets) > 0:
211 self.logger.info("found used bridge nets: " + ",".join(used_bridge_nets))
212 # get nets used by dhcp
213 if self.config.get("dhcp_server"):
214 for net in self.config["dhcp_server"].get("nets", ()):
tierno686b3952017-03-10 13:57:24 +0100215 r, nets = self.db.get_table(SELECT=('uuid',), FROM='nets', WHERE={'name': net})
tierno57f7bda2017-02-09 12:01:55 +0100216 if r > 0:
217 self.config['dhcp_nets'].append(nets[0]['uuid'])
218
mirabal580435e2017-03-01 16:17:10 +0100219 # OFC default
220 self._start_ofc_default_task()
tierno57f7bda2017-02-09 12:01:55 +0100221
mirabal580435e2017-03-01 16:17:10 +0100222 # OFC per tenant in DB
223 self._start_of_db_tasks()
tierno57f7bda2017-02-09 12:01:55 +0100224
225 # create dhcp_server thread
226 host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
227 dhcp_params = self.config.get("dhcp_server")
228 if dhcp_params:
229 thread = dt.dhcp_thread(dhcp_params=dhcp_params, test=host_test_mode, dhcp_nets=self.config["dhcp_nets"],
mirabal580435e2017-03-01 16:17:10 +0100230 db=self.db_of, db_lock=self.db_lock, debug=self.config['log_level_of'])
tierno57f7bda2017-02-09 12:01:55 +0100231 thread.start()
232 self.config['dhcp_thread'] = thread
233
234 # Create one thread for each host
235 host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
236 host_develop_mode = True if self.config['mode'] == 'development' else False
237 host_develop_bridge_iface = self.config.get('development_bridge', None)
mirabal580435e2017-03-01 16:17:10 +0100238
239 # get host list from data base before starting threads
tierno686b3952017-03-10 13:57:24 +0100240 r, hosts = self.db.get_table(SELECT=('name', 'ip_name', 'user', 'uuid'), FROM='hosts', WHERE={'status': 'ok'})
mirabal580435e2017-03-01 16:17:10 +0100241 if r < 0:
242 raise ovimException("Cannot get hosts from database {}".format(hosts))
243
tierno57f7bda2017-02-09 12:01:55 +0100244 self.config['host_threads'] = {}
245 for host in hosts:
246 host['image_path'] = '/opt/VNF/images/openvim'
mirabal580435e2017-03-01 16:17:10 +0100247 thread = ht.host_thread(name=host['name'], user=host['user'], host=host['ip_name'], db=self.db_of,
248 db_lock=self.db_lock, test=host_test_mode, image_path=self.config['image_path'],
249 version=self.config['version'], host_id=host['uuid'], develop_mode=host_develop_mode,
tierno57f7bda2017-02-09 12:01:55 +0100250 develop_bridge_iface=host_develop_bridge_iface)
251 thread.start()
252 self.config['host_threads'][host['uuid']] = thread
253
mirabalb716ac52017-02-10 14:47:53 +0100254 # create ovs dhcp thread
255 result, content = self.db.get_table(FROM='nets')
256 if result < 0:
257 self.logger.error("http_get_ports Error %d %s", result, content)
258 raise ovimException(str(content), -result)
259
260 for net in content:
261 net_type = net['type']
mirabale9f6f1a2017-02-16 17:57:35 +0100262 if (net_type == 'bridge_data' or net_type == 'bridge_man') \
263 and net["provider"][:4] == 'OVS:' and net["enable_dhcp"] == "true":
mirabal65ba8f82017-02-15 12:36:33 +0100264 self.launch_dhcp_server(net['vlan'],
265 net['dhcp_first_ip'],
266 net['dhcp_last_ip'],
267 net['cidr'],
268 net['gateway_ip'])
mirabalb716ac52017-02-10 14:47:53 +0100269
mirabal580435e2017-03-01 16:17:10 +0100270 def _start_of_db_tasks(self):
271 """
272 Start ofc task for existing ofcs in database
273 :param db_of:
274 :param db_lock:
275 :return:
276 """
277 ofcs = self.get_of_controllers()
278
279 for ofc in ofcs:
280 of_conn = self._load_of_module(ofc)
281 # create ofc thread per of controller
282 self._create_ofc_task(ofc['uuid'], ofc['dpid'], of_conn)
283
284 def _create_ofc_task(self, ofc_uuid, dpid, of_conn):
285 """
286 Create an ofc thread for handle each sdn controllers
287 :param ofc_uuid: sdn controller uuid
288 :param dpid: sdn controller dpid
289 :param of_conn: OF_conn module
290 :return:
291 """
292 if 'ofcs_thread' not in self.config and 'ofcs_thread_dpid' not in self.config:
293 ofcs_threads = {}
294 ofcs_thread_dpid = []
295 else:
296 ofcs_threads = self.config['ofcs_thread']
297 ofcs_thread_dpid = self.config['ofcs_thread_dpid']
298
299 if ofc_uuid not in ofcs_threads:
300 ofc_thread = self._create_ofc_thread(of_conn, ofc_uuid)
301 if ofc_uuid == "Default":
302 self.config['of_thread'] = ofc_thread
303
304 ofcs_threads[ofc_uuid] = ofc_thread
305 self.config['ofcs_thread'] = ofcs_threads
306
307 ofcs_thread_dpid.append({dpid: ofc_thread})
308 self.config['ofcs_thread_dpid'] = ofcs_thread_dpid
309
310 def _start_ofc_default_task(self):
311 """
312 Create default ofc thread
313 """
314 if 'of_controller' not in self.config \
315 and 'of_controller_ip' not in self.config \
316 and 'of_controller_port' not in self.config \
317 and 'of_controller_dpid' not in self.config:
318 return
319
320 # OF THREAD
321 db_config = {}
322 db_config['ip'] = self.config.get('of_controller_ip')
323 db_config['port'] = self.config.get('of_controller_port')
324 db_config['dpid'] = self.config.get('of_controller_dpid')
325 db_config['type'] = self.config.get('of_controller')
326 db_config['user'] = self.config.get('of_user')
327 db_config['password'] = self.config.get('of_password')
328
329 # create connector to the openflow controller
330 # load other parameters starting by of_ from config dict in a temporal dict
331
332 of_conn = self._load_of_module(db_config)
333 # create openflow thread
334 self._create_ofc_task("Default", db_config['dpid'], of_conn)
335
336 def _load_of_module(self, db_config):
337 """
338 import python module for each SDN controller supported
mirabalf9a1a8d2017-03-15 12:42:27 +0100339 :param db_config: SDN dn information
mirabal580435e2017-03-01 16:17:10 +0100340 :return: Module
341 """
342 if not db_config:
343 raise ovimException("No module found it", HTTP_Internal_Server_Error)
344
345 module_info = None
346
347 try:
348 if self.of_test_mode:
mirabal6c600652017-03-16 17:22:57 +0100349 return openflow_conn.OfTestConnector({"name": db_config['type'],
350 "dpid": db_config['dpid'],
351 "of_debug": self.config['log_level_of']})
mirabal580435e2017-03-01 16:17:10 +0100352 temp_dict = {}
353
354 if db_config:
355 temp_dict['of_ip'] = db_config['ip']
356 temp_dict['of_port'] = db_config['port']
357 temp_dict['of_dpid'] = db_config['dpid']
358 temp_dict['of_controller'] = db_config['type']
montesmorenoba1862b2017-04-06 10:07:44 +0000359 temp_dict['of_user'] = db_config.get('user')
360 temp_dict['of_password'] = db_config.get('password')
mirabal580435e2017-03-01 16:17:10 +0100361
362 temp_dict['of_debug'] = self.config['log_level_of']
363
364 if temp_dict['of_controller'] == 'opendaylight':
365 module = "ODL"
366 else:
367 module = temp_dict['of_controller']
368
369 if module not in ovim.of_module:
370 module_info = imp.find_module(module)
371 of_conn_module = imp.load_module("OF_conn", *module_info)
372 ovim.of_module[module] = of_conn_module
373 else:
374 of_conn_module = ovim.of_module[module]
375
376 try:
377 return of_conn_module.OF_conn(temp_dict)
378 except Exception as e:
379 self.logger.error("Cannot open the Openflow controller '%s': %s", type(e).__name__, str(e))
380 if module_info and module_info[0]:
381 file.close(module_info[0])
382 raise ovimException("Cannot open the Openflow controller '{}': '{}'".format(type(e).__name__, str(e)),
383 HTTP_Internal_Server_Error)
384 except (IOError, ImportError) as e:
385 if module_info and module_info[0]:
386 file.close(module_info[0])
387 self.logger.error("Cannot open openflow controller module '%s'; %s: %s; revise 'of_controller' "
388 "field of configuration file.", module, type(e).__name__, str(e))
389 raise ovimException("Cannot open openflow controller module '{}'; {}: {}; revise 'of_controller' "
390 "field of configuration file.".format(module, type(e).__name__, str(e)),
391 HTTP_Internal_Server_Error)
392
393 def _create_ofc_thread(self, of_conn, ofc_uuid="Default"):
394 """
395 Create and launch a of thread
396 :return: thread obj
397 """
398 # create openflow thread
399
montesmoreno92827552017-03-30 13:24:17 +0200400 #if 'of_controller_nets_with_same_vlan' in self.config:
401 # ofc_net_same_vlan = self.config['of_controller_nets_with_same_vlan']
402 #else:
403 # ofc_net_same_vlan = False
404 ofc_net_same_vlan = False
mirabal580435e2017-03-01 16:17:10 +0100405
406 thread = oft.openflow_thread(ofc_uuid, of_conn, of_test=self.of_test_mode, db=self.db_of, db_lock=self.db_lock,
407 pmp_with_same_vlan=ofc_net_same_vlan, debug=self.config['log_level_of'])
408 #r, c = thread.OF_connector.obtain_port_correspondence()
409 #if r < 0:
410 # raise ovimException("Cannot get openflow information %s", c)
411 thread.start()
412 return thread
413
tierno57f7bda2017-02-09 12:01:55 +0100414 def stop_service(self):
415 threads = self.config.get('host_threads', {})
416 if 'of_thread' in self.config:
417 threads['of'] = (self.config['of_thread'])
mirabal580435e2017-03-01 16:17:10 +0100418 if 'ofcs_thread' in self.config:
419 ofcs_thread = self.config['ofcs_thread']
420 for ofc in ofcs_thread:
421 threads[ofc] = ofcs_thread[ofc]
422
tierno57f7bda2017-02-09 12:01:55 +0100423 if 'dhcp_thread' in self.config:
424 threads['dhcp'] = (self.config['dhcp_thread'])
425
426 for thread in threads.values():
427 thread.insert_task("exit")
428 for thread in threads.values():
429 thread.join()
tierno57f7bda2017-02-09 12:01:55 +0100430
mirabal9e194592017-02-17 11:03:25 +0100431 def get_networks(self, columns=None, db_filter={}, limit=None):
mirabal65ba8f82017-02-15 12:36:33 +0100432 """
433 Retreive networks available
mirabale9f6f1a2017-02-16 17:57:35 +0100434 :param columns: List with select query parameters
mirabal9e194592017-02-17 11:03:25 +0100435 :param db_filter: List with where query parameters
mirabale9f6f1a2017-02-16 17:57:35 +0100436 :param limit: Query limit result
mirabal65ba8f82017-02-15 12:36:33 +0100437 :return:
438 """
mirabal9e194592017-02-17 11:03:25 +0100439 result, content = self.db.get_table(SELECT=columns, FROM='nets', WHERE=db_filter, LIMIT=limit)
mirabal65ba8f82017-02-15 12:36:33 +0100440
441 if result < 0:
442 raise ovimException(str(content), -result)
443
444 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
445
446 return content
447
mirabal9e194592017-02-17 11:03:25 +0100448 def show_network(self, network_id, db_filter={}):
mirabal65ba8f82017-02-15 12:36:33 +0100449 """
mirabale9f6f1a2017-02-16 17:57:35 +0100450 Get network from DB by id
451 :param network_id: net Id
mirabal9e194592017-02-17 11:03:25 +0100452 :param db_filter: List with where query parameters
mirabal65ba8f82017-02-15 12:36:33 +0100453 :return:
454 """
455 # obtain data
mirabale9f6f1a2017-02-16 17:57:35 +0100456 if not network_id:
457 raise ovimException("Not network id was not found")
mirabal9e194592017-02-17 11:03:25 +0100458 db_filter['uuid'] = network_id
mirabal65ba8f82017-02-15 12:36:33 +0100459
mirabal9e194592017-02-17 11:03:25 +0100460 result, content = self.db.get_table(FROM='nets', WHERE=db_filter, LIMIT=100)
mirabal65ba8f82017-02-15 12:36:33 +0100461
462 if result < 0:
463 raise ovimException(str(content), -result)
464 elif result == 0:
mirabale9f6f1a2017-02-16 17:57:35 +0100465 raise ovimException("show_network network '%s' not found" % network_id, -result)
mirabal65ba8f82017-02-15 12:36:33 +0100466 else:
467 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
mirabale9f6f1a2017-02-16 17:57:35 +0100468 # get ports from DB
mirabal65ba8f82017-02-15 12:36:33 +0100469 result, ports = self.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
mirabale9f6f1a2017-02-16 17:57:35 +0100470 WHERE={'net_id': network_id}, LIMIT=100)
mirabal65ba8f82017-02-15 12:36:33 +0100471 if len(ports) > 0:
472 content[0]['ports'] = ports
mirabal65ba8f82017-02-15 12:36:33 +0100473
mirabale9f6f1a2017-02-16 17:57:35 +0100474 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
475 return content[0]
476
477 def new_network(self, network):
mirabal65ba8f82017-02-15 12:36:33 +0100478 """
mirabale9f6f1a2017-02-16 17:57:35 +0100479 Create a net in DB
mirabal65ba8f82017-02-15 12:36:33 +0100480 :return:
481 """
482 tenant_id = network.get('tenant_id')
483
484 if tenant_id:
485 result, _ = self.db.get_table(FROM='tenants', SELECT=('uuid',), WHERE={'uuid': tenant_id, "enabled": True})
486 if result <= 0:
487 raise ovimException("set_network error, no tenant founded", -result)
488
489 bridge_net = None
490 # check valid params
491 net_provider = network.get('provider')
492 net_type = network.get('type')
mirabal65ba8f82017-02-15 12:36:33 +0100493 net_vlan = network.get("vlan")
494 net_bind_net = network.get("bind_net")
495 net_bind_type = network.get("bind_type")
496 name = network["name"]
497
498 # check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
499 vlan_index = name.rfind(":")
500 if not net_bind_net and not net_bind_type and vlan_index > 1:
501 try:
502 vlan_tag = int(name[vlan_index + 1:])
503 if not vlan_tag and vlan_tag < 4096:
504 net_bind_net = name[:vlan_index]
505 net_bind_type = "vlan:" + name[vlan_index + 1:]
506 except:
507 pass
508
509 if net_bind_net:
510 # look for a valid net
511 if self._check_valid_uuid(net_bind_net):
512 net_bind_key = "uuid"
513 else:
514 net_bind_key = "name"
515 result, content = self.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net})
516 if result < 0:
517 raise ovimException(' getting nets from db ' + content, HTTP_Internal_Server_Error)
518 elif result == 0:
519 raise ovimException(" bind_net %s '%s'not found" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
520 elif result > 1:
521 raise ovimException(" more than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
522 network["bind_net"] = content[0]["uuid"]
523
524 if net_bind_type:
525 if net_bind_type[0:5] != "vlan:":
526 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>'", HTTP_Bad_Request)
527 if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:]) <= 0:
528 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095",
529 HTTP_Bad_Request)
530 network["bind_type"] = net_bind_type
531
532 if net_provider:
533 if net_provider[:9] == "openflow:":
534 if net_type:
535 if net_type != "ptp" and net_type != "data":
536 raise ovimException(" only 'ptp' or 'data' net types can be bound to 'openflow'",
537 HTTP_Bad_Request)
538 else:
539 net_type = 'data'
540 else:
541 if net_type:
542 if net_type != "bridge_man" and net_type != "bridge_data":
543 raise ovimException("Only 'bridge_man' or 'bridge_data' net types can be bound "
544 "to 'bridge', 'macvtap' or 'default", HTTP_Bad_Request)
545 else:
546 net_type = 'bridge_man'
547
548 if not net_type:
549 net_type = 'bridge_man'
550
551 if net_provider:
552 if net_provider[:7] == 'bridge:':
553 # check it is one of the pre-provisioned bridges
554 bridge_net_name = net_provider[7:]
555 for brnet in self.config['bridge_nets']:
556 if brnet[0] == bridge_net_name: # free
557 if not brnet[3]:
558 raise ovimException("invalid 'provider:physical', "
559 "bridge '%s' is already used" % bridge_net_name, HTTP_Conflict)
560 bridge_net = brnet
561 net_vlan = brnet[1]
562 break
mirabale9f6f1a2017-02-16 17:57:35 +0100563 # if bridge_net==None:
564 # bottle.abort(HTTP_Bad_Request, "invalid 'provider:physical', bridge '%s' is not one of the
565 # provisioned 'bridge_ifaces' in the configuration file" % bridge_net_name)
566 # return
567
mirabal65ba8f82017-02-15 12:36:33 +0100568 elif self.config['network_type'] == 'bridge' and (net_type == 'bridge_data' or net_type == 'bridge_man'):
569 # look for a free precreated nets
570 for brnet in self.config['bridge_nets']:
571 if not brnet[3]: # free
572 if not bridge_net:
573 if net_type == 'bridge_man': # look for the smaller speed
574 if brnet[2] < bridge_net[2]:
575 bridge_net = brnet
576 else: # look for the larger speed
577 if brnet[2] > bridge_net[2]:
578 bridge_net = brnet
579 else:
580 bridge_net = brnet
581 net_vlan = brnet[1]
582 if not bridge_net:
583 raise ovimException("Max limits of bridge networks reached. Future versions of VIM "
584 "will overcome this limit", HTTP_Bad_Request)
585 else:
mirabale9f6f1a2017-02-16 17:57:35 +0100586 self.logger.debug("using net " + bridge_net)
mirabal65ba8f82017-02-15 12:36:33 +0100587 net_provider = "bridge:" + bridge_net[0]
588 net_vlan = bridge_net[1]
589 elif net_type == 'bridge_data' or net_type == 'bridge_man' and self.config['network_type'] == 'ovs':
590 net_provider = 'OVS'
591 if not net_vlan and (net_type == "data" or net_type == "ptp" or net_provider == "OVS"):
592 net_vlan = self.db.get_free_net_vlan()
593 if net_vlan < 0:
594 raise ovimException("Error getting an available vlan", HTTP_Internal_Server_Error)
595 if net_provider == 'OVS':
596 net_provider = 'OVS' + ":" + str(net_vlan)
597
598 network['provider'] = net_provider
599 network['type'] = net_type
600 network['vlan'] = net_vlan
601 dhcp_integrity = True
602 if 'enable_dhcp' in network and network['enable_dhcp']:
603 dhcp_integrity = self._check_dhcp_data_integrity(network)
604
605 result, content = self.db.new_row('nets', network, True, True)
606
607 if result >= 0 and dhcp_integrity:
608 if bridge_net:
609 bridge_net[3] = content
610 if self.config.get("dhcp_server") and self.config['network_type'] == 'bridge':
611 if network["name"] in self.config["dhcp_server"].get("nets", ()):
612 self.config["dhcp_nets"].append(content)
mirabale9f6f1a2017-02-16 17:57:35 +0100613 self.logger.debug("dhcp_server: add new net", content)
mirabal65ba8f82017-02-15 12:36:33 +0100614 elif not bridge_net and bridge_net[0] in self.config["dhcp_server"].get("bridge_ifaces", ()):
615 self.config["dhcp_nets"].append(content)
mirabale9f6f1a2017-02-16 17:57:35 +0100616 self.logger.debug("dhcp_server: add new net", content, content)
mirabal65ba8f82017-02-15 12:36:33 +0100617 return content
618 else:
mirabal65ba8f82017-02-15 12:36:33 +0100619 raise ovimException("Error posting network", HTTP_Internal_Server_Error)
mirabale9f6f1a2017-02-16 17:57:35 +0100620# TODO kei change update->edit
mirabal65ba8f82017-02-15 12:36:33 +0100621
mirabale9f6f1a2017-02-16 17:57:35 +0100622 def edit_network(self, network_id, network):
mirabal65ba8f82017-02-15 12:36:33 +0100623 """
mirabale9f6f1a2017-02-16 17:57:35 +0100624 Update entwork data byt id
mirabal65ba8f82017-02-15 12:36:33 +0100625 :return:
626 """
mirabale9f6f1a2017-02-16 17:57:35 +0100627 # Look for the previous data
628 where_ = {'uuid': network_id}
629 result, network_old = self.db.get_table(FROM='nets', WHERE=where_)
630 if result < 0:
631 raise ovimException("Error updating network %s" % network_old, HTTP_Internal_Server_Error)
632 elif result == 0:
633 raise ovimException('network %s not found' % network_id, HTTP_Not_Found)
634 # get ports
635 nbports, content = self.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
636 WHERE={'net_id': network_id}, LIMIT=100)
637 if result < 0:
638 raise ovimException("http_put_network_id error %d %s" % (result, network_old), HTTP_Internal_Server_Error)
639 if nbports > 0:
640 if 'type' in network and network['type'] != network_old[0]['type']:
641 raise ovimException("Can not change type of network while having ports attached",
642 HTTP_Method_Not_Allowed)
643 if 'vlan' in network and network['vlan'] != network_old[0]['vlan']:
644 raise ovimException("Can not change vlan of network while having ports attached",
645 HTTP_Method_Not_Allowed)
mirabal65ba8f82017-02-15 12:36:33 +0100646
mirabale9f6f1a2017-02-16 17:57:35 +0100647 # check valid params
648 net_provider = network.get('provider', network_old[0]['provider'])
649 net_type = network.get('type', network_old[0]['type'])
650 net_bind_net = network.get("bind_net")
651 net_bind_type = network.get("bind_type")
652 if net_bind_net:
653 # look for a valid net
654 if self._check_valid_uuid(net_bind_net):
655 net_bind_key = "uuid"
656 else:
657 net_bind_key = "name"
658 result, content = self.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net})
659 if result < 0:
660 raise ovimException('Getting nets from db ' + content, HTTP_Internal_Server_Error)
661 elif result == 0:
662 raise ovimException("bind_net %s '%s'not found" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
663 elif result > 1:
664 raise ovimException("More than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net),
665 HTTP_Bad_Request)
666 network["bind_net"] = content[0]["uuid"]
667 if net_bind_type:
668 if net_bind_type[0:5] != "vlan:":
669 raise ovimException("Bad format for 'bind_type', must be 'vlan:<tag>'", HTTP_Bad_Request)
670 if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:]) <= 0:
671 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095",
672 HTTP_Bad_Request)
673 if net_provider:
674 if net_provider[:9] == "openflow:":
675 if net_type != "ptp" and net_type != "data":
676 raise ovimException("Only 'ptp' or 'data' net types can be bound to 'openflow'", HTTP_Bad_Request)
677 else:
678 if net_type != "bridge_man" and net_type != "bridge_data":
679 raise ovimException("Only 'bridge_man' or 'bridge_data' net types can be bound to "
680 "'bridge', 'macvtap' or 'default", HTTP_Bad_Request)
mirabal65ba8f82017-02-15 12:36:33 +0100681
mirabale9f6f1a2017-02-16 17:57:35 +0100682 # insert in data base
683 result, content = self.db.update_rows('nets', network, WHERE={'uuid': network_id}, log=True)
684 if result >= 0:
685 # if result > 0 and nbports>0 and 'admin_state_up' in network
686 # and network['admin_state_up'] != network_old[0]['admin_state_up']:
687 if result > 0:
mirabal7bbf50e2017-03-13 15:15:18 +0100688
689 try:
tiernoaa941462017-03-29 15:10:28 +0200690 if nbports:
691 self.net_update_ofc_thread(network_id)
mirabal7bbf50e2017-03-13 15:15:18 +0100692 except ovimException as e:
693 raise ovimException("Error while launching openflow rules in network '{}' {}"
694 .format(network_id, str(e)), HTTP_Internal_Server_Error)
695 except Exception as e:
696 raise ovimException("Error while launching openflow rules in network '{}' {}"
697 .format(network_id, str(e)), HTTP_Internal_Server_Error)
698
mirabale9f6f1a2017-02-16 17:57:35 +0100699 if self.config.get("dhcp_server"):
700 if network_id in self.config["dhcp_nets"]:
701 self.config["dhcp_nets"].remove(network_id)
mirabal7bbf50e2017-03-13 15:15:18 +0100702 if network.get("name", network_old[0]["name"]) in self.config["dhcp_server"].get("nets", ()):
mirabale9f6f1a2017-02-16 17:57:35 +0100703 self.config["dhcp_nets"].append(network_id)
704 else:
mirabal7bbf50e2017-03-13 15:15:18 +0100705 net_bind = network.get("bind_type", network_old[0]["bind_type"])
706 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 +0100707 "bridge_ifaces", ()):
708 self.config["dhcp_nets"].append(network_id)
709 return network_id
mirabal65ba8f82017-02-15 12:36:33 +0100710 else:
mirabale9f6f1a2017-02-16 17:57:35 +0100711 raise ovimException(content, -result)
mirabal65ba8f82017-02-15 12:36:33 +0100712
mirabale9f6f1a2017-02-16 17:57:35 +0100713 def delete_network(self, network_id):
mirabal9e194592017-02-17 11:03:25 +0100714 """
715 Delete network by network id
716 :param network_id: network id
717 :return:
718 """
mirabale9f6f1a2017-02-16 17:57:35 +0100719
720 # delete from the data base
721 result, content = self.db.delete_row('nets', network_id)
722
723 if result == 0:
724 raise ovimException("Network %s not found " % network_id, HTTP_Not_Found)
725 elif result > 0:
726 for brnet in self.config['bridge_nets']:
727 if brnet[3] == network_id:
728 brnet[3] = None
729 break
730 if self.config.get("dhcp_server") and network_id in self.config["dhcp_nets"]:
731 self.config["dhcp_nets"].remove(network_id)
732 return content
733 else:
734 raise ovimException("Error deleting network %s" % network_id, HTTP_Internal_Server_Error)
mirabal65ba8f82017-02-15 12:36:33 +0100735
736 def get_openflow_rules(self, network_id=None):
737 """
738 Get openflow id from DB
739 :param network_id: Network id, if none all networks will be retrieved
740 :return: Return a list with Openflow rules per net
741 """
742 # ignore input data
743 if not network_id:
744 where_ = {}
745 else:
746 where_ = {"net_id": network_id}
mirabal65ba8f82017-02-15 12:36:33 +0100747 result, content = self.db.get_table(
mirabalf9a1a8d2017-03-15 12:42:27 +0100748 SELECT=("name", "net_id", "ofc_id", "priority", "vlan_id", "ingress_port", "src_mac", "dst_mac", "actions"),
mirabal65ba8f82017-02-15 12:36:33 +0100749 WHERE=where_, FROM='of_flows')
750
751 if result < 0:
752 raise ovimException(str(content), -result)
753 return content
754
mirabale9f6f1a2017-02-16 17:57:35 +0100755 def edit_openflow_rules(self, network_id=None):
mirabal65ba8f82017-02-15 12:36:33 +0100756
757 """
758 To make actions over the net. The action is to reinstall the openflow rules
759 network_id can be 'all'
760 :param network_id: Network id, if none all networks will be retrieved
761 :return : Number of nets updated
762 """
763
764 # ignore input data
765 if not network_id:
766 where_ = {}
767 else:
768 where_ = {"uuid": network_id}
769 result, content = self.db.get_table(SELECT=("uuid", "type"), WHERE=where_, FROM='nets')
770
771 if result < 0:
772 raise ovimException(str(content), -result)
773
774 for net in content:
775 if net["type"] != "ptp" and net["type"] != "data":
776 result -= 1
777 continue
mirabal7bbf50e2017-03-13 15:15:18 +0100778
779 try:
780 self.net_update_ofc_thread(net['uuid'])
781 except ovimException as e:
782 raise ovimException("Error updating network'{}' {}".format(net['uuid'], str(e)),
783 HTTP_Internal_Server_Error)
784 except Exception as e:
785 raise ovimException("Error updating network '{}' {}".format(net['uuid'], str(e)),
786 HTTP_Internal_Server_Error)
787
mirabal65ba8f82017-02-15 12:36:33 +0100788 return result
789
mirabalf9a1a8d2017-03-15 12:42:27 +0100790 def delete_openflow_rules(self, ofc_id=None):
mirabal65ba8f82017-02-15 12:36:33 +0100791 """
792 To make actions over the net. The action is to delete ALL openflow rules
793 :return: return operation result
794 """
mirabalf9a1a8d2017-03-15 12:42:27 +0100795
796 if not ofc_id:
797 if 'Default' in self.config['ofcs_thread']:
798 r, c = self.config['ofcs_thread']['Default'].insert_task("clear-all")
799 else:
800 raise ovimException("Default Openflow controller not not running", HTTP_Not_Found)
801
802 elif ofc_id in self.config['ofcs_thread']:
803 r, c = self.config['ofcs_thread'][ofc_id].insert_task("clear-all")
804
805 # ignore input data
806 if r < 0:
807 raise ovimException(str(c), -r)
808 else:
809 raise ovimException("Openflow controller not found with ofc_id={}".format(ofc_id), HTTP_Not_Found)
mirabal65ba8f82017-02-15 12:36:33 +0100810 return r
811
mirabalf9a1a8d2017-03-15 12:42:27 +0100812 def get_openflow_ports(self, ofc_id=None):
mirabal65ba8f82017-02-15 12:36:33 +0100813 """
814 Obtain switch ports names of openflow controller
815 :return: Return flow ports in DB
816 """
mirabalf9a1a8d2017-03-15 12:42:27 +0100817 if not ofc_id:
818 if 'Default' in self.config['ofcs_thread']:
819 conn = self.config['ofcs_thread']['Default'].OF_connector
820 else:
821 raise ovimException("Default Openflow controller not not running", HTTP_Not_Found)
822
823 if ofc_id in self.config['ofcs_thread']:
824 conn = self.config['ofcs_thread'][ofc_id].OF_connector
825 else:
826 raise ovimException("Openflow controller not found with ofc_id={}".format(ofc_id), HTTP_Not_Found)
827 return conn.pp2ofi
tierno57f7bda2017-02-09 12:01:55 +0100828
829 def get_ports(self, columns=None, filter={}, limit=None):
830 # result, content = my.db.get_ports(where_)
831 result, content = self.db.get_table(SELECT=columns, WHERE=filter, FROM='ports', LIMIT=limit)
832 if result < 0:
833 self.logger.error("http_get_ports Error %d %s", result, content)
834 raise ovimException(str(content), -result)
835 else:
836 convert_boolean(content, ('admin_state_up',))
837 return content
838
tierno57f7bda2017-02-09 12:01:55 +0100839 def new_port(self, port_data):
840 port_data['type'] = 'external'
841 if port_data.get('net_id'):
842 # check that new net has the correct type
843 result, new_net = self.db.check_target_net(port_data['net_id'], None, 'external')
844 if result < 0:
845 raise ovimException(str(new_net), -result)
846 # insert in data base
847 result, uuid = self.db.new_row('ports', port_data, True, True)
848 if result > 0:
849 if 'net_id' in port_data:
mirabal7bbf50e2017-03-13 15:15:18 +0100850 try:
851 self.net_update_ofc_thread(port_data['net_id'])
852 except ovimException as e:
853 raise ovimException("Cannot insert a task for updating network '{}' {}"
854 .format(port_data['net_id'], str(e)), HTTP_Internal_Server_Error)
855 except Exception as e:
856 raise ovimException("Cannot insert a task for updating network '{}' {}"
857 .format(port_data['net_id'], str(e)), HTTP_Internal_Server_Error)
858
tierno57f7bda2017-02-09 12:01:55 +0100859 return uuid
860 else:
861 raise ovimException(str(uuid), -result)
862
mirabal37829452017-03-09 14:41:21 +0100863 def new_external_port(self, port_data):
864 """
865 Create new external port and check port mapping correspondence
866 :param port_data: port_data = {
867 'region': 'datacenter region',
868 'compute_node': 'compute node id',
869 'pci': 'pci port address',
870 'vlan': 'net vlan',
871 'net_id': 'net id',
872 'tenant_id': 'tenant id',
873 'mac': 'switch mac',
874 'name': 'port name'
875 'ip_address': 'ip address - optional'}
876 :return:
877 """
878
879 port_data['type'] = 'external'
880
881 if port_data.get('net_id'):
882 # check that new net has the correct type
883 result, new_net = self.db.check_target_net(port_data['net_id'], None, 'external')
884 if result < 0:
885 raise ovimException(str(new_net), -result)
886 # insert in data base
887 db_filter = {}
888
889 if port_data.get('region'):
890 db_filter['region'] = port_data['region']
891 if port_data.get('pci'):
892 db_filter['pci'] = port_data['pci']
893 if port_data.get('compute_node'):
894 db_filter['compute_node'] = port_data['compute_node']
895
896 columns = ['ofc_id', 'switch_dpid', 'switch_port', 'switch_mac', 'pci']
897 port_mapping_data = self.get_of_port_mappings(columns, db_filter)
898
899 if not len(port_mapping_data):
tierno16007502017-03-27 16:48:32 +0200900 raise ovimException("No port mapping founded for '{}'".format(str(db_filter)),
mirabal37829452017-03-09 14:41:21 +0100901 HTTP_Not_Found)
902 elif len(port_mapping_data) > 1:
903 raise ovimException("Wrong port data was given, please check pci, region & compute id data",
904 HTTP_Conflict)
905
906 port_data['ofc_id'] = port_mapping_data[0]['ofc_id']
907 port_data['switch_dpid'] = port_mapping_data[0]['switch_dpid']
908 port_data['switch_port'] = port_mapping_data[0]['switch_port']
909 port_data['switch_mac'] = port_mapping_data[0]['switch_mac']
910
911 # remove from compute_node, region and pci of_port_data to adapt to 'ports' structure
montesmoreno275b1992017-03-28 15:45:02 +0200912 if 'region' in port_data:
913 del port_data['region']
914 if 'pci' in port_data:
915 del port_data['pci']
916 if 'compute_node' in port_data:
917 del port_data['compute_node']
mirabal37829452017-03-09 14:41:21 +0100918
919 result, uuid = self.db.new_row('ports', port_data, True, True)
920 if result > 0:
mirabal7bbf50e2017-03-13 15:15:18 +0100921 try:
922 self.net_update_ofc_thread(port_data['net_id'], port_data['ofc_id'])
923 except ovimException as e:
924 raise ovimException("Cannot insert a task for updating network '{}' {}".
925 format(port_data['net_id'], str(e)), HTTP_Internal_Server_Error)
926 except Exception as e:
927 raise ovimException("Cannot insert a task for updating network '{}' {}"
928 .format(port_data['net_id'], e), HTTP_Internal_Server_Error)
mirabal37829452017-03-09 14:41:21 +0100929 return uuid
930 else:
931 raise ovimException(str(uuid), -result)
932
tiernoaa941462017-03-29 15:10:28 +0200933 def net_update_ofc_thread(self, net_id, ofc_id=None, switch_dpid=None):
mirabal7bbf50e2017-03-13 15:15:18 +0100934 """
935 Insert a update net task by net id or ofc_id for each ofc thread
936 :param net_id: network id
937 :param ofc_id: openflow controller id
tiernoaa941462017-03-29 15:10:28 +0200938 :param switch_dpid: switch dpid
mirabal7bbf50e2017-03-13 15:15:18 +0100939 :return:
940 """
941 if not net_id:
942 raise ovimException("No net_id received", HTTP_Internal_Server_Error)
943
mirabal7bbf50e2017-03-13 15:15:18 +0100944 r = -1
945 c = 'No valid ofc_id or switch_dpid received'
946
947 if not ofc_id:
948 ports = self.get_ports(filter={"net_id": net_id})
949 for port in ports:
950 port_ofc_id = port.get('ofc_id', None)
951 if port_ofc_id:
952 ofc_id = port['ofc_id']
953 switch_dpid = port['switch_dpid']
954 break
tiernoaa941462017-03-29 15:10:28 +0200955 #TODO if not ofc_id: look at database table ofcs
956
mirabal7bbf50e2017-03-13 15:15:18 +0100957
958 # If no ofc_id found it, default ofc_id is used.
959 if not ofc_id and not switch_dpid:
960 ofc_id = "Default"
961
962 if ofc_id and ofc_id in self.config['ofcs_thread']:
963 r, c = self.config['ofcs_thread'][ofc_id].insert_task("update-net", net_id)
964 elif switch_dpid:
965
966 ofcs_dpid_list = self.config['ofcs_thread_dpid']
967 for ofc_t in ofcs_dpid_list:
968 if switch_dpid in ofc_t:
969 r, c = ofc_t[switch_dpid].insert_task("update-net", net_id)
970
971 if r < 0:
tierno82232582017-03-15 18:09:16 +0100972 message = "Cannot insert a task for updating network '{}', {}".format(net_id, c)
mirabal7bbf50e2017-03-13 15:15:18 +0100973 self.logger.error(message)
974 raise ovimException(message, HTTP_Internal_Server_Error)
975
tierno57f7bda2017-02-09 12:01:55 +0100976 def delete_port(self, port_id):
977 # Look for the previous port data
978 result, ports = self.db.get_table(WHERE={'uuid': port_id, "type": "external"}, FROM='ports')
979 if result < 0:
980 raise ovimException("Cannot get port info from database: {}".format(ports), http_code=-result)
981 # delete from the data base
982 result, content = self.db.delete_row('ports', port_id)
983 if result == 0:
984 raise ovimException("External port '{}' not found".format(port_id), http_code=HTTP_Not_Found)
985 elif result < 0:
986 raise ovimException("Cannot delete port from database: {}".format(content), http_code=-result)
987 # update network
988 network = ports[0].get('net_id', None)
989 if network:
990 # change of net.
mirabal7bbf50e2017-03-13 15:15:18 +0100991
992 try:
tiernoaa941462017-03-29 15:10:28 +0200993 self.net_update_ofc_thread(network, ofc_id=ports[0]["ofc_id"], switch_dpid=ports[0]["switch_dpid"])
mirabal7bbf50e2017-03-13 15:15:18 +0100994 except ovimException as e:
995 raise ovimException("Cannot insert a task for delete network '{}' {}".format(network, str(e)),
996 HTTP_Internal_Server_Error)
997 except Exception as e:
998 raise ovimException("Cannot insert a task for delete network '{}' {}".format(network, str(e)),
999 HTTP_Internal_Server_Error)
1000
tierno57f7bda2017-02-09 12:01:55 +01001001 return content
1002
tierno57f7bda2017-02-09 12:01:55 +01001003 def edit_port(self, port_id, port_data, admin=True):
1004 # Look for the previous port data
1005 result, content = self.db.get_table(FROM="ports", WHERE={'uuid': port_id})
1006 if result < 0:
1007 raise ovimException("Cannot get port info from database: {}".format(content), http_code=-result)
1008 elif result == 0:
1009 raise ovimException("Port '{}' not found".format(port_id), http_code=HTTP_Not_Found)
1010 port = content[0]
1011 nets = []
1012 host_id = None
1013 result = 1
1014 if 'net_id' in port_data:
1015 # change of net.
1016 old_net = port.get('net_id', None)
1017 new_net = port_data['net_id']
1018 if old_net != new_net:
1019
1020 if new_net:
1021 nets.append(new_net) # put first the new net, so that new openflow rules are created before removing the old ones
1022 if old_net:
1023 nets.append(old_net)
1024 if port['type'] == 'instance:bridge' or port['type'] == 'instance:ovs':
1025 raise ovimException("bridge interfaces cannot be attached to a different net", http_code=HTTP_Forbidden)
1026 elif port['type'] == 'external' and not admin:
1027 raise ovimException("Needed admin privileges",http_code=HTTP_Unauthorized)
1028 if new_net:
1029 # check that new net has the correct type
1030 result, new_net_dict = self.db.check_target_net(new_net, None, port['type'])
1031 if result < 0:
1032 raise ovimException("Error {}".format(new_net_dict), http_code=HTTP_Conflict)
1033 # change VLAN for SR-IOV ports
1034 if result >= 0 and port["type"] == "instance:data" and port["model"] == "VF": # TODO consider also VFnotShared
1035 if new_net:
1036 port_data["vlan"] = None
1037 else:
1038 port_data["vlan"] = new_net_dict["vlan"]
1039 # get host where this VM is allocated
1040 result, content = self.db.get_table(FROM="instances", WHERE={"uuid": port["instance_id"]})
1041 if result > 0:
1042 host_id = content[0]["host_id"]
1043
1044 # insert in data base
1045 if result >= 0:
1046 result, content = self.db.update_rows('ports', port_data, WHERE={'uuid': port_id}, log=False)
tiernoaa941462017-03-29 15:10:28 +02001047 port.update(port_data)
tierno57f7bda2017-02-09 12:01:55 +01001048
1049 # Insert task to complete actions
1050 if result > 0:
1051 for net_id in nets:
mirabal7bbf50e2017-03-13 15:15:18 +01001052 try:
tiernoaa941462017-03-29 15:10:28 +02001053 self.net_update_ofc_thread(net_id, port["ofc_id"], switch_dpid=port["switch_dpid"])
mirabal7bbf50e2017-03-13 15:15:18 +01001054 except ovimException as e:
1055 raise ovimException("Error updating network'{}' {}".format(net_id, str(e)),
1056 HTTP_Internal_Server_Error)
1057 except Exception as e:
1058 raise ovimException("Error updating network '{}' {}".format(net_id, str(e)),
1059 HTTP_Internal_Server_Error)
1060
tierno57f7bda2017-02-09 12:01:55 +01001061 if host_id:
1062 r, v = self.config['host_threads'][host_id].insert_task("edit-iface", port_id, old_net, new_net)
1063 if r < 0:
1064 self.logger.error("Error updating network '{}' {}".format(r,v))
1065 # TODO Do something if fails
1066 if result >= 0:
1067 return port_id
1068 else:
1069 raise ovimException("Error {}".format(content), http_code=-result)
mirabalb716ac52017-02-10 14:47:53 +01001070
mirabal9e194592017-02-17 11:03:25 +01001071 def new_of_controller(self, ofc_data):
1072 """
1073 Create a new openflow controller into DB
1074 :param ofc_data: Dict openflow controller data
1075 :return: openflow controller dpid
1076 """
1077
mirabal580435e2017-03-01 16:17:10 +01001078 result, ofc_uuid = self.db.new_row('ofcs', ofc_data, True, True)
mirabal9e194592017-02-17 11:03:25 +01001079 if result < 0:
mirabal580435e2017-03-01 16:17:10 +01001080 raise ovimException("New ofc Error %s" % ofc_uuid, HTTP_Internal_Server_Error)
1081
1082 ofc_data['uuid'] = ofc_uuid
1083 of_conn = self._load_of_module(ofc_data)
1084 self._create_ofc_task(ofc_uuid, ofc_data['dpid'], of_conn)
1085
1086 return ofc_uuid
mirabal9e194592017-02-17 11:03:25 +01001087
1088 def edit_of_controller(self, of_id, ofc_data):
1089 """
1090 Edit an openflow controller entry from DB
1091 :return:
1092 """
1093 if not ofc_data:
1094 raise ovimException("No data received during uptade OF contorller", http_code=HTTP_Internal_Server_Error)
1095
1096 old_of_controller = self.show_of_controller(of_id)
1097
1098 if old_of_controller:
1099 result, content = self.db.update_rows('ofcs', ofc_data, WHERE={'uuid': of_id}, log=False)
1100 if result >= 0:
1101 return ofc_data
1102 else:
1103 raise ovimException("Error uptating OF contorller with uuid {}".format(of_id),
1104 http_code=-result)
1105 else:
1106 raise ovimException("Error uptating OF contorller with uuid {}".format(of_id),
1107 http_code=HTTP_Internal_Server_Error)
1108
1109 def delete_of_controller(self, of_id):
1110 """
1111 Delete an openflow controller from DB.
1112 :param of_id: openflow controller dpid
1113 :return:
1114 """
1115
mirabal580435e2017-03-01 16:17:10 +01001116 ofc = self.show_of_controller(of_id)
1117
Pablo Montes Moreno5b6f7492017-03-02 16:18:36 +01001118 result, content = self.db.delete_row("ofcs", of_id)
mirabal9e194592017-02-17 11:03:25 +01001119 if result < 0:
1120 raise ovimException("Cannot delete ofc from database: {}".format(content), http_code=-result)
1121 elif result == 0:
1122 raise ovimException("ofc {} not found ".format(content), http_code=HTTP_Not_Found)
mirabal580435e2017-03-01 16:17:10 +01001123
1124 ofc_thread = self.config['ofcs_thread'][of_id]
1125 del self.config['ofcs_thread'][of_id]
1126 for ofc_th in self.config['ofcs_thread_dpid']:
1127 if ofc['dpid'] in ofc_th:
1128 self.config['ofcs_thread_dpid'].remove(ofc_th)
1129
1130 ofc_thread.insert_task("exit")
1131 #ofc_thread.join()
1132
mirabal9e194592017-02-17 11:03:25 +01001133 return content
1134
1135 def show_of_controller(self, uuid):
1136 """
1137 Show an openflow controller by dpid from DB.
1138 :param db_filter: List with where query parameters
1139 :return:
1140 """
1141
1142 result, content = self.db.get_table(FROM='ofcs', WHERE={"uuid": uuid}, LIMIT=100)
1143
1144 if result == 0:
1145 raise ovimException("Openflow controller with uuid '{}' not found".format(uuid),
1146 http_code=HTTP_Not_Found)
1147 elif result < 0:
1148 raise ovimException("Openflow controller with uuid '{}' error".format(uuid),
1149 http_code=HTTP_Internal_Server_Error)
Pablo Montes Moreno5b6f7492017-03-02 16:18:36 +01001150 return content[0]
mirabal9e194592017-02-17 11:03:25 +01001151
mirabalfbfb7972017-02-27 17:36:17 +01001152 def get_of_controllers(self, columns=None, db_filter={}, limit=None):
mirabal9e194592017-02-17 11:03:25 +01001153 """
1154 Show an openflow controllers from DB.
1155 :param columns: List with SELECT query parameters
1156 :param db_filter: List with where query parameters
mirabalfbfb7972017-02-27 17:36:17 +01001157 :param limit: result Limit
mirabal9e194592017-02-17 11:03:25 +01001158 :return:
1159 """
mirabalfbfb7972017-02-27 17:36:17 +01001160 result, content = self.db.get_table(SELECT=columns, FROM='ofcs', WHERE=db_filter, LIMIT=limit)
mirabal9e194592017-02-17 11:03:25 +01001161
1162 if result < 0:
1163 raise ovimException(str(content), -result)
1164
1165 return content
1166
mirabalfbfb7972017-02-27 17:36:17 +01001167 def get_tenants(self, columns=None, db_filter={}, limit=None):
1168 """
1169 Retrieve tenant list from DB
1170 :param columns: List with SELECT query parameters
1171 :param db_filter: List with where query parameters
1172 :param limit: result limit
1173 :return:
1174 """
1175 result, content = self.db.get_table(FROM='tenants', SELECT=columns, WHERE=db_filter, LIMIT=limit)
1176 if result < 0:
1177 raise ovimException('get_tenatns Error {}'.format(str(content)), -result)
1178 else:
1179 convert_boolean(content, ('enabled',))
1180 return content
1181
1182 def show_tenant_id(self, tenant_id):
1183 """
1184 Get tenant from DB by id
1185 :param tenant_id: tenant id
1186 :return:
1187 """
1188 result, content = self.db.get_table(FROM='tenants', SELECT=('uuid', 'name', 'description', 'enabled'),
1189 WHERE={"uuid": tenant_id})
1190 if result < 0:
1191 raise ovimException(str(content), -result)
1192 elif result == 0:
1193 raise ovimException("tenant with uuid='{}' not found".format(tenant_id), HTTP_Not_Found)
1194 else:
1195 convert_boolean(content, ('enabled',))
1196 return content[0]
1197
1198 def new_tentant(self, tenant):
1199 """
1200 Create a tenant and store in DB
1201 :param tenant: Dictionary with tenant data
1202 :return: the uuid of created tenant. Raise exception upon error
1203 """
1204
1205 # insert in data base
1206 result, tenant_uuid = self.db.new_tenant(tenant)
1207
1208 if result >= 0:
1209 return tenant_uuid
1210 else:
1211 raise ovimException(str(tenant_uuid), -result)
1212
1213 def delete_tentant(self, tenant_id):
1214 """
1215 Delete a tenant from the database.
1216 :param tenant_id: Tenant id
1217 :return: delete tenant id
1218 """
1219
1220 # check permissions
1221 r, tenants_flavors = self.db.get_table(FROM='tenants_flavors', SELECT=('flavor_id', 'tenant_id'),
1222 WHERE={'tenant_id': tenant_id})
1223 if r <= 0:
1224 tenants_flavors = ()
1225 r, tenants_images = self.db.get_table(FROM='tenants_images', SELECT=('image_id', 'tenant_id'),
1226 WHERE={'tenant_id': tenant_id})
1227 if r <= 0:
1228 tenants_images = ()
1229
1230 result, content = self.db.delete_row('tenants', tenant_id)
1231 if result == 0:
1232 raise ovimException("tenant '%s' not found" % tenant_id, HTTP_Not_Found)
1233 elif result > 0:
1234 for flavor in tenants_flavors:
1235 self.db.delete_row_by_key("flavors", "uuid", flavor['flavor_id'])
1236 for image in tenants_images:
1237 self.db.delete_row_by_key("images", "uuid", image['image_id'])
1238 return content
1239 else:
1240 raise ovimException("Error deleting tenant '%s' " % tenant_id, HTTP_Internal_Server_Error)
1241
1242 def edit_tenant(self, tenant_id, tenant_data):
1243 """
1244 Update a tenant data identified by tenant id
1245 :param tenant_id: tenant id
1246 :param tenant_data: Dictionary with tenant data
1247 :return:
1248 """
1249
1250 # Look for the previous data
1251 result, tenant_data_old = self.db.get_table(FROM='tenants', WHERE={'uuid': tenant_id})
1252 if result < 0:
1253 raise ovimException("Error updating tenant with uuid='{}': {}".format(tenant_id, tenant_data_old),
1254 HTTP_Internal_Server_Error)
1255 elif result == 0:
1256 raise ovimException("tenant with uuid='{}' not found".format(tenant_id), HTTP_Not_Found)
1257
1258 # insert in data base
1259 result, content = self.db.update_rows('tenants', tenant_data, WHERE={'uuid': tenant_id}, log=True)
1260 if result >= 0:
1261 return content
1262 else:
1263 raise ovimException(str(content), -result)
1264
mirabal6045a9d2017-03-06 11:36:55 +01001265 def set_of_port_mapping(self, of_maps, ofc_id=None, switch_dpid=None, region=None):
1266 """
1267 Create new port mapping entry
1268 :param of_maps: List with port mapping information
1269 # maps =[{"ofc_id": <ofc_id>,"region": datacenter region,"compute_node": compute uuid,"pci": pci adress,
1270 "switch_dpid": swith dpid,"switch_port": port name,"switch_mac": mac}]
1271 :param ofc_id: ofc id
1272 :param switch_dpid: switch dpid
1273 :param region: datacenter region id
1274 :return:
1275 """
1276
1277 for map in of_maps:
1278 if ofc_id:
1279 map['ofc_id'] = ofc_id
1280 if switch_dpid:
1281 map['switch_dpid'] = switch_dpid
1282 if region:
1283 map['region'] = region
1284
1285 for of_map in of_maps:
1286 result, uuid = self.db.new_row('of_port_mappings', of_map, True)
1287 if result > 0:
1288 of_map["uuid"] = uuid
1289 else:
1290 raise ovimException(str(uuid), -result)
1291 return of_maps
1292
1293 def clear_of_port_mapping(self, db_filter={}):
1294 """
1295 Clear port mapping filtering using db_filter dict
1296 :param db_filter: Parameter to filter during remove process
1297 :return:
1298 """
1299 result, content = self.db.delete_row_by_dict(FROM='of_port_mappings', WHERE=db_filter)
1300 # delete_row_by_key
1301 if result >= 0:
1302 return content
1303 else:
1304 raise ovimException("Error deleting of_port_mappings with filter='{}'".format(str(db_filter)),
1305 HTTP_Internal_Server_Error)
1306
1307 def get_of_port_mappings(self, column=None, db_filter=None, db_limit=None):
1308 """
1309 Retrive port mapping from DB
1310 :param column:
1311 :param db_filter:
1312 :return:
1313 """
1314 result, content = self.db.get_table(SELECT=column, WHERE=db_filter, FROM='of_port_mappings', LIMIT=db_limit)
1315
1316 if result < 0:
1317 self.logger.error("get_of_port_mappings Error %d %s", result, content)
1318 raise ovimException(str(content), -result)
1319 else:
1320 return content
1321
mirabalb716ac52017-02-10 14:47:53 +01001322 def get_dhcp_controller(self):
1323 """
1324 Create an host_thread object for manage openvim controller and not create a thread for itself
1325 :return: dhcp_host openvim controller object
1326 """
1327
1328 if 'openvim_controller' in self.config['host_threads']:
1329 return self.config['host_threads']['openvim_controller']
1330
1331 bridge_ifaces = []
1332 controller_ip = self.config['ovs_controller_ip']
1333 ovs_controller_user = self.config['ovs_controller_user']
1334
1335 host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
1336 host_develop_mode = True if self.config['mode'] == 'development' else False
1337
1338 dhcp_host = ht.host_thread(name='openvim_controller', user=ovs_controller_user, host=controller_ip,
tierno686b3952017-03-10 13:57:24 +01001339 db=self.db_of,
1340 db_lock=self.db_lock, test=host_test_mode,
mirabalb716ac52017-02-10 14:47:53 +01001341 image_path=self.config['image_path'], version=self.config['version'],
1342 host_id='openvim_controller', develop_mode=host_develop_mode,
1343 develop_bridge_iface=bridge_ifaces)
1344
1345 self.config['host_threads']['openvim_controller'] = dhcp_host
1346 if not host_test_mode:
1347 dhcp_host.ssh_connect()
1348 return dhcp_host
1349
mirabal18f5de32017-02-13 12:41:49 +01001350 def launch_dhcp_server(self, vlan, first_ip, last_ip, cidr, gateway):
mirabalb716ac52017-02-10 14:47:53 +01001351 """
1352 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
1353 :param vlan: vlan identifier
1354 :param first_ip: First dhcp range ip
1355 :param last_ip: Last dhcp range ip
1356 :param cidr: net cidr
mirabale9f6f1a2017-02-16 17:57:35 +01001357 :param gateway: net gateway
mirabalb716ac52017-02-10 14:47:53 +01001358 :return:
1359 """
1360 ip_tools = IPNetwork(cidr)
1361 dhcp_netmask = str(ip_tools.netmask)
1362 ip_range = [first_ip, last_ip]
1363
1364 dhcp_path = self.config['ovs_controller_file_path']
1365
1366 controller_host = self.get_dhcp_controller()
1367 controller_host.create_linux_bridge(vlan)
1368 controller_host.create_dhcp_interfaces(vlan, first_ip, dhcp_netmask)
mirabal18f5de32017-02-13 12:41:49 +01001369 controller_host.launch_dhcp_server(vlan, ip_range, dhcp_netmask, dhcp_path, gateway)
mirabalb716ac52017-02-10 14:47:53 +01001370
mirabald87877c2017-03-31 15:15:52 +02001371if __name__ == "__main__":
1372
1373 parser = argparse.ArgumentParser()
tierno46ca3a92017-04-05 19:49:24 +02001374 parser.add_argument("-v","--version", help="show ovim library version", action="store_true")
1375 parser.add_argument("--database-version", help="show required database version", action="store_true")
mirabald87877c2017-03-31 15:15:52 +02001376 args = parser.parse_args()
1377 if args.version:
1378 print ('openvimd version {} {}'.format(ovim.get_version(), ovim.get_version_date()))
1379 print ('(c) Copyright Telefonica')
tierno46ca3a92017-04-05 19:49:24 +02001380 elif args.database_version:
1381 print ('required database version: {}'.format(ovim.get_database_version()))
mirabalb716ac52017-02-10 14:47:53 +01001382