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
, IPAddress
, all_matching_cidrs
42 HTTP_Bad_Request
= 400
43 HTTP_Unauthorized
= 401
46 HTTP_Method_Not_Allowed
= 405
47 HTTP_Not_Acceptable
= 406
48 HTTP_Request_Timeout
= 408
50 HTTP_Service_Unavailable
= 503
51 HTTP_Internal_Server_Error
= 500
54 def convert_boolean(data
, items
):
55 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
56 It assumes that bandwidth is well formed
58 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
59 'items': tuple of keys to convert
63 if type(data
) is dict:
65 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
66 convert_boolean(data
[k
], items
)
68 if type(data
[k
]) is str:
69 if data
[k
] == "false":
71 elif data
[k
] == "true":
73 if type(data
) is tuple or type(data
) is list:
75 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
76 convert_boolean(k
, items
)
80 class ovimException(Exception):
81 def __init__(self
, message
, http_code
=HTTP_Bad_Request
):
82 self
.http_code
= http_code
83 Exception.__init
__(self
, message
)
87 running_info
= {} #TODO OVIM move the info of running threads from config_dic to this static variable
88 def __init__(self
, configuration
):
89 self
.config
= configuration
90 self
.logger
= logging
.getLogger(configuration
["logger_name"])
92 self
.db
= self
._create
_database
_connection
()
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']) )
108 def start_service(self
):
109 #if self.running_info:
110 # return #TODO service can be checked and rebuild broken threads
111 r
= self
.db
.get_db_version()
113 raise ovimException("DATABASE is not a VIM one or it is a '0.0' version. Try to upgrade to version '{}' with "\
114 "'./database_utils/migrate_vim_db.sh'".format(self
.config
["database_version"]) )
115 elif r
[1]!=self
.config
["database_version"]:
116 raise ovimException("DATABASE wrong version '{}'. Try to upgrade/downgrade to version '{}' with "\
117 "'./database_utils/migrate_vim_db.sh'".format(r
[1], self
.config
["database_version"]) )
119 # create database connection for openflow threads
120 db_of
= self
._create
_database
_connection
()
121 self
.config
["db"] = db_of
122 db_lock
= threading
.Lock()
123 self
.config
["db_lock"] = db_lock
125 # precreate interfaces; [bridge:<host_bridge_name>, VLAN used at Host, uuid of network camping in this bridge, speed in Gbit/s
126 self
.config
['dhcp_nets'] = []
127 self
.config
['bridge_nets'] = []
128 for bridge
, vlan_speed
in self
.config
["bridge_ifaces"].items():
129 # skip 'development_bridge'
130 if self
.config
['mode'] == 'development' and self
.config
['development_bridge'] == bridge
:
132 self
.config
['bridge_nets'].append([bridge
, vlan_speed
[0], vlan_speed
[1], None])
134 # check if this bridge is already used (present at database) for a network)
135 used_bridge_nets
= []
136 for brnet
in self
.config
['bridge_nets']:
137 r
, nets
= db_of
.get_table(SELECT
=('uuid',), FROM
='nets', WHERE
={'provider': "bridge:" + brnet
[0]})
139 brnet
[3] = nets
[0]['uuid']
140 used_bridge_nets
.append(brnet
[0])
141 if self
.config
.get("dhcp_server"):
142 if brnet
[0] in self
.config
["dhcp_server"]["bridge_ifaces"]:
143 self
.config
['dhcp_nets'].append(nets
[0]['uuid'])
144 if len(used_bridge_nets
) > 0:
145 self
.logger
.info("found used bridge nets: " + ",".join(used_bridge_nets
))
146 # get nets used by dhcp
147 if self
.config
.get("dhcp_server"):
148 for net
in self
.config
["dhcp_server"].get("nets", ()):
149 r
, nets
= db_of
.get_table(SELECT
=('uuid',), FROM
='nets', WHERE
={'name': net
})
151 self
.config
['dhcp_nets'].append(nets
[0]['uuid'])
153 # get host list from data base before starting threads
154 r
, hosts
= db_of
.get_table(SELECT
=('name', 'ip_name', 'user', 'uuid'), FROM
='hosts', WHERE
={'status': 'ok'})
156 raise ovimException("Cannot get hosts from database {}".format(hosts
))
157 # create connector to the openflow controller
158 of_test_mode
= False if self
.config
['mode'] == 'normal' or self
.config
['mode'] == "OF only" else True
161 OF_conn
= oft
.of_test_connector({"of_debug": self
.config
['log_level_of']})
163 # load other parameters starting by of_ from config dict in a temporal dict
164 temp_dict
= {"of_ip": self
.config
['of_controller_ip'],
165 "of_port": self
.config
['of_controller_port'],
166 "of_dpid": self
.config
['of_controller_dpid'],
167 "of_debug": self
.config
['log_level_of']
169 for k
, v
in self
.config
.iteritems():
170 if type(k
) is str and k
[0:3] == "of_" and k
[0:13] != "of_controller":
172 if self
.config
['of_controller'] == 'opendaylight':
174 elif "of_controller_module" in self
.config
:
175 module
= self
.config
["of_controller_module"]
177 module
= self
.config
['of_controller']
180 module_info
= imp
.find_module(module
)
182 OF_conn
= imp
.load_module("OF_conn", *module_info
)
184 OF_conn
= OF_conn
.OF_conn(temp_dict
)
185 except Exception as e
:
186 self
.logger
.error("Cannot open the Openflow controller '%s': %s", type(e
).__name
__, str(e
))
187 if module_info
and module_info
[0]:
188 file.close(module_info
[0])
190 except (IOError, ImportError) as e
:
191 if module_info
and module_info
[0]:
192 file.close(module_info
[0])
194 "Cannot open openflow controller module '%s'; %s: %s; revise 'of_controller' field of configuration file.",
195 module
, type(e
).__name
__, str(e
))
196 raise ovimException("Cannot open openflow controller module '{}'; {}: {}; revise 'of_controller' field of configuration file.".fromat(
197 module
, type(e
).__name
__, str(e
)))
200 # create openflow thread
201 thread
= oft
.openflow_thread(OF_conn
, of_test
=of_test_mode
, db
=db_of
, db_lock
=db_lock
,
202 pmp_with_same_vlan
=self
.config
['of_controller_nets_with_same_vlan'],
203 debug
=self
.config
['log_level_of'])
204 r
, c
= thread
.OF_connector
.obtain_port_correspondence()
206 raise ovimException("Cannot get openflow information %s", c
)
208 self
.config
['of_thread'] = thread
210 # create dhcp_server thread
211 host_test_mode
= True if self
.config
['mode'] == 'test' or self
.config
['mode'] == "OF only" else False
212 dhcp_params
= self
.config
.get("dhcp_server")
214 thread
= dt
.dhcp_thread(dhcp_params
=dhcp_params
, test
=host_test_mode
, dhcp_nets
=self
.config
["dhcp_nets"],
215 db
=db_of
, db_lock
=db_lock
, debug
=self
.config
['log_level_of'])
217 self
.config
['dhcp_thread'] = thread
219 # Create one thread for each host
220 host_test_mode
= True if self
.config
['mode'] == 'test' or self
.config
['mode'] == "OF only" else False
221 host_develop_mode
= True if self
.config
['mode'] == 'development' else False
222 host_develop_bridge_iface
= self
.config
.get('development_bridge', None)
223 self
.config
['host_threads'] = {}
225 host
['image_path'] = '/opt/VNF/images/openvim'
226 thread
= ht
.host_thread(name
=host
['name'], user
=host
['user'], host
=host
['ip_name'], db
=db_of
, db_lock
=db_lock
,
227 test
=host_test_mode
, image_path
=self
.config
['image_path'], version
=self
.config
['version'],
228 host_id
=host
['uuid'], develop_mode
=host_develop_mode
,
229 develop_bridge_iface
=host_develop_bridge_iface
)
231 self
.config
['host_threads'][host
['uuid']] = thread
233 # create ovs dhcp thread
234 result
, content
= self
.db
.get_table(FROM
='nets')
236 self
.logger
.error("http_get_ports Error %d %s", result
, content
)
237 raise ovimException(str(content
), -result
)
240 net_type
= net
['type']
241 if net_type
== 'bridge_data' or net_type
== 'bridge_man' and net
["provider"][:4]=="OVS:" and net
["enable_dhcp"] == "true":
242 if net
['enable_dhcp'] == 'true':
243 self
.launch_dhcp_server(net
['vlan'], net
['dhcp_first_ip'], net
['dhcp_last_ip'], net
['cidr'])
245 def stop_service(self
):
246 threads
= self
.config
.get('host_threads', {})
247 if 'of_thread' in self
.config
:
248 threads
['of'] = (self
.config
['of_thread'])
249 if 'dhcp_thread' in self
.config
:
250 threads
['dhcp'] = (self
.config
['dhcp_thread'])
252 for thread
in threads
.values():
253 thread
.insert_task("exit")
254 for thread
in threads
.values():
258 def get_ports(self
, columns
=None, filter={}, limit
=None):
259 # result, content = my.db.get_ports(where_)
260 result
, content
= self
.db
.get_table(SELECT
=columns
, WHERE
=filter, FROM
='ports', LIMIT
=limit
)
262 self
.logger
.error("http_get_ports Error %d %s", result
, content
)
263 raise ovimException(str(content
), -result
)
265 convert_boolean(content
, ('admin_state_up',))
269 def new_port(self
, port_data
):
270 port_data
['type'] = 'external'
271 if port_data
.get('net_id'):
272 # check that new net has the correct type
273 result
, new_net
= self
.db
.check_target_net(port_data
['net_id'], None, 'external')
275 raise ovimException(str(new_net
), -result
)
276 # insert in data base
277 result
, uuid
= self
.db
.new_row('ports', port_data
, True, True)
279 if 'net_id' in port_data
:
280 r
, c
= self
.config
['of_thread'].insert_task("update-net", port_data
['net_id'])
282 self
.logger
.error("Cannot insert a task for updating network '$s' %s", port_data
['net_id'], c
)
283 #TODO put network in error status
286 raise ovimException(str(uuid
), -result
)
288 def delete_port(self
, port_id
):
289 # Look for the previous port data
290 result
, ports
= self
.db
.get_table(WHERE
={'uuid': port_id
, "type": "external"}, FROM
='ports')
292 raise ovimException("Cannot get port info from database: {}".format(ports
), http_code
=-result
)
293 # delete from the data base
294 result
, content
= self
.db
.delete_row('ports', port_id
)
296 raise ovimException("External port '{}' not found".format(port_id
), http_code
=HTTP_Not_Found
)
298 raise ovimException("Cannot delete port from database: {}".format(content
), http_code
=-result
)
300 network
= ports
[0].get('net_id', None)
303 r
, c
= self
.config
['of_thread'].insert_task("update-net", network
)
305 self
.logger
.error("Cannot insert a task for updating network '$s' %s", network
, c
)
309 def edit_port(self
, port_id
, port_data
, admin
=True):
310 # Look for the previous port data
311 result
, content
= self
.db
.get_table(FROM
="ports", WHERE
={'uuid': port_id
})
313 raise ovimException("Cannot get port info from database: {}".format(content
), http_code
=-result
)
315 raise ovimException("Port '{}' not found".format(port_id
), http_code
=HTTP_Not_Found
)
320 if 'net_id' in port_data
:
322 old_net
= port
.get('net_id', None)
323 new_net
= port_data
['net_id']
324 if old_net
!= new_net
:
327 nets
.append(new_net
) # put first the new net, so that new openflow rules are created before removing the old ones
330 if port
['type'] == 'instance:bridge' or port
['type'] == 'instance:ovs':
331 raise ovimException("bridge interfaces cannot be attached to a different net", http_code
=HTTP_Forbidden
)
332 elif port
['type'] == 'external' and not admin
:
333 raise ovimException("Needed admin privileges",http_code
=HTTP_Unauthorized
)
335 # check that new net has the correct type
336 result
, new_net_dict
= self
.db
.check_target_net(new_net
, None, port
['type'])
338 raise ovimException("Error {}".format(new_net_dict
), http_code
=HTTP_Conflict
)
339 # change VLAN for SR-IOV ports
340 if result
>= 0 and port
["type"] == "instance:data" and port
["model"] == "VF": # TODO consider also VFnotShared
342 port_data
["vlan"] = None
344 port_data
["vlan"] = new_net_dict
["vlan"]
345 # get host where this VM is allocated
346 result
, content
= self
.db
.get_table(FROM
="instances", WHERE
={"uuid": port
["instance_id"]})
348 host_id
= content
[0]["host_id"]
350 # insert in data base
352 result
, content
= self
.db
.update_rows('ports', port_data
, WHERE
={'uuid': port_id
}, log
=False)
354 # Insert task to complete actions
357 r
, v
= self
.config
['of_thread'].insert_task("update-net", net_id
)
359 self
.logger
.error("Error updating network '{}' {}".format(r
,v
))
360 # TODO Do something if fails
362 r
, v
= self
.config
['host_threads'][host_id
].insert_task("edit-iface", port_id
, old_net
, new_net
)
364 self
.logger
.error("Error updating network '{}' {}".format(r
,v
))
365 # TODO Do something if fails
369 raise ovimException("Error {}".format(content
), http_code
=-result
)
371 def get_dhcp_controller(self
):
373 Create an host_thread object for manage openvim controller and not create a thread for itself
374 :return: dhcp_host openvim controller object
377 if 'openvim_controller' in self
.config
['host_threads']:
378 return self
.config
['host_threads']['openvim_controller']
381 controller_ip
= self
.config
['ovs_controller_ip']
382 ovs_controller_user
= self
.config
['ovs_controller_user']
384 host_test_mode
= True if self
.config
['mode'] == 'test' or self
.config
['mode'] == "OF only" else False
385 host_develop_mode
= True if self
.config
['mode'] == 'development' else False
387 dhcp_host
= ht
.host_thread(name
='openvim_controller', user
=ovs_controller_user
, host
=controller_ip
,
388 db
=self
.config
['db'],
389 db_lock
=self
.config
['db_lock'], test
=host_test_mode
,
390 image_path
=self
.config
['image_path'], version
=self
.config
['version'],
391 host_id
='openvim_controller', develop_mode
=host_develop_mode
,
392 develop_bridge_iface
=bridge_ifaces
)
394 self
.config
['host_threads']['openvim_controller'] = dhcp_host
395 if not host_test_mode
:
396 dhcp_host
.ssh_connect()
399 def launch_dhcp_server(self
, vlan
, first_ip
, last_ip
, cidr
, gateway
):
401 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
402 :param vlan: vlan identifier
403 :param first_ip: First dhcp range ip
404 :param last_ip: Last dhcp range ip
405 :param cidr: net cidr
408 ip_tools
= IPNetwork(cidr
)
409 dhcp_netmask
= str(ip_tools
.netmask
)
410 ip_range
= [first_ip
, last_ip
]
412 dhcp_path
= self
.config
['ovs_controller_file_path']
414 controller_host
= self
.get_dhcp_controller()
415 controller_host
.create_linux_bridge(vlan
)
416 controller_host
.create_dhcp_interfaces(vlan
, first_ip
, dhcp_netmask
)
417 controller_host
.launch_dhcp_server(vlan
, ip_range
, dhcp_netmask
, dhcp_path
, gateway
)