blob: c9990497ee241bf294ccd22000a85e6ce04d4509 [file] [log] [blame]
mirabald87877c2017-03-31 15:15:52 +02001#!/usr/bin/env python
tierno57f7bda2017-02-09 12:01:55 +01002# -*- coding: utf-8 -*-
3
4##
5# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
6# This file is part of openvim
7# All Rights Reserved.
8#
9# Licensed under the Apache License, Version 2.0 (the "License"); you may
10# not use this file except in compliance with the License. You may obtain
11# a copy of the License at
12#
13# http://www.apache.org/licenses/LICENSE-2.0
14#
15# Unless required by applicable law or agreed to in writing, software
16# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
17# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
18# License for the specific language governing permissions and limitations
19# under the License.
20#
21# For those usages not covered by the Apache License, Version 2.0 please
22# contact with: nfvlabs@tid.es
23##
24
tierno51068952017-04-26 15:09:48 +020025"""
tierno57f7bda2017-02-09 12:01:55 +010026This is the thread for the http server North API.
27Two thread will be launched, with normal and administrative permissions.
tierno51068952017-04-26 15:09:48 +020028"""
tierno57f7bda2017-02-09 12:01:55 +010029
30import threading
garciadeblas071df952017-04-21 11:12:50 +020031import vim_db
tierno57f7bda2017-02-09 12:01:55 +010032import logging
tierno29d80242017-04-25 18:07:08 +020033# import imp
tierno95a9e832017-04-27 18:49:37 +020034import os.path
mirabal9f657102017-04-10 20:05:40 +020035import argparse
mirabal65ba8f82017-02-15 12:36:33 +010036from netaddr import IPNetwork
37from jsonschema import validate as js_v, exceptions as js_e
garciadeblas071df952017-04-21 11:12:50 +020038import host_thread as ht
39import dhcp_thread as dt
40import openflow_thread as oft
41import openflow_conn
mirabal9f657102017-04-10 20:05:40 +020042
tierno51068952017-04-26 15:09:48 +020043__author__ = "Alfonso Tierno, Leonardo Mirabal"
44__date__ = "$06-Feb-2017 12:07:15$"
tiernoa290d8f2017-05-03 17:42:52 +020045__version__ = "0.5.13-r529"
tierno95a9e832017-04-27 18:49:37 +020046version_date = "May 2017"
tiernoa290d8f2017-05-03 17:42:52 +020047database_version = 18 #needed database schema version
tierno57f7bda2017-02-09 12:01:55 +010048
49HTTP_Bad_Request = 400
50HTTP_Unauthorized = 401
51HTTP_Not_Found = 404
52HTTP_Forbidden = 403
53HTTP_Method_Not_Allowed = 405
54HTTP_Not_Acceptable = 406
55HTTP_Request_Timeout = 408
56HTTP_Conflict = 409
57HTTP_Service_Unavailable = 503
58HTTP_Internal_Server_Error= 500
59
60
61def convert_boolean(data, items):
62 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
63 It assumes that bandwidth is well formed
64 Attributes:
65 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
66 'items': tuple of keys to convert
67 Return:
68 None
69 '''
70 if type(data) is dict:
71 for k in data.keys():
72 if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
73 convert_boolean(data[k], items)
74 if k in items:
75 if type(data[k]) is str:
76 if data[k] == "false":
77 data[k] = False
78 elif data[k] == "true":
79 data[k] = True
80 if type(data) is tuple or type(data) is list:
81 for k in data:
82 if type(k) is dict or type(k) is tuple or type(k) is list:
83 convert_boolean(k, items)
84
85
86
87class ovimException(Exception):
88 def __init__(self, message, http_code=HTTP_Bad_Request):
89 self.http_code = http_code
90 Exception.__init__(self, message)
91
92
93class ovim():
94 running_info = {} #TODO OVIM move the info of running threads from config_dic to this static variable
mirabal580435e2017-03-01 16:17:10 +010095 of_module = {}
96
tierno57f7bda2017-02-09 12:01:55 +010097 def __init__(self, configuration):
98 self.config = configuration
tierno46ca3a92017-04-05 19:49:24 +020099 self.logger_name = configuration.get("logger_name", "openvim")
100 self.logger = logging.getLogger(self.logger_name)
tierno57f7bda2017-02-09 12:01:55 +0100101 self.db = None
tierno46ca3a92017-04-05 19:49:24 +0200102 self.db = self._create_database_connection()
mirabal580435e2017-03-01 16:17:10 +0100103 self.of_test_mode = False
tierno57f7bda2017-02-09 12:01:55 +0100104
105 def _create_database_connection(self):
106 db = vim_db.vim_db((self.config["network_vlan_range_start"], self.config["network_vlan_range_end"]),
tierno46ca3a92017-04-05 19:49:24 +0200107 self.logger_name + ".db", self.config.get('log_level_db'))
tierno57f7bda2017-02-09 12:01:55 +0100108 if db.connect(self.config['db_host'], self.config['db_user'], self.config['db_passwd'],
109 self.config['db_name']) == -1:
110 # self.logger.error("Cannot connect to database %s at %s@%s", self.config['db_name'], self.config['db_user'],
111 # self.config['db_host'])
112 raise ovimException("Cannot connect to database {} at {}@{}".format(self.config['db_name'],
113 self.config['db_user'],
114 self.config['db_host']) )
115 return db
116
mirabale9f6f1a2017-02-16 17:57:35 +0100117 @staticmethod
mirabal50a052f2017-03-27 18:08:07 +0200118 def get_version():
119 return __version__
120
121 @staticmethod
122 def get_version_date():
123 return version_date
124
125 @staticmethod
126 def get_database_version():
127 return database_version
128
129 @staticmethod
mirabale9f6f1a2017-02-16 17:57:35 +0100130 def _check_dhcp_data_integrity(network):
131 """
132 Check if all dhcp parameter for anet are valid, if not will be calculated from cidr value
133 :param network: list with user nets paramters
134 :return:
135 """
136 if "cidr" in network:
137 cidr = network["cidr"]
138 ip_tools = IPNetwork(cidr)
139 cidr_len = ip_tools.prefixlen
140 if cidr_len > 29:
141 return False
142
143 ips = IPNetwork(cidr)
144 if "dhcp_first_ip" not in network:
145 network["dhcp_first_ip"] = str(ips[2])
146 if "dhcp_last_ip" not in network:
147 network["dhcp_last_ip"] = str(ips[-2])
148 if "gateway_ip" not in network:
149 network["gateway_ip"] = str(ips[1])
150
151 return True
152 else:
153 return False
154
155 @staticmethod
156 def _check_valid_uuid(uuid):
157 id_schema = {"type": "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
158 try:
159 js_v(uuid, id_schema)
160 return True
161 except js_e.ValidationError:
162 return False
tierno57f7bda2017-02-09 12:01:55 +0100163
164 def start_service(self):
mirabal580435e2017-03-01 16:17:10 +0100165 """
166 Start ovim services
167 :return:
168 """
tierno2db743b2017-03-28 17:23:15 +0200169 global database_version
mirabal580435e2017-03-01 16:17:10 +0100170 # if self.running_info:
tierno57f7bda2017-02-09 12:01:55 +0100171 # return #TODO service can be checked and rebuild broken threads
172 r = self.db.get_db_version()
tierno95a9e832017-04-27 18:49:37 +0200173 db_path = __file__
174 db_path = db_path[:db_path.rfind("/")]
175 if os.path.exists(db_path + "/database_utils/migrate_vim_db.sh"):
176 db_path += "/database_utils"
177 else:
178 db_path += "/../database_utils"
179
mirabal580435e2017-03-01 16:17:10 +0100180 if r[0] < 0:
tierno95a9e832017-04-27 18:49:37 +0200181 raise ovimException("DATABASE is not valid. If you think it is corrupted, you can init it with"
182 " '{db_path}/init_vim_db.sh' script".format(db_path=db_path))
183 elif r[0] != database_version:
184 raise ovimException("DATABASE wrong version '{current}'. Try to upgrade/downgrade to version '{target}'"
185 " with '{db_path}/migrate_vim_db.sh {target}'".format(
186 current=r[0], target=database_version, db_path=db_path))
tierno46ca3a92017-04-05 19:49:24 +0200187 self.logger.critical("Starting ovim server version: '{} {}' database version '{}'".format(
188 self.get_version(), self.get_version_date(), self.get_database_version()))
tierno57f7bda2017-02-09 12:01:55 +0100189 # create database connection for openflow threads
tiernoe0c28c12017-05-04 18:44:40 +0200190 self.config["db"] = self._create_database_connection()
191 self.config["db_lock"] = threading.Lock()
tierno57f7bda2017-02-09 12:01:55 +0100192
mirabal580435e2017-03-01 16:17:10 +0100193 self.of_test_mode = False if self.config['mode'] == 'normal' or self.config['mode'] == "OF only" else True
194 # precreate interfaces; [bridge:<host_bridge_name>, VLAN used at Host, uuid of network camping in this bridge,
195 # speed in Gbit/s
196
tierno57f7bda2017-02-09 12:01:55 +0100197 self.config['dhcp_nets'] = []
198 self.config['bridge_nets'] = []
199 for bridge, vlan_speed in self.config["bridge_ifaces"].items():
mirabal580435e2017-03-01 16:17:10 +0100200 # skip 'development_bridge'
tierno57f7bda2017-02-09 12:01:55 +0100201 if self.config['mode'] == 'development' and self.config['development_bridge'] == bridge:
202 continue
203 self.config['bridge_nets'].append([bridge, vlan_speed[0], vlan_speed[1], None])
204
205 # check if this bridge is already used (present at database) for a network)
206 used_bridge_nets = []
207 for brnet in self.config['bridge_nets']:
tierno686b3952017-03-10 13:57:24 +0100208 r, nets = self.db.get_table(SELECT=('uuid',), FROM='nets', WHERE={'provider': "bridge:" + brnet[0]})
tierno57f7bda2017-02-09 12:01:55 +0100209 if r > 0:
210 brnet[3] = nets[0]['uuid']
211 used_bridge_nets.append(brnet[0])
212 if self.config.get("dhcp_server"):
213 if brnet[0] in self.config["dhcp_server"]["bridge_ifaces"]:
214 self.config['dhcp_nets'].append(nets[0]['uuid'])
215 if len(used_bridge_nets) > 0:
216 self.logger.info("found used bridge nets: " + ",".join(used_bridge_nets))
217 # get nets used by dhcp
218 if self.config.get("dhcp_server"):
219 for net in self.config["dhcp_server"].get("nets", ()):
tierno686b3952017-03-10 13:57:24 +0100220 r, nets = self.db.get_table(SELECT=('uuid',), FROM='nets', WHERE={'name': net})
tierno57f7bda2017-02-09 12:01:55 +0100221 if r > 0:
222 self.config['dhcp_nets'].append(nets[0]['uuid'])
223
mirabal580435e2017-03-01 16:17:10 +0100224 # OFC default
225 self._start_ofc_default_task()
tierno57f7bda2017-02-09 12:01:55 +0100226
mirabal580435e2017-03-01 16:17:10 +0100227 # OFC per tenant in DB
228 self._start_of_db_tasks()
tierno57f7bda2017-02-09 12:01:55 +0100229
230 # create dhcp_server thread
231 host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
232 dhcp_params = self.config.get("dhcp_server")
233 if dhcp_params:
234 thread = dt.dhcp_thread(dhcp_params=dhcp_params, test=host_test_mode, dhcp_nets=self.config["dhcp_nets"],
tiernoe0c28c12017-05-04 18:44:40 +0200235 db=self.config["db"], db_lock=self.config["db_lock"],
236 logger_name=self.logger_name + ".dhcp",
tierno43044b02017-04-06 18:58:24 +0200237 debug=self.config.get('log_level_of'))
tierno57f7bda2017-02-09 12:01:55 +0100238 thread.start()
239 self.config['dhcp_thread'] = thread
240
241 # Create one thread for each host
242 host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
243 host_develop_mode = True if self.config['mode'] == 'development' else False
244 host_develop_bridge_iface = self.config.get('development_bridge', None)
mirabal580435e2017-03-01 16:17:10 +0100245
246 # get host list from data base before starting threads
tierno686b3952017-03-10 13:57:24 +0100247 r, hosts = self.db.get_table(SELECT=('name', 'ip_name', 'user', 'uuid'), FROM='hosts', WHERE={'status': 'ok'})
mirabal580435e2017-03-01 16:17:10 +0100248 if r < 0:
249 raise ovimException("Cannot get hosts from database {}".format(hosts))
250
tierno57f7bda2017-02-09 12:01:55 +0100251 self.config['host_threads'] = {}
252 for host in hosts:
253 host['image_path'] = '/opt/VNF/images/openvim'
tiernoe0c28c12017-05-04 18:44:40 +0200254 thread = ht.host_thread(name=host['name'], user=host['user'], host=host['ip_name'], db=self.config["db"],
255 db_lock=self.config["db_lock"], test=host_test_mode,
256 image_path=self.config['image_path'],
257 version=self.config['version'], host_id=host['uuid'],
258 develop_mode=host_develop_mode,
tiernof135eff2017-04-19 19:11:53 +0200259 develop_bridge_iface=host_develop_bridge_iface,
260 logger_name=self.logger_name + ".host." + host['name'],
261 debug=self.config.get('log_level_host'))
tierno57f7bda2017-02-09 12:01:55 +0100262 thread.start()
263 self.config['host_threads'][host['uuid']] = thread
264
mirabalb716ac52017-02-10 14:47:53 +0100265 # create ovs dhcp thread
266 result, content = self.db.get_table(FROM='nets')
267 if result < 0:
268 self.logger.error("http_get_ports Error %d %s", result, content)
269 raise ovimException(str(content), -result)
270
271 for net in content:
272 net_type = net['type']
mirabale9f6f1a2017-02-16 17:57:35 +0100273 if (net_type == 'bridge_data' or net_type == 'bridge_man') \
274 and net["provider"][:4] == 'OVS:' and net["enable_dhcp"] == "true":
mirabal65ba8f82017-02-15 12:36:33 +0100275 self.launch_dhcp_server(net['vlan'],
276 net['dhcp_first_ip'],
277 net['dhcp_last_ip'],
278 net['cidr'],
279 net['gateway_ip'])
mirabalb716ac52017-02-10 14:47:53 +0100280
mirabal580435e2017-03-01 16:17:10 +0100281 def _start_of_db_tasks(self):
282 """
283 Start ofc task for existing ofcs in database
284 :param db_of:
285 :param db_lock:
286 :return:
287 """
288 ofcs = self.get_of_controllers()
289
290 for ofc in ofcs:
291 of_conn = self._load_of_module(ofc)
292 # create ofc thread per of controller
293 self._create_ofc_task(ofc['uuid'], ofc['dpid'], of_conn)
294
295 def _create_ofc_task(self, ofc_uuid, dpid, of_conn):
296 """
297 Create an ofc thread for handle each sdn controllers
298 :param ofc_uuid: sdn controller uuid
299 :param dpid: sdn controller dpid
300 :param of_conn: OF_conn module
301 :return:
302 """
303 if 'ofcs_thread' not in self.config and 'ofcs_thread_dpid' not in self.config:
304 ofcs_threads = {}
305 ofcs_thread_dpid = []
306 else:
307 ofcs_threads = self.config['ofcs_thread']
308 ofcs_thread_dpid = self.config['ofcs_thread_dpid']
309
310 if ofc_uuid not in ofcs_threads:
311 ofc_thread = self._create_ofc_thread(of_conn, ofc_uuid)
312 if ofc_uuid == "Default":
313 self.config['of_thread'] = ofc_thread
314
315 ofcs_threads[ofc_uuid] = ofc_thread
316 self.config['ofcs_thread'] = ofcs_threads
317
318 ofcs_thread_dpid.append({dpid: ofc_thread})
319 self.config['ofcs_thread_dpid'] = ofcs_thread_dpid
320
321 def _start_ofc_default_task(self):
322 """
323 Create default ofc thread
324 """
325 if 'of_controller' not in self.config \
326 and 'of_controller_ip' not in self.config \
327 and 'of_controller_port' not in self.config \
328 and 'of_controller_dpid' not in self.config:
329 return
330
331 # OF THREAD
332 db_config = {}
333 db_config['ip'] = self.config.get('of_controller_ip')
334 db_config['port'] = self.config.get('of_controller_port')
335 db_config['dpid'] = self.config.get('of_controller_dpid')
336 db_config['type'] = self.config.get('of_controller')
337 db_config['user'] = self.config.get('of_user')
338 db_config['password'] = self.config.get('of_password')
339
340 # create connector to the openflow controller
341 # load other parameters starting by of_ from config dict in a temporal dict
342
343 of_conn = self._load_of_module(db_config)
344 # create openflow thread
345 self._create_ofc_task("Default", db_config['dpid'], of_conn)
346
347 def _load_of_module(self, db_config):
348 """
349 import python module for each SDN controller supported
mirabalf9a1a8d2017-03-15 12:42:27 +0100350 :param db_config: SDN dn information
mirabal580435e2017-03-01 16:17:10 +0100351 :return: Module
352 """
353 if not db_config:
354 raise ovimException("No module found it", HTTP_Internal_Server_Error)
355
356 module_info = None
357
358 try:
359 if self.of_test_mode:
mirabal6c600652017-03-16 17:22:57 +0100360 return openflow_conn.OfTestConnector({"name": db_config['type'],
361 "dpid": db_config['dpid'],
362 "of_debug": self.config['log_level_of']})
mirabal580435e2017-03-01 16:17:10 +0100363 temp_dict = {}
364
365 if db_config:
366 temp_dict['of_ip'] = db_config['ip']
367 temp_dict['of_port'] = db_config['port']
368 temp_dict['of_dpid'] = db_config['dpid']
369 temp_dict['of_controller'] = db_config['type']
montesmorenoba1862b2017-04-06 10:07:44 +0000370 temp_dict['of_user'] = db_config.get('user')
371 temp_dict['of_password'] = db_config.get('password')
mirabal580435e2017-03-01 16:17:10 +0100372
373 temp_dict['of_debug'] = self.config['log_level_of']
374
375 if temp_dict['of_controller'] == 'opendaylight':
376 module = "ODL"
377 else:
378 module = temp_dict['of_controller']
379
380 if module not in ovim.of_module:
tierno29d80242017-04-25 18:07:08 +0200381 for base in ("", "osm_openvim.", "lib_osm_openvim."):
382 try:
383 pkg = __import__(base + module)
384 if base:
385 of_conn_module = getattr(pkg, module)
386 else:
387 of_conn_module = pkg
388 ovim.of_module[module] = of_conn_module
389 self.logger.debug("Module load from {}".format(base + module))
390 break
391 except Exception as e:
392 self.logger.warning("Module {} not found {}".format(base + module, e))
393 else:
394 self.logger.error("Cannot open openflow controller module of type '%s'", module)
395 raise ovimException("Cannot open openflow controller of type module '{}'"
396 "Revise it is installed".format(module),
397 HTTP_Internal_Server_Error)
mirabal580435e2017-03-01 16:17:10 +0100398 else:
399 of_conn_module = ovim.of_module[module]
tierno29d80242017-04-25 18:07:08 +0200400 return of_conn_module.OF_conn(temp_dict)
401 except Exception as e:
402 self.logger.error("Cannot open the Openflow controller '%s': %s", type(e).__name__, str(e))
403 raise ovimException("Cannot open the Openflow controller '{}': '{}'".format(type(e).__name__, str(e)),
mirabal580435e2017-03-01 16:17:10 +0100404 HTTP_Internal_Server_Error)
405
406 def _create_ofc_thread(self, of_conn, ofc_uuid="Default"):
407 """
408 Create and launch a of thread
409 :return: thread obj
410 """
411 # create openflow thread
412
montesmoreno92827552017-03-30 13:24:17 +0200413 #if 'of_controller_nets_with_same_vlan' in self.config:
414 # ofc_net_same_vlan = self.config['of_controller_nets_with_same_vlan']
415 #else:
416 # ofc_net_same_vlan = False
417 ofc_net_same_vlan = False
mirabal580435e2017-03-01 16:17:10 +0100418
tiernoe0c28c12017-05-04 18:44:40 +0200419 thread = oft.openflow_thread(ofc_uuid, of_conn, of_test=self.of_test_mode, db=self.config["db"],
420 db_lock=self.config["db_lock"],
421 pmp_with_same_vlan=ofc_net_same_vlan,
422 logger_name=self.logger_name + ".ofc." + ofc_uuid,
423 debug=self.config.get('log_level_of'))
mirabal580435e2017-03-01 16:17:10 +0100424 #r, c = thread.OF_connector.obtain_port_correspondence()
425 #if r < 0:
426 # raise ovimException("Cannot get openflow information %s", c)
427 thread.start()
428 return thread
429
tierno57f7bda2017-02-09 12:01:55 +0100430 def stop_service(self):
431 threads = self.config.get('host_threads', {})
432 if 'of_thread' in self.config:
433 threads['of'] = (self.config['of_thread'])
mirabal580435e2017-03-01 16:17:10 +0100434 if 'ofcs_thread' in self.config:
435 ofcs_thread = self.config['ofcs_thread']
436 for ofc in ofcs_thread:
437 threads[ofc] = ofcs_thread[ofc]
438
tierno57f7bda2017-02-09 12:01:55 +0100439 if 'dhcp_thread' in self.config:
440 threads['dhcp'] = (self.config['dhcp_thread'])
441
442 for thread in threads.values():
443 thread.insert_task("exit")
444 for thread in threads.values():
445 thread.join()
tierno57f7bda2017-02-09 12:01:55 +0100446
mirabal9e194592017-02-17 11:03:25 +0100447 def get_networks(self, columns=None, db_filter={}, limit=None):
mirabal65ba8f82017-02-15 12:36:33 +0100448 """
449 Retreive networks available
mirabale9f6f1a2017-02-16 17:57:35 +0100450 :param columns: List with select query parameters
mirabal9e194592017-02-17 11:03:25 +0100451 :param db_filter: List with where query parameters
mirabale9f6f1a2017-02-16 17:57:35 +0100452 :param limit: Query limit result
mirabal65ba8f82017-02-15 12:36:33 +0100453 :return:
454 """
mirabal9e194592017-02-17 11:03:25 +0100455 result, content = self.db.get_table(SELECT=columns, FROM='nets', WHERE=db_filter, LIMIT=limit)
mirabal65ba8f82017-02-15 12:36:33 +0100456
457 if result < 0:
458 raise ovimException(str(content), -result)
459
460 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
461
462 return content
463
mirabal9e194592017-02-17 11:03:25 +0100464 def show_network(self, network_id, db_filter={}):
mirabal65ba8f82017-02-15 12:36:33 +0100465 """
mirabale9f6f1a2017-02-16 17:57:35 +0100466 Get network from DB by id
467 :param network_id: net Id
mirabal9e194592017-02-17 11:03:25 +0100468 :param db_filter: List with where query parameters
mirabal65ba8f82017-02-15 12:36:33 +0100469 :return:
470 """
471 # obtain data
mirabale9f6f1a2017-02-16 17:57:35 +0100472 if not network_id:
473 raise ovimException("Not network id was not found")
mirabal9e194592017-02-17 11:03:25 +0100474 db_filter['uuid'] = network_id
mirabal65ba8f82017-02-15 12:36:33 +0100475
mirabal9e194592017-02-17 11:03:25 +0100476 result, content = self.db.get_table(FROM='nets', WHERE=db_filter, LIMIT=100)
mirabal65ba8f82017-02-15 12:36:33 +0100477
478 if result < 0:
479 raise ovimException(str(content), -result)
480 elif result == 0:
mirabale9f6f1a2017-02-16 17:57:35 +0100481 raise ovimException("show_network network '%s' not found" % network_id, -result)
mirabal65ba8f82017-02-15 12:36:33 +0100482 else:
483 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
mirabale9f6f1a2017-02-16 17:57:35 +0100484 # get ports from DB
mirabal65ba8f82017-02-15 12:36:33 +0100485 result, ports = self.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
mirabale9f6f1a2017-02-16 17:57:35 +0100486 WHERE={'net_id': network_id}, LIMIT=100)
mirabal65ba8f82017-02-15 12:36:33 +0100487 if len(ports) > 0:
488 content[0]['ports'] = ports
mirabal65ba8f82017-02-15 12:36:33 +0100489
mirabale9f6f1a2017-02-16 17:57:35 +0100490 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
491 return content[0]
492
493 def new_network(self, network):
mirabal65ba8f82017-02-15 12:36:33 +0100494 """
mirabale9f6f1a2017-02-16 17:57:35 +0100495 Create a net in DB
mirabal65ba8f82017-02-15 12:36:33 +0100496 :return:
497 """
498 tenant_id = network.get('tenant_id')
499
500 if tenant_id:
501 result, _ = self.db.get_table(FROM='tenants', SELECT=('uuid',), WHERE={'uuid': tenant_id, "enabled": True})
502 if result <= 0:
503 raise ovimException("set_network error, no tenant founded", -result)
504
505 bridge_net = None
506 # check valid params
507 net_provider = network.get('provider')
508 net_type = network.get('type')
mirabal65ba8f82017-02-15 12:36:33 +0100509 net_vlan = network.get("vlan")
510 net_bind_net = network.get("bind_net")
511 net_bind_type = network.get("bind_type")
tiernoa290d8f2017-05-03 17:42:52 +0200512 net_region = network.get("region")
mirabal65ba8f82017-02-15 12:36:33 +0100513 name = network["name"]
514
515 # check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
516 vlan_index = name.rfind(":")
517 if not net_bind_net and not net_bind_type and vlan_index > 1:
518 try:
519 vlan_tag = int(name[vlan_index + 1:])
520 if not vlan_tag and vlan_tag < 4096:
521 net_bind_net = name[:vlan_index]
522 net_bind_type = "vlan:" + name[vlan_index + 1:]
523 except:
524 pass
525
526 if net_bind_net:
527 # look for a valid net
528 if self._check_valid_uuid(net_bind_net):
529 net_bind_key = "uuid"
530 else:
531 net_bind_key = "name"
532 result, content = self.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net})
533 if result < 0:
534 raise ovimException(' getting nets from db ' + content, HTTP_Internal_Server_Error)
535 elif result == 0:
536 raise ovimException(" bind_net %s '%s'not found" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
537 elif result > 1:
538 raise ovimException(" more than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
539 network["bind_net"] = content[0]["uuid"]
540
541 if net_bind_type:
542 if net_bind_type[0:5] != "vlan:":
543 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>'", HTTP_Bad_Request)
544 if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:]) <= 0:
545 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095",
546 HTTP_Bad_Request)
547 network["bind_type"] = net_bind_type
548
549 if net_provider:
550 if net_provider[:9] == "openflow:":
551 if net_type:
552 if net_type != "ptp" and net_type != "data":
553 raise ovimException(" only 'ptp' or 'data' net types can be bound to 'openflow'",
554 HTTP_Bad_Request)
555 else:
556 net_type = 'data'
557 else:
558 if net_type:
559 if net_type != "bridge_man" and net_type != "bridge_data":
560 raise ovimException("Only 'bridge_man' or 'bridge_data' net types can be bound "
561 "to 'bridge', 'macvtap' or 'default", HTTP_Bad_Request)
562 else:
563 net_type = 'bridge_man'
564
565 if not net_type:
566 net_type = 'bridge_man'
567
568 if net_provider:
569 if net_provider[:7] == 'bridge:':
570 # check it is one of the pre-provisioned bridges
571 bridge_net_name = net_provider[7:]
572 for brnet in self.config['bridge_nets']:
573 if brnet[0] == bridge_net_name: # free
574 if not brnet[3]:
575 raise ovimException("invalid 'provider:physical', "
576 "bridge '%s' is already used" % bridge_net_name, HTTP_Conflict)
577 bridge_net = brnet
578 net_vlan = brnet[1]
579 break
mirabale9f6f1a2017-02-16 17:57:35 +0100580 # if bridge_net==None:
581 # bottle.abort(HTTP_Bad_Request, "invalid 'provider:physical', bridge '%s' is not one of the
582 # provisioned 'bridge_ifaces' in the configuration file" % bridge_net_name)
583 # return
584
mirabal65ba8f82017-02-15 12:36:33 +0100585 elif self.config['network_type'] == 'bridge' and (net_type == 'bridge_data' or net_type == 'bridge_man'):
586 # look for a free precreated nets
587 for brnet in self.config['bridge_nets']:
588 if not brnet[3]: # free
589 if not bridge_net:
590 if net_type == 'bridge_man': # look for the smaller speed
591 if brnet[2] < bridge_net[2]:
592 bridge_net = brnet
593 else: # look for the larger speed
594 if brnet[2] > bridge_net[2]:
595 bridge_net = brnet
596 else:
597 bridge_net = brnet
598 net_vlan = brnet[1]
599 if not bridge_net:
600 raise ovimException("Max limits of bridge networks reached. Future versions of VIM "
601 "will overcome this limit", HTTP_Bad_Request)
602 else:
mirabale9f6f1a2017-02-16 17:57:35 +0100603 self.logger.debug("using net " + bridge_net)
mirabal65ba8f82017-02-15 12:36:33 +0100604 net_provider = "bridge:" + bridge_net[0]
605 net_vlan = bridge_net[1]
606 elif net_type == 'bridge_data' or net_type == 'bridge_man' and self.config['network_type'] == 'ovs':
607 net_provider = 'OVS'
tiernoa290d8f2017-05-03 17:42:52 +0200608 if not net_region:
609 if net_type == "data" or net_type == "ptp":
610 net_region = "__DATA__"
611 elif net_provider == "OVS":
612 net_region = "__OVS__"
mirabal65ba8f82017-02-15 12:36:33 +0100613 if not net_vlan and (net_type == "data" or net_type == "ptp" or net_provider == "OVS"):
tiernoa290d8f2017-05-03 17:42:52 +0200614 net_vlan = self.db.get_free_net_vlan(net_region)
mirabal65ba8f82017-02-15 12:36:33 +0100615 if net_vlan < 0:
616 raise ovimException("Error getting an available vlan", HTTP_Internal_Server_Error)
617 if net_provider == 'OVS':
618 net_provider = 'OVS' + ":" + str(net_vlan)
619
620 network['provider'] = net_provider
621 network['type'] = net_type
622 network['vlan'] = net_vlan
tiernoa290d8f2017-05-03 17:42:52 +0200623 network['region'] = net_region
mirabal65ba8f82017-02-15 12:36:33 +0100624 dhcp_integrity = True
625 if 'enable_dhcp' in network and network['enable_dhcp']:
626 dhcp_integrity = self._check_dhcp_data_integrity(network)
627
628 result, content = self.db.new_row('nets', network, True, True)
629
630 if result >= 0 and dhcp_integrity:
631 if bridge_net:
632 bridge_net[3] = content
633 if self.config.get("dhcp_server") and self.config['network_type'] == 'bridge':
634 if network["name"] in self.config["dhcp_server"].get("nets", ()):
635 self.config["dhcp_nets"].append(content)
mirabale9f6f1a2017-02-16 17:57:35 +0100636 self.logger.debug("dhcp_server: add new net", content)
mirabal65ba8f82017-02-15 12:36:33 +0100637 elif not bridge_net and bridge_net[0] in self.config["dhcp_server"].get("bridge_ifaces", ()):
638 self.config["dhcp_nets"].append(content)
mirabale9f6f1a2017-02-16 17:57:35 +0100639 self.logger.debug("dhcp_server: add new net", content, content)
mirabal65ba8f82017-02-15 12:36:33 +0100640 return content
641 else:
mirabal65ba8f82017-02-15 12:36:33 +0100642 raise ovimException("Error posting network", HTTP_Internal_Server_Error)
mirabale9f6f1a2017-02-16 17:57:35 +0100643# TODO kei change update->edit
mirabal65ba8f82017-02-15 12:36:33 +0100644
mirabale9f6f1a2017-02-16 17:57:35 +0100645 def edit_network(self, network_id, network):
mirabal65ba8f82017-02-15 12:36:33 +0100646 """
mirabale9f6f1a2017-02-16 17:57:35 +0100647 Update entwork data byt id
mirabal65ba8f82017-02-15 12:36:33 +0100648 :return:
649 """
mirabale9f6f1a2017-02-16 17:57:35 +0100650 # Look for the previous data
651 where_ = {'uuid': network_id}
652 result, network_old = self.db.get_table(FROM='nets', WHERE=where_)
653 if result < 0:
654 raise ovimException("Error updating network %s" % network_old, HTTP_Internal_Server_Error)
655 elif result == 0:
656 raise ovimException('network %s not found' % network_id, HTTP_Not_Found)
657 # get ports
658 nbports, content = self.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
659 WHERE={'net_id': network_id}, LIMIT=100)
660 if result < 0:
661 raise ovimException("http_put_network_id error %d %s" % (result, network_old), HTTP_Internal_Server_Error)
662 if nbports > 0:
663 if 'type' in network and network['type'] != network_old[0]['type']:
664 raise ovimException("Can not change type of network while having ports attached",
665 HTTP_Method_Not_Allowed)
666 if 'vlan' in network and network['vlan'] != network_old[0]['vlan']:
667 raise ovimException("Can not change vlan of network while having ports attached",
668 HTTP_Method_Not_Allowed)
mirabal65ba8f82017-02-15 12:36:33 +0100669
mirabale9f6f1a2017-02-16 17:57:35 +0100670 # check valid params
671 net_provider = network.get('provider', network_old[0]['provider'])
672 net_type = network.get('type', network_old[0]['type'])
673 net_bind_net = network.get("bind_net")
674 net_bind_type = network.get("bind_type")
675 if net_bind_net:
676 # look for a valid net
677 if self._check_valid_uuid(net_bind_net):
678 net_bind_key = "uuid"
679 else:
680 net_bind_key = "name"
681 result, content = self.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net})
682 if result < 0:
683 raise ovimException('Getting nets from db ' + content, HTTP_Internal_Server_Error)
684 elif result == 0:
685 raise ovimException("bind_net %s '%s'not found" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
686 elif result > 1:
687 raise ovimException("More than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net),
688 HTTP_Bad_Request)
689 network["bind_net"] = content[0]["uuid"]
690 if net_bind_type:
691 if net_bind_type[0:5] != "vlan:":
692 raise ovimException("Bad format for 'bind_type', must be 'vlan:<tag>'", HTTP_Bad_Request)
693 if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:]) <= 0:
694 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095",
695 HTTP_Bad_Request)
696 if net_provider:
697 if net_provider[:9] == "openflow:":
698 if net_type != "ptp" and net_type != "data":
699 raise ovimException("Only 'ptp' or 'data' net types can be bound to 'openflow'", HTTP_Bad_Request)
700 else:
701 if net_type != "bridge_man" and net_type != "bridge_data":
702 raise ovimException("Only 'bridge_man' or 'bridge_data' net types can be bound to "
703 "'bridge', 'macvtap' or 'default", HTTP_Bad_Request)
mirabal65ba8f82017-02-15 12:36:33 +0100704
mirabale9f6f1a2017-02-16 17:57:35 +0100705 # insert in data base
706 result, content = self.db.update_rows('nets', network, WHERE={'uuid': network_id}, log=True)
707 if result >= 0:
708 # if result > 0 and nbports>0 and 'admin_state_up' in network
709 # and network['admin_state_up'] != network_old[0]['admin_state_up']:
710 if result > 0:
mirabal7bbf50e2017-03-13 15:15:18 +0100711
712 try:
tiernoaa941462017-03-29 15:10:28 +0200713 if nbports:
714 self.net_update_ofc_thread(network_id)
mirabal7bbf50e2017-03-13 15:15:18 +0100715 except ovimException as e:
716 raise ovimException("Error while launching openflow rules in network '{}' {}"
717 .format(network_id, str(e)), HTTP_Internal_Server_Error)
718 except Exception as e:
719 raise ovimException("Error while launching openflow rules in network '{}' {}"
720 .format(network_id, str(e)), HTTP_Internal_Server_Error)
721
mirabale9f6f1a2017-02-16 17:57:35 +0100722 if self.config.get("dhcp_server"):
723 if network_id in self.config["dhcp_nets"]:
724 self.config["dhcp_nets"].remove(network_id)
mirabal7bbf50e2017-03-13 15:15:18 +0100725 if network.get("name", network_old[0]["name"]) in self.config["dhcp_server"].get("nets", ()):
mirabale9f6f1a2017-02-16 17:57:35 +0100726 self.config["dhcp_nets"].append(network_id)
727 else:
mirabal7bbf50e2017-03-13 15:15:18 +0100728 net_bind = network.get("bind_type", network_old[0]["bind_type"])
729 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 +0100730 "bridge_ifaces", ()):
731 self.config["dhcp_nets"].append(network_id)
732 return network_id
mirabal65ba8f82017-02-15 12:36:33 +0100733 else:
mirabale9f6f1a2017-02-16 17:57:35 +0100734 raise ovimException(content, -result)
mirabal65ba8f82017-02-15 12:36:33 +0100735
mirabale9f6f1a2017-02-16 17:57:35 +0100736 def delete_network(self, network_id):
mirabal9e194592017-02-17 11:03:25 +0100737 """
738 Delete network by network id
739 :param network_id: network id
740 :return:
741 """
mirabale9f6f1a2017-02-16 17:57:35 +0100742
743 # delete from the data base
744 result, content = self.db.delete_row('nets', network_id)
745
746 if result == 0:
747 raise ovimException("Network %s not found " % network_id, HTTP_Not_Found)
748 elif result > 0:
749 for brnet in self.config['bridge_nets']:
750 if brnet[3] == network_id:
751 brnet[3] = None
752 break
753 if self.config.get("dhcp_server") and network_id in self.config["dhcp_nets"]:
754 self.config["dhcp_nets"].remove(network_id)
755 return content
756 else:
757 raise ovimException("Error deleting network %s" % network_id, HTTP_Internal_Server_Error)
mirabal65ba8f82017-02-15 12:36:33 +0100758
759 def get_openflow_rules(self, network_id=None):
760 """
761 Get openflow id from DB
762 :param network_id: Network id, if none all networks will be retrieved
763 :return: Return a list with Openflow rules per net
764 """
765 # ignore input data
766 if not network_id:
767 where_ = {}
768 else:
769 where_ = {"net_id": network_id}
mirabal65ba8f82017-02-15 12:36:33 +0100770 result, content = self.db.get_table(
mirabalf9a1a8d2017-03-15 12:42:27 +0100771 SELECT=("name", "net_id", "ofc_id", "priority", "vlan_id", "ingress_port", "src_mac", "dst_mac", "actions"),
mirabal65ba8f82017-02-15 12:36:33 +0100772 WHERE=where_, FROM='of_flows')
773
774 if result < 0:
775 raise ovimException(str(content), -result)
776 return content
777
mirabale9f6f1a2017-02-16 17:57:35 +0100778 def edit_openflow_rules(self, network_id=None):
mirabal65ba8f82017-02-15 12:36:33 +0100779
780 """
781 To make actions over the net. The action is to reinstall the openflow rules
782 network_id can be 'all'
783 :param network_id: Network id, if none all networks will be retrieved
784 :return : Number of nets updated
785 """
786
787 # ignore input data
788 if not network_id:
789 where_ = {}
790 else:
791 where_ = {"uuid": network_id}
792 result, content = self.db.get_table(SELECT=("uuid", "type"), WHERE=where_, FROM='nets')
793
794 if result < 0:
795 raise ovimException(str(content), -result)
796
797 for net in content:
798 if net["type"] != "ptp" and net["type"] != "data":
799 result -= 1
800 continue
mirabal7bbf50e2017-03-13 15:15:18 +0100801
802 try:
803 self.net_update_ofc_thread(net['uuid'])
804 except ovimException as e:
805 raise ovimException("Error updating network'{}' {}".format(net['uuid'], str(e)),
806 HTTP_Internal_Server_Error)
807 except Exception as e:
808 raise ovimException("Error updating network '{}' {}".format(net['uuid'], str(e)),
809 HTTP_Internal_Server_Error)
810
mirabal65ba8f82017-02-15 12:36:33 +0100811 return result
812
mirabalf9a1a8d2017-03-15 12:42:27 +0100813 def delete_openflow_rules(self, ofc_id=None):
mirabal65ba8f82017-02-15 12:36:33 +0100814 """
815 To make actions over the net. The action is to delete ALL openflow rules
816 :return: return operation result
817 """
mirabalf9a1a8d2017-03-15 12:42:27 +0100818
819 if not ofc_id:
820 if 'Default' in self.config['ofcs_thread']:
821 r, c = self.config['ofcs_thread']['Default'].insert_task("clear-all")
822 else:
823 raise ovimException("Default Openflow controller not not running", HTTP_Not_Found)
824
825 elif ofc_id in self.config['ofcs_thread']:
826 r, c = self.config['ofcs_thread'][ofc_id].insert_task("clear-all")
827
828 # ignore input data
829 if r < 0:
830 raise ovimException(str(c), -r)
831 else:
832 raise ovimException("Openflow controller not found with ofc_id={}".format(ofc_id), HTTP_Not_Found)
mirabal65ba8f82017-02-15 12:36:33 +0100833 return r
834
mirabalf9a1a8d2017-03-15 12:42:27 +0100835 def get_openflow_ports(self, ofc_id=None):
mirabal65ba8f82017-02-15 12:36:33 +0100836 """
837 Obtain switch ports names of openflow controller
838 :return: Return flow ports in DB
839 """
mirabalf9a1a8d2017-03-15 12:42:27 +0100840 if not ofc_id:
841 if 'Default' in self.config['ofcs_thread']:
842 conn = self.config['ofcs_thread']['Default'].OF_connector
843 else:
844 raise ovimException("Default Openflow controller not not running", HTTP_Not_Found)
845
846 if ofc_id in self.config['ofcs_thread']:
847 conn = self.config['ofcs_thread'][ofc_id].OF_connector
848 else:
849 raise ovimException("Openflow controller not found with ofc_id={}".format(ofc_id), HTTP_Not_Found)
850 return conn.pp2ofi
tierno57f7bda2017-02-09 12:01:55 +0100851
852 def get_ports(self, columns=None, filter={}, limit=None):
853 # result, content = my.db.get_ports(where_)
854 result, content = self.db.get_table(SELECT=columns, WHERE=filter, FROM='ports', LIMIT=limit)
855 if result < 0:
856 self.logger.error("http_get_ports Error %d %s", result, content)
857 raise ovimException(str(content), -result)
858 else:
859 convert_boolean(content, ('admin_state_up',))
860 return content
861
tierno57f7bda2017-02-09 12:01:55 +0100862 def new_port(self, port_data):
863 port_data['type'] = 'external'
864 if port_data.get('net_id'):
865 # check that new net has the correct type
866 result, new_net = self.db.check_target_net(port_data['net_id'], None, 'external')
867 if result < 0:
868 raise ovimException(str(new_net), -result)
869 # insert in data base
870 result, uuid = self.db.new_row('ports', port_data, True, True)
871 if result > 0:
872 if 'net_id' in port_data:
mirabal7bbf50e2017-03-13 15:15:18 +0100873 try:
874 self.net_update_ofc_thread(port_data['net_id'])
875 except ovimException as e:
876 raise ovimException("Cannot insert a task for updating network '{}' {}"
877 .format(port_data['net_id'], str(e)), HTTP_Internal_Server_Error)
878 except Exception as e:
879 raise ovimException("Cannot insert a task for updating network '{}' {}"
880 .format(port_data['net_id'], str(e)), HTTP_Internal_Server_Error)
881
tierno57f7bda2017-02-09 12:01:55 +0100882 return uuid
883 else:
884 raise ovimException(str(uuid), -result)
885
mirabal37829452017-03-09 14:41:21 +0100886 def new_external_port(self, port_data):
887 """
888 Create new external port and check port mapping correspondence
889 :param port_data: port_data = {
890 'region': 'datacenter region',
891 'compute_node': 'compute node id',
892 'pci': 'pci port address',
893 'vlan': 'net vlan',
894 'net_id': 'net id',
895 'tenant_id': 'tenant id',
896 'mac': 'switch mac',
897 'name': 'port name'
898 'ip_address': 'ip address - optional'}
899 :return:
900 """
901
902 port_data['type'] = 'external'
903
904 if port_data.get('net_id'):
905 # check that new net has the correct type
906 result, new_net = self.db.check_target_net(port_data['net_id'], None, 'external')
907 if result < 0:
908 raise ovimException(str(new_net), -result)
909 # insert in data base
910 db_filter = {}
911
912 if port_data.get('region'):
913 db_filter['region'] = port_data['region']
914 if port_data.get('pci'):
915 db_filter['pci'] = port_data['pci']
916 if port_data.get('compute_node'):
917 db_filter['compute_node'] = port_data['compute_node']
918
919 columns = ['ofc_id', 'switch_dpid', 'switch_port', 'switch_mac', 'pci']
920 port_mapping_data = self.get_of_port_mappings(columns, db_filter)
921
922 if not len(port_mapping_data):
tierno16007502017-03-27 16:48:32 +0200923 raise ovimException("No port mapping founded for '{}'".format(str(db_filter)),
mirabal37829452017-03-09 14:41:21 +0100924 HTTP_Not_Found)
925 elif len(port_mapping_data) > 1:
926 raise ovimException("Wrong port data was given, please check pci, region & compute id data",
927 HTTP_Conflict)
928
929 port_data['ofc_id'] = port_mapping_data[0]['ofc_id']
930 port_data['switch_dpid'] = port_mapping_data[0]['switch_dpid']
931 port_data['switch_port'] = port_mapping_data[0]['switch_port']
932 port_data['switch_mac'] = port_mapping_data[0]['switch_mac']
933
934 # remove from compute_node, region and pci of_port_data to adapt to 'ports' structure
montesmoreno275b1992017-03-28 15:45:02 +0200935 if 'region' in port_data:
936 del port_data['region']
937 if 'pci' in port_data:
938 del port_data['pci']
939 if 'compute_node' in port_data:
940 del port_data['compute_node']
mirabal37829452017-03-09 14:41:21 +0100941
942 result, uuid = self.db.new_row('ports', port_data, True, True)
943 if result > 0:
mirabal7bbf50e2017-03-13 15:15:18 +0100944 try:
945 self.net_update_ofc_thread(port_data['net_id'], port_data['ofc_id'])
946 except ovimException as e:
947 raise ovimException("Cannot insert a task for updating network '{}' {}".
948 format(port_data['net_id'], str(e)), HTTP_Internal_Server_Error)
949 except Exception as e:
950 raise ovimException("Cannot insert a task for updating network '{}' {}"
951 .format(port_data['net_id'], e), HTTP_Internal_Server_Error)
mirabal37829452017-03-09 14:41:21 +0100952 return uuid
953 else:
954 raise ovimException(str(uuid), -result)
955
tiernoaa941462017-03-29 15:10:28 +0200956 def net_update_ofc_thread(self, net_id, ofc_id=None, switch_dpid=None):
mirabal7bbf50e2017-03-13 15:15:18 +0100957 """
958 Insert a update net task by net id or ofc_id for each ofc thread
959 :param net_id: network id
960 :param ofc_id: openflow controller id
tiernoaa941462017-03-29 15:10:28 +0200961 :param switch_dpid: switch dpid
mirabal7bbf50e2017-03-13 15:15:18 +0100962 :return:
963 """
964 if not net_id:
965 raise ovimException("No net_id received", HTTP_Internal_Server_Error)
966
mirabal7bbf50e2017-03-13 15:15:18 +0100967 r = -1
968 c = 'No valid ofc_id or switch_dpid received'
969
970 if not ofc_id:
971 ports = self.get_ports(filter={"net_id": net_id})
972 for port in ports:
973 port_ofc_id = port.get('ofc_id', None)
974 if port_ofc_id:
975 ofc_id = port['ofc_id']
976 switch_dpid = port['switch_dpid']
977 break
tiernoaa941462017-03-29 15:10:28 +0200978 #TODO if not ofc_id: look at database table ofcs
979
mirabal7bbf50e2017-03-13 15:15:18 +0100980
981 # If no ofc_id found it, default ofc_id is used.
982 if not ofc_id and not switch_dpid:
983 ofc_id = "Default"
984
985 if ofc_id and ofc_id in self.config['ofcs_thread']:
986 r, c = self.config['ofcs_thread'][ofc_id].insert_task("update-net", net_id)
987 elif switch_dpid:
988
989 ofcs_dpid_list = self.config['ofcs_thread_dpid']
990 for ofc_t in ofcs_dpid_list:
991 if switch_dpid in ofc_t:
992 r, c = ofc_t[switch_dpid].insert_task("update-net", net_id)
993
994 if r < 0:
tierno82232582017-03-15 18:09:16 +0100995 message = "Cannot insert a task for updating network '{}', {}".format(net_id, c)
mirabal7bbf50e2017-03-13 15:15:18 +0100996 self.logger.error(message)
997 raise ovimException(message, HTTP_Internal_Server_Error)
998
tierno57f7bda2017-02-09 12:01:55 +0100999 def delete_port(self, port_id):
1000 # Look for the previous port data
1001 result, ports = self.db.get_table(WHERE={'uuid': port_id, "type": "external"}, FROM='ports')
1002 if result < 0:
1003 raise ovimException("Cannot get port info from database: {}".format(ports), http_code=-result)
1004 # delete from the data base
1005 result, content = self.db.delete_row('ports', port_id)
1006 if result == 0:
1007 raise ovimException("External port '{}' not found".format(port_id), http_code=HTTP_Not_Found)
1008 elif result < 0:
1009 raise ovimException("Cannot delete port from database: {}".format(content), http_code=-result)
1010 # update network
1011 network = ports[0].get('net_id', None)
1012 if network:
1013 # change of net.
mirabal7bbf50e2017-03-13 15:15:18 +01001014
1015 try:
tiernoaa941462017-03-29 15:10:28 +02001016 self.net_update_ofc_thread(network, ofc_id=ports[0]["ofc_id"], switch_dpid=ports[0]["switch_dpid"])
mirabal7bbf50e2017-03-13 15:15:18 +01001017 except ovimException as e:
1018 raise ovimException("Cannot insert a task for delete network '{}' {}".format(network, str(e)),
1019 HTTP_Internal_Server_Error)
1020 except Exception as e:
1021 raise ovimException("Cannot insert a task for delete network '{}' {}".format(network, str(e)),
1022 HTTP_Internal_Server_Error)
1023
tierno57f7bda2017-02-09 12:01:55 +01001024 return content
1025
tierno57f7bda2017-02-09 12:01:55 +01001026 def edit_port(self, port_id, port_data, admin=True):
1027 # Look for the previous port data
1028 result, content = self.db.get_table(FROM="ports", WHERE={'uuid': port_id})
1029 if result < 0:
1030 raise ovimException("Cannot get port info from database: {}".format(content), http_code=-result)
1031 elif result == 0:
1032 raise ovimException("Port '{}' not found".format(port_id), http_code=HTTP_Not_Found)
1033 port = content[0]
1034 nets = []
1035 host_id = None
1036 result = 1
1037 if 'net_id' in port_data:
1038 # change of net.
1039 old_net = port.get('net_id', None)
1040 new_net = port_data['net_id']
1041 if old_net != new_net:
1042
1043 if new_net:
1044 nets.append(new_net) # put first the new net, so that new openflow rules are created before removing the old ones
1045 if old_net:
1046 nets.append(old_net)
1047 if port['type'] == 'instance:bridge' or port['type'] == 'instance:ovs':
1048 raise ovimException("bridge interfaces cannot be attached to a different net", http_code=HTTP_Forbidden)
1049 elif port['type'] == 'external' and not admin:
1050 raise ovimException("Needed admin privileges",http_code=HTTP_Unauthorized)
1051 if new_net:
1052 # check that new net has the correct type
1053 result, new_net_dict = self.db.check_target_net(new_net, None, port['type'])
1054 if result < 0:
1055 raise ovimException("Error {}".format(new_net_dict), http_code=HTTP_Conflict)
1056 # change VLAN for SR-IOV ports
1057 if result >= 0 and port["type"] == "instance:data" and port["model"] == "VF": # TODO consider also VFnotShared
1058 if new_net:
1059 port_data["vlan"] = None
1060 else:
1061 port_data["vlan"] = new_net_dict["vlan"]
1062 # get host where this VM is allocated
1063 result, content = self.db.get_table(FROM="instances", WHERE={"uuid": port["instance_id"]})
1064 if result > 0:
1065 host_id = content[0]["host_id"]
1066
1067 # insert in data base
1068 if result >= 0:
1069 result, content = self.db.update_rows('ports', port_data, WHERE={'uuid': port_id}, log=False)
tiernoaa941462017-03-29 15:10:28 +02001070 port.update(port_data)
tierno57f7bda2017-02-09 12:01:55 +01001071
1072 # Insert task to complete actions
1073 if result > 0:
1074 for net_id in nets:
mirabal7bbf50e2017-03-13 15:15:18 +01001075 try:
tiernoaa941462017-03-29 15:10:28 +02001076 self.net_update_ofc_thread(net_id, port["ofc_id"], switch_dpid=port["switch_dpid"])
mirabal7bbf50e2017-03-13 15:15:18 +01001077 except ovimException as e:
1078 raise ovimException("Error updating network'{}' {}".format(net_id, str(e)),
1079 HTTP_Internal_Server_Error)
1080 except Exception as e:
1081 raise ovimException("Error updating network '{}' {}".format(net_id, str(e)),
1082 HTTP_Internal_Server_Error)
1083
tierno57f7bda2017-02-09 12:01:55 +01001084 if host_id:
1085 r, v = self.config['host_threads'][host_id].insert_task("edit-iface", port_id, old_net, new_net)
1086 if r < 0:
1087 self.logger.error("Error updating network '{}' {}".format(r,v))
1088 # TODO Do something if fails
1089 if result >= 0:
1090 return port_id
1091 else:
1092 raise ovimException("Error {}".format(content), http_code=-result)
mirabalb716ac52017-02-10 14:47:53 +01001093
mirabal9e194592017-02-17 11:03:25 +01001094 def new_of_controller(self, ofc_data):
1095 """
1096 Create a new openflow controller into DB
1097 :param ofc_data: Dict openflow controller data
1098 :return: openflow controller dpid
1099 """
1100
mirabal580435e2017-03-01 16:17:10 +01001101 result, ofc_uuid = self.db.new_row('ofcs', ofc_data, True, True)
mirabal9e194592017-02-17 11:03:25 +01001102 if result < 0:
mirabal580435e2017-03-01 16:17:10 +01001103 raise ovimException("New ofc Error %s" % ofc_uuid, HTTP_Internal_Server_Error)
1104
1105 ofc_data['uuid'] = ofc_uuid
1106 of_conn = self._load_of_module(ofc_data)
1107 self._create_ofc_task(ofc_uuid, ofc_data['dpid'], of_conn)
1108
1109 return ofc_uuid
mirabal9e194592017-02-17 11:03:25 +01001110
1111 def edit_of_controller(self, of_id, ofc_data):
1112 """
1113 Edit an openflow controller entry from DB
1114 :return:
1115 """
1116 if not ofc_data:
1117 raise ovimException("No data received during uptade OF contorller", http_code=HTTP_Internal_Server_Error)
1118
1119 old_of_controller = self.show_of_controller(of_id)
1120
1121 if old_of_controller:
1122 result, content = self.db.update_rows('ofcs', ofc_data, WHERE={'uuid': of_id}, log=False)
1123 if result >= 0:
1124 return ofc_data
1125 else:
1126 raise ovimException("Error uptating OF contorller with uuid {}".format(of_id),
1127 http_code=-result)
1128 else:
1129 raise ovimException("Error uptating OF contorller with uuid {}".format(of_id),
1130 http_code=HTTP_Internal_Server_Error)
1131
1132 def delete_of_controller(self, of_id):
1133 """
1134 Delete an openflow controller from DB.
1135 :param of_id: openflow controller dpid
1136 :return:
1137 """
1138
mirabal580435e2017-03-01 16:17:10 +01001139 ofc = self.show_of_controller(of_id)
1140
Pablo Montes Moreno5b6f7492017-03-02 16:18:36 +01001141 result, content = self.db.delete_row("ofcs", of_id)
mirabal9e194592017-02-17 11:03:25 +01001142 if result < 0:
1143 raise ovimException("Cannot delete ofc from database: {}".format(content), http_code=-result)
1144 elif result == 0:
1145 raise ovimException("ofc {} not found ".format(content), http_code=HTTP_Not_Found)
mirabal580435e2017-03-01 16:17:10 +01001146
1147 ofc_thread = self.config['ofcs_thread'][of_id]
1148 del self.config['ofcs_thread'][of_id]
1149 for ofc_th in self.config['ofcs_thread_dpid']:
1150 if ofc['dpid'] in ofc_th:
1151 self.config['ofcs_thread_dpid'].remove(ofc_th)
1152
1153 ofc_thread.insert_task("exit")
1154 #ofc_thread.join()
1155
mirabal9e194592017-02-17 11:03:25 +01001156 return content
1157
1158 def show_of_controller(self, uuid):
1159 """
1160 Show an openflow controller by dpid from DB.
1161 :param db_filter: List with where query parameters
1162 :return:
1163 """
1164
1165 result, content = self.db.get_table(FROM='ofcs', WHERE={"uuid": uuid}, LIMIT=100)
1166
1167 if result == 0:
1168 raise ovimException("Openflow controller with uuid '{}' not found".format(uuid),
1169 http_code=HTTP_Not_Found)
1170 elif result < 0:
1171 raise ovimException("Openflow controller with uuid '{}' error".format(uuid),
1172 http_code=HTTP_Internal_Server_Error)
Pablo Montes Moreno5b6f7492017-03-02 16:18:36 +01001173 return content[0]
mirabal9e194592017-02-17 11:03:25 +01001174
mirabalfbfb7972017-02-27 17:36:17 +01001175 def get_of_controllers(self, columns=None, db_filter={}, limit=None):
mirabal9e194592017-02-17 11:03:25 +01001176 """
1177 Show an openflow controllers from DB.
1178 :param columns: List with SELECT query parameters
1179 :param db_filter: List with where query parameters
mirabalfbfb7972017-02-27 17:36:17 +01001180 :param limit: result Limit
mirabal9e194592017-02-17 11:03:25 +01001181 :return:
1182 """
mirabalfbfb7972017-02-27 17:36:17 +01001183 result, content = self.db.get_table(SELECT=columns, FROM='ofcs', WHERE=db_filter, LIMIT=limit)
mirabal9e194592017-02-17 11:03:25 +01001184
1185 if result < 0:
1186 raise ovimException(str(content), -result)
1187
1188 return content
1189
mirabalfbfb7972017-02-27 17:36:17 +01001190 def get_tenants(self, columns=None, db_filter={}, limit=None):
1191 """
1192 Retrieve tenant list from DB
1193 :param columns: List with SELECT query parameters
1194 :param db_filter: List with where query parameters
1195 :param limit: result limit
1196 :return:
1197 """
1198 result, content = self.db.get_table(FROM='tenants', SELECT=columns, WHERE=db_filter, LIMIT=limit)
1199 if result < 0:
1200 raise ovimException('get_tenatns Error {}'.format(str(content)), -result)
1201 else:
1202 convert_boolean(content, ('enabled',))
1203 return content
1204
1205 def show_tenant_id(self, tenant_id):
1206 """
1207 Get tenant from DB by id
1208 :param tenant_id: tenant id
1209 :return:
1210 """
1211 result, content = self.db.get_table(FROM='tenants', SELECT=('uuid', 'name', 'description', 'enabled'),
1212 WHERE={"uuid": tenant_id})
1213 if result < 0:
1214 raise ovimException(str(content), -result)
1215 elif result == 0:
1216 raise ovimException("tenant with uuid='{}' not found".format(tenant_id), HTTP_Not_Found)
1217 else:
1218 convert_boolean(content, ('enabled',))
1219 return content[0]
1220
1221 def new_tentant(self, tenant):
1222 """
1223 Create a tenant and store in DB
1224 :param tenant: Dictionary with tenant data
1225 :return: the uuid of created tenant. Raise exception upon error
1226 """
1227
1228 # insert in data base
1229 result, tenant_uuid = self.db.new_tenant(tenant)
1230
1231 if result >= 0:
1232 return tenant_uuid
1233 else:
1234 raise ovimException(str(tenant_uuid), -result)
1235
1236 def delete_tentant(self, tenant_id):
1237 """
1238 Delete a tenant from the database.
1239 :param tenant_id: Tenant id
1240 :return: delete tenant id
1241 """
1242
1243 # check permissions
1244 r, tenants_flavors = self.db.get_table(FROM='tenants_flavors', SELECT=('flavor_id', 'tenant_id'),
1245 WHERE={'tenant_id': tenant_id})
1246 if r <= 0:
1247 tenants_flavors = ()
1248 r, tenants_images = self.db.get_table(FROM='tenants_images', SELECT=('image_id', 'tenant_id'),
1249 WHERE={'tenant_id': tenant_id})
1250 if r <= 0:
1251 tenants_images = ()
1252
1253 result, content = self.db.delete_row('tenants', tenant_id)
1254 if result == 0:
1255 raise ovimException("tenant '%s' not found" % tenant_id, HTTP_Not_Found)
1256 elif result > 0:
1257 for flavor in tenants_flavors:
1258 self.db.delete_row_by_key("flavors", "uuid", flavor['flavor_id'])
1259 for image in tenants_images:
1260 self.db.delete_row_by_key("images", "uuid", image['image_id'])
1261 return content
1262 else:
1263 raise ovimException("Error deleting tenant '%s' " % tenant_id, HTTP_Internal_Server_Error)
1264
1265 def edit_tenant(self, tenant_id, tenant_data):
1266 """
1267 Update a tenant data identified by tenant id
1268 :param tenant_id: tenant id
1269 :param tenant_data: Dictionary with tenant data
1270 :return:
1271 """
1272
1273 # Look for the previous data
1274 result, tenant_data_old = self.db.get_table(FROM='tenants', WHERE={'uuid': tenant_id})
1275 if result < 0:
1276 raise ovimException("Error updating tenant with uuid='{}': {}".format(tenant_id, tenant_data_old),
1277 HTTP_Internal_Server_Error)
1278 elif result == 0:
1279 raise ovimException("tenant with uuid='{}' not found".format(tenant_id), HTTP_Not_Found)
1280
1281 # insert in data base
1282 result, content = self.db.update_rows('tenants', tenant_data, WHERE={'uuid': tenant_id}, log=True)
1283 if result >= 0:
1284 return content
1285 else:
1286 raise ovimException(str(content), -result)
1287
mirabal6045a9d2017-03-06 11:36:55 +01001288 def set_of_port_mapping(self, of_maps, ofc_id=None, switch_dpid=None, region=None):
1289 """
1290 Create new port mapping entry
1291 :param of_maps: List with port mapping information
1292 # maps =[{"ofc_id": <ofc_id>,"region": datacenter region,"compute_node": compute uuid,"pci": pci adress,
1293 "switch_dpid": swith dpid,"switch_port": port name,"switch_mac": mac}]
1294 :param ofc_id: ofc id
1295 :param switch_dpid: switch dpid
1296 :param region: datacenter region id
1297 :return:
1298 """
1299
1300 for map in of_maps:
1301 if ofc_id:
1302 map['ofc_id'] = ofc_id
1303 if switch_dpid:
1304 map['switch_dpid'] = switch_dpid
1305 if region:
1306 map['region'] = region
1307
1308 for of_map in of_maps:
1309 result, uuid = self.db.new_row('of_port_mappings', of_map, True)
1310 if result > 0:
1311 of_map["uuid"] = uuid
1312 else:
1313 raise ovimException(str(uuid), -result)
1314 return of_maps
1315
1316 def clear_of_port_mapping(self, db_filter={}):
1317 """
1318 Clear port mapping filtering using db_filter dict
1319 :param db_filter: Parameter to filter during remove process
1320 :return:
1321 """
1322 result, content = self.db.delete_row_by_dict(FROM='of_port_mappings', WHERE=db_filter)
1323 # delete_row_by_key
1324 if result >= 0:
1325 return content
1326 else:
1327 raise ovimException("Error deleting of_port_mappings with filter='{}'".format(str(db_filter)),
1328 HTTP_Internal_Server_Error)
1329
1330 def get_of_port_mappings(self, column=None, db_filter=None, db_limit=None):
1331 """
1332 Retrive port mapping from DB
1333 :param column:
1334 :param db_filter:
1335 :return:
1336 """
1337 result, content = self.db.get_table(SELECT=column, WHERE=db_filter, FROM='of_port_mappings', LIMIT=db_limit)
1338
1339 if result < 0:
1340 self.logger.error("get_of_port_mappings Error %d %s", result, content)
1341 raise ovimException(str(content), -result)
1342 else:
1343 return content
1344
mirabalb716ac52017-02-10 14:47:53 +01001345 def get_dhcp_controller(self):
1346 """
1347 Create an host_thread object for manage openvim controller and not create a thread for itself
1348 :return: dhcp_host openvim controller object
1349 """
1350
1351 if 'openvim_controller' in self.config['host_threads']:
1352 return self.config['host_threads']['openvim_controller']
1353
1354 bridge_ifaces = []
1355 controller_ip = self.config['ovs_controller_ip']
1356 ovs_controller_user = self.config['ovs_controller_user']
1357
1358 host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
1359 host_develop_mode = True if self.config['mode'] == 'development' else False
1360
1361 dhcp_host = ht.host_thread(name='openvim_controller', user=ovs_controller_user, host=controller_ip,
tiernoe0c28c12017-05-04 18:44:40 +02001362 db=self.config["db"], db_lock=self.config["db_lock"], test=host_test_mode,
mirabalb716ac52017-02-10 14:47:53 +01001363 image_path=self.config['image_path'], version=self.config['version'],
1364 host_id='openvim_controller', develop_mode=host_develop_mode,
tiernoe0c28c12017-05-04 18:44:40 +02001365 develop_bridge_iface=bridge_ifaces,
1366 logger_name=self.logger_name + ".host.controller",
tiernof135eff2017-04-19 19:11:53 +02001367 debug=self.config.get('log_level_host'))
tiernoe0c28c12017-05-04 18:44:40 +02001368 dhcp_host.start()
mirabalb716ac52017-02-10 14:47:53 +01001369 self.config['host_threads']['openvim_controller'] = dhcp_host
1370 if not host_test_mode:
1371 dhcp_host.ssh_connect()
1372 return dhcp_host
1373
mirabal18f5de32017-02-13 12:41:49 +01001374 def launch_dhcp_server(self, vlan, first_ip, last_ip, cidr, gateway):
mirabalb716ac52017-02-10 14:47:53 +01001375 """
1376 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
1377 :param vlan: vlan identifier
1378 :param first_ip: First dhcp range ip
1379 :param last_ip: Last dhcp range ip
1380 :param cidr: net cidr
mirabale9f6f1a2017-02-16 17:57:35 +01001381 :param gateway: net gateway
mirabalb716ac52017-02-10 14:47:53 +01001382 :return:
1383 """
1384 ip_tools = IPNetwork(cidr)
1385 dhcp_netmask = str(ip_tools.netmask)
1386 ip_range = [first_ip, last_ip]
1387
1388 dhcp_path = self.config['ovs_controller_file_path']
1389
1390 controller_host = self.get_dhcp_controller()
1391 controller_host.create_linux_bridge(vlan)
1392 controller_host.create_dhcp_interfaces(vlan, first_ip, dhcp_netmask)
mirabal18f5de32017-02-13 12:41:49 +01001393 controller_host.launch_dhcp_server(vlan, ip_range, dhcp_netmask, dhcp_path, gateway)
mirabalb716ac52017-02-10 14:47:53 +01001394
mirabald87877c2017-03-31 15:15:52 +02001395if __name__ == "__main__":
1396
1397 parser = argparse.ArgumentParser()
tierno46ca3a92017-04-05 19:49:24 +02001398 parser.add_argument("-v","--version", help="show ovim library version", action="store_true")
1399 parser.add_argument("--database-version", help="show required database version", action="store_true")
mirabald87877c2017-03-31 15:15:52 +02001400 args = parser.parse_args()
1401 if args.version:
1402 print ('openvimd version {} {}'.format(ovim.get_version(), ovim.get_version_date()))
1403 print ('(c) Copyright Telefonica')
tierno46ca3a92017-04-05 19:49:24 +02001404 elif args.database_version:
1405 print ('required database version: {}'.format(ovim.get_database_version()))
mirabalb716ac52017-02-10 14:47:53 +01001406