minor changes on stopping threads
[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
41 HTTP_Bad_Request = 400
42 HTTP_Unauthorized = 401
43 HTTP_Not_Found = 404
44 HTTP_Forbidden = 403
45 HTTP_Method_Not_Allowed = 405
46 HTTP_Not_Acceptable = 406
47 HTTP_Request_Timeout = 408
48 HTTP_Conflict = 409
49 HTTP_Service_Unavailable = 503
50 HTTP_Internal_Server_Error= 500
51
52
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
56 Attributes:
57 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
58 'items': tuple of keys to convert
59 Return:
60 None
61 '''
62 if type(data) is dict:
63 for k in data.keys():
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)
66 if k in items:
67 if type(data[k]) is str:
68 if data[k] == "false":
69 data[k] = False
70 elif data[k] == "true":
71 data[k] = True
72 if type(data) is tuple or type(data) is list:
73 for k in data:
74 if type(k) is dict or type(k) is tuple or type(k) is list:
75 convert_boolean(k, items)
76
77
78
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)
83
84
85 class ovim():
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"])
90 self.db = None
91 self.db = self._create_database_connection()
92
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']) )
103 return db
104
105
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()
110 if r[0]<0:
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"]) )
116
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
122
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:
129 continue
130 self.config['bridge_nets'].append([bridge, vlan_speed[0], vlan_speed[1], None])
131
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]})
136 if r > 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})
148 if r > 0:
149 self.config['dhcp_nets'].append(nets[0]['uuid'])
150
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'})
153 if r < 0:
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
157
158 if of_test_mode:
159 OF_conn = oft.of_test_connector({"of_debug": self.config['log_level_of']})
160 else:
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']
166 }
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":
169 temp_dict[k] = v
170 if self.config['of_controller'] == 'opendaylight':
171 module = "ODL"
172 elif "of_controller_module" in self.config:
173 module = self.config["of_controller_module"]
174 else:
175 module = self.config['of_controller']
176 module_info = None
177 try:
178 module_info = imp.find_module(module)
179
180 OF_conn = imp.load_module("OF_conn", *module_info)
181 try:
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])
187 exit(-1)
188 except (IOError, ImportError) as e:
189 if module_info and module_info[0]:
190 file.close(module_info[0])
191 self.logger.error(
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)))
196
197
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()
203 if r < 0:
204 raise ovimException("Cannot get openflow information %s", c)
205 thread.start()
206 self.config['of_thread'] = thread
207
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")
211 if dhcp_params:
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'])
214 thread.start()
215 self.config['dhcp_thread'] = thread
216
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'] = {}
222 for host in hosts:
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)
228 thread.start()
229 self.config['host_threads'][host['uuid']] = thread
230
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'])
237
238 for thread in threads.values():
239 thread.insert_task("exit")
240 for thread in threads.values():
241 thread.join()
242
243
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)
247 if result < 0:
248 self.logger.error("http_get_ports Error %d %s", result, content)
249 raise ovimException(str(content), -result)
250 else:
251 convert_boolean(content, ('admin_state_up',))
252 return content
253
254
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')
260 if result < 0:
261 raise ovimException(str(new_net), -result)
262 # insert in data base
263 result, uuid = self.db.new_row('ports', port_data, True, True)
264 if result > 0:
265 if 'net_id' in port_data:
266 r, c = self.config['of_thread'].insert_task("update-net", port_data['net_id'])
267 if r < 0:
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
270 return uuid
271 else:
272 raise ovimException(str(uuid), -result)
273
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')
277 if result < 0:
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)
281 if result == 0:
282 raise ovimException("External port '{}' not found".format(port_id), http_code=HTTP_Not_Found)
283 elif result < 0:
284 raise ovimException("Cannot delete port from database: {}".format(content), http_code=-result)
285 # update network
286 network = ports[0].get('net_id', None)
287 if network:
288 # change of net.
289 r, c = self.config['of_thread'].insert_task("update-net", network)
290 if r < 0:
291 self.logger.error("Cannot insert a task for updating network '$s' %s", network, c)
292 return content
293
294
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})
298 if result < 0:
299 raise ovimException("Cannot get port info from database: {}".format(content), http_code=-result)
300 elif result == 0:
301 raise ovimException("Port '{}' not found".format(port_id), http_code=HTTP_Not_Found)
302 port = content[0]
303 nets = []
304 host_id = None
305 result = 1
306 if 'net_id' in port_data:
307 # change of net.
308 old_net = port.get('net_id', None)
309 new_net = port_data['net_id']
310 if old_net != new_net:
311
312 if new_net:
313 nets.append(new_net) # put first the new net, so that new openflow rules are created before removing the old ones
314 if old_net:
315 nets.append(old_net)
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)
320 if new_net:
321 # check that new net has the correct type
322 result, new_net_dict = self.db.check_target_net(new_net, None, port['type'])
323 if result < 0:
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
327 if new_net:
328 port_data["vlan"] = None
329 else:
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"]})
333 if result > 0:
334 host_id = content[0]["host_id"]
335
336 # insert in data base
337 if result >= 0:
338 result, content = self.db.update_rows('ports', port_data, WHERE={'uuid': port_id}, log=False)
339
340 # Insert task to complete actions
341 if result > 0:
342 for net_id in nets:
343 r, v = self.config['of_thread'].insert_task("update-net", net_id)
344 if r < 0:
345 self.logger.error("Error updating network '{}' {}".format(r,v))
346 # TODO Do something if fails
347 if host_id:
348 r, v = self.config['host_threads'][host_id].insert_task("edit-iface", port_id, old_net, new_net)
349 if r < 0:
350 self.logger.error("Error updating network '{}' {}".format(r,v))
351 # TODO Do something if fails
352 if result >= 0:
353 return port_id
354 else:
355 raise ovimException("Error {}".format(content), http_code=-result)