blob: c41d7bc892529a260a24be7e28251dd12f9dc92d [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##
sousaedu049cbb12022-01-05 11:39:35 +000018
tiernoed3e4d42019-10-21 15:31:27 +000019from http import HTTPStatus
sousaedu049cbb12022-01-05 11:39:35 +000020import logging
tiernoed3e4d42019-10-21 15:31:27 +000021from uuid import uuid4
22
sousaedu049cbb12022-01-05 11:39:35 +000023from osm_ro_plugin.sdnconn import SdnConnectorBase, SdnConnectorError
24
25
tiernoed3e4d42019-10-21 15:31:27 +000026"""
27Implement an Abstract class 'OpenflowConn' and an engine 'SdnConnectorOpenFlow' used for base class for SDN plugings
28that implements a pro-active opeflow rules.
29"""
30
31__author__ = "Alfonso Tierno"
32__date__ = "2019-11-11"
33
34
35class OpenflowConnException(Exception):
36 """Common and base class Exception for all vimconnector exceptions"""
sousaedu80135b92021-02-17 15:05:18 +010037
tiernoed3e4d42019-10-21 15:31:27 +000038 def __init__(self, message, http_code=HTTPStatus.BAD_REQUEST.value):
39 Exception.__init__(self, message)
40 self.http_code = http_code
41
42
43class OpenflowConnConnectionException(OpenflowConnException):
44 """Connectivity error with the VIM"""
sousaedu80135b92021-02-17 15:05:18 +010045
tiernoed3e4d42019-10-21 15:31:27 +000046 def __init__(self, message, http_code=HTTPStatus.SERVICE_UNAVAILABLE.value):
47 OpenflowConnException.__init__(self, message, http_code)
48
49
50class OpenflowConnUnexpectedResponse(OpenflowConnException):
51 """Get an wrong response from VIM"""
sousaedu80135b92021-02-17 15:05:18 +010052
tiernoed3e4d42019-10-21 15:31:27 +000053 def __init__(self, message, http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value):
54 OpenflowConnException.__init__(self, message, http_code)
55
56
57class OpenflowConnAuthException(OpenflowConnException):
58 """Invalid credentials or authorization to perform this action over the VIM"""
sousaedu80135b92021-02-17 15:05:18 +010059
tiernoed3e4d42019-10-21 15:31:27 +000060 def __init__(self, message, http_code=HTTPStatus.UNAUTHORIZED.value):
61 OpenflowConnException.__init__(self, message, http_code)
62
63
64class OpenflowConnNotFoundException(OpenflowConnException):
65 """The item is not found at VIM"""
sousaedu80135b92021-02-17 15:05:18 +010066
tiernoed3e4d42019-10-21 15:31:27 +000067 def __init__(self, message, http_code=HTTPStatus.NOT_FOUND.value):
68 OpenflowConnException.__init__(self, message, http_code)
69
70
71class OpenflowConnConflictException(OpenflowConnException):
72 """There is a conflict, e.g. more item found than one"""
sousaedu80135b92021-02-17 15:05:18 +010073
tiernoed3e4d42019-10-21 15:31:27 +000074 def __init__(self, message, http_code=HTTPStatus.CONFLICT.value):
75 OpenflowConnException.__init__(self, message, http_code)
76
77
78class OpenflowConnNotSupportedException(OpenflowConnException):
79 """The request is not supported by connector"""
sousaedu80135b92021-02-17 15:05:18 +010080
tiernoed3e4d42019-10-21 15:31:27 +000081 def __init__(self, message, http_code=HTTPStatus.SERVICE_UNAVAILABLE.value):
82 OpenflowConnException.__init__(self, message, http_code)
83
84
85class OpenflowConnNotImplemented(OpenflowConnException):
86 """The method is not implemented by the connected"""
sousaedu80135b92021-02-17 15:05:18 +010087
tiernoed3e4d42019-10-21 15:31:27 +000088 def __init__(self, message, http_code=HTTPStatus.NOT_IMPLEMENTED.value):
89 OpenflowConnException.__init__(self, message, http_code)
90
91
92class OpenflowConn:
93 """
94 Openflow controller connector abstract implementeation.
95 """
sousaedu80135b92021-02-17 15:05:18 +010096
tiernoed3e4d42019-10-21 15:31:27 +000097 def __init__(self, params):
98 self.name = "openflow_conector"
99 self.pp2ofi = {} # From Physical Port to OpenFlow Index
100 self.ofi2pp = {} # From OpenFlow Index to Physical Port
sousaedu80135b92021-02-17 15:05:18 +0100101 self.logger = logging.getLogger("ro.sdn.openflow_conn")
tiernoed3e4d42019-10-21 15:31:27 +0000102
103 def get_of_switches(self):
sousaedu80135b92021-02-17 15:05:18 +0100104 """
tiernoed3e4d42019-10-21 15:31:27 +0000105 Obtain a a list of switches or DPID detected by this controller
106 :return: list length, and a list where each element a tuple pair (DPID, IP address), text_error: if fails
107 """
108 raise OpenflowConnNotImplemented("Should have implemented this")
109
110 def obtain_port_correspondence(self):
111 """
112 Obtain the correspondence between physical and openflow port names
113 :return: dictionary: with physical name as key, openflow name as value, error_text: if fails
114 """
115 raise OpenflowConnNotImplemented("Should have implemented this")
116
117 def get_of_rules(self, translate_of_ports=True):
118 """
119 Obtain the rules inserted at openflow controller
120 :param translate_of_ports: if True it translates ports from openflow index to physical switch name
121 :return: list where each item is a dictionary with the following content:
122 priority: rule priority
123 priority: rule priority
124 name: rule name (present also as the master dict key)
125 ingress_port: match input port of the rule
126 dst_mac: match destination mac address of the rule, can be missing or None if not apply
127 vlan_id: match vlan tag of the rule, can be missing or None if not apply
128 actions: list of actions, composed by a pair tuples:
129 (vlan, None/int): for stripping/setting a vlan tag
130 (out, port): send to this port
131 switch: DPID, all
132 text_error if fails
133 """
134 raise OpenflowConnNotImplemented("Should have implemented this")
135
136 def del_flow(self, flow_name):
137 """
138 Delete all existing rules
139 :param flow_name: flow_name, this is the rule name
140 :return: None if ok, text_error if fails
141 """
142 raise OpenflowConnNotImplemented("Should have implemented this")
143
144 def new_flow(self, data):
145 """
146 Insert a new static rule
147 :param data: dictionary with the following content:
148 priority: rule priority
149 name: rule name
150 ingress_port: match input port of the rule
151 dst_mac: match destination mac address of the rule, missing or None if not apply
152 vlan_id: match vlan tag of the rule, missing or None if not apply
153 actions: list of actions, composed by a pair tuples with these posibilities:
154 ('vlan', None/int): for stripping/setting a vlan tag
155 ('out', port): send to this port
156 :return: None if ok, text_error if fails
157 """
158 raise OpenflowConnNotImplemented("Should have implemented this")
159
160 def clear_all_flows(self):
sousaedu80135b92021-02-17 15:05:18 +0100161 """
tiernoed3e4d42019-10-21 15:31:27 +0000162 Delete all existing rules
163 :return: None if ok, text_error if fails
164 """
165 raise OpenflowConnNotImplemented("Should have implemented this")
166
167
168class SdnConnectorOpenFlow(SdnConnectorBase):
169 """
170 This class is the base engine of SDN plugins base on openflow rules
171 """
sousaedu80135b92021-02-17 15:05:18 +0100172
173 flow_fields = (
174 "priority",
175 "vlan",
176 "ingress_port",
177 "actions",
178 "dst_mac",
179 "src_mac",
180 "net_id",
181 )
tiernoed3e4d42019-10-21 15:31:27 +0000182
183 def __init__(self, wim, wim_account, config=None, logger=None, of_connector=None):
sousaedu80135b92021-02-17 15:05:18 +0100184 self.logger = logger or logging.getLogger("ro.sdn.openflow_conn")
tiernoed3e4d42019-10-21 15:31:27 +0000185 self.of_connector = of_connector
tierno70eeb182020-10-19 16:38:00 +0000186 config = config or {}
sousaedu80135b92021-02-17 15:05:18 +0100187 self.of_controller_nets_with_same_vlan = config.get(
188 "of_controller_nets_with_same_vlan", False
189 )
tiernoed3e4d42019-10-21 15:31:27 +0000190
191 def check_credentials(self):
192 try:
193 self.openflow_conn.obtain_port_correspondence()
194 except OpenflowConnException as e:
195 raise SdnConnectorError(e, http_code=e.http_code)
196
197 def get_connectivity_service_status(self, service_uuid, conn_info=None):
198 conn_info = conn_info or {}
199 return {
200 "sdn_status": conn_info.get("status", "ERROR"),
201 "error_msg": conn_info.get("error_msg", "Variable conn_info not provided"),
202 }
203 # TODO check rules connectirng to of_connector
204
205 def create_connectivity_service(self, service_type, connection_points, **kwargs):
206 net_id = str(uuid4())
207 ports = []
sousaedu80135b92021-02-17 15:05:18 +0100208
tiernoed3e4d42019-10-21 15:31:27 +0000209 for cp in connection_points:
210 port = {
211 "uuid": cp["service_endpoint_id"],
212 "vlan": cp.get("service_endpoint_encapsulation_info", {}).get("vlan"),
213 "mac": cp.get("service_endpoint_encapsulation_info", {}).get("mac"),
sousaedu80135b92021-02-17 15:05:18 +0100214 "switch_port": cp.get("service_endpoint_encapsulation_info", {}).get(
215 "switch_port"
216 ),
tiernoed3e4d42019-10-21 15:31:27 +0000217 }
218 ports.append(port)
sousaedu80135b92021-02-17 15:05:18 +0100219
tiernoed3e4d42019-10-21 15:31:27 +0000220 try:
sousaedu80135b92021-02-17 15:05:18 +0100221 created_items = self._set_openflow_rules(
222 service_type, net_id, ports, created_items=None
223 )
224
tiernoed3e4d42019-10-21 15:31:27 +0000225 return net_id, created_items
226 except (SdnConnectorError, OpenflowConnException) as e:
227 raise SdnConnectorError(e, http_code=e.http_code)
228
229 def delete_connectivity_service(self, service_uuid, conn_info=None):
230 try:
231 service_type = "ELAN"
232 ports = []
sousaedu80135b92021-02-17 15:05:18 +0100233 self._set_openflow_rules(
234 service_type, service_uuid, ports, created_items=conn_info
235 )
236
tiernoed3e4d42019-10-21 15:31:27 +0000237 return None
238 except (SdnConnectorError, OpenflowConnException) as e:
239 raise SdnConnectorError(e, http_code=e.http_code)
240
sousaedu80135b92021-02-17 15:05:18 +0100241 def edit_connectivity_service(
242 self, service_uuid, conn_info=None, connection_points=None, **kwargs
243 ):
tiernoed3e4d42019-10-21 15:31:27 +0000244 ports = []
245 for cp in connection_points:
246 port = {
247 "uuid": cp["service_endpoint_id"],
248 "vlan": cp.get("service_endpoint_encapsulation_info", {}).get("vlan"),
249 "mac": cp.get("service_endpoint_encapsulation_info", {}).get("mac"),
sousaedu80135b92021-02-17 15:05:18 +0100250 "switch_port": cp.get("service_endpoint_encapsulation_info", {}).get(
251 "switch_port"
252 ),
tiernoed3e4d42019-10-21 15:31:27 +0000253 }
254 ports.append(port)
sousaedu80135b92021-02-17 15:05:18 +0100255
tiernoed3e4d42019-10-21 15:31:27 +0000256 service_type = "ELAN" # TODO. Store at conn_info for later use
sousaedu80135b92021-02-17 15:05:18 +0100257
tiernoed3e4d42019-10-21 15:31:27 +0000258 try:
sousaedu80135b92021-02-17 15:05:18 +0100259 created_items = self._set_openflow_rules(
260 service_type, service_uuid, ports, created_items=conn_info
261 )
262
tiernoed3e4d42019-10-21 15:31:27 +0000263 return created_items
264 except (SdnConnectorError, OpenflowConnException) as e:
265 raise SdnConnectorError(e, http_code=e.http_code)
266
267 def clear_all_connectivity_services(self):
268 """Delete all WAN Links corresponding to a WIM"""
269 pass
270
271 def get_all_active_connectivity_services(self):
272 """Provide information about all active connections provisioned by a
273 WIM
274 """
275 pass
276
277 def _set_openflow_rules(self, net_type, net_id, ports, created_items=None):
278 ifaces_nb = len(ports)
sousaedu80135b92021-02-17 15:05:18 +0100279
tiernoed3e4d42019-10-21 15:31:27 +0000280 if not created_items:
sousaedu80135b92021-02-17 15:05:18 +0100281 created_items = {
282 "status": None,
283 "error_msg": None,
284 "installed_rules_ids": [],
285 }
tiernoed3e4d42019-10-21 15:31:27 +0000286 rules_to_delete = created_items.get("installed_rules_ids") or []
287 new_installed_rules_ids = []
288 error_list = []
Gulsum Atici2f995052022-11-23 16:06:40 +0300289 step = "Checking ports and network type compatibility"
tiernoed3e4d42019-10-21 15:31:27 +0000290
291 try:
tiernoed3e4d42019-10-21 15:31:27 +0000292 if ifaces_nb < 2:
293 pass
sousaedu80135b92021-02-17 15:05:18 +0100294 elif net_type == "ELINE":
tiernoed3e4d42019-10-21 15:31:27 +0000295 if ifaces_nb > 2:
sousaedu80135b92021-02-17 15:05:18 +0100296 raise SdnConnectorError(
297 "'ELINE' type network cannot connect {} interfaces, only 2".format(
298 ifaces_nb
299 )
300 )
301 elif net_type == "ELAN":
tiernoed3e4d42019-10-21 15:31:27 +0000302 if ifaces_nb > 2 and self.of_controller_nets_with_same_vlan:
303 # check all ports are VLAN (tagged) or none
304 vlan_tags = []
sousaedu80135b92021-02-17 15:05:18 +0100305
tiernoed3e4d42019-10-21 15:31:27 +0000306 for port in ports:
307 if port["vlan"] not in vlan_tags:
308 vlan_tags.append(port["vlan"])
sousaedu80135b92021-02-17 15:05:18 +0100309
tiernoed3e4d42019-10-21 15:31:27 +0000310 if len(vlan_tags) > 1:
sousaedu80135b92021-02-17 15:05:18 +0100311 raise SdnConnectorError(
312 "This pluging cannot connect ports with diferent VLAN tags when flag "
313 "'of_controller_nets_with_same_vlan' is active"
314 )
tiernoed3e4d42019-10-21 15:31:27 +0000315 else:
sousaedu80135b92021-02-17 15:05:18 +0100316 raise SdnConnectorError(
317 "Only ELINE or ELAN network types are supported for openflow"
318 )
tiernoed3e4d42019-10-21 15:31:27 +0000319
320 # Get the existing flows at openflow controller
321 step = "Getting installed openflow rules"
322 existing_flows = self.of_connector.get_of_rules()
323 existing_flows_ids = [flow["name"] for flow in existing_flows]
324
325 # calculate new flows to be inserted
326 step = "Compute needed openflow rules"
327 new_flows = self._compute_net_flows(net_id, ports)
328
329 name_index = 0
330 for flow in new_flows:
331 # 1 check if an equal flow is already present
332 index = self._check_flow_already_present(flow, existing_flows)
sousaedu80135b92021-02-17 15:05:18 +0100333
tiernoed3e4d42019-10-21 15:31:27 +0000334 if index >= 0:
335 flow_id = existing_flows[index]["name"]
336 self.logger.debug("Skipping already present flow %s", str(flow))
337 else:
338 # 2 look for a non used name
339 flow_name = flow["net_id"] + "." + str(name_index)
sousaedu80135b92021-02-17 15:05:18 +0100340
tiernoed3e4d42019-10-21 15:31:27 +0000341 while flow_name in existing_flows_ids:
342 name_index += 1
343 flow_name = flow["net_id"] + "." + str(name_index)
sousaedu80135b92021-02-17 15:05:18 +0100344
345 flow["name"] = flow_name
346
tiernoed3e4d42019-10-21 15:31:27 +0000347 # 3 insert at openflow
348 try:
349 self.of_connector.new_flow(flow)
350 flow_id = flow["name"]
351 existing_flows_ids.append(flow_id)
352 except OpenflowConnException as e:
353 flow_id = None
sousaedu80135b92021-02-17 15:05:18 +0100354 error_list.append(
355 "Cannot create rule for ingress_port={}, dst_mac={}: {}".format(
356 flow["ingress_port"], flow["dst_mac"], e
357 )
358 )
tiernoed3e4d42019-10-21 15:31:27 +0000359
360 # 4 insert at database
361 if flow_id:
362 new_installed_rules_ids.append(flow_id)
363 if flow_id in rules_to_delete:
364 rules_to_delete.remove(flow_id)
365
366 # delete not needed old flows from openflow
367 for flow_id in rules_to_delete:
368 # Delete flow
369 try:
370 self.of_connector.del_flow(flow_id)
371 except OpenflowConnNotFoundException:
aticig7b521f72022-07-15 00:43:09 +0300372 self.logger.exception("OpenflowConnNotFoundException occured.")
tiernoed3e4d42019-10-21 15:31:27 +0000373 except OpenflowConnException as e:
tierno4126d052019-12-11 15:30:44 +0000374 error_text = "Cannot remove rule '{}': {}".format(flow_id, e)
tiernoed3e4d42019-10-21 15:31:27 +0000375 error_list.append(error_text)
376 self.logger.error(error_text)
sousaedu80135b92021-02-17 15:05:18 +0100377
tiernoed3e4d42019-10-21 15:31:27 +0000378 created_items["installed_rules_ids"] = new_installed_rules_ids
sousaedu80135b92021-02-17 15:05:18 +0100379
tiernoed3e4d42019-10-21 15:31:27 +0000380 if error_list:
381 created_items["error_msg"] = ";".join(error_list)[:1000]
382 created_items["error_msg"] = "ERROR"
383 else:
384 created_items["error_msg"] = None
385 created_items["status"] = "ACTIVE"
sousaedu80135b92021-02-17 15:05:18 +0100386
tiernoed3e4d42019-10-21 15:31:27 +0000387 return created_items
388 except (SdnConnectorError, OpenflowConnException) as e:
389 raise SdnConnectorError("Error while {}: {}".format(step, e)) from e
390 except Exception as e:
tierno4126d052019-12-11 15:30:44 +0000391 error_text = "Error while {}: {}".format(step, e)
tiernoed3e4d42019-10-21 15:31:27 +0000392 self.logger.critical(error_text, exc_info=True)
tierno4126d052019-12-11 15:30:44 +0000393 raise SdnConnectorError(error_text)
tiernoed3e4d42019-10-21 15:31:27 +0000394
395 def _compute_net_flows(self, net_id, ports):
396 new_flows = []
397 new_broadcast_flows = {}
398 nb_ports = len(ports)
399
400 # Check switch_port information is right
401 for port in ports:
402 nb_ports += 1
sousaedu80135b92021-02-17 15:05:18 +0100403
404 if str(port["switch_port"]) not in self.of_connector.pp2ofi:
405 raise SdnConnectorError(
406 "switch port name '{}' is not valid for the openflow controller".format(
407 port["switch_port"]
408 )
409 )
410
tiernoed3e4d42019-10-21 15:31:27 +0000411 priority = 1000 # 1100
412
413 for src_port in ports:
414 # if src_port.get("groups")
sousaedu80135b92021-02-17 15:05:18 +0100415 vlan_in = src_port["vlan"]
tiernoed3e4d42019-10-21 15:31:27 +0000416
417 # BROADCAST:
sousaedu80135b92021-02-17 15:05:18 +0100418 broadcast_key = src_port["uuid"] + "." + str(vlan_in)
tiernoed3e4d42019-10-21 15:31:27 +0000419 if broadcast_key in new_broadcast_flows:
420 flow_broadcast = new_broadcast_flows[broadcast_key]
421 else:
sousaedu80135b92021-02-17 15:05:18 +0100422 flow_broadcast = {
423 "priority": priority,
424 "net_id": net_id,
425 "dst_mac": "ff:ff:ff:ff:ff:ff",
426 "ingress_port": str(src_port["switch_port"]),
427 "vlan_id": vlan_in,
428 "actions": [],
429 }
tiernoed3e4d42019-10-21 15:31:27 +0000430 new_broadcast_flows[broadcast_key] = flow_broadcast
sousaedu80135b92021-02-17 15:05:18 +0100431
tiernoed3e4d42019-10-21 15:31:27 +0000432 if vlan_in is not None:
sousaedu80135b92021-02-17 15:05:18 +0100433 flow_broadcast["vlan_id"] = str(vlan_in)
tiernoed3e4d42019-10-21 15:31:27 +0000434
435 for dst_port in ports:
sousaedu80135b92021-02-17 15:05:18 +0100436 vlan_out = dst_port["vlan"]
437
438 if (
439 src_port["switch_port"] == dst_port["switch_port"]
440 and vlan_in == vlan_out
441 ):
tiernoed3e4d42019-10-21 15:31:27 +0000442 continue
sousaedu80135b92021-02-17 15:05:18 +0100443
tiernoed3e4d42019-10-21 15:31:27 +0000444 flow = {
445 "priority": priority,
sousaedu80135b92021-02-17 15:05:18 +0100446 "net_id": net_id,
447 "ingress_port": str(src_port["switch_port"]),
448 "vlan_id": vlan_in,
449 "actions": [],
tiernoed3e4d42019-10-21 15:31:27 +0000450 }
sousaedu80135b92021-02-17 15:05:18 +0100451
tiernoed3e4d42019-10-21 15:31:27 +0000452 # allow that one port have no mac
sousaedu80135b92021-02-17 15:05:18 +0100453 # point to point or nets with 2 elements
454 if dst_port["mac"] is None or nb_ports == 2:
455 flow["priority"] = priority - 5 # less priority
tiernoed3e4d42019-10-21 15:31:27 +0000456 else:
sousaedu80135b92021-02-17 15:05:18 +0100457 flow["dst_mac"] = str(dst_port["mac"])
tiernoed3e4d42019-10-21 15:31:27 +0000458
459 if vlan_out is None:
460 if vlan_in:
sousaedu80135b92021-02-17 15:05:18 +0100461 flow["actions"].append(("vlan", None))
tiernoed3e4d42019-10-21 15:31:27 +0000462 else:
sousaedu80135b92021-02-17 15:05:18 +0100463 flow["actions"].append(("vlan", vlan_out))
464
465 flow["actions"].append(("out", str(dst_port["switch_port"])))
tiernoed3e4d42019-10-21 15:31:27 +0000466
467 if self._check_flow_already_present(flow, new_flows) >= 0:
468 self.logger.debug("Skipping repeated flow '%s'", str(flow))
469 continue
470
471 new_flows.append(flow)
472
473 # BROADCAST:
sousaedu80135b92021-02-17 15:05:18 +0100474 # point to multipoint or nets with more than 2 elements
475 if nb_ports <= 2:
tiernoed3e4d42019-10-21 15:31:27 +0000476 continue
sousaedu80135b92021-02-17 15:05:18 +0100477
478 out = (vlan_out, str(dst_port["switch_port"]))
479
480 if out not in flow_broadcast["actions"]:
481 flow_broadcast["actions"].append(out)
tiernoed3e4d42019-10-21 15:31:27 +0000482
483 # BROADCAST
484 for flow_broadcast in new_broadcast_flows.values():
sousaedu80135b92021-02-17 15:05:18 +0100485 if len(flow_broadcast["actions"]) == 0:
tiernoed3e4d42019-10-21 15:31:27 +0000486 continue # nothing to do, skip
sousaedu80135b92021-02-17 15:05:18 +0100487
488 flow_broadcast["actions"].sort()
489
490 if "vlan_id" in flow_broadcast:
491 # indicates that a packet contains a vlan, and the vlan
492 previous_vlan = 0
tiernoed3e4d42019-10-21 15:31:27 +0000493 else:
494 previous_vlan = None
sousaedu80135b92021-02-17 15:05:18 +0100495
tiernoed3e4d42019-10-21 15:31:27 +0000496 final_actions = []
497 action_number = 0
sousaedu80135b92021-02-17 15:05:18 +0100498
499 for action in flow_broadcast["actions"]:
tiernoed3e4d42019-10-21 15:31:27 +0000500 if action[0] != previous_vlan:
sousaedu80135b92021-02-17 15:05:18 +0100501 final_actions.append(("vlan", action[0]))
tiernoed3e4d42019-10-21 15:31:27 +0000502 previous_vlan = action[0]
sousaedu80135b92021-02-17 15:05:18 +0100503
tiernoed3e4d42019-10-21 15:31:27 +0000504 if self.of_controller_nets_with_same_vlan and action_number:
sousaedu80135b92021-02-17 15:05:18 +0100505 raise SdnConnectorError(
506 "Cannot interconnect different vlan tags in a network when flag "
507 "'of_controller_nets_with_same_vlan' is True."
508 )
509
tiernoed3e4d42019-10-21 15:31:27 +0000510 action_number += 1
sousaedu80135b92021-02-17 15:05:18 +0100511 final_actions.append(("out", action[1]))
512 flow_broadcast["actions"] = final_actions
tiernoed3e4d42019-10-21 15:31:27 +0000513
514 if self._check_flow_already_present(flow_broadcast, new_flows) >= 0:
515 self.logger.debug("Skipping repeated flow '%s'", str(flow_broadcast))
516 continue
517
518 new_flows.append(flow_broadcast)
519
520 # UNIFY openflow rules with the same input port and vlan and the same output actions
521 # These flows differ at the dst_mac; and they are unified by not filtering by dst_mac
522 # this can happen if there is only two ports. It is converted to a point to point connection
sousaedu80135b92021-02-17 15:05:18 +0100523 # use as key vlan_id+ingress_port and as value the list of flows matching these values
524 flow_dict = {}
tiernoed3e4d42019-10-21 15:31:27 +0000525 for flow in new_flows:
526 key = str(flow.get("vlan_id")) + ":" + flow["ingress_port"]
sousaedu80135b92021-02-17 15:05:18 +0100527
tiernoed3e4d42019-10-21 15:31:27 +0000528 if key in flow_dict:
529 flow_dict[key].append(flow)
530 else:
531 flow_dict[key] = [flow]
sousaedu80135b92021-02-17 15:05:18 +0100532
tiernoed3e4d42019-10-21 15:31:27 +0000533 new_flows2 = []
sousaedu80135b92021-02-17 15:05:18 +0100534
tiernoed3e4d42019-10-21 15:31:27 +0000535 for flow_list in flow_dict.values():
536 convert2ptp = False
sousaedu80135b92021-02-17 15:05:18 +0100537
tiernoed3e4d42019-10-21 15:31:27 +0000538 if len(flow_list) >= 2:
539 convert2ptp = True
sousaedu80135b92021-02-17 15:05:18 +0100540
tiernoed3e4d42019-10-21 15:31:27 +0000541 for f in flow_list:
sousaedu80135b92021-02-17 15:05:18 +0100542 if f["actions"] != flow_list[0]["actions"]:
tiernoed3e4d42019-10-21 15:31:27 +0000543 convert2ptp = False
544 break
sousaedu80135b92021-02-17 15:05:18 +0100545
tiernoed3e4d42019-10-21 15:31:27 +0000546 if convert2ptp: # add only one unified rule without dst_mac
sousaedu80135b92021-02-17 15:05:18 +0100547 self.logger.debug(
548 "Convert flow rules to NON mac dst_address " + str(flow_list)
549 )
550 flow_list[0].pop("dst_mac")
tiernoed3e4d42019-10-21 15:31:27 +0000551 flow_list[0]["priority"] -= 5
552 new_flows2.append(flow_list[0])
553 else: # add all the rules
554 new_flows2 += flow_list
sousaedu80135b92021-02-17 15:05:18 +0100555
tiernoed3e4d42019-10-21 15:31:27 +0000556 return new_flows2
557
558 def _check_flow_already_present(self, new_flow, flow_list):
sousaedu80135b92021-02-17 15:05:18 +0100559 """check if the same flow is already present in the flow list
tiernoed3e4d42019-10-21 15:31:27 +0000560 The flow is repeated if all the fields, apart from name, are equal
sousaedu80135b92021-02-17 15:05:18 +0100561 Return the index of matching flow, -1 if not match
562 """
tiernoed3e4d42019-10-21 15:31:27 +0000563 for index, flow in enumerate(flow_list):
564 for f in self.flow_fields:
565 if flow.get(f) != new_flow.get(f):
566 break
567 else:
568 return index
sousaedu80135b92021-02-17 15:05:18 +0100569
tiernoed3e4d42019-10-21 15:31:27 +0000570 return -1