blob: 17351b0d2cab66331f00e9844943425b82721140 [file] [log] [blame]
tiernoed3e4d42019-10-21 15:31:27 +00001##
2# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U.
3# All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16#
17##
18import logging
19from http import HTTPStatus
tierno72774862020-05-04 11:44:15 +000020from osm_ro_plugin.sdnconn import SdnConnectorBase, SdnConnectorError
tiernoed3e4d42019-10-21 15:31:27 +000021from uuid import uuid4
22
23"""
24Implement an Abstract class 'OpenflowConn' and an engine 'SdnConnectorOpenFlow' used for base class for SDN plugings
25that implements a pro-active opeflow rules.
26"""
27
28__author__ = "Alfonso Tierno"
29__date__ = "2019-11-11"
30
31
32class OpenflowConnException(Exception):
33 """Common and base class Exception for all vimconnector exceptions"""
sousaedu80135b92021-02-17 15:05:18 +010034
tiernoed3e4d42019-10-21 15:31:27 +000035 def __init__(self, message, http_code=HTTPStatus.BAD_REQUEST.value):
36 Exception.__init__(self, message)
37 self.http_code = http_code
38
39
40class OpenflowConnConnectionException(OpenflowConnException):
41 """Connectivity error with the VIM"""
sousaedu80135b92021-02-17 15:05:18 +010042
tiernoed3e4d42019-10-21 15:31:27 +000043 def __init__(self, message, http_code=HTTPStatus.SERVICE_UNAVAILABLE.value):
44 OpenflowConnException.__init__(self, message, http_code)
45
46
47class OpenflowConnUnexpectedResponse(OpenflowConnException):
48 """Get an wrong response from VIM"""
sousaedu80135b92021-02-17 15:05:18 +010049
tiernoed3e4d42019-10-21 15:31:27 +000050 def __init__(self, message, http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value):
51 OpenflowConnException.__init__(self, message, http_code)
52
53
54class OpenflowConnAuthException(OpenflowConnException):
55 """Invalid credentials or authorization to perform this action over the VIM"""
sousaedu80135b92021-02-17 15:05:18 +010056
tiernoed3e4d42019-10-21 15:31:27 +000057 def __init__(self, message, http_code=HTTPStatus.UNAUTHORIZED.value):
58 OpenflowConnException.__init__(self, message, http_code)
59
60
61class OpenflowConnNotFoundException(OpenflowConnException):
62 """The item is not found at VIM"""
sousaedu80135b92021-02-17 15:05:18 +010063
tiernoed3e4d42019-10-21 15:31:27 +000064 def __init__(self, message, http_code=HTTPStatus.NOT_FOUND.value):
65 OpenflowConnException.__init__(self, message, http_code)
66
67
68class OpenflowConnConflictException(OpenflowConnException):
69 """There is a conflict, e.g. more item found than one"""
sousaedu80135b92021-02-17 15:05:18 +010070
tiernoed3e4d42019-10-21 15:31:27 +000071 def __init__(self, message, http_code=HTTPStatus.CONFLICT.value):
72 OpenflowConnException.__init__(self, message, http_code)
73
74
75class OpenflowConnNotSupportedException(OpenflowConnException):
76 """The request is not supported by connector"""
sousaedu80135b92021-02-17 15:05:18 +010077
tiernoed3e4d42019-10-21 15:31:27 +000078 def __init__(self, message, http_code=HTTPStatus.SERVICE_UNAVAILABLE.value):
79 OpenflowConnException.__init__(self, message, http_code)
80
81
82class OpenflowConnNotImplemented(OpenflowConnException):
83 """The method is not implemented by the connected"""
sousaedu80135b92021-02-17 15:05:18 +010084
tiernoed3e4d42019-10-21 15:31:27 +000085 def __init__(self, message, http_code=HTTPStatus.NOT_IMPLEMENTED.value):
86 OpenflowConnException.__init__(self, message, http_code)
87
88
89class OpenflowConn:
90 """
91 Openflow controller connector abstract implementeation.
92 """
sousaedu80135b92021-02-17 15:05:18 +010093
tiernoed3e4d42019-10-21 15:31:27 +000094 def __init__(self, params):
95 self.name = "openflow_conector"
96 self.pp2ofi = {} # From Physical Port to OpenFlow Index
97 self.ofi2pp = {} # From OpenFlow Index to Physical Port
sousaedu80135b92021-02-17 15:05:18 +010098 self.logger = logging.getLogger("ro.sdn.openflow_conn")
tiernoed3e4d42019-10-21 15:31:27 +000099
100 def get_of_switches(self):
sousaedu80135b92021-02-17 15:05:18 +0100101 """
tiernoed3e4d42019-10-21 15:31:27 +0000102 Obtain a a list of switches or DPID detected by this controller
103 :return: list length, and a list where each element a tuple pair (DPID, IP address), text_error: if fails
104 """
105 raise OpenflowConnNotImplemented("Should have implemented this")
106
107 def obtain_port_correspondence(self):
108 """
109 Obtain the correspondence between physical and openflow port names
110 :return: dictionary: with physical name as key, openflow name as value, error_text: if fails
111 """
112 raise OpenflowConnNotImplemented("Should have implemented this")
113
114 def get_of_rules(self, translate_of_ports=True):
115 """
116 Obtain the rules inserted at openflow controller
117 :param translate_of_ports: if True it translates ports from openflow index to physical switch name
118 :return: list where each item is a dictionary with the following content:
119 priority: rule priority
120 priority: rule priority
121 name: rule name (present also as the master dict key)
122 ingress_port: match input port of the rule
123 dst_mac: match destination mac address of the rule, can be missing or None if not apply
124 vlan_id: match vlan tag of the rule, can be missing or None if not apply
125 actions: list of actions, composed by a pair tuples:
126 (vlan, None/int): for stripping/setting a vlan tag
127 (out, port): send to this port
128 switch: DPID, all
129 text_error if fails
130 """
131 raise OpenflowConnNotImplemented("Should have implemented this")
132
133 def del_flow(self, flow_name):
134 """
135 Delete all existing rules
136 :param flow_name: flow_name, this is the rule name
137 :return: None if ok, text_error if fails
138 """
139 raise OpenflowConnNotImplemented("Should have implemented this")
140
141 def new_flow(self, data):
142 """
143 Insert a new static rule
144 :param data: dictionary with the following content:
145 priority: rule priority
146 name: rule name
147 ingress_port: match input port of the rule
148 dst_mac: match destination mac address of the rule, missing or None if not apply
149 vlan_id: match vlan tag of the rule, missing or None if not apply
150 actions: list of actions, composed by a pair tuples with these posibilities:
151 ('vlan', None/int): for stripping/setting a vlan tag
152 ('out', port): send to this port
153 :return: None if ok, text_error if fails
154 """
155 raise OpenflowConnNotImplemented("Should have implemented this")
156
157 def clear_all_flows(self):
sousaedu80135b92021-02-17 15:05:18 +0100158 """
tiernoed3e4d42019-10-21 15:31:27 +0000159 Delete all existing rules
160 :return: None if ok, text_error if fails
161 """
162 raise OpenflowConnNotImplemented("Should have implemented this")
163
164
165class SdnConnectorOpenFlow(SdnConnectorBase):
166 """
167 This class is the base engine of SDN plugins base on openflow rules
168 """
sousaedu80135b92021-02-17 15:05:18 +0100169
170 flow_fields = (
171 "priority",
172 "vlan",
173 "ingress_port",
174 "actions",
175 "dst_mac",
176 "src_mac",
177 "net_id",
178 )
tiernoed3e4d42019-10-21 15:31:27 +0000179
180 def __init__(self, wim, wim_account, config=None, logger=None, of_connector=None):
sousaedu80135b92021-02-17 15:05:18 +0100181 self.logger = logger or logging.getLogger("ro.sdn.openflow_conn")
tiernoed3e4d42019-10-21 15:31:27 +0000182 self.of_connector = of_connector
tierno70eeb182020-10-19 16:38:00 +0000183 config = config or {}
sousaedu80135b92021-02-17 15:05:18 +0100184 self.of_controller_nets_with_same_vlan = config.get(
185 "of_controller_nets_with_same_vlan", False
186 )
tiernoed3e4d42019-10-21 15:31:27 +0000187
188 def check_credentials(self):
189 try:
190 self.openflow_conn.obtain_port_correspondence()
191 except OpenflowConnException as e:
192 raise SdnConnectorError(e, http_code=e.http_code)
193
194 def get_connectivity_service_status(self, service_uuid, conn_info=None):
195 conn_info = conn_info or {}
196 return {
197 "sdn_status": conn_info.get("status", "ERROR"),
198 "error_msg": conn_info.get("error_msg", "Variable conn_info not provided"),
199 }
200 # TODO check rules connectirng to of_connector
201
202 def create_connectivity_service(self, service_type, connection_points, **kwargs):
203 net_id = str(uuid4())
204 ports = []
sousaedu80135b92021-02-17 15:05:18 +0100205
tiernoed3e4d42019-10-21 15:31:27 +0000206 for cp in connection_points:
207 port = {
208 "uuid": cp["service_endpoint_id"],
209 "vlan": cp.get("service_endpoint_encapsulation_info", {}).get("vlan"),
210 "mac": cp.get("service_endpoint_encapsulation_info", {}).get("mac"),
sousaedu80135b92021-02-17 15:05:18 +0100211 "switch_port": cp.get("service_endpoint_encapsulation_info", {}).get(
212 "switch_port"
213 ),
tiernoed3e4d42019-10-21 15:31:27 +0000214 }
215 ports.append(port)
sousaedu80135b92021-02-17 15:05:18 +0100216
tiernoed3e4d42019-10-21 15:31:27 +0000217 try:
sousaedu80135b92021-02-17 15:05:18 +0100218 created_items = self._set_openflow_rules(
219 service_type, net_id, ports, created_items=None
220 )
221
tiernoed3e4d42019-10-21 15:31:27 +0000222 return net_id, created_items
223 except (SdnConnectorError, OpenflowConnException) as e:
224 raise SdnConnectorError(e, http_code=e.http_code)
225
226 def delete_connectivity_service(self, service_uuid, conn_info=None):
227 try:
228 service_type = "ELAN"
229 ports = []
sousaedu80135b92021-02-17 15:05:18 +0100230 self._set_openflow_rules(
231 service_type, service_uuid, ports, created_items=conn_info
232 )
233
tiernoed3e4d42019-10-21 15:31:27 +0000234 return None
235 except (SdnConnectorError, OpenflowConnException) as e:
236 raise SdnConnectorError(e, http_code=e.http_code)
237
sousaedu80135b92021-02-17 15:05:18 +0100238 def edit_connectivity_service(
239 self, service_uuid, conn_info=None, connection_points=None, **kwargs
240 ):
tiernoed3e4d42019-10-21 15:31:27 +0000241 ports = []
242 for cp in connection_points:
243 port = {
244 "uuid": cp["service_endpoint_id"],
245 "vlan": cp.get("service_endpoint_encapsulation_info", {}).get("vlan"),
246 "mac": cp.get("service_endpoint_encapsulation_info", {}).get("mac"),
sousaedu80135b92021-02-17 15:05:18 +0100247 "switch_port": cp.get("service_endpoint_encapsulation_info", {}).get(
248 "switch_port"
249 ),
tiernoed3e4d42019-10-21 15:31:27 +0000250 }
251 ports.append(port)
sousaedu80135b92021-02-17 15:05:18 +0100252
tiernoed3e4d42019-10-21 15:31:27 +0000253 service_type = "ELAN" # TODO. Store at conn_info for later use
sousaedu80135b92021-02-17 15:05:18 +0100254
tiernoed3e4d42019-10-21 15:31:27 +0000255 try:
sousaedu80135b92021-02-17 15:05:18 +0100256 created_items = self._set_openflow_rules(
257 service_type, service_uuid, ports, created_items=conn_info
258 )
259
tiernoed3e4d42019-10-21 15:31:27 +0000260 return created_items
261 except (SdnConnectorError, OpenflowConnException) as e:
262 raise SdnConnectorError(e, http_code=e.http_code)
263
264 def clear_all_connectivity_services(self):
265 """Delete all WAN Links corresponding to a WIM"""
266 pass
267
268 def get_all_active_connectivity_services(self):
269 """Provide information about all active connections provisioned by a
270 WIM
271 """
272 pass
273
274 def _set_openflow_rules(self, net_type, net_id, ports, created_items=None):
275 ifaces_nb = len(ports)
sousaedu80135b92021-02-17 15:05:18 +0100276
tiernoed3e4d42019-10-21 15:31:27 +0000277 if not created_items:
sousaedu80135b92021-02-17 15:05:18 +0100278 created_items = {
279 "status": None,
280 "error_msg": None,
281 "installed_rules_ids": [],
282 }
tiernoed3e4d42019-10-21 15:31:27 +0000283 rules_to_delete = created_items.get("installed_rules_ids") or []
284 new_installed_rules_ids = []
285 error_list = []
286
287 try:
288 step = "Checking ports and network type compatibility"
289 if ifaces_nb < 2:
290 pass
sousaedu80135b92021-02-17 15:05:18 +0100291 elif net_type == "ELINE":
tiernoed3e4d42019-10-21 15:31:27 +0000292 if ifaces_nb > 2:
sousaedu80135b92021-02-17 15:05:18 +0100293 raise SdnConnectorError(
294 "'ELINE' type network cannot connect {} interfaces, only 2".format(
295 ifaces_nb
296 )
297 )
298 elif net_type == "ELAN":
tiernoed3e4d42019-10-21 15:31:27 +0000299 if ifaces_nb > 2 and self.of_controller_nets_with_same_vlan:
300 # check all ports are VLAN (tagged) or none
301 vlan_tags = []
sousaedu80135b92021-02-17 15:05:18 +0100302
tiernoed3e4d42019-10-21 15:31:27 +0000303 for port in ports:
304 if port["vlan"] not in vlan_tags:
305 vlan_tags.append(port["vlan"])
sousaedu80135b92021-02-17 15:05:18 +0100306
tiernoed3e4d42019-10-21 15:31:27 +0000307 if len(vlan_tags) > 1:
sousaedu80135b92021-02-17 15:05:18 +0100308 raise SdnConnectorError(
309 "This pluging cannot connect ports with diferent VLAN tags when flag "
310 "'of_controller_nets_with_same_vlan' is active"
311 )
tiernoed3e4d42019-10-21 15:31:27 +0000312 else:
sousaedu80135b92021-02-17 15:05:18 +0100313 raise SdnConnectorError(
314 "Only ELINE or ELAN network types are supported for openflow"
315 )
tiernoed3e4d42019-10-21 15:31:27 +0000316
317 # Get the existing flows at openflow controller
318 step = "Getting installed openflow rules"
319 existing_flows = self.of_connector.get_of_rules()
320 existing_flows_ids = [flow["name"] for flow in existing_flows]
321
322 # calculate new flows to be inserted
323 step = "Compute needed openflow rules"
324 new_flows = self._compute_net_flows(net_id, ports)
325
326 name_index = 0
327 for flow in new_flows:
328 # 1 check if an equal flow is already present
329 index = self._check_flow_already_present(flow, existing_flows)
sousaedu80135b92021-02-17 15:05:18 +0100330
tiernoed3e4d42019-10-21 15:31:27 +0000331 if index >= 0:
332 flow_id = existing_flows[index]["name"]
333 self.logger.debug("Skipping already present flow %s", str(flow))
334 else:
335 # 2 look for a non used name
336 flow_name = flow["net_id"] + "." + str(name_index)
sousaedu80135b92021-02-17 15:05:18 +0100337
tiernoed3e4d42019-10-21 15:31:27 +0000338 while flow_name in existing_flows_ids:
339 name_index += 1
340 flow_name = flow["net_id"] + "." + str(name_index)
sousaedu80135b92021-02-17 15:05:18 +0100341
342 flow["name"] = flow_name
343
tiernoed3e4d42019-10-21 15:31:27 +0000344 # 3 insert at openflow
345 try:
346 self.of_connector.new_flow(flow)
347 flow_id = flow["name"]
348 existing_flows_ids.append(flow_id)
349 except OpenflowConnException as e:
350 flow_id = None
sousaedu80135b92021-02-17 15:05:18 +0100351 error_list.append(
352 "Cannot create rule for ingress_port={}, dst_mac={}: {}".format(
353 flow["ingress_port"], flow["dst_mac"], e
354 )
355 )
tiernoed3e4d42019-10-21 15:31:27 +0000356
357 # 4 insert at database
358 if flow_id:
359 new_installed_rules_ids.append(flow_id)
360 if flow_id in rules_to_delete:
361 rules_to_delete.remove(flow_id)
362
363 # delete not needed old flows from openflow
364 for flow_id in rules_to_delete:
365 # Delete flow
366 try:
367 self.of_connector.del_flow(flow_id)
368 except OpenflowConnNotFoundException:
369 pass
370 except OpenflowConnException as e:
tierno4126d052019-12-11 15:30:44 +0000371 error_text = "Cannot remove rule '{}': {}".format(flow_id, e)
tiernoed3e4d42019-10-21 15:31:27 +0000372 error_list.append(error_text)
373 self.logger.error(error_text)
sousaedu80135b92021-02-17 15:05:18 +0100374
tiernoed3e4d42019-10-21 15:31:27 +0000375 created_items["installed_rules_ids"] = new_installed_rules_ids
sousaedu80135b92021-02-17 15:05:18 +0100376
tiernoed3e4d42019-10-21 15:31:27 +0000377 if error_list:
378 created_items["error_msg"] = ";".join(error_list)[:1000]
379 created_items["error_msg"] = "ERROR"
380 else:
381 created_items["error_msg"] = None
382 created_items["status"] = "ACTIVE"
sousaedu80135b92021-02-17 15:05:18 +0100383
tiernoed3e4d42019-10-21 15:31:27 +0000384 return created_items
385 except (SdnConnectorError, OpenflowConnException) as e:
386 raise SdnConnectorError("Error while {}: {}".format(step, e)) from e
387 except Exception as e:
tierno4126d052019-12-11 15:30:44 +0000388 error_text = "Error while {}: {}".format(step, e)
tiernoed3e4d42019-10-21 15:31:27 +0000389 self.logger.critical(error_text, exc_info=True)
tierno4126d052019-12-11 15:30:44 +0000390 raise SdnConnectorError(error_text)
tiernoed3e4d42019-10-21 15:31:27 +0000391
392 def _compute_net_flows(self, net_id, ports):
393 new_flows = []
394 new_broadcast_flows = {}
395 nb_ports = len(ports)
396
397 # Check switch_port information is right
398 for port in ports:
399 nb_ports += 1
sousaedu80135b92021-02-17 15:05:18 +0100400
401 if str(port["switch_port"]) not in self.of_connector.pp2ofi:
402 raise SdnConnectorError(
403 "switch port name '{}' is not valid for the openflow controller".format(
404 port["switch_port"]
405 )
406 )
407
tiernoed3e4d42019-10-21 15:31:27 +0000408 priority = 1000 # 1100
409
410 for src_port in ports:
411 # if src_port.get("groups")
sousaedu80135b92021-02-17 15:05:18 +0100412 vlan_in = src_port["vlan"]
tiernoed3e4d42019-10-21 15:31:27 +0000413
414 # BROADCAST:
sousaedu80135b92021-02-17 15:05:18 +0100415 broadcast_key = src_port["uuid"] + "." + str(vlan_in)
tiernoed3e4d42019-10-21 15:31:27 +0000416 if broadcast_key in new_broadcast_flows:
417 flow_broadcast = new_broadcast_flows[broadcast_key]
418 else:
sousaedu80135b92021-02-17 15:05:18 +0100419 flow_broadcast = {
420 "priority": priority,
421 "net_id": net_id,
422 "dst_mac": "ff:ff:ff:ff:ff:ff",
423 "ingress_port": str(src_port["switch_port"]),
424 "vlan_id": vlan_in,
425 "actions": [],
426 }
tiernoed3e4d42019-10-21 15:31:27 +0000427 new_broadcast_flows[broadcast_key] = flow_broadcast
sousaedu80135b92021-02-17 15:05:18 +0100428
tiernoed3e4d42019-10-21 15:31:27 +0000429 if vlan_in is not None:
sousaedu80135b92021-02-17 15:05:18 +0100430 flow_broadcast["vlan_id"] = str(vlan_in)
tiernoed3e4d42019-10-21 15:31:27 +0000431
432 for dst_port in ports:
sousaedu80135b92021-02-17 15:05:18 +0100433 vlan_out = dst_port["vlan"]
434
435 if (
436 src_port["switch_port"] == dst_port["switch_port"]
437 and vlan_in == vlan_out
438 ):
tiernoed3e4d42019-10-21 15:31:27 +0000439 continue
sousaedu80135b92021-02-17 15:05:18 +0100440
tiernoed3e4d42019-10-21 15:31:27 +0000441 flow = {
442 "priority": priority,
sousaedu80135b92021-02-17 15:05:18 +0100443 "net_id": net_id,
444 "ingress_port": str(src_port["switch_port"]),
445 "vlan_id": vlan_in,
446 "actions": [],
tiernoed3e4d42019-10-21 15:31:27 +0000447 }
sousaedu80135b92021-02-17 15:05:18 +0100448
tiernoed3e4d42019-10-21 15:31:27 +0000449 # allow that one port have no mac
sousaedu80135b92021-02-17 15:05:18 +0100450 # point to point or nets with 2 elements
451 if dst_port["mac"] is None or nb_ports == 2:
452 flow["priority"] = priority - 5 # less priority
tiernoed3e4d42019-10-21 15:31:27 +0000453 else:
sousaedu80135b92021-02-17 15:05:18 +0100454 flow["dst_mac"] = str(dst_port["mac"])
tiernoed3e4d42019-10-21 15:31:27 +0000455
456 if vlan_out is None:
457 if vlan_in:
sousaedu80135b92021-02-17 15:05:18 +0100458 flow["actions"].append(("vlan", None))
tiernoed3e4d42019-10-21 15:31:27 +0000459 else:
sousaedu80135b92021-02-17 15:05:18 +0100460 flow["actions"].append(("vlan", vlan_out))
461
462 flow["actions"].append(("out", str(dst_port["switch_port"])))
tiernoed3e4d42019-10-21 15:31:27 +0000463
464 if self._check_flow_already_present(flow, new_flows) >= 0:
465 self.logger.debug("Skipping repeated flow '%s'", str(flow))
466 continue
467
468 new_flows.append(flow)
469
470 # BROADCAST:
sousaedu80135b92021-02-17 15:05:18 +0100471 # point to multipoint or nets with more than 2 elements
472 if nb_ports <= 2:
tiernoed3e4d42019-10-21 15:31:27 +0000473 continue
sousaedu80135b92021-02-17 15:05:18 +0100474
475 out = (vlan_out, str(dst_port["switch_port"]))
476
477 if out not in flow_broadcast["actions"]:
478 flow_broadcast["actions"].append(out)
tiernoed3e4d42019-10-21 15:31:27 +0000479
480 # BROADCAST
481 for flow_broadcast in new_broadcast_flows.values():
sousaedu80135b92021-02-17 15:05:18 +0100482 if len(flow_broadcast["actions"]) == 0:
tiernoed3e4d42019-10-21 15:31:27 +0000483 continue # nothing to do, skip
sousaedu80135b92021-02-17 15:05:18 +0100484
485 flow_broadcast["actions"].sort()
486
487 if "vlan_id" in flow_broadcast:
488 # indicates that a packet contains a vlan, and the vlan
489 previous_vlan = 0
tiernoed3e4d42019-10-21 15:31:27 +0000490 else:
491 previous_vlan = None
sousaedu80135b92021-02-17 15:05:18 +0100492
tiernoed3e4d42019-10-21 15:31:27 +0000493 final_actions = []
494 action_number = 0
sousaedu80135b92021-02-17 15:05:18 +0100495
496 for action in flow_broadcast["actions"]:
tiernoed3e4d42019-10-21 15:31:27 +0000497 if action[0] != previous_vlan:
sousaedu80135b92021-02-17 15:05:18 +0100498 final_actions.append(("vlan", action[0]))
tiernoed3e4d42019-10-21 15:31:27 +0000499 previous_vlan = action[0]
sousaedu80135b92021-02-17 15:05:18 +0100500
tiernoed3e4d42019-10-21 15:31:27 +0000501 if self.of_controller_nets_with_same_vlan and action_number:
sousaedu80135b92021-02-17 15:05:18 +0100502 raise SdnConnectorError(
503 "Cannot interconnect different vlan tags in a network when flag "
504 "'of_controller_nets_with_same_vlan' is True."
505 )
506
tiernoed3e4d42019-10-21 15:31:27 +0000507 action_number += 1
sousaedu80135b92021-02-17 15:05:18 +0100508 final_actions.append(("out", action[1]))
509 flow_broadcast["actions"] = final_actions
tiernoed3e4d42019-10-21 15:31:27 +0000510
511 if self._check_flow_already_present(flow_broadcast, new_flows) >= 0:
512 self.logger.debug("Skipping repeated flow '%s'", str(flow_broadcast))
513 continue
514
515 new_flows.append(flow_broadcast)
516
517 # UNIFY openflow rules with the same input port and vlan and the same output actions
518 # These flows differ at the dst_mac; and they are unified by not filtering by dst_mac
519 # this can happen if there is only two ports. It is converted to a point to point connection
sousaedu80135b92021-02-17 15:05:18 +0100520 # use as key vlan_id+ingress_port and as value the list of flows matching these values
521 flow_dict = {}
tiernoed3e4d42019-10-21 15:31:27 +0000522 for flow in new_flows:
523 key = str(flow.get("vlan_id")) + ":" + flow["ingress_port"]
sousaedu80135b92021-02-17 15:05:18 +0100524
tiernoed3e4d42019-10-21 15:31:27 +0000525 if key in flow_dict:
526 flow_dict[key].append(flow)
527 else:
528 flow_dict[key] = [flow]
sousaedu80135b92021-02-17 15:05:18 +0100529
tiernoed3e4d42019-10-21 15:31:27 +0000530 new_flows2 = []
sousaedu80135b92021-02-17 15:05:18 +0100531
tiernoed3e4d42019-10-21 15:31:27 +0000532 for flow_list in flow_dict.values():
533 convert2ptp = False
sousaedu80135b92021-02-17 15:05:18 +0100534
tiernoed3e4d42019-10-21 15:31:27 +0000535 if len(flow_list) >= 2:
536 convert2ptp = True
sousaedu80135b92021-02-17 15:05:18 +0100537
tiernoed3e4d42019-10-21 15:31:27 +0000538 for f in flow_list:
sousaedu80135b92021-02-17 15:05:18 +0100539 if f["actions"] != flow_list[0]["actions"]:
tiernoed3e4d42019-10-21 15:31:27 +0000540 convert2ptp = False
541 break
sousaedu80135b92021-02-17 15:05:18 +0100542
tiernoed3e4d42019-10-21 15:31:27 +0000543 if convert2ptp: # add only one unified rule without dst_mac
sousaedu80135b92021-02-17 15:05:18 +0100544 self.logger.debug(
545 "Convert flow rules to NON mac dst_address " + str(flow_list)
546 )
547 flow_list[0].pop("dst_mac")
tiernoed3e4d42019-10-21 15:31:27 +0000548 flow_list[0]["priority"] -= 5
549 new_flows2.append(flow_list[0])
550 else: # add all the rules
551 new_flows2 += flow_list
sousaedu80135b92021-02-17 15:05:18 +0100552
tiernoed3e4d42019-10-21 15:31:27 +0000553 return new_flows2
554
555 def _check_flow_already_present(self, new_flow, flow_list):
sousaedu80135b92021-02-17 15:05:18 +0100556 """check if the same flow is already present in the flow list
tiernoed3e4d42019-10-21 15:31:27 +0000557 The flow is repeated if all the fields, apart from name, are equal
sousaedu80135b92021-02-17 15:05:18 +0100558 Return the index of matching flow, -1 if not match
559 """
tiernoed3e4d42019-10-21 15:31:27 +0000560 for index, flow in enumerate(flow_list):
561 for f in self.flow_fields:
562 if flow.get(f) != new_flow.get(f):
563 break
564 else:
565 return index
sousaedu80135b92021-02-17 15:05:18 +0100566
tiernoed3e4d42019-10-21 15:31:27 +0000567 return -1