636be552a5a2e959248dd389e114fb5983aba597
[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
42 def __init__(self, db, plugins):
43 self.db = db
44 self.plugins = plugins
45
46 def start_service(self):
47 pass # TODO py3 needed to load wims and plugins
48
49 def stop_service(self):
50 pass # nothing needed
51
52 def show_network(self, uuid):
53 pass
54
55 def delete_network(self, uuid):
56 pass
57
58 def new_network(self, network):
59 pass
60
61 def get_openflow_rules(self, network_id=None):
62 """
63 Get openflow id from DB
64 :param network_id: Network id, if none all networks will be retrieved
65 :return: Return a list with Openflow rules per net
66 """
67 # ignore input data
68 if not network_id:
69
70 where_ = {}
71 else:
72 where_ = {"net_id": network_id}
73 result, content = self.db.get_table(
74 SELECT=("name", "net_id", "ofc_id", "priority", "vlan_id", "ingress_port", "src_mac", "dst_mac", "actions"),
75 WHERE=where_, FROM='of_flows')
76
77 if result < 0:
78 raise SdnException(str(content), -result)
79 return content
80
81 def edit_openflow_rules(self, network_id=None):
82 """
83 To make actions over the net. The action is to reinstall the openflow rules
84 network_id can be 'all'
85 :param network_id: Network id, if none all networks will be retrieved
86 :return : Number of nets updated
87 """
88
89 # ignore input data
90 if not network_id:
91 where_ = {}
92 else:
93 where_ = {"uuid": network_id}
94 result, content = self.db.get_table(SELECT=("uuid", "type"), WHERE=where_, FROM='nets')
95
96 if result < 0:
97 raise SdnException(str(content), -result)
98
99 for net in content:
100 if net["type"] != "ptp" and net["type"] != "data":
101 result -= 1
102 continue
103
104 try:
105 self.net_update_ofc_thread(net['uuid'])
106 except SdnException as e:
107 raise SdnException("Error updating network'{}' {}".format(net['uuid'], e),
108 HTTPStatus.INTERNAL_SERVER_ERROR.value)
109 except Exception as e:
110 raise SdnException("Error updating network '{}' {}".format(net['uuid'], e),
111 HTTPStatus.INTERNAL_SERVER_ERROR.value)
112
113 return result
114
115 def delete_openflow_rules(self, ofc_id=None):
116 """
117 To make actions over the net. The action is to delete ALL openflow rules
118 :return: return operation result
119 """
120
121 if not ofc_id:
122 if 'Default' in self.config['ofcs_thread']:
123 r, c = self.config['ofcs_thread']['Default'].insert_task("clear-all")
124 else:
125 raise SdnException("Default Openflow controller not not running", HTTPStatus.NOT_FOUND.value)
126
127 elif ofc_id in self.config['ofcs_thread']:
128 r, c = self.config['ofcs_thread'][ofc_id].insert_task("clear-all")
129
130 # ignore input data
131 if r < 0:
132 raise SdnException(str(c), -r)
133 else:
134 raise SdnException("Openflow controller not found with ofc_id={}".format(ofc_id),
135 HTTPStatus.NOT_FOUND.value)
136 return r
137
138 def get_openflow_ports(self, ofc_id=None):
139 """
140 Obtain switch ports names of openflow controller
141 :return: Return flow ports in DB
142 """
143 if not ofc_id:
144 if 'Default' in self.config['ofcs_thread']:
145 conn = self.config['ofcs_thread']['Default'].OF_connector
146 else:
147 raise SdnException("Default Openflow controller not not running", HTTPStatus.NOT_FOUND.value)
148
149 elif ofc_id in self.config['ofcs_thread']:
150 conn = self.config['ofcs_thread'][ofc_id].OF_connector
151 else:
152 raise SdnException("Openflow controller not found with ofc_id={}".format(ofc_id),
153 HTTPStatus.NOT_FOUND.value)
154 return conn.pp2ofi
155
156 def new_of_controller(self, ofc_data):
157 """
158 Create a new openflow controller into DB
159 :param ofc_data: Dict openflow controller data
160 :return: openflow controller uuid
161 """
162 db_wim = {
163 "uuid": str(uuid4()),
164 "name": ofc_data["name"],
165 "description": ofc_data.get("description"),
166 "type": ofc_data["type"],
167 "wim_url": ofc_data.get("url"),
168 }
169 if not db_wim["wim_url"]:
170 if not ofc_data.get("ip") or not ofc_data.get("port"):
171 raise SdnException("Provide either 'url' or both 'ip' and 'port'")
172 db_wim["wim_url"] = "{}:{}".format(ofc_data["ip"], ofc_data["port"])
173
174 db_wim_account = {
175 "uuid": str(uuid4()),
176 "name": ofc_data["name"],
177 "wim_id": db_wim["uuid"],
178 "sdn": "true",
179 "user": ofc_data.get("user"),
180 "password": ofc_data.get("password"),
181 }
182 db_wim_account_config = ofc_data.get("config", {})
183 if ofc_data.get("dpid"):
184 db_wim_account_config["dpid"] = ofc_data["dpid"]
185 if ofc_data.get("version"):
186 db_wim_account_config["version"] = ofc_data["version"]
187
188 db_wim_account["config"] = yaml.safe_dump(db_wim_account_config, default_flow_style=True, width=256)
189
190 db_tables = [
191 {"wims": db_wim},
192 {"wim_accounts": db_wim_account},
193 ]
194 uuid_list = [db_wim["uuid"], db_wim_account["uuid"]]
195 self.db.new_rows(db_tables, uuid_list)
196 return db_wim_account["uuid"]
197
198 def edit_of_controller(self, of_id, ofc_data):
199 """
200 Edit an openflow controller entry from DB
201 :return:
202 """
203 if not ofc_data:
204 raise SdnException("No data received during uptade OF contorller",
205 http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value)
206
207 # get database wim_accounts
208 wim_account = self._get_of_controller(of_id)
209
210 db_wim_update = {x: ofc_data[x] for x in ("name", "description", "type", "wim_url")}
211 db_wim_account_update = {x: ofc_data[x] for x in ("name", "user", "password")}
212 db_wim_account_config = ofc_data.get("config", {})
213
214 if ofc_data.get("ip") or ofc_data.get("port"):
215 if not ofc_data.get("ip") or not ofc_data.get("port"):
216 raise SdnException("Provide or both 'ip' and 'port'")
217 db_wim_update["wim_url"] = "{}:{}".format(ofc_data["ip"], ofc_data["port"])
218
219 if ofc_data.get("dpid"):
220 db_wim_account_config["dpid"] = ofc_data["dpid"]
221 if ofc_data.get("version"):
222 db_wim_account_config["version"] = ofc_data["version"]
223
224 if db_wim_account_config:
225 db_wim_account_update["config"] = yaml.load(wim_account["config"]) or {}
226 db_wim_account_update["config"].update(db_wim_account_config)
227
228 if db_wim_account_update:
229 self.db.update_rows('wim_accounts', db_wim_account_update, WHERE={'uuid': of_id})
230 if db_wim_update:
231 self.db.update_rows('wims', db_wim_account_update, WHERE={'uuid': wim_account["wim_id"]})
232
233 def _get_of_controller(self, of_id):
234 wim_accounts = self.db.get_rows(FROM='wim_accounts', WHERE={"uuid": of_id, "sdn": "true"})
235
236 if not wim_accounts:
237 raise SdnException("Cannot find sdn controller with id='{}'".format(of_id),
238 http_code=HTTPStatus.NOT_FOUND.value)
239 elif len(wim_accounts) > 1:
240 raise SdnException("Found more than one sdn controller with id='{}'".format(of_id),
241 http_code=HTTPStatus.CONFLICT.value)
242 return wim_accounts[0]
243
244 def delete_of_controller(self, of_id):
245 """
246 Delete an openflow controller from DB.
247 :param of_id: openflow controller dpid
248 :return:
249 """
250 wim_account = self._get_of_controller(of_id)
251 self.db.delete_row(FROM='wim_accounts', WHERE={"uuid": of_id})
252 self.db.delete_row(FROM='wims', WHERE={"uuid": wim_account["wim_id"]})
253 return of_id
254
255 @staticmethod
256 def _format_of_controller(wim_account, wim=None):
257 of_data = {x: wim_account[x] for x in ("uuid", "name", "user")}
258 if isinstance(wim_account["config"], str):
259 config = yaml.load(wim_account["config"], Loader=yaml.Loader)
260 of_data["dpid"] = config.get("switch_id") or config.get("dpid")
261 of_data["version"] = config.get("version")
262 if wim:
263 of_data["url"] = wim["wim_url"]
264 of_data["type"] = wim["type"]
265 return of_data
266
267 def show_of_controller(self, of_id):
268 """
269 Show an openflow controller by dpid from DB.
270 :param db_filter: List with where query parameters
271 :return:
272 """
273 wim_account = self._get_of_controller(of_id)
274 wims = self.db.get_rows(FROM='wims', WHERE={"uuid": wim_account["wim_id"]})
275 return self._format_of_controller(wim_account, wims[0])
276
277 def get_of_controllers(self, filter=None):
278 """
279 Show an openflow controllers from DB.
280 :return:
281 """
282 filter = filter or {}
283 filter["sdn"] = "true"
284 wim_accounts = self.db.get_rows(FROM='wim_accounts', WHERE=filter)
285 return [self._format_of_controller(w) for w in wim_accounts]
286
287 def set_of_port_mapping(self, maps, sdn_id, switch_dpid, vim_id):
288 """
289 Create new port mapping entry
290 :param of_maps: List with port mapping information
291 # maps =[{"ofc_id": <ofc_id>,"region": datacenter region,"compute_node": compute uuid,"pci": pci adress,
292 "switch_dpid": swith dpid,"switch_port": port name,"switch_mac": mac}]
293 :param sdn_id: ofc id
294 :param switch_dpid: switch dpid
295 :param vim_id: datacenter
296 :return:
297 """
298 # get wim from wim_account
299 wim_account = self._get_of_controller(sdn_id)
300 wim_id = wim_account["wim_id"]
301 db_wim_port_mappings = []
302 for map in maps:
303 _switch_dpid = map.get("switch_id") or map.get("switch_dpid") or switch_dpid
304 new_map = {
305 'wim_id': wim_id,
306 'switch_dpid': _switch_dpid,
307 "switch_port": map.get("switch_port"),
308 'datacenter_id': vim_id,
309 "device_id": map.get("compute_node"),
310 "service_endpoint_id": _switch_dpid + "-" + str(uuid4())
311 }
312 if map.get("pci"):
313 new_map["device_interface_id"] = map["pci"].lower()
314 config = {}
315 if map.get("switch_mac"):
316 config["switch_mac"] = map["switch_mac"]
317 if config:
318 new_map["service_mapping_info"] = yaml.safe_dump(config, default_flow_style=True, width=256)
319 db_wim_port_mappings.append(new_map)
320
321 db_tables = [
322 {"wim_port_mappings": db_wim_port_mappings},
323 ]
324 self.db.new_rows(db_tables, [])
325 return db_wim_port_mappings
326
327 def clear_of_port_mapping(self, db_filter=None):
328 """
329 Clear port mapping filtering using db_filter dict
330 :param db_filter: Parameter to filter during remove process
331 :return:
332 """
333 return self.db.delete_row(FROM='wim_port_mappings', WHERE=db_filter)
334
335 def get_of_port_mappings(self, db_filter=None):
336 """
337 Retrive port mapping from DB
338 :param db_filter:
339 :return:
340 """
341 maps = self.db.get_rows(WHERE=db_filter, FROM='wim_port_mappings')
342 for map in maps:
343 if map.get("service_mapping_info"):
344 map["service_mapping_info"] = yaml.load(map["service_mapping_info"], Loader=yaml.Loader)
345 else:
346 map["service_mapping_info"] = {}
347 return maps