91cc9b2c17fa94a12d8a1d9d0f4a87423b89cf25
[osm/RO.git] / RO / osm_ro / sdn.py
1 # -*- coding: utf-8 -*-
2
3 ##
4 # Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U.
5 # All Rights Reserved.
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License"); you may
8 # not use this file except in compliance with the License. You may obtain
9 # a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16 # License for the specific language governing permissions and limitations
17 # under the License.
18 ##
19
20 """
21 This is the thread for the http server North API.
22 Two thread will be launched, with normal and administrative permissions.
23 """
24 import yaml
25 from uuid import uuid4
26 from http import HTTPStatus
27
28 __author__ = "Alfonso Tierno"
29 __date__ = "2019-10-22"
30 __version__ = "0.1"
31 version_date = "Oct 2019"
32
33
34 class SdnException(Exception):
35 def __init__(self, message, http_code=HTTPStatus.BAD_REQUEST.value):
36 self.http_code = http_code
37 Exception.__init__(self, message)
38
39
40 class Sdn():
41 running_info = {} # TODO OVIM move the info of running threads from config_dic to this static variable
42 of_module = {}
43
44 def __init__(self, db, plugins):
45 self.db = db
46 self.plugins = plugins
47
48 def start_service(self):
49 pass # TODO py3 needed to load wims and plugins
50
51 def stop_service(self):
52 pass # nothing needed
53
54 def show_network(self, uuid):
55 pass
56
57 def delete_network(self, uuid):
58 pass
59
60 def new_network(self, network):
61 pass
62
63 def get_openflow_rules(self, network_id=None):
64 """
65 Get openflow id from DB
66 :param network_id: Network id, if none all networks will be retrieved
67 :return: Return a list with Openflow rules per net
68 """
69 # ignore input data
70 if not network_id:
71
72 where_ = {}
73 else:
74 where_ = {"net_id": network_id}
75 result, content = self.db.get_table(
76 SELECT=("name", "net_id", "ofc_id", "priority", "vlan_id", "ingress_port", "src_mac", "dst_mac", "actions"),
77 WHERE=where_, FROM='of_flows')
78
79 if result < 0:
80 raise SdnException(str(content), -result)
81 return content
82
83 def edit_openflow_rules(self, network_id=None):
84
85 """
86 To make actions over the net. The action is to reinstall the openflow rules
87 network_id can be 'all'
88 :param network_id: Network id, if none all networks will be retrieved
89 :return : Number of nets updated
90 """
91
92 # ignore input data
93 if not network_id:
94 where_ = {}
95 else:
96 where_ = {"uuid": network_id}
97 result, content = self.db.get_table(SELECT=("uuid", "type"), WHERE=where_, FROM='nets')
98
99 if result < 0:
100 raise SdnException(str(content), -result)
101
102 for net in content:
103 if net["type"] != "ptp" and net["type"] != "data":
104 result -= 1
105 continue
106
107 try:
108 self.net_update_ofc_thread(net['uuid'])
109 except SdnException as e:
110 raise SdnException("Error updating network'{}' {}".format(net['uuid'], e),
111 HTTPStatus.INTERNAL_SERVER_ERROR.value)
112 except Exception as e:
113 raise SdnException("Error updating network '{}' {}".format(net['uuid'], e),
114 HTTPStatus.INTERNAL_SERVER_ERROR.value)
115
116 return result
117
118 def delete_openflow_rules(self, ofc_id=None):
119 """
120 To make actions over the net. The action is to delete ALL openflow rules
121 :return: return operation result
122 """
123
124 if not ofc_id:
125 if 'Default' in self.config['ofcs_thread']:
126 r, c = self.config['ofcs_thread']['Default'].insert_task("clear-all")
127 else:
128 raise SdnException("Default Openflow controller not not running", HTTPStatus.NOT_FOUND.value)
129
130 elif ofc_id in self.config['ofcs_thread']:
131 r, c = self.config['ofcs_thread'][ofc_id].insert_task("clear-all")
132
133 # ignore input data
134 if r < 0:
135 raise SdnException(str(c), -r)
136 else:
137 raise SdnException("Openflow controller not found with ofc_id={}".format(ofc_id),
138 HTTPStatus.NOT_FOUND.value)
139 return r
140
141 def get_openflow_ports(self, ofc_id=None):
142 """
143 Obtain switch ports names of openflow controller
144 :return: Return flow ports in DB
145 """
146 if not ofc_id:
147 if 'Default' in self.config['ofcs_thread']:
148 conn = self.config['ofcs_thread']['Default'].OF_connector
149 else:
150 raise SdnException("Default Openflow controller not not running", HTTPStatus.NOT_FOUND.value)
151
152 elif ofc_id in self.config['ofcs_thread']:
153 conn = self.config['ofcs_thread'][ofc_id].OF_connector
154 else:
155 raise SdnException("Openflow controller not found with ofc_id={}".format(ofc_id),
156 HTTPStatus.NOT_FOUND.value)
157 return conn.pp2ofi
158
159 def new_of_controller(self, ofc_data):
160 """
161 Create a new openflow controller into DB
162 :param ofc_data: Dict openflow controller data
163 :return: openflow controller dpid
164 """
165 db_wim = {
166 "uuid": str(uuid4()),
167 "name": ofc_data["name"],
168 "description": "",
169 "type": ofc_data["type"],
170 "wim_url": "{}:{}".format(ofc_data["ip"], ofc_data["port"]),
171 }
172 db_wim_account = {
173 "uuid": str(uuid4()),
174 "name": ofc_data["name"],
175 "wim_id": db_wim["uuid"],
176 "sdn": "true",
177 "user": ofc_data.get("user"),
178 "password": ofc_data.get("password"),
179 "config": yaml.safe_dump({"dpid": ofc_data["dpid"], "version": ofc_data.get("version")},
180 default_flow_style=True, width=256)
181 }
182 db_tables = [
183 {"wims": db_wim},
184 {"wim_accounts": db_wim_account},
185 ]
186 uuid_list = [db_wim["uuid"], db_wim_account["uuid"]]
187 self.db.new_rows(db_tables, uuid_list)
188 return db_wim_account["uuid"]
189
190 def edit_of_controller(self, of_id, ofc_data):
191 """
192 Edit an openflow controller entry from DB
193 :return:
194 """
195 if not ofc_data:
196 raise SdnException("No data received during uptade OF contorller",
197 http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value)
198
199 old_of_controller = self.show_of_controller(of_id)
200
201 if old_of_controller:
202 result, content = self.db.update_rows('ofcs', ofc_data, WHERE={'uuid': of_id}, log=False)
203 if result >= 0:
204 return ofc_data
205 else:
206 raise SdnException("Error uptating OF contorller with uuid {}".format(of_id),
207 http_code=-result)
208 else:
209 raise SdnException("Error uptating OF contorller with uuid {}".format(of_id),
210 http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value)
211
212 def delete_of_controller(self, of_id):
213 """
214 Delete an openflow controller from DB.
215 :param of_id: openflow controller dpid
216 :return:
217 """
218 wim_accounts = self.db.get_rows(FROM='wim_accounts', WHERE={"uuid": of_id, "sdn": "true"})
219 if not wim_accounts:
220 raise SdnException("Cannot find sdn controller with id='{}'".format(of_id),
221 http_code=HTTPStatus.NOT_FOUND.value)
222 elif len(wim_accounts) > 1:
223 raise SdnException("Found more than one sdn controller with id='{}'".format(of_id),
224 http_code=HTTPStatus.CONFLICT.value)
225 self.db.delete_row(FROM='wim_accounts', WHERE={"uuid": of_id})
226 self.db.delete_row(FROM='wims', WHERE={"uuid": wim_accounts[0]["wim_id"]})
227 return of_id
228
229 def _format_of_controller(self, wim_account, wim=None):
230 of_data = {x: wim_account[x] for x in ("uuid", "name", "user")}
231 if isinstance(wim_account["config"], str):
232 config = yaml.load(wim_account["config"], Loader=yaml.Loader)
233 of_data["dpid"] = config.get("dpid")
234 of_data["version"] = config.get("version")
235 if wim:
236 ip, port = wim["wim_url"].split(":")
237 of_data["ip"] = ip
238 of_data["port"] = port
239 of_data["type"] = wim["type"]
240 return of_data
241
242 def show_of_controller(self, of_id):
243 """
244 Show an openflow controller by dpid from DB.
245 :param db_filter: List with where query parameters
246 :return:
247 """
248 wim_accounts = self.db.get_rows(FROM='wim_accounts', WHERE={"uuid": of_id, "sdn": "true"})
249 if not wim_accounts:
250 raise SdnException("Cannot find sdn controller with id='{}'".format(of_id),
251 http_code=HTTPStatus.NOT_FOUND.value)
252 elif len(wim_accounts) > 1:
253 raise SdnException("Found more than one sdn controller with id='{}'".format(of_id),
254 http_code=HTTPStatus.CONFLICT.value)
255 wims = self.db.get_rows(FROM='wims', WHERE={"uuid": wim_accounts[0]["wim_id"]})
256 return self._format_of_controller(wim_accounts[0], wims[0])
257
258 def get_of_controllers(self, filter=None):
259 """
260 Show an openflow controllers from DB.
261 :return:
262 """
263 filter = filter or {}
264 filter["sdn"] = "true"
265 wim_accounts = self.db.get_rows(FROM='wim_accounts', WHERE=filter)
266 return [self._format_of_controller(w) for w in wim_accounts]
267
268 def set_of_port_mapping(self, maps, sdn_id, switch_dpid, vim_id):
269 """
270 Create new port mapping entry
271 :param of_maps: List with port mapping information
272 # maps =[{"ofc_id": <ofc_id>,"region": datacenter region,"compute_node": compute uuid,"pci": pci adress,
273 "switch_dpid": swith dpid,"switch_port": port name,"switch_mac": mac}]
274 :param sdn_id: ofc id
275 :param switch_dpid: switch dpid
276 :param vim_id: datacenter
277 :return:
278 """
279 # get wim from wim_account
280 wim_accounts = self.db.get_rows(FROM='wim_accounts', WHERE={"uuid": sdn_id})
281 if not wim_accounts:
282 raise SdnException("Not found sdn id={}".format(sdn_id), http_code=HTTPStatus.NOT_FOUND.value)
283 wim_id = wim_accounts[0]["wim_id"]
284 db_wim_port_mappings = []
285 for map in maps:
286 new_map = {
287 'wim_id': wim_id,
288 'switch_dpid': switch_dpid,
289 "switch_port": map.get("switch_port"),
290 'datacenter_id': vim_id,
291 "device_id": map.get("compute_node"),
292 "service_endpoint_id": switch_dpid + "-" + str(uuid4())
293 }
294 if map.get("pci"):
295 new_map["device_interface_id"] = map["pci"].lower()
296 config = {}
297 if map.get("switch_mac"):
298 config["switch_mac"] = map["switch_mac"]
299 if config:
300 new_map["service_mapping_info"] = yaml.safe_dump(config, default_flow_style=True, width=256)
301 db_wim_port_mappings.append(new_map)
302
303 db_tables = [
304 {"wim_port_mappings": db_wim_port_mappings},
305 ]
306 self.db.new_rows(db_tables, [])
307 return db_wim_port_mappings
308
309 def clear_of_port_mapping(self, db_filter=None):
310 """
311 Clear port mapping filtering using db_filter dict
312 :param db_filter: Parameter to filter during remove process
313 :return:
314 """
315 return self.db.delete_row(FROM='wim_port_mappings', WHERE=db_filter)
316
317 def get_of_port_mappings(self, db_filter=None):
318 """
319 Retrive port mapping from DB
320 :param db_filter:
321 :return:
322 """
323 maps = self.db.get_rows(WHERE=db_filter, FROM='wim_port_mappings')
324 for map in maps:
325 if map.get("service_mapping_info"):
326 map["service_mapping_info"] = yaml.load(map["service_mapping_info"], Loader=yaml.Loader)
327 else:
328 map["service_mapping_info"] = {}
329 return maps