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
41 HTTP_Bad_Request
= 400
42 HTTP_Unauthorized
= 401
45 HTTP_Method_Not_Allowed
= 405
46 HTTP_Not_Acceptable
= 406
47 HTTP_Request_Timeout
= 408
49 HTTP_Service_Unavailable
= 503
50 HTTP_Internal_Server_Error
= 500
53 def convert_boolean(data
, items
):
54 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
55 It assumes that bandwidth is well formed
57 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
58 'items': tuple of keys to convert
62 if type(data
) is dict:
64 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
65 convert_boolean(data
[k
], items
)
67 if type(data
[k
]) is str:
68 if data
[k
] == "false":
70 elif data
[k
] == "true":
72 if type(data
) is tuple or type(data
) is list:
74 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
75 convert_boolean(k
, items
)
79 class ovimException(Exception):
80 def __init__(self
, message
, http_code
=HTTP_Bad_Request
):
81 self
.http_code
= http_code
82 Exception.__init
__(self
, message
)
86 running_info
= {} #TODO OVIM move the info of running threads from config_dic to this static variable
87 def __init__(self
, configuration
):
88 self
.config
= configuration
89 self
.logger
= logging
.getLogger(configuration
["logger_name"])
91 self
.db
= self
._create
_database
_connection
()
93 def _create_database_connection(self
):
94 db
= vim_db
.vim_db((self
.config
["network_vlan_range_start"], self
.config
["network_vlan_range_end"]),
95 self
.config
['log_level_db']);
96 if db
.connect(self
.config
['db_host'], self
.config
['db_user'], self
.config
['db_passwd'],
97 self
.config
['db_name']) == -1:
98 # self.logger.error("Cannot connect to database %s at %s@%s", self.config['db_name'], self.config['db_user'],
99 # self.config['db_host'])
100 raise ovimException("Cannot connect to database {} at {}@{}".format(self
.config
['db_name'],
101 self
.config
['db_user'],
102 self
.config
['db_host']) )
106 def start_service(self
):
107 #if self.running_info:
108 # return #TODO service can be checked and rebuild broken threads
109 r
= self
.db
.get_db_version()
111 raise ovimException("DATABASE is not a VIM one or it is a '0.0' version. Try to upgrade to version '{}' with "\
112 "'./database_utils/migrate_vim_db.sh'".format(self
.config
["database_version"]) )
113 elif r
[1]!=self
.config
["database_version"]:
114 raise ovimException("DATABASE wrong version '{}'. Try to upgrade/downgrade to version '{}' with "\
115 "'./database_utils/migrate_vim_db.sh'".format(r
[1], self
.config
["database_version"]) )
117 # create database connection for openflow threads
118 db_of
= self
._create
_database
_connection
()
119 self
.config
["db"] = db_of
120 db_lock
= threading
.Lock()
121 self
.config
["db_lock"] = db_lock
123 # precreate interfaces; [bridge:<host_bridge_name>, VLAN used at Host, uuid of network camping in this bridge, speed in Gbit/s
124 self
.config
['dhcp_nets'] = []
125 self
.config
['bridge_nets'] = []
126 for bridge
, vlan_speed
in self
.config
["bridge_ifaces"].items():
127 # skip 'development_bridge'
128 if self
.config
['mode'] == 'development' and self
.config
['development_bridge'] == bridge
:
130 self
.config
['bridge_nets'].append([bridge
, vlan_speed
[0], vlan_speed
[1], None])
132 # check if this bridge is already used (present at database) for a network)
133 used_bridge_nets
= []
134 for brnet
in self
.config
['bridge_nets']:
135 r
, nets
= db_of
.get_table(SELECT
=('uuid',), FROM
='nets', WHERE
={'provider': "bridge:" + brnet
[0]})
137 brnet
[3] = nets
[0]['uuid']
138 used_bridge_nets
.append(brnet
[0])
139 if self
.config
.get("dhcp_server"):
140 if brnet
[0] in self
.config
["dhcp_server"]["bridge_ifaces"]:
141 self
.config
['dhcp_nets'].append(nets
[0]['uuid'])
142 if len(used_bridge_nets
) > 0:
143 self
.logger
.info("found used bridge nets: " + ",".join(used_bridge_nets
))
144 # get nets used by dhcp
145 if self
.config
.get("dhcp_server"):
146 for net
in self
.config
["dhcp_server"].get("nets", ()):
147 r
, nets
= db_of
.get_table(SELECT
=('uuid',), FROM
='nets', WHERE
={'name': net
})
149 self
.config
['dhcp_nets'].append(nets
[0]['uuid'])
151 # get host list from data base before starting threads
152 r
, hosts
= db_of
.get_table(SELECT
=('name', 'ip_name', 'user', 'uuid'), FROM
='hosts', WHERE
={'status': 'ok'})
154 raise ovimException("Cannot get hosts from database {}".format(hosts
))
155 # create connector to the openflow controller
156 of_test_mode
= False if self
.config
['mode'] == 'normal' or self
.config
['mode'] == "OF only" else True
159 OF_conn
= oft
.of_test_connector({"of_debug": self
.config
['log_level_of']})
161 # load other parameters starting by of_ from config dict in a temporal dict
162 temp_dict
= {"of_ip": self
.config
['of_controller_ip'],
163 "of_port": self
.config
['of_controller_port'],
164 "of_dpid": self
.config
['of_controller_dpid'],
165 "of_debug": self
.config
['log_level_of']
167 for k
, v
in self
.config
.iteritems():
168 if type(k
) is str and k
[0:3] == "of_" and k
[0:13] != "of_controller":
170 if self
.config
['of_controller'] == 'opendaylight':
172 elif "of_controller_module" in self
.config
:
173 module
= self
.config
["of_controller_module"]
175 module
= self
.config
['of_controller']
178 module_info
= imp
.find_module(module
)
180 OF_conn
= imp
.load_module("OF_conn", *module_info
)
182 OF_conn
= OF_conn
.OF_conn(temp_dict
)
183 except Exception as e
:
184 self
.logger
.error("Cannot open the Openflow controller '%s': %s", type(e
).__name
__, str(e
))
185 if module_info
and module_info
[0]:
186 file.close(module_info
[0])
188 except (IOError, ImportError) as e
:
189 if module_info
and module_info
[0]:
190 file.close(module_info
[0])
192 "Cannot open openflow controller module '%s'; %s: %s; revise 'of_controller' field of configuration file.",
193 module
, type(e
).__name
__, str(e
))
194 raise ovimException("Cannot open openflow controller module '{}'; {}: {}; revise 'of_controller' field of configuration file.".fromat(
195 module
, type(e
).__name
__, str(e
)))
198 # create openflow thread
199 thread
= oft
.openflow_thread(OF_conn
, of_test
=of_test_mode
, db
=db_of
, db_lock
=db_lock
,
200 pmp_with_same_vlan
=self
.config
['of_controller_nets_with_same_vlan'],
201 debug
=self
.config
['log_level_of'])
202 r
, c
= thread
.OF_connector
.obtain_port_correspondence()
204 raise ovimException("Cannot get openflow information %s", c
)
206 self
.config
['of_thread'] = thread
208 # create dhcp_server thread
209 host_test_mode
= True if self
.config
['mode'] == 'test' or self
.config
['mode'] == "OF only" else False
210 dhcp_params
= self
.config
.get("dhcp_server")
212 thread
= dt
.dhcp_thread(dhcp_params
=dhcp_params
, test
=host_test_mode
, dhcp_nets
=self
.config
["dhcp_nets"],
213 db
=db_of
, db_lock
=db_lock
, debug
=self
.config
['log_level_of'])
215 self
.config
['dhcp_thread'] = thread
217 # Create one thread for each host
218 host_test_mode
= True if self
.config
['mode'] == 'test' or self
.config
['mode'] == "OF only" else False
219 host_develop_mode
= True if self
.config
['mode'] == 'development' else False
220 host_develop_bridge_iface
= self
.config
.get('development_bridge', None)
221 self
.config
['host_threads'] = {}
223 host
['image_path'] = '/opt/VNF/images/openvim'
224 thread
= ht
.host_thread(name
=host
['name'], user
=host
['user'], host
=host
['ip_name'], db
=db_of
, db_lock
=db_lock
,
225 test
=host_test_mode
, image_path
=self
.config
['image_path'], version
=self
.config
['version'],
226 host_id
=host
['uuid'], develop_mode
=host_develop_mode
,
227 develop_bridge_iface
=host_develop_bridge_iface
)
229 self
.config
['host_threads'][host
['uuid']] = thread
231 def stop_service(self
):
232 threads
= self
.config
.get('host_threads', {})
233 if 'of_thread' in self
.config
:
234 threads
['of'] = (self
.config
['of_thread'])
235 if 'dhcp_thread' in self
.config
:
236 threads
['dhcp'] = (self
.config
['dhcp_thread'])
238 for thread
in threads
.values():
239 thread
.insert_task("exit")
240 for thread
in threads
.values():
244 def get_ports(self
, columns
=None, filter={}, limit
=None):
245 # result, content = my.db.get_ports(where_)
246 result
, content
= self
.db
.get_table(SELECT
=columns
, WHERE
=filter, FROM
='ports', LIMIT
=limit
)
248 self
.logger
.error("http_get_ports Error %d %s", result
, content
)
249 raise ovimException(str(content
), -result
)
251 convert_boolean(content
, ('admin_state_up',))
255 def new_port(self
, port_data
):
256 port_data
['type'] = 'external'
257 if port_data
.get('net_id'):
258 # check that new net has the correct type
259 result
, new_net
= self
.db
.check_target_net(port_data
['net_id'], None, 'external')
261 raise ovimException(str(new_net
), -result
)
262 # insert in data base
263 result
, uuid
= self
.db
.new_row('ports', port_data
, True, True)
265 if 'net_id' in port_data
:
266 r
, c
= self
.config
['of_thread'].insert_task("update-net", port_data
['net_id'])
268 self
.logger
.error("Cannot insert a task for updating network '$s' %s", port_data
['net_id'], c
)
269 #TODO put network in error status
272 raise ovimException(str(uuid
), -result
)
274 def delete_port(self
, port_id
):
275 # Look for the previous port data
276 result
, ports
= self
.db
.get_table(WHERE
={'uuid': port_id
, "type": "external"}, FROM
='ports')
278 raise ovimException("Cannot get port info from database: {}".format(ports
), http_code
=-result
)
279 # delete from the data base
280 result
, content
= self
.db
.delete_row('ports', port_id
)
282 raise ovimException("External port '{}' not found".format(port_id
), http_code
=HTTP_Not_Found
)
284 raise ovimException("Cannot delete port from database: {}".format(content
), http_code
=-result
)
286 network
= ports
[0].get('net_id', None)
289 r
, c
= self
.config
['of_thread'].insert_task("update-net", network
)
291 self
.logger
.error("Cannot insert a task for updating network '$s' %s", network
, c
)
295 def edit_port(self
, port_id
, port_data
, admin
=True):
296 # Look for the previous port data
297 result
, content
= self
.db
.get_table(FROM
="ports", WHERE
={'uuid': port_id
})
299 raise ovimException("Cannot get port info from database: {}".format(content
), http_code
=-result
)
301 raise ovimException("Port '{}' not found".format(port_id
), http_code
=HTTP_Not_Found
)
306 if 'net_id' in port_data
:
308 old_net
= port
.get('net_id', None)
309 new_net
= port_data
['net_id']
310 if old_net
!= new_net
:
313 nets
.append(new_net
) # put first the new net, so that new openflow rules are created before removing the old ones
316 if port
['type'] == 'instance:bridge' or port
['type'] == 'instance:ovs':
317 raise ovimException("bridge interfaces cannot be attached to a different net", http_code
=HTTP_Forbidden
)
318 elif port
['type'] == 'external' and not admin
:
319 raise ovimException("Needed admin privileges",http_code
=HTTP_Unauthorized
)
321 # check that new net has the correct type
322 result
, new_net_dict
= self
.db
.check_target_net(new_net
, None, port
['type'])
324 raise ovimException("Error {}".format(new_net_dict
), http_code
=HTTP_Conflict
)
325 # change VLAN for SR-IOV ports
326 if result
>= 0 and port
["type"] == "instance:data" and port
["model"] == "VF": # TODO consider also VFnotShared
328 port_data
["vlan"] = None
330 port_data
["vlan"] = new_net_dict
["vlan"]
331 # get host where this VM is allocated
332 result
, content
= self
.db
.get_table(FROM
="instances", WHERE
={"uuid": port
["instance_id"]})
334 host_id
= content
[0]["host_id"]
336 # insert in data base
338 result
, content
= self
.db
.update_rows('ports', port_data
, WHERE
={'uuid': port_id
}, log
=False)
340 # Insert task to complete actions
343 r
, v
= self
.config
['of_thread'].insert_task("update-net", net_id
)
345 self
.logger
.error("Error updating network '{}' {}".format(r
,v
))
346 # TODO Do something if fails
348 r
, v
= self
.config
['host_threads'][host_id
].insert_task("edit-iface", port_id
, old_net
, new_net
)
350 self
.logger
.error("Error updating network '{}' {}".format(r
,v
))
351 # TODO Do something if fails
355 raise ovimException("Error {}".format(content
), http_code
=-result
)