02114a18d1592c8a0a7b31bc20f02c324ebb489c
[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 of_module = {}
90
91 def __init__(self, configuration):
92 self.config = configuration
93 self.logger = logging.getLogger(configuration["logger_name"])
94 self.db = None
95 self.db = self._create_database_connection()
96 self.db_lock = None
97 self.db_of = None
98 self.of_test_mode = False
99
100 def _create_database_connection(self):
101 db = vim_db.vim_db((self.config["network_vlan_range_start"], self.config["network_vlan_range_end"]),
102 self.config['log_level_db']);
103 if db.connect(self.config['db_host'], self.config['db_user'], self.config['db_passwd'],
104 self.config['db_name']) == -1:
105 # self.logger.error("Cannot connect to database %s at %s@%s", self.config['db_name'], self.config['db_user'],
106 # self.config['db_host'])
107 raise ovimException("Cannot connect to database {} at {}@{}".format(self.config['db_name'],
108 self.config['db_user'],
109 self.config['db_host']) )
110 return db
111
112 @staticmethod
113 def _check_dhcp_data_integrity(network):
114 """
115 Check if all dhcp parameter for anet are valid, if not will be calculated from cidr value
116 :param network: list with user nets paramters
117 :return:
118 """
119 if "cidr" in network:
120 cidr = network["cidr"]
121 ip_tools = IPNetwork(cidr)
122 cidr_len = ip_tools.prefixlen
123 if cidr_len > 29:
124 return False
125
126 ips = IPNetwork(cidr)
127 if "dhcp_first_ip" not in network:
128 network["dhcp_first_ip"] = str(ips[2])
129 if "dhcp_last_ip" not in network:
130 network["dhcp_last_ip"] = str(ips[-2])
131 if "gateway_ip" not in network:
132 network["gateway_ip"] = str(ips[1])
133
134 return True
135 else:
136 return False
137
138 @staticmethod
139 def _check_valid_uuid(uuid):
140 id_schema = {"type": "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
141 try:
142 js_v(uuid, id_schema)
143 return True
144 except js_e.ValidationError:
145 return False
146
147 def start_service(self):
148 """
149 Start ovim services
150 :return:
151 """
152 # if self.running_info:
153 # return #TODO service can be checked and rebuild broken threads
154 r = self.db.get_db_version()
155 if r[0] < 0:
156 raise ovimException("DATABASE is not a VIM one or it is a '0.0' version. Try to upgrade to version '{}' with "\
157 "'./database_utils/migrate_vim_db.sh'".format(self.config["database_version"]) )
158 elif r[1] != self.config["database_version"]:
159 raise ovimException("DATABASE wrong version '{}'. Try to upgrade/downgrade to version '{}' with "\
160 "'./database_utils/migrate_vim_db.sh'".format(r[1], self.config["database_version"]) )
161
162 # create database connection for openflow threads
163 self.db_of = self._create_database_connection()
164 self.config["db"] = self.db_of
165 self.db_lock = threading.Lock()
166 self.config["db_lock"] = self.db_lock
167
168 self.of_test_mode = False if self.config['mode'] == 'normal' or self.config['mode'] == "OF only" else True
169 # precreate interfaces; [bridge:<host_bridge_name>, VLAN used at Host, uuid of network camping in this bridge,
170 # speed in Gbit/s
171
172 self.config['dhcp_nets'] = []
173 self.config['bridge_nets'] = []
174 for bridge, vlan_speed in self.config["bridge_ifaces"].items():
175 # skip 'development_bridge'
176 if self.config['mode'] == 'development' and self.config['development_bridge'] == bridge:
177 continue
178 self.config['bridge_nets'].append([bridge, vlan_speed[0], vlan_speed[1], None])
179
180 # check if this bridge is already used (present at database) for a network)
181 used_bridge_nets = []
182 for brnet in self.config['bridge_nets']:
183 r, nets = self.db_of.get_table(SELECT=('uuid',), FROM='nets', WHERE={'provider': "bridge:" + brnet[0]})
184 if r > 0:
185 brnet[3] = nets[0]['uuid']
186 used_bridge_nets.append(brnet[0])
187 if self.config.get("dhcp_server"):
188 if brnet[0] in self.config["dhcp_server"]["bridge_ifaces"]:
189 self.config['dhcp_nets'].append(nets[0]['uuid'])
190 if len(used_bridge_nets) > 0:
191 self.logger.info("found used bridge nets: " + ",".join(used_bridge_nets))
192 # get nets used by dhcp
193 if self.config.get("dhcp_server"):
194 for net in self.config["dhcp_server"].get("nets", ()):
195 r, nets = self.db_of.get_table(SELECT=('uuid',), FROM='nets', WHERE={'name': net})
196 if r > 0:
197 self.config['dhcp_nets'].append(nets[0]['uuid'])
198
199 # OFC default
200 self._start_ofc_default_task()
201
202 # OFC per tenant in DB
203 self._start_of_db_tasks()
204
205 # create dhcp_server thread
206 host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
207 dhcp_params = self.config.get("dhcp_server")
208 if dhcp_params:
209 thread = dt.dhcp_thread(dhcp_params=dhcp_params, test=host_test_mode, dhcp_nets=self.config["dhcp_nets"],
210 db=self.db_of, db_lock=self.db_lock, debug=self.config['log_level_of'])
211 thread.start()
212 self.config['dhcp_thread'] = thread
213
214 # Create one thread for each host
215 host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
216 host_develop_mode = True if self.config['mode'] == 'development' else False
217 host_develop_bridge_iface = self.config.get('development_bridge', None)
218
219 # get host list from data base before starting threads
220 r, hosts = self.db_of.get_table(SELECT=('name', 'ip_name', 'user', 'uuid'), FROM='hosts', WHERE={'status': 'ok'})
221 if r < 0:
222 raise ovimException("Cannot get hosts from database {}".format(hosts))
223
224 self.config['host_threads'] = {}
225 for host in hosts:
226 host['image_path'] = '/opt/VNF/images/openvim'
227 thread = ht.host_thread(name=host['name'], user=host['user'], host=host['ip_name'], db=self.db_of,
228 db_lock=self.db_lock, test=host_test_mode, image_path=self.config['image_path'],
229 version=self.config['version'], host_id=host['uuid'], develop_mode=host_develop_mode,
230 develop_bridge_iface=host_develop_bridge_iface)
231 thread.start()
232 self.config['host_threads'][host['uuid']] = thread
233
234 # create ovs dhcp thread
235 result, content = self.db.get_table(FROM='nets')
236 if result < 0:
237 self.logger.error("http_get_ports Error %d %s", result, content)
238 raise ovimException(str(content), -result)
239
240 for net in content:
241 net_type = net['type']
242 if (net_type == 'bridge_data' or net_type == 'bridge_man') \
243 and net["provider"][:4] == 'OVS:' and net["enable_dhcp"] == "true":
244 self.launch_dhcp_server(net['vlan'],
245 net['dhcp_first_ip'],
246 net['dhcp_last_ip'],
247 net['cidr'],
248 net['gateway_ip'])
249
250 def _start_of_db_tasks(self):
251 """
252 Start ofc task for existing ofcs in database
253 :param db_of:
254 :param db_lock:
255 :return:
256 """
257 ofcs = self.get_of_controllers()
258
259 for ofc in ofcs:
260 of_conn = self._load_of_module(ofc)
261 # create ofc thread per of controller
262 self._create_ofc_task(ofc['uuid'], ofc['dpid'], of_conn)
263
264 def _create_ofc_task(self, ofc_uuid, dpid, of_conn):
265 """
266 Create an ofc thread for handle each sdn controllers
267 :param ofc_uuid: sdn controller uuid
268 :param dpid: sdn controller dpid
269 :param of_conn: OF_conn module
270 :return:
271 """
272 if 'ofcs_thread' not in self.config and 'ofcs_thread_dpid' not in self.config:
273 ofcs_threads = {}
274 ofcs_thread_dpid = []
275 else:
276 ofcs_threads = self.config['ofcs_thread']
277 ofcs_thread_dpid = self.config['ofcs_thread_dpid']
278
279 if ofc_uuid not in ofcs_threads:
280 ofc_thread = self._create_ofc_thread(of_conn, ofc_uuid)
281 if ofc_uuid == "Default":
282 self.config['of_thread'] = ofc_thread
283
284 ofcs_threads[ofc_uuid] = ofc_thread
285 self.config['ofcs_thread'] = ofcs_threads
286
287 ofcs_thread_dpid.append({dpid: ofc_thread})
288 self.config['ofcs_thread_dpid'] = ofcs_thread_dpid
289
290 def _start_ofc_default_task(self):
291 """
292 Create default ofc thread
293 """
294 if 'of_controller' not in self.config \
295 and 'of_controller_ip' not in self.config \
296 and 'of_controller_port' not in self.config \
297 and 'of_controller_dpid' not in self.config:
298 return
299
300 # OF THREAD
301 db_config = {}
302 db_config['ip'] = self.config.get('of_controller_ip')
303 db_config['port'] = self.config.get('of_controller_port')
304 db_config['dpid'] = self.config.get('of_controller_dpid')
305 db_config['type'] = self.config.get('of_controller')
306 db_config['user'] = self.config.get('of_user')
307 db_config['password'] = self.config.get('of_password')
308
309 # create connector to the openflow controller
310 # load other parameters starting by of_ from config dict in a temporal dict
311
312 of_conn = self._load_of_module(db_config)
313 # create openflow thread
314 self._create_ofc_task("Default", db_config['dpid'], of_conn)
315
316 def _load_of_module(self, db_config):
317 """
318 import python module for each SDN controller supported
319 :param default: SDN dn information
320 :return: Module
321 """
322 if not db_config:
323 raise ovimException("No module found it", HTTP_Internal_Server_Error)
324
325 module_info = None
326
327 try:
328 if self.of_test_mode:
329 return oft.of_test_connector({"name": db_config['type'], "dpid": db_config['dpid'],
330 "of_debug": self.config['log_level_of']})
331 temp_dict = {}
332
333 if db_config:
334 temp_dict['of_ip'] = db_config['ip']
335 temp_dict['of_port'] = db_config['port']
336 temp_dict['of_dpid'] = db_config['dpid']
337 temp_dict['of_controller'] = db_config['type']
338
339 temp_dict['of_debug'] = self.config['log_level_of']
340
341 if temp_dict['of_controller'] == 'opendaylight':
342 module = "ODL"
343 else:
344 module = temp_dict['of_controller']
345
346 if module not in ovim.of_module:
347 module_info = imp.find_module(module)
348 of_conn_module = imp.load_module("OF_conn", *module_info)
349 ovim.of_module[module] = of_conn_module
350 else:
351 of_conn_module = ovim.of_module[module]
352
353 try:
354 return of_conn_module.OF_conn(temp_dict)
355 except Exception as e:
356 self.logger.error("Cannot open the Openflow controller '%s': %s", type(e).__name__, str(e))
357 if module_info and module_info[0]:
358 file.close(module_info[0])
359 raise ovimException("Cannot open the Openflow controller '{}': '{}'".format(type(e).__name__, str(e)),
360 HTTP_Internal_Server_Error)
361 except (IOError, ImportError) as e:
362 if module_info and module_info[0]:
363 file.close(module_info[0])
364 self.logger.error("Cannot open openflow controller module '%s'; %s: %s; revise 'of_controller' "
365 "field of configuration file.", module, type(e).__name__, str(e))
366 raise ovimException("Cannot open openflow controller module '{}'; {}: {}; revise 'of_controller' "
367 "field of configuration file.".format(module, type(e).__name__, str(e)),
368 HTTP_Internal_Server_Error)
369
370 def _create_ofc_thread(self, of_conn, ofc_uuid="Default"):
371 """
372 Create and launch a of thread
373 :return: thread obj
374 """
375 # create openflow thread
376
377 if 'of_controller_nets_with_same_vlan' in self.config:
378 ofc_net_same_vlan = self.config['of_controller_nets_with_same_vlan']
379 else:
380 ofc_net_same_vlan = False
381
382 thread = oft.openflow_thread(ofc_uuid, of_conn, of_test=self.of_test_mode, db=self.db_of, db_lock=self.db_lock,
383 pmp_with_same_vlan=ofc_net_same_vlan, debug=self.config['log_level_of'])
384 #r, c = thread.OF_connector.obtain_port_correspondence()
385 #if r < 0:
386 # raise ovimException("Cannot get openflow information %s", c)
387 thread.start()
388 return thread
389
390 def stop_service(self):
391 threads = self.config.get('host_threads', {})
392 if 'of_thread' in self.config:
393 threads['of'] = (self.config['of_thread'])
394 if 'ofcs_thread' in self.config:
395 ofcs_thread = self.config['ofcs_thread']
396 for ofc in ofcs_thread:
397 threads[ofc] = ofcs_thread[ofc]
398
399 if 'dhcp_thread' in self.config:
400 threads['dhcp'] = (self.config['dhcp_thread'])
401
402 for thread in threads.values():
403 thread.insert_task("exit")
404 for thread in threads.values():
405 thread.join()
406
407 def get_networks(self, columns=None, db_filter={}, limit=None):
408 """
409 Retreive networks available
410 :param columns: List with select query parameters
411 :param db_filter: List with where query parameters
412 :param limit: Query limit result
413 :return:
414 """
415 result, content = self.db.get_table(SELECT=columns, FROM='nets', WHERE=db_filter, LIMIT=limit)
416
417 if result < 0:
418 raise ovimException(str(content), -result)
419
420 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
421
422 return content
423
424 def show_network(self, network_id, db_filter={}):
425 """
426 Get network from DB by id
427 :param network_id: net Id
428 :param db_filter: List with where query parameters
429 :return:
430 """
431 # obtain data
432 if not network_id:
433 raise ovimException("Not network id was not found")
434 db_filter['uuid'] = network_id
435
436 result, content = self.db.get_table(FROM='nets', WHERE=db_filter, LIMIT=100)
437
438 if result < 0:
439 raise ovimException(str(content), -result)
440 elif result == 0:
441 raise ovimException("show_network network '%s' not found" % network_id, -result)
442 else:
443 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
444 # get ports from DB
445 result, ports = self.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
446 WHERE={'net_id': network_id}, LIMIT=100)
447 if len(ports) > 0:
448 content[0]['ports'] = ports
449
450 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
451 return content[0]
452
453 def new_network(self, network):
454 """
455 Create a net in DB
456 :return:
457 """
458 tenant_id = network.get('tenant_id')
459
460 if tenant_id:
461 result, _ = self.db.get_table(FROM='tenants', SELECT=('uuid',), WHERE={'uuid': tenant_id, "enabled": True})
462 if result <= 0:
463 raise ovimException("set_network error, no tenant founded", -result)
464
465 bridge_net = None
466 # check valid params
467 net_provider = network.get('provider')
468 net_type = network.get('type')
469 net_vlan = network.get("vlan")
470 net_bind_net = network.get("bind_net")
471 net_bind_type = network.get("bind_type")
472 name = network["name"]
473
474 # check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
475 vlan_index = name.rfind(":")
476 if not net_bind_net and not net_bind_type and vlan_index > 1:
477 try:
478 vlan_tag = int(name[vlan_index + 1:])
479 if not vlan_tag and vlan_tag < 4096:
480 net_bind_net = name[:vlan_index]
481 net_bind_type = "vlan:" + name[vlan_index + 1:]
482 except:
483 pass
484
485 if net_bind_net:
486 # look for a valid net
487 if self._check_valid_uuid(net_bind_net):
488 net_bind_key = "uuid"
489 else:
490 net_bind_key = "name"
491 result, content = self.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net})
492 if result < 0:
493 raise ovimException(' getting nets from db ' + content, HTTP_Internal_Server_Error)
494 elif result == 0:
495 raise ovimException(" bind_net %s '%s'not found" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
496 elif result > 1:
497 raise ovimException(" more than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
498 network["bind_net"] = content[0]["uuid"]
499
500 if net_bind_type:
501 if net_bind_type[0:5] != "vlan:":
502 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>'", HTTP_Bad_Request)
503 if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:]) <= 0:
504 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095",
505 HTTP_Bad_Request)
506 network["bind_type"] = net_bind_type
507
508 if net_provider:
509 if net_provider[:9] == "openflow:":
510 if net_type:
511 if net_type != "ptp" and net_type != "data":
512 raise ovimException(" only 'ptp' or 'data' net types can be bound to 'openflow'",
513 HTTP_Bad_Request)
514 else:
515 net_type = 'data'
516 else:
517 if net_type:
518 if net_type != "bridge_man" and net_type != "bridge_data":
519 raise ovimException("Only 'bridge_man' or 'bridge_data' net types can be bound "
520 "to 'bridge', 'macvtap' or 'default", HTTP_Bad_Request)
521 else:
522 net_type = 'bridge_man'
523
524 if not net_type:
525 net_type = 'bridge_man'
526
527 if net_provider:
528 if net_provider[:7] == 'bridge:':
529 # check it is one of the pre-provisioned bridges
530 bridge_net_name = net_provider[7:]
531 for brnet in self.config['bridge_nets']:
532 if brnet[0] == bridge_net_name: # free
533 if not brnet[3]:
534 raise ovimException("invalid 'provider:physical', "
535 "bridge '%s' is already used" % bridge_net_name, HTTP_Conflict)
536 bridge_net = brnet
537 net_vlan = brnet[1]
538 break
539 # if bridge_net==None:
540 # bottle.abort(HTTP_Bad_Request, "invalid 'provider:physical', bridge '%s' is not one of the
541 # provisioned 'bridge_ifaces' in the configuration file" % bridge_net_name)
542 # return
543
544 elif self.config['network_type'] == 'bridge' and (net_type == 'bridge_data' or net_type == 'bridge_man'):
545 # look for a free precreated nets
546 for brnet in self.config['bridge_nets']:
547 if not brnet[3]: # free
548 if not bridge_net:
549 if net_type == 'bridge_man': # look for the smaller speed
550 if brnet[2] < bridge_net[2]:
551 bridge_net = brnet
552 else: # look for the larger speed
553 if brnet[2] > bridge_net[2]:
554 bridge_net = brnet
555 else:
556 bridge_net = brnet
557 net_vlan = brnet[1]
558 if not bridge_net:
559 raise ovimException("Max limits of bridge networks reached. Future versions of VIM "
560 "will overcome this limit", HTTP_Bad_Request)
561 else:
562 self.logger.debug("using net " + bridge_net)
563 net_provider = "bridge:" + bridge_net[0]
564 net_vlan = bridge_net[1]
565 elif net_type == 'bridge_data' or net_type == 'bridge_man' and self.config['network_type'] == 'ovs':
566 net_provider = 'OVS'
567 if not net_vlan and (net_type == "data" or net_type == "ptp" or net_provider == "OVS"):
568 net_vlan = self.db.get_free_net_vlan()
569 if net_vlan < 0:
570 raise ovimException("Error getting an available vlan", HTTP_Internal_Server_Error)
571 if net_provider == 'OVS':
572 net_provider = 'OVS' + ":" + str(net_vlan)
573
574 network['provider'] = net_provider
575 network['type'] = net_type
576 network['vlan'] = net_vlan
577 dhcp_integrity = True
578 if 'enable_dhcp' in network and network['enable_dhcp']:
579 dhcp_integrity = self._check_dhcp_data_integrity(network)
580
581 result, content = self.db.new_row('nets', network, True, True)
582
583 if result >= 0 and dhcp_integrity:
584 if bridge_net:
585 bridge_net[3] = content
586 if self.config.get("dhcp_server") and self.config['network_type'] == 'bridge':
587 if network["name"] in self.config["dhcp_server"].get("nets", ()):
588 self.config["dhcp_nets"].append(content)
589 self.logger.debug("dhcp_server: add new net", content)
590 elif not bridge_net and bridge_net[0] in self.config["dhcp_server"].get("bridge_ifaces", ()):
591 self.config["dhcp_nets"].append(content)
592 self.logger.debug("dhcp_server: add new net", content, content)
593 return content
594 else:
595 raise ovimException("Error posting network", HTTP_Internal_Server_Error)
596 # TODO kei change update->edit
597
598 def edit_network(self, network_id, network):
599 """
600 Update entwork data byt id
601 :return:
602 """
603 # Look for the previous data
604 where_ = {'uuid': network_id}
605 result, network_old = self.db.get_table(FROM='nets', WHERE=where_)
606 if result < 0:
607 raise ovimException("Error updating network %s" % network_old, HTTP_Internal_Server_Error)
608 elif result == 0:
609 raise ovimException('network %s not found' % network_id, HTTP_Not_Found)
610 # get ports
611 nbports, content = self.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
612 WHERE={'net_id': network_id}, LIMIT=100)
613 if result < 0:
614 raise ovimException("http_put_network_id error %d %s" % (result, network_old), HTTP_Internal_Server_Error)
615 if nbports > 0:
616 if 'type' in network and network['type'] != network_old[0]['type']:
617 raise ovimException("Can not change type of network while having ports attached",
618 HTTP_Method_Not_Allowed)
619 if 'vlan' in network and network['vlan'] != network_old[0]['vlan']:
620 raise ovimException("Can not change vlan of network while having ports attached",
621 HTTP_Method_Not_Allowed)
622
623 # check valid params
624 net_provider = network.get('provider', network_old[0]['provider'])
625 net_type = network.get('type', network_old[0]['type'])
626 net_bind_net = network.get("bind_net")
627 net_bind_type = network.get("bind_type")
628 if net_bind_net:
629 # look for a valid net
630 if self._check_valid_uuid(net_bind_net):
631 net_bind_key = "uuid"
632 else:
633 net_bind_key = "name"
634 result, content = self.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net})
635 if result < 0:
636 raise ovimException('Getting nets from db ' + content, HTTP_Internal_Server_Error)
637 elif result == 0:
638 raise ovimException("bind_net %s '%s'not found" % (net_bind_key, net_bind_net), HTTP_Bad_Request)
639 elif result > 1:
640 raise ovimException("More than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net),
641 HTTP_Bad_Request)
642 network["bind_net"] = content[0]["uuid"]
643 if net_bind_type:
644 if net_bind_type[0:5] != "vlan:":
645 raise ovimException("Bad format for 'bind_type', must be 'vlan:<tag>'", HTTP_Bad_Request)
646 if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:]) <= 0:
647 raise ovimException("bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095",
648 HTTP_Bad_Request)
649 if net_provider:
650 if net_provider[:9] == "openflow:":
651 if net_type != "ptp" and net_type != "data":
652 raise ovimException("Only 'ptp' or 'data' net types can be bound to 'openflow'", HTTP_Bad_Request)
653 else:
654 if net_type != "bridge_man" and net_type != "bridge_data":
655 raise ovimException("Only 'bridge_man' or 'bridge_data' net types can be bound to "
656 "'bridge', 'macvtap' or 'default", HTTP_Bad_Request)
657
658 # insert in data base
659 result, content = self.db.update_rows('nets', network, WHERE={'uuid': network_id}, log=True)
660 if result >= 0:
661 # if result > 0 and nbports>0 and 'admin_state_up' in network
662 # and network['admin_state_up'] != network_old[0]['admin_state_up']:
663 if result > 0:
664 r, c = self.config['of_thread'].insert_task("update-net", network_id)
665 if r < 0:
666 raise ovimException("Error while launching openflow rules %s" % c, HTTP_Internal_Server_Error)
667 if self.config.get("dhcp_server"):
668 if network_id in self.config["dhcp_nets"]:
669 self.config["dhcp_nets"].remove(network_id)
670 if network.get("name", network_old["name"]) in self.config["dhcp_server"].get("nets", ()):
671 self.config["dhcp_nets"].append(network_id)
672 else:
673 net_bind = network.get("bind", network_old["bind"])
674 if net_bind and net_bind[:7] == "bridge:" and net_bind[7:] in self.config["dhcp_server"].get(
675 "bridge_ifaces", ()):
676 self.config["dhcp_nets"].append(network_id)
677 return network_id
678 else:
679 raise ovimException(content, -result)
680
681 def delete_network(self, network_id):
682 """
683 Delete network by network id
684 :param network_id: network id
685 :return:
686 """
687
688 # delete from the data base
689 result, content = self.db.delete_row('nets', network_id)
690
691 if result == 0:
692 raise ovimException("Network %s not found " % network_id, HTTP_Not_Found)
693 elif result > 0:
694 for brnet in self.config['bridge_nets']:
695 if brnet[3] == network_id:
696 brnet[3] = None
697 break
698 if self.config.get("dhcp_server") and network_id in self.config["dhcp_nets"]:
699 self.config["dhcp_nets"].remove(network_id)
700 return content
701 else:
702 raise ovimException("Error deleting network %s" % network_id, HTTP_Internal_Server_Error)
703
704 def get_openflow_rules(self, network_id=None):
705 """
706 Get openflow id from DB
707 :param network_id: Network id, if none all networks will be retrieved
708 :return: Return a list with Openflow rules per net
709 """
710 # ignore input data
711 if not network_id:
712 where_ = {}
713 else:
714 where_ = {"net_id": network_id}
715
716 result, content = self.db.get_table(
717 SELECT=("name", "net_id", "priority", "vlan_id", "ingress_port", "src_mac", "dst_mac", "actions"),
718 WHERE=where_, FROM='of_flows')
719
720 if result < 0:
721 raise ovimException(str(content), -result)
722 return content
723
724 def edit_openflow_rules(self, network_id=None):
725
726 """
727 To make actions over the net. The action is to reinstall the openflow rules
728 network_id can be 'all'
729 :param network_id: Network id, if none all networks will be retrieved
730 :return : Number of nets updated
731 """
732
733 # ignore input data
734 if not network_id:
735 where_ = {}
736 else:
737 where_ = {"uuid": network_id}
738 result, content = self.db.get_table(SELECT=("uuid", "type"), WHERE=where_, FROM='nets')
739
740 if result < 0:
741 raise ovimException(str(content), -result)
742
743 for net in content:
744 if net["type"] != "ptp" and net["type"] != "data":
745 result -= 1
746 continue
747 r, c = self.config['of_thread'].insert_task("update-net", net['uuid'])
748 if r < 0:
749 raise ovimException(str(c), -r)
750 return result
751
752 def delete_openflow_rules(self):
753 """
754 To make actions over the net. The action is to delete ALL openflow rules
755 :return: return operation result
756 """
757 # ignore input data
758 r, c = self.config['of_thread'].insert_task("clear-all")
759 if r < 0:
760 raise ovimException(str(c), -r)
761 return r
762
763 def get_openflow_ports(self):
764 """
765 Obtain switch ports names of openflow controller
766 :return: Return flow ports in DB
767 """
768 data = {'ports': self.config['of_thread'].OF_connector.pp2ofi}
769 return data
770
771 def get_ports(self, columns=None, filter={}, limit=None):
772 # result, content = my.db.get_ports(where_)
773 result, content = self.db.get_table(SELECT=columns, WHERE=filter, FROM='ports', LIMIT=limit)
774 if result < 0:
775 self.logger.error("http_get_ports Error %d %s", result, content)
776 raise ovimException(str(content), -result)
777 else:
778 convert_boolean(content, ('admin_state_up',))
779 return content
780
781 def new_port(self, port_data):
782 port_data['type'] = 'external'
783 if port_data.get('net_id'):
784 # check that new net has the correct type
785 result, new_net = self.db.check_target_net(port_data['net_id'], None, 'external')
786 if result < 0:
787 raise ovimException(str(new_net), -result)
788 # insert in data base
789 result, uuid = self.db.new_row('ports', port_data, True, True)
790 if result > 0:
791 if 'net_id' in port_data:
792 r, c = self.config['of_thread'].insert_task("update-net", port_data['net_id'])
793 if r < 0:
794 self.logger.error("Cannot insert a task for updating network '$s' %s", port_data['net_id'], c)
795 #TODO put network in error status
796 return uuid
797 else:
798 raise ovimException(str(uuid), -result)
799
800 def delete_port(self, port_id):
801 # Look for the previous port data
802 result, ports = self.db.get_table(WHERE={'uuid': port_id, "type": "external"}, FROM='ports')
803 if result < 0:
804 raise ovimException("Cannot get port info from database: {}".format(ports), http_code=-result)
805 # delete from the data base
806 result, content = self.db.delete_row('ports', port_id)
807 if result == 0:
808 raise ovimException("External port '{}' not found".format(port_id), http_code=HTTP_Not_Found)
809 elif result < 0:
810 raise ovimException("Cannot delete port from database: {}".format(content), http_code=-result)
811 # update network
812 network = ports[0].get('net_id', None)
813 if network:
814 # change of net.
815 r, c = self.config['of_thread'].insert_task("update-net", network)
816 if r < 0:
817 self.logger.error("Cannot insert a task for updating network '$s' %s", network, c)
818 return content
819
820 def edit_port(self, port_id, port_data, admin=True):
821 # Look for the previous port data
822 result, content = self.db.get_table(FROM="ports", WHERE={'uuid': port_id})
823 if result < 0:
824 raise ovimException("Cannot get port info from database: {}".format(content), http_code=-result)
825 elif result == 0:
826 raise ovimException("Port '{}' not found".format(port_id), http_code=HTTP_Not_Found)
827 port = content[0]
828 nets = []
829 host_id = None
830 result = 1
831 if 'net_id' in port_data:
832 # change of net.
833 old_net = port.get('net_id', None)
834 new_net = port_data['net_id']
835 if old_net != new_net:
836
837 if new_net:
838 nets.append(new_net) # put first the new net, so that new openflow rules are created before removing the old ones
839 if old_net:
840 nets.append(old_net)
841 if port['type'] == 'instance:bridge' or port['type'] == 'instance:ovs':
842 raise ovimException("bridge interfaces cannot be attached to a different net", http_code=HTTP_Forbidden)
843 elif port['type'] == 'external' and not admin:
844 raise ovimException("Needed admin privileges",http_code=HTTP_Unauthorized)
845 if new_net:
846 # check that new net has the correct type
847 result, new_net_dict = self.db.check_target_net(new_net, None, port['type'])
848 if result < 0:
849 raise ovimException("Error {}".format(new_net_dict), http_code=HTTP_Conflict)
850 # change VLAN for SR-IOV ports
851 if result >= 0 and port["type"] == "instance:data" and port["model"] == "VF": # TODO consider also VFnotShared
852 if new_net:
853 port_data["vlan"] = None
854 else:
855 port_data["vlan"] = new_net_dict["vlan"]
856 # get host where this VM is allocated
857 result, content = self.db.get_table(FROM="instances", WHERE={"uuid": port["instance_id"]})
858 if result > 0:
859 host_id = content[0]["host_id"]
860
861 # insert in data base
862 if result >= 0:
863 result, content = self.db.update_rows('ports', port_data, WHERE={'uuid': port_id}, log=False)
864
865 # Insert task to complete actions
866 if result > 0:
867 for net_id in nets:
868 r, v = self.config['of_thread'].insert_task("update-net", net_id)
869 if r < 0:
870 self.logger.error("Error updating network '{}' {}".format(r,v))
871 # TODO Do something if fails
872 if host_id:
873 r, v = self.config['host_threads'][host_id].insert_task("edit-iface", port_id, old_net, new_net)
874 if r < 0:
875 self.logger.error("Error updating network '{}' {}".format(r,v))
876 # TODO Do something if fails
877 if result >= 0:
878 return port_id
879 else:
880 raise ovimException("Error {}".format(content), http_code=-result)
881
882 def new_of_controller(self, ofc_data):
883 """
884 Create a new openflow controller into DB
885 :param ofc_data: Dict openflow controller data
886 :return: openflow controller dpid
887 """
888
889 result, ofc_uuid = self.db.new_row('ofcs', ofc_data, True, True)
890 if result < 0:
891 raise ovimException("New ofc Error %s" % ofc_uuid, HTTP_Internal_Server_Error)
892
893 ofc_data['uuid'] = ofc_uuid
894 of_conn = self._load_of_module(ofc_data)
895 self._create_ofc_task(ofc_uuid, ofc_data['dpid'], of_conn)
896
897 return ofc_uuid
898
899 def edit_of_controller(self, of_id, ofc_data):
900 """
901 Edit an openflow controller entry from DB
902 :return:
903 """
904 if not ofc_data:
905 raise ovimException("No data received during uptade OF contorller", http_code=HTTP_Internal_Server_Error)
906
907 old_of_controller = self.show_of_controller(of_id)
908
909 if old_of_controller:
910 result, content = self.db.update_rows('ofcs', ofc_data, WHERE={'uuid': of_id}, log=False)
911 if result >= 0:
912 return ofc_data
913 else:
914 raise ovimException("Error uptating OF contorller with uuid {}".format(of_id),
915 http_code=-result)
916 else:
917 raise ovimException("Error uptating OF contorller with uuid {}".format(of_id),
918 http_code=HTTP_Internal_Server_Error)
919
920 def delete_of_controller(self, of_id):
921 """
922 Delete an openflow controller from DB.
923 :param of_id: openflow controller dpid
924 :return:
925 """
926
927 ofc = self.show_of_controller(of_id)
928
929 result, content = self.db.delete_row("ofcs", of_id)
930 if result < 0:
931 raise ovimException("Cannot delete ofc from database: {}".format(content), http_code=-result)
932 elif result == 0:
933 raise ovimException("ofc {} not found ".format(content), http_code=HTTP_Not_Found)
934
935 ofc_thread = self.config['ofcs_thread'][of_id]
936 del self.config['ofcs_thread'][of_id]
937 for ofc_th in self.config['ofcs_thread_dpid']:
938 if ofc['dpid'] in ofc_th:
939 self.config['ofcs_thread_dpid'].remove(ofc_th)
940
941 ofc_thread.insert_task("exit")
942 #ofc_thread.join()
943
944 return content
945
946 def show_of_controller(self, uuid):
947 """
948 Show an openflow controller by dpid from DB.
949 :param db_filter: List with where query parameters
950 :return:
951 """
952
953 result, content = self.db.get_table(FROM='ofcs', WHERE={"uuid": uuid}, LIMIT=100)
954
955 if result == 0:
956 raise ovimException("Openflow controller with uuid '{}' not found".format(uuid),
957 http_code=HTTP_Not_Found)
958 elif result < 0:
959 raise ovimException("Openflow controller with uuid '{}' error".format(uuid),
960 http_code=HTTP_Internal_Server_Error)
961 return content[0]
962
963 def get_of_controllers(self, columns=None, db_filter={}, limit=None):
964 """
965 Show an openflow controllers from DB.
966 :param columns: List with SELECT query parameters
967 :param db_filter: List with where query parameters
968 :param limit: result Limit
969 :return:
970 """
971 result, content = self.db.get_table(SELECT=columns, FROM='ofcs', WHERE=db_filter, LIMIT=limit)
972
973 if result < 0:
974 raise ovimException(str(content), -result)
975
976 return content
977
978 def get_tenants(self, columns=None, db_filter={}, limit=None):
979 """
980 Retrieve tenant list from DB
981 :param columns: List with SELECT query parameters
982 :param db_filter: List with where query parameters
983 :param limit: result limit
984 :return:
985 """
986 result, content = self.db.get_table(FROM='tenants', SELECT=columns, WHERE=db_filter, LIMIT=limit)
987 if result < 0:
988 raise ovimException('get_tenatns Error {}'.format(str(content)), -result)
989 else:
990 convert_boolean(content, ('enabled',))
991 return content
992
993 def show_tenant_id(self, tenant_id):
994 """
995 Get tenant from DB by id
996 :param tenant_id: tenant id
997 :return:
998 """
999 result, content = self.db.get_table(FROM='tenants', SELECT=('uuid', 'name', 'description', 'enabled'),
1000 WHERE={"uuid": tenant_id})
1001 if result < 0:
1002 raise ovimException(str(content), -result)
1003 elif result == 0:
1004 raise ovimException("tenant with uuid='{}' not found".format(tenant_id), HTTP_Not_Found)
1005 else:
1006 convert_boolean(content, ('enabled',))
1007 return content[0]
1008
1009 def new_tentant(self, tenant):
1010 """
1011 Create a tenant and store in DB
1012 :param tenant: Dictionary with tenant data
1013 :return: the uuid of created tenant. Raise exception upon error
1014 """
1015
1016 # insert in data base
1017 result, tenant_uuid = self.db.new_tenant(tenant)
1018
1019 if result >= 0:
1020 return tenant_uuid
1021 else:
1022 raise ovimException(str(tenant_uuid), -result)
1023
1024 def delete_tentant(self, tenant_id):
1025 """
1026 Delete a tenant from the database.
1027 :param tenant_id: Tenant id
1028 :return: delete tenant id
1029 """
1030
1031 # check permissions
1032 r, tenants_flavors = self.db.get_table(FROM='tenants_flavors', SELECT=('flavor_id', 'tenant_id'),
1033 WHERE={'tenant_id': tenant_id})
1034 if r <= 0:
1035 tenants_flavors = ()
1036 r, tenants_images = self.db.get_table(FROM='tenants_images', SELECT=('image_id', 'tenant_id'),
1037 WHERE={'tenant_id': tenant_id})
1038 if r <= 0:
1039 tenants_images = ()
1040
1041 result, content = self.db.delete_row('tenants', tenant_id)
1042 if result == 0:
1043 raise ovimException("tenant '%s' not found" % tenant_id, HTTP_Not_Found)
1044 elif result > 0:
1045 for flavor in tenants_flavors:
1046 self.db.delete_row_by_key("flavors", "uuid", flavor['flavor_id'])
1047 for image in tenants_images:
1048 self.db.delete_row_by_key("images", "uuid", image['image_id'])
1049 return content
1050 else:
1051 raise ovimException("Error deleting tenant '%s' " % tenant_id, HTTP_Internal_Server_Error)
1052
1053 def edit_tenant(self, tenant_id, tenant_data):
1054 """
1055 Update a tenant data identified by tenant id
1056 :param tenant_id: tenant id
1057 :param tenant_data: Dictionary with tenant data
1058 :return:
1059 """
1060
1061 # Look for the previous data
1062 result, tenant_data_old = self.db.get_table(FROM='tenants', WHERE={'uuid': tenant_id})
1063 if result < 0:
1064 raise ovimException("Error updating tenant with uuid='{}': {}".format(tenant_id, tenant_data_old),
1065 HTTP_Internal_Server_Error)
1066 elif result == 0:
1067 raise ovimException("tenant with uuid='{}' not found".format(tenant_id), HTTP_Not_Found)
1068
1069 # insert in data base
1070 result, content = self.db.update_rows('tenants', tenant_data, WHERE={'uuid': tenant_id}, log=True)
1071 if result >= 0:
1072 return content
1073 else:
1074 raise ovimException(str(content), -result)
1075
1076 def get_dhcp_controller(self):
1077 """
1078 Create an host_thread object for manage openvim controller and not create a thread for itself
1079 :return: dhcp_host openvim controller object
1080 """
1081
1082 if 'openvim_controller' in self.config['host_threads']:
1083 return self.config['host_threads']['openvim_controller']
1084
1085 bridge_ifaces = []
1086 controller_ip = self.config['ovs_controller_ip']
1087 ovs_controller_user = self.config['ovs_controller_user']
1088
1089 host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
1090 host_develop_mode = True if self.config['mode'] == 'development' else False
1091
1092 dhcp_host = ht.host_thread(name='openvim_controller', user=ovs_controller_user, host=controller_ip,
1093 db=self.config['db'],
1094 db_lock=self.config['db_lock'], test=host_test_mode,
1095 image_path=self.config['image_path'], version=self.config['version'],
1096 host_id='openvim_controller', develop_mode=host_develop_mode,
1097 develop_bridge_iface=bridge_ifaces)
1098
1099 self.config['host_threads']['openvim_controller'] = dhcp_host
1100 if not host_test_mode:
1101 dhcp_host.ssh_connect()
1102 return dhcp_host
1103
1104 def launch_dhcp_server(self, vlan, first_ip, last_ip, cidr, gateway):
1105 """
1106 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
1107 :param vlan: vlan identifier
1108 :param first_ip: First dhcp range ip
1109 :param last_ip: Last dhcp range ip
1110 :param cidr: net cidr
1111 :param gateway: net gateway
1112 :return:
1113 """
1114 ip_tools = IPNetwork(cidr)
1115 dhcp_netmask = str(ip_tools.netmask)
1116 ip_range = [first_ip, last_ip]
1117
1118 dhcp_path = self.config['ovs_controller_file_path']
1119
1120 controller_host = self.get_dhcp_controller()
1121 controller_host.create_linux_bridge(vlan)
1122 controller_host.create_dhcp_interfaces(vlan, first_ip, dhcp_netmask)
1123 controller_host.launch_dhcp_server(vlan, ip_range, dhcp_netmask, dhcp_path, gateway)
1124
1125