Fix 1475 - Incorrect description in instantiating error
[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") if x in ofc_data}
211 db_wim_account_update = {x: ofc_data[x] for x in ("name", "user", "password") if x in ofc_data}
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"], Loader=yaml.Loader) or {}
226 db_wim_account_update["config"].update(db_wim_account_config)
227 db_wim_account_update["config"] = yaml.safe_dump(db_wim_account_update["config"], default_flow_style=True,
228 width=256)
229
230 if db_wim_account_update:
231 self.db.update_rows('wim_accounts', db_wim_account_update, WHERE={'uuid': of_id})
232 if db_wim_update:
233 self.db.update_rows('wims', db_wim_update, WHERE={'uuid': wim_account["wim_id"]})
234
235 def _get_of_controller(self, of_id):
236 wim_accounts = self.db.get_rows(FROM='wim_accounts', WHERE={"uuid": of_id, "sdn": "true"})
237
238 if not wim_accounts:
239 raise SdnException("Cannot find sdn controller with id='{}'".format(of_id),
240 http_code=HTTPStatus.NOT_FOUND.value)
241 elif len(wim_accounts) > 1:
242 raise SdnException("Found more than one sdn controller with id='{}'".format(of_id),
243 http_code=HTTPStatus.CONFLICT.value)
244 return wim_accounts[0]
245
246 def delete_of_controller(self, of_id):
247 """
248 Delete an openflow controller from DB.
249 :param of_id: openflow controller dpid
250 :return:
251 """
252 wim_account = self._get_of_controller(of_id)
253 self.db.delete_row(FROM='wim_accounts', WHERE={"uuid": of_id})
254 self.db.delete_row(FROM='wims', WHERE={"uuid": wim_account["wim_id"]})
255 return of_id
256
257 @staticmethod
258 def _format_of_controller(wim_account, wim=None):
259 of_data = {x: wim_account[x] for x in ("uuid", "name", "user")}
260 if isinstance(wim_account["config"], str):
261 config = yaml.load(wim_account["config"], Loader=yaml.Loader)
262 of_data["dpid"] = config.get("switch_id") or config.get("dpid")
263 of_data["version"] = config.get("version")
264 if wim:
265 of_data["url"] = wim["wim_url"]
266 of_data["type"] = wim["type"]
267 return of_data
268
269 def show_of_controller(self, of_id):
270 """
271 Show an openflow controller by dpid from DB.
272 :param db_filter: List with where query parameters
273 :return:
274 """
275 wim_account = self._get_of_controller(of_id)
276 wims = self.db.get_rows(FROM='wims', WHERE={"uuid": wim_account["wim_id"]})
277 return self._format_of_controller(wim_account, wims[0])
278
279 def get_of_controllers(self, filter=None):
280 """
281 Show an openflow controllers from DB.
282 :return:
283 """
284 filter = filter or {}
285 filter["sdn"] = "true"
286 wim_accounts = self.db.get_rows(FROM='wim_accounts', WHERE=filter)
287 return [self._format_of_controller(w) for w in wim_accounts]
288
289 def set_of_port_mapping(self, maps, sdn_id, switch_dpid, vim_id):
290 """
291 Create new port mapping entry
292 :param of_maps: List with port mapping information
293 # maps =[{"ofc_id": <ofc_id>,"region": datacenter region,"compute_node": compute uuid,"pci": pci adress,
294 "switch_dpid": swith dpid,"switch_port": port name,"switch_mac": mac}]
295 :param sdn_id: ofc id
296 :param switch_dpid: switch dpid
297 :param vim_id: datacenter
298 :return:
299 """
300 # get wim from wim_account
301 wim_account = self._get_of_controller(sdn_id)
302 wim_id = wim_account["wim_id"]
303 db_wim_port_mappings = []
304 for map in maps:
305 _switch_dpid = map.get("switch_id") or map.get("switch_dpid") or switch_dpid
306 new_map = {
307 'wim_id': wim_id,
308 'switch_dpid': _switch_dpid,
309 "switch_port": map.get("switch_port"),
310 'datacenter_id': vim_id,
311 "device_id": map.get("compute_node"),
312 "service_endpoint_id": _switch_dpid + "-" + str(uuid4())
313 }
314 if map.get("pci"):
315 new_map["device_interface_id"] = map["pci"].lower()
316 config = {}
317 if map.get("switch_mac"):
318 config["switch_mac"] = map["switch_mac"]
319 if config:
320 new_map["service_mapping_info"] = yaml.safe_dump(config, default_flow_style=True, width=256)
321 db_wim_port_mappings.append(new_map)
322
323 db_tables = [
324 {"wim_port_mappings": db_wim_port_mappings},
325 ]
326 self.db.new_rows(db_tables, [])
327 return db_wim_port_mappings
328
329 def clear_of_port_mapping(self, db_filter=None):
330 """
331 Clear port mapping filtering using db_filter dict
332 :param db_filter: Parameter to filter during remove process
333 :return:
334 """
335 return self.db.delete_row(FROM='wim_port_mappings', WHERE=db_filter)
336
337 def get_of_port_mappings(self, db_filter=None):
338 """
339 Retrive port mapping from DB
340 :param db_filter:
341 :return:
342 """
343 maps = self.db.get_rows(WHERE=db_filter, FROM='wim_port_mappings')
344 for map in maps:
345 if map.get("service_mapping_info"):
346 map["service_mapping_info"] = yaml.load(map["service_mapping_info"], Loader=yaml.Loader)
347 else:
348 map["service_mapping_info"] = {}
349 return maps
350
351 def get_ports(self, instance_wim_net_id):
352 # get wim_id
353 instance_wim_net = self.db.get_rows(FROM='instance_wim_nets', WHERE={"uuid": instance_wim_net_id})
354 wim_id = instance_wim_net[0]["wim_id"]
355 switch_ports = []
356 ports = self.db.get_rows(FROM='instance_interfaces', WHERE={"instance_wim_net_id": instance_wim_net_id})
357 maps = self.get_of_port_mappings(db_filter={"wim_id": wim_id})
358 for port in ports:
359 map_ = next((x for x in maps if x.get("device_id") == port["compute_node"] and
360 x.get("device_interface_id") == port["pci"]), None)
361 if map_:
362 switch_port = {'switch_dpid': map_.get('switch_dpid') or map_.get('switch_id'),
363 'switch_port': map_.get('switch_port')}
364 if switch_port not in switch_ports:
365 switch_ports.append(switch_port)
366 return switch_ports
367