Skip to content
Snippets Groups Projects
Commit 48ac5326 authored by Georgios Katsikas's avatar Georgios Katsikas
Browse files

feat: P4 L3 service handler

parent 60e61895
No related branches found
No related tags found
No related merge requests found
......@@ -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,
......
# 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.
# 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
......@@ -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
......
{
"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": []
}
]
}
#!/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
#!/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
# 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)
# 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)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment