From 210ed9344c3f36ea667182a531488edfe62d00dc Mon Sep 17 00:00:00 2001 From: "Georgios P. Katsikas" <gkatsikas@ubitech.eu> Date: Wed, 19 Mar 2025 19:34:34 +0000 Subject: [PATCH 01/14] fix: basic P4 refactoring --- proto/context.proto | 24 +- src/common/type_checkers/Checkers.py | 79 +++++ .../service/database/models/enums/LinkType.py | 13 +- .../database/models/enums/ServiceType.py | 3 + src/device/service/drivers/p4/p4_common.py | 84 +---- src/device/service/drivers/p4/p4_driver.py | 32 +- src/device/tests/test_internal_p4.py | 28 +- .../service_handler_api/FilterFields.py | 3 + .../service/service_handlers/__init__.py | 6 +- .../{p4 => p4_l1}/__init__.py | 0 .../p4_l1_service_handler.py} | 48 +-- ...ert-acl.json => sbi-rules-insert-acl.json} | 0 ...t-b1.json => sbi-rules-insert-int-b1.json} | 0 ...t-b2.json => sbi-rules-insert-int-b2.json} | 0 ...t-b3.json => sbi-rules-insert-int-b3.json} | 0 ...son => sbi-rules-insert-routing-corp.json} | 0 ...son => sbi-rules-insert-routing-edge.json} | 0 ...ules-remove.json => sbi-rules-remove.json} | 0 .../descriptors/topology.json | 310 ++++-------------- ....sh => run_test_02_sbi_rules_provision.sh} | 2 +- ...h => run_test_03_sbi_rules_deprovision.sh} | 2 +- ...t_04_cleanup.sh => run_test_06_cleanup.sh} | 0 src/tests/p4-int-routing-acl/test_common.py | 27 +- .../test_functional_cleanup.py | 1 - ... test_functional_sbi_rules_deprovision.py} | 0 ...=> test_functional_sbi_rules_provision.py} | 0 .../static/topology_icons/p4-switch.png | Bin 6288 -> 244738 bytes 27 files changed, 261 insertions(+), 401 deletions(-) rename src/service/service/service_handlers/{p4 => p4_l1}/__init__.py (100%) rename src/service/service/service_handlers/{p4/p4_service_handler.py => p4_l1/p4_l1_service_handler.py} (95%) rename src/tests/p4-int-routing-acl/descriptors/{rules-insert-acl.json => sbi-rules-insert-acl.json} (100%) rename src/tests/p4-int-routing-acl/descriptors/{rules-insert-int-b1.json => sbi-rules-insert-int-b1.json} (100%) rename src/tests/p4-int-routing-acl/descriptors/{rules-insert-int-b2.json => sbi-rules-insert-int-b2.json} (100%) rename src/tests/p4-int-routing-acl/descriptors/{rules-insert-int-b3.json => sbi-rules-insert-int-b3.json} (100%) rename src/tests/p4-int-routing-acl/descriptors/{rules-insert-routing-corp.json => sbi-rules-insert-routing-corp.json} (100%) rename src/tests/p4-int-routing-acl/descriptors/{rules-insert-routing-edge.json => sbi-rules-insert-routing-edge.json} (100%) rename src/tests/p4-int-routing-acl/descriptors/{rules-remove.json => sbi-rules-remove.json} (100%) rename src/tests/p4-int-routing-acl/{run_test_02_rules_provision.sh => run_test_02_sbi_rules_provision.sh} (95%) rename src/tests/p4-int-routing-acl/{run_test_03_rules_deprovision.sh => run_test_03_sbi_rules_deprovision.sh} (95%) rename src/tests/p4-int-routing-acl/{run_test_04_cleanup.sh => run_test_06_cleanup.sh} (100%) rename src/tests/p4-int-routing-acl/{test_functional_rules_deprovision.py => test_functional_sbi_rules_deprovision.py} (100%) rename src/tests/p4-int-routing-acl/{test_functional_rules_provision.py => test_functional_sbi_rules_provision.py} (100%) diff --git a/proto/context.proto b/proto/context.proto index fb0111e14..0f2c8011f 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -199,13 +199,13 @@ message Device { DeviceId controller_id = 9; // Identifier of node controlling the actual device } -message Component { //Defined previously to this section - Tested OK - Uuid component_uuid = 1; - string name = 2; - string type = 3; +message Component { // Defined previously in this section + Uuid component_uuid = 1; + string name = 2; + string type = 3; map<string, string> attributes = 4; // dict[attr.name => json.dumps(attr.value)] - string parent = 5; + string parent = 5; } message DeviceConfig { @@ -271,6 +271,7 @@ enum LinkTypeEnum { LINKTYPE_FIBER = 2; LINKTYPE_RADIO = 3; LINKTYPE_VIRTUAL = 4; + LINKTYPE_MANAGEMENT = 5; } message LinkAttributes { @@ -320,11 +321,14 @@ enum ServiceTypeEnum { SERVICETYPE_UNKNOWN = 0; SERVICETYPE_L3NM = 1; SERVICETYPE_L2NM = 2; - SERVICETYPE_TAPI_CONNECTIVITY_SERVICE = 3; - SERVICETYPE_TE = 4; - SERVICETYPE_E2E = 5; - SERVICETYPE_OPTICAL_CONNECTIVITY = 6; - SERVICETYPE_QKD = 7; + SERVICETYPE_L1NM = 3; + SERVICETYPE_TAPI_CONNECTIVITY_SERVICE = 4; + SERVICETYPE_TE = 5; + SERVICETYPE_E2E = 6; + SERVICETYPE_OPTICAL_CONNECTIVITY = 7; + SERVICETYPE_QKD = 8; + SERVICETYPE_INT = 9; + SERVICETYPE_ACL = 10; } enum ServiceStatusEnum { diff --git a/src/common/type_checkers/Checkers.py b/src/common/type_checkers/Checkers.py index e1bbe3f06..694f14102 100644 --- a/src/common/type_checkers/Checkers.py +++ b/src/common/type_checkers/Checkers.py @@ -13,6 +13,8 @@ # limitations under the License. import re +import ipaddress +from ctypes import c_uint16, sizeof from typing import Any, Container, Dict, List, Optional, Pattern, Set, Sized, Tuple, Union def chk_none(name : str, value : Any, reason=None) -> Any: @@ -107,3 +109,80 @@ def chk_options(name : str, value : Any, options : Container) -> Any: msg = '{}({}) is not one of options({}).' raise ValueError(msg.format(str(name), str(value), str(options))) return value + +# MAC address checker +mac_pattern = re.compile(r"^([\da-fA-F]{2}:){5}([\da-fA-F]{2})$") + +def chk_address_mac(mac_addr : str): + """ + Check whether input string is a valid MAC address or not. + + :param mac_addr: string-based MAC address + :return: boolean status + """ + return mac_pattern.match(mac_addr) is not None + +# IPv4/IPv6 address checkers +IPV4_LOCALHOST = "localhost" + +def chk_address_ipv4(ip_addr : str): + """ + Check whether input string is a valid IPv4 address or not. + + :param ip_addr: string-based IPv4 address + :return: boolean status + """ + if ip_addr == IPV4_LOCALHOST: + return True + try: + addr = ipaddress.ip_address(ip_addr) + return isinstance(addr, ipaddress.IPv4Address) + except ValueError: + return False + +def chk_address_ipv6(ip_addr : str): + """ + Check whether input string is a valid IPv6 address or not. + + :param ip_addr: string-based IPv6 address + :return: boolean status + """ + try: + addr = ipaddress.ip_address(ip_addr) + return isinstance(addr, ipaddress.IPv6Address) + except ValueError: + return False + + +# VLAN ID checker +VLAN_ID_MIN = 1 +VLAN_ID_MAX = 4094 + +def chk_vlan_id(vlan_id : int): + return VLAN_ID_MIN <= vlan_id <= VLAN_ID_MAX + + +# Transport port checker + +def limits(c_int_type): + """ + Discover limits of numerical type. + + :param c_int_type: numerical type + :return: tuple of numerical type's limits + """ + signed = c_int_type(-1).value < c_int_type(0).value + bit_size = sizeof(c_int_type) * 8 + signed_limit = 2 ** (bit_size - 1) + return (-signed_limit, signed_limit - 1) \ + if signed else (0, 2 * signed_limit - 1) + +def chk_transport_port(trans_port : int): + """ + Check whether input is a valid transport port number or not. + + :param trans_port: transport port number + :return: boolean status + """ + lim = limits(c_uint16) + return lim[0] <= trans_port <= lim[1] diff --git a/src/context/service/database/models/enums/LinkType.py b/src/context/service/database/models/enums/LinkType.py index 68624af84..97eacdd8b 100644 --- a/src/context/service/database/models/enums/LinkType.py +++ b/src/context/service/database/models/enums/LinkType.py @@ -18,15 +18,16 @@ from ._GrpcToEnum import grpc_to_enum # IMPORTANT: Entries of enum class ORM_LinkTypeEnum should be named as in # the proto files removing the prefixes. For example, proto item -# LinkTypeEnum.DEVICEDRIVER_COPPER should be included as COPPER. +# LinkTypeEnum.COPPER should be included as COPPER. # If item name does not match, automatic mapping of proto enums # to database enums will fail. class ORM_LinkTypeEnum(enum.Enum): - UNKNOWN = LinkTypeEnum.LINKTYPE_UNKNOWN - COPPER = LinkTypeEnum.LINKTYPE_COPPER - FIBER = LinkTypeEnum.LINKTYPE_FIBER - RADIO = LinkTypeEnum.LINKTYPE_RADIO - VIRTUAL = LinkTypeEnum.LINKTYPE_VIRTUAL + UNKNOWN = LinkTypeEnum.LINKTYPE_UNKNOWN + COPPER = LinkTypeEnum.LINKTYPE_COPPER + FIBER = LinkTypeEnum.LINKTYPE_FIBER + RADIO = LinkTypeEnum.LINKTYPE_RADIO + VIRTUAL = LinkTypeEnum.LINKTYPE_VIRTUAL + MANAGEMENT = LinkTypeEnum.LINKTYPE_MANAGEMENT grpc_to_enum__link_type_enum = functools.partial( grpc_to_enum, LinkTypeEnum, ORM_LinkTypeEnum diff --git a/src/context/service/database/models/enums/ServiceType.py b/src/context/service/database/models/enums/ServiceType.py index 45f849a26..899c71acf 100644 --- a/src/context/service/database/models/enums/ServiceType.py +++ b/src/context/service/database/models/enums/ServiceType.py @@ -25,11 +25,14 @@ class ORM_ServiceTypeEnum(enum.Enum): UNKNOWN = ServiceTypeEnum.SERVICETYPE_UNKNOWN L3NM = ServiceTypeEnum.SERVICETYPE_L3NM L2NM = ServiceTypeEnum.SERVICETYPE_L2NM + L1NM = ServiceTypeEnum.SERVICETYPE_L1NM TAPI_CONNECTIVITY_SERVICE = ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE TE = ServiceTypeEnum.SERVICETYPE_TE E2E = ServiceTypeEnum.SERVICETYPE_E2E OPTICAL_CONNECTIVITY = ServiceTypeEnum.SERVICETYPE_OPTICAL_CONNECTIVITY QKD = ServiceTypeEnum.SERVICETYPE_QKD + INT = ServiceTypeEnum.SERVICETYPE_INT + ACL = ServiceTypeEnum.SERVICETYPE_ACL grpc_to_enum__service_type = functools.partial( grpc_to_enum, ServiceTypeEnum, ORM_ServiceTypeEnum) diff --git a/src/device/service/drivers/p4/p4_common.py b/src/device/service/drivers/p4/p4_common.py index b55296a65..b4c0d8832 100644 --- a/src/device/service/drivers/p4/p4_common.py +++ b/src/device/service/drivers/p4/p4_common.py @@ -23,16 +23,15 @@ as well as static variables used by various P4 driver components. """ import logging +import ipaddress +import macaddress import math -import re import socket -import ipaddress from typing import Any, Dict, List, Optional, Tuple -from ctypes import c_uint16, sizeof -import macaddress from common.type_checkers.Checkers import \ - chk_attribute, chk_string, chk_type, chk_issubclass + chk_attribute, chk_string, chk_type, chk_issubclass,\ + chk_address_mac, chk_address_ipv4, chk_address_ipv6 try: from .p4_exception import UserBadValueError except ImportError: @@ -60,17 +59,6 @@ LOGGER = logging.getLogger(__name__) # MAC address encoding/decoding -mac_pattern = re.compile(r"^([\da-fA-F]{2}:){5}([\da-fA-F]{2})$") - - -def matches_mac(mac_addr_string): - """ - Check whether input string is a valid MAC address or not. - - :param mac_addr_string: string-based MAC address - :return: boolean status - """ - return mac_pattern.match(mac_addr_string) is not None def encode_mac(mac_addr_string): @@ -94,23 +82,6 @@ def decode_mac(encoded_mac_addr): # IP address encoding/decoding -IPV4_LOCALHOST = "localhost" - - -def matches_ipv4(ip_addr_string): - """ - Check whether input string is a valid IPv4 address or not. - - :param ip_addr_string: string-based IPv4 address - :return: boolean status - """ - if ip_addr_string == IPV4_LOCALHOST: - return True - try: - addr = ipaddress.ip_address(ip_addr_string) - return isinstance(addr, ipaddress.IPv4Address) - except ValueError: - return False def encode_ipv4(ip_addr_string): @@ -133,20 +104,6 @@ def decode_ipv4(encoded_ip_addr): return socket.inet_ntoa(encoded_ip_addr) -def matches_ipv6(ip_addr_string): - """ - Check whether input string is a valid IPv6 address or not. - - :param ip_addr_string: string-based IPv6 address - :return: boolean status - """ - try: - addr = ipaddress.ip_address(ip_addr_string) - return isinstance(addr, ipaddress.IPv6Address) - except ValueError: - return False - - def encode_ipv6(ip_addr_string): """ Convert string-based IPv6 address into bytes. @@ -170,31 +127,6 @@ def decode_ipv6(encoded_ip_addr): # Numerical encoding/decoding -def limits(c_int_type): - """ - Discover limits of numerical type. - - :param c_int_type: numerical type - :return: tuple of numerical type's limits - """ - signed = c_int_type(-1).value < c_int_type(0).value - bit_size = sizeof(c_int_type) * 8 - signed_limit = 2 ** (bit_size - 1) - return (-signed_limit, signed_limit - 1) \ - if signed else (0, 2 * signed_limit - 1) - - -def valid_port(port): - """ - Check whether input is a valid port number or not. - - :param port: port number - :return: boolean status - """ - lim = limits(c_uint16) - return lim[0] <= port <= lim[1] - - def bitwidth_to_bytes(bitwidth): """ Convert number of bits to number of bytes. @@ -245,11 +177,11 @@ def encode(variable, bitwidth): if isinstance(variable, int): encoded_bytes = encode_num(variable, bitwidth) elif isinstance(variable, str): - if matches_mac(variable): + if chk_address_mac(variable): encoded_bytes = encode_mac(variable) - elif matches_ipv4(variable): + elif chk_address_ipv4(variable): encoded_bytes = encode_ipv4(variable) - elif matches_ipv6(variable): + elif chk_address_ipv6(variable): encoded_bytes = encode_ipv6(variable) else: try: @@ -471,7 +403,7 @@ def parse_integer_list_from_json(resource, resource_list, resource_item): return integers_list def process_optional_string_field( - #TODO: Consider adding this in common methdos as it is taken by the Emulated driver + #TODO: Consider adding this in common methods as it is taken by the Emulated driver endpoint_data : Dict[str, Any], field_name : str, endpoint_resource_value : Dict[str, Any] ) -> None: field_value = chk_attribute(field_name, endpoint_data, 'endpoint_data', default=None) diff --git a/src/device/service/drivers/p4/p4_driver.py b/src/device/service/drivers/p4/p4_driver.py index c89a42bad..0b109529d 100644 --- a/src/device/service/drivers/p4/p4_driver.py +++ b/src/device/service/drivers/p4/p4_driver.py @@ -22,10 +22,10 @@ import logging import threading from typing import Any, Iterator, List, Optional, Tuple, Union from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method -from common.type_checkers.Checkers import chk_type, chk_length, chk_string +from common.type_checkers.Checkers import chk_type, chk_length, chk_string, \ + chk_address_ipv4, chk_address_ipv6, chk_transport_port from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_RULES -from .p4_common import matches_ipv4, matches_ipv6, valid_port,\ - compose_resource_endpoints, parse_resource_string_from_json,\ +from .p4_common import compose_resource_endpoints,\ P4_ATTR_DEV_ID, P4_ATTR_DEV_NAME, P4_ATTR_DEV_ENDPOINTS,\ P4_ATTR_DEV_VENDOR, P4_ATTR_DEV_HW_VER, P4_ATTR_DEV_SW_VER,\ P4_ATTR_DEV_P4BIN, P4_ATTR_DEV_P4INFO, P4_ATTR_DEV_TIMEOUT,\ @@ -336,9 +336,9 @@ class P4Driver(_Driver): :return: void or exception in case of validation error """ # Device endpoint information - assert matches_ipv4(self.__address) or (matches_ipv6(self.__address)),\ + assert chk_address_ipv4(self.__address) or (chk_address_ipv6(self.__address)),\ f"{self.__address} not a valid IPv4 or IPv6 address" - assert valid_port(self.__port), \ + assert chk_transport_port(self.__port), \ f"{self.__port} not a valid transport port" self.__grpc_endpoint = f"{self.__address}:{self.__port}" @@ -395,18 +395,24 @@ class P4Driver(_Driver): # Path to P4 binary file if P4_ATTR_DEV_P4BIN in self.__settings: self.__p4bin_path = self.__settings.get(P4_ATTR_DEV_P4BIN) - assert os.path.exists(self.__p4bin_path),\ - "Invalid path to p4bin file: {}".format(self.__p4bin_path) - assert P4_ATTR_DEV_P4INFO in self.__settings,\ - "p4info and p4bin settings must be provided together" + if not os.path.exists(self.__p4bin_path): + LOGGER.warning( + "Invalid path to p4bin file: {}".format(self.__p4bin_path)) + self.__p4bin_path = "" + else: + assert P4_ATTR_DEV_P4INFO in self.__settings,\ + "p4info and p4bin settings must be provided together" # Path to P4 info file if P4_ATTR_DEV_P4INFO in self.__settings: self.__p4info_path = self.__settings.get(P4_ATTR_DEV_P4INFO) - assert os.path.exists(self.__p4info_path),\ - "Invalid path to p4info file: {}".format(self.__p4info_path) - assert P4_ATTR_DEV_P4BIN in self.__settings,\ - "p4info and p4bin settings must be provided together" + if not os.path.exists(self.__p4info_path): + LOGGER.warning( + "Invalid path to p4info file: {}".format(self.__p4info_path)) + self.__p4info_path = "" + else: + assert P4_ATTR_DEV_P4BIN in self.__settings,\ + "p4info and p4bin settings must be provided together" if (not self.__p4bin_path) or (not self.__p4info_path): LOGGER.warning( diff --git a/src/device/tests/test_internal_p4.py b/src/device/tests/test_internal_p4.py index af99bb86b..48501bfd8 100644 --- a/src/device/tests/test_internal_p4.py +++ b/src/device/tests/test_internal_p4.py @@ -17,11 +17,13 @@ Internal P4 driver tests. """ import pytest +from common.type_checkers.Checkers import chk_address_mac, \ + chk_address_ipv4, chk_address_ipv6 from device.service.drivers.p4.p4_driver import P4Driver from device.service.drivers.p4.p4_common import ( - matches_mac, encode_mac, decode_mac, encode, - matches_ipv4, encode_ipv4, decode_ipv4, - matches_ipv6, encode_ipv6, decode_ipv6, + encode_mac, decode_mac, encode, + encode_ipv4, decode_ipv4, + encode_ipv6, decode_ipv6, encode_num, decode_num ) from .device_p4 import( @@ -172,10 +174,10 @@ def test_p4_common_mac(): :return: void """ wrong_mac = "aa:bb:cc:dd:ee" - assert not matches_mac(wrong_mac) + assert not chk_address_mac(wrong_mac) mac = "aa:bb:cc:dd:ee:fe" - assert matches_mac(mac) + assert chk_address_mac(mac) enc_mac = encode_mac(mac) assert enc_mac == b'\xaa\xbb\xcc\xdd\xee\xfe',\ "String-based MAC address to bytes failed" @@ -193,13 +195,13 @@ def test_p4_common_ipv4(): :return: void """ - assert not matches_ipv4("10.0.0.1.5") - assert not matches_ipv4("256.0.0.1") - assert not matches_ipv4("256.0.1") - assert not matches_ipv4("10001") + assert not chk_address_ipv4("10.0.0.1.5") + assert not chk_address_ipv4("256.0.0.1") + assert not chk_address_ipv4("256.0.1") + assert not chk_address_ipv4("10001") ipv4 = "10.0.0.1" - assert matches_ipv4(ipv4) + assert chk_address_ipv4(ipv4) enc_ipv4 = encode_ipv4(ipv4) assert enc_ipv4 == b'\x0a\x00\x00\x01',\ "String-based IPv4 address to bytes failed" @@ -214,11 +216,11 @@ def test_p4_common_ipv6(): :return: void """ - assert not matches_ipv6('10.0.0.1') - assert matches_ipv6('2001:0000:85a3::8a2e:370:1111') + assert not chk_address_ipv6('10.0.0.1') + assert chk_address_ipv6('2001:0000:85a3::8a2e:370:1111') ipv6 = "1:2:3:4:5:6:7:8" - assert matches_ipv6(ipv6) + assert chk_address_ipv6(ipv6) enc_ipv6 = encode_ipv6(ipv6) assert enc_ipv6 == \ b'\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08',\ diff --git a/src/service/service/service_handler_api/FilterFields.py b/src/service/service/service_handler_api/FilterFields.py index 34f5ce59a..170f34a61 100644 --- a/src/service/service/service_handler_api/FilterFields.py +++ b/src/service/service/service_handler_api/FilterFields.py @@ -23,11 +23,14 @@ SERVICE_TYPE_VALUES = { ServiceTypeEnum.SERVICETYPE_UNKNOWN, ServiceTypeEnum.SERVICETYPE_L3NM, ServiceTypeEnum.SERVICETYPE_L2NM, + ServiceTypeEnum.SERVICETYPE_L1NM, ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, ServiceTypeEnum.SERVICETYPE_TE, ServiceTypeEnum.SERVICETYPE_E2E, ServiceTypeEnum.SERVICETYPE_OPTICAL_CONNECTIVITY, ServiceTypeEnum.SERVICETYPE_QKD, + ServiceTypeEnum.SERVICETYPE_INT, + ServiceTypeEnum.SERVICETYPE_ACL, } DEVICE_DRIVER_VALUES = { diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py index f63866d9d..933725aa5 100644 --- a/src/service/service/service_handlers/__init__.py +++ b/src/service/service/service_handlers/__init__.py @@ -25,7 +25,7 @@ from .l3nm_ietf_actn.L3NMIetfActnServiceHandler import L3NMIetfActnServiceHandle from .l3nm_nce.L3NMNCEServiceHandler import L3NMNCEServiceHandler from .l3slice_ietfslice.L3SliceIETFSliceServiceHandler import L3NMSliceIETFSliceServiceHandler from .microwave.MicrowaveServiceHandler import MicrowaveServiceHandler -from .p4.p4_service_handler import P4ServiceHandler +from .p4_l1.p4_l1_service_handler import P4L1ServiceHandler from .tapi_tapi.TapiServiceHandler import TapiServiceHandler from .tapi_xr.TapiXrServiceHandler import TapiXrServiceHandler from .optical_tfs.OpticalTfsServiceHandler import OpticalTfsServiceHandler @@ -105,9 +105,9 @@ SERVICE_HANDLERS = [ FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY, DeviceDriverEnum.DEVICEDRIVER_ONF_TR_532], } ]), - (P4ServiceHandler, [ + (P4L1ServiceHandler, [ { - FilterFieldEnum.SERVICE_TYPE: ServiceTypeEnum.SERVICETYPE_L2NM, + FilterFieldEnum.SERVICE_TYPE: ServiceTypeEnum.SERVICETYPE_L1NM, FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4, } ]), diff --git a/src/service/service/service_handlers/p4/__init__.py b/src/service/service/service_handlers/p4_l1/__init__.py similarity index 100% rename from src/service/service/service_handlers/p4/__init__.py rename to src/service/service/service_handlers/p4_l1/__init__.py diff --git a/src/service/service/service_handlers/p4/p4_service_handler.py b/src/service/service/service_handlers/p4_l1/p4_l1_service_handler.py similarity index 95% rename from src/service/service/service_handlers/p4/p4_service_handler.py rename to src/service/service/service_handlers/p4_l1/p4_l1_service_handler.py index 49bedbb22..b1ff9c600 100644 --- a/src/service/service/service_handlers/p4/p4_service_handler.py +++ b/src/service/service/service_handlers/p4_l1/p4_l1_service_handler.py @@ -13,7 +13,8 @@ # limitations under the License. """ -P4 service handler for the TeraFlowSDN controller. +P4 service handler for L1 connectivity services +(in-port to out-port or input endpoint to output endpoint). """ import logging @@ -28,7 +29,7 @@ from service.service.task_scheduler.TaskExecutor import TaskExecutor LOGGER = logging.getLogger(__name__) -METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'p4'}) +METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'p4_l1'}) def create_rule_set(endpoint_a, endpoint_b): return json_config_rule_set( @@ -79,14 +80,13 @@ def find_names(uuid_a, uuid_b, device_endpoints): endpoint_a = endpoint.name elif endpoint.endpoint_id.endpoint_uuid.uuid == uuid_b: endpoint_b = endpoint.name - + return (endpoint_a, endpoint_b) -class P4ServiceHandler(_ServiceHandler): - def __init__(self, - service: Service, - task_executor : TaskExecutor, - **settings) -> None: +class P4L1ServiceHandler(_ServiceHandler): + def __init__( # pylint: disable=super-init-not-called + self, service : Service, task_executor : TaskExecutor, **settings + ) -> None: """ Initialize Driver. Parameters: service @@ -106,7 +106,7 @@ class P4ServiceHandler(_ServiceHandler): self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None ) -> List[Union[bool, Exception]]: - """ Create/Update service endpoints form a list. + """ Create/Update service endpoints from a list. Parameters: endpoints: List[Tuple[str, str, Optional[str]]] List of tuples, each containing a device_uuid, @@ -126,21 +126,21 @@ class P4ServiceHandler(_ServiceHandler): if len(endpoints) == 0: return [] service_uuid = self.__service.service_id.service_uuid.uuid + LOGGER.info("SetEndpoint - Service {}".format(service_uuid)) history = {} - results = [] index = {} i = 0 - for endpoint in endpoints: + for endpoint in endpoints: device_uuid, endpoint_uuid = endpoint[0:2] # ignore topology_uuid by now - if device_uuid in history: + if device_uuid in history: try: matched_endpoint_uuid = history.pop(device_uuid) device = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) del device.device_config.config_rules[:] - + # Find names from uuids (endpoint_a, endpoint_b) = find_names(matched_endpoint_uuid, endpoint_uuid, device.device_endpoints) if endpoint_a is None: @@ -151,14 +151,14 @@ class P4ServiceHandler(_ServiceHandler): raise Exception('Unable to find name of endpoint({:s})'.format(str(endpoint_uuid))) # One way - rule = create_rule_set(endpoint_a, endpoint_b) + rule = create_rule_set(endpoint_a, endpoint_b) device.device_config.config_rules.append(ConfigRule(**rule)) # The other way - rule = create_rule_set(endpoint_b, endpoint_a) + rule = create_rule_set(endpoint_b, endpoint_a) device.device_config.config_rules.append(ConfigRule(**rule)) self.__task_executor.configure_device(device) - + results.append(True) results[index[device_uuid]] = True except Exception as e: @@ -177,7 +177,7 @@ class P4ServiceHandler(_ServiceHandler): self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None ) -> List[Union[bool, Exception]]: - """ Delete service endpoints form a list. + """ Delete service endpoints from a list. Parameters: endpoints: List[Tuple[str, str, Optional[str]]] List of tuples, each containing a device_uuid, @@ -197,15 +197,15 @@ class P4ServiceHandler(_ServiceHandler): if len(endpoints) == 0: return [] service_uuid = self.__service.service_id.service_uuid.uuid + LOGGER.info("DeleteEndpoint - Service {}".format(service_uuid)) history = {} - results = [] index = {} i = 0 - for endpoint in endpoints: + for endpoint in endpoints: device_uuid, endpoint_uuid = endpoint[0:2] # ignore topology_uuid by now - if device_uuid in history: + if device_uuid in history: try: matched_endpoint_uuid = history.pop(device_uuid) device = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) @@ -222,14 +222,14 @@ class P4ServiceHandler(_ServiceHandler): raise Exception('Unable to find name of endpoint({:s})'.format(str(endpoint_uuid))) # One way - rule = create_rule_del(endpoint_a, endpoint_b) + rule = create_rule_del(endpoint_a, endpoint_b) device.device_config.config_rules.append(ConfigRule(**rule)) # The other way - rule = create_rule_del(endpoint_b, endpoint_a) + rule = create_rule_del(endpoint_b, endpoint_a) device.device_config.config_rules.append(ConfigRule(**rule)) self.__task_executor.configure_device(device) - + results.append(True) results[index[device_uuid]] = True except Exception as e: @@ -338,4 +338,4 @@ class P4ServiceHandler(_ServiceHandler): msg = '[SetConfig] Method not implemented. Resources({:s}) are being ignored.' LOGGER.warning(msg.format(str(resources))) - return [True for _ in range(len(resources))] \ No newline at end of file + return [True for _ in range(len(resources))] diff --git a/src/tests/p4-int-routing-acl/descriptors/rules-insert-acl.json b/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-acl.json similarity index 100% rename from src/tests/p4-int-routing-acl/descriptors/rules-insert-acl.json rename to src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-acl.json diff --git a/src/tests/p4-int-routing-acl/descriptors/rules-insert-int-b1.json b/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b1.json similarity index 100% rename from src/tests/p4-int-routing-acl/descriptors/rules-insert-int-b1.json rename to src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b1.json diff --git a/src/tests/p4-int-routing-acl/descriptors/rules-insert-int-b2.json b/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b2.json similarity index 100% rename from src/tests/p4-int-routing-acl/descriptors/rules-insert-int-b2.json rename to src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b2.json diff --git a/src/tests/p4-int-routing-acl/descriptors/rules-insert-int-b3.json b/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b3.json similarity index 100% rename from src/tests/p4-int-routing-acl/descriptors/rules-insert-int-b3.json rename to src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b3.json diff --git a/src/tests/p4-int-routing-acl/descriptors/rules-insert-routing-corp.json b/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-routing-corp.json similarity index 100% rename from src/tests/p4-int-routing-acl/descriptors/rules-insert-routing-corp.json rename to src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-routing-corp.json diff --git a/src/tests/p4-int-routing-acl/descriptors/rules-insert-routing-edge.json b/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-routing-edge.json similarity index 100% rename from src/tests/p4-int-routing-acl/descriptors/rules-insert-routing-edge.json rename to src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-routing-edge.json diff --git a/src/tests/p4-int-routing-acl/descriptors/rules-remove.json b/src/tests/p4-int-routing-acl/descriptors/sbi-rules-remove.json similarity index 100% rename from src/tests/p4-int-routing-acl/descriptors/rules-remove.json rename to src/tests/p4-int-routing-acl/descriptors/sbi-rules-remove.json diff --git a/src/tests/p4-int-routing-acl/descriptors/topology.json b/src/tests/p4-int-routing-acl/descriptors/topology.json index 3b1f6e410..30e1f5e9a 100644 --- a/src/tests/p4-int-routing-acl/descriptors/topology.json +++ b/src/tests/p4-int-routing-acl/descriptors/topology.json @@ -1,287 +1,113 @@ { "contexts": [ - { - "context_id": { - "context_uuid": { - "uuid": "admin" - } - } - } + {"context_id": {"context_uuid": {"uuid": "admin"}}} ], "topologies": [ - { - "topology_id": { - "context_id": { - "context_uuid": { - "uuid": "admin" - } - }, - "topology_uuid": { - "uuid": "admin" - } - } + {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} } ], "devices": [ { - "device_id": { - "device_uuid": { - "uuid": "edge-net" - } - }, + "device_id": {"device_uuid": {"uuid": "tfs-sdn-controller"}}, + "name": "tfs-sdn-controller", + "device_type": "teraflowsdn", + "device_drivers": ["DEVICEDRIVER_IETF_L3VPN"], + "device_operational_status": "DEVICEOPERATIONALSTATUS_UNDEFINED", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.10.10.41"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8002"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": { + "endpoints": [{"uuid": "mgmt", "name": "mgmt", "type": "mgmt-int"}], + "scheme": "http", "username": "admin", "password": "admin", "import_topology": "topology" + }}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "edge-net"}}, "device_type": "network", - "device_drivers": [ - "DEVICEDRIVER_UNDEFINED" - ], + "device_drivers": ["DEVICEDRIVER_UNDEFINED"], "device_config": { "config_rules": [ - { - "action": "CONFIGACTION_SET", - "custom": { - "resource_key": "_connect/address", - "resource_value": "127.0.0.1" - } - }, - { - "action": "CONFIGACTION_SET", - "custom": { - "resource_key": "_connect/port", - "resource_value": "0" - } - }, - { - "action": "CONFIGACTION_SET", - "custom": { - "resource_key": "_connect/settings", - "resource_value": { - "endpoints": [ - { - "uuid": "eth1", - "type": "copper" - } - ] - } - } - } + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/settings", "resource_value": { + "endpoints": [{"uuid": "eth1", "name": "eth1", "type": "copper"}] + }}} ] } }, { - "device_id": { - "device_uuid": { - "uuid": "corporate-net" - } - }, + "device_id": {"device_uuid": {"uuid": "corporate-net"}}, "device_type": "network", - "device_drivers": [ - "DEVICEDRIVER_UNDEFINED" - ], + "device_drivers": ["DEVICEDRIVER_UNDEFINED"], "device_config": { "config_rules": [ - { - "action": "CONFIGACTION_SET", - "custom": { - "resource_key": "_connect/address", - "resource_value": "127.0.0.1" - } - }, - { - "action": "CONFIGACTION_SET", - "custom": { - "resource_key": "_connect/port", - "resource_value": "0" - } - }, - { - "action": "CONFIGACTION_SET", - "custom": { - "resource_key": "_connect/settings", - "resource_value": { - "endpoints": [ - { - "uuid": "eth1", - "type": "copper" - } - ] - } - } - } + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/settings", "resource_value": { + "endpoints": [{"uuid": "eth1", "name": "eth1", "type": "copper"}] + }}} ] } }, { - "device_id": { - "device_uuid": { - "uuid": "p4-sw1" - } - }, + "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, "device_type": "p4-switch", - "device_drivers": [ - "DEVICEDRIVER_P4" - ], + "device_drivers": ["DEVICEDRIVER_P4"], "device_operational_status": "DEVICEOPERATIONALSTATUS_DISABLED", "name": "p4-sw1", "device_config": { "config_rules": [ - { - "action": "CONFIGACTION_SET", - "custom": { - "resource_key": "_connect/address", - "resource_value": "10.10.10.120" - } - }, - { - "action": "CONFIGACTION_SET", - "custom": { - "resource_key": "_connect/port", - "resource_value": "50001" - } - }, - { - "action": "CONFIGACTION_SET", - "custom": { - "resource_key": "_connect/settings", - "resource_value": { - "id": 1, - "name": "p4-sw1", - "vendor": "Open Networking Foundation", - "hw_ver": "BMv2 simple_switch", - "sw_ver": "Stratum", - "p4bin": "/root/p4/bmv2.json", - "p4info": "/root/p4/p4info.txt", - "timeout": 60, - "endpoints": [ - { - "uuid": "1", - "type": "port" - }, - { - "uuid": "2", - "type": "port" - } - ] - } - } - } + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/address", "resource_value": "10.10.10.120"}}, + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/port", "resource_value": "50001"}}, + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/settings", "resource_value": { + "id": 1, + "name": "p4-sw1", + "vendor": "Open Networking Foundation", + "hw_ver": "BMv2 simple_switch", + "sw_ver": "Stratum", + "timeout": 60, + "p4bin": "/root/p4/bmv2.json", + "p4info": "/root/p4/p4info.txt", + "endpoints": [ + {"uuid": "1", "name": "1", "type": "port-dataplane"}, + {"uuid": "2", "name": "2", "type": "port-dataplane"}, + {"uuid": "3", "name": "3", "type": "port-int"} + ] + }}} ] } } ], "links": [ { - "link_id": { - "link_uuid": { - "uuid": "p4-sw1/1==edge-net/eth1" - } - }, - "link_endpoint_ids": [ - { - "device_id": { - "device_uuid": { - "uuid": "p4-sw1" - } - }, - "endpoint_uuid": { - "uuid": "1" - } - }, - { - "device_id": { - "device_uuid": { - "uuid": "edge-net" - } - }, - "endpoint_uuid": { - "uuid": "eth1" - } - } + "link_id": {"link_uuid": {"uuid": "p4-sw1/1==edge-net/eth1"}}, "link_type": "LINKTYPE_VIRTUAL", "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "p4-sw1"}}, "endpoint_uuid": {"uuid": "1"}}, + {"device_id": {"device_uuid": {"uuid": "edge-net"}}, "endpoint_uuid": {"uuid": "eth1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "edge-net/eth1==p4-sw1/1"}}, "link_type": "LINKTYPE_VIRTUAL", "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "edge-net"}}, "endpoint_uuid": {"uuid": "eth1"}}, + {"device_id": {"device_uuid": {"uuid": "p4-sw1"}}, "endpoint_uuid": {"uuid": "1"}} ] }, { - "link_id": { - "link_uuid": { - "uuid": "edge-net/eth1==p4-sw1/1" - } - }, - "link_endpoint_ids": [ - { - "device_id": { - "device_uuid": { - "uuid": "edge-net" - } - }, - "endpoint_uuid": { - "uuid": "eth1" - } - }, - { - "device_id": { - "device_uuid": { - "uuid": "p4-sw1" - } - }, - "endpoint_uuid": { - "uuid": "1" - } - } + "link_id": {"link_uuid": {"uuid": "p4-sw1/2==corporate-net/eth1"}}, "link_type": "LINKTYPE_VIRTUAL", "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "p4-sw1"}}, "endpoint_uuid": {"uuid": "2"}}, + {"device_id": {"device_uuid": {"uuid": "corporate-net"}}, "endpoint_uuid": {"uuid": "eth1"}} ] }, { - "link_id": { - "link_uuid": { - "uuid": "p4-sw1/2==corporate-net/eth1" - } - }, - "link_endpoint_ids": [ - { - "device_id": { - "device_uuid": { - "uuid": "p4-sw1" - } - }, - "endpoint_uuid": { - "uuid": "2" - } - }, - { - "device_id": { - "device_uuid": { - "uuid": "corporate-net" - } - }, - "endpoint_uuid": { - "uuid": "eth1" - } - } + "link_id": {"link_uuid": {"uuid": "corporate-net/eth1==p4-sw1/2"}}, "link_type": "LINKTYPE_VIRTUAL", "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "corporate-net"}}, "endpoint_uuid": {"uuid": "eth1"}}, + {"device_id": {"device_uuid": {"uuid": "p4-sw1"}}, "endpoint_uuid": {"uuid": "2"}} ] }, { - "link_id": { - "link_uuid": { - "uuid": "corporate-net/eth1==p4-sw1/2" - } - }, - "link_endpoint_ids": [ - { - "device_id": { - "device_uuid": { - "uuid": "corporate-net" - } - }, - "endpoint_uuid": { - "uuid": "eth1" - } - }, - { - "device_id": { - "device_uuid": { - "uuid": "p4-sw1" - } - }, - "endpoint_uuid": { - "uuid": "2" - } - } + "link_id": {"link_uuid": {"uuid": "p4-sw1/3==tfs-sdn-controller/mgmt"}}, "link_type": "LINKTYPE_MANAGEMENT", "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "p4-sw1"}}, "endpoint_uuid": {"uuid": "3"}}, + {"device_id": {"device_uuid": {"uuid": "tfs-sdn-controller"}}, "endpoint_uuid": {"uuid": "mgmt"}} ] } ] diff --git a/src/tests/p4-int-routing-acl/run_test_02_rules_provision.sh b/src/tests/p4-int-routing-acl/run_test_02_sbi_rules_provision.sh similarity index 95% rename from src/tests/p4-int-routing-acl/run_test_02_rules_provision.sh rename to src/tests/p4-int-routing-acl/run_test_02_sbi_rules_provision.sh index 6709d66c6..7c485d401 100755 --- a/src/tests/p4-int-routing-acl/run_test_02_rules_provision.sh +++ b/src/tests/p4-int-routing-acl/run_test_02_sbi_rules_provision.sh @@ -14,4 +14,4 @@ # limitations under the License. source tfs_runtime_env_vars.sh -python3 -m pytest --verbose src/tests/p4-int-routing-acl/test_functional_rules_provision.py +python3 -m pytest --verbose src/tests/p4-int-routing-acl/test_functional_sbi_rules_provision.py diff --git a/src/tests/p4-int-routing-acl/run_test_03_rules_deprovision.sh b/src/tests/p4-int-routing-acl/run_test_03_sbi_rules_deprovision.sh similarity index 95% rename from src/tests/p4-int-routing-acl/run_test_03_rules_deprovision.sh rename to src/tests/p4-int-routing-acl/run_test_03_sbi_rules_deprovision.sh index 3a67fad8a..4032c01df 100755 --- a/src/tests/p4-int-routing-acl/run_test_03_rules_deprovision.sh +++ b/src/tests/p4-int-routing-acl/run_test_03_sbi_rules_deprovision.sh @@ -14,4 +14,4 @@ # limitations under the License. source tfs_runtime_env_vars.sh -python3 -m pytest --verbose src/tests/p4-int-routing-acl/test_functional_rules_deprovision.py +python3 -m pytest --verbose src/tests/p4-int-routing-acl/test_functional_sbi_rules_deprovision.py diff --git a/src/tests/p4-int-routing-acl/run_test_04_cleanup.sh b/src/tests/p4-int-routing-acl/run_test_06_cleanup.sh similarity index 100% rename from src/tests/p4-int-routing-acl/run_test_04_cleanup.sh rename to src/tests/p4-int-routing-acl/run_test_06_cleanup.sh diff --git a/src/tests/p4-int-routing-acl/test_common.py b/src/tests/p4-int-routing-acl/test_common.py index 8254eddc5..f2d00f1dc 100644 --- a/src/tests/p4-int-routing-acl/test_common.py +++ b/src/tests/p4-int-routing-acl/test_common.py @@ -22,9 +22,9 @@ CONTEXT_NAME_P4 = DEFAULT_CONTEXT_NAME ADMIN_CONTEXT_ID = ContextId(**json_context_id(CONTEXT_NAME_P4)) # Device and rule cardinality variables -DEV_NB = 3 +DEV_NB = 4 CONNECTION_RULES = 3 -ENDPOINT_RULES = 2 +ENDPOINT_RULES = 3 DATAPLANE_RULES_NB_INT_B1 = 5 DATAPLANE_RULES_NB_INT_B2 = 6 DATAPLANE_RULES_NB_INT_B3 = 8 @@ -39,6 +39,11 @@ DATAPLANE_RULES_NB_TOT = \ DATAPLANE_RULES_NB_RT_CORP +\ DATAPLANE_RULES_NB_ACL +# Service-related variables +SVC_NB = 1 +NO_SERVICES = 0 +NO_SLICES = 0 + # Topology descriptor DESC_TOPO = os.path.join( os.path.dirname( @@ -47,51 +52,51 @@ DESC_TOPO = os.path.join( 'descriptors', 'topology.json' ) -# Rule insertion descriptors +# SBI rule insertion descriptors # The switch cannot digest all rules at once, hence we insert in batches DESC_FILE_RULES_INSERT_INT_B1 = os.path.join( os.path.dirname( os.path.abspath(__file__) ), - 'descriptors', 'rules-insert-int-b1.json' + 'descriptors', 'sbi-rules-insert-int-b1.json' ) DESC_FILE_RULES_INSERT_INT_B2 = os.path.join( os.path.dirname( os.path.abspath(__file__) ), - 'descriptors', 'rules-insert-int-b2.json' + 'descriptors', 'sbi-rules-insert-int-b2.json' ) DESC_FILE_RULES_INSERT_INT_B3 = os.path.join( os.path.dirname( os.path.abspath(__file__) ), - 'descriptors', 'rules-insert-int-b3.json' + 'descriptors', 'sbi-rules-insert-int-b3.json' ) DESC_FILE_RULES_INSERT_ROUTING_EDGE = os.path.join( os.path.dirname( os.path.abspath(__file__) ), - 'descriptors', 'rules-insert-routing-edge.json' + 'descriptors', 'sbi-rules-insert-routing-edge.json' ) DESC_FILE_RULES_INSERT_ROUTING_CORP = os.path.join( os.path.dirname( os.path.abspath(__file__) ), - 'descriptors', 'rules-insert-routing-corp.json' + 'descriptors', 'sbi-rules-insert-routing-corp.json' ) DESC_FILE_RULES_INSERT_ACL = os.path.join( os.path.dirname( os.path.abspath(__file__) ), - 'descriptors', 'rules-insert-acl.json' + 'descriptors', 'sbi-rules-insert-acl.json' ) -# Rule deletion descriptor +# SBI rule deletion descriptor DESC_FILE_RULES_DELETE_ALL = os.path.join( os.path.dirname( os.path.abspath(__file__) ), - 'descriptors', 'rules-remove.json' + 'descriptors', 'sbi-rules-remove.json' ) def verify_number_of_rules(devices, desired_rules_nb): diff --git a/src/tests/p4-int-routing-acl/test_functional_cleanup.py b/src/tests/p4-int-routing-acl/test_functional_cleanup.py index 60c8684b0..fc29be24d 100644 --- a/src/tests/p4-int-routing-acl/test_functional_cleanup.py +++ b/src/tests/p4-int-routing-acl/test_functional_cleanup.py @@ -14,7 +14,6 @@ import logging, os from common.Constants import DEFAULT_CONTEXT_NAME -from common.proto.context_pb2 import ContextId from common.tools.descriptor.Loader import DescriptorLoader, validate_empty_scenario from context.client.ContextClient import ContextClient from device.client.DeviceClient import DeviceClient diff --git a/src/tests/p4-int-routing-acl/test_functional_rules_deprovision.py b/src/tests/p4-int-routing-acl/test_functional_sbi_rules_deprovision.py similarity index 100% rename from src/tests/p4-int-routing-acl/test_functional_rules_deprovision.py rename to src/tests/p4-int-routing-acl/test_functional_sbi_rules_deprovision.py diff --git a/src/tests/p4-int-routing-acl/test_functional_rules_provision.py b/src/tests/p4-int-routing-acl/test_functional_sbi_rules_provision.py similarity index 100% rename from src/tests/p4-int-routing-acl/test_functional_rules_provision.py rename to src/tests/p4-int-routing-acl/test_functional_sbi_rules_provision.py diff --git a/src/webui/service/static/topology_icons/p4-switch.png b/src/webui/service/static/topology_icons/p4-switch.png index 9afcda1c0e38cb5757574024f1a3f96001b03943..178943d67e0b0a1e501d27b32a5356d8afdd4e56 100644 GIT binary patch literal 244738 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�+KjfP(J4X%c>gU`4n2Eq}iBa_| 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�SH*0*W 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 Rln8z>aw<3RH%$Ki{{Z3|?F|3` literal 6288 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 -- GitLab From 1338291b933e7851f9b6ef88ac9b6adf522f9f88 Mon Sep 17 00:00:00 2001 From: "Georgios P. Katsikas" <gkatsikas@ubitech.eu> Date: Sat, 22 Mar 2025 08:09:29 +0000 Subject: [PATCH 02/14] fix: refactoring of p4 fabric tna testing suite --- .../service/service_handlers/__init__.py | 4 +- .../{p4_l1 => p4_dummy_l1}/__init__.py | 0 .../p4_dummy_l1_service_handler.py} | 4 +- .../README.md | 40 ++-- .../__init__.py | 0 .../descriptors/sbi-rules-insert-acl.json | 0 .../descriptors/sbi-rules-insert-int-b1.json | 0 .../descriptors/sbi-rules-insert-int-b2.json | 0 .../descriptors/sbi-rules-insert-int-b3.json | 0 .../sbi-rules-insert-routing-corp.json | 0 .../sbi-rules-insert-routing-edge.json | 0 .../descriptors/sbi-rules-remove.json | 0 .../descriptors/topology.json | 0 .../p4src/README.md | 0 .../p4src/_pp.p4 | 0 .../p4src/bmv2.json | 0 .../p4src/p4info.txt | 0 .../run_test_01_bootstrap.sh | 2 +- ...n_test_02a_sbi_provision_int_l2_l3_acl.sh} | 2 +- ..._test_02b_sbi_deprovision_int_l2_l3_acl.sh | 17 ++ .../run_test_07_cleanup.sh} | 2 +- .../run_test_08_purge.sh} | 2 +- .../setup.sh | 4 +- .../test_functional_sbi_rules_deprovision.py | 2 +- .../test_functional_sbi_rules_provision.py | 2 +- .../tests-setup}/test_functional_bootstrap.py | 2 +- .../tests-setup}/test_functional_cleanup.py | 5 +- .../tests-setup/test_functional_purge.py | 81 +++++++++ .../topology/README.md | 0 .../topology/p4-switch-conf-common.sh | 0 .../topology/p4-switch-setup.sh | 0 .../topology/p4-switch-tear-down.sh | 0 ...witch-three-port-chassis-config-phy.pb.txt | 0 .../topology/run-stratum.sh | 0 src/tests/p4-int-routing-acl/test_common.py | 116 ------------ src/tests/tools/test_tools_p4.py | 172 ++++++++++++++++++ 36 files changed, 309 insertions(+), 148 deletions(-) rename src/service/service/service_handlers/{p4_l1 => p4_dummy_l1}/__init__.py (100%) rename src/service/service/service_handlers/{p4_l1/p4_l1_service_handler.py => p4_dummy_l1/p4_dummy_l1_service_handler.py} (99%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/README.md (73%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/__init__.py (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/descriptors/sbi-rules-insert-acl.json (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/descriptors/sbi-rules-insert-int-b1.json (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/descriptors/sbi-rules-insert-int-b2.json (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/descriptors/sbi-rules-insert-int-b3.json (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/descriptors/sbi-rules-insert-routing-corp.json (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/descriptors/sbi-rules-insert-routing-edge.json (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/descriptors/sbi-rules-remove.json (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/descriptors/topology.json (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/p4src/README.md (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/p4src/_pp.p4 (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/p4src/bmv2.json (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/p4src/p4info.txt (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/run_test_01_bootstrap.sh (89%) rename src/tests/{p4-int-routing-acl/run_test_03_sbi_rules_deprovision.sh => p4-fabric-tna/run_test_02a_sbi_provision_int_l2_l3_acl.sh} (86%) create mode 100755 src/tests/p4-fabric-tna/run_test_02b_sbi_deprovision_int_l2_l3_acl.sh rename src/tests/{p4-int-routing-acl/run_test_02_sbi_rules_provision.sh => p4-fabric-tna/run_test_07_cleanup.sh} (87%) rename src/tests/{p4-int-routing-acl/run_test_06_cleanup.sh => p4-fabric-tna/run_test_08_purge.sh} (88%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/setup.sh (80%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna/tests-sbi}/test_functional_sbi_rules_deprovision.py (99%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna/tests-sbi}/test_functional_sbi_rules_provision.py (99%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna/tests-setup}/test_functional_bootstrap.py (98%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna/tests-setup}/test_functional_cleanup.py (94%) create mode 100644 src/tests/p4-fabric-tna/tests-setup/test_functional_purge.py rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/topology/README.md (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/topology/p4-switch-conf-common.sh (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/topology/p4-switch-setup.sh (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/topology/p4-switch-tear-down.sh (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/topology/p4-switch-three-port-chassis-config-phy.pb.txt (100%) rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/topology/run-stratum.sh (100%) delete mode 100644 src/tests/p4-int-routing-acl/test_common.py create mode 100644 src/tests/tools/test_tools_p4.py diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py index 933725aa5..e03c1172e 100644 --- a/src/service/service/service_handlers/__init__.py +++ b/src/service/service/service_handlers/__init__.py @@ -25,7 +25,7 @@ from .l3nm_ietf_actn.L3NMIetfActnServiceHandler import L3NMIetfActnServiceHandle from .l3nm_nce.L3NMNCEServiceHandler import L3NMNCEServiceHandler from .l3slice_ietfslice.L3SliceIETFSliceServiceHandler import L3NMSliceIETFSliceServiceHandler from .microwave.MicrowaveServiceHandler import MicrowaveServiceHandler -from .p4_l1.p4_l1_service_handler import P4L1ServiceHandler +from .p4_dummy_l1.p4_dummy_l1_service_handler import P4DummyL1ServiceHandler from .tapi_tapi.TapiServiceHandler import TapiServiceHandler from .tapi_xr.TapiXrServiceHandler import TapiXrServiceHandler from .optical_tfs.OpticalTfsServiceHandler import OpticalTfsServiceHandler @@ -105,7 +105,7 @@ SERVICE_HANDLERS = [ FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY, DeviceDriverEnum.DEVICEDRIVER_ONF_TR_532], } ]), - (P4L1ServiceHandler, [ + (P4DummyL1ServiceHandler, [ { FilterFieldEnum.SERVICE_TYPE: ServiceTypeEnum.SERVICETYPE_L1NM, FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4, diff --git a/src/service/service/service_handlers/p4_l1/__init__.py b/src/service/service/service_handlers/p4_dummy_l1/__init__.py similarity index 100% rename from src/service/service/service_handlers/p4_l1/__init__.py rename to src/service/service/service_handlers/p4_dummy_l1/__init__.py diff --git a/src/service/service/service_handlers/p4_l1/p4_l1_service_handler.py b/src/service/service/service_handlers/p4_dummy_l1/p4_dummy_l1_service_handler.py similarity index 99% rename from src/service/service/service_handlers/p4_l1/p4_l1_service_handler.py rename to src/service/service/service_handlers/p4_dummy_l1/p4_dummy_l1_service_handler.py index b1ff9c600..6e9141caf 100644 --- a/src/service/service/service_handlers/p4_l1/p4_l1_service_handler.py +++ b/src/service/service/service_handlers/p4_dummy_l1/p4_dummy_l1_service_handler.py @@ -29,7 +29,7 @@ from service.service.task_scheduler.TaskExecutor import TaskExecutor LOGGER = logging.getLogger(__name__) -METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'p4_l1'}) +METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'p4_dummy_l1'}) def create_rule_set(endpoint_a, endpoint_b): return json_config_rule_set( @@ -83,7 +83,7 @@ def find_names(uuid_a, uuid_b, device_endpoints): return (endpoint_a, endpoint_b) -class P4L1ServiceHandler(_ServiceHandler): +class P4DummyL1ServiceHandler(_ServiceHandler): def __init__( # pylint: disable=super-init-not-called self, service : Service, task_executor : TaskExecutor, **settings ) -> None: diff --git a/src/tests/p4-int-routing-acl/README.md b/src/tests/p4-fabric-tna/README.md similarity index 73% rename from src/tests/p4-int-routing-acl/README.md rename to src/tests/p4-fabric-tna/README.md index fa935e1b2..fc49276a9 100644 --- a/src/tests/p4-int-routing-acl/README.md +++ b/src/tests/p4-fabric-tna/README.md @@ -1,6 +1,7 @@ # Tests for P4 routing, ACL, and In-Band Network Telemetry functions -This directory contains the necessary scripts and configurations to run tests atop a Stratum-based P4 whitebox that performs a set of network functions, including routing, access control list (ACL), and In-Band Network Telemetry (INT). +This directory contains the necessary scripts and configurations to run tests atop a Stratum-based P4 whitebox that performs a set of network functions, including forwarding (L2), routing (L3), L3/L4 access control list (ACL), and In-Band Network Telemetry (INT). +The P4 data plane is based on ONF's SD-Fabric implementation, titled [fabric-tna](https://github.com/stratum/fabric-tna) ## Prerequisites @@ -16,6 +17,7 @@ pip3 install grpcio-tools ``` The versions used for this test are: + - `protobuf` 3.20.3 - `grpclib` 0.4.4 - `grpcio` 1.47.5 @@ -29,11 +31,11 @@ First we copy it to /usr/local/bin/: ``` Then, we include this path to the PYTHONPATH: + ```shell export PYTHONPATH="${PYTHONPATH}:/usr/local/bin/protoc-gen-grpclib_python" ``` - You need to build and deploy a software-based Stratum switch, before being able to use TFS to control it. To do so, follow the instructions in the `./topology` folder. @@ -41,7 +43,7 @@ To do so, follow the instructions in the `./topology` folder. To conduct this test, follow the steps below. -### TFS re-deploy +### Deploy TFS ```shell cd ~/tfs-ctrl/ @@ -67,7 +69,7 @@ The `./setup.sh` script copies from this directory. If you need to change the P4 ## Tests -The following tests are implemented. +A set of tests is implemented, each focusing on different aspects of TFS. For each of these tests, an auxiliary bash script allows to run it with less typing. | Test | Bash Runner | Purpose | @@ -80,7 +82,7 @@ For each of these tests, an auxiliary bash script allows to run it with less typ Each of the tests above is described in detail below. -### Step 1: Copy the necessary P4 artifacts into the TFS SBI service pod +### Step 0: Copy the necessary P4 artifacts into the TFS SBI service pod The setup script copies the necessary artifacts to the SBI service pod. It should be run just once, after a fresh install of TFS. @@ -89,34 +91,33 @@ If you `deploy/all.sh` again, you need to repeat this step. ```shell cd ~/tfs-ctrl/ source my_deploy.sh && source tfs_runtime_env_vars.sh -bash src/tests/p4-int-routing-acl/setup.sh +bash src/tests/p4-fabric-tna/setup.sh ``` -### Step 2: Bootstrap topology +### Step 1: Bootstrap topology The bootstrap script registers the context, topology, links, and devices to TFS. ```shell cd ~/tfs-ctrl/ -bash src/tests/p4-int-routing-acl/run_test_01_bootstrap.sh +bash src/tests/p4-fabric-tna/run_test_01_bootstrap.sh ``` -### Step 3: Provision rules via the SBI API +### Step 2: Manage L2, L3, ACL, and INT via the SBI API (rules) -Implement routing, ACL, and INT functions by installing P4 rules to the Stratum switch via the TFS SBI API. +Implement forwarding, routing, ACL, and INT network functions by installing P4 rules to the Stratum switch via the TFS SBI API. +In this test, these rules are installed in batches, as the switch cannot digest all these rules at once. ```shell cd ~/tfs-ctrl/ -bash src/tests/p4-int-routing-acl/run_test_02_rules_provision.sh +bash src/tests/p4-fabric-tna/run_test_02a_sbi_provision_int_l2_l3_acl.sh ``` -### Step 4: Deprovision rules via the SBI API - -Deprovision the routing, ACL, and INT network functions by removing the previously installed P4 rules (via the TFS SBI API) from the Stratum switch. +Deprovision forwarding, routing, ACL, and INT network functions by removing the previously installed P4 rules (via the TFS SBI API) from the Stratum switch. ```shell cd ~/tfs-ctrl/ -bash src/tests/p4-int-routing-acl/run_test_03_rules_deprovision.sh +bash src/tests/p4-fabric-tna/run_test_02b_sbi_deprovision_int_l2_l3_acl.sh ``` ### Step 4: Deprovision topology @@ -125,5 +126,12 @@ Delete all the objects (context, topology, links, devices) from TFS: ```shell cd ~/tfs-ctrl/ -bash src/tests/p4-int-routing-acl/run_test_04_cleanup.sh +bash src/tests/p4-fabric-tna/run_test_07_cleanup.sh +``` + +Alternatively, a purge test is implemented; this test removes services, links, devices, topology, and context in this order. + +```shell +cd ~/tfs-ctrl/ +bash src/tests/p4-fabric-tna/run_test_08_purge.sh ``` diff --git a/src/tests/p4-int-routing-acl/__init__.py b/src/tests/p4-fabric-tna/__init__.py similarity index 100% rename from src/tests/p4-int-routing-acl/__init__.py rename to src/tests/p4-fabric-tna/__init__.py diff --git a/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-acl.json b/src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-acl.json similarity index 100% rename from src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-acl.json rename to src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-acl.json diff --git a/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b1.json b/src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-int-b1.json similarity index 100% rename from src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b1.json rename to src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-int-b1.json diff --git a/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b2.json b/src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-int-b2.json similarity index 100% rename from src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b2.json rename to src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-int-b2.json diff --git a/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b3.json b/src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-int-b3.json similarity index 100% rename from src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b3.json rename to src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-int-b3.json diff --git a/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-routing-corp.json b/src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-routing-corp.json similarity index 100% rename from src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-routing-corp.json rename to src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-routing-corp.json diff --git a/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-routing-edge.json b/src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-routing-edge.json similarity index 100% rename from src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-routing-edge.json rename to src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-routing-edge.json diff --git a/src/tests/p4-int-routing-acl/descriptors/sbi-rules-remove.json b/src/tests/p4-fabric-tna/descriptors/sbi-rules-remove.json similarity index 100% rename from src/tests/p4-int-routing-acl/descriptors/sbi-rules-remove.json rename to src/tests/p4-fabric-tna/descriptors/sbi-rules-remove.json diff --git a/src/tests/p4-int-routing-acl/descriptors/topology.json b/src/tests/p4-fabric-tna/descriptors/topology.json similarity index 100% rename from src/tests/p4-int-routing-acl/descriptors/topology.json rename to src/tests/p4-fabric-tna/descriptors/topology.json diff --git a/src/tests/p4-int-routing-acl/p4src/README.md b/src/tests/p4-fabric-tna/p4src/README.md similarity index 100% rename from src/tests/p4-int-routing-acl/p4src/README.md rename to src/tests/p4-fabric-tna/p4src/README.md diff --git a/src/tests/p4-int-routing-acl/p4src/_pp.p4 b/src/tests/p4-fabric-tna/p4src/_pp.p4 similarity index 100% rename from src/tests/p4-int-routing-acl/p4src/_pp.p4 rename to src/tests/p4-fabric-tna/p4src/_pp.p4 diff --git a/src/tests/p4-int-routing-acl/p4src/bmv2.json b/src/tests/p4-fabric-tna/p4src/bmv2.json similarity index 100% rename from src/tests/p4-int-routing-acl/p4src/bmv2.json rename to src/tests/p4-fabric-tna/p4src/bmv2.json diff --git a/src/tests/p4-int-routing-acl/p4src/p4info.txt b/src/tests/p4-fabric-tna/p4src/p4info.txt similarity index 100% rename from src/tests/p4-int-routing-acl/p4src/p4info.txt rename to src/tests/p4-fabric-tna/p4src/p4info.txt diff --git a/src/tests/p4-int-routing-acl/run_test_01_bootstrap.sh b/src/tests/p4-fabric-tna/run_test_01_bootstrap.sh similarity index 89% rename from src/tests/p4-int-routing-acl/run_test_01_bootstrap.sh rename to src/tests/p4-fabric-tna/run_test_01_bootstrap.sh index 76469ca55..81d87476e 100755 --- a/src/tests/p4-int-routing-acl/run_test_01_bootstrap.sh +++ b/src/tests/p4-fabric-tna/run_test_01_bootstrap.sh @@ -18,4 +18,4 @@ # - tfs_runtime_env_vars.sh source tfs_runtime_env_vars.sh -python3 -m pytest --verbose src/tests/p4-int-routing-acl/test_functional_bootstrap.py +python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-setup/test_functional_bootstrap.py diff --git a/src/tests/p4-int-routing-acl/run_test_03_sbi_rules_deprovision.sh b/src/tests/p4-fabric-tna/run_test_02a_sbi_provision_int_l2_l3_acl.sh similarity index 86% rename from src/tests/p4-int-routing-acl/run_test_03_sbi_rules_deprovision.sh rename to src/tests/p4-fabric-tna/run_test_02a_sbi_provision_int_l2_l3_acl.sh index 4032c01df..a3ab51c22 100755 --- a/src/tests/p4-int-routing-acl/run_test_03_sbi_rules_deprovision.sh +++ b/src/tests/p4-fabric-tna/run_test_02a_sbi_provision_int_l2_l3_acl.sh @@ -14,4 +14,4 @@ # limitations under the License. source tfs_runtime_env_vars.sh -python3 -m pytest --verbose src/tests/p4-int-routing-acl/test_functional_sbi_rules_deprovision.py +python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-sbi/test_functional_sbi_rules_provision.py diff --git a/src/tests/p4-fabric-tna/run_test_02b_sbi_deprovision_int_l2_l3_acl.sh b/src/tests/p4-fabric-tna/run_test_02b_sbi_deprovision_int_l2_l3_acl.sh new file mode 100755 index 000000000..49a686638 --- /dev/null +++ b/src/tests/p4-fabric-tna/run_test_02b_sbi_deprovision_int_l2_l3_acl.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source tfs_runtime_env_vars.sh +python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-sbi/test_functional_sbi_rules_deprovision.py diff --git a/src/tests/p4-int-routing-acl/run_test_02_sbi_rules_provision.sh b/src/tests/p4-fabric-tna/run_test_07_cleanup.sh similarity index 87% rename from src/tests/p4-int-routing-acl/run_test_02_sbi_rules_provision.sh rename to src/tests/p4-fabric-tna/run_test_07_cleanup.sh index 7c485d401..c7b829168 100755 --- a/src/tests/p4-int-routing-acl/run_test_02_sbi_rules_provision.sh +++ b/src/tests/p4-fabric-tna/run_test_07_cleanup.sh @@ -14,4 +14,4 @@ # limitations under the License. source tfs_runtime_env_vars.sh -python3 -m pytest --verbose src/tests/p4-int-routing-acl/test_functional_sbi_rules_provision.py +python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-setup/test_functional_cleanup.py diff --git a/src/tests/p4-int-routing-acl/run_test_06_cleanup.sh b/src/tests/p4-fabric-tna/run_test_08_purge.sh similarity index 88% rename from src/tests/p4-int-routing-acl/run_test_06_cleanup.sh rename to src/tests/p4-fabric-tna/run_test_08_purge.sh index 917a2af2d..9aef079f1 100755 --- a/src/tests/p4-int-routing-acl/run_test_06_cleanup.sh +++ b/src/tests/p4-fabric-tna/run_test_08_purge.sh @@ -14,4 +14,4 @@ # limitations under the License. source tfs_runtime_env_vars.sh -python3 -m pytest --verbose src/tests/p4-int-routing-acl/test_functional_cleanup.py +python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-setup/test_functional_purge.py diff --git a/src/tests/p4-int-routing-acl/setup.sh b/src/tests/p4-fabric-tna/setup.sh similarity index 80% rename from src/tests/p4-int-routing-acl/setup.sh rename to src/tests/p4-fabric-tna/setup.sh index c77164276..9418ac3d7 100755 --- a/src/tests/p4-int-routing-acl/setup.sh +++ b/src/tests/p4-fabric-tna/setup.sh @@ -18,5 +18,5 @@ export POD_NAME=$(kubectl get pods -n=tfs | grep device | awk '{print $1}') kubectl exec ${POD_NAME} -n=tfs -c=server -- mkdir -p /root/p4 -kubectl cp src/tests/p4-int-routing-acl/p4src/p4info.txt tfs/${POD_NAME}:/root/p4 -c=server -kubectl cp src/tests/p4-int-routing-acl/p4src/bmv2.json tfs/${POD_NAME}:/root/p4 -c=server +kubectl cp src/tests/p4-fabric-tna/p4src/p4info.txt tfs/${POD_NAME}:/root/p4 -c=server +kubectl cp src/tests/p4-fabric-tna/p4src/bmv2.json tfs/${POD_NAME}:/root/p4 -c=server diff --git a/src/tests/p4-int-routing-acl/test_functional_sbi_rules_deprovision.py b/src/tests/p4-fabric-tna/tests-sbi/test_functional_sbi_rules_deprovision.py similarity index 99% rename from src/tests/p4-int-routing-acl/test_functional_sbi_rules_deprovision.py rename to src/tests/p4-fabric-tna/tests-sbi/test_functional_sbi_rules_deprovision.py index 2d54ae908..6d5f7dfd2 100644 --- a/src/tests/p4-int-routing-acl/test_functional_sbi_rules_deprovision.py +++ b/src/tests/p4-fabric-tna/tests-sbi/test_functional_sbi_rules_deprovision.py @@ -18,7 +18,7 @@ from common.tools.grpc.Tools import grpc_message_to_json_string from context.client.ContextClient import ContextClient from device.client.DeviceClient import DeviceClient from tests.Fixtures import context_client, device_client # pylint: disable=unused-import -from test_common import * +from tests.tools.test_tools_p4 import * LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) diff --git a/src/tests/p4-int-routing-acl/test_functional_sbi_rules_provision.py b/src/tests/p4-fabric-tna/tests-sbi/test_functional_sbi_rules_provision.py similarity index 99% rename from src/tests/p4-int-routing-acl/test_functional_sbi_rules_provision.py rename to src/tests/p4-fabric-tna/tests-sbi/test_functional_sbi_rules_provision.py index 86a82d212..49d9aba4d 100644 --- a/src/tests/p4-int-routing-acl/test_functional_sbi_rules_provision.py +++ b/src/tests/p4-fabric-tna/tests-sbi/test_functional_sbi_rules_provision.py @@ -18,7 +18,7 @@ from common.tools.grpc.Tools import grpc_message_to_json_string from context.client.ContextClient import ContextClient from device.client.DeviceClient import DeviceClient from tests.Fixtures import context_client, device_client # pylint: disable=unused-import -from test_common import * +from tests.tools.test_tools_p4 import * LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) diff --git a/src/tests/p4-int-routing-acl/test_functional_bootstrap.py b/src/tests/p4-fabric-tna/tests-setup/test_functional_bootstrap.py similarity index 98% rename from src/tests/p4-int-routing-acl/test_functional_bootstrap.py rename to src/tests/p4-fabric-tna/tests-setup/test_functional_bootstrap.py index b5b72cc33..2f9130ad0 100644 --- a/src/tests/p4-int-routing-acl/test_functional_bootstrap.py +++ b/src/tests/p4-fabric-tna/tests-setup/test_functional_bootstrap.py @@ -19,7 +19,7 @@ from common.tools.descriptor.Loader import DescriptorLoader, \ from context.client.ContextClient import ContextClient from device.client.DeviceClient import DeviceClient from tests.Fixtures import context_client, device_client # pylint: disable=unused-import -from test_common import * +from tests.tools.test_tools_p4 import * LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) diff --git a/src/tests/p4-int-routing-acl/test_functional_cleanup.py b/src/tests/p4-fabric-tna/tests-setup/test_functional_cleanup.py similarity index 94% rename from src/tests/p4-int-routing-acl/test_functional_cleanup.py rename to src/tests/p4-fabric-tna/tests-setup/test_functional_cleanup.py index fc29be24d..4d98c9e05 100644 --- a/src/tests/p4-int-routing-acl/test_functional_cleanup.py +++ b/src/tests/p4-fabric-tna/tests-setup/test_functional_cleanup.py @@ -12,13 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging, os -from common.Constants import DEFAULT_CONTEXT_NAME +import logging from common.tools.descriptor.Loader import DescriptorLoader, validate_empty_scenario from context.client.ContextClient import ContextClient from device.client.DeviceClient import DeviceClient from tests.Fixtures import context_client, device_client # pylint: disable=unused-import -from test_common import * +from tests.tools.test_tools_p4 import * LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) diff --git a/src/tests/p4-fabric-tna/tests-setup/test_functional_purge.py b/src/tests/p4-fabric-tna/tests-setup/test_functional_purge.py new file mode 100644 index 000000000..ba37fbd89 --- /dev/null +++ b/src/tests/p4-fabric-tna/tests-setup/test_functional_purge.py @@ -0,0 +1,81 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from common.proto.context_pb2 import ServiceId, DeviceId, LinkId, ServiceStatusEnum, ServiceTypeEnum +from common.tools.grpc.Tools import grpc_message_to_json_string +from common.tools.object_factory.Service import json_service_id +from context.client.ContextClient import ContextClient +from device.client.DeviceClient import DeviceClient +from service.client.ServiceClient import ServiceClient +from tests.Fixtures import context_client, device_client, service_client # pylint: disable=unused-import +from tests.tools.test_tools_p4 import * + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +def test_clean_services( + context_client : ContextClient, # pylint: disable=redefined-outer-name + service_client : ServiceClient # pylint: disable=redefined-outer-name +) -> None: + response = context_client.ListServices(ADMIN_CONTEXT_ID) + LOGGER.warning('Services[{:d}] = {:s}'.format(len(response.services), grpc_message_to_json_string(response))) + + for service in response.services: + service_id = service.service_id + assert service_id + + service_uuid = service_id.service_uuid.uuid + context_uuid = service_id.context_id.context_uuid.uuid + assert service.service_status.service_status == ServiceStatusEnum.SERVICESTATUS_ACTIVE + assert service.service_type == ServiceTypeEnum.SERVICETYPE_INT + + # Delete service + service_client.DeleteService(ServiceId(**json_service_id(service_uuid, json_context_id(context_uuid)))) + +def test_clean_links( + context_client : ContextClient, # pylint: disable=redefined-outer-name +) -> None: + response = context_client.ListLinks(ADMIN_CONTEXT_ID) + + for link in response.links: + link_id = link.link_id + + # Delete link + context_client.RemoveLink(LinkId(**link_id)) + +def test_clean_devices( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient # pylint: disable=redefined-outer-name +) -> None: + response = context_client.ListDevices(ADMIN_CONTEXT_ID) + LOGGER.warning('Devices[{:d}] = {:s}'.format(len(response.devices), grpc_message_to_json_string(response))) + + for device in response.devices: + device_id = device.device_id + + # Delete device + device_client.DeleteDevice(DeviceId(**device_id)) + +def test_clean_context( + context_client : ContextClient # pylint: disable=redefined-outer-name +) -> None: + # Verify the scenario has no services/slices + response = context_client.ListTopologies(ADMIN_CONTEXT_ID) + + for topology in response.topologies: + topology_id = topology.topology_id + response = context_client.RemoveTopology(topology_id) + + response = context_client.RemoveContext(ADMIN_CONTEXT_ID) diff --git a/src/tests/p4-int-routing-acl/topology/README.md b/src/tests/p4-fabric-tna/topology/README.md similarity index 100% rename from src/tests/p4-int-routing-acl/topology/README.md rename to src/tests/p4-fabric-tna/topology/README.md diff --git a/src/tests/p4-int-routing-acl/topology/p4-switch-conf-common.sh b/src/tests/p4-fabric-tna/topology/p4-switch-conf-common.sh similarity index 100% rename from src/tests/p4-int-routing-acl/topology/p4-switch-conf-common.sh rename to src/tests/p4-fabric-tna/topology/p4-switch-conf-common.sh diff --git a/src/tests/p4-int-routing-acl/topology/p4-switch-setup.sh b/src/tests/p4-fabric-tna/topology/p4-switch-setup.sh similarity index 100% rename from src/tests/p4-int-routing-acl/topology/p4-switch-setup.sh rename to src/tests/p4-fabric-tna/topology/p4-switch-setup.sh diff --git a/src/tests/p4-int-routing-acl/topology/p4-switch-tear-down.sh b/src/tests/p4-fabric-tna/topology/p4-switch-tear-down.sh similarity index 100% rename from src/tests/p4-int-routing-acl/topology/p4-switch-tear-down.sh rename to src/tests/p4-fabric-tna/topology/p4-switch-tear-down.sh diff --git a/src/tests/p4-int-routing-acl/topology/p4-switch-three-port-chassis-config-phy.pb.txt b/src/tests/p4-fabric-tna/topology/p4-switch-three-port-chassis-config-phy.pb.txt similarity index 100% rename from src/tests/p4-int-routing-acl/topology/p4-switch-three-port-chassis-config-phy.pb.txt rename to src/tests/p4-fabric-tna/topology/p4-switch-three-port-chassis-config-phy.pb.txt diff --git a/src/tests/p4-int-routing-acl/topology/run-stratum.sh b/src/tests/p4-fabric-tna/topology/run-stratum.sh similarity index 100% rename from src/tests/p4-int-routing-acl/topology/run-stratum.sh rename to src/tests/p4-fabric-tna/topology/run-stratum.sh diff --git a/src/tests/p4-int-routing-acl/test_common.py b/src/tests/p4-int-routing-acl/test_common.py deleted file mode 100644 index f2d00f1dc..000000000 --- a/src/tests/p4-int-routing-acl/test_common.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -from common.Constants import DEFAULT_CONTEXT_NAME -from common.proto.context_pb2 import ContextId, DeviceOperationalStatusEnum -from common.tools.object_factory.Context import json_context_id - -# Context info -CONTEXT_NAME_P4 = DEFAULT_CONTEXT_NAME -ADMIN_CONTEXT_ID = ContextId(**json_context_id(CONTEXT_NAME_P4)) - -# Device and rule cardinality variables -DEV_NB = 4 -CONNECTION_RULES = 3 -ENDPOINT_RULES = 3 -DATAPLANE_RULES_NB_INT_B1 = 5 -DATAPLANE_RULES_NB_INT_B2 = 6 -DATAPLANE_RULES_NB_INT_B3 = 8 -DATAPLANE_RULES_NB_RT_EDGE = 7 -DATAPLANE_RULES_NB_RT_CORP = 7 -DATAPLANE_RULES_NB_ACL = 1 -DATAPLANE_RULES_NB_TOT = \ - DATAPLANE_RULES_NB_INT_B1 +\ - DATAPLANE_RULES_NB_INT_B2 +\ - DATAPLANE_RULES_NB_INT_B3 +\ - DATAPLANE_RULES_NB_RT_EDGE +\ - DATAPLANE_RULES_NB_RT_CORP +\ - DATAPLANE_RULES_NB_ACL - -# Service-related variables -SVC_NB = 1 -NO_SERVICES = 0 -NO_SLICES = 0 - -# Topology descriptor -DESC_TOPO = os.path.join( - os.path.dirname( - os.path.abspath(__file__) - ), - 'descriptors', 'topology.json' -) - -# SBI rule insertion descriptors -# The switch cannot digest all rules at once, hence we insert in batches -DESC_FILE_RULES_INSERT_INT_B1 = os.path.join( - os.path.dirname( - os.path.abspath(__file__) - ), - 'descriptors', 'sbi-rules-insert-int-b1.json' -) -DESC_FILE_RULES_INSERT_INT_B2 = os.path.join( - os.path.dirname( - os.path.abspath(__file__) - ), - 'descriptors', 'sbi-rules-insert-int-b2.json' -) -DESC_FILE_RULES_INSERT_INT_B3 = os.path.join( - os.path.dirname( - os.path.abspath(__file__) - ), - 'descriptors', 'sbi-rules-insert-int-b3.json' -) -DESC_FILE_RULES_INSERT_ROUTING_EDGE = os.path.join( - os.path.dirname( - os.path.abspath(__file__) - ), - 'descriptors', 'sbi-rules-insert-routing-edge.json' -) -DESC_FILE_RULES_INSERT_ROUTING_CORP = os.path.join( - os.path.dirname( - os.path.abspath(__file__) - ), - 'descriptors', 'sbi-rules-insert-routing-corp.json' -) -DESC_FILE_RULES_INSERT_ACL = os.path.join( - os.path.dirname( - os.path.abspath(__file__) - ), - 'descriptors', 'sbi-rules-insert-acl.json' -) - -# SBI rule deletion descriptor -DESC_FILE_RULES_DELETE_ALL = os.path.join( - os.path.dirname( - os.path.abspath(__file__) - ), - 'descriptors', 'sbi-rules-remove.json' -) - -def verify_number_of_rules(devices, desired_rules_nb): - # Iterate all devices - for device in devices: - # Skip non-P4 devices - if device.device_type != "p4-switch": continue - - # We want the device to be active - assert \ - device.device_operational_status == DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED - - # Get the configuration rules of this device - config_rules = device.device_config.config_rules - - # Expected rule cardinality - assert len(config_rules) == desired_rules_nb diff --git a/src/tests/tools/test_tools_p4.py b/src/tests/tools/test_tools_p4.py new file mode 100644 index 000000000..4557fef61 --- /dev/null +++ b/src/tests/tools/test_tools_p4.py @@ -0,0 +1,172 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from common.Constants import DEFAULT_CONTEXT_NAME +from common.proto.context_pb2 import ContextId, DeviceOperationalStatusEnum,\ + DeviceDriverEnum, ServiceTypeEnum, ServiceStatusEnum +from common.tools.object_factory.Context import json_context_id + +# Context info +CONTEXT_NAME_P4 = DEFAULT_CONTEXT_NAME +ADMIN_CONTEXT_ID = ContextId(**json_context_id(CONTEXT_NAME_P4)) + +# Device and rule cardinality variables +DEV_NB = 4 +P4_DEV_NB = 1 +CONNECTION_RULES = 3 +ENDPOINT_RULES = 3 +INT_RULES = 19 +L2_RULES = 8 +L3_RULES = 8 +ACL_RULES = 1 + +DATAPLANE_RULES_NB_INT_B1 = 5 +DATAPLANE_RULES_NB_INT_B2 = 6 +DATAPLANE_RULES_NB_INT_B3 = 8 +DATAPLANE_RULES_NB_RT_EDGE = 7 +DATAPLANE_RULES_NB_RT_CORP = 7 +DATAPLANE_RULES_NB_ACL = 1 +DATAPLANE_RULES_NB_TOT = \ + DATAPLANE_RULES_NB_INT_B1 +\ + DATAPLANE_RULES_NB_INT_B2 +\ + DATAPLANE_RULES_NB_INT_B3 +\ + DATAPLANE_RULES_NB_RT_EDGE +\ + DATAPLANE_RULES_NB_RT_CORP +\ + DATAPLANE_RULES_NB_ACL + +# Service-related variables +SVC_NB = 1 +NO_SERVICES = 0 +NO_SLICES = 0 + +TEST_PATH = os.path.join( + os.path.dirname(os.path.dirname( + os.path.abspath(__file__) + )) + '/p4-fabric-tna/descriptors') +assert os.path.exists(TEST_PATH), "Invalid path to P4 SD-Fabric tests" + +# Topology descriptor +DESC_TOPO = os.path.join(TEST_PATH, 'topology.json') +assert os.path.exists(DESC_TOPO), "Invalid path to the SD-Fabric topology descriptor" + +# SBI descriptors +# The switch cannot digest all rules at once, hence we insert in batches +DESC_FILE_RULES_INSERT_INT_B1 = os.path.join(TEST_PATH, 'sbi-rules-insert-int-b1.json') +assert os.path.exists(DESC_FILE_RULES_INSERT_INT_B1),\ + "Invalid path to the SD-Fabric INT SBI descriptor (batch #1)" + +DESC_FILE_RULES_INSERT_INT_B2 = os.path.join(TEST_PATH, 'sbi-rules-insert-int-b2.json') +assert os.path.exists(DESC_FILE_RULES_INSERT_INT_B2),\ + "Invalid path to the SD-Fabric INT SBI descriptor (batch #2)" + +DESC_FILE_RULES_INSERT_INT_B3 = os.path.join(TEST_PATH, 'sbi-rules-insert-int-b3.json') +assert os.path.exists(DESC_FILE_RULES_INSERT_INT_B3),\ + "Invalid path to the SD-Fabric INT SBI descriptor (batch #3)" + +DESC_FILE_RULES_INSERT_ROUTING_EDGE = os.path.join(TEST_PATH, 'sbi-rules-insert-routing-edge.json') +assert os.path.exists(DESC_FILE_RULES_INSERT_ROUTING_EDGE),\ + "Invalid path to the SD-Fabric routing SBI descriptor (domain1-side)" + +DESC_FILE_RULES_INSERT_ROUTING_CORP = os.path.join(TEST_PATH, 'sbi-rules-insert-routing-corp.json') +assert os.path.exists(DESC_FILE_RULES_INSERT_ROUTING_CORP),\ + "Invalid path to the SD-Fabric routing SBI descriptor (domain2-side)" + +DESC_FILE_RULES_INSERT_ACL = os.path.join(TEST_PATH, 'sbi-rules-insert-acl.json') +assert os.path.exists(DESC_FILE_RULES_INSERT_ACL),\ + "Invalid path to the SD-Fabric ACL SBI descriptor" + +DESC_FILE_RULES_DELETE_ALL = os.path.join(TEST_PATH, 'sbi-rules-remove.json') +assert os.path.exists(DESC_FILE_RULES_DELETE_ALL),\ + "Invalid path to the SD-Fabric rule removal SBI descriptor" + +# Service descriptors +DESC_FILE_SERVICE_CREATE_INT = os.path.join(TEST_PATH, 'service-create-int.json') +assert os.path.exists(DESC_FILE_SERVICE_CREATE_INT),\ + "Invalid path to the SD-Fabric INT service descriptor" + +DESC_FILE_SERVICE_CREATE_L2 = os.path.join(TEST_PATH, 'service-create-l2.json') +assert os.path.exists(DESC_FILE_SERVICE_CREATE_L2),\ + "Invalid path to the SD-Fabric L2 service descriptor" + +DESC_FILE_SERVICE_CREATE_L3 = os.path.join(TEST_PATH, 'service-create-l3.json') +assert os.path.exists(DESC_FILE_SERVICE_CREATE_L3),\ + "Invalid path to the SD-Fabric L3 service descriptor" + +DESC_FILE_SERVICE_CREATE_ACL = os.path.join(TEST_PATH, 'service-create-acl.json') +assert os.path.exists(DESC_FILE_SERVICE_CREATE_ACL),\ + "Invalid path to the SD-Fabric ACL service descriptor" + +def identify_number_of_p4_devices(devices) -> int: + p4_dev_no = 0 + + # Iterate all devices + for device in devices: + # Skip non-P4 devices + if not DeviceDriverEnum.DEVICEDRIVER_P4 in device.device_drivers: continue + + p4_dev_no += 1 + + return p4_dev_no + +def get_number_of_rules(devices) -> int: + total_rules_no = 0 + + # Iterate all devices + for device in devices: + # Skip non-P4 devices + if not DeviceDriverEnum.DEVICEDRIVER_P4 in device.device_drivers: continue + + # We want the device to be active + assert device.device_operational_status == \ + DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED + + # Get the configuration rules of this device + config_rules = device.device_config.config_rules + + # Expected rule cardinality + total_rules_no += len(config_rules) + + return total_rules_no + +def verify_number_of_rules(devices, desired_rules_nb : int) -> None: + # Iterate all devices + for device in devices: + # Skip non-P4 devices + if not DeviceDriverEnum.DEVICEDRIVER_P4 in device.device_drivers: continue + + # We want the device to be active + assert device.device_operational_status == \ + DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED + + # Get the configuration rules of this device + config_rules = device.device_config.config_rules + + # Expected rule cardinality + assert len(config_rules) == desired_rules_nb + +def verify_active_service_type(services, target_service_type : ServiceTypeEnum) -> bool: # type: ignore + # Iterate all services + for service in services: + # Ignore services of other types + if service.service_type != target_service_type: + continue + + service_id = service.service_id + assert service_id + assert service.service_status.service_status == ServiceStatusEnum.SERVICESTATUS_ACTIVE + assert service.service_config + return True + + return False -- GitLab From 578f87b930361b7589ab6e3747b1a7de65caed0e Mon Sep 17 00:00:00 2001 From: "Georgios P. Katsikas" <gkatsikas@ubitech.eu> Date: Sat, 22 Mar 2025 08:20:56 +0000 Subject: [PATCH 03/14] feat: common service library for the SD-Fabric P4 dataplane --- .../p4_fabric_tna_commons.py | 908 ++++++++++++++++++ 1 file changed, 908 insertions(+) create mode 100644 src/service/service/service_handlers/p4_fabric_tna_commons/p4_fabric_tna_commons.py diff --git a/src/service/service/service_handlers/p4_fabric_tna_commons/p4_fabric_tna_commons.py b/src/service/service/service_handlers/p4_fabric_tna_commons/p4_fabric_tna_commons.py new file mode 100644 index 000000000..eb8077ff3 --- /dev/null +++ b/src/service/service/service_handlers/p4_fabric_tna_commons/p4_fabric_tna_commons.py @@ -0,0 +1,908 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Common objects and methods for the SD-Fabric (fabric TNA) dataplane. +This dataplane covers both software based and hardware-based Stratum-enabled P4 switches, +such as the BMv2 software switch and Intel's Tofino/Tofino-2 switches. + +SD-Fabric repo: https://github.com/stratum/fabric-tna +SD-Fabric docs: https://docs.sd-fabric.org/master/index.html +""" + +import time +import logging +import struct +from common.proto.context_pb2 import ConfigActionEnum, ConfigRule +from common.tools.object_factory.ConfigRule import json_config_rule +from common.type_checkers.Checkers import chk_address_mac, chk_vlan_id, \ + chk_address_ipv4, chk_transport_port +from random import randint +from typing import List, Tuple + +LOGGER = logging.getLogger(__name__) + +# Common service handler settings +TARGET_P4_ARCH = "target_p4_arch" +SWITCH_DATAPLANE_ID_MAP = "switch_dataplane_id_map" +VLAN_ID = "vlan_id" + +# P4 architectures +TARGET_ARCH_TNA = "tna" +TARGET_ARCH_V1MODEL = "v1model" +SUPPORTED_TARGET_ARCH_LIST = [TARGET_ARCH_TNA, TARGET_ARCH_V1MODEL] + +# Recirculation ports for various targets +RECIRCULATION_PORTS_TNA = [68, 196, 324, 452] # Tofino-2 (2-pipe switches use only the first 2 entries) +RECIRCULATION_PORTS_V1MODEL = [510] # Variable FAKE_V1MODEL_RECIRC_PORT in p4 source program + +# P4 tables +TABLE_INGRESS_VLAN = "FabricIngress.filtering.ingress_port_vlan" +TABLE_EGRESS_VLAN = "FabricEgress.egress_next.egress_vlan" +TABLE_FWD_CLASSIFIER = "FabricIngress.filtering.fwd_classifier" +TABLE_BRIDGING = "FabricIngress.forwarding.bridging" +TABLE_ROUTING_V4 = "FabricIngress.forwarding.routing_v4" +TABLE_NEXT_SIMPLE = "FabricIngress.next.simple" +TABLE_NEXT_HASHED = "FabricIngress.next.hashed" +TABLE_ACL = "FabricIngress.acl.acl" + +# Action profile members +ACTION_PROFILE_NEXT_HASHED = "FabricIngress.next.hashed_profile" + +# Clone sessions +CLONE_SESSION = "/clone_sessions/clone_session" + +# Forwarding types +FORWARDING_TYPE_BRIDGING = 0 +FORWARDING_TYPE_MPLS = 1 +FORWARDING_TYPE_UNICAST_IPV4 = 2 +FORWARDING_TYPE_IPV4_MULTICAST = 3 +FORWARDING_TYPE_IPV6_UNICAST = 4 +FORWARDING_TYPE_IPV6_MULTICAST = 5 +FORWARDING_TYPE_UNKNOWN = 7 + +FORWARDING_TYPES_VALID = [ + FORWARDING_TYPE_BRIDGING, + FORWARDING_TYPE_MPLS, + FORWARDING_TYPE_UNICAST_IPV4, + FORWARDING_TYPE_IPV4_MULTICAST, + FORWARDING_TYPE_IPV6_UNICAST, + FORWARDING_TYPE_IPV6_MULTICAST, + FORWARDING_TYPE_UNKNOWN +] + +# Port types +PORT_TYPE_INT = "int" +PORT_TYPE_HOST = "host" +PORT_TYPE_SWITCH = "switch" + +PORT_TYPE_ACTION_EDGE = 1 +PORT_TYPE_ACTION_INFRA = 2 +PORT_TYPE_ACTION_INTERNAL = 3 + +PORT_TYPE_MAP = { + PORT_TYPE_INT: PORT_TYPE_ACTION_INTERNAL, + PORT_TYPE_HOST: PORT_TYPE_ACTION_EDGE, + PORT_TYPE_SWITCH: PORT_TYPE_ACTION_INFRA +} + +PORT_TYPES_STR_VALID = [PORT_TYPE_INT, PORT_TYPE_HOST, PORT_TYPE_SWITCH] +PORT_TYPES_INT_VALID = [PORT_TYPE_ACTION_EDGE, PORT_TYPE_ACTION_INFRA, PORT_TYPE_ACTION_INTERNAL] + +# Bridged metadata type +BRIDGED_MD_TYPE_EGRESS_MIRROR = 2 +BRIDGED_MD_TYPE_INGRESS_MIRROR = 3 +BRIDGED_MD_TYPE_INT_INGRESS_DROP = 4 +BRIDGED_MD_TYPE_DEFLECTED = 5 + +# Mirror types +MIRROR_TYPE_INVALID = 0 +MIRROR_TYPE_INT_REPORT = 1 + +# VLAN +VLAN_DEF = 4094 + +# Supported Ethernet types +ETHER_TYPE_IPV4 = "0x0800" +ETHER_TYPE_IPV6 = "0x86DD" + +# Member ID +NEXT_MEMBER_ID = 1 + +# Time interval in seconds for consecutive rule management (insert/delete) operations +RULE_CONF_INTERVAL_SEC = 0.1 + +################################################################################################################ +### Miscellaneous methods +################################################################################################################ + +def arch_tna(arch : str) -> bool: + return arch == TARGET_ARCH_TNA + +def arch_v1model(arch : str) -> bool: + return not arch_tna(arch) + +def generate_random_mac() -> str: + mac = [randint(0x00, 0xff)] * 6 + mac_str = ':'.join(map(lambda x: "%02x" % x, mac)) + chk_address_mac(mac_str), "Invalid MAC address generated" + + return mac_str + +def prefix_to_hex_mask(prefix_len): + # Calculate the binary mask + binary_mask = (1 << 32) - (1 << (32 - prefix_len)) + + # Convert the binary mask to the 4 octets (32 bits) + mask = struct.pack('!I', binary_mask) + + # Convert to a string of hex values + hex_mask = ''.join(f'{byte:02x}' for byte in mask) + + return "0x"+hex_mask.upper() + +def sleep_for(time_sec : int) -> None: + assert time_sec > 0, "Invalid sleep period in seconds" + time.sleep(time_sec) + +################################################################################################################ +### Rule generation methods +################################################################################################################ + +################################### +### A. Port setup +################################### + +def rules_set_up_port_ingress( + ingress_port : int, + port_type : str, + vlan_id: int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert ingress_port >= 0, "Invalid ingress port to configure ingress port" + assert port_type.lower() in PORT_TYPES_STR_VALID, "Invalid port type to configure ingress port" + assert chk_vlan_id(vlan_id), "Invalid VLAN ID to configure ingress port" + + rule_no = cache_rule(TABLE_INGRESS_VLAN, action) + + port_type_int = PORT_TYPE_MAP[port_type.lower()] + assert port_type_int in PORT_TYPES_INT_VALID, "Invalid port type to configure ingress filtering" + + rules_filtering_vlan_ingress = [] + rules_filtering_vlan_ingress.append( + json_config_rule( + action, + '/tables/table/'+TABLE_INGRESS_VLAN+'['+str(rule_no)+']', + { + 'table-name': TABLE_INGRESS_VLAN, + 'match-fields': [ + { + 'match-field': 'ig_port', + 'match-value': str(ingress_port) + }, + { + 'match-field': 'vlan_is_valid', + 'match-value': '0' + } + ], + 'action-name': 'FabricIngress.filtering.permit_with_internal_vlan', + 'action-params': [ + { + 'action-param': 'port_type', + 'action-value': str(port_type_int) + }, + { + 'action-param': 'vlan_id', + 'action-value': str(vlan_id) + } + ], + 'priority': 10 + } + ) + ) + + return rules_filtering_vlan_ingress + +def rules_set_up_port_egress( + egress_port : int, + vlan_id: int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert egress_port >= 0, "Invalid egress port to configure egress vlan" + assert chk_vlan_id(vlan_id), "Invalid VLAN ID to configure egress vlan" + + rule_no = cache_rule(TABLE_EGRESS_VLAN, action) + + rules_vlan_egress = [] + rules_vlan_egress.append( + json_config_rule( + action, + '/tables/table/'+TABLE_EGRESS_VLAN+'['+str(rule_no)+']', + { + 'table-name': TABLE_EGRESS_VLAN, + 'match-fields': [ + { + 'match-field': 'eg_port', + 'match-value': str(egress_port) + }, + { + 'match-field': 'vlan_id', + 'match-value': str(vlan_id) + } + ], + 'action-name': 'FabricEgress.egress_next.pop_vlan', + 'action-params': [] + } + ) + ) + + return rules_vlan_egress + +def rules_set_up_fwd_classifier( + ingress_port : int, + fwd_type : int, + eth_type: str, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert ingress_port >= 0, "Invalid ingress port to configure forwarding classifier" + assert fwd_type in FORWARDING_TYPES_VALID, "Invalid forwarding type to configure forwarding classifier" + + rule_no = cache_rule(TABLE_FWD_CLASSIFIER, action) + + rules_filtering_fwd_classifier = [] + rules_filtering_fwd_classifier.append( + json_config_rule( + action, + '/tables/table/'+TABLE_FWD_CLASSIFIER+'['+str(rule_no)+']', + { + 'table-name': TABLE_FWD_CLASSIFIER, + 'match-fields': [ + { + 'match-field': 'ig_port', + 'match-value': str(ingress_port) + }, + { + 'match-field': 'eth_type', + 'match-value': eth_type + } + ], + 'action-name': 'FabricIngress.filtering.set_forwarding_type', + 'action-params': [ + { + 'action-param': 'fwd_type', + 'action-value': str(FORWARDING_TYPE_UNICAST_IPV4) + }, + ], + 'priority': 10 + } + ) + ) + + return rules_filtering_fwd_classifier + +def rules_set_up_port( + port : int, + port_type : str, + fwd_type : int, + vlan_id : int, + action : ConfigActionEnum, # type: ignore + eth_type=ETHER_TYPE_IPV4): + rules_list = [] + + rules_list.extend( + rules_set_up_port_ingress( + ingress_port=port, + port_type=port_type, + vlan_id=vlan_id, + action=action + ) + ) + rules_list.extend( + rules_set_up_fwd_classifier( + ingress_port=port, + fwd_type=fwd_type, + eth_type=eth_type, + action=action + ) + ) + rules_list.extend( + rules_set_up_port_egress( + egress_port=port, + vlan_id=vlan_id, + action=action + ) + ) + LOGGER.debug("Port configured:{}".format(port)) + + return rules_list + +################################### +### A. End of port setup +################################### + + +################################### +### B. L2 setup +################################### + +def rules_set_up_fwd_bridging( + vlan_id: int, + eth_dst : str, + egress_port : int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert chk_vlan_id(vlan_id), "Invalid VLAN ID to configure bridging" + assert chk_address_mac(eth_dst), "Invalid destination Ethernet address to configure bridging" + assert egress_port >= 0, "Invalid outport to configure bridging" + + rule_no = cache_rule(TABLE_BRIDGING, action) + + rules_fwd_bridging = [] + rules_fwd_bridging.append( + json_config_rule( + action, + '/tables/table/'+TABLE_BRIDGING+'['+str(rule_no)+']', + { + 'table-name': TABLE_BRIDGING, + 'match-fields': [ + { + 'match-field': 'vlan_id', + 'match-value': str(vlan_id) + }, + { + 'match-field': 'eth_dst', + 'match-value': eth_dst + } + ], + 'action-name': 'FabricIngress.forwarding.set_next_id_bridging', + 'action-params': [ + { + 'action-param': 'next_id', + 'action-value': str(egress_port) + } + ], + 'priority': 1 + } + ) + ) + + return rules_fwd_bridging + +def rules_set_up_next_output_simple( + egress_port : int, + eth_src : str, + eth_dst : str, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert egress_port >= 0, "Invalid outport to configure next output simple" + assert chk_address_mac(eth_src), "Invalid source Ethernet address to configure next output simple" + assert chk_address_mac(eth_dst), "Invalid destination Ethernet address to configure next output simple" + + rule_no = cache_rule(TABLE_NEXT_SIMPLE, action) + + rules_next_output_simple = [] + rules_next_output_simple.append( + json_config_rule( + action, + '/tables/table/'+TABLE_NEXT_SIMPLE+'['+str(rule_no)+']', + { + 'table-name': TABLE_NEXT_SIMPLE, + 'match-fields': [ + { + 'match-field': 'next_id', + 'match-value': str(egress_port) + } + ], + 'action-name': 'FabricIngress.next.output_simple', + 'action-params': [ + { + 'action-param': 'port_num', + 'action-value': str(egress_port) + } + ] + } + ) + ) + + return rules_next_output_simple + +def rules_set_up_next_output_hashed( + egress_port : int, + action : ConfigActionEnum, # type: ignore + next_id = None) -> List [Tuple]: + assert egress_port >= 0, "Invalid outport to configure next output hashed" + + if next_id is None: + next_id = egress_port + + global NEXT_MEMBER_ID + + rule_no = cache_rule(ACTION_PROFILE_NEXT_HASHED, action) + + rules_next_output_hashed = [] + rules_next_output_hashed.append( + json_config_rule( + action, + '/action_profiles/action_profile/'+ACTION_PROFILE_NEXT_HASHED+'['+str(rule_no)+']', + { + 'action-profile-name': ACTION_PROFILE_NEXT_HASHED, + 'member-id': NEXT_MEMBER_ID, + 'action-name': 'FabricIngress.next.output_hashed', + 'action-params': [ + { + 'action-param': 'port_num', + 'action-value': str(egress_port) + } + ] + } + ) + ) + + rule_no = cache_rule(TABLE_NEXT_HASHED, action) + + rules_next_output_hashed.append( + json_config_rule( + action, + '/tables/table/'+TABLE_NEXT_HASHED+'['+str(rule_no)+']', + { + 'table-name': TABLE_NEXT_HASHED, + 'member-id': NEXT_MEMBER_ID, + 'match-fields': [ + { + 'match-field': 'next_id', + 'match-value': str(next_id) + } + ] + } + ) + ) + + NEXT_MEMBER_ID += 1 + + return rules_next_output_hashed + +################################### +### B. End of L2 setup +################################### + + +################################### +### C. L3 setup +################################### + +def rules_set_up_routing( + ipv4_dst : str, + egress_port : int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert chk_address_ipv4(ipv4_dst), "Invalid destination IPv4 address to configure routing" + assert egress_port >= 0, "Invalid outport to configure routing" + + rule_no = cache_rule(TABLE_ROUTING_V4, action) + + rules_routing = [] + rules_routing.append( + json_config_rule( + action, + '/tables/table/'+TABLE_ROUTING_V4+'['+str(rule_no)+']', + { + 'table-name': TABLE_ROUTING_V4, + 'match-fields': [ + { + 'match-field': 'ipv4_dst', + 'match-value': ipv4_dst + } + ], + 'action-name': 'FabricIngress.forwarding.set_next_id_routing_v4', + 'action-params': [ + { + 'action-param': 'next_id', + 'action-value': str(egress_port) + } + ] + } + ) + ) + + return rules_routing + +def rules_set_up_next_routing_simple( + egress_port : int, + eth_src : str, + eth_dst : str, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert egress_port >= 0, "Invalid outport to configure next routing simple" + assert chk_address_mac(eth_src), "Invalid source Ethernet address to configure next routing simple" + assert chk_address_mac(eth_dst), "Invalid destination Ethernet address to configure next routing simple" + + rule_no = cache_rule(TABLE_NEXT_SIMPLE, action) + + rules_next_routing_simple = [] + rules_next_routing_simple.append( + json_config_rule( + action, + '/tables/table/'+TABLE_NEXT_SIMPLE+'['+str(rule_no)+']', + { + 'table-name': TABLE_NEXT_SIMPLE, + 'match-fields': [ + { + 'match-field': 'next_id', + 'match-value': str(egress_port) + } + ], + 'action-name': 'FabricIngress.next.routing_simple', + 'action-params': [ + { + 'action-param': 'port_num', + 'action-value': str(egress_port) + }, + { + 'action-param': 'smac', + 'action-value': eth_src + }, + { + 'action-param': 'dmac', + 'action-value': eth_dst + } + ] + } + ) + ) + + return rules_next_routing_simple + +def rules_set_up_next_routing_hashed( + egress_port : int, + action : ConfigActionEnum, # type: ignore + next_id = None) -> List [Tuple]: + assert egress_port >= 0, "Invalid outport to configure next routing hashed" + random_mac_src = generate_random_mac() + random_mac_dst = generate_random_mac() + if next_id is None: + next_id = egress_port + + global NEXT_MEMBER_ID + + rule_no = cache_rule(ACTION_PROFILE_NEXT_HASHED, action) + + rules_next_routing_hashed = [] + rules_next_routing_hashed.append( + json_config_rule( + action, + '/action_profiles/action_profile/'+ACTION_PROFILE_NEXT_HASHED+'['+str(rule_no)+']', + { + 'action-profile-name': ACTION_PROFILE_NEXT_HASHED, + 'member-id': NEXT_MEMBER_ID, + 'action-name': 'FabricIngress.next.routing_hashed', + 'action-params': [ + { + 'action-param': 'port_num', + 'action-value': str(egress_port) + }, + { + 'action-param': 'smac', + 'action-value': random_mac_src + }, + { + 'action-param': 'dmac', + 'action-value': random_mac_dst + } + ] + } + ) + ) + + rule_no = cache_rule(TABLE_NEXT_HASHED, action) + + rules_next_routing_hashed.append( + json_config_rule( + action, + '/tables/table/'+TABLE_NEXT_HASHED+'['+str(rule_no)+']', + { + 'table-name': TABLE_NEXT_HASHED, + 'member-id': NEXT_MEMBER_ID, + 'match-fields': [ + { + 'match-field': 'next_id', + 'match-value': str(next_id) + } + ] + } + ) + ) + + return rules_next_routing_hashed + +################################### +### C. End of L3 setup +################################### + + +################################### +### D. Flow mirroring +################################### + +def rules_set_up_report_mirror_flow( + recirculation_port_list : List, + report_mirror_id_list : List, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + rules_list = [] + + for i, mirror_id in enumerate(report_mirror_id_list): + LOGGER.debug("Mirror ID:{} - Recirculation port: {}".format( + mirror_id, recirculation_port_list[i])) + rules_list.extend( + rules_set_up_clone_session( + session_id=mirror_id, + egress_port=recirculation_port_list[i], + instance=0, + action=action + ) + ) + + return rules_list + +def rules_set_up_clone_session( + session_id : int, + egress_port : int, + instance : int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert session_id >= 0, "Invalid session identifier to configure clone session" + assert egress_port >= 0, "Invalid egress port number to configure clone session" + assert instance >= 0, "Invalid instance number to configure clone session" + + rule_no = cache_rule(CLONE_SESSION, action) + + #TODO: For TNA pass also: packet_length_bytes = 128 + packet_length_bytes = 128 + + rules_clone_session = [] + + rules_clone_session.append( + json_config_rule( + action, + CLONE_SESSION+'['+str(rule_no)+']', + { + 'session-id': session_id, + 'replicas': [ + { + 'egress-port': egress_port, + 'instance': instance + } + ] + } + ) + ) + + return rules_clone_session + +################################### +### D. End of flow mirroring +################################### + + +################################### +### E. Access Control Lists +################################### + +def rules_set_up_acl_filter_host( + ingress_port : int, + ip_address : str, + prefix_len : int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert ingress_port >= 0, "Invalid ingress port to configure ACL" + assert chk_address_ipv4(ip_address), "Invalid IP address to configure ACL" + assert 0 < prefix_len <= 32, "Invalid IP address prefix length to configure ACL" + + prefix_len_hex = prefix_to_hex_mask(prefix_len) + + rule_no = cache_rule(TABLE_ACL, action) + + rules_acl = [] + rules_acl.append( + json_config_rule( + action, + '/tables/table/'+TABLE_ACL+'['+str(rule_no)+']', + { + 'table-name': TABLE_ACL, + 'match-fields': [ + { + 'match-field': 'ig_port', + 'match-value': str(ingress_port) + }, + { + 'match-field': 'ipv4_src', + 'match-value': '%s&&&%s' % (ip_address, prefix_len_hex) + } + ], + 'action-name': 'FabricIngress.acl.drop', + 'action-params': [], + 'priority': 1 + } + ) + ) + + return rules_acl + +def rules_set_up_acl_filter_port( + ingress_port : int, + transport_port : int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert ingress_port >= 0, "Invalid ingress port to configure ACL" + assert chk_transport_port(transport_port), "Invalid transport port to configure ACL" + + rule_no = cache_rule(TABLE_ACL, action) + + rules_acl = [] + rules_acl.append( + json_config_rule( + action, + '/tables/table/'+TABLE_ACL+'['+str(rule_no)+']', + { + 'table-name': TABLE_ACL, + 'match-fields': [ + { + 'match-field': 'ig_port', + 'match-value': str(ingress_port) + }, + { + 'match-field': 'l4_dport', + 'match-value': str(transport_port) + } + ], + 'action-name': 'FabricIngress.acl.drop', + 'action-params': [], + 'priority': 1 + } + ) + ) + + return rules_acl + +########################################### +### E. End of Access Control Lists +########################################### + +################################################################################################################ +### Rule management methods +################################################################################################################ + +def apply_rules(task_executor, device_obj, json_config_rules): + applied_rules = 0 + failed_rules = 0 + total_rules = len(json_config_rules) + assert device_obj, "Cannot apply rules to invalid device object" + + if total_rules == 0: + return applied_rules, failed_rules + + # Provision rules one-by-one + for i, json_config_rule in enumerate(json_config_rules): + LOGGER.debug("Applying rule #{}: {}".format(i, json_config_rule)) + try: + # Cleanup the rules of this particular object + del device_obj.device_config.config_rules[:] + + # Add the new rule to apply + device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule)) + + # Configure the device via the SBI + # TODO: Acquire status of this RPC to ensure that the rule is actually applied + task_executor.configure_device(device_obj) + + # Sleep for some time till the next operation + sleep_for(RULE_CONF_INTERVAL_SEC) + + applied_rules += 1 + except Exception as ex: + LOGGER.error("Error while applying rule #{}: {}".format(i, ex)) + failed_rules += 1 + + LOGGER.info("Batch rules: {}/{} applied".format(applied_rules, total_rules)) + LOGGER.info("Batch rules: {}/{} failed".format(failed_rules, total_rules)) + + return applied_rules, failed_rules + +# Map for keeping rule counts per table +RULE_ENTRY_MAP = {} + +def cache_rule( + table_name : str, + action : ConfigActionEnum) -> int: # type: ignore + rule_no = -1 + + if action == ConfigActionEnum.CONFIGACTION_SET: + rule_no = add_rule_to_map(table_name) + elif action == ConfigActionEnum.CONFIGACTION_DELETE: + rule_no = delete_rule_from_map(table_name) + else: + assert True, "Invalid rule configuration action" + + assert rule_no > 0, "Invalid rule identifier to configure table {}".format(table_name) + + return rule_no + +def add_rule_to_map(table_name : str) -> int: + if table_name not in RULE_ENTRY_MAP: + RULE_ENTRY_MAP[table_name] = [] + + # Current number of rules + rules_no = len(RULE_ENTRY_MAP[table_name]) + + # Get a new valid rule index + new_index = find_minimum_available_rule_index(RULE_ENTRY_MAP[table_name]) + LOGGER.debug("Minimum available rule index for table {} is: {}".format(table_name, new_index)) + assert new_index > 0, "Invalid rule index for table {}".format(table_name) + + # New entry + new_rule_entry = table_name+"["+str(new_index)+"]" + + # Add entry to the list + RULE_ENTRY_MAP[table_name].append(new_rule_entry) + assert len(RULE_ENTRY_MAP[table_name]) == rules_no + 1 + + return new_index + +def delete_rule_from_map(table_name : str) -> int: + if table_name not in RULE_ENTRY_MAP: + LOGGER.error("Table {} has no entries".format(table_name)) + return -1 + + # Current number of rules + rules_no = len(RULE_ENTRY_MAP[table_name]) + + # Remove last rule + rule_entry = RULE_ENTRY_MAP[table_name].pop() + # Get its index + rule_no = int(rule_entry.split('[')[1].split(']')[0]) + + assert len(RULE_ENTRY_MAP[table_name]) == rules_no - 1 + + # Return the index of the removed rule + return rule_no + +def string_contains_number(input_string : str, target_number : int) -> bool: + return str(target_number) in input_string + +def rule_index_exists(rule_entry_list : List, target_rule_index : int) -> bool: + # Rule indices start from 1 + if target_rule_index <= 0: + return False + + rules_no = len(rule_entry_list) + if rules_no == 0: + return False + + for rule in rule_entry_list: + if string_contains_number(rule, target_rule_index): + return True + + return False + +def find_minimum_available_rule_index(rule_entry_list : List) -> int: + rules_no = len(rule_entry_list) + if rules_no == 0: + return 1 + + min_index = -1 + for i, _ in enumerate(rule_entry_list): + index = i+1 + idx_exists = rule_index_exists(rule_entry_list, index) + # This index is not present in the rule list, so it is available + if not idx_exists and min_index < index: + min_index = index + + # All of the existing rule indices are taken, proceed to the next one + if min_index == -1: + min_index = rules_no + 1 + + return min_index + +def print_rule_map() -> None: + for k in RULE_ENTRY_MAP.keys(): + LOGGER.info("Table {} entries: {}".format(k, RULE_ENTRY_MAP[k])) -- GitLab From afbe8b59cf07312e05ad1e378e40df7ea259c52e Mon Sep 17 00:00:00 2001 From: "Georgios P. Katsikas" <gkatsikas@ubitech.eu> Date: Sat, 22 Mar 2025 08:27:07 +0000 Subject: [PATCH 04/14] fix: remove unecessary whitespaces --- .../p4_fabric_tna_commons/p4_fabric_tna_commons.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/service/service/service_handlers/p4_fabric_tna_commons/p4_fabric_tna_commons.py b/src/service/service/service_handlers/p4_fabric_tna_commons/p4_fabric_tna_commons.py index eb8077ff3..4b60f9118 100644 --- a/src/service/service/service_handlers/p4_fabric_tna_commons/p4_fabric_tna_commons.py +++ b/src/service/service/service_handlers/p4_fabric_tna_commons/p4_fabric_tna_commons.py @@ -129,7 +129,7 @@ RULE_CONF_INTERVAL_SEC = 0.1 def arch_tna(arch : str) -> bool: return arch == TARGET_ARCH_TNA - + def arch_v1model(arch : str) -> bool: return not arch_tna(arch) @@ -143,13 +143,13 @@ def generate_random_mac() -> str: def prefix_to_hex_mask(prefix_len): # Calculate the binary mask binary_mask = (1 << 32) - (1 << (32 - prefix_len)) - + # Convert the binary mask to the 4 octets (32 bits) mask = struct.pack('!I', binary_mask) - + # Convert to a string of hex values hex_mask = ''.join(f'{byte:02x}' for byte in mask) - + return "0x"+hex_mask.upper() def sleep_for(time_sec : int) -> None: @@ -172,7 +172,7 @@ def rules_set_up_port_ingress( assert ingress_port >= 0, "Invalid ingress port to configure ingress port" assert port_type.lower() in PORT_TYPES_STR_VALID, "Invalid port type to configure ingress port" assert chk_vlan_id(vlan_id), "Invalid VLAN ID to configure ingress port" - + rule_no = cache_rule(TABLE_INGRESS_VLAN, action) port_type_int = PORT_TYPE_MAP[port_type.lower()] -- GitLab From 7b9e30103abab96bf40473c3203666257557e4eb Mon Sep 17 00:00:00 2001 From: "Georgios P. Katsikas" <gkatsikas@ubitech.eu> Date: Mon, 31 Mar 2025 06:30:53 +0000 Subject: [PATCH 05/14] feat: support for more NFs and fixes --- src/common/type_checkers/Checkers.py | 9 ++ .../p4_fabric_tna_commons.py | 96 ++++++++++++++----- .../service/task_scheduler/TaskExecutor.py | 2 +- src/tests/tools/test_tools_p4.py | 6 +- 4 files changed, 87 insertions(+), 26 deletions(-) diff --git a/src/common/type_checkers/Checkers.py b/src/common/type_checkers/Checkers.py index 694f14102..e797d6441 100644 --- a/src/common/type_checkers/Checkers.py +++ b/src/common/type_checkers/Checkers.py @@ -140,6 +140,15 @@ def chk_address_ipv4(ip_addr : str): except ValueError: return False +def chk_prefix_len_ipv4(ip_prefix_len : int): + """ + Check whether input integer is a valid IPv4 address prefix length. + + :param ip_prefix_len: IPv4 address prefix length + :return: boolean status + """ + return 0 <= ip_prefix_len <= 32 + def chk_address_ipv6(ip_addr : str): """ Check whether input string is a valid IPv6 address or not. diff --git a/src/service/service/service_handlers/p4_fabric_tna_commons/p4_fabric_tna_commons.py b/src/service/service/service_handlers/p4_fabric_tna_commons/p4_fabric_tna_commons.py index 4b60f9118..a97bce07f 100644 --- a/src/service/service/service_handlers/p4_fabric_tna_commons/p4_fabric_tna_commons.py +++ b/src/service/service/service_handlers/p4_fabric_tna_commons/p4_fabric_tna_commons.py @@ -24,19 +24,37 @@ SD-Fabric docs: https://docs.sd-fabric.org/master/index.html import time import logging import struct -from common.proto.context_pb2 import ConfigActionEnum, ConfigRule -from common.tools.object_factory.ConfigRule import json_config_rule -from common.type_checkers.Checkers import chk_address_mac, chk_vlan_id, \ - chk_address_ipv4, chk_transport_port from random import randint from typing import List, Tuple +from common.proto.context_pb2 import ConfigActionEnum, ConfigRule, Device, EndPoint +from common.tools.object_factory.ConfigRule import json_config_rule +from common.type_checkers.Checkers import chk_address_mac, chk_vlan_id, \ + chk_address_ipv4, chk_prefix_len_ipv4, chk_transport_port +from service.service.task_scheduler.TaskExecutor import TaskExecutor LOGGER = logging.getLogger(__name__) # Common service handler settings -TARGET_P4_ARCH = "target_p4_arch" -SWITCH_DATAPLANE_ID_MAP = "switch_dataplane_id_map" +SWITCH_INFO = "switch_info" +ARCH = "arch" +DPID = "dpid" +MAC = "mac" +IP = "ip" +PORT = "port" # Dataplane port +PORT_ID = "port_id" +PORT_TYPE = "port_type" VLAN_ID = "vlan_id" +RECIRCULATION_PORT_LIST = "recirculation_port_list" +PORT_LIST = "port_list" +PORT_PREFIX = "port-" +ROUTING_LIST = "routing_list" +MAC_SRC = "mac_src" +MAC_DST = "mac_dst" +IPV4_SRC = "ipv4_src" +IPV4_DST = "ipv4_dst" +IPV4_PREFIX_LEN = "ipv4_prefix_len" +TRN_PORT_SRC = "trn_port_src" # Transport network port (TCP, UDP) +TRN_PORT_DST = "trn_port_dst" # P4 architectures TARGET_ARCH_TNA = "tna" @@ -140,7 +158,7 @@ def generate_random_mac() -> str: return mac_str -def prefix_to_hex_mask(prefix_len): +def prefix_to_hex_mask(prefix_len : int) -> str: # Calculate the binary mask binary_mask = (1 << 32) - (1 << (32 - prefix_len)) @@ -156,6 +174,30 @@ def sleep_for(time_sec : int) -> None: assert time_sec > 0, "Invalid sleep period in seconds" time.sleep(time_sec) +def find_port_id_in_endpoint(endpoint : EndPoint, target_endpoint_uuid : str) -> int: # type: ignore + assert endpoint, "Invalid device endpoint" + endpoint_uuid = endpoint.endpoint_id.endpoint_uuid.uuid + assert endpoint_uuid, "Invalid device endpoint UUID" + if endpoint_uuid == target_endpoint_uuid: + try: + dpid = int(endpoint.name) # P4 devices have integer dataplane port IDs + assert dpid > 0, "Invalid device endpoint DPID" + except Exception as ex: + LOGGER.error(ex) + return -1 + return dpid + + return -1 + +def find_port_id_in_endpoint_list(endpoint_list : List, target_endpoint_uuid : str) -> int: + assert endpoint_list, "Invalid device endpoint list" + for endpoint in endpoint_list: + result = find_port_id_in_endpoint(endpoint, target_endpoint_uuid) + if result != -1: + return result + + return -1 + ################################################################################################################ ### Rule generation methods ################################################################################################################ @@ -173,6 +215,9 @@ def rules_set_up_port_ingress( assert port_type.lower() in PORT_TYPES_STR_VALID, "Invalid port type to configure ingress port" assert chk_vlan_id(vlan_id), "Invalid VLAN ID to configure ingress port" + # VLAN support if 1 + vlan_is_valid = 1 if vlan_id != VLAN_DEF else 0 + rule_no = cache_rule(TABLE_INGRESS_VLAN, action) port_type_int = PORT_TYPE_MAP[port_type.lower()] @@ -192,7 +237,7 @@ def rules_set_up_port_ingress( }, { 'match-field': 'vlan_is_valid', - 'match-value': '0' + 'match-value': str(vlan_is_valid) } ], 'action-name': 'FabricIngress.filtering.permit_with_internal_vlan', @@ -270,7 +315,7 @@ def rules_set_up_fwd_classifier( 'match-value': str(ingress_port) }, { - 'match-field': 'eth_type', + 'match-field': 'ip_eth_type', 'match-value': eth_type } ], @@ -278,10 +323,10 @@ def rules_set_up_fwd_classifier( 'action-params': [ { 'action-param': 'fwd_type', - 'action-value': str(FORWARDING_TYPE_UNICAST_IPV4) + 'action-value': str(fwd_type) }, ], - 'priority': 10 + 'priority': 1 } ) ) @@ -294,7 +339,7 @@ def rules_set_up_port( fwd_type : int, vlan_id : int, action : ConfigActionEnum, # type: ignore - eth_type=ETHER_TYPE_IPV4): + eth_type=ETHER_TYPE_IPV4) -> List [Tuple]: rules_list = [] rules_list.extend( @@ -377,12 +422,8 @@ def rules_set_up_fwd_bridging( def rules_set_up_next_output_simple( egress_port : int, - eth_src : str, - eth_dst : str, action : ConfigActionEnum) -> List [Tuple]: # type: ignore assert egress_port >= 0, "Invalid outport to configure next output simple" - assert chk_address_mac(eth_src), "Invalid source Ethernet address to configure next output simple" - assert chk_address_mac(eth_dst), "Invalid destination Ethernet address to configure next output simple" rule_no = cache_rule(TABLE_NEXT_SIMPLE, action) @@ -478,9 +519,11 @@ def rules_set_up_next_output_hashed( def rules_set_up_routing( ipv4_dst : str, + ipv4_prefix_len : int, egress_port : int, action : ConfigActionEnum) -> List [Tuple]: # type: ignore assert chk_address_ipv4(ipv4_dst), "Invalid destination IPv4 address to configure routing" + assert chk_prefix_len_ipv4(ipv4_prefix_len), "Invalid IPv4 prefix length" assert egress_port >= 0, "Invalid outport to configure routing" rule_no = cache_rule(TABLE_ROUTING_V4, action) @@ -495,7 +538,7 @@ def rules_set_up_routing( 'match-fields': [ { 'match-field': 'ipv4_dst', - 'match-value': ipv4_dst + 'match-value': ipv4_dst + "/" + str(ipv4_prefix_len) } ], 'action-name': 'FabricIngress.forwarding.set_next_id_routing_v4', @@ -694,11 +737,14 @@ def rules_set_up_acl_filter_host( ingress_port : int, ip_address : str, prefix_len : int, + ip_direction : str, action : ConfigActionEnum) -> List [Tuple]: # type: ignore assert ingress_port >= 0, "Invalid ingress port to configure ACL" assert chk_address_ipv4(ip_address), "Invalid IP address to configure ACL" assert 0 < prefix_len <= 32, "Invalid IP address prefix length to configure ACL" + ip_match = "ipv4_src" if ip_direction == "src" else "ipv4_dst" + prefix_len_hex = prefix_to_hex_mask(prefix_len) rule_no = cache_rule(TABLE_ACL, action) @@ -716,7 +762,7 @@ def rules_set_up_acl_filter_host( 'match-value': str(ingress_port) }, { - 'match-field': 'ipv4_src', + 'match-field': ip_match, 'match-value': '%s&&&%s' % (ip_address, prefix_len_hex) } ], @@ -732,10 +778,13 @@ def rules_set_up_acl_filter_host( def rules_set_up_acl_filter_port( ingress_port : int, transport_port : int, + transport_direction : str, action : ConfigActionEnum) -> List [Tuple]: # type: ignore assert ingress_port >= 0, "Invalid ingress port to configure ACL" assert chk_transport_port(transport_port), "Invalid transport port to configure ACL" + trn_match = "l4_sport" if transport_direction == "src" else "l4_dport" + rule_no = cache_rule(TABLE_ACL, action) rules_acl = [] @@ -751,7 +800,7 @@ def rules_set_up_acl_filter_port( 'match-value': str(ingress_port) }, { - 'match-field': 'l4_dport', + 'match-field': trn_match, 'match-value': str(transport_port) } ], @@ -772,7 +821,10 @@ def rules_set_up_acl_filter_port( ### Rule management methods ################################################################################################################ -def apply_rules(task_executor, device_obj, json_config_rules): +def apply_rules( + task_executor : TaskExecutor, + device_obj : Device, # type: ignore + json_config_rules : List): # type: ignore applied_rules = 0 failed_rules = 0 total_rules = len(json_config_rules) @@ -802,9 +854,9 @@ def apply_rules(task_executor, device_obj, json_config_rules): except Exception as ex: LOGGER.error("Error while applying rule #{}: {}".format(i, ex)) failed_rules += 1 + raise Exception(ex) - LOGGER.info("Batch rules: {}/{} applied".format(applied_rules, total_rules)) - LOGGER.info("Batch rules: {}/{} failed".format(failed_rules, total_rules)) + LOGGER.debug("Batch rules: {}/{} applied".format(applied_rules, total_rules)) return applied_rules, failed_rules diff --git a/src/service/service/task_scheduler/TaskExecutor.py b/src/service/service/task_scheduler/TaskExecutor.py index 51fc42a5a..8f797882f 100644 --- a/src/service/service/task_scheduler/TaskExecutor.py +++ b/src/service/service/task_scheduler/TaskExecutor.py @@ -203,7 +203,7 @@ class TaskExecutor: self, service_id : ServiceId, config_key : str, config_value : str ): service_configRule = ServiceConfigRule() - service_configRule.service_id.CopyFrom( service_id) + service_configRule.service_id.CopyFrom(service_id) service_configRule.configrule_custom.resource_key = config_key service_configRule.configrule_custom.resource_value = config_value try: diff --git a/src/tests/tools/test_tools_p4.py b/src/tests/tools/test_tools_p4.py index 4557fef61..65257aefc 100644 --- a/src/tests/tools/test_tools_p4.py +++ b/src/tests/tools/test_tools_p4.py @@ -28,9 +28,9 @@ P4_DEV_NB = 1 CONNECTION_RULES = 3 ENDPOINT_RULES = 3 INT_RULES = 19 -L2_RULES = 8 -L3_RULES = 8 -ACL_RULES = 1 +L2_RULES = 10 +L3_RULES = 4 +ACL_RULES = 2 DATAPLANE_RULES_NB_INT_B1 = 5 DATAPLANE_RULES_NB_INT_B2 = 6 -- GitLab From 80b308271192264727d37722f52ab3798074f9c2 Mon Sep 17 00:00:00 2001 From: "Georgios P. Katsikas" <gkatsikas@ubitech.eu> Date: Mon, 31 Mar 2025 06:42:01 +0000 Subject: [PATCH 06/14] feat: P4 In-band Network Telemetry (INT) service hnalder --- .../service/service_handlers/__init__.py | 7 + .../p4_fabric_tna_int/__init__.py | 13 + .../p4_fabric_tna_int_config.py | 425 ++++++++++++++++ .../p4_fabric_tna_int_service_handler.py | 467 ++++++++++++++++++ src/tests/p4-fabric-tna/README.md | 19 + .../descriptors/service-create-int.json | 53 ++ .../run_test_06a_service_provision_int.sh | 17 + .../run_test_06b_service_deprovision_int.sh | 17 + ...test_functional_service_deprovision_int.py | 78 +++ .../test_functional_service_provision_int.py | 73 +++ 10 files changed, 1169 insertions(+) create mode 100644 src/service/service/service_handlers/p4_fabric_tna_int/__init__.py create mode 100644 src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_config.py create mode 100644 src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_service_handler.py create mode 100644 src/tests/p4-fabric-tna/descriptors/service-create-int.json create mode 100755 src/tests/p4-fabric-tna/run_test_06a_service_provision_int.sh create mode 100755 src/tests/p4-fabric-tna/run_test_06b_service_deprovision_int.sh create mode 100644 src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_int.py create mode 100644 src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_int.py diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py index e03c1172e..76107f03d 100644 --- a/src/service/service/service_handlers/__init__.py +++ b/src/service/service/service_handlers/__init__.py @@ -26,6 +26,7 @@ from .l3nm_nce.L3NMNCEServiceHandler import L3NMNCEServiceHandler from .l3slice_ietfslice.L3SliceIETFSliceServiceHandler import L3NMSliceIETFSliceServiceHandler from .microwave.MicrowaveServiceHandler import MicrowaveServiceHandler from .p4_dummy_l1.p4_dummy_l1_service_handler import P4DummyL1ServiceHandler +from .p4_fabric_tna_int.p4_fabric_tna_int_service_handler import P4FabricINTServiceHandler from .tapi_tapi.TapiServiceHandler import TapiServiceHandler from .tapi_xr.TapiXrServiceHandler import TapiXrServiceHandler from .optical_tfs.OpticalTfsServiceHandler import OpticalTfsServiceHandler @@ -111,6 +112,12 @@ SERVICE_HANDLERS = [ FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4, } ]), + (P4FabricINTServiceHandler, [ + { + FilterFieldEnum.SERVICE_TYPE: ServiceTypeEnum.SERVICETYPE_INT, + FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4, + } + ]), (L2NM_IETFL2VPN_ServiceHandler, [ { FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_L2NM, diff --git a/src/service/service/service_handlers/p4_fabric_tna_int/__init__.py b/src/service/service/service_handlers/p4_fabric_tna_int/__init__.py new file mode 100644 index 000000000..023830645 --- /dev/null +++ b/src/service/service/service_handlers/p4_fabric_tna_int/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_config.py b/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_config.py new file mode 100644 index 000000000..8fb22ee97 --- /dev/null +++ b/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_config.py @@ -0,0 +1,425 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Common objects and methods for In-band Network Telemetry (INT) dataplane +based on the SD-Fabric dataplane model. +This dataplane covers both software based and hardware-based Stratum-enabled P4 switches, +such as the BMv2 software switch and Intel's Tofino/Tofino-2 switches. + +SD-Fabric repo: https://github.com/stratum/fabric-tna +SD-Fabric docs: https://docs.sd-fabric.org/master/index.html +""" + +import logging +from typing import List, Tuple +from common.proto.context_pb2 import ConfigActionEnum +from common.tools.object_factory.ConfigRule import json_config_rule +from common.type_checkers.Checkers import chk_address_ipv4, chk_transport_port + +from service.service.service_handlers.p4_fabric_tna_commons.p4_fabric_tna_commons import * + +LOGGER = logging.getLogger(__name__) + +# INT service handler settings +INT_COLLECTOR_INFO = "int_collector_info" +INT_REPORT_MIRROR_ID_LIST = "int_report_mirror_id_list" +PORT_INT = "int_port" # In-band Network Telemetry transport port (of the collector) + +# INT tables +TABLE_INT_WATCHLIST = "FabricIngress.int_watchlist.watchlist" +TABLE_INT_EGRESS_REPORT = "FabricEgress.int_egress.report" + +# Mirror IDs for INT reports +INT_REPORT_MIRROR_ID_LIST_TNA = [0x200, 0x201, 0x202, 0x203] # Tofino-2 (2-pipe Tofino switches use only the first 2 entries) +INT_REPORT_MIRROR_ID_LIST_V1MODEL = [0x1FA] # Variable V1MODEL_INT_MIRROR_SESSION in p4 source program + +# INT report types +INT_REPORT_TYPE_NO_REPORT = 0 +INT_REPORT_TYPE_FLOW = 1 +INT_REPORT_TYPE_QUEUE = 2 +INT_REPORT_TYPE_DROP = 4 + + +def rules_set_up_int_watchlist(action : ConfigActionEnum) -> List [Tuple]: # type: ignore + rule_no = cache_rule(TABLE_INT_WATCHLIST, action) + + rules_int_watchlist = [] + rules_int_watchlist.append( + json_config_rule( + action, + '/tables/table/'+TABLE_INT_WATCHLIST+'['+str(rule_no)+']', + { + 'table-name': TABLE_INT_WATCHLIST, + 'match-fields': [ + { + 'match-field': 'ipv4_valid', + 'match-value': '1' + } + ], + 'action-name': 'FabricIngress.int_watchlist.mark_to_report', + 'action-params': [], + 'priority': 1 + } + ) + ) + + return rules_int_watchlist + +def rules_set_up_int_report_collector( + int_collector_ip : str, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert chk_address_ipv4(int_collector_ip), "Invalid INT collector IPv4 address to configure watchlist" + + rule_no = cache_rule(TABLE_INT_WATCHLIST, action) + + rules_int_col_report = [] + rules_int_col_report.append( + json_config_rule( + action, + '/tables/table/'+TABLE_INT_WATCHLIST+'['+str(rule_no)+']', + { + 'table-name': TABLE_INT_WATCHLIST, + 'match-fields': [ + { + 'match-field': 'ipv4_valid', + 'match-value': '1' + }, + { + 'match-field': 'ipv4_dst', + 'match-value': int_collector_ip+'&&&0xFFFFFFFF' + } + ], + 'action-name': 'FabricIngress.int_watchlist.no_report_collector', + 'action-params': [], + 'priority': 10 + } + ) + ) + + return rules_int_col_report + +def rules_set_up_int_recirculation_ports( + recirculation_port_list : List, + port_type : str, + fwd_type : int, + vlan_id : int, + action : ConfigActionEnum): # type: ignore + rules_list = [] + + for port in recirculation_port_list: + rules_list.extend( + rules_set_up_port( + port=port, + port_type=port_type, + fwd_type=fwd_type, + vlan_id=vlan_id, + action=action + ) + ) + + LOGGER.debug("INT recirculation ports configured:{}".format(recirculation_port_list)) + + return rules_list + +def rules_set_up_int_report_flow( + switch_id : int, + src_ip : str, + int_collector_ip : str, + int_collector_port : int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert switch_id > 0, "Invalid switch identifier to configure egress INT report" + assert chk_address_ipv4(src_ip), "Invalid source IPv4 address to configure egress INT report" + assert chk_address_ipv4(int_collector_ip), "Invalid INT collector IPv4 address to configure egress INT report" + assert chk_transport_port(int_collector_port), "Invalid INT collector port number to configure egress INT report" + + rule_no = cache_rule(TABLE_INT_EGRESS_REPORT, action) + + rules_int_egress = [] + + # Rule #1 + rules_int_egress.append( + json_config_rule( + action, + '/tables/table/'+TABLE_INT_EGRESS_REPORT+'['+str(rule_no)+']', + { + 'table-name': TABLE_INT_EGRESS_REPORT, + 'match-fields': [ + { + 'match-field': 'bmd_type', + 'match-value': str(BRIDGED_MD_TYPE_INT_INGRESS_DROP) + }, + { + 'match-field': 'mirror_type', + 'match-value': str(MIRROR_TYPE_INVALID) + }, + { + 'match-field': 'int_report_type', + 'match-value': str(INT_REPORT_TYPE_DROP) + } + ], + 'action-name': 'FabricEgress.int_egress.do_drop_report_encap', + 'action-params': [ + { + 'action-param': 'switch_id', + 'action-value': str(switch_id) + }, + { + 'action-param': 'src_ip', + 'action-value': src_ip + }, + { + 'action-param': 'mon_ip', + 'action-value': int_collector_ip + }, + { + 'action-param': 'mon_port', + 'action-value': str(int_collector_port) + } + ] + } + ) + ) + + rule_no = cache_rule(TABLE_INT_EGRESS_REPORT, action) + + # Rule #2 + rules_int_egress.append( + json_config_rule( + action, + '/tables/table/'+TABLE_INT_EGRESS_REPORT+'['+str(rule_no)+']', + { + 'table-name': TABLE_INT_EGRESS_REPORT, + 'match-fields': [ + { + 'match-field': 'bmd_type', + 'match-value': str(BRIDGED_MD_TYPE_EGRESS_MIRROR) + }, + { + 'match-field': 'mirror_type', + 'match-value': str(MIRROR_TYPE_INT_REPORT) + }, + { + 'match-field': 'int_report_type', + 'match-value': str(INT_REPORT_TYPE_DROP) + } + ], + 'action-name': 'FabricEgress.int_egress.do_drop_report_encap', + 'action-params': [ + { + 'action-param': 'switch_id', + 'action-value': str(switch_id) + }, + { + 'action-param': 'src_ip', + 'action-value': src_ip + }, + { + 'action-param': 'mon_ip', + 'action-value': int_collector_ip + }, + { + 'action-param': 'mon_port', + 'action-value': str(int_collector_port) + } + ] + } + ) + ) + + rule_no = cache_rule(TABLE_INT_EGRESS_REPORT, action) + + # Rule #3 + rules_int_egress.append( + json_config_rule( + action, + '/tables/table/'+TABLE_INT_EGRESS_REPORT+'['+str(rule_no)+']', + { + 'table-name': TABLE_INT_EGRESS_REPORT, + 'match-fields': [ + { + 'match-field': 'bmd_type', + 'match-value': str(BRIDGED_MD_TYPE_EGRESS_MIRROR) + }, + { + 'match-field': 'mirror_type', + 'match-value': str(MIRROR_TYPE_INT_REPORT) + }, + { + 'match-field': 'int_report_type', + 'match-value': str(INT_REPORT_TYPE_FLOW) + } + ], + 'action-name': 'FabricEgress.int_egress.do_local_report_encap', + 'action-params': [ + { + 'action-param': 'switch_id', + 'action-value': str(switch_id) + }, + { + 'action-param': 'src_ip', + 'action-value': src_ip + }, + { + 'action-param': 'mon_ip', + 'action-value': int_collector_ip + }, + { + 'action-param': 'mon_port', + 'action-value': str(int_collector_port) + } + ] + } + ) + ) + + rule_no = cache_rule(TABLE_INT_EGRESS_REPORT, action) + + # Rule #4 + rules_int_egress.append( + json_config_rule( + action, + '/tables/table/'+TABLE_INT_EGRESS_REPORT+'['+str(rule_no)+']', + { + 'table-name': TABLE_INT_EGRESS_REPORT, + 'match-fields': [ + { + 'match-field': 'bmd_type', + 'match-value': str(BRIDGED_MD_TYPE_DEFLECTED) + }, + { + 'match-field': 'mirror_type', + 'match-value': str(MIRROR_TYPE_INVALID) + }, + { + 'match-field': 'int_report_type', + 'match-value': str(INT_REPORT_TYPE_DROP) + } + ], + 'action-name': 'FabricEgress.int_egress.do_drop_report_encap', + 'action-params': [ + { + 'action-param': 'switch_id', + 'action-value': str(switch_id) + }, + { + 'action-param': 'src_ip', + 'action-value': src_ip + }, + { + 'action-param': 'mon_ip', + 'action-value': int_collector_ip + }, + { + 'action-param': 'mon_port', + 'action-value': str(int_collector_port) + } + ] + } + ) + ) + + rule_no = cache_rule(TABLE_INT_EGRESS_REPORT, action) + + # Rule #5 + rules_int_egress.append( + json_config_rule( + action, + '/tables/table/'+TABLE_INT_EGRESS_REPORT+'['+str(rule_no)+']', + { + 'table-name': TABLE_INT_EGRESS_REPORT, + 'match-fields': [ + { + 'match-field': 'bmd_type', + 'match-value': str(BRIDGED_MD_TYPE_EGRESS_MIRROR) + }, + { + 'match-field': 'mirror_type', + 'match-value': str(MIRROR_TYPE_INT_REPORT) + }, + { + 'match-field': 'int_report_type', + 'match-value': str(INT_REPORT_TYPE_QUEUE) + } + ], + 'action-name': 'FabricEgress.int_egress.do_local_report_encap', + 'action-params': [ + { + 'action-param': 'switch_id', + 'action-value': str(switch_id) + }, + { + 'action-param': 'src_ip', + 'action-value': src_ip + }, + { + 'action-param': 'mon_ip', + 'action-value': int_collector_ip + }, + { + 'action-param': 'mon_port', + 'action-value': str(int_collector_port) + } + ] + } + ) + ) + + rule_no = cache_rule(TABLE_INT_EGRESS_REPORT, action) + + # Rule #6 + rules_int_egress.append( + json_config_rule( + action, + '/tables/table/'+TABLE_INT_EGRESS_REPORT+'['+str(rule_no)+']', + { + 'table-name': TABLE_INT_EGRESS_REPORT, + 'match-fields': [ + { + 'match-field': 'bmd_type', + 'match-value': str(BRIDGED_MD_TYPE_EGRESS_MIRROR) + }, + { + 'match-field': 'mirror_type', + 'match-value': str(MIRROR_TYPE_INT_REPORT) + }, + { + 'match-field': 'int_report_type', + 'match-value': str(INT_REPORT_TYPE_QUEUE | INT_REPORT_TYPE_FLOW) + } + ], + 'action-name': 'FabricEgress.int_egress.do_local_report_encap', + 'action-params': [ + { + 'action-param': 'switch_id', + 'action-value': str(switch_id) + }, + { + 'action-param': 'src_ip', + 'action-value': src_ip + }, + { + 'action-param': 'mon_ip', + 'action-value': int_collector_ip + }, + { + 'action-param': 'mon_port', + 'action-value': str(int_collector_port) + } + ] + } + ) + ) + + return rules_int_egress diff --git a/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_service_handler.py b/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_service_handler.py new file mode 100644 index 000000000..efdac5fea --- /dev/null +++ b/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_service_handler.py @@ -0,0 +1,467 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Service handler for P4-based In-band Network Telemetry (INT) v0.5. +The spec. is based on P4.org Application WG INT Dataplane +Specification v0.5 (2017-12): + +https://p4.org/p4-spec/docs/INT_v0_5.pdf +""" + +import logging +from typing import Any, List, Optional, Tuple, Union +from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method +from common.proto.context_pb2 import ConfigActionEnum, DeviceId, Service, Device +from common.tools.object_factory.Device import json_device_id +from common.type_checkers.Checkers import chk_type, chk_address_mac, chk_address_ipv4,\ + chk_transport_port, chk_vlan_id +from service.service.service_handler_api._ServiceHandler import _ServiceHandler +from service.service.service_handler_api.SettingsHandler import SettingsHandler +from service.service.service_handlers.p4_fabric_tna_commons.p4_fabric_tna_commons import * +from service.service.task_scheduler.TaskExecutor import TaskExecutor + +from .p4_fabric_tna_int_config import * + +LOGGER = logging.getLogger(__name__) + +METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'p4_fabric_tna_int'}) + +class P4FabricINTServiceHandler(_ServiceHandler): + def __init__( # pylint: disable=super-init-not-called + self, service : Service, task_executor : TaskExecutor, **settings # type: ignore + ) -> None: + """ Initialize Driver. + Parameters: + service + The service instance (gRPC message) to be managed. + task_executor + An instance of Task Executor providing access to the + service handlers factory, the context and device clients, + and an internal cache of already-loaded gRPC entities. + **settings + Extra settings required by the service handler. + + """ + self.__service_label = "P4 In-band Network Telemetry (INT) connectivity service" + self.__service = service + self.__task_executor = task_executor + self.__settings_handler = SettingsHandler(self.__service.service_config, **settings) + + self._init_settings() + self._parse_settings() + self._print_settings() + + @metered_subclass_method(METRICS_POOL) + def SetEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + """ Create/Update service endpoints from a list. + Parameters: + endpoints: List[Tuple[str, str, Optional[str]]] + List of tuples, each containing a device_uuid, + endpoint_uuid and, optionally, the topology_uuid + of the endpoint to be added. + connection_uuid : Optional[str] + If specified, is the UUID of the connection this endpoint is associated to. + Returns: + results: List[Union[bool, Exception]] + List of results for endpoint changes requested. + Return values must be in the same order as the requested + endpoints. If an endpoint is properly added, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('endpoints', endpoints, list) + if len(endpoints) == 0: return [] + + LOGGER.info("{} - Provision service configuration".format( + self.__service_label)) + + visited = set() + results = [] + for endpoint in endpoints: + device_uuid, _ = endpoint[0:2] + device = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + + # Skip already visited devices + if device.name in visited: + continue + LOGGER.info("Device {} - Setting up In-band Network Telemetry (INT) configuration".format( + device.name)) + + rules = [] + actual_rules = -1 + applied_rules, failed_rules = 0, -1 + + # Create and apply rules + try: + rules = self._create_rules(device_obj=device, action=ConfigActionEnum.CONFIGACTION_SET) + actual_rules = len(rules) + applied_rules, failed_rules = apply_rules( + task_executor=self.__task_executor, + device_obj=device, + json_config_rules=rules + ) + except Exception as ex: + LOGGER.error("Failed to insert INT rules on device {} due to {}".format(device.name, ex)) + finally: + rules.clear() + + # Ensure correct status + results.append(True) if (failed_rules == 0) and (applied_rules == actual_rules) \ + else results.append(False) + + # You should no longer visit this device again + visited.add(device.name) + + LOGGER.info("Installed {}/{} INT rules on device {}".format( + applied_rules, actual_rules, device.name)) + + return results + + @metered_subclass_method(METRICS_POOL) + def DeleteEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + """ Delete service endpoints from a list. + Parameters: + endpoints: List[Tuple[str, str, Optional[str]]] + List of tuples, each containing a device_uuid, + endpoint_uuid, and the topology_uuid of the endpoint + to be removed. + connection_uuid : Optional[str] + If specified, is the UUID of the connection this endpoint is associated to. + Returns: + results: List[Union[bool, Exception]] + List of results for endpoint deletions requested. + Return values must be in the same order as the requested + endpoints. If an endpoint is properly deleted, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('endpoints', endpoints, list) + if len(endpoints) == 0: return [] + + LOGGER.info("{} - Deprovision service configuration".format( + self.__service_label)) + + visited = set() + results = [] + for endpoint in endpoints: + device_uuid, _ = endpoint[0:2] + device = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + + # Skip already visited devices + if device.name in visited: + continue + LOGGER.info("Device {} - Removing In-band Network Telemetry (INT) configuration".format( + device.name)) + + rules = [] + actual_rules = -1 + applied_rules, failed_rules = 0, -1 + + # Create and apply rules + try: + rules = self._create_rules(device_obj=device, action=ConfigActionEnum.CONFIGACTION_DELETE) + actual_rules = len(rules) + applied_rules, failed_rules = apply_rules( + task_executor=self.__task_executor, device_obj=device, json_config_rules=rules) + except Exception as ex: + LOGGER.error("Failed to delete INT rules from device {} due to {}".format(device.name, ex)) + finally: + rules.clear() + + # Ensure correct status + results.append(True) if (failed_rules == 0) and (applied_rules == actual_rules) \ + else results.append(False) + + # You should no longer visit this device again + visited.add(device.name) + + LOGGER.info("Deleted {}/{} INT rules from device {}".format( + applied_rules, actual_rules, device.name)) + + return results + + @metered_subclass_method(METRICS_POOL) + def SetConstraint(self, constraints: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Create/Update service constraints. + Parameters: + constraints: List[Tuple[str, Any]] + List of tuples, each containing a constraint_type and the + new constraint_value to be set. + Returns: + results: List[Union[bool, Exception]] + List of results for constraint changes requested. + Return values must be in the same order as the requested + constraints. If a constraint is properly set, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('constraints', constraints, list) + if len(constraints) == 0: return [] + + msg = '[SetConstraint] Method not implemented. Constraints({:s}) are being ignored.' + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @metered_subclass_method(METRICS_POOL) + def DeleteConstraint(self, constraints: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Delete service constraints. + Parameters: + constraints: List[Tuple[str, Any]] + List of tuples, each containing a constraint_type pointing + to the constraint to be deleted, and a constraint_value + containing possible additionally required values to locate + the constraint to be removed. + Returns: + results: List[Union[bool, Exception]] + List of results for constraint deletions requested. + Return values must be in the same order as the requested + constraints. If a constraint is properly deleted, True must + be returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('constraints', constraints, list) + if len(constraints) == 0: return [] + + msg = '[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored.' + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @metered_subclass_method(METRICS_POOL) + def SetConfig(self, resources: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Create/Update configuration for a list of service resources. + Parameters: + resources: List[Tuple[str, Any]] + List of tuples, each containing a resource_key pointing to + the resource to be modified, and a resource_value + containing the new value to be set. + Returns: + results: List[Union[bool, Exception]] + List of results for resource key changes requested. + Return values must be in the same order as the requested + resource keys. If a resource is properly set, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('resources', resources, list) + if len(resources) == 0: return [] + + msg = '[SetConfig] Method not implemented. Resources({:s}) are being ignored.' + LOGGER.warning(msg.format(str(resources))) + return [True for _ in range(len(resources))] + + @metered_subclass_method(METRICS_POOL) + def DeleteConfig(self, resources: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Delete configuration for a list of service resources. + Parameters: + resources: List[Tuple[str, Any]] + List of tuples, each containing a resource_key pointing to + the resource to be modified, and a resource_value containing + possible additionally required values to locate the value + to be removed. + Returns: + results: List[Union[bool, Exception]] + List of results for resource key deletions requested. + Return values must be in the same order as the requested + resource keys. If a resource is properly deleted, True must + be returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('resources', resources, list) + if len(resources) == 0: return [] + + msg = '[SetConfig] Method not implemented. Resources({:s}) are being ignored.' + LOGGER.warning(msg.format(str(resources))) + return [True for _ in range(len(resources))] + + def _init_settings(self): + self.__switch_info = {} + self.__int_collector_info = {} + self.__int_collector_mac = "" + self.__int_collector_ip = "" + self.__int_collector_port = -1 + self.__int_vlan_id = -1 + + try: + self.__settings = self.__settings_handler.get('/settings') + LOGGER.info("{} with settings: {}".format(self.__service_label, self.__settings)) + except Exception as ex: + self.__settings = {} + LOGGER.error("Failed to parse service settings: {}".format(ex)) + + def _default_settings(self): + switch_info = { + "p4-sw1": { + ARCH: TARGET_ARCH_V1MODEL, + DPID: 1, + MAC: "fa:16:3e:93:8c:c0", + IP: "10.10.10.120", + PORT_INT: { + PORT_ID: 3, + PORT_TYPE: "host" + }, + RECIRCULATION_PORT_LIST: RECIRCULATION_PORTS_V1MODEL, + INT_REPORT_MIRROR_ID_LIST: INT_REPORT_MIRROR_ID_LIST_V1MODEL + } + } + int_collector_info = { + MAC: "fa:16:3e:fb:cf:96", + IP: "10.10.10.41", + PORT: 32766, + VLAN_ID: 4094 + } + self.__settings = { + SWITCH_INFO: switch_info, + INT_COLLECTOR_INFO: int_collector_info + } + + def _parse_settings(self): + #TODO: Pass settings in a correct way + try: + self.__switch_info = self.__settings[SWITCH_INFO] + except Exception as ex: + LOGGER.error("Failed to parse settings: {}".format(ex)) + self._default_settings() #TODO: Remove when bug is fixed + self.__switch_info = self.__settings[SWITCH_INFO] + assert isinstance(self.__switch_info, dict), "Switch info object must be a map with switch names as keys" + + for switch_name, switch_info in self.__switch_info.items(): + assert switch_name, "Invalid P4 switch name" + assert isinstance(switch_info, dict), "Switch {} info must be a map with arch, dpid, mac, ip, and int_port items)" + assert switch_info[ARCH] in SUPPORTED_TARGET_ARCH_LIST, \ + "Switch {} - Supported P4 architectures are: {}".format(switch_name, ','.join(SUPPORTED_TARGET_ARCH_LIST)) + assert switch_info[DPID] > 0, "Switch {} - P4 switch dataplane ID must be a positive integer".format(switch_name, switch_info[DPID]) + assert chk_address_mac(switch_info[MAC]), "Switch {} - Invalid source Ethernet address".format(switch_name) + assert chk_address_ipv4(switch_info[IP]), "Switch {} - Invalid source IP address".format(switch_name) + assert isinstance(switch_info[PORT_INT], dict), "Switch {} - INT port object must be a map with port_id and port_type items".format(switch_name) + assert switch_info[PORT_INT][PORT_ID] >= 0, "Switch {} - Invalid P4 switch port ID".format(switch_name) + assert switch_info[PORT_INT][PORT_TYPE] in PORT_TYPES_STR_VALID, "Switch {} - Valid P4 switch port types are: {}".format( + switch_name, ','.join(PORT_TYPES_STR_VALID)) + if arch_tna(switch_info[ARCH]): + switch_info[RECIRCULATION_PORT_LIST] = RECIRCULATION_PORTS_TNA + switch_info[INT_REPORT_MIRROR_ID_LIST] = INT_REPORT_MIRROR_ID_LIST_TNA + else: + switch_info[RECIRCULATION_PORT_LIST] = RECIRCULATION_PORTS_V1MODEL + switch_info[INT_REPORT_MIRROR_ID_LIST] = INT_REPORT_MIRROR_ID_LIST_V1MODEL + assert isinstance(switch_info[RECIRCULATION_PORT_LIST], list), "Switch {} - Recirculation ports must be described as a list".format(switch_name) + + self.__int_collector_info = self.__settings[INT_COLLECTOR_INFO] + assert isinstance(self.__int_collector_info, dict), "INT collector info object must be a map with mac, ip, port, and vlan_id keys)" + + self.__int_collector_mac = self.__int_collector_info[MAC] + assert chk_address_mac(self.__int_collector_mac), "Invalid P4 INT collector MAC address" + + self.__int_collector_ip = self.__int_collector_info[IP] + assert chk_address_ipv4(self.__int_collector_ip), "Invalid P4 INT collector IPv4 address" + + self.__int_collector_port = self.__int_collector_info[PORT] + assert chk_transport_port(self.__int_collector_port), "Invalid P4 INT collector transport port" + + self.__int_vlan_id = self.__int_collector_info[VLAN_ID] + assert chk_vlan_id(self.__int_vlan_id), "Invalid VLAN ID" + + def _print_settings(self): + LOGGER.info("-------------------- {} settings --------------------".format(self.__service.name)) + LOGGER.info("--- Topology info") + for switch_name, switch_info in self.__switch_info.items(): + LOGGER.info("\t Device {}".format(switch_name)) + LOGGER.info("\t\t| Target P4 architecture: {}".format(switch_info[ARCH])) + LOGGER.info("\t\t| Data plane ID: {}".format(switch_info[DPID])) + LOGGER.info("\t\t| Source MAC address: {}".format(switch_info[MAC])) + LOGGER.info("\t\t| Source IP address: {}".format(switch_info[IP])) + LOGGER.info("\t\t| INT port ID: {}".format(switch_info[PORT_INT][PORT_ID])) + LOGGER.info("\t\t| INT port type: {}".format(switch_info[PORT_INT][PORT_TYPE])) + LOGGER.info("\t\t| Recirculation port list: {}".format(switch_info[RECIRCULATION_PORT_LIST])) + LOGGER.info("\t\t| Report mirror ID list: {}".format(switch_info[INT_REPORT_MIRROR_ID_LIST])) + LOGGER.info("--- INT collector MAC: {}".format(self.__int_collector_mac)) + LOGGER.info("--- INT collector IP: {}".format(self.__int_collector_ip)) + LOGGER.info("--- INT collector port: {}".format(self.__int_collector_port)) + LOGGER.info("--- INT VLAN ID: {}".format(self.__int_vlan_id)) + LOGGER.info("-----------------------------------------------------------------") + + def _create_rules(self, device_obj : Device, action : ConfigActionEnum): # type: ignore + dev_name = device_obj.name + rules = [] + + try: + ### INT reporting rules + rules += rules_set_up_int_watchlist(action=action) + rules += rules_set_up_int_recirculation_ports( + recirculation_port_list=self.__switch_info[dev_name][RECIRCULATION_PORT_LIST], + port_type=PORT_TYPE_INT, + fwd_type=FORWARDING_TYPE_UNICAST_IPV4, + vlan_id=self.__int_vlan_id, + action=action + ) + rules += rules_set_up_int_report_collector( + int_collector_ip=self.__int_collector_ip, + action=action + ) + rules += rules_set_up_int_report_flow( + switch_id=self.__switch_info[dev_name][DPID], + src_ip=self.__switch_info[dev_name][IP], + int_collector_ip=self.__int_collector_ip, + int_collector_port=self.__int_collector_port, + action=action + ) + rules += rules_set_up_report_mirror_flow( + recirculation_port_list=self.__switch_info[dev_name][RECIRCULATION_PORT_LIST], + report_mirror_id_list=self.__switch_info[dev_name][INT_REPORT_MIRROR_ID_LIST], + action=action + ) + ### INT port setup rules + rules += rules_set_up_port( + port=self.__switch_info[dev_name][PORT_INT][PORT_ID], + port_type=PORT_TYPE_HOST, + fwd_type=FORWARDING_TYPE_BRIDGING, + vlan_id=self.__int_vlan_id, + action=action + ) + ### INT port forwarding rules + rules += rules_set_up_fwd_bridging( + vlan_id=self.__int_vlan_id, + eth_dst=self.__int_collector_mac, + egress_port=self.__switch_info[dev_name][PORT_INT][PORT_ID], + action=action + ) + rules += rules_set_up_next_output_simple( + egress_port=self.__switch_info[dev_name][PORT_INT][PORT_ID], + action=action + ) + ### INT packet routing rules + rules += rules_set_up_next_routing_simple( + egress_port=self.__switch_info[dev_name][PORT_INT][PORT_ID], + eth_src=self.__switch_info[dev_name][MAC], + eth_dst=self.__int_collector_mac, + action=action + ) + rules += rules_set_up_routing( + ipv4_dst=self.__int_collector_ip, + ipv4_prefix_len=32, + egress_port=self.__switch_info[dev_name][PORT_INT][PORT_ID], + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating rules") + raise Exception(ex) + + return rules diff --git a/src/tests/p4-fabric-tna/README.md b/src/tests/p4-fabric-tna/README.md index fc49276a9..10303009d 100644 --- a/src/tests/p4-fabric-tna/README.md +++ b/src/tests/p4-fabric-tna/README.md @@ -120,6 +120,25 @@ cd ~/tfs-ctrl/ bash src/tests/p4-fabric-tna/run_test_02b_sbi_deprovision_int_l2_l3_acl.sh ``` +### Step 3: Manage L2, L3, ACL, and INT via the Service API + +To avoid interacting with the switch using low-level P4 rules (via the SBI), we created modular network services, which allow users to easily provision L2, L3, ACL, and INT network functions. +These services require users to define the service endpoints as well as some high-level service configuration, while leaving the rest of complexity to tailored service handlers that interact with the SBI on behalf of the user. + +#### Provision INT service via the Service API + +```shell +cd ~/tfs-ctrl/ +bash src/tests/p4-fabric-tna/run_test_06a_service_provision_int.sh +``` + +#### Deprovision INT service via the Service API + +```shell +cd ~/tfs-ctrl/ +bash src/tests/p4-fabric-tna/run_test_06b_service_deprovision_int.sh +``` + ### Step 4: Deprovision topology Delete all the objects (context, topology, links, devices) from TFS: diff --git a/src/tests/p4-fabric-tna/descriptors/service-create-int.json b/src/tests/p4-fabric-tna/descriptors/service-create-int.json new file mode 100644 index 000000000..98956b959 --- /dev/null +++ b/src/tests/p4-fabric-tna/descriptors/service-create-int.json @@ -0,0 +1,53 @@ +{ + "services": [ + { + "service_id": { + "context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "p4-service-int"} + }, + "name": "p4-service-int", + "service_type": "SERVICETYPE_INT", + "service_status": {"service_status": "SERVICESTATUS_PLANNED"}, + "service_endpoint_ids": [ + { + "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, + "endpoint_uuid": {"uuid": "1"} + }, + { + "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, + "endpoint_uuid": {"uuid": "2"} + } + ], + "service_config": { + "config_rules": [ + { + "action": "CONFIGACTION_SET", + "custom": { + "resource_key": "/settings", + "resource_value": { + "switch_info": { + "p4-sw1": { + "arch": "v1model", + "dpid": 1, + "mac": "fa:16:3e:93:8c:c0", + "ip": "10.10.10.120", + "int_port": { + "port_id": 3, + "port_type": "host" + } + } + }, + "int_collector_info": { + "mac": "fa:16:3e:fb:cf:96", + "ip": "10.10.10.41", + "port": 32766, + "vlan_id": 4094 + } + } + } + } + ] + }, + "service_constraints": [] + } + ] +} diff --git a/src/tests/p4-fabric-tna/run_test_06a_service_provision_int.sh b/src/tests/p4-fabric-tna/run_test_06a_service_provision_int.sh new file mode 100755 index 000000000..12bc82352 --- /dev/null +++ b/src/tests/p4-fabric-tna/run_test_06a_service_provision_int.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source tfs_runtime_env_vars.sh +python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_int.py diff --git a/src/tests/p4-fabric-tna/run_test_06b_service_deprovision_int.sh b/src/tests/p4-fabric-tna/run_test_06b_service_deprovision_int.sh new file mode 100755 index 000000000..a501de770 --- /dev/null +++ b/src/tests/p4-fabric-tna/run_test_06b_service_deprovision_int.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source tfs_runtime_env_vars.sh +python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_int.py diff --git a/src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_int.py b/src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_int.py new file mode 100644 index 000000000..f29f6b17c --- /dev/null +++ b/src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_int.py @@ -0,0 +1,78 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from common.proto.context_pb2 import ServiceId, ServiceStatusEnum, ServiceTypeEnum +from common.tools.grpc.Tools import grpc_message_to_json_string +from common.tools.object_factory.Service import json_service_id +from context.client.ContextClient import ContextClient +from service.client.ServiceClient import ServiceClient +from tests.Fixtures import context_client, service_client # pylint: disable=unused-import +from tests.tools.test_tools_p4 import * + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +def test_service_deletion_int( + context_client : ContextClient, # pylint: disable=redefined-outer-name + service_client : ServiceClient # pylint: disable=redefined-outer-name +) -> None: + # Get the current number of devices + response = context_client.ListDevices(ADMIN_CONTEXT_ID) + LOGGER.warning('Devices[{:d}] = {:s}'.format(len(response.devices), grpc_message_to_json_string(response))) + + # Total devices + dev_nb = len(response.devices) + assert dev_nb == DEV_NB + + # P4 devices + p4_dev_nb = identify_number_of_p4_devices(response.devices) + assert p4_dev_nb == P4_DEV_NB + + # Get the current number of rules in the P4 devices + p4_rules_before_deletion = get_number_of_rules(response.devices) + + # Get the current number of services + response = context_client.ListServices(ADMIN_CONTEXT_ID) + services_nb_before_deletion = len(response.services) + assert verify_active_service_type(response.services, ServiceTypeEnum.SERVICETYPE_INT) + + for service in response.services: + # Ignore services of other types + if service.service_type != ServiceTypeEnum.SERVICETYPE_INT: + continue + + service_id = service.service_id + assert service_id + + service_uuid = service_id.service_uuid.uuid + context_uuid = service_id.context_id.context_uuid.uuid + assert service.service_status.service_status == ServiceStatusEnum.SERVICESTATUS_ACTIVE + + # Delete INT service + service_client.DeleteService(ServiceId(**json_service_id(service_uuid, json_context_id(context_uuid)))) + + # Get an updated view of the services + response = context_client.ListServices(ADMIN_CONTEXT_ID) + services_nb_after_deletion = len(response.services) + assert services_nb_after_deletion == services_nb_before_deletion - 1, "Exactly one new service must be deleted" + + # Get an updated view of the devices + response = context_client.ListDevices(ADMIN_CONTEXT_ID) + p4_rules_after_deletion = get_number_of_rules(response.devices) + + rules_diff = p4_rules_before_deletion - p4_rules_after_deletion + + assert p4_rules_after_deletion < p4_rules_before_deletion, "INT service must contain some rules" + assert rules_diff == P4_DEV_NB * INT_RULES, "INT service must contain {} rules per device".format(INT_RULES) diff --git a/src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_int.py b/src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_int.py new file mode 100644 index 000000000..7a875c66a --- /dev/null +++ b/src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_int.py @@ -0,0 +1,73 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from common.proto.context_pb2 import ServiceTypeEnum +from common.tools.descriptor.Loader import DescriptorLoader, check_descriptor_load_results +from common.tools.grpc.Tools import grpc_message_to_json_string +from context.client.ContextClient import ContextClient +from device.client.DeviceClient import DeviceClient +from service.client.ServiceClient import ServiceClient +from tests.Fixtures import context_client, device_client, service_client # pylint: disable=unused-import +from tests.tools.test_tools_p4 import * + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +def test_service_creation_int( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + service_client : ServiceClient # pylint: disable=redefined-outer-name +) -> None: + # Get the current number of services + response = context_client.ListServices(ADMIN_CONTEXT_ID) + services_nb_before = len(response.services) + + # Get the current number of devices + response = context_client.ListDevices(ADMIN_CONTEXT_ID) + LOGGER.warning('Devices[{:d}] = {:s}'.format(len(response.devices), grpc_message_to_json_string(response))) + + # Total devices + dev_nb = len(response.devices) + assert dev_nb == DEV_NB + + # P4 devices + p4_dev_nb = identify_number_of_p4_devices(response.devices) + assert p4_dev_nb == P4_DEV_NB + + # Get the current number of rules in the P4 devices + p4_rules_before = get_number_of_rules(response.devices) + + # Load service + descriptor_loader = DescriptorLoader( + descriptors_file=DESC_FILE_SERVICE_CREATE_INT, + context_client=context_client, device_client=device_client, service_client=service_client + ) + results = descriptor_loader.process() + check_descriptor_load_results(results, descriptor_loader) + + # Get an updated view of the services + response = context_client.ListServices(ADMIN_CONTEXT_ID) + services_nb_after = len(response.services) + assert services_nb_after == services_nb_before + 1, "Exactly one new service must be in place" + assert verify_active_service_type(response.services, ServiceTypeEnum.SERVICETYPE_INT) + + # Get an updated view of the devices + response = context_client.ListDevices(ADMIN_CONTEXT_ID) + p4_rules_after = get_number_of_rules(response.devices) + + rules_diff = p4_rules_after - p4_rules_before + + assert p4_rules_after > p4_rules_before, "INT service must install some rules" + assert rules_diff == P4_DEV_NB * INT_RULES, "INT service must install {} rules per device".format(INT_RULES) -- GitLab From e071dfc4d9ef68a79e7c828c9bc251f682ae4896 Mon Sep 17 00:00:00 2001 From: "Georgios P. Katsikas" <gkatsikas@ubitech.eu> Date: Mon, 31 Mar 2025 07:12:29 +0000 Subject: [PATCH 07/14] feat: P4 L2 simple service handler --- .../service/service_handlers/__init__.py | 7 + .../p4_fabric_tna_l2_simple/__init__.py | 13 + .../p4_fabric_tna_l2_simple_config.py | 69 +++ ...p4_fabric_tna_l2_simple_service_handler.py | 518 ++++++++++++++++++ src/tests/p4-fabric-tna/README.md | 14 + .../descriptors/service-create-l2-simple.json | 63 +++ .../run_test_03a_service_provision_l2.sh | 17 + .../run_test_03b_service_deprovision_l2.sh | 17 + .../test_functional_service_deprovision_l2.py | 78 +++ .../test_functional_service_provision_l2.py | 73 +++ src/tests/tools/test_tools_p4.py | 6 +- 11 files changed, 872 insertions(+), 3 deletions(-) create mode 100644 src/service/service/service_handlers/p4_fabric_tna_l2_simple/__init__.py create mode 100644 src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_config.py create mode 100644 src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_service_handler.py create mode 100644 src/tests/p4-fabric-tna/descriptors/service-create-l2-simple.json create mode 100755 src/tests/p4-fabric-tna/run_test_03a_service_provision_l2.sh create mode 100755 src/tests/p4-fabric-tna/run_test_03b_service_deprovision_l2.sh create mode 100644 src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_l2.py create mode 100644 src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_l2.py diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py index 76107f03d..756d0fffb 100644 --- a/src/service/service/service_handlers/__init__.py +++ b/src/service/service/service_handlers/__init__.py @@ -27,6 +27,7 @@ from .l3slice_ietfslice.L3SliceIETFSliceServiceHandler import L3NMSliceIETFSlice from .microwave.MicrowaveServiceHandler import MicrowaveServiceHandler from .p4_dummy_l1.p4_dummy_l1_service_handler import P4DummyL1ServiceHandler from .p4_fabric_tna_int.p4_fabric_tna_int_service_handler import P4FabricINTServiceHandler +from .p4_fabric_tna_l2_simple.p4_fabric_tna_l2_simple_service_handler import P4FabricL2SimpleServiceHandler from .tapi_tapi.TapiServiceHandler import TapiServiceHandler from .tapi_xr.TapiXrServiceHandler import TapiXrServiceHandler from .optical_tfs.OpticalTfsServiceHandler import OpticalTfsServiceHandler @@ -118,6 +119,12 @@ SERVICE_HANDLERS = [ FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4, } ]), + (P4FabricL2SimpleServiceHandler, [ + { + FilterFieldEnum.SERVICE_TYPE: ServiceTypeEnum.SERVICETYPE_L2NM, + FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4, + } + ]), (L2NM_IETFL2VPN_ServiceHandler, [ { FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_L2NM, diff --git a/src/service/service/service_handlers/p4_fabric_tna_l2_simple/__init__.py b/src/service/service/service_handlers/p4_fabric_tna_l2_simple/__init__.py new file mode 100644 index 000000000..023830645 --- /dev/null +++ b/src/service/service/service_handlers/p4_fabric_tna_l2_simple/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_config.py b/src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_config.py new file mode 100644 index 000000000..5e2aed066 --- /dev/null +++ b/src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_config.py @@ -0,0 +1,69 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Common objects and methods for L2 forwarding based on the SD-Fabric dataplane model. +This dataplane covers both software based and hardware-based Stratum-enabled P4 switches, +such as the BMv2 software switch and Intel's Tofino/Tofino-2 switches. + +SD-Fabric repo: https://github.com/stratum/fabric-tna +SD-Fabric docs: https://docs.sd-fabric.org/master/index.html +""" + +import logging +from common.proto.context_pb2 import ConfigActionEnum + +from service.service.service_handlers.p4_fabric_tna_commons.p4_fabric_tna_commons import * + +LOGGER = logging.getLogger(__name__) + +# L2 simple service handler settings +FORWARDING_LIST = "fwd_list" +HOST_MAC = "host_mac" + +def rules_set_up_port_host( + port : int, + vlan_id : int, + action : ConfigActionEnum, # type: ignore + fwd_type=FORWARDING_TYPE_BRIDGING, + eth_type=ETHER_TYPE_IPV4): + # This is a host facing port + port_type = PORT_TYPE_HOST + + return rules_set_up_port( + port=port, + port_type=port_type, + fwd_type=fwd_type, + vlan_id=vlan_id, + action=action, + eth_type=eth_type + ) + +def rules_set_up_port_switch( + port : int, + vlan_id : int, + action : ConfigActionEnum, # type: ignore + fwd_type=FORWARDING_TYPE_BRIDGING, + eth_type=ETHER_TYPE_IPV4): + # This is a switch facing port + port_type = PORT_TYPE_SWITCH + + return rules_set_up_port( + port=port, + port_type=port_type, + fwd_type=fwd_type, + vlan_id=vlan_id, + action=action, + eth_type=eth_type + ) diff --git a/src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_service_handler.py b/src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_service_handler.py new file mode 100644 index 000000000..c25fb087c --- /dev/null +++ b/src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_service_handler.py @@ -0,0 +1,518 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Service handler for P4-based L2 forwarding using the SD-Fabric P4 dataplane +for BMv2 and Intel Tofino switches. +""" + +import logging +from typing import Any, List, Dict, Optional, Tuple, Union +from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method +from common.proto.context_pb2 import ConfigActionEnum, DeviceId, Service, Device +from common.tools.object_factory.Device import json_device_id +from common.type_checkers.Checkers import chk_type, chk_address_mac, chk_vlan_id +from service.service.service_handler_api._ServiceHandler import _ServiceHandler +from service.service.service_handler_api.SettingsHandler import SettingsHandler +from service.service.service_handlers.p4_fabric_tna_commons.p4_fabric_tna_commons import * +from service.service.task_scheduler.TaskExecutor import TaskExecutor + +from .p4_fabric_tna_l2_simple_config import * + +LOGGER = logging.getLogger(__name__) + +METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'p4_fabric_tna_l2_simple'}) + +class P4FabricL2SimpleServiceHandler(_ServiceHandler): + def __init__( # pylint: disable=super-init-not-called + self, service : Service, task_executor : TaskExecutor, **settings # type: ignore + ) -> None: + """ Initialize Driver. + Parameters: + service + The service instance (gRPC message) to be managed. + task_executor + An instance of Task Executor providing access to the + service handlers factory, the context and device clients, + and an internal cache of already-loaded gRPC entities. + **settings + Extra settings required by the service handler. + + """ + self.__service_label = "P4 L2 simple connectivity service" + self.__service = service + self.__task_executor = task_executor + self.__settings_handler = SettingsHandler(self.__service.service_config, **settings) + + self._init_settings() + self._parse_settings() + self._print_settings() + + @metered_subclass_method(METRICS_POOL) + def SetEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], + connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + """ Create/Update service endpoints from a list. + Parameters: + endpoints: List[Tuple[str, str, Optional[str]]] + List of tuples, each containing a device_uuid, + endpoint_uuid and, optionally, the topology_uuid + of the endpoint to be added. + connection_uuid : Optional[str] + If specified, is the UUID of the connection this endpoint is associated to. + Returns: + results: List[Union[bool, Exception]] + List of results for endpoint changes requested. + Return values must be in the same order as the requested + endpoints. If an endpoint is properly added, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('endpoints', endpoints, list) + if len(endpoints) == 0: return [] + + LOGGER.info("{} - Provision service configuration".format( + self.__service_label)) + + visited = set() + results = [] + for endpoint in endpoints: + device_uuid, endpoint_uuid = endpoint[0:2] + device = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + device_name = device.name + + LOGGER.info("Device {}".format(device_name)) + LOGGER.info("\t | Service endpoint UUID: {}".format(endpoint_uuid)) + + port_id = find_port_id_in_endpoint_list(device.device_endpoints, endpoint_uuid) + LOGGER.info("\t | Service port ID: {}".format(port_id)) + + dev_port_key = device_name + "-" + PORT_PREFIX + str(port_id) + + # Skip already visited device ports + if dev_port_key in visited: + continue + + rules = [] + actual_rules = -1 + applied_rules, failed_rules = 0, -1 + + # Create and apply rules + try: + rules = self._create_rules( + device_obj=device, port_id=port_id, action=ConfigActionEnum.CONFIGACTION_SET) + actual_rules = len(rules) + applied_rules, failed_rules = apply_rules( + task_executor=self.__task_executor, + device_obj=device, + json_config_rules=rules + ) + except Exception as ex: + LOGGER.error("Failed to insert L2 rules on device {} due to {}".format(device.name, ex)) + finally: + rules.clear() + + # Ensure correct status + results.append(True) if (failed_rules == 0) and (applied_rules == actual_rules) \ + else results.append(False) + + # You should no longer visit this device port again + visited.add(dev_port_key) + + LOGGER.info("Installed {}/{} L2 rules on device {} and port {}".format( + applied_rules, actual_rules, device_name, port_id)) + + return results + + @metered_subclass_method(METRICS_POOL) + def DeleteEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], + connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + """ Delete service endpoints from a list. + Parameters: + endpoints: List[Tuple[str, str, Optional[str]]] + List of tuples, each containing a device_uuid, + endpoint_uuid, and the topology_uuid of the endpoint + to be removed. + connection_uuid : Optional[str] + If specified, is the UUID of the connection this endpoint is associated to. + Returns: + results: List[Union[bool, Exception]] + List of results for endpoint deletions requested. + Return values must be in the same order as the requested + endpoints. If an endpoint is properly deleted, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('endpoints', endpoints, list) + if len(endpoints) == 0: return [] + + LOGGER.info("{} - Deprovision service configuration".format( + self.__service_label)) + + visited = set() + results = [] + for endpoint in endpoints: + device_uuid, endpoint_uuid = endpoint[0:2] + device = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + device_name = device.name + + LOGGER.info("Device {}".format(device_name)) + LOGGER.info("\t | Service endpoint UUID: {}".format(endpoint_uuid)) + + port_id = find_port_id_in_endpoint_list(device.device_endpoints, endpoint_uuid) + LOGGER.info("\t | Service port ID: {}".format(port_id)) + + dev_port_key = device_name + "-" + PORT_PREFIX + str(port_id) + + # Skip already visited device ports + if dev_port_key in visited: + continue + + rules = [] + actual_rules = -1 + applied_rules, failed_rules = 0, -1 + + # Create and apply rules + try: + rules = self._create_rules( + device_obj=device, port_id=port_id, action=ConfigActionEnum.CONFIGACTION_DELETE) + actual_rules = len(rules) + applied_rules, failed_rules = apply_rules( + task_executor=self.__task_executor, + device_obj=device, + json_config_rules=rules + ) + except Exception as ex: + LOGGER.error("Failed to insert L2 rules on device {} due to {}".format(device.name, ex)) + finally: + rules.clear() + + # Ensure correct status + results.append(True) if (failed_rules == 0) and (applied_rules == actual_rules) \ + else results.append(False) + + # You should no longer visit this device port again + visited.add(dev_port_key) + + LOGGER.info("Deleted {}/{} L2 rules from device {} and port {}".format( + applied_rules, actual_rules, device_name, port_id)) + + return results + + @metered_subclass_method(METRICS_POOL) + def SetConstraint(self, constraints: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Create/Update service constraints. + Parameters: + constraints: List[Tuple[str, Any]] + List of tuples, each containing a constraint_type and the + new constraint_value to be set. + Returns: + results: List[Union[bool, Exception]] + List of results for constraint changes requested. + Return values must be in the same order as the requested + constraints. If a constraint is properly set, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('constraints', constraints, list) + if len(constraints) == 0: return [] + + msg = '[SetConstraint] Method not implemented. Constraints({:s}) are being ignored.' + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @metered_subclass_method(METRICS_POOL) + def DeleteConstraint(self, constraints: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Delete service constraints. + Parameters: + constraints: List[Tuple[str, Any]] + List of tuples, each containing a constraint_type pointing + to the constraint to be deleted, and a constraint_value + containing possible additionally required values to locate + the constraint to be removed. + Returns: + results: List[Union[bool, Exception]] + List of results for constraint deletions requested. + Return values must be in the same order as the requested + constraints. If a constraint is properly deleted, True must + be returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('constraints', constraints, list) + if len(constraints) == 0: return [] + + msg = '[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored.' + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @metered_subclass_method(METRICS_POOL) + def SetConfig(self, resources: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Create/Update configuration for a list of service resources. + Parameters: + resources: List[Tuple[str, Any]] + List of tuples, each containing a resource_key pointing to + the resource to be modified, and a resource_value + containing the new value to be set. + Returns: + results: List[Union[bool, Exception]] + List of results for resource key changes requested. + Return values must be in the same order as the requested + resource keys. If a resource is properly set, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('resources', resources, list) + if len(resources) == 0: return [] + + msg = '[SetConfig] Method not implemented. Resources({:s}) are being ignored.' + LOGGER.warning(msg.format(str(resources))) + return [True for _ in range(len(resources))] + + @metered_subclass_method(METRICS_POOL) + def DeleteConfig(self, resources: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Delete configuration for a list of service resources. + Parameters: + resources: List[Tuple[str, Any]] + List of tuples, each containing a resource_key pointing to + the resource to be modified, and a resource_value containing + possible additionally required values to locate the value + to be removed. + Returns: + results: List[Union[bool, Exception]] + List of results for resource key deletions requested. + Return values must be in the same order as the requested + resource keys. If a resource is properly deleted, True must + be returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('resources', resources, list) + if len(resources) == 0: return [] + + msg = '[SetConfig] Method not implemented. Resources({:s}) are being ignored.' + LOGGER.warning(msg.format(str(resources))) + return [True for _ in range(len(resources))] + + def _init_settings(self): + self.__switch_info = {} + self.__port_map = {} + + try: + self.__settings = self.__settings_handler.get('/settings') + LOGGER.info("{} with settings: {}".format(self.__service_label, self.__settings)) + except Exception as ex: + self.__settings = {} + LOGGER.error("Failed to parse service settings: {}".format(ex)) + + def _default_settings(self): + port_list = [ + { + PORT_ID: 1, + PORT_TYPE: "host", + VLAN_ID: 4094 + }, + { + PORT_ID: 2, + PORT_TYPE: "host", + VLAN_ID: 4094 + }, + ] + fwd_list = [ + { + PORT_ID: 1, + HOST_MAC: "fa:16:3e:75:9c:e5" + }, + { + PORT_ID: 2, + HOST_MAC: "fa:16:3e:e2:af:28" + } + ] + switch_info = { + "p4-sw1": { + ARCH: TARGET_ARCH_V1MODEL, + DPID: 1, + PORT_LIST: port_list, + FORWARDING_LIST: fwd_list + } + } + self.__settings = { + SWITCH_INFO: switch_info + } + + # port_map = { + # "p4-sw1": { + # "port-1": { + # PORT_ID: 1, + # PORT_TYPE: PORT_TYPE_HOST, + # VLAN_ID: 4094, + # FORWARDING_LIST: ["fa:16:3e:75:9c:e5"] + # }, + # "port-2": { + # PORT_ID: 2, + # PORT_TYPE: PORT_TYPE_HOST, + # VLAN_ID: 4094, + # FORWARDING_LIST: ["fa:16:3e:e2:af:28"] + # } + # } + # } + + def _parse_settings(self): + #TODO: Pass settings in a correct way + try: + self.__switch_info = self.__settings[SWITCH_INFO] + except Exception as ex: + LOGGER.error("Failed to parse settings: {}".format(ex)) + self._default_settings() #TODO: Remove when bug is fixed + self.__switch_info = self.__settings[SWITCH_INFO] + assert isinstance(self.__switch_info, dict), "Switch info object must be a map with switch names as keys" + + for switch_name, switch_info in self.__switch_info.items(): + assert switch_name, "Invalid P4 switch name" + assert isinstance(switch_info, dict), "Switch {} info must be a map with arch, dpid, and fwd_list items)" + assert switch_info[ARCH] in SUPPORTED_TARGET_ARCH_LIST, \ + "Switch {} - Supported P4 architectures are: {}".format(switch_name, ','.join(SUPPORTED_TARGET_ARCH_LIST)) + switch_dpid = switch_info[DPID] + assert switch_dpid > 0, "Switch {} - P4 switch dataplane ID must be a positive integer".format(switch_name, switch_info[DPID]) + + # Port list + port_list = switch_info[PORT_LIST] + assert isinstance(port_list, list), "Switch {} port list must be a list with port_id, port_type, and vlan_id items)" + for port in port_list: + port_id = port[PORT_ID] + assert port_id >= 0, "Switch {} - Invalid P4 switch port ID".format(switch_name) + port_type = port[PORT_TYPE] + assert port_type in PORT_TYPES_STR_VALID, "Switch {} - Valid P4 switch port types are: {}".format( + switch_name, ','.join(PORT_TYPES_STR_VALID)) + vlan_id = port[VLAN_ID] + assert chk_vlan_id(vlan_id), "Switch {} - Invalid VLAN ID for port {}".format(switch_name, port_id) + + if switch_name not in self.__port_map: + self.__port_map[switch_name] = {} + port_key = PORT_PREFIX + str(port_id) + if port_key not in self.__port_map[switch_name]: + self.__port_map[switch_name][port_key] = {} + self.__port_map[switch_name][port_key][PORT_ID] = port_id + self.__port_map[switch_name][port_key][PORT_TYPE] = port_type + self.__port_map[switch_name][port_key][VLAN_ID] = vlan_id + self.__port_map[switch_name][port_key][FORWARDING_LIST] = [] + + # Forwarding list + fwd_list = switch_info[FORWARDING_LIST] + assert isinstance(fwd_list, list), "Switch {} forwarding list be a list)" + for fwd_entry in fwd_list: + port_id = fwd_entry[PORT_ID] + assert port_id >= 0, "Invalid port ID: {}".format(port_id) + host_mac = fwd_entry[HOST_MAC] + assert chk_address_mac(host_mac), "Invalid host MAC address {}".format(host_mac) + + # Retrieve entry from the port map + switch_port_entry = self._get_switch_port_in_port_map(switch_name, port_id) + + host_facing_port = self._is_host_facing_port(switch_name, port_id) + LOGGER.info("Switch {} - Port {}: Is host facing: {}".format(switch_name, port_id, "True" if host_facing_port else "False")) + switch_port_entry[FORWARDING_LIST].append(host_mac) + + def _print_settings(self): + LOGGER.info("--------------- {} settings ---------------".format(self.__service.name)) + LOGGER.info("--- Topology info") + for switch_name, switch_info in self.__switch_info.items(): + LOGGER.info("\t Device {}".format(switch_name)) + LOGGER.info("\t\t| Target P4 architecture: {}".format(switch_info[ARCH])) + LOGGER.info("\t\t| Data plane ID: {}".format(switch_info[DPID])) + LOGGER.info("\t\t| Port map: {}".format(self.__port_map[switch_name])) + LOGGER.info("-------------------------------------------------------") + + def _get_switch_port_in_port_map(self, switch_name : str, port_id : int) -> Dict: + assert switch_name, "A valid switch name must be used as a key to the port map" + assert port_id > 0, "A valid switch port ID must be used as a key to a switch's port map" + switch_entry = self.__port_map[switch_name] + assert switch_entry, "Switch {} does not exist in the port map".format(switch_name) + port_key = PORT_PREFIX + str(port_id) + assert switch_entry[port_key], "Port with ID {} does not exist in the switch map".format(port_id) + + return switch_entry[port_key] + + def _get_port_type_of_switch_port(self, switch_name : str, port_id : int) -> str: + switch_port_entry = self._get_switch_port_in_port_map(switch_name, port_id) + return switch_port_entry[PORT_TYPE] + + def _get_vlan_id_of_switch_port(self, switch_name : str, port_id : int) -> int: + switch_port_entry = self._get_switch_port_in_port_map(switch_name, port_id) + return switch_port_entry[VLAN_ID] + + def _get_fwd_list_of_switch_port(self, switch_name : str, port_id : int) -> List [Tuple]: + switch_port_entry = self._get_switch_port_in_port_map(switch_name, port_id) + return switch_port_entry[FORWARDING_LIST] + + def _is_host_facing_port(self, switch_name : str, port_id : int) -> bool: + return self._get_port_type_of_switch_port(switch_name, port_id) == PORT_TYPE_HOST + + def _create_rules(self, device_obj : Device, port_id : int, action : ConfigActionEnum): # type: ignore + dev_name = device_obj.name + + host_facing_port = self._is_host_facing_port(dev_name, port_id) + LOGGER.info("\t | Service endpoint is host facing: {}".format("True" if host_facing_port else "False")) + + rules = [] + + try: + ### Port setup rules + if host_facing_port: + rules += rules_set_up_port_host( + port=port_id, + vlan_id=self._get_vlan_id_of_switch_port(switch_name=dev_name, port_id=port_id), + action=action + ) + else: + rules += rules_set_up_port_switch( + port=port_id, + vlan_id=self._get_vlan_id_of_switch_port(switch_name=dev_name, port_id=port_id), + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating port setup rules") + raise Exception(ex) + + fwd_list = self._get_fwd_list_of_switch_port(switch_name=dev_name, port_id=port_id) + for mac in fwd_list: + LOGGER.info("Switch {} - Port {} - Creating rule for host MAC: {}".format(dev_name, port_id, mac)) + try: + ### Bridging rules + rules += rules_set_up_fwd_bridging( + vlan_id=self._get_vlan_id_of_switch_port(switch_name=dev_name, port_id=port_id), + eth_dst=mac, + egress_port=port_id, + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating bridging rules") + raise Exception(ex) + + try: + ### Next output rule + rules += rules_set_up_next_output_simple( + egress_port=port_id, + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating next output L2 rules") + raise Exception(ex) + + return rules diff --git a/src/tests/p4-fabric-tna/README.md b/src/tests/p4-fabric-tna/README.md index 10303009d..b96bb02e9 100644 --- a/src/tests/p4-fabric-tna/README.md +++ b/src/tests/p4-fabric-tna/README.md @@ -125,6 +125,20 @@ bash src/tests/p4-fabric-tna/run_test_02b_sbi_deprovision_int_l2_l3_acl.sh To avoid interacting with the switch using low-level P4 rules (via the SBI), we created modular network services, which allow users to easily provision L2, L3, ACL, and INT network functions. These services require users to define the service endpoints as well as some high-level service configuration, while leaving the rest of complexity to tailored service handlers that interact with the SBI on behalf of the user. +#### Provision L2 network service via the Service API + +```shell +cd ~/tfs-ctrl/ +bash src/tests/p4-fabric-tna/run_test_03a_service_provision_l2.sh +``` + +#### Deprovision L2 network service via the Service API + +```shell +cd ~/tfs-ctrl/ +bash src/tests/p4-fabric-tna/run_test_03b_service_deprovision_l2.sh +``` + #### Provision INT service via the Service API ```shell diff --git a/src/tests/p4-fabric-tna/descriptors/service-create-l2-simple.json b/src/tests/p4-fabric-tna/descriptors/service-create-l2-simple.json new file mode 100644 index 000000000..5a659f3d9 --- /dev/null +++ b/src/tests/p4-fabric-tna/descriptors/service-create-l2-simple.json @@ -0,0 +1,63 @@ +{ + "services": [ + { + "service_id": { + "context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "p4-service-l2"} + }, + "name": "p4-service-l2", + "service_type": "SERVICETYPE_L2NM", + "service_status": {"service_status": "SERVICESTATUS_PLANNED"}, + "service_endpoint_ids": [ + { + "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, + "endpoint_uuid": {"uuid": "1"} + }, + { + "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, + "endpoint_uuid": {"uuid": "2"} + } + ], + "service_config": { + "config_rules": [ + { + "action": "CONFIGACTION_SET", + "custom": { + "resource_key": "/settings", + "resource_value": { + "switch_info": { + "p4-sw1": { + "arch": "v1model", + "dpid": 1, + "port_list": [ + { + "port_id": 1, + "port_type": "host", + "vlan_id": 4094 + }, + { + "port_id": 2, + "port_type": "host", + "vlan_id": 4094 + } + ], + "fwd_list": [ + { + "port_id": 1, + "host_mac": "fa:16:3e:75:9c:e5" + }, + { + "port_id": 2, + "host_mac": "fa:16:3e:e2:af:28" + } + ] + } + } + } + } + } + ] + }, + "service_constraints": [] + } + ] +} diff --git a/src/tests/p4-fabric-tna/run_test_03a_service_provision_l2.sh b/src/tests/p4-fabric-tna/run_test_03a_service_provision_l2.sh new file mode 100755 index 000000000..6da590469 --- /dev/null +++ b/src/tests/p4-fabric-tna/run_test_03a_service_provision_l2.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source tfs_runtime_env_vars.sh +python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_l2.py diff --git a/src/tests/p4-fabric-tna/run_test_03b_service_deprovision_l2.sh b/src/tests/p4-fabric-tna/run_test_03b_service_deprovision_l2.sh new file mode 100755 index 000000000..fdb30c2fc --- /dev/null +++ b/src/tests/p4-fabric-tna/run_test_03b_service_deprovision_l2.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source tfs_runtime_env_vars.sh +python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_l2.py diff --git a/src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_l2.py b/src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_l2.py new file mode 100644 index 000000000..87ef21285 --- /dev/null +++ b/src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_l2.py @@ -0,0 +1,78 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from common.proto.context_pb2 import ServiceId, ServiceStatusEnum, ServiceTypeEnum +from common.tools.grpc.Tools import grpc_message_to_json_string +from common.tools.object_factory.Service import json_service_id +from context.client.ContextClient import ContextClient +from service.client.ServiceClient import ServiceClient +from tests.Fixtures import context_client, service_client # pylint: disable=unused-import +from tests.tools.test_tools_p4 import * + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +def test_service_deletion_l2( + context_client : ContextClient, # pylint: disable=redefined-outer-name + service_client : ServiceClient # pylint: disable=redefined-outer-name +) -> None: + # Get the current number of devices + response = context_client.ListDevices(ADMIN_CONTEXT_ID) + LOGGER.warning('Devices[{:d}] = {:s}'.format(len(response.devices), grpc_message_to_json_string(response))) + + # Total devices + dev_nb = len(response.devices) + assert dev_nb == DEV_NB + + # P4 devices + p4_dev_nb = identify_number_of_p4_devices(response.devices) + assert p4_dev_nb == P4_DEV_NB + + # Get the current number of rules in the P4 devices + p4_rules_before_deletion = get_number_of_rules(response.devices) + + # Get the current number of services + response = context_client.ListServices(ADMIN_CONTEXT_ID) + services_nb_before_deletion = len(response.services) + assert verify_active_service_type(response.services, ServiceTypeEnum.SERVICETYPE_L2NM) + + for service in response.services: + # Ignore services of other types + if service.service_type != ServiceTypeEnum.SERVICETYPE_L2NM: + continue + + service_id = service.service_id + assert service_id + + service_uuid = service_id.service_uuid.uuid + context_uuid = service_id.context_id.context_uuid.uuid + assert service.service_status.service_status == ServiceStatusEnum.SERVICESTATUS_ACTIVE + + # Delete L2 service + service_client.DeleteService(ServiceId(**json_service_id(service_uuid, json_context_id(context_uuid)))) + + # Get an updated view of the services + response = context_client.ListServices(ADMIN_CONTEXT_ID) + services_nb_after_deletion = len(response.services) + assert services_nb_after_deletion == services_nb_before_deletion - 1, "Exactly one new service must be deleted" + + # Get an updated view of the devices + response = context_client.ListDevices(ADMIN_CONTEXT_ID) + p4_rules_after_deletion = get_number_of_rules(response.devices) + + rules_diff = p4_rules_before_deletion - p4_rules_after_deletion + + assert p4_rules_after_deletion < p4_rules_before_deletion, "L2 service must contain some rules" + assert rules_diff == P4_DEV_NB * L2_RULES, "L2 service must contain {} rules per device".format(L2_RULES) diff --git a/src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_l2.py b/src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_l2.py new file mode 100644 index 000000000..a42c05546 --- /dev/null +++ b/src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_l2.py @@ -0,0 +1,73 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from common.proto.context_pb2 import ServiceStatusEnum, ServiceTypeEnum +from common.tools.descriptor.Loader import DescriptorLoader, check_descriptor_load_results +from common.tools.grpc.Tools import grpc_message_to_json_string +from context.client.ContextClient import ContextClient +from device.client.DeviceClient import DeviceClient +from service.client.ServiceClient import ServiceClient +from tests.Fixtures import context_client, device_client, service_client # pylint: disable=unused-import +from tests.tools.test_tools_p4 import * + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +def test_service_creation_l2( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + service_client : ServiceClient # pylint: disable=redefined-outer-name +) -> None: + # Get the current number of services + response = context_client.ListServices(ADMIN_CONTEXT_ID) + services_nb_before = len(response.services) + + # Get the current number of devices + response = context_client.ListDevices(ADMIN_CONTEXT_ID) + LOGGER.warning('Devices[{:d}] = {:s}'.format(len(response.devices), grpc_message_to_json_string(response))) + + # Total devices + dev_nb = len(response.devices) + assert dev_nb == DEV_NB + + # P4 devices + p4_dev_nb = identify_number_of_p4_devices(response.devices) + assert p4_dev_nb == P4_DEV_NB + + # Get the current number of rules in the P4 devices + p4_rules_before = get_number_of_rules(response.devices) + + # Load service + descriptor_loader = DescriptorLoader( + descriptors_file=DESC_FILE_SERVICE_CREATE_L2_SIMPLE, + context_client=context_client, device_client=device_client, service_client=service_client + ) + results = descriptor_loader.process() + check_descriptor_load_results(results, descriptor_loader) + + # Get an updated view of the services + response = context_client.ListServices(ADMIN_CONTEXT_ID) + services_nb_after = len(response.services) + assert services_nb_after == services_nb_before + 1, "Exactly one new service must be in place" + assert verify_active_service_type(response.services, ServiceTypeEnum.SERVICETYPE_L2NM) + + # Get an updated view of the devices + response = context_client.ListDevices(ADMIN_CONTEXT_ID) + p4_rules_after = get_number_of_rules(response.devices) + + rules_diff = p4_rules_after - p4_rules_before + + assert p4_rules_after > p4_rules_before, "L2 service must install some rules" + assert rules_diff == P4_DEV_NB * L2_RULES, "L2 service must install {} rules per device".format(L2_RULES) diff --git a/src/tests/tools/test_tools_p4.py b/src/tests/tools/test_tools_p4.py index 65257aefc..aa37f9e2c 100644 --- a/src/tests/tools/test_tools_p4.py +++ b/src/tests/tools/test_tools_p4.py @@ -96,9 +96,9 @@ DESC_FILE_SERVICE_CREATE_INT = os.path.join(TEST_PATH, 'service-create-int.json' assert os.path.exists(DESC_FILE_SERVICE_CREATE_INT),\ "Invalid path to the SD-Fabric INT service descriptor" -DESC_FILE_SERVICE_CREATE_L2 = os.path.join(TEST_PATH, 'service-create-l2.json') -assert os.path.exists(DESC_FILE_SERVICE_CREATE_L2),\ - "Invalid path to the SD-Fabric L2 service descriptor" +DESC_FILE_SERVICE_CREATE_L2_SIMPLE = os.path.join(TEST_PATH, 'service-create-l2-simple.json') +assert os.path.exists(DESC_FILE_SERVICE_CREATE_L2_SIMPLE),\ + "Invalid path to the SD-Fabric L2 simple service descriptor" DESC_FILE_SERVICE_CREATE_L3 = os.path.join(TEST_PATH, 'service-create-l3.json') assert os.path.exists(DESC_FILE_SERVICE_CREATE_L3),\ -- GitLab From 918206dc6016b5a67aeae8ead548a51430ada6de Mon Sep 17 00:00:00 2001 From: "Georgios P. Katsikas" <gkatsikas@ubitech.eu> Date: Mon, 31 Mar 2025 07:17:43 +0000 Subject: [PATCH 08/14] feat: P4 L3 service handler --- .../service/service_handlers/__init__.py | 7 + .../p4_fabric_tna_l3/__init__.py | 13 + .../p4_fabric_tna_l3_service_handler.py | 505 ++++++++++++++++++ src/tests/p4-fabric-tna/README.md | 14 + .../descriptors/service-create-l3.json | 67 +++ .../run_test_04a_service_provision_l3.sh | 17 + .../run_test_04b_service_deprovision_l3.sh | 17 + .../test_functional_service_deprovision_l3.py | 78 +++ .../test_functional_service_provision_l3.py | 73 +++ 9 files changed, 791 insertions(+) create mode 100644 src/service/service/service_handlers/p4_fabric_tna_l3/__init__.py create mode 100644 src/service/service/service_handlers/p4_fabric_tna_l3/p4_fabric_tna_l3_service_handler.py create mode 100644 src/tests/p4-fabric-tna/descriptors/service-create-l3.json create mode 100755 src/tests/p4-fabric-tna/run_test_04a_service_provision_l3.sh create mode 100755 src/tests/p4-fabric-tna/run_test_04b_service_deprovision_l3.sh create mode 100644 src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_l3.py create mode 100644 src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_l3.py diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py index 756d0fffb..0ecef7c22 100644 --- a/src/service/service/service_handlers/__init__.py +++ b/src/service/service/service_handlers/__init__.py @@ -28,6 +28,7 @@ from .microwave.MicrowaveServiceHandler import MicrowaveServiceHandler from .p4_dummy_l1.p4_dummy_l1_service_handler import P4DummyL1ServiceHandler from .p4_fabric_tna_int.p4_fabric_tna_int_service_handler import P4FabricINTServiceHandler from .p4_fabric_tna_l2_simple.p4_fabric_tna_l2_simple_service_handler import P4FabricL2SimpleServiceHandler +from .p4_fabric_tna_l3.p4_fabric_tna_l3_service_handler import P4FabricL3ServiceHandler from .tapi_tapi.TapiServiceHandler import TapiServiceHandler from .tapi_xr.TapiXrServiceHandler import TapiXrServiceHandler from .optical_tfs.OpticalTfsServiceHandler import OpticalTfsServiceHandler @@ -125,6 +126,12 @@ SERVICE_HANDLERS = [ FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4, } ]), + (P4FabricL3ServiceHandler, [ + { + FilterFieldEnum.SERVICE_TYPE: ServiceTypeEnum.SERVICETYPE_L3NM, + FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4, + } + ]), (L2NM_IETFL2VPN_ServiceHandler, [ { FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_L2NM, diff --git a/src/service/service/service_handlers/p4_fabric_tna_l3/__init__.py b/src/service/service/service_handlers/p4_fabric_tna_l3/__init__.py new file mode 100644 index 000000000..023830645 --- /dev/null +++ b/src/service/service/service_handlers/p4_fabric_tna_l3/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/src/service/service/service_handlers/p4_fabric_tna_l3/p4_fabric_tna_l3_service_handler.py b/src/service/service/service_handlers/p4_fabric_tna_l3/p4_fabric_tna_l3_service_handler.py new file mode 100644 index 000000000..849d1db92 --- /dev/null +++ b/src/service/service/service_handlers/p4_fabric_tna_l3/p4_fabric_tna_l3_service_handler.py @@ -0,0 +1,505 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Service handler for P4-based static L3 routing using the SD-Fabric P4 dataplane +for BMv2 and Intel Tofino switches. +""" + +import logging +from typing import Any, List, Dict, Optional, Tuple, Union +from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method +from common.proto.context_pb2 import ConfigActionEnum, DeviceId, Service, Device +from common.tools.object_factory.Device import json_device_id +from common.type_checkers.Checkers import chk_type, chk_address_mac, chk_address_ipv4, chk_prefix_len_ipv4 +from service.service.service_handler_api._ServiceHandler import _ServiceHandler +from service.service.service_handler_api.SettingsHandler import SettingsHandler +from service.service.service_handlers.p4_fabric_tna_commons.p4_fabric_tna_commons import * +from service.service.task_scheduler.TaskExecutor import TaskExecutor + +LOGGER = logging.getLogger(__name__) + +METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'p4_fabric_tna_l3'}) + +class P4FabricL3ServiceHandler(_ServiceHandler): + def __init__( # pylint: disable=super-init-not-called + self, service : Service, task_executor : TaskExecutor, **settings # type: ignore + ) -> None: + """ Initialize Driver. + Parameters: + service + The service instance (gRPC message) to be managed. + task_executor + An instance of Task Executor providing access to the + service handlers factory, the context and device clients, + and an internal cache of already-loaded gRPC entities. + **settings + Extra settings required by the service handler. + + """ + self.__service_label = "P4 static L3 connectivity service" + self.__service = service + self.__task_executor = task_executor + self.__settings_handler = SettingsHandler(self.__service.service_config, **settings) + + self._init_settings() + self._parse_settings() + self._print_settings() + + @metered_subclass_method(METRICS_POOL) + def SetEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], + connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + """ Create/Update service endpoints from a list. + Parameters: + endpoints: List[Tuple[str, str, Optional[str]]] + List of tuples, each containing a device_uuid, + endpoint_uuid and, optionally, the topology_uuid + of the endpoint to be added. + connection_uuid : Optional[str] + If specified, is the UUID of the connection this endpoint is associated to. + Returns: + results: List[Union[bool, Exception]] + List of results for endpoint changes requested. + Return values must be in the same order as the requested + endpoints. If an endpoint is properly added, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('endpoints', endpoints, list) + if len(endpoints) == 0: return [] + + LOGGER.info("{} - Provision service configuration".format( + self.__service_label)) + + visited = set() + results = [] + for endpoint in endpoints: + device_uuid, endpoint_uuid = endpoint[0:2] + device = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + device_name = device.name + + LOGGER.info("Device {}".format(device_name)) + LOGGER.info("\t | Service endpoint UUID: {}".format(endpoint_uuid)) + + port_id = find_port_id_in_endpoint_list(device.device_endpoints, endpoint_uuid) + LOGGER.info("\t | Service port ID: {}".format(port_id)) + + dev_port_key = device_name + "-" + PORT_PREFIX + str(port_id) + + # Skip already visited device ports + if dev_port_key in visited: + continue + + rules = [] + actual_rules = -1 + applied_rules, failed_rules = 0, -1 + + # Create and apply rules + try: + rules = self._create_rules( + device_obj=device, port_id=port_id, action=ConfigActionEnum.CONFIGACTION_SET) + actual_rules = len(rules) + applied_rules, failed_rules = apply_rules( + task_executor=self.__task_executor, + device_obj=device, + json_config_rules=rules + ) + except Exception as ex: + LOGGER.error("Failed to insert L3 rules on device {} due to {}".format(device.name, ex)) + finally: + rules.clear() + + # Ensure correct status + results.append(True) if (failed_rules == 0) and (applied_rules == actual_rules) \ + else results.append(False) + + # You should no longer visit this device port again + visited.add(dev_port_key) + + LOGGER.info("Installed {}/{} L3 rules on device {} and port {}".format( + applied_rules, actual_rules, device_name, port_id)) + + return results + + @metered_subclass_method(METRICS_POOL) + def DeleteEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], + connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + """ Delete service endpoints from a list. + Parameters: + endpoints: List[Tuple[str, str, Optional[str]]] + List of tuples, each containing a device_uuid, + endpoint_uuid, and the topology_uuid of the endpoint + to be removed. + connection_uuid : Optional[str] + If specified, is the UUID of the connection this endpoint is associated to. + Returns: + results: List[Union[bool, Exception]] + List of results for endpoint deletions requested. + Return values must be in the same order as the requested + endpoints. If an endpoint is properly deleted, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('endpoints', endpoints, list) + if len(endpoints) == 0: return [] + + LOGGER.info("{} - Deprovision service configuration".format( + self.__service_label)) + + visited = set() + results = [] + for endpoint in endpoints: + device_uuid, endpoint_uuid = endpoint[0:2] + device = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + device_name = device.name + + LOGGER.info("Device {}".format(device_name)) + LOGGER.info("\t | Service endpoint UUID: {}".format(endpoint_uuid)) + + port_id = find_port_id_in_endpoint_list(device.device_endpoints, endpoint_uuid) + LOGGER.info("\t | Service port ID: {}".format(port_id)) + + dev_port_key = device_name + "-" + PORT_PREFIX + str(port_id) + + # Skip already visited device ports + if dev_port_key in visited: + continue + + rules = [] + actual_rules = -1 + applied_rules, failed_rules = 0, -1 + + # Create and apply rules + try: + rules = self._create_rules( + device_obj=device, port_id=port_id, action=ConfigActionEnum.CONFIGACTION_DELETE) + actual_rules = len(rules) + applied_rules, failed_rules = apply_rules( + task_executor=self.__task_executor, + device_obj=device, + json_config_rules=rules + ) + except Exception as ex: + LOGGER.error("Failed to insert L3 rules on device {} due to {}".format(device.name, ex)) + finally: + rules.clear() + + # Ensure correct status + results.append(True) if (failed_rules == 0) and (applied_rules == actual_rules) \ + else results.append(False) + + # You should no longer visit this device port again + visited.add(dev_port_key) + + LOGGER.info("Deleted {}/{} L3 rules from device {} and port {}".format( + applied_rules, actual_rules, device_name, port_id)) + + return results + + @metered_subclass_method(METRICS_POOL) + def SetConstraint(self, constraints: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Create/Update service constraints. + Parameters: + constraints: List[Tuple[str, Any]] + List of tuples, each containing a constraint_type and the + new constraint_value to be set. + Returns: + results: List[Union[bool, Exception]] + List of results for constraint changes requested. + Return values must be in the same order as the requested + constraints. If a constraint is properly set, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('constraints', constraints, list) + if len(constraints) == 0: return [] + + msg = '[SetConstraint] Method not implemented. Constraints({:s}) are being ignored.' + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @metered_subclass_method(METRICS_POOL) + def DeleteConstraint(self, constraints: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Delete service constraints. + Parameters: + constraints: List[Tuple[str, Any]] + List of tuples, each containing a constraint_type pointing + to the constraint to be deleted, and a constraint_value + containing possible additionally required values to locate + the constraint to be removed. + Returns: + results: List[Union[bool, Exception]] + List of results for constraint deletions requested. + Return values must be in the same order as the requested + constraints. If a constraint is properly deleted, True must + be returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('constraints', constraints, list) + if len(constraints) == 0: return [] + + msg = '[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored.' + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @metered_subclass_method(METRICS_POOL) + def SetConfig(self, resources: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Create/Update configuration for a list of service resources. + Parameters: + resources: List[Tuple[str, Any]] + List of tuples, each containing a resource_key pointing to + the resource to be modified, and a resource_value + containing the new value to be set. + Returns: + results: List[Union[bool, Exception]] + List of results for resource key changes requested. + Return values must be in the same order as the requested + resource keys. If a resource is properly set, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('resources', resources, list) + if len(resources) == 0: return [] + + msg = '[SetConfig] Method not implemented. Resources({:s}) are being ignored.' + LOGGER.warning(msg.format(str(resources))) + return [True for _ in range(len(resources))] + + @metered_subclass_method(METRICS_POOL) + def DeleteConfig(self, resources: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Delete configuration for a list of service resources. + Parameters: + resources: List[Tuple[str, Any]] + List of tuples, each containing a resource_key pointing to + the resource to be modified, and a resource_value containing + possible additionally required values to locate the value + to be removed. + Returns: + results: List[Union[bool, Exception]] + List of results for resource key deletions requested. + Return values must be in the same order as the requested + resource keys. If a resource is properly deleted, True must + be returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('resources', resources, list) + if len(resources) == 0: return [] + + msg = '[SetConfig] Method not implemented. Resources({:s}) are being ignored.' + LOGGER.warning(msg.format(str(resources))) + return [True for _ in range(len(resources))] + + def _init_settings(self): + self.__switch_info = {} + self.__port_map = {} + + try: + self.__settings = self.__settings_handler.get('/settings') + LOGGER.info("{} with settings: {}".format(self.__service_label, self.__settings)) + except Exception as ex: + self.__settings = {} + LOGGER.error("Failed to parse service settings: {}".format(ex)) + + def _default_settings(self): + port_list = [ + { + PORT_ID: 1, + PORT_TYPE: "host" + }, + { + PORT_ID: 2, + PORT_TYPE: "host" + }, + ] + routing_list = [ + { + PORT_ID: 1, + IPV4_DST: "10.158.72.11", + IPV4_PREFIX_LEN: 32, + MAC_SRC: "fa:16:3e:e2:af:28", + MAC_DST: "fa:16:3e:75:9c:e5" + }, + { + PORT_ID: 2, + IPV4_DST: "172.16.10.9", + IPV4_PREFIX_LEN: 32, + MAC_SRC: "fa:16:3e:75:9c:e5", + MAC_DST: "fa:16:3e:e2:af:28" + } + ] + switch_info = { + "p4-sw1": { + ARCH: TARGET_ARCH_V1MODEL, + DPID: 1, + PORT_LIST: port_list, + ROUTING_LIST: routing_list + } + } + self.__settings = { + SWITCH_INFO: switch_info + } + + port_map = { + "p4-sw1": { + "port-1": { + PORT_ID: 1, + PORT_TYPE: PORT_TYPE_HOST, + ROUTING_LIST: [ + { + IPV4_DST: "10.158.72.11", + IPV4_PREFIX_LEN: 32, + MAC_SRC: "fa:16:3e:e2:af:28", + MAC_DST: "fa:16:3e:75:9c:e5" + } + ] + }, + "port-2": { + PORT_ID: 2, + PORT_TYPE: PORT_TYPE_HOST, + ROUTING_LIST: [ + { + IPV4_DST: "172.16.10.9", + IPV4_PREFIX_LEN: 32, + MAC_SRC: "fa:16:3e:75:9c:e5", + MAC_DST: "fa:16:3e:e2:af:28" + } + ] + } + } + } + + def _parse_settings(self): + #TODO: Pass settings in a correct way + try: + self.__switch_info = self.__settings[SWITCH_INFO] + except Exception as ex: + LOGGER.error("Failed to parse settings: {}".format(ex)) + self._default_settings() #TODO: Remove when bug is fixed + self.__switch_info = self.__settings[SWITCH_INFO] + assert isinstance(self.__switch_info, dict), "Switch info object must be a map with switch names as keys" + + for switch_name, switch_info in self.__switch_info.items(): + assert switch_name, "Invalid P4 switch name" + assert isinstance(switch_info, dict), "Switch {} info must be a map with arch, dpid, and fwd_list items)" + assert switch_info[ARCH] in SUPPORTED_TARGET_ARCH_LIST, \ + "Switch {} - Supported P4 architectures are: {}".format(switch_name, ','.join(SUPPORTED_TARGET_ARCH_LIST)) + switch_dpid = switch_info[DPID] + assert switch_dpid > 0, "Switch {} - P4 switch dataplane ID must be a positive integer".format(switch_name, switch_info[DPID]) + + # Port list + port_list = switch_info[PORT_LIST] + assert isinstance(port_list, list), "Switch {} port list must be a list with port_id and port_type items)" + for port in port_list: + port_id = port[PORT_ID] + assert port_id >= 0, "Switch {} - Invalid P4 switch port ID".format(switch_name) + port_type = port[PORT_TYPE] + assert port_type in PORT_TYPES_STR_VALID, "Switch {} - Valid P4 switch port types are: {}".format( + switch_name, ','.join(PORT_TYPES_STR_VALID)) + + if switch_name not in self.__port_map: + self.__port_map[switch_name] = {} + port_key = PORT_PREFIX + str(port_id) + if port_key not in self.__port_map[switch_name]: + self.__port_map[switch_name][port_key] = {} + self.__port_map[switch_name][port_key][PORT_ID] = port_id + self.__port_map[switch_name][port_key][PORT_TYPE] = port_type + self.__port_map[switch_name][port_key][ROUTING_LIST] = [] + + # Routing list + routing_list = switch_info[ROUTING_LIST] + assert isinstance(routing_list, list), "Switch {} routing list be a list)" + for rt_entry in routing_list: + port_id = rt_entry[PORT_ID] + assert port_id >= 0, "Invalid port ID: {}".format(port_id) + ipv4_dst = rt_entry[IPV4_DST] + assert chk_address_ipv4(ipv4_dst), "Invalid destination IPv4 address {}".format(ipv4_dst) + ipv4_prefix_len = rt_entry[IPV4_PREFIX_LEN] + assert chk_prefix_len_ipv4(ipv4_prefix_len), "Invalid IPv4 address prefix length {}".format(ipv4_prefix_len) + mac_src = rt_entry[MAC_SRC] + assert chk_address_mac(mac_src), "Invalid source MAC address {}".format(mac_src) + mac_dst = rt_entry[MAC_DST] + assert chk_address_mac(mac_dst), "Invalid destination MAC address {}".format(mac_dst) + + # Retrieve entry from the port map + switch_port_entry = self._get_switch_port_in_port_map(switch_name, port_id) + + # Add routing entry + switch_port_entry[ROUTING_LIST].append( + { + PORT_ID: port_id, + IPV4_DST: ipv4_dst, + IPV4_PREFIX_LEN: ipv4_prefix_len, + MAC_SRC: mac_src, + MAC_DST: mac_dst + } + ) + + def _print_settings(self): + LOGGER.info("--------------- {} settings ---------------".format(self.__service.name)) + LOGGER.info("--- Topology info") + for switch_name, switch_info in self.__switch_info.items(): + LOGGER.info("\t Device {}".format(switch_name)) + LOGGER.info("\t\t| Target P4 architecture: {}".format(switch_info[ARCH])) + LOGGER.info("\t\t| Data plane ID: {}".format(switch_info[DPID])) + LOGGER.info("\t\t| Port map: {}".format(self.__port_map[switch_name])) + LOGGER.info("-------------------------------------------------------") + + def _get_switch_port_in_port_map(self, switch_name : str, port_id : int) -> Dict: + assert switch_name, "A valid switch name must be used as a key to the port map" + assert port_id > 0, "A valid switch port ID must be used as a key to a switch's port map" + switch_entry = self.__port_map[switch_name] + assert switch_entry, "Switch {} does not exist in the port map".format(switch_name) + port_key = PORT_PREFIX + str(port_id) + assert switch_entry[port_key], "Port with ID {} does not exist in the switch map".format(port_id) + + return switch_entry[port_key] + + def _get_routing_list_of_switch_port(self, switch_name : str, port_id : int) -> List [Tuple]: + switch_port_entry = self._get_switch_port_in_port_map(switch_name, port_id) + return switch_port_entry[ROUTING_LIST] + + def _create_rules(self, device_obj : Device, port_id : int, action : ConfigActionEnum): # type: ignore + dev_name = device_obj.name + + rules = [] + + ### Static routing rules + routing_list = self._get_routing_list_of_switch_port(switch_name=dev_name, port_id=port_id) + for rt_entry in routing_list: + try: + rules += rules_set_up_next_routing_simple( + egress_port=port_id, + eth_src=rt_entry[MAC_SRC], + eth_dst=rt_entry[MAC_DST], + action=action + ) + rules += rules_set_up_routing( + ipv4_dst=rt_entry[IPV4_DST], + ipv4_prefix_len=rt_entry[IPV4_PREFIX_LEN], + egress_port=port_id, + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating static L3 routing rules") + raise Exception(ex) + + return rules diff --git a/src/tests/p4-fabric-tna/README.md b/src/tests/p4-fabric-tna/README.md index b96bb02e9..f6bc2dd0c 100644 --- a/src/tests/p4-fabric-tna/README.md +++ b/src/tests/p4-fabric-tna/README.md @@ -139,6 +139,20 @@ cd ~/tfs-ctrl/ bash src/tests/p4-fabric-tna/run_test_03b_service_deprovision_l2.sh ``` +#### Provision L3 network service via the Service API + +```shell +cd ~/tfs-ctrl/ +bash src/tests/p4-fabric-tna/run_test_04a_service_provision_l3.sh +``` + +#### Deprovision L3 network service via the Service API + +```shell +cd ~/tfs-ctrl/ +bash src/tests/p4-fabric-tna/run_test_04b_service_deprovision_l3.sh +``` + #### Provision INT service via the Service API ```shell diff --git a/src/tests/p4-fabric-tna/descriptors/service-create-l3.json b/src/tests/p4-fabric-tna/descriptors/service-create-l3.json new file mode 100644 index 000000000..7d8153e7a --- /dev/null +++ b/src/tests/p4-fabric-tna/descriptors/service-create-l3.json @@ -0,0 +1,67 @@ +{ + "services": [ + { + "service_id": { + "context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "p4-service-l3"} + }, + "name": "p4-service-l3", + "service_type": "SERVICETYPE_L3NM", + "service_status": {"service_status": "SERVICESTATUS_PLANNED"}, + "service_endpoint_ids": [ + { + "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, + "endpoint_uuid": {"uuid": "1"} + }, + { + "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, + "endpoint_uuid": {"uuid": "2"} + } + ], + "service_config": { + "config_rules": [ + { + "action": "CONFIGACTION_SET", + "custom": { + "resource_key": "/settings", + "resource_value": { + "switch_info": { + "p4-sw1": { + "arch": "v1model", + "dpid": 1, + "port_list": [ + { + "port_id": 1, + "port_type": "host" + }, + { + "port_id": 2, + "port_type": "host" + } + ], + "routing_list": [ + { + "port_id": 1, + "ipv4_dst": "10.158.72.11", + "ipv4_prefix_len": 32, + "mac_src": "fa:16:3e:e2:af:28", + "mac_dst": "fa:16:3e:75:9c:e5" + }, + { + "port_id": 2, + "ipv4_dst": "172.16.10.9", + "ipv4_prefix_len": 32, + "mac_src": "fa:16:3e:75:9c:e5", + "mac_dst": "fa:16:3e:e2:af:28" + } + ] + } + } + } + } + } + ] + }, + "service_constraints": [] + } + ] +} diff --git a/src/tests/p4-fabric-tna/run_test_04a_service_provision_l3.sh b/src/tests/p4-fabric-tna/run_test_04a_service_provision_l3.sh new file mode 100755 index 000000000..96c629370 --- /dev/null +++ b/src/tests/p4-fabric-tna/run_test_04a_service_provision_l3.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source tfs_runtime_env_vars.sh +python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_l3.py diff --git a/src/tests/p4-fabric-tna/run_test_04b_service_deprovision_l3.sh b/src/tests/p4-fabric-tna/run_test_04b_service_deprovision_l3.sh new file mode 100755 index 000000000..fdc1d72ac --- /dev/null +++ b/src/tests/p4-fabric-tna/run_test_04b_service_deprovision_l3.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source tfs_runtime_env_vars.sh +python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_l3.py diff --git a/src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_l3.py b/src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_l3.py new file mode 100644 index 000000000..d349a0874 --- /dev/null +++ b/src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_l3.py @@ -0,0 +1,78 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from common.proto.context_pb2 import ServiceId, ServiceStatusEnum, ServiceTypeEnum +from common.tools.grpc.Tools import grpc_message_to_json_string +from common.tools.object_factory.Service import json_service_id +from context.client.ContextClient import ContextClient +from service.client.ServiceClient import ServiceClient +from tests.Fixtures import context_client, service_client # pylint: disable=unused-import +from tests.tools.test_tools_p4 import * + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +def test_service_deletion_l3( + context_client : ContextClient, # pylint: disable=redefined-outer-name + service_client : ServiceClient # pylint: disable=redefined-outer-name +) -> None: + # Get the current number of devices + response = context_client.ListDevices(ADMIN_CONTEXT_ID) + LOGGER.warning('Devices[{:d}] = {:s}'.format(len(response.devices), grpc_message_to_json_string(response))) + + # Total devices + dev_nb = len(response.devices) + assert dev_nb == DEV_NB + + # P4 devices + p4_dev_nb = identify_number_of_p4_devices(response.devices) + assert p4_dev_nb == P4_DEV_NB + + # Get the current number of rules in the P4 devices + p4_rules_before_deletion = get_number_of_rules(response.devices) + + # Get the current number of services + response = context_client.ListServices(ADMIN_CONTEXT_ID) + services_nb_before_deletion = len(response.services) + assert verify_active_service_type(response.services, ServiceTypeEnum.SERVICETYPE_L3NM) + + for service in response.services: + # Ignore services of other types + if service.service_type != ServiceTypeEnum.SERVICETYPE_L3NM: + continue + + service_id = service.service_id + assert service_id + + service_uuid = service_id.service_uuid.uuid + context_uuid = service_id.context_id.context_uuid.uuid + assert service.service_status.service_status == ServiceStatusEnum.SERVICESTATUS_ACTIVE + + # Delete L3 service + service_client.DeleteService(ServiceId(**json_service_id(service_uuid, json_context_id(context_uuid)))) + + # Get an updated view of the services + response = context_client.ListServices(ADMIN_CONTEXT_ID) + services_nb_after_deletion = len(response.services) + assert services_nb_after_deletion == services_nb_before_deletion - 1, "Exactly one new service must be deleted" + + # Get an updated view of the devices + response = context_client.ListDevices(ADMIN_CONTEXT_ID) + p4_rules_after_deletion = get_number_of_rules(response.devices) + + rules_diff = p4_rules_before_deletion - p4_rules_after_deletion + + assert p4_rules_after_deletion < p4_rules_before_deletion, "L3 service must contain some rules" + assert rules_diff == P4_DEV_NB * L3_RULES, "L3 service must contain {} rules per device".format(L3_RULES) diff --git a/src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_l3.py b/src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_l3.py new file mode 100644 index 000000000..9c0009b14 --- /dev/null +++ b/src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_l3.py @@ -0,0 +1,73 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from common.proto.context_pb2 import ServiceStatusEnum, ServiceTypeEnum +from common.tools.descriptor.Loader import DescriptorLoader, check_descriptor_load_results +from common.tools.grpc.Tools import grpc_message_to_json_string +from context.client.ContextClient import ContextClient +from device.client.DeviceClient import DeviceClient +from service.client.ServiceClient import ServiceClient +from tests.Fixtures import context_client, device_client, service_client # pylint: disable=unused-import +from tests.tools.test_tools_p4 import * + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +def test_service_creation_l3( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + service_client : ServiceClient # pylint: disable=redefined-outer-name +) -> None: + # Get the current number of services + response = context_client.ListServices(ADMIN_CONTEXT_ID) + services_nb_before = len(response.services) + + # Get the current number of devices + response = context_client.ListDevices(ADMIN_CONTEXT_ID) + LOGGER.warning('Devices[{:d}] = {:s}'.format(len(response.devices), grpc_message_to_json_string(response))) + + # Total devices + dev_nb = len(response.devices) + assert dev_nb == DEV_NB + + # P4 devices + p4_dev_nb = identify_number_of_p4_devices(response.devices) + assert p4_dev_nb == P4_DEV_NB + + # Get the current number of rules in the P4 devices + p4_rules_before = get_number_of_rules(response.devices) + + # Load service + descriptor_loader = DescriptorLoader( + descriptors_file=DESC_FILE_SERVICE_CREATE_L3, + context_client=context_client, device_client=device_client, service_client=service_client + ) + results = descriptor_loader.process() + check_descriptor_load_results(results, descriptor_loader) + + # Get an updated view of the services + response = context_client.ListServices(ADMIN_CONTEXT_ID) + services_nb_after = len(response.services) + assert services_nb_after == services_nb_before + 1, "Exactly one new service must be in place" + assert verify_active_service_type(response.services, ServiceTypeEnum.SERVICETYPE_L3NM) + + # Get an updated view of the devices + response = context_client.ListDevices(ADMIN_CONTEXT_ID) + p4_rules_after = get_number_of_rules(response.devices) + + rules_diff = p4_rules_after - p4_rules_before + + assert p4_rules_after > p4_rules_before, "L3 service must install some rules" + assert rules_diff == P4_DEV_NB * L3_RULES, "L3 service must install {} rules per device".format(L3_RULES) -- GitLab From 5c553a4f03d55be0fd2730d8f776661c1b8deee2 Mon Sep 17 00:00:00 2001 From: "Georgios P. Katsikas" <gkatsikas@ubitech.eu> Date: Mon, 31 Mar 2025 08:53:13 +0000 Subject: [PATCH 09/14] feat: P4 ACL service handler --- .../service/service_handlers/__init__.py | 7 + .../p4_fabric_tna_acl/__init__.py | 13 + .../p4_fabric_tna_acl_config.py | 39 ++ .../p4_fabric_tna_acl_service_handler.py | 526 ++++++++++++++++++ src/tests/p4-fabric-tna/README.md | 14 + .../descriptors/service-create-acl.json | 65 +++ .../run_test_05a_service_provision_acl.sh | 17 + .../run_test_05b_service_deprovision_acl.sh | 17 + ...test_functional_service_deprovision_acl.py | 78 +++ .../test_functional_service_provision_acl.py | 73 +++ 10 files changed, 849 insertions(+) create mode 100644 src/service/service/service_handlers/p4_fabric_tna_acl/__init__.py create mode 100644 src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_config.py create mode 100644 src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py create mode 100644 src/tests/p4-fabric-tna/descriptors/service-create-acl.json create mode 100755 src/tests/p4-fabric-tna/run_test_05a_service_provision_acl.sh create mode 100755 src/tests/p4-fabric-tna/run_test_05b_service_deprovision_acl.sh create mode 100644 src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_acl.py create mode 100644 src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_acl.py diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py index 0ecef7c22..7c00d5a85 100644 --- a/src/service/service/service_handlers/__init__.py +++ b/src/service/service/service_handlers/__init__.py @@ -29,6 +29,7 @@ from .p4_dummy_l1.p4_dummy_l1_service_handler import P4DummyL1ServiceHandler from .p4_fabric_tna_int.p4_fabric_tna_int_service_handler import P4FabricINTServiceHandler from .p4_fabric_tna_l2_simple.p4_fabric_tna_l2_simple_service_handler import P4FabricL2SimpleServiceHandler from .p4_fabric_tna_l3.p4_fabric_tna_l3_service_handler import P4FabricL3ServiceHandler +from .p4_fabric_tna_acl.p4_fabric_tna_acl_service_handler import P4FabricACLServiceHandler from .tapi_tapi.TapiServiceHandler import TapiServiceHandler from .tapi_xr.TapiXrServiceHandler import TapiXrServiceHandler from .optical_tfs.OpticalTfsServiceHandler import OpticalTfsServiceHandler @@ -132,6 +133,12 @@ SERVICE_HANDLERS = [ FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4, } ]), + (P4FabricACLServiceHandler, [ + { + FilterFieldEnum.SERVICE_TYPE: ServiceTypeEnum.SERVICETYPE_ACL, + FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4, + } + ]), (L2NM_IETFL2VPN_ServiceHandler, [ { FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_L2NM, diff --git a/src/service/service/service_handlers/p4_fabric_tna_acl/__init__.py b/src/service/service/service_handlers/p4_fabric_tna_acl/__init__.py new file mode 100644 index 000000000..023830645 --- /dev/null +++ b/src/service/service/service_handlers/p4_fabric_tna_acl/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_config.py b/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_config.py new file mode 100644 index 000000000..09dbcc5aa --- /dev/null +++ b/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_config.py @@ -0,0 +1,39 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Common objects and methods for In-band Network Telemetry (INT) dataplane +based on the SD-Fabric dataplane model. +This dataplane covers both software based and hardware-based Stratum-enabled P4 switches, +such as the BMv2 software switch and Intel's Tofino/Tofino-2 switches. + +SD-Fabric repo: https://github.com/stratum/fabric-tna +SD-Fabric docs: https://docs.sd-fabric.org/master/index.html +""" + +import logging + +from service.service.service_handlers.p4_fabric_tna_commons.p4_fabric_tna_commons import * + +LOGGER = logging.getLogger(__name__) + +# ACL service handler settings +ACL = "acl" +ACTION = "action" +ACTION_DROP = "drop" +ACTION_ALLOW = "allow" +ACTION_LIST = [ACTION_ALLOW, ACTION_DROP] + +def is_valid_acl_action(action : str) -> bool: + return action in ACTION_LIST diff --git a/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py b/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py new file mode 100644 index 000000000..0b44a1ce8 --- /dev/null +++ b/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py @@ -0,0 +1,526 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Service handler for P4-based access control using the SD-Fabric P4 dataplane +for BMv2 and Intel Tofino switches. +""" + +import logging +from typing import Any, List, Dict, Optional, Tuple, Union +from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method +from common.proto.context_pb2 import ConfigActionEnum, DeviceId, Service, Device +from common.tools.object_factory.Device import json_device_id +from common.type_checkers.Checkers import chk_type, chk_address_ipv4, chk_prefix_len_ipv4,\ + chk_transport_port +from service.service.service_handler_api._ServiceHandler import _ServiceHandler +from service.service.service_handler_api.SettingsHandler import SettingsHandler +from service.service.service_handlers.p4_fabric_tna_commons.p4_fabric_tna_commons import * +from service.service.task_scheduler.TaskExecutor import TaskExecutor + +from .p4_fabric_tna_acl_config import * + +LOGGER = logging.getLogger(__name__) + +METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'p4_fabric_tna_acl'}) + +class P4FabricACLServiceHandler(_ServiceHandler): + def __init__( # pylint: disable=super-init-not-called + self, service : Service, task_executor : TaskExecutor, **settings # type: ignore + ) -> None: + """ Initialize Driver. + Parameters: + service + The service instance (gRPC message) to be managed. + task_executor + An instance of Task Executor providing access to the + service handlers factory, the context and device clients, + and an internal cache of already-loaded gRPC entities. + **settings + Extra settings required by the service handler. + + """ + self.__service_label = "P4 Access Control connectivity service" + self.__service = service + self.__task_executor = task_executor + self.__settings_handler = SettingsHandler(self.__service.service_config, **settings) + + self._init_settings() + self._parse_settings() + self._print_settings() + + @metered_subclass_method(METRICS_POOL) + def SetEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], + connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + """ Create/Update service endpoints from a list. + Parameters: + endpoints: List[Tuple[str, str, Optional[str]]] + List of tuples, each containing a device_uuid, + endpoint_uuid and, optionally, the topology_uuid + of the endpoint to be added. + connection_uuid : Optional[str] + If specified, is the UUID of the connection this endpoint is associated to. + Returns: + results: List[Union[bool, Exception]] + List of results for endpoint changes requested. + Return values must be in the same order as the requested + endpoints. If an endpoint is properly added, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('endpoints', endpoints, list) + if len(endpoints) == 0: return [] + + LOGGER.info("{} - Provision service configuration".format( + self.__service_label)) + + visited = set() + results = [] + for endpoint in endpoints: + device_uuid, endpoint_uuid = endpoint[0:2] + device = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + device_name = device.name + + LOGGER.info("Device {}".format(device_name)) + LOGGER.info("\t | Service endpoint UUID: {}".format(endpoint_uuid)) + + port_id = find_port_id_in_endpoint_list(device.device_endpoints, endpoint_uuid) + LOGGER.info("\t | Service port ID: {}".format(port_id)) + + try: + # Check if this port is part of the ACL configuration + _ = self._get_switch_port_in_port_map(device_name, port_id) + except Exception: + LOGGER.warning("Switch {} endpoint {} is not part of the ACL configuration".format(device_name, port_id)) + results.append(False) + continue + + dev_port_key = device_name + "-" + PORT_PREFIX + str(port_id) + + # Skip already visited device ports + if dev_port_key in visited: + continue + + rules = [] + actual_rules = -1 + applied_rules, failed_rules = 0, -1 + + # Create and apply rules + try: + rules = self._create_rules( + device_obj=device, port_id=port_id, action=ConfigActionEnum.CONFIGACTION_SET) + actual_rules = len(rules) + applied_rules, failed_rules = apply_rules( + task_executor=self.__task_executor, + device_obj=device, + json_config_rules=rules + ) + except Exception as ex: + LOGGER.error("Failed to insert ACL rules on device {} due to {}".format(device.name, ex)) + finally: + rules.clear() + + # Ensure correct status + results.append(True) if (failed_rules == 0) and (applied_rules == actual_rules) \ + else results.append(False) + + # You should no longer visit this device port again + visited.add(dev_port_key) + + LOGGER.info("Installed {}/{} ACL rules on device {} and port {}".format( + applied_rules, actual_rules, device_name, port_id)) + + return results + + @metered_subclass_method(METRICS_POOL) + def DeleteEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], + connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + """ Delete service endpoints from a list. + Parameters: + endpoints: List[Tuple[str, str, Optional[str]]] + List of tuples, each containing a device_uuid, + endpoint_uuid, and the topology_uuid of the endpoint + to be removed. + connection_uuid : Optional[str] + If specified, is the UUID of the connection this endpoint is associated to. + Returns: + results: List[Union[bool, Exception]] + List of results for endpoint deletions requested. + Return values must be in the same order as the requested + endpoints. If an endpoint is properly deleted, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('endpoints', endpoints, list) + if len(endpoints) == 0: return [] + + LOGGER.info("{} - Deprovision service configuration".format( + self.__service_label)) + + visited = set() + results = [] + for endpoint in endpoints: + device_uuid, endpoint_uuid = endpoint[0:2] + device = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + device_name = device.name + + LOGGER.info("Device {}".format(device_name)) + LOGGER.info("\t | Service endpoint UUID: {}".format(endpoint_uuid)) + + port_id = find_port_id_in_endpoint_list(device.device_endpoints, endpoint_uuid) + LOGGER.info("\t | Service port ID: {}".format(port_id)) + + try: + # Check if this port is part of the ACL configuration + _ = self._get_switch_port_in_port_map(device_name, port_id) + except Exception: + LOGGER.warning("Switch {} endpoint {} is not part of the ACL configuration".format(device_name, port_id)) + results.append(False) + continue + + dev_port_key = device_name + "-" + PORT_PREFIX + str(port_id) + + # Skip already visited device ports + if dev_port_key in visited: + continue + + rules = [] + actual_rules = -1 + applied_rules, failed_rules = 0, -1 + + # Create and apply rules + try: + rules = self._create_rules( + device_obj=device, port_id=port_id, action=ConfigActionEnum.CONFIGACTION_DELETE) + actual_rules = len(rules) + applied_rules, failed_rules = apply_rules( + task_executor=self.__task_executor, + device_obj=device, + json_config_rules=rules + ) + except Exception as ex: + LOGGER.error("Failed to insert ACL rules on device {} due to {}".format(device.name, ex)) + finally: + rules.clear() + + # Ensure correct status + results.append(True) if (failed_rules == 0) and (applied_rules == actual_rules) \ + else results.append(False) + + # You should no longer visit this device port again + visited.add(dev_port_key) + + LOGGER.info("Deleted {}/{} ACL rules from device {} and port {}".format( + applied_rules, actual_rules, device_name, port_id)) + + return results + + @metered_subclass_method(METRICS_POOL) + def SetConstraint(self, constraints: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Create/Update service constraints. + Parameters: + constraints: List[Tuple[str, Any]] + List of tuples, each containing a constraint_type and the + new constraint_value to be set. + Returns: + results: List[Union[bool, Exception]] + List of results for constraint changes requested. + Return values must be in the same order as the requested + constraints. If a constraint is properly set, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('constraints', constraints, list) + if len(constraints) == 0: return [] + + msg = '[SetConstraint] Method not implemented. Constraints({:s}) are being ignored.' + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @metered_subclass_method(METRICS_POOL) + def DeleteConstraint(self, constraints: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Delete service constraints. + Parameters: + constraints: List[Tuple[str, Any]] + List of tuples, each containing a constraint_type pointing + to the constraint to be deleted, and a constraint_value + containing possible additionally required values to locate + the constraint to be removed. + Returns: + results: List[Union[bool, Exception]] + List of results for constraint deletions requested. + Return values must be in the same order as the requested + constraints. If a constraint is properly deleted, True must + be returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('constraints', constraints, list) + if len(constraints) == 0: return [] + + msg = '[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored.' + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @metered_subclass_method(METRICS_POOL) + def SetConfig(self, resources: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Create/Update configuration for a list of service resources. + Parameters: + resources: List[Tuple[str, Any]] + List of tuples, each containing a resource_key pointing to + the resource to be modified, and a resource_value + containing the new value to be set. + Returns: + results: List[Union[bool, Exception]] + List of results for resource key changes requested. + Return values must be in the same order as the requested + resource keys. If a resource is properly set, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('resources', resources, list) + if len(resources) == 0: return [] + + msg = '[SetConfig] Method not implemented. Resources({:s}) are being ignored.' + LOGGER.warning(msg.format(str(resources))) + return [True for _ in range(len(resources))] + + @metered_subclass_method(METRICS_POOL) + def DeleteConfig(self, resources: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Delete configuration for a list of service resources. + Parameters: + resources: List[Tuple[str, Any]] + List of tuples, each containing a resource_key pointing to + the resource to be modified, and a resource_value containing + possible additionally required values to locate the value + to be removed. + Returns: + results: List[Union[bool, Exception]] + List of results for resource key deletions requested. + Return values must be in the same order as the requested + resource keys. If a resource is properly deleted, True must + be returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('resources', resources, list) + if len(resources) == 0: return [] + + msg = '[SetConfig] Method not implemented. Resources({:s}) are being ignored.' + LOGGER.warning(msg.format(str(resources))) + return [True for _ in range(len(resources))] + + def _init_settings(self): + self.__switch_info = {} + self.__port_map = {} + + try: + self.__settings = self.__settings_handler.get('/settings') + LOGGER.info("{} with settings: {}".format(self.__service_label, self.__settings)) + except Exception as ex: + self.__settings = {} + LOGGER.error("Failed to parse service settings: {}".format(ex)) + + def _default_settings(self): + acl = [ + { + PORT_ID: 1, + IPV4_SRC: "10.158.72.11", + IPV4_PREFIX_LEN: 32, + ACTION: ACTION_DROP + }, + { + PORT_ID: 1, + TRN_PORT_DST: 8080, + ACTION: ACTION_DROP + } + ] + + switch_info = { + "p4-sw1": { + ARCH: TARGET_ARCH_V1MODEL, + DPID: 1, + ACL: acl + } + } + self.__settings = { + SWITCH_INFO: switch_info + } + + port_map = { + "p4-sw1": { + "port-1": { + PORT_ID: 1, + ACL: [ + { + IPV4_SRC: "10.158.72.11", + IPV4_PREFIX_LEN: 32, + ACTION: ACTION_DROP + }, + { + TRN_PORT_DST: 8080, + ACTION: ACTION_DROP + } + ] + } + } + } + + def _parse_settings(self): + #TODO: Pass settings in a correct way + try: + self.__switch_info = self.__settings[SWITCH_INFO] + except Exception as ex: + LOGGER.error("Failed to parse settings: {}".format(ex)) + self._default_settings() #TODO: Remove when bug is fixed + self.__switch_info = self.__settings[SWITCH_INFO] + assert isinstance(self.__switch_info, dict), "Switch info object must be a map with switch names as keys" + + for switch_name, switch_info in self.__switch_info.items(): + assert switch_name, "Invalid P4 switch name" + assert isinstance(switch_info, dict), "Switch {} info must be a map with arch, dpid, and fwd_list items)" + assert switch_info[ARCH] in SUPPORTED_TARGET_ARCH_LIST, \ + "Switch {} - Supported P4 architectures are: {}".format(switch_name, ','.join(SUPPORTED_TARGET_ARCH_LIST)) + switch_dpid = switch_info[DPID] + assert switch_dpid > 0, "Switch {} - P4 switch dataplane ID must be a positive integer".format(switch_name, switch_info[DPID]) + + # Access Control list + acl = switch_info[ACL] + assert isinstance(acl, list), "Switch {} access control list must be a list with port_id, [ipv4_dst/src, trn_post_dst/src], and action items)" + for acl_entry in acl: + LOGGER.info("ACL entry: {}".format(acl_entry)) + port_id = acl_entry[PORT_ID] + assert port_id >= 0, "Switch {} - Invalid P4 switch port ID".format(switch_name) + + # Prepare the port map + if switch_name not in self.__port_map: + self.__port_map[switch_name] = {} + port_key = PORT_PREFIX + str(port_id) + if port_key not in self.__port_map[switch_name]: + self.__port_map[switch_name][port_key] = {} + self.__port_map[switch_name][port_key][PORT_ID] = port_id + if ACL not in self.__port_map[switch_name][port_key]: + self.__port_map[switch_name][port_key][ACL] = [] + + map_entry = {} + + ipv4_src = "" + if IPV4_SRC in acl_entry: + ipv4_src = acl_entry[IPV4_SRC] + assert chk_address_ipv4(ipv4_src), "Invalid source IPv4 address {}".format(ipv4_dst) + map_entry[IPV4_SRC] = ipv4_src + + ipv4_dst = "" + if IPV4_DST in acl_entry: + ipv4_dst = acl_entry[IPV4_DST] + assert chk_address_ipv4(ipv4_dst), "Invalid destination IPv4 address {}".format(ipv4_dst) + map_entry[IPV4_DST] = ipv4_dst + + ipv4_prefix_len = -1 + if ipv4_src or ipv4_dst: + ipv4_prefix_len = acl_entry[IPV4_PREFIX_LEN] + assert chk_prefix_len_ipv4(ipv4_prefix_len), "Invalid IPv4 address prefix length {}".format(ipv4_prefix_len) + map_entry[IPV4_PREFIX_LEN] = ipv4_prefix_len + + trn_port_src = -1 + if TRN_PORT_SRC in acl_entry: + trn_port_src = acl_entry[TRN_PORT_SRC] + assert chk_transport_port(trn_port_src), "Invalid source transport port" + map_entry[TRN_PORT_SRC] = trn_port_src + + trn_port_dst = -1 + if TRN_PORT_DST in acl_entry: + trn_port_dst = acl_entry[TRN_PORT_DST] + assert chk_transport_port(trn_port_dst), "Invalid destination transport port" + map_entry[TRN_PORT_DST] = trn_port_dst + + action = acl_entry[ACTION] + assert is_valid_acl_action(action), "Valid actions are: {}".format(','.join(ACTION_LIST)) + + # Retrieve entry from the port map + switch_port_entry = self._get_switch_port_in_port_map(switch_name, port_id) + + # Add routing entry + switch_port_entry[ACL].append(map_entry) + + def _print_settings(self): + LOGGER.info("--------------- {} settings ---------------".format(self.__service.name)) + LOGGER.info("--- Topology info") + for switch_name, switch_info in self.__switch_info.items(): + LOGGER.info("\t Device {}".format(switch_name)) + LOGGER.info("\t\t| Target P4 architecture: {}".format(switch_info[ARCH])) + LOGGER.info("\t\t| Data plane ID: {}".format(switch_info[DPID])) + LOGGER.info("\t\t| Port map: {}".format(self.__port_map[switch_name])) + LOGGER.info("-------------------------------------------------------") + + def _get_switch_port_in_port_map(self, switch_name : str, port_id : int) -> Dict: + assert switch_name, "A valid switch name must be used as a key to the port map" + assert port_id > 0, "A valid switch port ID must be used as a key to a switch's port map" + switch_entry = self.__port_map[switch_name] + assert switch_entry, "Switch {} does not exist in the port map".format(switch_name) + port_key = PORT_PREFIX + str(port_id) + assert switch_entry[port_key], "Port with ID {} does not exist in the switch map".format(port_id) + + return switch_entry[port_key] + + def _get_acl_of_switch_port(self, switch_name : str, port_id : int) -> List [Tuple]: + switch_port_entry = self._get_switch_port_in_port_map(switch_name, port_id) + return switch_port_entry[ACL] + + def _create_rules(self, device_obj : Device, port_id : int, action : ConfigActionEnum): # type: ignore + dev_name = device_obj.name + + rules = [] + + ### ACL rules + acl = self._get_acl_of_switch_port(switch_name=dev_name, port_id=port_id) + for acl_entry in acl: + if IPV4_SRC in acl_entry: + rules += rules_set_up_acl_filter_host( + ingress_port=port_id, + ip_address=acl_entry[IPV4_SRC], + prefix_len=acl_entry[IPV4_PREFIX_LEN], + ip_direction="src", + action=action + ) + if IPV4_DST in acl_entry: + rules += rules_set_up_acl_filter_host( + ingress_port=port_id, + ip_address=acl_entry[IPV4_DST], + prefix_len=acl_entry[IPV4_PREFIX_LEN], + ip_direction="dst", + action=action + ) + if TRN_PORT_SRC in acl_entry: + rules += rules_set_up_acl_filter_port( + ingress_port=port_id, + transport_port=acl_entry[TRN_PORT_SRC], + transport_direction="src", + action=action + ) + if TRN_PORT_DST in acl_entry: + rules += rules_set_up_acl_filter_port( + ingress_port=port_id, + transport_port=acl_entry[TRN_PORT_DST], + transport_direction="dst", + action=action + ) + + return rules diff --git a/src/tests/p4-fabric-tna/README.md b/src/tests/p4-fabric-tna/README.md index f6bc2dd0c..115932bb4 100644 --- a/src/tests/p4-fabric-tna/README.md +++ b/src/tests/p4-fabric-tna/README.md @@ -153,6 +153,20 @@ cd ~/tfs-ctrl/ bash src/tests/p4-fabric-tna/run_test_04b_service_deprovision_l3.sh ``` +#### Provision ACL network service via the Service API + +```shell +cd ~/tfs-ctrl/ +bash src/tests/p4-fabric-tna/run_test_05a_service_provision_acl.sh +``` + +#### Deprovision ACL network service via the Service API + +```shell +cd ~/tfs-ctrl/ +bash src/tests/p4-fabric-tna/run_test_05b_service_deprovision_acl.sh +``` + #### Provision INT service via the Service API ```shell diff --git a/src/tests/p4-fabric-tna/descriptors/service-create-acl.json b/src/tests/p4-fabric-tna/descriptors/service-create-acl.json new file mode 100644 index 000000000..d0beef010 --- /dev/null +++ b/src/tests/p4-fabric-tna/descriptors/service-create-acl.json @@ -0,0 +1,65 @@ +{ + "services": [ + { + "service_id": { + "context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "p4-service-acl"} + }, + "name": "p4-service-acl", + "service_type": "SERVICETYPE_ACL", + "service_status": {"service_status": "SERVICESTATUS_PLANNED"}, + "service_endpoint_ids": [ + { + "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, + "endpoint_uuid": {"uuid": "1"} + }, + { + "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, + "endpoint_uuid": {"uuid": "2"} + } + ], + "service_config": { + "config_rules": [ + { + "action": "CONFIGACTION_SET", + "custom": { + "resource_key": "/settings", + "resource_value": { + "switch_info": { + "p4-sw1": { + "arch": "v1model", + "dpid": 1, + "acl": [ + { + "port_id": 1, + "trn_port_dst": 8080, + "action": "drop" + }, + { + "port_id": 1, + "trn_port_src": 12345, + "action": "drop" + }, + { + "port_id": 1, + "ipv4_dst": "172.16.10.10", + "ipv4_prefix_len": 32, + "action": "drop" + }, + { + "port_id": 2, + "ipv4_src": "172.16.10.10", + "ipv4_prefix_len": 32, + "action": "drop" + } + ] + } + } + } + } + } + ] + }, + "service_constraints": [] + } + ] +} diff --git a/src/tests/p4-fabric-tna/run_test_05a_service_provision_acl.sh b/src/tests/p4-fabric-tna/run_test_05a_service_provision_acl.sh new file mode 100755 index 000000000..2cf94b1bd --- /dev/null +++ b/src/tests/p4-fabric-tna/run_test_05a_service_provision_acl.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source tfs_runtime_env_vars.sh +python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_acl.py diff --git a/src/tests/p4-fabric-tna/run_test_05b_service_deprovision_acl.sh b/src/tests/p4-fabric-tna/run_test_05b_service_deprovision_acl.sh new file mode 100755 index 000000000..681490896 --- /dev/null +++ b/src/tests/p4-fabric-tna/run_test_05b_service_deprovision_acl.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source tfs_runtime_env_vars.sh +python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_acl.py diff --git a/src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_acl.py b/src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_acl.py new file mode 100644 index 000000000..fcecbd2c7 --- /dev/null +++ b/src/tests/p4-fabric-tna/tests-service/test_functional_service_deprovision_acl.py @@ -0,0 +1,78 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from common.proto.context_pb2 import ServiceId, ServiceStatusEnum, ServiceTypeEnum +from common.tools.grpc.Tools import grpc_message_to_json_string +from common.tools.object_factory.Service import json_service_id +from context.client.ContextClient import ContextClient +from service.client.ServiceClient import ServiceClient +from tests.Fixtures import context_client, service_client # pylint: disable=unused-import +from tests.tools.test_tools_p4 import * + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +def test_service_deletion_acl( + context_client : ContextClient, # pylint: disable=redefined-outer-name + service_client : ServiceClient # pylint: disable=redefined-outer-name +) -> None: + # Get the current number of devices + response = context_client.ListDevices(ADMIN_CONTEXT_ID) + LOGGER.warning('Devices[{:d}] = {:s}'.format(len(response.devices), grpc_message_to_json_string(response))) + + # Total devices + dev_nb = len(response.devices) + assert dev_nb == DEV_NB + + # P4 devices + p4_dev_nb = identify_number_of_p4_devices(response.devices) + assert p4_dev_nb == P4_DEV_NB + + # Get the current number of rules in the P4 devices + p4_rules_before_deletion = get_number_of_rules(response.devices) + + # Get the current number of services + response = context_client.ListServices(ADMIN_CONTEXT_ID) + services_nb_before_deletion = len(response.services) + assert verify_active_service_type(response.services, ServiceTypeEnum.SERVICETYPE_ACL) + + for service in response.services: + # Ignore services of other types + if service.service_type != ServiceTypeEnum.SERVICETYPE_ACL: + continue + + service_id = service.service_id + assert service_id + + service_uuid = service_id.service_uuid.uuid + context_uuid = service_id.context_id.context_uuid.uuid + assert service.service_status.service_status == ServiceStatusEnum.SERVICESTATUS_ACTIVE + + # Delete ACL service + service_client.DeleteService(ServiceId(**json_service_id(service_uuid, json_context_id(context_uuid)))) + + # Get an updated view of the services + response = context_client.ListServices(ADMIN_CONTEXT_ID) + services_nb_after_deletion = len(response.services) + assert services_nb_after_deletion == services_nb_before_deletion - 1, "Exactly one new service must be deleted" + + # Get an updated view of the devices + response = context_client.ListDevices(ADMIN_CONTEXT_ID) + p4_rules_after_deletion = get_number_of_rules(response.devices) + + rules_diff = p4_rules_before_deletion - p4_rules_after_deletion + + assert p4_rules_after_deletion < p4_rules_before_deletion, "ACL service must contain some rules" + assert rules_diff == P4_DEV_NB * ACL_RULES, "ACL service must contain {} rules per device".format(ACL_RULES) diff --git a/src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_acl.py b/src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_acl.py new file mode 100644 index 000000000..58de046b4 --- /dev/null +++ b/src/tests/p4-fabric-tna/tests-service/test_functional_service_provision_acl.py @@ -0,0 +1,73 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from common.proto.context_pb2 import ServiceStatusEnum, ServiceTypeEnum +from common.tools.descriptor.Loader import DescriptorLoader, check_descriptor_load_results +from common.tools.grpc.Tools import grpc_message_to_json_string +from context.client.ContextClient import ContextClient +from device.client.DeviceClient import DeviceClient +from service.client.ServiceClient import ServiceClient +from tests.Fixtures import context_client, device_client, service_client # pylint: disable=unused-import +from tests.tools.test_tools_p4 import * + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +def test_service_creation_acl( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + service_client : ServiceClient # pylint: disable=redefined-outer-name +) -> None: + # Get the current number of services + response = context_client.ListServices(ADMIN_CONTEXT_ID) + services_nb_before = len(response.services) + + # Get the current number of devices + response = context_client.ListDevices(ADMIN_CONTEXT_ID) + LOGGER.warning('Devices[{:d}] = {:s}'.format(len(response.devices), grpc_message_to_json_string(response))) + + # Total devices + dev_nb = len(response.devices) + assert dev_nb == DEV_NB + + # P4 devices + p4_dev_nb = identify_number_of_p4_devices(response.devices) + assert p4_dev_nb == P4_DEV_NB + + # Get the current number of rules in the P4 devices + p4_rules_before = get_number_of_rules(response.devices) + + # Load service + descriptor_loader = DescriptorLoader( + descriptors_file=DESC_FILE_SERVICE_CREATE_ACL, + context_client=context_client, device_client=device_client, service_client=service_client + ) + results = descriptor_loader.process() + check_descriptor_load_results(results, descriptor_loader) + + # Get an updated view of the services + response = context_client.ListServices(ADMIN_CONTEXT_ID) + services_nb_after = len(response.services) + assert services_nb_after == services_nb_before + 1, "Exactly one new service must be in place" + assert verify_active_service_type(response.services, ServiceTypeEnum.SERVICETYPE_ACL) + + # Get an updated view of the devices + response = context_client.ListDevices(ADMIN_CONTEXT_ID) + p4_rules_after = get_number_of_rules(response.devices) + + rules_diff = p4_rules_after - p4_rules_before + + assert p4_rules_after > p4_rules_before, "ACL service must install some rules" + assert rules_diff == P4_DEV_NB * ACL_RULES, "ACL service must install {} rules per device".format(ACL_RULES) -- GitLab From 22bb98ab7a75738d8d7417d282182cc99d3b9a1d Mon Sep 17 00:00:00 2001 From: "Georgios P. Katsikas" <gkatsikas@ubitech.eu> Date: Mon, 31 Mar 2025 10:30:36 +0000 Subject: [PATCH 10/14] fix: service handlers' temporal configuration --- .../p4_fabric_tna_acl_service_handler.py | 53 +++++++++++------- .../p4_fabric_tna_l3_service_handler.py | 56 +++++++++---------- .../descriptors/service-create-acl.json | 8 +-- src/tests/tools/test_tools_p4.py | 2 +- 4 files changed, 65 insertions(+), 54 deletions(-) diff --git a/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py b/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py index 0b44a1ce8..dc86dc535 100644 --- a/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py +++ b/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py @@ -342,13 +342,24 @@ class P4FabricACLServiceHandler(_ServiceHandler): acl = [ { PORT_ID: 1, - IPV4_SRC: "10.158.72.11", + TRN_PORT_DST: 8080, + ACTION: ACTION_DROP + }, + { + PORT_ID: 2, + TRN_PORT_SRC: 12345, + ACTION: ACTION_DROP + }, + { + PORT_ID: 2, + IPV4_DST: "10.158.72.11", IPV4_PREFIX_LEN: 32, ACTION: ACTION_DROP }, { - PORT_ID: 1, - TRN_PORT_DST: 8080, + PORT_ID: 2, + IPV4_SRC: "10.158.72.12", + IPV4_PREFIX_LEN: 32, ACTION: ACTION_DROP } ] @@ -364,24 +375,24 @@ class P4FabricACLServiceHandler(_ServiceHandler): SWITCH_INFO: switch_info } - port_map = { - "p4-sw1": { - "port-1": { - PORT_ID: 1, - ACL: [ - { - IPV4_SRC: "10.158.72.11", - IPV4_PREFIX_LEN: 32, - ACTION: ACTION_DROP - }, - { - TRN_PORT_DST: 8080, - ACTION: ACTION_DROP - } - ] - } - } - } + # port_map = { + # "p4-sw1": { + # "port-1": { + # PORT_ID: 1, + # ACL: [ + # { + # IPV4_SRC: "10.158.72.11", + # IPV4_PREFIX_LEN: 32, + # ACTION: ACTION_DROP + # }, + # { + # TRN_PORT_DST: 8080, + # ACTION: ACTION_DROP + # } + # ] + # } + # } + # } def _parse_settings(self): #TODO: Pass settings in a correct way diff --git a/src/service/service/service_handlers/p4_fabric_tna_l3/p4_fabric_tna_l3_service_handler.py b/src/service/service/service_handlers/p4_fabric_tna_l3/p4_fabric_tna_l3_service_handler.py index 849d1db92..ed5dd25bd 100644 --- a/src/service/service/service_handlers/p4_fabric_tna_l3/p4_fabric_tna_l3_service_handler.py +++ b/src/service/service/service_handlers/p4_fabric_tna_l3/p4_fabric_tna_l3_service_handler.py @@ -358,34 +358,34 @@ class P4FabricL3ServiceHandler(_ServiceHandler): SWITCH_INFO: switch_info } - port_map = { - "p4-sw1": { - "port-1": { - PORT_ID: 1, - PORT_TYPE: PORT_TYPE_HOST, - ROUTING_LIST: [ - { - IPV4_DST: "10.158.72.11", - IPV4_PREFIX_LEN: 32, - MAC_SRC: "fa:16:3e:e2:af:28", - MAC_DST: "fa:16:3e:75:9c:e5" - } - ] - }, - "port-2": { - PORT_ID: 2, - PORT_TYPE: PORT_TYPE_HOST, - ROUTING_LIST: [ - { - IPV4_DST: "172.16.10.9", - IPV4_PREFIX_LEN: 32, - MAC_SRC: "fa:16:3e:75:9c:e5", - MAC_DST: "fa:16:3e:e2:af:28" - } - ] - } - } - } + # port_map = { + # "p4-sw1": { + # "port-1": { + # PORT_ID: 1, + # PORT_TYPE: PORT_TYPE_HOST, + # ROUTING_LIST: [ + # { + # IPV4_DST: "10.158.72.11", + # IPV4_PREFIX_LEN: 32, + # MAC_SRC: "fa:16:3e:e2:af:28", + # MAC_DST: "fa:16:3e:75:9c:e5" + # } + # ] + # }, + # "port-2": { + # PORT_ID: 2, + # PORT_TYPE: PORT_TYPE_HOST, + # ROUTING_LIST: [ + # { + # IPV4_DST: "172.16.10.9", + # IPV4_PREFIX_LEN: 32, + # MAC_SRC: "fa:16:3e:75:9c:e5", + # MAC_DST: "fa:16:3e:e2:af:28" + # } + # ] + # } + # } + # } def _parse_settings(self): #TODO: Pass settings in a correct way diff --git a/src/tests/p4-fabric-tna/descriptors/service-create-acl.json b/src/tests/p4-fabric-tna/descriptors/service-create-acl.json index d0beef010..225e8e85e 100644 --- a/src/tests/p4-fabric-tna/descriptors/service-create-acl.json +++ b/src/tests/p4-fabric-tna/descriptors/service-create-acl.json @@ -35,19 +35,19 @@ "action": "drop" }, { - "port_id": 1, + "port_id": 2, "trn_port_src": 12345, "action": "drop" }, { - "port_id": 1, - "ipv4_dst": "172.16.10.10", + "port_id": 2, + "ipv4_dst": "10.158.72.11", "ipv4_prefix_len": 32, "action": "drop" }, { "port_id": 2, - "ipv4_src": "172.16.10.10", + "ipv4_src": "10.158.72.12", "ipv4_prefix_len": 32, "action": "drop" } diff --git a/src/tests/tools/test_tools_p4.py b/src/tests/tools/test_tools_p4.py index aa37f9e2c..c68f35183 100644 --- a/src/tests/tools/test_tools_p4.py +++ b/src/tests/tools/test_tools_p4.py @@ -30,7 +30,7 @@ ENDPOINT_RULES = 3 INT_RULES = 19 L2_RULES = 10 L3_RULES = 4 -ACL_RULES = 2 +ACL_RULES = 1 DATAPLANE_RULES_NB_INT_B1 = 5 DATAPLANE_RULES_NB_INT_B2 = 6 -- GitLab From b042d3f7ad6cc35926aa2117b8c31fda101eb90e Mon Sep 17 00:00:00 2001 From: "Georgios P. Katsikas" <gkatsikas@ubitech.eu> Date: Mon, 31 Mar 2025 10:54:15 +0000 Subject: [PATCH 11/14] fix: P4 ACL temporary configuration --- .../p4_fabric_tna_acl_service_handler.py | 17 ----------------- .../descriptors/service-create-acl.json | 17 ----------------- 2 files changed, 34 deletions(-) diff --git a/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py b/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py index dc86dc535..53c4e7c86 100644 --- a/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py +++ b/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py @@ -344,23 +344,6 @@ class P4FabricACLServiceHandler(_ServiceHandler): PORT_ID: 1, TRN_PORT_DST: 8080, ACTION: ACTION_DROP - }, - { - PORT_ID: 2, - TRN_PORT_SRC: 12345, - ACTION: ACTION_DROP - }, - { - PORT_ID: 2, - IPV4_DST: "10.158.72.11", - IPV4_PREFIX_LEN: 32, - ACTION: ACTION_DROP - }, - { - PORT_ID: 2, - IPV4_SRC: "10.158.72.12", - IPV4_PREFIX_LEN: 32, - ACTION: ACTION_DROP } ] diff --git a/src/tests/p4-fabric-tna/descriptors/service-create-acl.json b/src/tests/p4-fabric-tna/descriptors/service-create-acl.json index 225e8e85e..a0383f472 100644 --- a/src/tests/p4-fabric-tna/descriptors/service-create-acl.json +++ b/src/tests/p4-fabric-tna/descriptors/service-create-acl.json @@ -33,23 +33,6 @@ "port_id": 1, "trn_port_dst": 8080, "action": "drop" - }, - { - "port_id": 2, - "trn_port_src": 12345, - "action": "drop" - }, - { - "port_id": 2, - "ipv4_dst": "10.158.72.11", - "ipv4_prefix_len": 32, - "action": "drop" - }, - { - "port_id": 2, - "ipv4_src": "10.158.72.12", - "ipv4_prefix_len": 32, - "action": "drop" } ] } -- GitLab From 9dfe1e7fb80cd9704b8bd7de0684f396e90c28b6 Mon Sep 17 00:00:00 2001 From: "Georgios P. Katsikas" <gkatsikas@ubitech.eu> Date: Mon, 31 Mar 2025 16:32:07 +0000 Subject: [PATCH 12/14] doc: update README --- src/tests/p4-fabric-tna/README.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/tests/p4-fabric-tna/README.md b/src/tests/p4-fabric-tna/README.md index 115932bb4..e8ae8fde9 100644 --- a/src/tests/p4-fabric-tna/README.md +++ b/src/tests/p4-fabric-tna/README.md @@ -72,13 +72,21 @@ The `./setup.sh` script copies from this directory. If you need to change the P4 A set of tests is implemented, each focusing on different aspects of TFS. For each of these tests, an auxiliary bash script allows to run it with less typing. -| Test | Bash Runner | Purpose | -| ------------------------------------ | ---------------------------------- | ---------------------------------- | -| - | setup.sh | Copy P4 artifacts into the SBI pod | -| test_functional_bootstrap.py | run_test_01_bootstrap.sh | Connect TFS to the P4 switch | -| test_functional_rules_provision.py | run_test_02_rules_provision.sh | Install rules on the P4 switch | -| test_functional_rules_deprovision.py | run_test_03_rules_deprovision.sh | Uninstall rules from the P4 switch | -| test_functional_cleanup.py | run_test_04_cleanup.sh | Disconnect TFS from the P4 switch | +| Bash Runner | Purpose | +| --------------------------------------------- | --------------------------------------------------------------------------- | +| setup.sh | Copy P4 artifacts into the SBI pod | +| run_test_01_bootstrap.sh | Connect TFS to the P4 switch | +| run_test_02a_sbi_provision_int_l2_l3_acl.sh | Install L2, L3, INT, and ACL rules on the P4 switch via the SBI service | +| run_test_02b_sbi_deprovision_int_l2_l3_acl.sh | Uninstall L2, L3, INT, and ACL rules from the P4 switch via the SBI service | +| run_test_03a_service_provision_l2.sh | Install L2 forwarding rules via a dedicated P4 L2 service handler | +| run_test_03b_service_deprovision_l2.sh | Uninstall L2 forwarding rules via a dedicated P4 L2 service handler | +| run_test_04a_service_provision_l3.sh | Install L3 routing rules via a dedicated P4 L3 service handler | +| run_test_04b_service_deprovision_l3.sh | Uninstall L3 routing rules via a dedicated P4 L3 service handler | +| run_test_05a_service_provision_acl.sh | Install ACL rules via a dedicated P4 ACL service handler | +| run_test_05b_service_deprovision_acl.sh | Uninstall ACL rules via a dedicated P4 ACL service handler | +| run_test_06a_service_provision_int.sh | Install INT rules via a dedicated P4 INT service handler | +| run_test_06b_service_deprovision_int.sh | Uninstall INT rules via a dedicated P4 INT service handler | +| run_test_07_cleanup.sh | Clean-up context and topology and disconnect TFS from the P4 switch | Each of the tests above is described in detail below. -- GitLab From 1296e935c1863d0f5ad7f284b5d3fa060a5de597 Mon Sep 17 00:00:00 2001 From: "Georgios P. Katsikas" <gkatsikas@ubitech.eu> Date: Mon, 5 May 2025 20:07:40 +0300 Subject: [PATCH 13/14] fix: remove hardcoded service settings after pathcomp bug fix --- .../p4_fabric_tna_acl_service_handler.py | 51 ++---------- .../p4_fabric_tna_int_service_handler.py | 40 ++-------- ...p4_fabric_tna_l2_simple_service_handler.py | 64 ++------------- .../p4_fabric_tna_l3_service_handler.py | 80 ++----------------- .../descriptors/service-create-acl.json | 6 +- .../descriptors/service-create-int.json | 14 ++-- .../descriptors/service-create-l2-simple.json | 10 +-- .../descriptors/service-create-l3.json | 18 ++--- .../p4-fabric-tna/descriptors/topology.json | 34 ++++---- ...witch-three-port-chassis-config-phy.pb.txt | 3 +- 10 files changed, 64 insertions(+), 256 deletions(-) diff --git a/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py b/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py index 53c4e7c86..b57086a29 100644 --- a/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py +++ b/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py @@ -335,56 +335,15 @@ class P4FabricACLServiceHandler(_ServiceHandler): self.__settings = self.__settings_handler.get('/settings') LOGGER.info("{} with settings: {}".format(self.__service_label, self.__settings)) except Exception as ex: - self.__settings = {} - LOGGER.error("Failed to parse service settings: {}".format(ex)) - - def _default_settings(self): - acl = [ - { - PORT_ID: 1, - TRN_PORT_DST: 8080, - ACTION: ACTION_DROP - } - ] - - switch_info = { - "p4-sw1": { - ARCH: TARGET_ARCH_V1MODEL, - DPID: 1, - ACL: acl - } - } - self.__settings = { - SWITCH_INFO: switch_info - } - - # port_map = { - # "p4-sw1": { - # "port-1": { - # PORT_ID: 1, - # ACL: [ - # { - # IPV4_SRC: "10.158.72.11", - # IPV4_PREFIX_LEN: 32, - # ACTION: ACTION_DROP - # }, - # { - # TRN_PORT_DST: 8080, - # ACTION: ACTION_DROP - # } - # ] - # } - # } - # } + LOGGER.error("Failed to retrieve service settings: {}".format(ex)) + raise Exception(ex) def _parse_settings(self): - #TODO: Pass settings in a correct way try: - self.__switch_info = self.__settings[SWITCH_INFO] + self.__switch_info = self.__settings.value[SWITCH_INFO] except Exception as ex: - LOGGER.error("Failed to parse settings: {}".format(ex)) - self._default_settings() #TODO: Remove when bug is fixed - self.__switch_info = self.__settings[SWITCH_INFO] + LOGGER.error("Failed to parse service settings: {}".format(ex)) + raise Exception(ex) assert isinstance(self.__switch_info, dict), "Switch info object must be a map with switch names as keys" for switch_name, switch_info in self.__switch_info.items(): diff --git a/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_service_handler.py b/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_service_handler.py index efdac5fea..dd19c4f89 100644 --- a/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_service_handler.py +++ b/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_service_handler.py @@ -305,43 +305,15 @@ class P4FabricINTServiceHandler(_ServiceHandler): self.__settings = self.__settings_handler.get('/settings') LOGGER.info("{} with settings: {}".format(self.__service_label, self.__settings)) except Exception as ex: - self.__settings = {} - LOGGER.error("Failed to parse service settings: {}".format(ex)) - - def _default_settings(self): - switch_info = { - "p4-sw1": { - ARCH: TARGET_ARCH_V1MODEL, - DPID: 1, - MAC: "fa:16:3e:93:8c:c0", - IP: "10.10.10.120", - PORT_INT: { - PORT_ID: 3, - PORT_TYPE: "host" - }, - RECIRCULATION_PORT_LIST: RECIRCULATION_PORTS_V1MODEL, - INT_REPORT_MIRROR_ID_LIST: INT_REPORT_MIRROR_ID_LIST_V1MODEL - } - } - int_collector_info = { - MAC: "fa:16:3e:fb:cf:96", - IP: "10.10.10.41", - PORT: 32766, - VLAN_ID: 4094 - } - self.__settings = { - SWITCH_INFO: switch_info, - INT_COLLECTOR_INFO: int_collector_info - } + LOGGER.error("Failed to retrieve service settings: {}".format(ex)) + raise Exception(ex) def _parse_settings(self): - #TODO: Pass settings in a correct way try: - self.__switch_info = self.__settings[SWITCH_INFO] + self.__switch_info = self.__settings.value[SWITCH_INFO] except Exception as ex: - LOGGER.error("Failed to parse settings: {}".format(ex)) - self._default_settings() #TODO: Remove when bug is fixed - self.__switch_info = self.__settings[SWITCH_INFO] + LOGGER.error("Failed to parse service settings: {}".format(ex)) + raise Exception(ex) assert isinstance(self.__switch_info, dict), "Switch info object must be a map with switch names as keys" for switch_name, switch_info in self.__switch_info.items(): @@ -364,7 +336,7 @@ class P4FabricINTServiceHandler(_ServiceHandler): switch_info[INT_REPORT_MIRROR_ID_LIST] = INT_REPORT_MIRROR_ID_LIST_V1MODEL assert isinstance(switch_info[RECIRCULATION_PORT_LIST], list), "Switch {} - Recirculation ports must be described as a list".format(switch_name) - self.__int_collector_info = self.__settings[INT_COLLECTOR_INFO] + self.__int_collector_info = self.__settings.value[INT_COLLECTOR_INFO] assert isinstance(self.__int_collector_info, dict), "INT collector info object must be a map with mac, ip, port, and vlan_id keys)" self.__int_collector_mac = self.__int_collector_info[MAC] diff --git a/src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_service_handler.py b/src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_service_handler.py index c25fb087c..8d4aaf081 100644 --- a/src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_service_handler.py +++ b/src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_service_handler.py @@ -318,69 +318,15 @@ class P4FabricL2SimpleServiceHandler(_ServiceHandler): self.__settings = self.__settings_handler.get('/settings') LOGGER.info("{} with settings: {}".format(self.__service_label, self.__settings)) except Exception as ex: - self.__settings = {} - LOGGER.error("Failed to parse service settings: {}".format(ex)) - - def _default_settings(self): - port_list = [ - { - PORT_ID: 1, - PORT_TYPE: "host", - VLAN_ID: 4094 - }, - { - PORT_ID: 2, - PORT_TYPE: "host", - VLAN_ID: 4094 - }, - ] - fwd_list = [ - { - PORT_ID: 1, - HOST_MAC: "fa:16:3e:75:9c:e5" - }, - { - PORT_ID: 2, - HOST_MAC: "fa:16:3e:e2:af:28" - } - ] - switch_info = { - "p4-sw1": { - ARCH: TARGET_ARCH_V1MODEL, - DPID: 1, - PORT_LIST: port_list, - FORWARDING_LIST: fwd_list - } - } - self.__settings = { - SWITCH_INFO: switch_info - } - - # port_map = { - # "p4-sw1": { - # "port-1": { - # PORT_ID: 1, - # PORT_TYPE: PORT_TYPE_HOST, - # VLAN_ID: 4094, - # FORWARDING_LIST: ["fa:16:3e:75:9c:e5"] - # }, - # "port-2": { - # PORT_ID: 2, - # PORT_TYPE: PORT_TYPE_HOST, - # VLAN_ID: 4094, - # FORWARDING_LIST: ["fa:16:3e:e2:af:28"] - # } - # } - # } + LOGGER.error("Failed to retrieve service settings: {}".format(ex)) + raise Exception(ex) def _parse_settings(self): - #TODO: Pass settings in a correct way try: - self.__switch_info = self.__settings[SWITCH_INFO] + self.__switch_info = self.__settings.value[SWITCH_INFO] except Exception as ex: - LOGGER.error("Failed to parse settings: {}".format(ex)) - self._default_settings() #TODO: Remove when bug is fixed - self.__switch_info = self.__settings[SWITCH_INFO] + LOGGER.error("Failed to parse service settings: {}".format(ex)) + raise Exception(ex) assert isinstance(self.__switch_info, dict), "Switch info object must be a map with switch names as keys" for switch_name, switch_info in self.__switch_info.items(): diff --git a/src/service/service/service_handlers/p4_fabric_tna_l3/p4_fabric_tna_l3_service_handler.py b/src/service/service/service_handlers/p4_fabric_tna_l3/p4_fabric_tna_l3_service_handler.py index ed5dd25bd..4b013328e 100644 --- a/src/service/service/service_handlers/p4_fabric_tna_l3/p4_fabric_tna_l3_service_handler.py +++ b/src/service/service/service_handlers/p4_fabric_tna_l3/p4_fabric_tna_l3_service_handler.py @@ -316,85 +316,15 @@ class P4FabricL3ServiceHandler(_ServiceHandler): self.__settings = self.__settings_handler.get('/settings') LOGGER.info("{} with settings: {}".format(self.__service_label, self.__settings)) except Exception as ex: - self.__settings = {} - LOGGER.error("Failed to parse service settings: {}".format(ex)) - - def _default_settings(self): - port_list = [ - { - PORT_ID: 1, - PORT_TYPE: "host" - }, - { - PORT_ID: 2, - PORT_TYPE: "host" - }, - ] - routing_list = [ - { - PORT_ID: 1, - IPV4_DST: "10.158.72.11", - IPV4_PREFIX_LEN: 32, - MAC_SRC: "fa:16:3e:e2:af:28", - MAC_DST: "fa:16:3e:75:9c:e5" - }, - { - PORT_ID: 2, - IPV4_DST: "172.16.10.9", - IPV4_PREFIX_LEN: 32, - MAC_SRC: "fa:16:3e:75:9c:e5", - MAC_DST: "fa:16:3e:e2:af:28" - } - ] - switch_info = { - "p4-sw1": { - ARCH: TARGET_ARCH_V1MODEL, - DPID: 1, - PORT_LIST: port_list, - ROUTING_LIST: routing_list - } - } - self.__settings = { - SWITCH_INFO: switch_info - } - - # port_map = { - # "p4-sw1": { - # "port-1": { - # PORT_ID: 1, - # PORT_TYPE: PORT_TYPE_HOST, - # ROUTING_LIST: [ - # { - # IPV4_DST: "10.158.72.11", - # IPV4_PREFIX_LEN: 32, - # MAC_SRC: "fa:16:3e:e2:af:28", - # MAC_DST: "fa:16:3e:75:9c:e5" - # } - # ] - # }, - # "port-2": { - # PORT_ID: 2, - # PORT_TYPE: PORT_TYPE_HOST, - # ROUTING_LIST: [ - # { - # IPV4_DST: "172.16.10.9", - # IPV4_PREFIX_LEN: 32, - # MAC_SRC: "fa:16:3e:75:9c:e5", - # MAC_DST: "fa:16:3e:e2:af:28" - # } - # ] - # } - # } - # } + LOGGER.error("Failed to retrieve service settings: {}".format(ex)) + raise Exception(ex) def _parse_settings(self): - #TODO: Pass settings in a correct way try: - self.__switch_info = self.__settings[SWITCH_INFO] + self.__switch_info = self.__settings.value[SWITCH_INFO] except Exception as ex: - LOGGER.error("Failed to parse settings: {}".format(ex)) - self._default_settings() #TODO: Remove when bug is fixed - self.__switch_info = self.__settings[SWITCH_INFO] + LOGGER.error("Failed to parse service settings: {}".format(ex)) + raise Exception(ex) assert isinstance(self.__switch_info, dict), "Switch info object must be a map with switch names as keys" for switch_name, switch_info in self.__switch_info.items(): diff --git a/src/tests/p4-fabric-tna/descriptors/service-create-acl.json b/src/tests/p4-fabric-tna/descriptors/service-create-acl.json index a0383f472..2ec9c6674 100644 --- a/src/tests/p4-fabric-tna/descriptors/service-create-acl.json +++ b/src/tests/p4-fabric-tna/descriptors/service-create-acl.json @@ -9,11 +9,11 @@ "service_status": {"service_status": "SERVICESTATUS_PLANNED"}, "service_endpoint_ids": [ { - "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, + "device_id": {"device_uuid": {"uuid": "sw1"}}, "endpoint_uuid": {"uuid": "1"} }, { - "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, + "device_id": {"device_uuid": {"uuid": "sw1"}}, "endpoint_uuid": {"uuid": "2"} } ], @@ -25,7 +25,7 @@ "resource_key": "/settings", "resource_value": { "switch_info": { - "p4-sw1": { + "sw1": { "arch": "v1model", "dpid": 1, "acl": [ diff --git a/src/tests/p4-fabric-tna/descriptors/service-create-int.json b/src/tests/p4-fabric-tna/descriptors/service-create-int.json index 98956b959..785468f68 100644 --- a/src/tests/p4-fabric-tna/descriptors/service-create-int.json +++ b/src/tests/p4-fabric-tna/descriptors/service-create-int.json @@ -9,11 +9,11 @@ "service_status": {"service_status": "SERVICESTATUS_PLANNED"}, "service_endpoint_ids": [ { - "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, + "device_id": {"device_uuid": {"uuid": "sw1"}}, "endpoint_uuid": {"uuid": "1"} }, { - "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, + "device_id": {"device_uuid": {"uuid": "sw1"}}, "endpoint_uuid": {"uuid": "2"} } ], @@ -25,11 +25,11 @@ "resource_key": "/settings", "resource_value": { "switch_info": { - "p4-sw1": { + "sw1": { "arch": "v1model", "dpid": 1, - "mac": "fa:16:3e:93:8c:c0", - "ip": "10.10.10.120", + "mac": "ee:ee:8c:6c:f3:2c", + "ip": "192.168.5.139", "int_port": { "port_id": 3, "port_type": "host" @@ -37,8 +37,8 @@ } }, "int_collector_info": { - "mac": "fa:16:3e:fb:cf:96", - "ip": "10.10.10.41", + "mac": "46:e4:58:c6:74:53", + "ip": "192.168.5.137", "port": 32766, "vlan_id": 4094 } diff --git a/src/tests/p4-fabric-tna/descriptors/service-create-l2-simple.json b/src/tests/p4-fabric-tna/descriptors/service-create-l2-simple.json index 5a659f3d9..05f53d7a6 100644 --- a/src/tests/p4-fabric-tna/descriptors/service-create-l2-simple.json +++ b/src/tests/p4-fabric-tna/descriptors/service-create-l2-simple.json @@ -9,11 +9,11 @@ "service_status": {"service_status": "SERVICESTATUS_PLANNED"}, "service_endpoint_ids": [ { - "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, + "device_id": {"device_uuid": {"uuid": "sw1"}}, "endpoint_uuid": {"uuid": "1"} }, { - "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, + "device_id": {"device_uuid": {"uuid": "sw1"}}, "endpoint_uuid": {"uuid": "2"} } ], @@ -25,7 +25,7 @@ "resource_key": "/settings", "resource_value": { "switch_info": { - "p4-sw1": { + "sw1": { "arch": "v1model", "dpid": 1, "port_list": [ @@ -43,11 +43,11 @@ "fwd_list": [ { "port_id": 1, - "host_mac": "fa:16:3e:75:9c:e5" + "host_mac": "00:00:00:00:00:01" }, { "port_id": 2, - "host_mac": "fa:16:3e:e2:af:28" + "host_mac": "00:00:00:00:00:02" } ] } diff --git a/src/tests/p4-fabric-tna/descriptors/service-create-l3.json b/src/tests/p4-fabric-tna/descriptors/service-create-l3.json index 7d8153e7a..4a016f255 100644 --- a/src/tests/p4-fabric-tna/descriptors/service-create-l3.json +++ b/src/tests/p4-fabric-tna/descriptors/service-create-l3.json @@ -9,11 +9,11 @@ "service_status": {"service_status": "SERVICESTATUS_PLANNED"}, "service_endpoint_ids": [ { - "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, + "device_id": {"device_uuid": {"uuid": "sw1"}}, "endpoint_uuid": {"uuid": "1"} }, { - "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, + "device_id": {"device_uuid": {"uuid": "sw1"}}, "endpoint_uuid": {"uuid": "2"} } ], @@ -25,7 +25,7 @@ "resource_key": "/settings", "resource_value": { "switch_info": { - "p4-sw1": { + "sw1": { "arch": "v1model", "dpid": 1, "port_list": [ @@ -41,17 +41,17 @@ "routing_list": [ { "port_id": 1, - "ipv4_dst": "10.158.72.11", + "ipv4_dst": "10.0.0.1", "ipv4_prefix_len": 32, - "mac_src": "fa:16:3e:e2:af:28", - "mac_dst": "fa:16:3e:75:9c:e5" + "mac_src": "00:00:00:00:00:02", + "mac_dst": "00:00:00:00:00:01" }, { "port_id": 2, - "ipv4_dst": "172.16.10.9", + "ipv4_dst": "10.0.0.2", "ipv4_prefix_len": 32, - "mac_src": "fa:16:3e:75:9c:e5", - "mac_dst": "fa:16:3e:e2:af:28" + "mac_src": "00:00:00:00:00:01", + "mac_dst": "00:00:00:00:00:02" } ] } diff --git a/src/tests/p4-fabric-tna/descriptors/topology.json b/src/tests/p4-fabric-tna/descriptors/topology.json index 30e1f5e9a..908faaa7d 100644 --- a/src/tests/p4-fabric-tna/descriptors/topology.json +++ b/src/tests/p4-fabric-tna/descriptors/topology.json @@ -11,11 +11,11 @@ "device_id": {"device_uuid": {"uuid": "tfs-sdn-controller"}}, "name": "tfs-sdn-controller", "device_type": "teraflowsdn", - "device_drivers": ["DEVICEDRIVER_IETF_L3VPN"], + "device_drivers": ["DEVICEDRIVER_IETF_L2VPN"], "device_operational_status": "DEVICEOPERATIONALSTATUS_UNDEFINED", "device_config": {"config_rules": [ - {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.10.10.41"}}, - {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8002"}}, + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "192.168.5.137"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8003"}}, {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": { "endpoints": [{"uuid": "mgmt", "name": "mgmt", "type": "mgmt-int"}], "scheme": "http", "username": "admin", "password": "admin", "import_topology": "topology" @@ -51,18 +51,18 @@ } }, { - "device_id": {"device_uuid": {"uuid": "p4-sw1"}}, + "device_id": {"device_uuid": {"uuid": "sw1"}}, "device_type": "p4-switch", "device_drivers": ["DEVICEDRIVER_P4"], "device_operational_status": "DEVICEOPERATIONALSTATUS_DISABLED", - "name": "p4-sw1", + "name": "sw1", "device_config": { "config_rules": [ - {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/address", "resource_value": "10.10.10.120"}}, + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/address", "resource_value": "192.168.5.139"}}, {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/port", "resource_value": "50001"}}, {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/settings", "resource_value": { "id": 1, - "name": "p4-sw1", + "name": "sw1", "vendor": "Open Networking Foundation", "hw_ver": "BMv2 simple_switch", "sw_ver": "Stratum", @@ -81,32 +81,32 @@ ], "links": [ { - "link_id": {"link_uuid": {"uuid": "p4-sw1/1==edge-net/eth1"}}, "link_type": "LINKTYPE_VIRTUAL", "link_endpoint_ids": [ - {"device_id": {"device_uuid": {"uuid": "p4-sw1"}}, "endpoint_uuid": {"uuid": "1"}}, + "link_id": {"link_uuid": {"uuid": "sw1/1==edge-net/eth1"}}, "link_type": "LINKTYPE_VIRTUAL", "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "sw1"}}, "endpoint_uuid": {"uuid": "1"}}, {"device_id": {"device_uuid": {"uuid": "edge-net"}}, "endpoint_uuid": {"uuid": "eth1"}} ] }, { - "link_id": {"link_uuid": {"uuid": "edge-net/eth1==p4-sw1/1"}}, "link_type": "LINKTYPE_VIRTUAL", "link_endpoint_ids": [ + "link_id": {"link_uuid": {"uuid": "edge-net/eth1==sw1/1"}}, "link_type": "LINKTYPE_VIRTUAL", "link_endpoint_ids": [ {"device_id": {"device_uuid": {"uuid": "edge-net"}}, "endpoint_uuid": {"uuid": "eth1"}}, - {"device_id": {"device_uuid": {"uuid": "p4-sw1"}}, "endpoint_uuid": {"uuid": "1"}} + {"device_id": {"device_uuid": {"uuid": "sw1"}}, "endpoint_uuid": {"uuid": "1"}} ] }, { - "link_id": {"link_uuid": {"uuid": "p4-sw1/2==corporate-net/eth1"}}, "link_type": "LINKTYPE_VIRTUAL", "link_endpoint_ids": [ - {"device_id": {"device_uuid": {"uuid": "p4-sw1"}}, "endpoint_uuid": {"uuid": "2"}}, + "link_id": {"link_uuid": {"uuid": "sw1/2==corporate-net/eth1"}}, "link_type": "LINKTYPE_VIRTUAL", "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "sw1"}}, "endpoint_uuid": {"uuid": "2"}}, {"device_id": {"device_uuid": {"uuid": "corporate-net"}}, "endpoint_uuid": {"uuid": "eth1"}} ] }, { - "link_id": {"link_uuid": {"uuid": "corporate-net/eth1==p4-sw1/2"}}, "link_type": "LINKTYPE_VIRTUAL", "link_endpoint_ids": [ + "link_id": {"link_uuid": {"uuid": "corporate-net/eth1==sw1/2"}}, "link_type": "LINKTYPE_VIRTUAL", "link_endpoint_ids": [ {"device_id": {"device_uuid": {"uuid": "corporate-net"}}, "endpoint_uuid": {"uuid": "eth1"}}, - {"device_id": {"device_uuid": {"uuid": "p4-sw1"}}, "endpoint_uuid": {"uuid": "2"}} + {"device_id": {"device_uuid": {"uuid": "sw1"}}, "endpoint_uuid": {"uuid": "2"}} ] }, { - "link_id": {"link_uuid": {"uuid": "p4-sw1/3==tfs-sdn-controller/mgmt"}}, "link_type": "LINKTYPE_MANAGEMENT", "link_endpoint_ids": [ - {"device_id": {"device_uuid": {"uuid": "p4-sw1"}}, "endpoint_uuid": {"uuid": "3"}}, + "link_id": {"link_uuid": {"uuid": "sw1/3==tfs-sdn-controller/mgmt"}}, "link_type": "LINKTYPE_MANAGEMENT", "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "sw1"}}, "endpoint_uuid": {"uuid": "3"}}, {"device_id": {"device_uuid": {"uuid": "tfs-sdn-controller"}}, "endpoint_uuid": {"uuid": "mgmt"}} ] } diff --git a/src/tests/p4-fabric-tna/topology/p4-switch-three-port-chassis-config-phy.pb.txt b/src/tests/p4-fabric-tna/topology/p4-switch-three-port-chassis-config-phy.pb.txt index 038d36269..bc13f29d9 100644 --- a/src/tests/p4-fabric-tna/topology/p4-switch-three-port-chassis-config-phy.pb.txt +++ b/src/tests/p4-fabric-tna/topology/p4-switch-three-port-chassis-config-phy.pb.txt @@ -18,10 +18,11 @@ description: "Chassis configuration for a single Stratum bmv2 switch with 3 ports" chassis { platform: PLT_P4_SOFT_SWITCH - name: "bmv2-switch" + name: "sw1" } nodes { id: 1 + name: "sw1 node 1" slot: 1 index: 1 } -- GitLab From 08533d97d5d59cc97765777fce9d85dc3b59b5cd Mon Sep 17 00:00:00 2001 From: "Georgios P. Katsikas" <gkatsikas@ubitech.eu> Date: Thu, 8 May 2025 12:25:17 +0300 Subject: [PATCH 14/14] fix: service types order in proto --- proto/context.proto | 12 ++++++------ src/common/tools/object_factory/Service.py | 2 +- .../p4-fwd-l1/tests/topologies/6switchObjects.py | 14 ++------------ 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/proto/context.proto b/proto/context.proto index 0f2c8011f..525997847 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -321,12 +321,12 @@ enum ServiceTypeEnum { SERVICETYPE_UNKNOWN = 0; SERVICETYPE_L3NM = 1; SERVICETYPE_L2NM = 2; - SERVICETYPE_L1NM = 3; - SERVICETYPE_TAPI_CONNECTIVITY_SERVICE = 4; - SERVICETYPE_TE = 5; - SERVICETYPE_E2E = 6; - SERVICETYPE_OPTICAL_CONNECTIVITY = 7; - SERVICETYPE_QKD = 8; + SERVICETYPE_TAPI_CONNECTIVITY_SERVICE = 3; + SERVICETYPE_TE = 4; + SERVICETYPE_E2E = 5; + SERVICETYPE_OPTICAL_CONNECTIVITY = 6; + SERVICETYPE_QKD = 7; + SERVICETYPE_L1NM = 8; SERVICETYPE_INT = 9; SERVICETYPE_ACL = 10; } diff --git a/src/common/tools/object_factory/Service.py b/src/common/tools/object_factory/Service.py index 74c183230..0d039ac35 100644 --- a/src/common/tools/object_factory/Service.py +++ b/src/common/tools/object_factory/Service.py @@ -90,6 +90,6 @@ def json_service_p4_planned( ): return json_service( - service_uuid, ServiceTypeEnum.SERVICETYPE_L2NM, context_id=json_context_id(context_uuid), + service_uuid, ServiceTypeEnum.SERVICETYPE_L1NM, context_id=json_context_id(context_uuid), status=ServiceStatusEnum.SERVICESTATUS_PLANNED, endpoint_ids=endpoint_ids, constraints=constraints, config_rules=config_rules) \ No newline at end of file diff --git a/src/tests/p4-fwd-l1/tests/topologies/6switchObjects.py b/src/tests/p4-fwd-l1/tests/topologies/6switchObjects.py index 522066bb0..69a68a569 100644 --- a/src/tests/p4-fwd-l1/tests/topologies/6switchObjects.py +++ b/src/tests/p4-fwd-l1/tests/topologies/6switchObjects.py @@ -312,12 +312,6 @@ LINK_SW5_SW6 = json_link(LINK_SW5_SW6_UUID, [ENDPOINT_ID_SW5_2, E # ----- Service ---------------------------------------------------------------------------------------------------------- -#SERVICE_SW1_UUID = get_service_uuid(ENDPOINT_ID_SW1_1, ENDPOINT_ID_SW1_2) -#SERVICE_SW1 = json_service_p4_planned(SERVICE_SW1_UUID) - -#SERVICE_SW2_UUID = get_service_uuid(ENDPOINT_ID_SW2_1, ENDPOINT_ID_SW2_2) -#SERVICE_SW2 = json_service_p4_planned(SERVICE_SW2_UUID) - SERVICE_SW1_SW6_UUID = get_service_uuid(ENDPOINT_ID_SW1_3, ENDPOINT_ID_SW6_3) SERVICE_SW1_SW6 = json_service_p4_planned(SERVICE_SW1_SW6_UUID) SERVICE_SW1_SW6_ENDPOINT_IDS = [DEVICE_SW1_ENDPOINT_IDS[2], DEVICE_SW6_ENDPOINT_IDS[2]] @@ -345,10 +339,6 @@ LINKS = [ LINK_SW4_SW6, LINK_SW5_SW6 - ] - -#SERVICES = [(SERVICE_SW1, DEVICE_SW1_ENDPOINT_IDS), (SERVICE_SW2, DEVICE_SW2_ENDPOINT_IDS)] - -#SERVICE_SW1_SW2_ENDPOINT_IDS = DEVICE_SW1_ENDPOINT_IDS + DEVICE_SW2_ENDPOINT_IDS + ] -SERVICES = [(SERVICE_SW1_SW6, SERVICE_SW1_SW6_ENDPOINT_IDS)] \ No newline at end of file +SERVICES = [(SERVICE_SW1_SW6, SERVICE_SW1_SW6_ENDPOINT_IDS)] -- GitLab