blob: b369594b2a74f7c6b97c2e18196f831ae7d5be81 [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"],
tierno43044b02017-04-06 18:58:24 +0200230 db=self.db_of, db_lock=self.db_lock, logger_name=self.logger_name + ".dhcp",
231 debug=self.config.get('log_level_of'))
tierno57f7bda2017-02-09 12:01:55 +0100232 thread.start()
233 self.config['dhcp_thread'] = thread
234
235 # Create one thread for each host
236 host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
237 host_develop_mode = True if self.config['mode'] == 'development' else False
238 host_develop_bridge_iface = self.config.get('development_bridge', None)
mirabal580435e2017-03-01 16:17:10 +0100239
240 # get host list from data base before starting threads
tierno686b3952017-03-10 13:57:24 +0100241 r, hosts = self.db.get_table(SELECT=('name', 'ip_name', 'user', 'uuid'), FROM='hosts', WHERE={'status': 'ok'})
mirabal580435e2017-03-01 16:17:10 +0100242 if r < 0:
243 raise ovimException("Cannot get hosts from database {}".format(hosts))
244
tierno57f7bda2017-02-09 12:01:55 +0100245 self.config['host_threads'] = {}
246 for host in hosts:
247 host['image_path'] = '/opt/VNF/images/openvim'
mirabal580435e2017-03-01 16:17:10 +0100248 thread = ht.host_thread(name=host['name'], user=host['user'], host=host['ip_name'], db=self.db_of,
249 db_lock=self.db_lock, test=host_test_mode, image_path=self.config['image_path'],
250 version=self.config['version'], host_id=host['uuid'], develop_mode=host_develop_mode,
tierno57f7bda2017-02-09 12:01:55 +0100251 develop_bridge_iface=host_develop_bridge_iface)
252 thread.start()
253 self.config['host_threads'][host['uuid']] = thread
254
mirabalb716ac52017-02-10 14:47:53 +0100255 # create ovs dhcp thread
256 result, content = self.db.get_table(FROM='nets')
257 if result < 0:
258 self.logger.error("http_get_ports Error %d %s", result, content)
259 raise ovimException(str(content), -result)
260
261 for net in content:
262 net_type = net['type']
mirabale9f6f1a2017-02-16 17:57:35 +0100263 if (net_type == 'bridge_data' or net_type == 'bridge_man') \
264 and net["provider"][:4] == 'OVS:' and net["enable_dhcp"] == "true":
mirabal65ba8f82017-02-15 12:36:33 +0100265 self.launch_dhcp_server(net['vlan'],
266 net['dhcp_first_ip'],
267 net['dhcp_last_ip'],
268 net['cidr'],
269 net['gateway_ip'])
mirabalb716ac52017-02-10 14:47:53 +0100270
mirabal580435e2017-03-01 16:17:10 +0100271 def _start_of_db_tasks(self):
272 """
273 Start ofc task for existing ofcs in database
274 :param db_of:
275 :param db_lock:
276 :return:
277 """
278 ofcs = self.get_of_controllers()
279
280 for ofc in ofcs:
281 of_conn = self._load_of_module(ofc)
282 # create ofc thread per of controller
283 self._create_ofc_task(ofc['uuid'], ofc['dpid'], of_conn)
284
285 def _create_ofc_task(self, ofc_uuid, dpid, of_conn):
286 """
287 Create an ofc thread for handle each sdn controllers
288 :param ofc_uuid: sdn controller uuid
289 :param dpid: sdn controller dpid
290 :param of_conn: OF_conn module
291 :return:
292 """
293 if 'ofcs_thread' not in self.config and 'ofcs_thread_dpid' not in self.config:
294 ofcs_threads = {}
295 ofcs_thread_dpid = []
296 else:
297 ofcs_threads = self.config['ofcs_thread']
298 ofcs_thread_dpid = self.config['ofcs_thread_dpid']
299
300 if ofc_uuid not in ofcs_threads:
301 ofc_thread = self._create_ofc_thread(of_conn, ofc_uuid)
302 if ofc_uuid == "Default":
303 self.config['of_thread'] = ofc_thread
304
305 ofcs_threads[ofc_uuid] = ofc_thread
306 self.config['ofcs_thread'] = ofcs_threads
307
308 ofcs_thread_dpid.append({dpid: ofc_thread})
309 self.config['ofcs_thread_dpid'] = ofcs_thread_dpid
310
311 def _start_ofc_default_task(self):
312 """
313 Create default ofc thread
314 """
315 if 'of_controller' not in self.config \
316 and 'of_controller_ip' not in self.config \
317 and 'of_controller_port' not in self.config \
318 and 'of_controller_dpid' not in self.config:
319 return
320
321 # OF THREAD
322 db_config = {}
323 db_config['ip'] = self.config.get('of_controller_ip')
324 db_config['port'] = self.config.get('of_controller_port')
325 db_config['dpid'] = self.config.get('of_controller_dpid')
326 db_config['type'] = self.config.get('of_controller')
327 db_config['user'] = self.config.get('of_user')
328 db_config['password'] = self.config.get('of_password')
329
330 # create connector to the openflow controller
331 # load other parameters starting by of_ from config dict in a temporal dict
332
333 of_conn = self._load_of_module(db_config)
334 # create openflow thread
335 self._create_ofc_task("Default", db_config['dpid'], of_conn)
336
337 def _load_of_module(self, db_config):
338 """
339 import python module for each SDN controller supported
mirabalf9a1a8d2017-03-15 12:42:27 +0100340 :param db_config: SDN dn information
mirabal580435e2017-03-01 16:17:10 +0100341 :return: Module
342 """
343 if not db_config:
344 raise ovimException("No module found it", HTTP_Internal_Server_Error)
345
346 module_info = None
347
348 try:
349 if self.of_test_mode:
mirabal6c600652017-03-16 17:22:57 +0100350 return openflow_conn.OfTestConnector({"name": db_config['type'],
351 "dpid": db_config['dpid'],
352 "of_debug": self.config['log_level_of']})
mirabal580435e2017-03-01 16:17:10 +0100353 temp_dict = {}
354
355 if db_config:
356 temp_dict['of_ip'] = db_config['ip']
357 temp_dict['of_port'] = db_config['port']
358 temp_dict['of_dpid'] = db_config['dpid']
359 temp_dict['of_controller'] = db_config['type']
montesmorenoba1862b2017-04-06 10:07:44 +0000360 temp_dict['of_user'] = db_config.get('user')
361 temp_dict['of_password'] = db_config.get('password')
mirabal580435e2017-03-01 16:17:10 +0100362
363 temp_dict['of_debug'] = self.config['log_level_of']
364
365 if temp_dict['of_controller'] == 'opendaylight':
366 module = "ODL"
367 else:
368 module = temp_dict['of_controller']
369
370 if module not in ovim.of_module:
371 module_info = imp.find_module(module)
372 of_conn_module = imp.load_module("OF_conn", *module_info)
373 ovim.of_module[module] = of_conn_module
374 else:
375 of_conn_module = ovim.of_module[module]
376
377 try:
378 return of_conn_module.OF_conn(temp_dict)
379 except Exception as e:
380 self.logger.error("Cannot open the Openflow controller '%s': %s", type(e).__name__, str(e))
381 if module_info and module_info[0]:
382 file.close(module_info[0])
383 raise ovimException("Cannot open the Openflow controller '{}': '{}'".format(type(e).__name__, str(e)),
384 HTTP_Internal_Server_Error)
385 except (IOError, ImportError) as e:
386 if module_info and module_info[0]:
387 file.close(module_info[0])
388 self.logger.error("Cannot open openflow controller module '%s'; %s: %s; revise 'of_controller' "
389 "field of configuration file.", module, type(e).__name__, str(e))
390 raise ovimException("Cannot open openflow controller module '{}'; {}: {}; revise 'of_controller' "
391 "field of configuration file.".format(module, type(e).__name__, str(e)),
392 HTTP_Internal_Server_Error)
393
394 def _create_ofc_thread(self, of_conn, ofc_uuid="Default"):
395 """
396 Create and launch a of thread
397 :return: thread obj
398 """
399 # create openflow thread
400
montesmoreno92827552017-03-30 13:24:17 +0200401 #if 'of_controller_nets_with_same_vlan' in self.config:
402 # ofc_net_same_vlan = self.config['of_controller_nets_with_same_vlan']
403 #else:
404 # ofc_net_same_vlan = False
405 ofc_net_same_vlan = False
mirabal580435e2017-03-01 16:17:10 +0100406
407 thread = oft.openflow_thread(ofc_uuid, of_conn, of_test=self.of_test_mode, db=self.db_of, db_lock=self.db_lock,
408 pmp_with_same_vlan=ofc_net_same_vlan, debug=self.config['log_level_of'])
409 #r, c = thread.OF_connector.obtain_port_correspondence()
410 #if r < 0:
411 # raise ovimException("Cannot get openflow information %s", c)
412 thread.start()
413 return thread
414
tierno57f7bda2017-02-09 12:01:55 +0100415 def stop_service(self):
416 threads = self.config.get('host_threads', {})
417 if 'of_thread' in self.config:
418 threads['of'] = (self.config['of_thread'])
mirabal580435e2017-03-01 16:17:10 +0100419 if 'ofcs_thread' in self.config:
420 ofcs_thread = self.config['ofcs_thread']
421 for ofc in ofcs_thread:
422 threads[ofc] = ofcs_thread[ofc]
423
tierno57f7bda2017-02-09 12:01:55 +0100424 if 'dhcp_thread' in self.config:
425 threads['dhcp'] = (self.config['dhcp_thread'])
426
427 for thread in threads.values():
428 thread.insert_task("exit")
429 for thread in threads.values():
430 thread.join()
tierno57f7bda2017-02-09 12:01:55 +0100431
mirabal9e194592017-02-17 11:03:25 +0100432 def get_networks(self, columns=None, db_filter={}, limit=None):
mirabal65ba8f82017-02-15 12:36:33 +0100433 """
434 Retreive networks available
mirabale9f6f1a2017-02-16 17:57:35 +0100435 :param columns: List with select query parameters
mirabal9e194592017-02-17 11:03:25 +0100436 :param db_filter: List with where query parameters
mirabale9f6f1a2017-02-16 17:57:35 +0100437 :param limit: Query limit result
mirabal65ba8f82017-02-15 12:36:33 +0100438 :return:
439 """
mirabal9e194592017-02-17 11:03:25 +0100440 result, content = self.db.get_table(SELECT=columns, FROM='nets', WHERE=db_filter, LIMIT=limit)
mirabal65ba8f82017-02-15 12:36:33 +0100441
442 if result < 0:
443 raise ovimException(str(content), -result)
444
445 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
446
447 return content
448
mirabal9e194592017-02-17 11:03:25 +0100449 def show_network(self, network_id, db_filter={}):
mirabal65ba8f82017-02-15 12:36:33 +0100450 """
mirabale9f6f1a2017-02-16 17:57:35 +0100451 Get network from DB by id
452 :param network_id: net Id
mirabal9e194592017-02-17 11:03:25 +0100453 :param db_filter: List with where query parameters
mirabal65ba8f82017-02-15 12:36:33 +0100454 :return:
455 """
456 # obtain data
mirabale9f6f1a2017-02-16 17:57:35 +0100457 if not network_id:
458 raise ovimException("Not network id was not found")
mirabal9e194592017-02-17 11:03:25 +0100459 db_filter['uuid'] = network_id
mirabal65ba8f82017-02-15 12:36:33 +0100460
mirabal9e194592017-02-17 11:03:25 +0100461 result, content = self.db.get_table(FROM='nets', WHERE=db_filter, LIMIT=100)
mirabal65ba8f82017-02-15 12:36:33 +0100462
463 if result < 0:
464 raise ovimException(str(content), -result)
465 elif result == 0:
mirabale9f6f1a2017-02-16 17:57:35 +0100466 raise ovimException("show_network network '%s' not found" % network_id, -result)
mirabal65ba8f82017-02-15 12:36:33 +0100467 else:
468 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
mirabale9f6f1a2017-02-16 17:57:35 +0100469 # get ports from DB
mirabal65ba8f82017-02-15 12:36:33 +0100470 result, ports = self.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
mirabale9f6f1a2017-02-16 17:57:35 +0100471 WHERE={'net_id': network_id}, LIMIT=100)
mirabal65ba8f82017-02-15 12:36:33 +0100472 if len(ports) > 0:
473 content[0]['ports'] = ports
mirabal65ba8f82017-02-15 12:36:33 +0100474
mirabale9f6f1a2017-02-16 17:57:35 +0100475 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
476 return content[0]
477
478 def new_network(self, network):
mirabal65ba8f82017-02-15 12:36:33 +0100479 """
mirabale9f6f1a2017-02-16 17:57:35 +0100480 Create a net in DB
mirabal65ba8f82017-02-15 12:36:33 +0100481 :return:
482 """
483 tenant_id = network.get('tenant_id')
484
485 if tenant_id:
486 result, _ = self.db.get_table(FROM='tenants', SELECT=('uuid',), WHERE={'uuid': tenant_id, "enabled": True})
487 if result <= 0:
488 raise ovimException("set_network error, no tenant founded", -result)
489
490 bridge_net = None
491 # check valid params
492 net_provider = network.get('provider')
493 net_type = network.get('type')
mirabal65ba8f82017-02-15 12:36:33 +0100494 net_vlan = network.get("vlan")
495 net_bind_net = network.get("bind_net")
496 net_bind_type = network.get("bind_type")
497 name = network["name"]
498
499 # check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
500 vlan_index = name.rfind(":")
501 if not net_bind_net and not net_bind_type and vlan_index > 1:
502 try:
503 vlan_tag = int(name[vlan_index + 1:])
504 if not vlan_tag and vlan_tag < 4096:
505 net_bind_net = name[:vlan_index]
506 net_bind_type = "vlan:" + name[vlan_index + 1:]
507 except:
508 pass
509
510 if net_bind_net:
511 # look for a valid net
512 if self._check_valid_uuid(net_bind_net):
513 net_bind_key = "uuid"
514 else:
515 net_bind_key = "name"
516 result, content = self.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net})
517 if result < 0:
518 raise ovimException(' getting nets from db ' + content, HTTP_Internal_Server_Error)
519 elif result == 0:
520 raise ovimException(" bind_net %s '%s'not found" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
521 elif result > 1:
522 raise ovimException(" more than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
523 network["bind_net"] = content[0]["uuid"]
524
525 if net_bind_type:
526 if net_bind_type[0:5] != "vlan:":
527 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>'", HTTP_Bad_Request)
528 if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:]) <= 0:
529 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095",
530 HTTP_Bad_Request)
531 network["bind_type"] = net_bind_type
532
533 if net_provider:
534 if net_provider[:9] == "openflow:":
535 if net_type:
536 if net_type != "ptp" and net_type != "data":
537 raise ovimException(" only 'ptp' or 'data' net types can be bound to 'openflow'",
538 HTTP_Bad_Request)
539 else:
540 net_type = 'data'
541 else:
542 if net_type:
543 if net_type != "bridge_man" and net_type != "bridge_data":
544 raise ovimException("Only 'bridge_man' or 'bridge_data' net types can be bound "
545 "to 'bridge', 'macvtap' or 'default", HTTP_Bad_Request)
546 else:
547 net_type = 'bridge_man'
548
549 if not net_type:
550 net_type = 'bridge_man'
551
552 if net_provider:
553 if net_provider[:7] == 'bridge:':
554 # check it is one of the pre-provisioned bridges
555 bridge_net_name = net_provider[7:]
556 for brnet in self.config['bridge_nets']:
557 if brnet[0] == bridge_net_name: # free
558 if not brnet[3]:
559 raise ovimException("invalid 'provider:physical', "
560 "bridge '%s' is already used" % bridge_net_name, HTTP_Conflict)
561 bridge_net = brnet
562 net_vlan = brnet[1]
563 break
mirabale9f6f1a2017-02-16 17:57:35 +0100564 # if bridge_net==None:
565 # bottle.abort(HTTP_Bad_Request, "invalid 'provider:physical', bridge '%s' is not one of the
566 # provisioned 'bridge_ifaces' in the configuration file" % bridge_net_name)
567 # return
568
mirabal65ba8f82017-02-15 12:36:33 +0100569 elif self.config['network_type'] == 'bridge' and (net_type == 'bridge_data' or net_type == 'bridge_man'):
570 # look for a free precreated nets
571 for brnet in self.config['bridge_nets']:
572 if not brnet[3]: # free
573 if not bridge_net:
574 if net_type == 'bridge_man': # look for the smaller speed
575 if brnet[2] < bridge_net[2]:
576 bridge_net = brnet
577 else: # look for the larger speed
578 if brnet[2] > bridge_net[2]:
579 bridge_net = brnet
580 else:
581 bridge_net = brnet
582 net_vlan = brnet[1]
583 if not bridge_net:
584 raise ovimException("Max limits of bridge networks reached. Future versions of VIM "
585 "will overcome this limit", HTTP_Bad_Request)
586 else:
mirabale9f6f1a2017-02-16 17:57:35 +0100587 self.logger.debug("using net " + bridge_net)
mirabal65ba8f82017-02-15 12:36:33 +0100588 net_provider = "bridge:" + bridge_net[0]
589 net_vlan = bridge_net[1]
590 elif net_type == 'bridge_data' or net_type == 'bridge_man' and self.config['network_type'] == 'ovs':
591 net_provider = 'OVS'
592 if not net_vlan and (net_type == "data" or net_type == "ptp" or net_provider == "OVS"):
593 net_vlan = self.db.get_free_net_vlan()
594 if net_vlan < 0:
595 raise ovimException("Error getting an available vlan", HTTP_Internal_Server_Error)
596 if net_provider == 'OVS':
597 net_provider = 'OVS' + ":" + str(net_vlan)
598
599 network['provider'] = net_provider
600 network['type'] = net_type
601 network['vlan'] = net_vlan
602 dhcp_integrity = True
603 if 'enable_dhcp' in network and network['enable_dhcp']:
604 dhcp_integrity = self._check_dhcp_data_integrity(network)
605
606 result, content = self.db.new_row('nets', network, True, True)
607
608 if result >= 0 and dhcp_integrity:
609 if bridge_net:
610 bridge_net[3] = content
611 if self.config.get("dhcp_server") and self.config['network_type'] == 'bridge':
612 if network["name"] in self.config["dhcp_server"].get("nets", ()):
613 self.config["dhcp_nets"].append(content)
mirabale9f6f1a2017-02-16 17:57:35 +0100614 self.logger.debug("dhcp_server: add new net", content)
mirabal65ba8f82017-02-15 12:36:33 +0100615 elif not bridge_net and bridge_net[0] in self.config["dhcp_server"].get("bridge_ifaces", ()):
616 self.config["dhcp_nets"].append(content)
mirabale9f6f1a2017-02-16 17:57:35 +0100617 self.logger.debug("dhcp_server: add new net", content, content)
mirabal65ba8f82017-02-15 12:36:33 +0100618 return content
619 else:
mirabal65ba8f82017-02-15 12:36:33 +0100620 raise ovimException("Error posting network", HTTP_Internal_Server_Error)
mirabale9f6f1a2017-02-16 17:57:35 +0100621# TODO kei change update->edit
mirabal65ba8f82017-02-15 12:36:33 +0100622
mirabale9f6f1a2017-02-16 17:57:35 +0100623 def edit_network(self, network_id, network):
mirabal65ba8f82017-02-15 12:36:33 +0100624 """
mirabale9f6f1a2017-02-16 17:57:35 +0100625 Update entwork data byt id
mirabal65ba8f82017-02-15 12:36:33 +0100626 :return:
627 """
mirabale9f6f1a2017-02-16 17:57:35 +0100628 # Look for the previous data
629 where_ = {'uuid': network_id}
630 result, network_old = self.db.get_table(FROM='nets', WHERE=where_)
631 if result < 0:
632 raise ovimException("Error updating network %s" % network_old, HTTP_Internal_Server_Error)
633 elif result == 0:
634 raise ovimException('network %s not found' % network_id, HTTP_Not_Found)
635 # get ports
636 nbports, content = self.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
637 WHERE={'net_id': network_id}, LIMIT=100)
638 if result < 0:
639 raise ovimException("http_put_network_id error %d %s" % (result, network_old), HTTP_Internal_Server_Error)
640 if nbports > 0:
641 if 'type' in network and network['type'] != network_old[0]['type']:
642 raise ovimException("Can not change type of network while having ports attached",
643 HTTP_Method_Not_Allowed)
644 if 'vlan' in network and network['vlan'] != network_old[0]['vlan']:
645 raise ovimException("Can not change vlan of network while having ports attached",
646 HTTP_Method_Not_Allowed)
mirabal65ba8f82017-02-15 12:36:33 +0100647
mirabale9f6f1a2017-02-16 17:57:35 +0100648 # check valid params
649 net_provider = network.get('provider', network_old[0]['provider'])
650 net_type = network.get('type', network_old[0]['type'])
651 net_bind_net = network.get("bind_net")
652 net_bind_type = network.get("bind_type")
653 if net_bind_net:
654 # look for a valid net
655 if self._check_valid_uuid(net_bind_net):
656 net_bind_key = "uuid"
657 else:
658 net_bind_key = "name"
659 result, content = self.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net})
660 if result < 0:
661 raise ovimException('Getting nets from db ' + content, HTTP_Internal_Server_Error)
662 elif result == 0:
663 raise ovimException("bind_net %s '%s'not found" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
664 elif result > 1:
665 raise ovimException("More than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net),
666 HTTP_Bad_Request)
667 network["bind_net"] = content[0]["uuid"]
668 if net_bind_type:
669 if net_bind_type[0:5] != "vlan:":
670 raise ovimException("Bad format for 'bind_type', must be 'vlan:<tag>'", HTTP_Bad_Request)
671 if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:]) <= 0:
672 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095",
673 HTTP_Bad_Request)
674 if net_provider:
675 if net_provider[:9] == "openflow:":
676 if net_type != "ptp" and net_type != "data":
677 raise ovimException("Only 'ptp' or 'data' net types can be bound to 'openflow'", HTTP_Bad_Request)
678 else:
679 if net_type != "bridge_man" and net_type != "bridge_data":
680 raise ovimException("Only 'bridge_man' or 'bridge_data' net types can be bound to "
681 "'bridge', 'macvtap' or 'default", HTTP_Bad_Request)
mirabal65ba8f82017-02-15 12:36:33 +0100682
mirabale9f6f1a2017-02-16 17:57:35 +0100683 # insert in data base
684 result, content = self.db.update_rows('nets', network, WHERE={'uuid': network_id}, log=True)
685 if result >= 0:
686 # if result > 0 and nbports>0 and 'admin_state_up' in network
687 # and network['admin_state_up'] != network_old[0]['admin_state_up']:
688 if result > 0:
mirabal7bbf50e2017-03-13 15:15:18 +0100689
690 try:
tiernoaa941462017-03-29 15:10:28 +0200691 if nbports:
692 self.net_update_ofc_thread(network_id)
mirabal7bbf50e2017-03-13 15:15:18 +0100693 except ovimException as e:
694 raise ovimException("Error while launching openflow rules in network '{}' {}"
695 .format(network_id, str(e)), HTTP_Internal_Server_Error)
696 except Exception as e:
697 raise ovimException("Error while launching openflow rules in network '{}' {}"
698 .format(network_id, str(e)), HTTP_Internal_Server_Error)
699
mirabale9f6f1a2017-02-16 17:57:35 +0100700 if self.config.get("dhcp_server"):
701 if network_id in self.config["dhcp_nets"]:
702 self.config["dhcp_nets"].remove(network_id)
mirabal7bbf50e2017-03-13 15:15:18 +0100703 if network.get("name", network_old[0]["name"]) in self.config["dhcp_server"].get("nets", ()):
mirabale9f6f1a2017-02-16 17:57:35 +0100704 self.config["dhcp_nets"].append(network_id)
705 else:
mirabal7bbf50e2017-03-13 15:15:18 +0100706 net_bind = network.get("bind_type", network_old[0]["bind_type"])
707 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 +0100708 "bridge_ifaces", ()):
709 self.config["dhcp_nets"].append(network_id)
710 return network_id
mirabal65ba8f82017-02-15 12:36:33 +0100711 else:
mirabale9f6f1a2017-02-16 17:57:35 +0100712 raise ovimException(content, -result)
mirabal65ba8f82017-02-15 12:36:33 +0100713
mirabale9f6f1a2017-02-16 17:57:35 +0100714 def delete_network(self, network_id):
mirabal9e194592017-02-17 11:03:25 +0100715 """
716 Delete network by network id
717 :param network_id: network id
718 :return:
719 """
mirabale9f6f1a2017-02-16 17:57:35 +0100720
721 # delete from the data base
722 result, content = self.db.delete_row('nets', network_id)
723
724 if result == 0:
725 raise ovimException("Network %s not found " % network_id, HTTP_Not_Found)
726 elif result > 0:
727 for brnet in self.config['bridge_nets']:
728 if brnet[3] == network_id:
729 brnet[3] = None
730 break
731 if self.config.get("dhcp_server") and network_id in self.config["dhcp_nets"]:
732 self.config["dhcp_nets"].remove(network_id)
733 return content
734 else:
735 raise ovimException("Error deleting network %s" % network_id, HTTP_Internal_Server_Error)
mirabal65ba8f82017-02-15 12:36:33 +0100736
737 def get_openflow_rules(self, network_id=None):
738 """
739 Get openflow id from DB
740 :param network_id: Network id, if none all networks will be retrieved
741 :return: Return a list with Openflow rules per net
742 """
743 # ignore input data
744 if not network_id:
745 where_ = {}
746 else:
747 where_ = {"net_id": network_id}
mirabal65ba8f82017-02-15 12:36:33 +0100748 result, content = self.db.get_table(
mirabalf9a1a8d2017-03-15 12:42:27 +0100749 SELECT=("name", "net_id", "ofc_id", "priority", "vlan_id", "ingress_port", "src_mac", "dst_mac", "actions"),
mirabal65ba8f82017-02-15 12:36:33 +0100750 WHERE=where_, FROM='of_flows')
751
752 if result < 0:
753 raise ovimException(str(content), -result)
754 return content
755
mirabale9f6f1a2017-02-16 17:57:35 +0100756 def edit_openflow_rules(self, network_id=None):
mirabal65ba8f82017-02-15 12:36:33 +0100757
758 """
759 To make actions over the net. The action is to reinstall the openflow rules
760 network_id can be 'all'
761 :param network_id: Network id, if none all networks will be retrieved
762 :return : Number of nets updated
763 """
764
765 # ignore input data
766 if not network_id:
767 where_ = {}
768 else:
769 where_ = {"uuid": network_id}
770 result, content = self.db.get_table(SELECT=("uuid", "type"), WHERE=where_, FROM='nets')
771
772 if result < 0:
773 raise ovimException(str(content), -result)
774
775 for net in content:
776 if net["type"] != "ptp" and net["type"] != "data":
777 result -= 1
778 continue
mirabal7bbf50e2017-03-13 15:15:18 +0100779
780 try:
781 self.net_update_ofc_thread(net['uuid'])
782 except ovimException as e:
783 raise ovimException("Error updating network'{}' {}".format(net['uuid'], str(e)),
784 HTTP_Internal_Server_Error)
785 except Exception as e:
786 raise ovimException("Error updating network '{}' {}".format(net['uuid'], str(e)),
787 HTTP_Internal_Server_Error)
788
mirabal65ba8f82017-02-15 12:36:33 +0100789 return result
790
mirabalf9a1a8d2017-03-15 12:42:27 +0100791 def delete_openflow_rules(self, ofc_id=None):
mirabal65ba8f82017-02-15 12:36:33 +0100792 """
793 To make actions over the net. The action is to delete ALL openflow rules
794 :return: return operation result
795 """
mirabalf9a1a8d2017-03-15 12:42:27 +0100796
797 if not ofc_id:
798 if 'Default' in self.config['ofcs_thread']:
799 r, c = self.config['ofcs_thread']['Default'].insert_task("clear-all")
800 else:
801 raise ovimException("Default Openflow controller not not running", HTTP_Not_Found)
802
803 elif ofc_id in self.config['ofcs_thread']:
804 r, c = self.config['ofcs_thread'][ofc_id].insert_task("clear-all")
805
806 # ignore input data
807 if r < 0:
808 raise ovimException(str(c), -r)
809 else:
810 raise ovimException("Openflow controller not found with ofc_id={}".format(ofc_id), HTTP_Not_Found)
mirabal65ba8f82017-02-15 12:36:33 +0100811 return r
812
mirabalf9a1a8d2017-03-15 12:42:27 +0100813 def get_openflow_ports(self, ofc_id=None):
mirabal65ba8f82017-02-15 12:36:33 +0100814 """
815 Obtain switch ports names of openflow controller
816 :return: Return flow ports in DB
817 """
mirabalf9a1a8d2017-03-15 12:42:27 +0100818 if not ofc_id:
819 if 'Default' in self.config['ofcs_thread']:
820 conn = self.config['ofcs_thread']['Default'].OF_connector
821 else:
822 raise ovimException("Default Openflow controller not not running", HTTP_Not_Found)
823
824 if ofc_id in self.config['ofcs_thread']:
825 conn = self.config['ofcs_thread'][ofc_id].OF_connector
826 else:
827 raise ovimException("Openflow controller not found with ofc_id={}".format(ofc_id), HTTP_Not_Found)
828 return conn.pp2ofi
tierno57f7bda2017-02-09 12:01:55 +0100829
830 def get_ports(self, columns=None, filter={}, limit=None):
831 # result, content = my.db.get_ports(where_)
832 result, content = self.db.get_table(SELECT=columns, WHERE=filter, FROM='ports', LIMIT=limit)
833 if result < 0:
834 self.logger.error("http_get_ports Error %d %s", result, content)
835 raise ovimException(str(content), -result)
836 else:
837 convert_boolean(content, ('admin_state_up',))
838 return content
839
tierno57f7bda2017-02-09 12:01:55 +0100840 def new_port(self, port_data):
841 port_data['type'] = 'external'
842 if port_data.get('net_id'):
843 # check that new net has the correct type
844 result, new_net = self.db.check_target_net(port_data['net_id'], None, 'external')
845 if result < 0:
846 raise ovimException(str(new_net), -result)
847 # insert in data base
848 result, uuid = self.db.new_row('ports', port_data, True, True)
849 if result > 0:
850 if 'net_id' in port_data:
mirabal7bbf50e2017-03-13 15:15:18 +0100851 try:
852 self.net_update_ofc_thread(port_data['net_id'])
853 except ovimException as e:
854 raise ovimException("Cannot insert a task for updating network '{}' {}"
855 .format(port_data['net_id'], str(e)), HTTP_Internal_Server_Error)
856 except Exception as e:
857 raise ovimException("Cannot insert a task for updating network '{}' {}"
858 .format(port_data['net_id'], str(e)), HTTP_Internal_Server_Error)
859
tierno57f7bda2017-02-09 12:01:55 +0100860 return uuid
861 else:
862 raise ovimException(str(uuid), -result)
863
mirabal37829452017-03-09 14:41:21 +0100864 def new_external_port(self, port_data):
865 """
866 Create new external port and check port mapping correspondence
867 :param port_data: port_data = {
868 'region': 'datacenter region',
869 'compute_node': 'compute node id',
870 'pci': 'pci port address',
871 'vlan': 'net vlan',
872 'net_id': 'net id',
873 'tenant_id': 'tenant id',
874 'mac': 'switch mac',
875 'name': 'port name'
876 'ip_address': 'ip address - optional'}
877 :return:
878 """
879
880 port_data['type'] = 'external'
881
882 if port_data.get('net_id'):
883 # check that new net has the correct type
884 result, new_net = self.db.check_target_net(port_data['net_id'], None, 'external')
885 if result < 0:
886 raise ovimException(str(new_net), -result)
887 # insert in data base
888 db_filter = {}
889
890 if port_data.get('region'):
891 db_filter['region'] = port_data['region']
892 if port_data.get('pci'):
893 db_filter['pci'] = port_data['pci']
894 if port_data.get('compute_node'):
895 db_filter['compute_node'] = port_data['compute_node']
896
897 columns = ['ofc_id', 'switch_dpid', 'switch_port', 'switch_mac', 'pci']
898 port_mapping_data = self.get_of_port_mappings(columns, db_filter)
899
900 if not len(port_mapping_data):
tierno16007502017-03-27 16:48:32 +0200901 raise ovimException("No port mapping founded for '{}'".format(str(db_filter)),
mirabal37829452017-03-09 14:41:21 +0100902 HTTP_Not_Found)
903 elif len(port_mapping_data) > 1:
904 raise ovimException("Wrong port data was given, please check pci, region & compute id data",
905 HTTP_Conflict)
906
907 port_data['ofc_id'] = port_mapping_data[0]['ofc_id']
908 port_data['switch_dpid'] = port_mapping_data[0]['switch_dpid']
909 port_data['switch_port'] = port_mapping_data[0]['switch_port']
910 port_data['switch_mac'] = port_mapping_data[0]['switch_mac']
911
912 # remove from compute_node, region and pci of_port_data to adapt to 'ports' structure
montesmoreno275b1992017-03-28 15:45:02 +0200913 if 'region' in port_data:
914 del port_data['region']
915 if 'pci' in port_data:
916 del port_data['pci']
917 if 'compute_node' in port_data:
918 del port_data['compute_node']
mirabal37829452017-03-09 14:41:21 +0100919
920 result, uuid = self.db.new_row('ports', port_data, True, True)
921 if result > 0:
mirabal7bbf50e2017-03-13 15:15:18 +0100922 try:
923 self.net_update_ofc_thread(port_data['net_id'], port_data['ofc_id'])
924 except ovimException as e:
925 raise ovimException("Cannot insert a task for updating network '{}' {}".
926 format(port_data['net_id'], str(e)), HTTP_Internal_Server_Error)
927 except Exception as e:
928 raise ovimException("Cannot insert a task for updating network '{}' {}"
929 .format(port_data['net_id'], e), HTTP_Internal_Server_Error)
mirabal37829452017-03-09 14:41:21 +0100930 return uuid
931 else:
932 raise ovimException(str(uuid), -result)
933
tiernoaa941462017-03-29 15:10:28 +0200934 def net_update_ofc_thread(self, net_id, ofc_id=None, switch_dpid=None):
mirabal7bbf50e2017-03-13 15:15:18 +0100935 """
936 Insert a update net task by net id or ofc_id for each ofc thread
937 :param net_id: network id
938 :param ofc_id: openflow controller id
tiernoaa941462017-03-29 15:10:28 +0200939 :param switch_dpid: switch dpid
mirabal7bbf50e2017-03-13 15:15:18 +0100940 :return:
941 """
942 if not net_id:
943 raise ovimException("No net_id received", HTTP_Internal_Server_Error)
944
mirabal7bbf50e2017-03-13 15:15:18 +0100945 r = -1
946 c = 'No valid ofc_id or switch_dpid received'
947
948 if not ofc_id:
949 ports = self.get_ports(filter={"net_id": net_id})
950 for port in ports:
951 port_ofc_id = port.get('ofc_id', None)
952 if port_ofc_id:
953 ofc_id = port['ofc_id']
954 switch_dpid = port['switch_dpid']
955 break
tiernoaa941462017-03-29 15:10:28 +0200956 #TODO if not ofc_id: look at database table ofcs
957
mirabal7bbf50e2017-03-13 15:15:18 +0100958
959 # If no ofc_id found it, default ofc_id is used.
960 if not ofc_id and not switch_dpid:
961 ofc_id = "Default"
962
963 if ofc_id and ofc_id in self.config['ofcs_thread']:
964 r, c = self.config['ofcs_thread'][ofc_id].insert_task("update-net", net_id)
965 elif switch_dpid:
966
967 ofcs_dpid_list = self.config['ofcs_thread_dpid']
968 for ofc_t in ofcs_dpid_list:
969 if switch_dpid in ofc_t:
970 r, c = ofc_t[switch_dpid].insert_task("update-net", net_id)
971
972 if r < 0:
tierno82232582017-03-15 18:09:16 +0100973 message = "Cannot insert a task for updating network '{}', {}".format(net_id, c)
mirabal7bbf50e2017-03-13 15:15:18 +0100974 self.logger.error(message)
975 raise ovimException(message, HTTP_Internal_Server_Error)
976
tierno57f7bda2017-02-09 12:01:55 +0100977 def delete_port(self, port_id):
978 # Look for the previous port data
979 result, ports = self.db.get_table(WHERE={'uuid': port_id, "type": "external"}, FROM='ports')
980 if result < 0:
981 raise ovimException("Cannot get port info from database: {}".format(ports), http_code=-result)
982 # delete from the data base
983 result, content = self.db.delete_row('ports', port_id)
984 if result == 0:
985 raise ovimException("External port '{}' not found".format(port_id), http_code=HTTP_Not_Found)
986 elif result < 0:
987 raise ovimException("Cannot delete port from database: {}".format(content), http_code=-result)
988 # update network
989 network = ports[0].get('net_id', None)
990 if network:
991 # change of net.
mirabal7bbf50e2017-03-13 15:15:18 +0100992
993 try:
tiernoaa941462017-03-29 15:10:28 +0200994 self.net_update_ofc_thread(network, ofc_id=ports[0]["ofc_id"], switch_dpid=ports[0]["switch_dpid"])
mirabal7bbf50e2017-03-13 15:15:18 +0100995 except ovimException as e:
996 raise ovimException("Cannot insert a task for delete network '{}' {}".format(network, str(e)),
997 HTTP_Internal_Server_Error)
998 except Exception as e:
999 raise ovimException("Cannot insert a task for delete network '{}' {}".format(network, str(e)),
1000 HTTP_Internal_Server_Error)
1001
tierno57f7bda2017-02-09 12:01:55 +01001002 return content
1003
tierno57f7bda2017-02-09 12:01:55 +01001004 def edit_port(self, port_id, port_data, admin=True):
1005 # Look for the previous port data
1006 result, content = self.db.get_table(FROM="ports", WHERE={'uuid': port_id})
1007 if result < 0:
1008 raise ovimException("Cannot get port info from database: {}".format(content), http_code=-result)
1009 elif result == 0:
1010 raise ovimException("Port '{}' not found".format(port_id), http_code=HTTP_Not_Found)
1011 port = content[0]
1012 nets = []
1013 host_id = None
1014 result = 1
1015 if 'net_id' in port_data:
1016 # change of net.
1017 old_net = port.get('net_id', None)
1018 new_net = port_data['net_id']
1019 if old_net != new_net:
1020
1021 if new_net:
1022 nets.append(new_net) # put first the new net, so that new openflow rules are created before removing the old ones
1023 if old_net:
1024 nets.append(old_net)
1025 if port['type'] == 'instance:bridge' or port['type'] == 'instance:ovs':
1026 raise ovimException("bridge interfaces cannot be attached to a different net", http_code=HTTP_Forbidden)
1027 elif port['type'] == 'external' and not admin:
1028 raise ovimException("Needed admin privileges",http_code=HTTP_Unauthorized)
1029 if new_net:
1030 # check that new net has the correct type
1031 result, new_net_dict = self.db.check_target_net(new_net, None, port['type'])
1032 if result < 0:
1033 raise ovimException("Error {}".format(new_net_dict), http_code=HTTP_Conflict)
1034 # change VLAN for SR-IOV ports
1035 if result >= 0 and port["type"] == "instance:data" and port["model"] == "VF": # TODO consider also VFnotShared
1036 if new_net:
1037 port_data["vlan"] = None
1038 else:
1039 port_data["vlan"] = new_net_dict["vlan"]
1040 # get host where this VM is allocated
1041 result, content = self.db.get_table(FROM="instances", WHERE={"uuid": port["instance_id"]})
1042 if result > 0:
1043 host_id = content[0]["host_id"]
1044
1045 # insert in data base
1046 if result >= 0:
1047 result, content = self.db.update_rows('ports', port_data, WHERE={'uuid': port_id}, log=False)
tiernoaa941462017-03-29 15:10:28 +02001048 port.update(port_data)
tierno57f7bda2017-02-09 12:01:55 +01001049
1050 # Insert task to complete actions
1051 if result > 0:
1052 for net_id in nets:
mirabal7bbf50e2017-03-13 15:15:18 +01001053 try:
tiernoaa941462017-03-29 15:10:28 +02001054 self.net_update_ofc_thread(net_id, port["ofc_id"], switch_dpid=port["switch_dpid"])
mirabal7bbf50e2017-03-13 15:15:18 +01001055 except ovimException as e:
1056 raise ovimException("Error updating network'{}' {}".format(net_id, str(e)),
1057 HTTP_Internal_Server_Error)
1058 except Exception as e:
1059 raise ovimException("Error updating network '{}' {}".format(net_id, str(e)),
1060 HTTP_Internal_Server_Error)
1061
tierno57f7bda2017-02-09 12:01:55 +01001062 if host_id:
1063 r, v = self.config['host_threads'][host_id].insert_task("edit-iface", port_id, old_net, new_net)
1064 if r < 0:
1065 self.logger.error("Error updating network '{}' {}".format(r,v))
1066 # TODO Do something if fails
1067 if result >= 0:
1068 return port_id
1069 else:
1070 raise ovimException("Error {}".format(content), http_code=-result)
mirabalb716ac52017-02-10 14:47:53 +01001071
mirabal9e194592017-02-17 11:03:25 +01001072 def new_of_controller(self, ofc_data):
1073 """
1074 Create a new openflow controller into DB
1075 :param ofc_data: Dict openflow controller data
1076 :return: openflow controller dpid
1077 """
1078
mirabal580435e2017-03-01 16:17:10 +01001079 result, ofc_uuid = self.db.new_row('ofcs', ofc_data, True, True)
mirabal9e194592017-02-17 11:03:25 +01001080 if result < 0:
mirabal580435e2017-03-01 16:17:10 +01001081 raise ovimException("New ofc Error %s" % ofc_uuid, HTTP_Internal_Server_Error)
1082
1083 ofc_data['uuid'] = ofc_uuid
1084 of_conn = self._load_of_module(ofc_data)
1085 self._create_ofc_task(ofc_uuid, ofc_data['dpid'], of_conn)
1086
1087 return ofc_uuid
mirabal9e194592017-02-17 11:03:25 +01001088
1089 def edit_of_controller(self, of_id, ofc_data):
1090 """
1091 Edit an openflow controller entry from DB
1092 :return:
1093 """
1094 if not ofc_data:
1095 raise ovimException("No data received during uptade OF contorller", http_code=HTTP_Internal_Server_Error)
1096
1097 old_of_controller = self.show_of_controller(of_id)
1098
1099 if old_of_controller:
1100 result, content = self.db.update_rows('ofcs', ofc_data, WHERE={'uuid': of_id}, log=False)
1101 if result >= 0:
1102 return ofc_data
1103 else:
1104 raise ovimException("Error uptating OF contorller with uuid {}".format(of_id),
1105 http_code=-result)
1106 else:
1107 raise ovimException("Error uptating OF contorller with uuid {}".format(of_id),
1108 http_code=HTTP_Internal_Server_Error)
1109
1110 def delete_of_controller(self, of_id):
1111 """
1112 Delete an openflow controller from DB.
1113 :param of_id: openflow controller dpid
1114 :return:
1115 """
1116
mirabal580435e2017-03-01 16:17:10 +01001117 ofc = self.show_of_controller(of_id)
1118
Pablo Montes Moreno5b6f7492017-03-02 16:18:36 +01001119 result, content = self.db.delete_row("ofcs", of_id)
mirabal9e194592017-02-17 11:03:25 +01001120 if result < 0:
1121 raise ovimException("Cannot delete ofc from database: {}".format(content), http_code=-result)
1122 elif result == 0:
1123 raise ovimException("ofc {} not found ".format(content), http_code=HTTP_Not_Found)
mirabal580435e2017-03-01 16:17:10 +01001124
1125 ofc_thread = self.config['ofcs_thread'][of_id]
1126 del self.config['ofcs_thread'][of_id]
1127 for ofc_th in self.config['ofcs_thread_dpid']:
1128 if ofc['dpid'] in ofc_th:
1129 self.config['ofcs_thread_dpid'].remove(ofc_th)
1130
1131 ofc_thread.insert_task("exit")
1132 #ofc_thread.join()
1133
mirabal9e194592017-02-17 11:03:25 +01001134 return content
1135
1136 def show_of_controller(self, uuid):
1137 """
1138 Show an openflow controller by dpid from DB.
1139 :param db_filter: List with where query parameters
1140 :return:
1141 """
1142
1143 result, content = self.db.get_table(FROM='ofcs', WHERE={"uuid": uuid}, LIMIT=100)
1144
1145 if result == 0:
1146 raise ovimException("Openflow controller with uuid '{}' not found".format(uuid),
1147 http_code=HTTP_Not_Found)
1148 elif result < 0:
1149 raise ovimException("Openflow controller with uuid '{}' error".format(uuid),
1150 http_code=HTTP_Internal_Server_Error)
Pablo Montes Moreno5b6f7492017-03-02 16:18:36 +01001151 return content[0]
mirabal9e194592017-02-17 11:03:25 +01001152
mirabalfbfb7972017-02-27 17:36:17 +01001153 def get_of_controllers(self, columns=None, db_filter={}, limit=None):
mirabal9e194592017-02-17 11:03:25 +01001154 """
1155 Show an openflow controllers from DB.
1156 :param columns: List with SELECT query parameters
1157 :param db_filter: List with where query parameters
mirabalfbfb7972017-02-27 17:36:17 +01001158 :param limit: result Limit
mirabal9e194592017-02-17 11:03:25 +01001159 :return:
1160 """
mirabalfbfb7972017-02-27 17:36:17 +01001161 result, content = self.db.get_table(SELECT=columns, FROM='ofcs', WHERE=db_filter, LIMIT=limit)
mirabal9e194592017-02-17 11:03:25 +01001162
1163 if result < 0:
1164 raise ovimException(str(content), -result)
1165
1166 return content
1167
mirabalfbfb7972017-02-27 17:36:17 +01001168 def get_tenants(self, columns=None, db_filter={}, limit=None):
1169 """
1170 Retrieve tenant list from DB
1171 :param columns: List with SELECT query parameters
1172 :param db_filter: List with where query parameters
1173 :param limit: result limit
1174 :return:
1175 """
1176 result, content = self.db.get_table(FROM='tenants', SELECT=columns, WHERE=db_filter, LIMIT=limit)
1177 if result < 0:
1178 raise ovimException('get_tenatns Error {}'.format(str(content)), -result)
1179 else:
1180 convert_boolean(content, ('enabled',))
1181 return content
1182
1183 def show_tenant_id(self, tenant_id):
1184 """
1185 Get tenant from DB by id
1186 :param tenant_id: tenant id
1187 :return:
1188 """
1189 result, content = self.db.get_table(FROM='tenants', SELECT=('uuid', 'name', 'description', 'enabled'),
1190 WHERE={"uuid": tenant_id})
1191 if result < 0:
1192 raise ovimException(str(content), -result)
1193 elif result == 0:
1194 raise ovimException("tenant with uuid='{}' not found".format(tenant_id), HTTP_Not_Found)
1195 else:
1196 convert_boolean(content, ('enabled',))
1197 return content[0]
1198
1199 def new_tentant(self, tenant):
1200 """
1201 Create a tenant and store in DB
1202 :param tenant: Dictionary with tenant data
1203 :return: the uuid of created tenant. Raise exception upon error
1204 """
1205
1206 # insert in data base
1207 result, tenant_uuid = self.db.new_tenant(tenant)
1208
1209 if result >= 0:
1210 return tenant_uuid
1211 else:
1212 raise ovimException(str(tenant_uuid), -result)
1213
1214 def delete_tentant(self, tenant_id):
1215 """
1216 Delete a tenant from the database.
1217 :param tenant_id: Tenant id
1218 :return: delete tenant id
1219 """
1220
1221 # check permissions
1222 r, tenants_flavors = self.db.get_table(FROM='tenants_flavors', SELECT=('flavor_id', 'tenant_id'),
1223 WHERE={'tenant_id': tenant_id})
1224 if r <= 0:
1225 tenants_flavors = ()
1226 r, tenants_images = self.db.get_table(FROM='tenants_images', SELECT=('image_id', 'tenant_id'),
1227 WHERE={'tenant_id': tenant_id})
1228 if r <= 0:
1229 tenants_images = ()
1230
1231 result, content = self.db.delete_row('tenants', tenant_id)
1232 if result == 0:
1233 raise ovimException("tenant '%s' not found" % tenant_id, HTTP_Not_Found)
1234 elif result > 0:
1235 for flavor in tenants_flavors:
1236 self.db.delete_row_by_key("flavors", "uuid", flavor['flavor_id'])
1237 for image in tenants_images:
1238 self.db.delete_row_by_key("images", "uuid", image['image_id'])
1239 return content
1240 else:
1241 raise ovimException("Error deleting tenant '%s' " % tenant_id, HTTP_Internal_Server_Error)
1242
1243 def edit_tenant(self, tenant_id, tenant_data):
1244 """
1245 Update a tenant data identified by tenant id
1246 :param tenant_id: tenant id
1247 :param tenant_data: Dictionary with tenant data
1248 :return:
1249 """
1250
1251 # Look for the previous data
1252 result, tenant_data_old = self.db.get_table(FROM='tenants', WHERE={'uuid': tenant_id})
1253 if result < 0:
1254 raise ovimException("Error updating tenant with uuid='{}': {}".format(tenant_id, tenant_data_old),
1255 HTTP_Internal_Server_Error)
1256 elif result == 0:
1257 raise ovimException("tenant with uuid='{}' not found".format(tenant_id), HTTP_Not_Found)
1258
1259 # insert in data base
1260 result, content = self.db.update_rows('tenants', tenant_data, WHERE={'uuid': tenant_id}, log=True)
1261 if result >= 0:
1262 return content
1263 else:
1264 raise ovimException(str(content), -result)
1265
mirabal6045a9d2017-03-06 11:36:55 +01001266 def set_of_port_mapping(self, of_maps, ofc_id=None, switch_dpid=None, region=None):
1267 """
1268 Create new port mapping entry
1269 :param of_maps: List with port mapping information
1270 # maps =[{"ofc_id": <ofc_id>,"region": datacenter region,"compute_node": compute uuid,"pci": pci adress,
1271 "switch_dpid": swith dpid,"switch_port": port name,"switch_mac": mac}]
1272 :param ofc_id: ofc id
1273 :param switch_dpid: switch dpid
1274 :param region: datacenter region id
1275 :return:
1276 """
1277
1278 for map in of_maps:
1279 if ofc_id:
1280 map['ofc_id'] = ofc_id
1281 if switch_dpid:
1282 map['switch_dpid'] = switch_dpid
1283 if region:
1284 map['region'] = region
1285
1286 for of_map in of_maps:
1287 result, uuid = self.db.new_row('of_port_mappings', of_map, True)
1288 if result > 0:
1289 of_map["uuid"] = uuid
1290 else:
1291 raise ovimException(str(uuid), -result)
1292 return of_maps
1293
1294 def clear_of_port_mapping(self, db_filter={}):
1295 """
1296 Clear port mapping filtering using db_filter dict
1297 :param db_filter: Parameter to filter during remove process
1298 :return:
1299 """
1300 result, content = self.db.delete_row_by_dict(FROM='of_port_mappings', WHERE=db_filter)
1301 # delete_row_by_key
1302 if result >= 0:
1303 return content
1304 else:
1305 raise ovimException("Error deleting of_port_mappings with filter='{}'".format(str(db_filter)),
1306 HTTP_Internal_Server_Error)
1307
1308 def get_of_port_mappings(self, column=None, db_filter=None, db_limit=None):
1309 """
1310 Retrive port mapping from DB
1311 :param column:
1312 :param db_filter:
1313 :return:
1314 """
1315 result, content = self.db.get_table(SELECT=column, WHERE=db_filter, FROM='of_port_mappings', LIMIT=db_limit)
1316
1317 if result < 0:
1318 self.logger.error("get_of_port_mappings Error %d %s", result, content)
1319 raise ovimException(str(content), -result)
1320 else:
1321 return content
1322
mirabalb716ac52017-02-10 14:47:53 +01001323 def get_dhcp_controller(self):
1324 """
1325 Create an host_thread object for manage openvim controller and not create a thread for itself
1326 :return: dhcp_host openvim controller object
1327 """
1328
1329 if 'openvim_controller' in self.config['host_threads']:
1330 return self.config['host_threads']['openvim_controller']
1331
1332 bridge_ifaces = []
1333 controller_ip = self.config['ovs_controller_ip']
1334 ovs_controller_user = self.config['ovs_controller_user']
1335
1336 host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
1337 host_develop_mode = True if self.config['mode'] == 'development' else False
1338
1339 dhcp_host = ht.host_thread(name='openvim_controller', user=ovs_controller_user, host=controller_ip,
tierno686b3952017-03-10 13:57:24 +01001340 db=self.db_of,
1341 db_lock=self.db_lock, test=host_test_mode,
mirabalb716ac52017-02-10 14:47:53 +01001342 image_path=self.config['image_path'], version=self.config['version'],
1343 host_id='openvim_controller', develop_mode=host_develop_mode,
1344 develop_bridge_iface=bridge_ifaces)
1345
1346 self.config['host_threads']['openvim_controller'] = dhcp_host
1347 if not host_test_mode:
1348 dhcp_host.ssh_connect()
1349 return dhcp_host
1350
mirabal18f5de32017-02-13 12:41:49 +01001351 def launch_dhcp_server(self, vlan, first_ip, last_ip, cidr, gateway):
mirabalb716ac52017-02-10 14:47:53 +01001352 """
1353 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
1354 :param vlan: vlan identifier
1355 :param first_ip: First dhcp range ip
1356 :param last_ip: Last dhcp range ip
1357 :param cidr: net cidr
mirabale9f6f1a2017-02-16 17:57:35 +01001358 :param gateway: net gateway
mirabalb716ac52017-02-10 14:47:53 +01001359 :return:
1360 """
1361 ip_tools = IPNetwork(cidr)
1362 dhcp_netmask = str(ip_tools.netmask)
1363 ip_range = [first_ip, last_ip]
1364
1365 dhcp_path = self.config['ovs_controller_file_path']
1366
1367 controller_host = self.get_dhcp_controller()
1368 controller_host.create_linux_bridge(vlan)
1369 controller_host.create_dhcp_interfaces(vlan, first_ip, dhcp_netmask)
mirabal18f5de32017-02-13 12:41:49 +01001370 controller_host.launch_dhcp_server(vlan, ip_range, dhcp_netmask, dhcp_path, gateway)
mirabalb716ac52017-02-10 14:47:53 +01001371
mirabald87877c2017-03-31 15:15:52 +02001372if __name__ == "__main__":
1373
1374 parser = argparse.ArgumentParser()
tierno46ca3a92017-04-05 19:49:24 +02001375 parser.add_argument("-v","--version", help="show ovim library version", action="store_true")
1376 parser.add_argument("--database-version", help="show required database version", action="store_true")
mirabald87877c2017-03-31 15:15:52 +02001377 args = parser.parse_args()
1378 if args.version:
1379 print ('openvimd version {} {}'.format(ovim.get_version(), ovim.get_version_date()))
1380 print ('(c) Copyright Telefonica')
tierno46ca3a92017-04-05 19:49:24 +02001381 elif args.database_version:
1382 print ('required database version: {}'.format(ovim.get_database_version()))
mirabalb716ac52017-02-10 14:47:53 +01001383