38c49dd83c5f5cad19c4273a50ae9481e0f9dad8
[osm/openvim.git] / ovim.py
1 # -*- coding: utf-8 -*-
2
3 ##
4 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
5 # This file is part of openvim
6 # All Rights Reserved.
7 #
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
11 #
12 # http://www.apache.org/licenses/LICENSE-2.0
13 #
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # License for the specific language governing permissions and limitations
18 # under the License.
19 #
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: nfvlabs@tid.es
22 ##
23
24 '''
25 This is the thread for the http server North API.
26 Two thread will be launched, with normal and administrative permissions.
27 '''
28
29 __author__ = "Alfonso Tierno, Leonardo Mirabal"
30 __date__ = "$06-Feb-2017 12:07:15$"
31
32 import threading
33 import vim_db
34 import logging
35 import threading
36 import imp
37 import host_thread as ht
38 import dhcp_thread as dt
39 import openflow_thread as oft
40 from netaddr import IPNetwork
41 from jsonschema import validate as js_v, exceptions as js_e
42
43 HTTP_Bad_Request = 400
44 HTTP_Unauthorized = 401
45 HTTP_Not_Found = 404
46 HTTP_Forbidden = 403
47 HTTP_Method_Not_Allowed = 405
48 HTTP_Not_Acceptable = 406
49 HTTP_Request_Timeout = 408
50 HTTP_Conflict = 409
51 HTTP_Service_Unavailable = 503
52 HTTP_Internal_Server_Error= 500
53
54
55 def convert_boolean(data, items):
56 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
57 It assumes that bandwidth is well formed
58 Attributes:
59 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
60 'items': tuple of keys to convert
61 Return:
62 None
63 '''
64 if type(data) is dict:
65 for k in data.keys():
66 if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
67 convert_boolean(data[k], items)
68 if k in items:
69 if type(data[k]) is str:
70 if data[k] == "false":
71 data[k] = False
72 elif data[k] == "true":
73 data[k] = True
74 if type(data) is tuple or type(data) is list:
75 for k in data:
76 if type(k) is dict or type(k) is tuple or type(k) is list:
77 convert_boolean(k, items)
78
79
80
81 class ovimException(Exception):
82 def __init__(self, message, http_code=HTTP_Bad_Request):
83 self.http_code = http_code
84 Exception.__init__(self, message)
85
86
87 class ovim():
88 running_info = {} #TODO OVIM move the info of running threads from config_dic to this static variable
89 def __init__(self, configuration):
90 self.config = configuration
91 self.logger = logging.getLogger(configuration["logger_name"])
92 self.db = None
93 self.db = self._create_database_connection()
94
95 def _create_database_connection(self):
96 db = vim_db.vim_db((self.config["network_vlan_range_start"], self.config["network_vlan_range_end"]),
97 self.config['log_level_db']);
98 if db.connect(self.config['db_host'], self.config['db_user'], self.config['db_passwd'],
99 self.config['db_name']) == -1:
100 # self.logger.error("Cannot connect to database %s at %s@%s", self.config['db_name'], self.config['db_user'],
101 # self.config['db_host'])
102 raise ovimException("Cannot connect to database {} at {}@{}".format(self.config['db_name'],
103 self.config['db_user'],
104 self.config['db_host']) )
105 return db
106
107 @staticmethod
108 def _check_dhcp_data_integrity(network):
109 """
110 Check if all dhcp parameter for anet are valid, if not will be calculated from cidr value
111 :param network: list with user nets paramters
112 :return:
113 """
114 if "cidr" in network:
115 cidr = network["cidr"]
116 ip_tools = IPNetwork(cidr)
117 cidr_len = ip_tools.prefixlen
118 if cidr_len > 29:
119 return False
120
121 ips = IPNetwork(cidr)
122 if "dhcp_first_ip" not in network:
123 network["dhcp_first_ip"] = str(ips[2])
124 if "dhcp_last_ip" not in network:
125 network["dhcp_last_ip"] = str(ips[-2])
126 if "gateway_ip" not in network:
127 network["gateway_ip"] = str(ips[1])
128
129 return True
130 else:
131 return False
132
133 @staticmethod
134 def _check_valid_uuid(uuid):
135 id_schema = {"type": "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
136 try:
137 js_v(uuid, id_schema)
138 return True
139 except js_e.ValidationError:
140 return False
141
142 def start_service(self):
143 #if self.running_info:
144 # return #TODO service can be checked and rebuild broken threads
145 r = self.db.get_db_version()
146 if r[0]<0:
147 raise ovimException("DATABASE is not a VIM one or it is a '0.0' version. Try to upgrade to version '{}' with "\
148 "'./database_utils/migrate_vim_db.sh'".format(self.config["database_version"]) )
149 elif r[1]!=self.config["database_version"]:
150 raise ovimException("DATABASE wrong version '{}'. Try to upgrade/downgrade to version '{}' with "\
151 "'./database_utils/migrate_vim_db.sh'".format(r[1], self.config["database_version"]) )
152
153 # create database connection for openflow threads
154 db_of = self._create_database_connection()
155 self.config["db"] = db_of
156 db_lock = threading.Lock()
157 self.config["db_lock"] = db_lock
158
159 # precreate interfaces; [bridge:<host_bridge_name>, VLAN used at Host, uuid of network camping in this bridge, speed in Gbit/s
160 self.config['dhcp_nets'] = []
161 self.config['bridge_nets'] = []
162 for bridge, vlan_speed in self.config["bridge_ifaces"].items():
163 # skip 'development_bridge'
164 if self.config['mode'] == 'development' and self.config['development_bridge'] == bridge:
165 continue
166 self.config['bridge_nets'].append([bridge, vlan_speed[0], vlan_speed[1], None])
167
168 # check if this bridge is already used (present at database) for a network)
169 used_bridge_nets = []
170 for brnet in self.config['bridge_nets']:
171 r, nets = db_of.get_table(SELECT=('uuid',), FROM='nets', WHERE={'provider': "bridge:" + brnet[0]})
172 if r > 0:
173 brnet[3] = nets[0]['uuid']
174 used_bridge_nets.append(brnet[0])
175 if self.config.get("dhcp_server"):
176 if brnet[0] in self.config["dhcp_server"]["bridge_ifaces"]:
177 self.config['dhcp_nets'].append(nets[0]['uuid'])
178 if len(used_bridge_nets) > 0:
179 self.logger.info("found used bridge nets: " + ",".join(used_bridge_nets))
180 # get nets used by dhcp
181 if self.config.get("dhcp_server"):
182 for net in self.config["dhcp_server"].get("nets", ()):
183 r, nets = db_of.get_table(SELECT=('uuid',), FROM='nets', WHERE={'name': net})
184 if r > 0:
185 self.config['dhcp_nets'].append(nets[0]['uuid'])
186
187 # get host list from data base before starting threads
188 r, hosts = db_of.get_table(SELECT=('name', 'ip_name', 'user', 'uuid'), FROM='hosts', WHERE={'status': 'ok'})
189 if r < 0:
190 raise ovimException("Cannot get hosts from database {}".format(hosts))
191 # create connector to the openflow controller
192 of_test_mode = False if self.config['mode'] == 'normal' or self.config['mode'] == "OF only" else True
193
194 if of_test_mode:
195 OF_conn = oft.of_test_connector({"of_debug": self.config['log_level_of']})
196 else:
197 # load other parameters starting by of_ from config dict in a temporal dict
198 temp_dict = {"of_ip": self.config['of_controller_ip'],
199 "of_port": self.config['of_controller_port'],
200 "of_dpid": self.config['of_controller_dpid'],
201 "of_debug": self.config['log_level_of']
202 }
203 for k, v in self.config.iteritems():
204 if type(k) is str and k[0:3] == "of_" and k[0:13] != "of_controller":
205 temp_dict[k] = v
206 if self.config['of_controller'] == 'opendaylight':
207 module = "ODL"
208 elif "of_controller_module" in self.config:
209 module = self.config["of_controller_module"]
210 else:
211 module = self.config['of_controller']
212 module_info = None
213 try:
214 module_info = imp.find_module(module)
215
216 OF_conn = imp.load_module("OF_conn", *module_info)
217 try:
218 OF_conn = OF_conn.OF_conn(temp_dict)
219 except Exception as e:
220 self.logger.error("Cannot open the Openflow controller '%s': %s", type(e).__name__, str(e))
221 if module_info and module_info[0]:
222 file.close(module_info[0])
223 exit(-1)
224 except (IOError, ImportError) as e:
225 if module_info and module_info[0]:
226 file.close(module_info[0])
227 self.logger.error(
228 "Cannot open openflow controller module '%s'; %s: %s; revise 'of_controller' field of configuration file.",
229 module, type(e).__name__, str(e))
230 raise ovimException("Cannot open openflow controller module '{}'; {}: {}; revise 'of_controller' field of configuration file.".fromat(
231 module, type(e).__name__, str(e)))
232
233
234 # create openflow thread
235 thread = oft.openflow_thread(OF_conn, of_test=of_test_mode, db=db_of, db_lock=db_lock,
236 pmp_with_same_vlan=self.config['of_controller_nets_with_same_vlan'],
237 debug=self.config['log_level_of'])
238 r, c = thread.OF_connector.obtain_port_correspondence()
239 if r < 0:
240 raise ovimException("Cannot get openflow information %s", c)
241 thread.start()
242 self.config['of_thread'] = thread
243
244 # create dhcp_server thread
245 host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
246 dhcp_params = self.config.get("dhcp_server")
247 if dhcp_params:
248 thread = dt.dhcp_thread(dhcp_params=dhcp_params, test=host_test_mode, dhcp_nets=self.config["dhcp_nets"],
249 db=db_of, db_lock=db_lock, debug=self.config['log_level_of'])
250 thread.start()
251 self.config['dhcp_thread'] = thread
252
253 # Create one thread for each host
254 host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
255 host_develop_mode = True if self.config['mode'] == 'development' else False
256 host_develop_bridge_iface = self.config.get('development_bridge', None)
257 self.config['host_threads'] = {}
258 for host in hosts:
259 host['image_path'] = '/opt/VNF/images/openvim'
260 thread = ht.host_thread(name=host['name'], user=host['user'], host=host['ip_name'], db=db_of, db_lock=db_lock,
261 test=host_test_mode, image_path=self.config['image_path'], version=self.config['version'],
262 host_id=host['uuid'], develop_mode=host_develop_mode,
263 develop_bridge_iface=host_develop_bridge_iface)
264 thread.start()
265 self.config['host_threads'][host['uuid']] = thread
266
267 # create ovs dhcp thread
268 result, content = self.db.get_table(FROM='nets')
269 if result < 0:
270 self.logger.error("http_get_ports Error %d %s", result, content)
271 raise ovimException(str(content), -result)
272
273 for net in content:
274 net_type = net['type']
275 if (net_type == 'bridge_data' or net_type == 'bridge_man') \
276 and net["provider"][:4] == 'OVS:' and net["enable_dhcp"] == "true":
277 self.launch_dhcp_server(net['vlan'],
278 net['dhcp_first_ip'],
279 net['dhcp_last_ip'],
280 net['cidr'],
281 net['gateway_ip'])
282
283 def stop_service(self):
284 threads = self.config.get('host_threads', {})
285 if 'of_thread' in self.config:
286 threads['of'] = (self.config['of_thread'])
287 if 'dhcp_thread' in self.config:
288 threads['dhcp'] = (self.config['dhcp_thread'])
289
290 for thread in threads.values():
291 thread.insert_task("exit")
292 for thread in threads.values():
293 thread.join()
294
295 def get_networks(self, columns=None, db_filter={}, limit=None):
296 """
297 Retreive networks available
298 :param columns: List with select query parameters
299 :param db_filter: List with where query parameters
300 :param limit: Query limit result
301 :return:
302 """
303 result, content = self.db.get_table(SELECT=columns, FROM='nets', WHERE=db_filter, LIMIT=limit)
304
305 if result < 0:
306 raise ovimException(str(content), -result)
307
308 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
309
310 return content
311
312 def show_network(self, network_id, db_filter={}):
313 """
314 Get network from DB by id
315 :param network_id: net Id
316 :param db_filter: List with where query parameters
317 :return:
318 """
319 # obtain data
320 if not network_id:
321 raise ovimException("Not network id was not found")
322 db_filter['uuid'] = network_id
323
324 result, content = self.db.get_table(FROM='nets', WHERE=db_filter, LIMIT=100)
325
326 if result < 0:
327 raise ovimException(str(content), -result)
328 elif result == 0:
329 raise ovimException("show_network network '%s' not found" % network_id, -result)
330 else:
331 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
332 # get ports from DB
333 result, ports = self.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
334 WHERE={'net_id': network_id}, LIMIT=100)
335 if len(ports) > 0:
336 content[0]['ports'] = ports
337
338 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
339 return content[0]
340
341 def new_network(self, network):
342 """
343 Create a net in DB
344 :return:
345 """
346 tenant_id = network.get('tenant_id')
347
348 if tenant_id:
349 result, _ = self.db.get_table(FROM='tenants', SELECT=('uuid',), WHERE={'uuid': tenant_id, "enabled": True})
350 if result <= 0:
351 raise ovimException("set_network error, no tenant founded", -result)
352
353 bridge_net = None
354 # check valid params
355 net_provider = network.get('provider')
356 net_type = network.get('type')
357 net_vlan = network.get("vlan")
358 net_bind_net = network.get("bind_net")
359 net_bind_type = network.get("bind_type")
360 name = network["name"]
361
362 # check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
363 vlan_index = name.rfind(":")
364 if not net_bind_net and not net_bind_type and vlan_index > 1:
365 try:
366 vlan_tag = int(name[vlan_index + 1:])
367 if not vlan_tag and vlan_tag < 4096:
368 net_bind_net = name[:vlan_index]
369 net_bind_type = "vlan:" + name[vlan_index + 1:]
370 except:
371 pass
372
373 if net_bind_net:
374 # look for a valid net
375 if self._check_valid_uuid(net_bind_net):
376 net_bind_key = "uuid"
377 else:
378 net_bind_key = "name"
379 result, content = self.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net})
380 if result < 0:
381 raise ovimException(' getting nets from db ' + content, HTTP_Internal_Server_Error)
382 elif result == 0:
383 raise ovimException(" bind_net %s '%s'not found" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
384 elif result > 1:
385 raise ovimException(" more than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
386 network["bind_net"] = content[0]["uuid"]
387
388 if net_bind_type:
389 if net_bind_type[0:5] != "vlan:":
390 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>'", HTTP_Bad_Request)
391 if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:]) <= 0:
392 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095",
393 HTTP_Bad_Request)
394 network["bind_type"] = net_bind_type
395
396 if net_provider:
397 if net_provider[:9] == "openflow:":
398 if net_type:
399 if net_type != "ptp" and net_type != "data":
400 raise ovimException(" only 'ptp' or 'data' net types can be bound to 'openflow'",
401 HTTP_Bad_Request)
402 else:
403 net_type = 'data'
404 else:
405 if net_type:
406 if net_type != "bridge_man" and net_type != "bridge_data":
407 raise ovimException("Only 'bridge_man' or 'bridge_data' net types can be bound "
408 "to 'bridge', 'macvtap' or 'default", HTTP_Bad_Request)
409 else:
410 net_type = 'bridge_man'
411
412 if not net_type:
413 net_type = 'bridge_man'
414
415 if net_provider:
416 if net_provider[:7] == 'bridge:':
417 # check it is one of the pre-provisioned bridges
418 bridge_net_name = net_provider[7:]
419 for brnet in self.config['bridge_nets']:
420 if brnet[0] == bridge_net_name: # free
421 if not brnet[3]:
422 raise ovimException("invalid 'provider:physical', "
423 "bridge '%s' is already used" % bridge_net_name, HTTP_Conflict)
424 bridge_net = brnet
425 net_vlan = brnet[1]
426 break
427 # if bridge_net==None:
428 # bottle.abort(HTTP_Bad_Request, "invalid 'provider:physical', bridge '%s' is not one of the
429 # provisioned 'bridge_ifaces' in the configuration file" % bridge_net_name)
430 # return
431
432 elif self.config['network_type'] == 'bridge' and (net_type == 'bridge_data' or net_type == 'bridge_man'):
433 # look for a free precreated nets
434 for brnet in self.config['bridge_nets']:
435 if not brnet[3]: # free
436 if not bridge_net:
437 if net_type == 'bridge_man': # look for the smaller speed
438 if brnet[2] < bridge_net[2]:
439 bridge_net = brnet
440 else: # look for the larger speed
441 if brnet[2] > bridge_net[2]:
442 bridge_net = brnet
443 else:
444 bridge_net = brnet
445 net_vlan = brnet[1]
446 if not bridge_net:
447 raise ovimException("Max limits of bridge networks reached. Future versions of VIM "
448 "will overcome this limit", HTTP_Bad_Request)
449 else:
450 self.logger.debug("using net " + bridge_net)
451 net_provider = "bridge:" + bridge_net[0]
452 net_vlan = bridge_net[1]
453 elif net_type == 'bridge_data' or net_type == 'bridge_man' and self.config['network_type'] == 'ovs':
454 net_provider = 'OVS'
455 if not net_vlan and (net_type == "data" or net_type == "ptp" or net_provider == "OVS"):
456 net_vlan = self.db.get_free_net_vlan()
457 if net_vlan < 0:
458 raise ovimException("Error getting an available vlan", HTTP_Internal_Server_Error)
459 if net_provider == 'OVS':
460 net_provider = 'OVS' + ":" + str(net_vlan)
461
462 network['provider'] = net_provider
463 network['type'] = net_type
464 network['vlan'] = net_vlan
465 dhcp_integrity = True
466 if 'enable_dhcp' in network and network['enable_dhcp']:
467 dhcp_integrity = self._check_dhcp_data_integrity(network)
468
469 result, content = self.db.new_row('nets', network, True, True)
470
471 if result >= 0 and dhcp_integrity:
472 if bridge_net:
473 bridge_net[3] = content
474 if self.config.get("dhcp_server") and self.config['network_type'] == 'bridge':
475 if network["name"] in self.config["dhcp_server"].get("nets", ()):
476 self.config["dhcp_nets"].append(content)
477 self.logger.debug("dhcp_server: add new net", content)
478 elif not bridge_net and bridge_net[0] in self.config["dhcp_server"].get("bridge_ifaces", ()):
479 self.config["dhcp_nets"].append(content)
480 self.logger.debug("dhcp_server: add new net", content, content)
481 return content
482 else:
483 raise ovimException("Error posting network", HTTP_Internal_Server_Error)
484 # TODO kei change update->edit
485
486 def edit_network(self, network_id, network):
487 """
488 Update entwork data byt id
489 :return:
490 """
491 # Look for the previous data
492 where_ = {'uuid': network_id}
493 result, network_old = self.db.get_table(FROM='nets', WHERE=where_)
494 if result < 0:
495 raise ovimException("Error updating network %s" % network_old, HTTP_Internal_Server_Error)
496 elif result == 0:
497 raise ovimException('network %s not found' % network_id, HTTP_Not_Found)
498 # get ports
499 nbports, content = self.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
500 WHERE={'net_id': network_id}, LIMIT=100)
501 if result < 0:
502 raise ovimException("http_put_network_id error %d %s" % (result, network_old), HTTP_Internal_Server_Error)
503 if nbports > 0:
504 if 'type' in network and network['type'] != network_old[0]['type']:
505 raise ovimException("Can not change type of network while having ports attached",
506 HTTP_Method_Not_Allowed)
507 if 'vlan' in network and network['vlan'] != network_old[0]['vlan']:
508 raise ovimException("Can not change vlan of network while having ports attached",
509 HTTP_Method_Not_Allowed)
510
511 # check valid params
512 net_provider = network.get('provider', network_old[0]['provider'])
513 net_type = network.get('type', network_old[0]['type'])
514 net_bind_net = network.get("bind_net")
515 net_bind_type = network.get("bind_type")
516 if net_bind_net:
517 # look for a valid net
518 if self._check_valid_uuid(net_bind_net):
519 net_bind_key = "uuid"
520 else:
521 net_bind_key = "name"
522 result, content = self.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net})
523 if result < 0:
524 raise ovimException('Getting nets from db ' + content, HTTP_Internal_Server_Error)
525 elif result == 0:
526 raise ovimException("bind_net %s '%s'not found" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
527 elif result > 1:
528 raise ovimException("More than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net),
529 HTTP_Bad_Request)
530 network["bind_net"] = content[0]["uuid"]
531 if net_bind_type:
532 if net_bind_type[0:5] != "vlan:":
533 raise ovimException("Bad format for 'bind_type', must be 'vlan:<tag>'", HTTP_Bad_Request)
534 if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:]) <= 0:
535 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095",
536 HTTP_Bad_Request)
537 if net_provider:
538 if net_provider[:9] == "openflow:":
539 if net_type != "ptp" and net_type != "data":
540 raise ovimException("Only 'ptp' or 'data' net types can be bound to 'openflow'", HTTP_Bad_Request)
541 else:
542 if net_type != "bridge_man" and net_type != "bridge_data":
543 raise ovimException("Only 'bridge_man' or 'bridge_data' net types can be bound to "
544 "'bridge', 'macvtap' or 'default", HTTP_Bad_Request)
545
546 # insert in data base
547 result, content = self.db.update_rows('nets', network, WHERE={'uuid': network_id}, log=True)
548 if result >= 0:
549 # if result > 0 and nbports>0 and 'admin_state_up' in network
550 # and network['admin_state_up'] != network_old[0]['admin_state_up']:
551 if result > 0:
552 r, c = self.config['of_thread'].insert_task("update-net", network_id)
553 if r < 0:
554 raise ovimException("Error while launching openflow rules %s" % c, HTTP_Internal_Server_Error)
555 if self.config.get("dhcp_server"):
556 if network_id in self.config["dhcp_nets"]:
557 self.config["dhcp_nets"].remove(network_id)
558 if network.get("name", network_old["name"]) in self.config["dhcp_server"].get("nets", ()):
559 self.config["dhcp_nets"].append(network_id)
560 else:
561 net_bind = network.get("bind", network_old["bind"])
562 if net_bind and net_bind[:7] == "bridge:" and net_bind[7:] in self.config["dhcp_server"].get(
563 "bridge_ifaces", ()):
564 self.config["dhcp_nets"].append(network_id)
565 return network_id
566 else:
567 raise ovimException(content, -result)
568
569 def delete_network(self, network_id):
570 """
571 Delete network by network id
572 :param network_id: network id
573 :return:
574 """
575
576 # delete from the data base
577 result, content = self.db.delete_row('nets', network_id)
578
579 if result == 0:
580 raise ovimException("Network %s not found " % network_id, HTTP_Not_Found)
581 elif result > 0:
582 for brnet in self.config['bridge_nets']:
583 if brnet[3] == network_id:
584 brnet[3] = None
585 break
586 if self.config.get("dhcp_server") and network_id in self.config["dhcp_nets"]:
587 self.config["dhcp_nets"].remove(network_id)
588 return content
589 else:
590 raise ovimException("Error deleting network %s" % network_id, HTTP_Internal_Server_Error)
591
592 def get_openflow_rules(self, network_id=None):
593 """
594 Get openflow id from DB
595 :param network_id: Network id, if none all networks will be retrieved
596 :return: Return a list with Openflow rules per net
597 """
598 # ignore input data
599 if not network_id:
600 where_ = {}
601 else:
602 where_ = {"net_id": network_id}
603
604 result, content = self.db.get_table(
605 SELECT=("name", "net_id", "priority", "vlan_id", "ingress_port", "src_mac", "dst_mac", "actions"),
606 WHERE=where_, FROM='of_flows')
607
608 if result < 0:
609 raise ovimException(str(content), -result)
610 return content
611
612 def edit_openflow_rules(self, network_id=None):
613
614 """
615 To make actions over the net. The action is to reinstall the openflow rules
616 network_id can be 'all'
617 :param network_id: Network id, if none all networks will be retrieved
618 :return : Number of nets updated
619 """
620
621 # ignore input data
622 if not network_id:
623 where_ = {}
624 else:
625 where_ = {"uuid": network_id}
626 result, content = self.db.get_table(SELECT=("uuid", "type"), WHERE=where_, FROM='nets')
627
628 if result < 0:
629 raise ovimException(str(content), -result)
630
631 for net in content:
632 if net["type"] != "ptp" and net["type"] != "data":
633 result -= 1
634 continue
635 r, c = self.config['of_thread'].insert_task("update-net", net['uuid'])
636 if r < 0:
637 raise ovimException(str(c), -r)
638 return result
639
640 def delete_openflow_rules(self):
641 """
642 To make actions over the net. The action is to delete ALL openflow rules
643 :return: return operation result
644 """
645 # ignore input data
646 r, c = self.config['of_thread'].insert_task("clear-all")
647 if r < 0:
648 raise ovimException(str(c), -r)
649 return r
650
651 def get_openflow_ports(self):
652 """
653 Obtain switch ports names of openflow controller
654 :return: Return flow ports in DB
655 """
656 data = {'ports': self.config['of_thread'].OF_connector.pp2ofi}
657 return data
658
659 def get_ports(self, columns=None, filter={}, limit=None):
660 # result, content = my.db.get_ports(where_)
661 result, content = self.db.get_table(SELECT=columns, WHERE=filter, FROM='ports', LIMIT=limit)
662 if result < 0:
663 self.logger.error("http_get_ports Error %d %s", result, content)
664 raise ovimException(str(content), -result)
665 else:
666 convert_boolean(content, ('admin_state_up',))
667 return content
668
669 def new_port(self, port_data):
670 port_data['type'] = 'external'
671 if port_data.get('net_id'):
672 # check that new net has the correct type
673 result, new_net = self.db.check_target_net(port_data['net_id'], None, 'external')
674 if result < 0:
675 raise ovimException(str(new_net), -result)
676 # insert in data base
677 result, uuid = self.db.new_row('ports', port_data, True, True)
678 if result > 0:
679 if 'net_id' in port_data:
680 r, c = self.config['of_thread'].insert_task("update-net", port_data['net_id'])
681 if r < 0:
682 self.logger.error("Cannot insert a task for updating network '$s' %s", port_data['net_id'], c)
683 #TODO put network in error status
684 return uuid
685 else:
686 raise ovimException(str(uuid), -result)
687
688 def delete_port(self, port_id):
689 # Look for the previous port data
690 result, ports = self.db.get_table(WHERE={'uuid': port_id, "type": "external"}, FROM='ports')
691 if result < 0:
692 raise ovimException("Cannot get port info from database: {}".format(ports), http_code=-result)
693 # delete from the data base
694 result, content = self.db.delete_row('ports', port_id)
695 if result == 0:
696 raise ovimException("External port '{}' not found".format(port_id), http_code=HTTP_Not_Found)
697 elif result < 0:
698 raise ovimException("Cannot delete port from database: {}".format(content), http_code=-result)
699 # update network
700 network = ports[0].get('net_id', None)
701 if network:
702 # change of net.
703 r, c = self.config['of_thread'].insert_task("update-net", network)
704 if r < 0:
705 self.logger.error("Cannot insert a task for updating network '$s' %s", network, c)
706 return content
707
708 def edit_port(self, port_id, port_data, admin=True):
709 # Look for the previous port data
710 result, content = self.db.get_table(FROM="ports", WHERE={'uuid': port_id})
711 if result < 0:
712 raise ovimException("Cannot get port info from database: {}".format(content), http_code=-result)
713 elif result == 0:
714 raise ovimException("Port '{}' not found".format(port_id), http_code=HTTP_Not_Found)
715 port = content[0]
716 nets = []
717 host_id = None
718 result = 1
719 if 'net_id' in port_data:
720 # change of net.
721 old_net = port.get('net_id', None)
722 new_net = port_data['net_id']
723 if old_net != new_net:
724
725 if new_net:
726 nets.append(new_net) # put first the new net, so that new openflow rules are created before removing the old ones
727 if old_net:
728 nets.append(old_net)
729 if port['type'] == 'instance:bridge' or port['type'] == 'instance:ovs':
730 raise ovimException("bridge interfaces cannot be attached to a different net", http_code=HTTP_Forbidden)
731 elif port['type'] == 'external' and not admin:
732 raise ovimException("Needed admin privileges",http_code=HTTP_Unauthorized)
733 if new_net:
734 # check that new net has the correct type
735 result, new_net_dict = self.db.check_target_net(new_net, None, port['type'])
736 if result < 0:
737 raise ovimException("Error {}".format(new_net_dict), http_code=HTTP_Conflict)
738 # change VLAN for SR-IOV ports
739 if result >= 0 and port["type"] == "instance:data" and port["model"] == "VF": # TODO consider also VFnotShared
740 if new_net:
741 port_data["vlan"] = None
742 else:
743 port_data["vlan"] = new_net_dict["vlan"]
744 # get host where this VM is allocated
745 result, content = self.db.get_table(FROM="instances", WHERE={"uuid": port["instance_id"]})
746 if result > 0:
747 host_id = content[0]["host_id"]
748
749 # insert in data base
750 if result >= 0:
751 result, content = self.db.update_rows('ports', port_data, WHERE={'uuid': port_id}, log=False)
752
753 # Insert task to complete actions
754 if result > 0:
755 for net_id in nets:
756 r, v = self.config['of_thread'].insert_task("update-net", net_id)
757 if r < 0:
758 self.logger.error("Error updating network '{}' {}".format(r,v))
759 # TODO Do something if fails
760 if host_id:
761 r, v = self.config['host_threads'][host_id].insert_task("edit-iface", port_id, old_net, new_net)
762 if r < 0:
763 self.logger.error("Error updating network '{}' {}".format(r,v))
764 # TODO Do something if fails
765 if result >= 0:
766 return port_id
767 else:
768 raise ovimException("Error {}".format(content), http_code=-result)
769
770 def new_of_controller(self, ofc_data):
771 """
772 Create a new openflow controller into DB
773 :param ofc_data: Dict openflow controller data
774 :return: openflow controller dpid
775 """
776
777 result, content = self.db.new_row('ofcs', ofc_data, True, True)
778 if result < 0:
779 raise ovimException("New ofc Error %s" % content, HTTP_Internal_Server_Error)
780 return content
781
782 def edit_of_controller(self, of_id, ofc_data):
783 """
784 Edit an openflow controller entry from DB
785 :return:
786 """
787 if not ofc_data:
788 raise ovimException("No data received during uptade OF contorller", http_code=HTTP_Internal_Server_Error)
789
790 old_of_controller = self.show_of_controller(of_id)
791
792 if old_of_controller:
793 result, content = self.db.update_rows('ofcs', ofc_data, WHERE={'uuid': of_id}, log=False)
794 if result >= 0:
795 return ofc_data
796 else:
797 raise ovimException("Error uptating OF contorller with uuid {}".format(of_id),
798 http_code=-result)
799 else:
800 raise ovimException("Error uptating OF contorller with uuid {}".format(of_id),
801 http_code=HTTP_Internal_Server_Error)
802
803 def delete_of_controller(self, of_id):
804 """
805 Delete an openflow controller from DB.
806 :param of_id: openflow controller dpid
807 :return:
808 """
809
810 result, content = self.db.delete_row_by_key("ofcs", "uuid", of_id)
811 if result < 0:
812 raise ovimException("Cannot delete ofc from database: {}".format(content), http_code=-result)
813 elif result == 0:
814 raise ovimException("ofc {} not found ".format(content), http_code=HTTP_Not_Found)
815 return content
816
817 def show_of_controller(self, uuid):
818 """
819 Show an openflow controller by dpid from DB.
820 :param db_filter: List with where query parameters
821 :return:
822 """
823
824 result, content = self.db.get_table(FROM='ofcs', WHERE={"uuid": uuid}, LIMIT=100)
825
826 if result == 0:
827 raise ovimException("Openflow controller with uuid '{}' not found".format(uuid),
828 http_code=HTTP_Not_Found)
829 elif result < 0:
830 raise ovimException("Openflow controller with uuid '{}' error".format(uuid),
831 http_code=HTTP_Internal_Server_Error)
832 return content
833
834 def get_of_controllers(self, columns=None, db_filter={}):
835 """
836 Show an openflow controllers from DB.
837 :param columns: List with SELECT query parameters
838 :param db_filter: List with where query parameters
839 :return:
840 """
841 result, content = self.db.get_table(SELECT=columns, FROM='ofcs', WHERE=db_filter, LIMIT=100)
842
843 if result < 0:
844 raise ovimException(str(content), -result)
845
846 return content
847
848 def get_dhcp_controller(self):
849 """
850 Create an host_thread object for manage openvim controller and not create a thread for itself
851 :return: dhcp_host openvim controller object
852 """
853
854 if 'openvim_controller' in self.config['host_threads']:
855 return self.config['host_threads']['openvim_controller']
856
857 bridge_ifaces = []
858 controller_ip = self.config['ovs_controller_ip']
859 ovs_controller_user = self.config['ovs_controller_user']
860
861 host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
862 host_develop_mode = True if self.config['mode'] == 'development' else False
863
864 dhcp_host = ht.host_thread(name='openvim_controller', user=ovs_controller_user, host=controller_ip,
865 db=self.config['db'],
866 db_lock=self.config['db_lock'], test=host_test_mode,
867 image_path=self.config['image_path'], version=self.config['version'],
868 host_id='openvim_controller', develop_mode=host_develop_mode,
869 develop_bridge_iface=bridge_ifaces)
870
871 self.config['host_threads']['openvim_controller'] = dhcp_host
872 if not host_test_mode:
873 dhcp_host.ssh_connect()
874 return dhcp_host
875
876 def launch_dhcp_server(self, vlan, first_ip, last_ip, cidr, gateway):
877 """
878 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
879 :param vlan: vlan identifier
880 :param first_ip: First dhcp range ip
881 :param last_ip: Last dhcp range ip
882 :param cidr: net cidr
883 :param gateway: net gateway
884 :return:
885 """
886 ip_tools = IPNetwork(cidr)
887 dhcp_netmask = str(ip_tools.netmask)
888 ip_range = [first_ip, last_ip]
889
890 dhcp_path = self.config['ovs_controller_file_path']
891
892 controller_host = self.get_dhcp_controller()
893 controller_host.create_linux_bridge(vlan)
894 controller_host.create_dhcp_interfaces(vlan, first_ip, dhcp_netmask)
895 controller_host.launch_dhcp_server(vlan, ip_range, dhcp_netmask, dhcp_path, gateway)
896
897