1 # -*- coding: utf-8 -*-
4 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
5 # This file is part of openvim
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
12 # http://www.apache.org/licenses/LICENSE-2.0
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
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: nfvlabs@tid.es
25 This is the thread for the http server North API.
26 Two thread will be launched, with normal and administrative permissions.
29 __author__
= "Alfonso Tierno, Leonardo Mirabal"
30 __date__
= "$06-Feb-2017 12:07:15$"
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
43 HTTP_Bad_Request
= 400
44 HTTP_Unauthorized
= 401
47 HTTP_Method_Not_Allowed
= 405
48 HTTP_Not_Acceptable
= 406
49 HTTP_Request_Timeout
= 408
51 HTTP_Service_Unavailable
= 503
52 HTTP_Internal_Server_Error
= 500
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
59 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
60 'items': tuple of keys to convert
64 if type(data
) is dict:
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
)
69 if type(data
[k
]) is str:
70 if data
[k
] == "false":
72 elif data
[k
] == "true":
74 if type(data
) is tuple or type(data
) is list:
76 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
77 convert_boolean(k
, items
)
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
)
88 running_info
= {} #TODO OVIM move the info of running threads from config_dic to this static variable
91 def __init__(self
, configuration
):
92 self
.config
= configuration
93 self
.logger
= logging
.getLogger(configuration
["logger_name"])
95 self
.db
= self
._create
_database
_connection
()
98 self
.of_test_mode
= False
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']) )
113 def _check_dhcp_data_integrity(network
):
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
119 if "cidr" in network
:
120 cidr
= network
["cidr"]
121 ip_tools
= IPNetwork(cidr
)
122 cidr_len
= ip_tools
.prefixlen
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])
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}$"}
142 js_v(uuid
, id_schema
)
144 except js_e
.ValidationError
:
147 def start_service(self
):
152 # if self.running_info:
153 # return #TODO service can be checked and rebuild broken threads
154 r
= self
.db
.get_db_version()
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"]) )
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
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,
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
:
178 self
.config
['bridge_nets'].append([bridge
, vlan_speed
[0], vlan_speed
[1], None])
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
.get_table(SELECT
=('uuid',), FROM
='nets', WHERE
={'provider': "bridge:" + brnet
[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
.get_table(SELECT
=('uuid',), FROM
='nets', WHERE
={'name': net
})
197 self
.config
['dhcp_nets'].append(nets
[0]['uuid'])
200 self
._start
_ofc
_default
_task
()
202 # OFC per tenant in DB
203 self
._start
_of
_db
_tasks
()
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")
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'])
212 self
.config
['dhcp_thread'] = thread
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)
219 # get host list from data base before starting threads
220 r
, hosts
= self
.db
.get_table(SELECT
=('name', 'ip_name', 'user', 'uuid'), FROM
='hosts', WHERE
={'status': 'ok'})
222 raise ovimException("Cannot get hosts from database {}".format(hosts
))
224 self
.config
['host_threads'] = {}
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
)
232 self
.config
['host_threads'][host
['uuid']] = thread
234 # create ovs dhcp thread
235 result
, content
= self
.db
.get_table(FROM
='nets')
237 self
.logger
.error("http_get_ports Error %d %s", result
, content
)
238 raise ovimException(str(content
), -result
)
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'],
250 def _start_of_db_tasks(self
):
252 Start ofc task for existing ofcs in database
257 ofcs
= self
.get_of_controllers()
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
)
264 def _create_ofc_task(self
, ofc_uuid
, dpid
, of_conn
):
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
272 if 'ofcs_thread' not in self
.config
and 'ofcs_thread_dpid' not in self
.config
:
274 ofcs_thread_dpid
= []
276 ofcs_threads
= self
.config
['ofcs_thread']
277 ofcs_thread_dpid
= self
.config
['ofcs_thread_dpid']
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
284 ofcs_threads
[ofc_uuid
] = ofc_thread
285 self
.config
['ofcs_thread'] = ofcs_threads
287 ofcs_thread_dpid
.append({dpid
: ofc_thread
})
288 self
.config
['ofcs_thread_dpid'] = ofcs_thread_dpid
290 def _start_ofc_default_task(self
):
292 Create default ofc thread
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
:
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')
309 # create connector to the openflow controller
310 # load other parameters starting by of_ from config dict in a temporal dict
312 of_conn
= self
._load
_of
_module
(db_config
)
313 # create openflow thread
314 self
._create
_ofc
_task
("Default", db_config
['dpid'], of_conn
)
316 def _load_of_module(self
, db_config
):
318 import python module for each SDN controller supported
319 :param default: SDN dn information
323 raise ovimException("No module found it", HTTP_Internal_Server_Error
)
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']})
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']
339 temp_dict
['of_debug'] = self
.config
['log_level_of']
341 if temp_dict
['of_controller'] == 'opendaylight':
344 module
= temp_dict
['of_controller']
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
351 of_conn_module
= ovim
.of_module
[module
]
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
)
370 def _create_ofc_thread(self
, of_conn
, ofc_uuid
="Default"):
372 Create and launch a of thread
375 # create openflow thread
377 if 'of_controller_nets_with_same_vlan' in self
.config
:
378 ofc_net_same_vlan
= self
.config
['of_controller_nets_with_same_vlan']
380 ofc_net_same_vlan
= False
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()
386 # raise ovimException("Cannot get openflow information %s", c)
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
]
399 if 'dhcp_thread' in self
.config
:
400 threads
['dhcp'] = (self
.config
['dhcp_thread'])
402 for thread
in threads
.values():
403 thread
.insert_task("exit")
404 for thread
in threads
.values():
407 def get_networks(self
, columns
=None, db_filter
={}, limit
=None):
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
415 result
, content
= self
.db
.get_table(SELECT
=columns
, FROM
='nets', WHERE
=db_filter
, LIMIT
=limit
)
418 raise ovimException(str(content
), -result
)
420 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp'))
424 def show_network(self
, network_id
, db_filter
={}):
426 Get network from DB by id
427 :param network_id: net Id
428 :param db_filter: List with where query parameters
433 raise ovimException("Not network id was not found")
434 db_filter
['uuid'] = network_id
436 result
, content
= self
.db
.get_table(FROM
='nets', WHERE
=db_filter
, LIMIT
=100)
439 raise ovimException(str(content
), -result
)
441 raise ovimException("show_network network '%s' not found" % network_id
, -result
)
443 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp'))
445 result
, ports
= self
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
446 WHERE
={'net_id': network_id
}, LIMIT
=100)
448 content
[0]['ports'] = ports
450 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp'))
453 def new_network(self
, network
):
458 tenant_id
= network
.get('tenant_id')
461 result
, _
= self
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
, "enabled": True})
463 raise ovimException("set_network error, no tenant founded", -result
)
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"]
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:
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:]
486 # look for a valid net
487 if self
._check
_valid
_uuid
(net_bind_net
):
488 net_bind_key
= "uuid"
490 net_bind_key
= "name"
491 result
, content
= self
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
})
493 raise ovimException(' getting nets from db ' + content
, HTTP_Internal_Server_Error
)
495 raise ovimException(" bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
), HTTP_Bad_Request
)
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"]
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",
506 network
["bind_type"] = net_bind_type
509 if net_provider
[:9] == "openflow:":
511 if net_type
!= "ptp" and net_type
!= "data":
512 raise ovimException(" only 'ptp' or 'data' net types can be bound to 'openflow'",
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
)
522 net_type
= 'bridge_man'
525 net_type
= 'bridge_man'
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
534 raise ovimException("invalid 'provider:physical', "
535 "bridge '%s' is already used" % bridge_net_name
, HTTP_Conflict
)
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)
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
549 if net_type
== 'bridge_man': # look for the smaller speed
550 if brnet
[2] < bridge_net
[2]:
552 else: # look for the larger speed
553 if brnet
[2] > bridge_net
[2]:
559 raise ovimException("Max limits of bridge networks reached. Future versions of VIM "
560 "will overcome this limit", HTTP_Bad_Request
)
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':
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()
570 raise ovimException("Error getting an available vlan", HTTP_Internal_Server_Error
)
571 if net_provider
== 'OVS':
572 net_provider
= 'OVS' + ":" + str(net_vlan
)
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
)
581 result
, content
= self
.db
.new_row('nets', network
, True, True)
583 if result
>= 0 and dhcp_integrity
:
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
)
595 raise ovimException("Error posting network", HTTP_Internal_Server_Error
)
596 # TODO kei change update->edit
598 def edit_network(self
, network_id
, network
):
600 Update entwork data byt id
603 # Look for the previous data
604 where_
= {'uuid': network_id
}
605 result
, network_old
= self
.db
.get_table(FROM
='nets', WHERE
=where_
)
607 raise ovimException("Error updating network %s" % network_old
, HTTP_Internal_Server_Error
)
609 raise ovimException('network %s not found' % network_id
, HTTP_Not_Found
)
611 nbports
, content
= self
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
612 WHERE
={'net_id': network_id
}, LIMIT
=100)
614 raise ovimException("http_put_network_id error %d %s" % (result
, network_old
), HTTP_Internal_Server_Error
)
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
)
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")
629 # look for a valid net
630 if self
._check
_valid
_uuid
(net_bind_net
):
631 net_bind_key
= "uuid"
633 net_bind_key
= "name"
634 result
, content
= self
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
})
636 raise ovimException('Getting nets from db ' + content
, HTTP_Internal_Server_Error
)
638 raise ovimException("bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
), HTTP_Bad_Request
)
640 raise ovimException("More than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
),
642 network
["bind_net"] = content
[0]["uuid"]
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",
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
)
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
)
658 # insert in data base
659 result
, content
= self
.db
.update_rows('nets', network
, WHERE
={'uuid': network_id
}, log
=True)
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']:
664 r
, c
= self
.config
['of_thread'].insert_task("update-net", network_id
)
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
)
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
)
679 raise ovimException(content
, -result
)
681 def delete_network(self
, network_id
):
683 Delete network by network id
684 :param network_id: network id
688 # delete from the data base
689 result
, content
= self
.db
.delete_row('nets', network_id
)
692 raise ovimException("Network %s not found " % network_id
, HTTP_Not_Found
)
694 for brnet
in self
.config
['bridge_nets']:
695 if brnet
[3] == network_id
:
698 if self
.config
.get("dhcp_server") and network_id
in self
.config
["dhcp_nets"]:
699 self
.config
["dhcp_nets"].remove(network_id
)
702 raise ovimException("Error deleting network %s" % network_id
, HTTP_Internal_Server_Error
)
704 def get_openflow_rules(self
, network_id
=None):
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
714 where_
= {"net_id": network_id
}
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')
721 raise ovimException(str(content
), -result
)
724 def edit_openflow_rules(self
, network_id
=None):
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
737 where_
= {"uuid": network_id
}
738 result
, content
= self
.db
.get_table(SELECT
=("uuid", "type"), WHERE
=where_
, FROM
='nets')
741 raise ovimException(str(content
), -result
)
744 if net
["type"] != "ptp" and net
["type"] != "data":
747 r
, c
= self
.config
['of_thread'].insert_task("update-net", net
['uuid'])
749 raise ovimException(str(c
), -r
)
752 def delete_openflow_rules(self
):
754 To make actions over the net. The action is to delete ALL openflow rules
755 :return: return operation result
758 r
, c
= self
.config
['of_thread'].insert_task("clear-all")
760 raise ovimException(str(c
), -r
)
763 def get_openflow_ports(self
):
765 Obtain switch ports names of openflow controller
766 :return: Return flow ports in DB
768 data
= {'ports': self
.config
['of_thread'].OF_connector
.pp2ofi
}
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
)
775 self
.logger
.error("http_get_ports Error %d %s", result
, content
)
776 raise ovimException(str(content
), -result
)
778 convert_boolean(content
, ('admin_state_up',))
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')
787 raise ovimException(str(new_net
), -result
)
788 # insert in data base
789 result
, uuid
= self
.db
.new_row('ports', port_data
, True, True)
791 if 'net_id' in port_data
:
792 r
, c
= self
.config
['of_thread'].insert_task("update-net", port_data
['net_id'])
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
798 raise ovimException(str(uuid
), -result
)
800 def new_external_port(self
, port_data
):
802 Create new external port and check port mapping correspondence
803 :param port_data: port_data = {
804 'region': 'datacenter region',
805 'compute_node': 'compute node id',
806 'pci': 'pci port address',
809 'tenant_id': 'tenant id',
812 'ip_address': 'ip address - optional'}
816 port_data
['type'] = 'external'
818 if port_data
.get('net_id'):
819 # check that new net has the correct type
820 result
, new_net
= self
.db
.check_target_net(port_data
['net_id'], None, 'external')
822 raise ovimException(str(new_net
), -result
)
823 # insert in data base
826 if port_data
.get('region'):
827 db_filter
['region'] = port_data
['region']
828 if port_data
.get('pci'):
829 db_filter
['pci'] = port_data
['pci']
830 if port_data
.get('compute_node'):
831 db_filter
['compute_node'] = port_data
['compute_node']
833 columns
= ['ofc_id', 'switch_dpid', 'switch_port', 'switch_mac', 'pci']
834 port_mapping_data
= self
.get_of_port_mappings(columns
, db_filter
)
836 if not len(port_mapping_data
):
837 raise ovimException("No port mapping founded for region='{}', compute id='{}' and pci='{}'".
838 format(db_filter
['region'], db_filter
['compute_node'], db_filter
['pci']),
840 elif len(port_mapping_data
) > 1:
841 raise ovimException("Wrong port data was given, please check pci, region & compute id data",
844 port_data
['ofc_id'] = port_mapping_data
[0]['ofc_id']
845 port_data
['switch_dpid'] = port_mapping_data
[0]['switch_dpid']
846 port_data
['switch_port'] = port_mapping_data
[0]['switch_port']
847 port_data
['switch_mac'] = port_mapping_data
[0]['switch_mac']
849 # remove from compute_node, region and pci of_port_data to adapt to 'ports' structure
850 del port_data
['compute_node']
851 del port_data
['region']
854 result
, uuid
= self
.db
.new_row('ports', port_data
, True, True)
856 if 'net_id' in port_data
and port_data
['ofc_id'] in self
.config
['ofcs_thread']:
857 r
, c
= self
.config
['ofcs_thread'][port_data
['ofc_id']].insert_task("update-net", port_data
['net_id'])
859 message
= "Cannot insert a task for updating network '$s' %s", port_data
['net_id'], c
860 self
.logger
.error(message
)
861 raise ovimException(message
, HTTP_Internal_Server_Error
)
864 raise ovimException(str(uuid
), -result
)
866 def delete_port(self
, port_id
):
867 # Look for the previous port data
868 result
, ports
= self
.db
.get_table(WHERE
={'uuid': port_id
, "type": "external"}, FROM
='ports')
870 raise ovimException("Cannot get port info from database: {}".format(ports
), http_code
=-result
)
871 # delete from the data base
872 result
, content
= self
.db
.delete_row('ports', port_id
)
874 raise ovimException("External port '{}' not found".format(port_id
), http_code
=HTTP_Not_Found
)
876 raise ovimException("Cannot delete port from database: {}".format(content
), http_code
=-result
)
878 network
= ports
[0].get('net_id', None)
881 r
, c
= self
.config
['of_thread'].insert_task("update-net", network
)
883 self
.logger
.error("Cannot insert a task for updating network '$s' %s", network
, c
)
886 def edit_port(self
, port_id
, port_data
, admin
=True):
887 # Look for the previous port data
888 result
, content
= self
.db
.get_table(FROM
="ports", WHERE
={'uuid': port_id
})
890 raise ovimException("Cannot get port info from database: {}".format(content
), http_code
=-result
)
892 raise ovimException("Port '{}' not found".format(port_id
), http_code
=HTTP_Not_Found
)
897 if 'net_id' in port_data
:
899 old_net
= port
.get('net_id', None)
900 new_net
= port_data
['net_id']
901 if old_net
!= new_net
:
904 nets
.append(new_net
) # put first the new net, so that new openflow rules are created before removing the old ones
907 if port
['type'] == 'instance:bridge' or port
['type'] == 'instance:ovs':
908 raise ovimException("bridge interfaces cannot be attached to a different net", http_code
=HTTP_Forbidden
)
909 elif port
['type'] == 'external' and not admin
:
910 raise ovimException("Needed admin privileges",http_code
=HTTP_Unauthorized
)
912 # check that new net has the correct type
913 result
, new_net_dict
= self
.db
.check_target_net(new_net
, None, port
['type'])
915 raise ovimException("Error {}".format(new_net_dict
), http_code
=HTTP_Conflict
)
916 # change VLAN for SR-IOV ports
917 if result
>= 0 and port
["type"] == "instance:data" and port
["model"] == "VF": # TODO consider also VFnotShared
919 port_data
["vlan"] = None
921 port_data
["vlan"] = new_net_dict
["vlan"]
922 # get host where this VM is allocated
923 result
, content
= self
.db
.get_table(FROM
="instances", WHERE
={"uuid": port
["instance_id"]})
925 host_id
= content
[0]["host_id"]
927 # insert in data base
929 result
, content
= self
.db
.update_rows('ports', port_data
, WHERE
={'uuid': port_id
}, log
=False)
931 # Insert task to complete actions
934 r
, v
= self
.config
['of_thread'].insert_task("update-net", net_id
)
936 self
.logger
.error("Error updating network '{}' {}".format(r
,v
))
937 # TODO Do something if fails
939 r
, v
= self
.config
['host_threads'][host_id
].insert_task("edit-iface", port_id
, old_net
, new_net
)
941 self
.logger
.error("Error updating network '{}' {}".format(r
,v
))
942 # TODO Do something if fails
946 raise ovimException("Error {}".format(content
), http_code
=-result
)
948 def new_of_controller(self
, ofc_data
):
950 Create a new openflow controller into DB
951 :param ofc_data: Dict openflow controller data
952 :return: openflow controller dpid
955 result
, ofc_uuid
= self
.db
.new_row('ofcs', ofc_data
, True, True)
957 raise ovimException("New ofc Error %s" % ofc_uuid
, HTTP_Internal_Server_Error
)
959 ofc_data
['uuid'] = ofc_uuid
960 of_conn
= self
._load
_of
_module
(ofc_data
)
961 self
._create
_ofc
_task
(ofc_uuid
, ofc_data
['dpid'], of_conn
)
965 def edit_of_controller(self
, of_id
, ofc_data
):
967 Edit an openflow controller entry from DB
971 raise ovimException("No data received during uptade OF contorller", http_code
=HTTP_Internal_Server_Error
)
973 old_of_controller
= self
.show_of_controller(of_id
)
975 if old_of_controller
:
976 result
, content
= self
.db
.update_rows('ofcs', ofc_data
, WHERE
={'uuid': of_id
}, log
=False)
980 raise ovimException("Error uptating OF contorller with uuid {}".format(of_id
),
983 raise ovimException("Error uptating OF contorller with uuid {}".format(of_id
),
984 http_code
=HTTP_Internal_Server_Error
)
986 def delete_of_controller(self
, of_id
):
988 Delete an openflow controller from DB.
989 :param of_id: openflow controller dpid
993 ofc
= self
.show_of_controller(of_id
)
995 result
, content
= self
.db
.delete_row("ofcs", of_id
)
997 raise ovimException("Cannot delete ofc from database: {}".format(content
), http_code
=-result
)
999 raise ovimException("ofc {} not found ".format(content
), http_code
=HTTP_Not_Found
)
1001 ofc_thread
= self
.config
['ofcs_thread'][of_id
]
1002 del self
.config
['ofcs_thread'][of_id
]
1003 for ofc_th
in self
.config
['ofcs_thread_dpid']:
1004 if ofc
['dpid'] in ofc_th
:
1005 self
.config
['ofcs_thread_dpid'].remove(ofc_th
)
1007 ofc_thread
.insert_task("exit")
1012 def show_of_controller(self
, uuid
):
1014 Show an openflow controller by dpid from DB.
1015 :param db_filter: List with where query parameters
1019 result
, content
= self
.db
.get_table(FROM
='ofcs', WHERE
={"uuid": uuid
}, LIMIT
=100)
1022 raise ovimException("Openflow controller with uuid '{}' not found".format(uuid
),
1023 http_code
=HTTP_Not_Found
)
1025 raise ovimException("Openflow controller with uuid '{}' error".format(uuid
),
1026 http_code
=HTTP_Internal_Server_Error
)
1029 def get_of_controllers(self
, columns
=None, db_filter
={}, limit
=None):
1031 Show an openflow controllers from DB.
1032 :param columns: List with SELECT query parameters
1033 :param db_filter: List with where query parameters
1034 :param limit: result Limit
1037 result
, content
= self
.db
.get_table(SELECT
=columns
, FROM
='ofcs', WHERE
=db_filter
, LIMIT
=limit
)
1040 raise ovimException(str(content
), -result
)
1044 def get_tenants(self
, columns
=None, db_filter
={}, limit
=None):
1046 Retrieve tenant list from DB
1047 :param columns: List with SELECT query parameters
1048 :param db_filter: List with where query parameters
1049 :param limit: result limit
1052 result
, content
= self
.db
.get_table(FROM
='tenants', SELECT
=columns
, WHERE
=db_filter
, LIMIT
=limit
)
1054 raise ovimException('get_tenatns Error {}'.format(str(content
)), -result
)
1056 convert_boolean(content
, ('enabled',))
1059 def show_tenant_id(self
, tenant_id
):
1061 Get tenant from DB by id
1062 :param tenant_id: tenant id
1065 result
, content
= self
.db
.get_table(FROM
='tenants', SELECT
=('uuid', 'name', 'description', 'enabled'),
1066 WHERE
={"uuid": tenant_id
})
1068 raise ovimException(str(content
), -result
)
1070 raise ovimException("tenant with uuid='{}' not found".format(tenant_id
), HTTP_Not_Found
)
1072 convert_boolean(content
, ('enabled',))
1075 def new_tentant(self
, tenant
):
1077 Create a tenant and store in DB
1078 :param tenant: Dictionary with tenant data
1079 :return: the uuid of created tenant. Raise exception upon error
1082 # insert in data base
1083 result
, tenant_uuid
= self
.db
.new_tenant(tenant
)
1088 raise ovimException(str(tenant_uuid
), -result
)
1090 def delete_tentant(self
, tenant_id
):
1092 Delete a tenant from the database.
1093 :param tenant_id: Tenant id
1094 :return: delete tenant id
1098 r
, tenants_flavors
= self
.db
.get_table(FROM
='tenants_flavors', SELECT
=('flavor_id', 'tenant_id'),
1099 WHERE
={'tenant_id': tenant_id
})
1101 tenants_flavors
= ()
1102 r
, tenants_images
= self
.db
.get_table(FROM
='tenants_images', SELECT
=('image_id', 'tenant_id'),
1103 WHERE
={'tenant_id': tenant_id
})
1107 result
, content
= self
.db
.delete_row('tenants', tenant_id
)
1109 raise ovimException("tenant '%s' not found" % tenant_id
, HTTP_Not_Found
)
1111 for flavor
in tenants_flavors
:
1112 self
.db
.delete_row_by_key("flavors", "uuid", flavor
['flavor_id'])
1113 for image
in tenants_images
:
1114 self
.db
.delete_row_by_key("images", "uuid", image
['image_id'])
1117 raise ovimException("Error deleting tenant '%s' " % tenant_id
, HTTP_Internal_Server_Error
)
1119 def edit_tenant(self
, tenant_id
, tenant_data
):
1121 Update a tenant data identified by tenant id
1122 :param tenant_id: tenant id
1123 :param tenant_data: Dictionary with tenant data
1127 # Look for the previous data
1128 result
, tenant_data_old
= self
.db
.get_table(FROM
='tenants', WHERE
={'uuid': tenant_id
})
1130 raise ovimException("Error updating tenant with uuid='{}': {}".format(tenant_id
, tenant_data_old
),
1131 HTTP_Internal_Server_Error
)
1133 raise ovimException("tenant with uuid='{}' not found".format(tenant_id
), HTTP_Not_Found
)
1135 # insert in data base
1136 result
, content
= self
.db
.update_rows('tenants', tenant_data
, WHERE
={'uuid': tenant_id
}, log
=True)
1140 raise ovimException(str(content
), -result
)
1142 def set_of_port_mapping(self
, of_maps
, ofc_id
=None, switch_dpid
=None, region
=None):
1144 Create new port mapping entry
1145 :param of_maps: List with port mapping information
1146 # maps =[{"ofc_id": <ofc_id>,"region": datacenter region,"compute_node": compute uuid,"pci": pci adress,
1147 "switch_dpid": swith dpid,"switch_port": port name,"switch_mac": mac}]
1148 :param ofc_id: ofc id
1149 :param switch_dpid: switch dpid
1150 :param region: datacenter region id
1156 map['ofc_id'] = ofc_id
1158 map['switch_dpid'] = switch_dpid
1160 map['region'] = region
1162 for of_map
in of_maps
:
1163 result
, uuid
= self
.db
.new_row('of_port_mappings', of_map
, True)
1165 of_map
["uuid"] = uuid
1167 raise ovimException(str(uuid
), -result
)
1170 def clear_of_port_mapping(self
, db_filter
={}):
1172 Clear port mapping filtering using db_filter dict
1173 :param db_filter: Parameter to filter during remove process
1176 result
, content
= self
.db
.delete_row_by_dict(FROM
='of_port_mappings', WHERE
=db_filter
)
1181 raise ovimException("Error deleting of_port_mappings with filter='{}'".format(str(db_filter
)),
1182 HTTP_Internal_Server_Error
)
1184 def get_of_port_mappings(self
, column
=None, db_filter
=None, db_limit
=None):
1186 Retrive port mapping from DB
1191 result
, content
= self
.db
.get_table(SELECT
=column
, WHERE
=db_filter
, FROM
='of_port_mappings', LIMIT
=db_limit
)
1194 self
.logger
.error("get_of_port_mappings Error %d %s", result
, content
)
1195 raise ovimException(str(content
), -result
)
1199 def get_dhcp_controller(self
):
1201 Create an host_thread object for manage openvim controller and not create a thread for itself
1202 :return: dhcp_host openvim controller object
1205 if 'openvim_controller' in self
.config
['host_threads']:
1206 return self
.config
['host_threads']['openvim_controller']
1209 controller_ip
= self
.config
['ovs_controller_ip']
1210 ovs_controller_user
= self
.config
['ovs_controller_user']
1212 host_test_mode
= True if self
.config
['mode'] == 'test' or self
.config
['mode'] == "OF only" else False
1213 host_develop_mode
= True if self
.config
['mode'] == 'development' else False
1215 dhcp_host
= ht
.host_thread(name
='openvim_controller', user
=ovs_controller_user
, host
=controller_ip
,
1217 db_lock
=self
.db_lock
, test
=host_test_mode
,
1218 image_path
=self
.config
['image_path'], version
=self
.config
['version'],
1219 host_id
='openvim_controller', develop_mode
=host_develop_mode
,
1220 develop_bridge_iface
=bridge_ifaces
)
1222 self
.config
['host_threads']['openvim_controller'] = dhcp_host
1223 if not host_test_mode
:
1224 dhcp_host
.ssh_connect()
1227 def launch_dhcp_server(self
, vlan
, first_ip
, last_ip
, cidr
, gateway
):
1229 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
1230 :param vlan: vlan identifier
1231 :param first_ip: First dhcp range ip
1232 :param last_ip: Last dhcp range ip
1233 :param cidr: net cidr
1234 :param gateway: net gateway
1237 ip_tools
= IPNetwork(cidr
)
1238 dhcp_netmask
= str(ip_tools
.netmask
)
1239 ip_range
= [first_ip
, last_ip
]
1241 dhcp_path
= self
.config
['ovs_controller_file_path']
1243 controller_host
= self
.get_dhcp_controller()
1244 controller_host
.create_linux_bridge(vlan
)
1245 controller_host
.create_dhcp_interfaces(vlan
, first_ip
, dhcp_netmask
)
1246 controller_host
.launch_dhcp_server(vlan
, ip_range
, dhcp_netmask
, dhcp_path
, gateway
)