Commits (4)
......@@ -61,3 +61,13 @@ def json_service_tapi_planned(
service_uuid, ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, context_id=json_context_id(context_uuid),
status=ServiceStatusEnum.SERVICESTATUS_PLANNED, endpoint_ids=endpoint_ids, constraints=constraints,
config_rules=config_rules)
def json_service_p4_planned(
service_uuid : str, endpoint_ids : List[Dict] = [], constraints : List[Dict] = [],
config_rules : List[Dict] = [], context_uuid : str = DEFAULT_CONTEXT_UUID
):
return json_service(
service_uuid, ServiceTypeEnum.SERVICETYPE_L2NM, 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
......@@ -23,7 +23,7 @@ SERVICE_TYPE_VALUES = {
ServiceTypeEnum.SERVICETYPE_UNKNOWN,
ServiceTypeEnum.SERVICETYPE_L3NM,
ServiceTypeEnum.SERVICETYPE_L2NM,
ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE,
ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE
}
DEVICE_DRIVER_VALUES = {
......
......@@ -17,6 +17,7 @@ from ..service_handler_api.FilterFields import FilterFieldEnum
from .l2nm_emulated.L2NMEmulatedServiceHandler import L2NMEmulatedServiceHandler
from .l3nm_emulated.L3NMEmulatedServiceHandler import L3NMEmulatedServiceHandler
from .l3nm_openconfig.L3NMOpenConfigServiceHandler import L3NMOpenConfigServiceHandler
from .p4.p4_service_handler import P4ServiceHandler
from .tapi_tapi.TapiServiceHandler import TapiServiceHandler
from .microwave.MicrowaveServiceHandler import MicrowaveServiceHandler
......@@ -51,4 +52,10 @@ SERVICE_HANDLERS = [
FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY,
}
]),
]
\ No newline at end of file
(P4ServiceHandler, [
{
FilterFieldEnum.SERVICE_TYPE: ServiceTypeEnum.SERVICETYPE_L2NM,
FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4,
}
]),
]
# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# 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 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# 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.
"""
P4 service handler for the TeraFlowSDN controller.
"""
import anytree, json, logging
from typing import Any, Dict, List, Optional, Tuple, Union
from common.proto.context_pb2 import ConfigActionEnum, ConfigRule, DeviceId, Service
from common.tools.object_factory.ConfigRule import json_config_rule, json_config_rule_delete, json_config_rule_set
from common.tools.object_factory.Device import json_device_id
from common.type_checkers.Checkers import chk_type, chk_length
from service.service.service_handler_api._ServiceHandler import _ServiceHandler
from service.service.service_handler_api.AnyTreeTools import TreeNode, delete_subnode, get_subnode, set_subnode_value
from service.service.task_scheduler.TaskExecutor import TaskExecutor
LOGGER = logging.getLogger(__name__)
def create_rule_set(endpoint_a, endpoint_b):
return json_config_rule_set(
'table',
{
'table-name': 'IngressPipeImpl.l2_exact_table',
'match-fields': [
{
'match-field': 'standard_metadata.ingress_port',
'match-value': endpoint_a
}
],
'action-name': 'IngressPipeImpl.set_egress_port',
'action-params': [
{
'action-param': 'port',
'action-value': endpoint_b
}
]
}
)
def create_rule_del(endpoint_a, endpoint_b):
return json_config_rule_delete(
'table',
{
'table-name': 'IngressPipeImpl.l2_exact_table',
'match-fields': [
{
'match-field': 'standard_metadata.ingress_port',
'match-value': endpoint_a
}
],
'action-name': 'IngressPipeImpl.set_egress_port',
'action-params': [
{
'action-param': 'port',
'action-value': endpoint_b
}
]
}
)
class P4ServiceHandler(_ServiceHandler):
def __init__(self,
service: Service,
task_executor : TaskExecutor,
**settings) -> 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 = service
self.__task_executor = task_executor # pylint: disable=unused-private-member
def SetEndpoint(
self, endpoints : List[Tuple[str, str, Optional[str]]],
connection_uuid : Optional[str] = None
) -> List[Union[bool, Exception]]:
""" Create/Update service endpoints form 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 []
service_uuid = self.__service.service_id.service_uuid.uuid
history = {}
results = []
index = {}
i = 0
for endpoint in endpoints:
device_uuid, endpoint_uuid = endpoint[0:2] # ignore topology_uuid by now
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[:]
# One way
rule = create_rule_set(matched_endpoint_uuid, endpoint_uuid)
device.device_config.config_rules.append(ConfigRule(**rule))
# The other way
rule = create_rule_set(endpoint_uuid, matched_endpoint_uuid)
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:
LOGGER.exception('Unable to SetEndpoint({:s})'.format(str(endpoint)))
results.append(e)
else:
history[device_uuid] = endpoint_uuid
index[device_uuid] = i
results.append(False)
i = i+1
return results
def DeleteEndpoint(
self, endpoints : List[Tuple[str, str, Optional[str]]],
connection_uuid : Optional[str] = None
) -> List[Union[bool, Exception]]:
""" Delete service endpoints form 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 []
service_uuid = self.__service.service_id.service_uuid.uuid
history = {}
results = []
index = {}
i = 0
for endpoint in endpoints:
device_uuid, endpoint_uuid = endpoint[0:2] # ignore topology_uuid by now
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[:]
# One way
rule = create_rule_del(matched_endpoint_uuid, endpoint_uuid)
device.device_config.config_rules.append(ConfigRule(**rule))
# The other way
rule = create_rule_del(endpoint_uuid, matched_endpoint_uuid)
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:
LOGGER.exception('Unable to SetEndpoint({:s})'.format(str(endpoint)))
results.append(e)
else:
history[device_uuid] = endpoint_uuid
index[device_uuid] = i
results.append(False)
i = i+1
return results
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))]
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))]
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))]
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))]
\ No newline at end of file
......@@ -15,12 +15,11 @@
import copy, grpc, logging, pytest
from common.proto.context_pb2 import (
Context, ContextId, Device, DeviceId, Link, LinkId, Service, ServiceId, Topology, TopologyId)
from common.tests.PytestGenerateTests import pytest_generate_tests # (required) pylint: disable=unused-import
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 .PrepareTestScenario import ( # pylint: disable=unused-import
from .PrepareTestScenario import ( # pylint: disable=unused-import
# be careful, order of symbols is important here!
mock_service, service_service, context_client, device_client, service_client)
......
# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# 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.
# Set the URL of your local Docker registry where the images will be uploaded to.
export TFS_REGISTRY_IMAGE="http://localhost:32000/tfs/"
# Set the list of components, separated by spaces, you want to build images for, and deploy.
export TFS_COMPONENTS="context device automation service compute monitoring webui"
# Set the tag you want to use for your images.
export TFS_IMAGE_TAG="dev"
# Set the name of the Kubernetes namespace to deploy to.
export TFS_K8S_NAMESPACE="tfs"
# Set additional manifest files to be applied after the deployment
export TFS_EXTRA_MANIFESTS="manifests/nginx_ingress_http.yaml"
# Set the neew Grafana admin password
export TFS_GRAFANA_PASSWORD="admin123+"
#!/usr/bin/python
# Copyright 2019-present Open Networking Foundation
#
# 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 argparse
from mininet.cli import CLI
from mininet.log import setLogLevel
from mininet.net import Mininet
from mininet.node import Host
from mininet.topo import Topo
from stratum import StratumBmv2Switch
CPU_PORT = 255
class IPv4Host(Host):
"""Host that can be configured with an IPv4 gateway (default route).
"""
def config(self, mac=None, ip=None, defaultRoute=None, lo='up', gw=None,
**_params):
super(IPv4Host, self).config(mac, ip, defaultRoute, lo, **_params)
self.cmd('ip -4 addr flush dev %s' % self.defaultIntf())
self.cmd('ip -6 addr flush dev %s' % self.defaultIntf())
self.cmd('ip -4 link set up %s' % self.defaultIntf())
self.cmd('ip -4 addr add %s dev %s' % (ip, self.defaultIntf()))
if gw:
self.cmd('ip -4 route add default via %s' % gw)
# Disable offload
for attr in ["rx", "tx", "sg"]:
cmd = "/sbin/ethtool --offload %s %s off" % (
self.defaultIntf(), attr)
self.cmd(cmd)
def updateIP():
return ip.split('/')[0]
self.defaultIntf().updateIP = updateIP
class TutorialTopo(Topo):
"""Basic Server-Client topology with IPv4 hosts"""
def __init__(self, *args, **kwargs):
Topo.__init__(self, *args, **kwargs)
# Spines
# gRPC port 50001
switch1 = self.addSwitch('switch1', cls=StratumBmv2Switch, cpuport=CPU_PORT)
# IPv4 hosts attached to switch 1
client = self.addHost('client', cls=IPv4Host, mac="aa:bb:cc:dd:ee:11",
ip='10.0.0.1/24', gw='10.0.0.100')
# client.sendCmd('arp -s 10.0.0.2 aa:bb:cc:dd:ee:22')
# client.setARP('10.0.0.2', 'aa:bb:cc:dd:ee:22')
server = self.addHost('server', cls=IPv4Host, mac="aa:bb:cc:dd:ee:22",
ip='10.0.0.2/24', gw='10.0.0.100')
# server.sendCmd('arp -s 10.0.0.1 aa:bb:cc:dd:ee:11')
# server.setARP('10.0.0.1', 'aa:bb:cc:dd:ee:11')
self.addLink(client, switch1) # port 1
self.addLink(server, switch1) # port 2
def main():
net = Mininet(topo=TutorialTopo(), controller=None)
net.start()
client = net.hosts[0]
server = net.hosts[1]
client.setARP('10.0.0.2', 'aa:bb:cc:dd:ee:22')
server.setARP('10.0.0.1', 'aa:bb:cc:dd:ee:11')
CLI(net)
net.stop()
print '#' * 80
print 'ATTENTION: Mininet was stopped! Perhaps accidentally?'
print 'No worries, it will restart automatically in a few seconds...'
print 'To access again the Mininet CLI, use `make mn-cli`'
print 'To detach from the CLI (without stopping), press Ctrl-D'
print 'To permanently quit Mininet, use `make stop`'
print '#' * 80
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description='Mininet topology script for 2x2 fabric with stratum_bmv2 and IPv4 hosts')
args = parser.parse_args()
setLogLevel('info')
main()
#!/usr/bin/python
# Copyright 2019-present Open Networking Foundation
#
# 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 argparse
from mininet.cli import CLI
from mininet.log import setLogLevel
from mininet.net import Mininet
from mininet.node import Host
from mininet.topo import Topo
from stratum import StratumBmv2Switch
CPU_PORT = 255
class IPv4Host(Host):
"""Host that can be configured with an IPv4 gateway (default route).
"""
def config(self, mac=None, ip=None, defaultRoute=None, lo='up', gw=None,
**_params):
super(IPv4Host, self).config(mac, ip, defaultRoute, lo, **_params)
self.cmd('ip -4 addr flush dev %s' % self.defaultIntf())
self.cmd('ip -6 addr flush dev %s' % self.defaultIntf())
self.cmd('ip -4 link set up %s' % self.defaultIntf())
self.cmd('ip -4 addr add %s dev %s' % (ip, self.defaultIntf()))
if gw:
self.cmd('ip -4 route add default via %s' % gw)
# Disable offload
for attr in ["rx", "tx", "sg"]:
cmd = "/sbin/ethtool --offload %s %s off" % (
self.defaultIntf(), attr)
self.cmd(cmd)
def updateIP():
return ip.split('/')[0]
self.defaultIntf().updateIP = updateIP
class TutorialTopo(Topo):
"""Basic Server-Client topology with IPv4 hosts"""
def __init__(self, *args, **kwargs):
Topo.__init__(self, *args, **kwargs)
# Spines
# gRPC port 50001
switch1 = self.addSwitch('switch1', cls=StratumBmv2Switch, cpuport=CPU_PORT)
# gRPC port 50002
switch2 = self.addSwitch('switch2', cls=StratumBmv2Switch, cpuport=CPU_PORT)
# IPv4 hosts attached to switch 1
client = self.addHost('client', cls=IPv4Host, mac="aa:bb:cc:dd:ee:11",
ip='10.0.0.1/24', gw='10.0.0.100')
server = self.addHost('server', cls=IPv4Host, mac="aa:bb:cc:dd:ee:22",
ip='10.0.0.2/24', gw='10.0.0.100')
self.addLink(client, switch1) # switch1: port 1
self.addLink(switch1, switch2) # switch1: port 2 == switch2: port 1
self.addLink(switch2, server) # switch2: port 2
def main():
net = Mininet(topo=TutorialTopo(), controller=None)
net.start()
client = net.hosts[0]
client.setARP('10.0.0.2', 'aa:bb:cc:dd:ee:22')
server = net.hosts[1]
server.setARP('10.0.0.1', 'aa:bb:cc:dd:ee:11')
CLI(net)
net.stop()
print '#' * 80
print 'ATTENTION: Mininet was stopped! Perhaps accidentally?'
print 'No worries, it will restart automatically in a few seconds...'
print 'To access again the Mininet CLI, use `make mn-cli`'
print 'To detach from the CLI (without stopping), press Ctrl-D'
print 'To permanently quit Mininet, use `make stop`'
print '#' * 80
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description='Mininet topology script for 2x2 fabric with stratum_bmv2 and IPv4 hosts')
args = parser.parse_args()
setLogLevel('info')
main()
#!/usr/bin/python
# Copyright 2019-present Open Networking Foundation
#
# 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 argparse
from mininet.cli import CLI
from mininet.log import setLogLevel
from mininet.net import Mininet
from mininet.node import Host
from mininet.topo import Topo
from stratum import StratumBmv2Switch
CPU_PORT = 255
class IPv4Host(Host):
"""Host that can be configured with an IPv4 gateway (default route).
"""
def config(self, mac=None, ip=None, defaultRoute=None, lo='up', gw=None,
**_params):
super(IPv4Host, self).config(mac, ip, defaultRoute, lo, **_params)
self.cmd('ip -4 addr flush dev %s' % self.defaultIntf())
self.cmd('ip -6 addr flush dev %s' % self.defaultIntf())
self.cmd('ip -4 link set up %s' % self.defaultIntf())
self.cmd('ip -4 addr add %s dev %s' % (ip, self.defaultIntf()))
if gw:
self.cmd('ip -4 route add default via %s' % gw)
# Disable offload
for attr in ["rx", "tx", "sg"]:
cmd = "/sbin/ethtool --offload %s %s off" % (
self.defaultIntf(), attr)
self.cmd(cmd)
def updateIP():
return ip.split('/')[0]
self.defaultIntf().updateIP = updateIP
class TutorialTopo(Topo):
"""Basic Server-Client topology with IPv4 hosts"""
def __init__(self, *args, **kwargs):
Topo.__init__(self, *args, **kwargs)
# Switches
# gRPC port 50001
switch1 = self.addSwitch('switch1', cls=StratumBmv2Switch, cpuport=CPU_PORT)
# gRPC port 50002
switch2 = self.addSwitch('switch2', cls=StratumBmv2Switch, cpuport=CPU_PORT)
# gRPC port 50003
switch3 = self.addSwitch('switch3', cls=StratumBmv2Switch, cpuport=CPU_PORT)
# gRPC port 50004
switch4 = self.addSwitch('switch4', cls=StratumBmv2Switch, cpuport=CPU_PORT)
# Hosts
client = self.addHost('client', cls=IPv4Host, mac="aa:bb:cc:dd:ee:11",
ip='10.0.0.1/24', gw='10.0.0.100')
server = self.addHost('server', cls=IPv4Host, mac="aa:bb:cc:dd:ee:22",
ip='10.0.0.2/24', gw='10.0.0.100')
# Switch links
self.addLink(switch1, switch2) # Switch1:port 1, Switch2:port 1
self.addLink(switch1, switch3) # Switch1:port 2, Switch3:port 1
self.addLink(switch2, switch4) # Switch2:port 2, Switch4:port 1
self.addLink(switch3, switch4) # Switch3:port 2, Switch4:port 2
# Host links
self.addLink(client, switch1) # Switch 1: port 3
self.addLink(server, switch4) # Switch 4: port 3
def main():
net = Mininet(topo=TutorialTopo(), controller=None)
net.start()
client = net.hosts[0]
client.setARP('10.0.0.2', 'aa:bb:cc:dd:ee:22')
server = net.hosts[1]
server.setARP('10.0.0.1', 'aa:bb:cc:dd:ee:11')
CLI(net)
net.stop()
print '#' * 80
print 'ATTENTION: Mininet was stopped! Perhaps accidentally?'
print 'No worries, it will restart automatically in a few seconds...'
print 'To access again the Mininet CLI, use `make mn-cli`'
print 'To detach from the CLI (without stopping), press Ctrl-D'
print 'To permanently quit Mininet, use `make stop`'
print '#' * 80
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description='Mininet topology script for 2x2 fabric with stratum_bmv2 and IPv4 hosts')
args = parser.parse_args()
setLogLevel('info')
main()
#!/usr/bin/python
# Copyright 2019-present Open Networking Foundation
#
# 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 argparse
from mininet.cli import CLI
from mininet.log import setLogLevel
from mininet.net import Mininet
from mininet.node import Host
from mininet.topo import Topo
from stratum import StratumBmv2Switch
CPU_PORT = 255
class IPv4Host(Host):
"""Host that can be configured with an IPv4 gateway (default route).
"""
def config(self, mac=None, ip=None, defaultRoute=None, lo='up', gw=None,
**_params):
super(IPv4Host, self).config(mac, ip, defaultRoute, lo, **_params)
self.cmd('ip -4 addr flush dev %s' % self.defaultIntf())
self.cmd('ip -6 addr flush dev %s' % self.defaultIntf())
self.cmd('ip -4 link set up %s' % self.defaultIntf())
self.cmd('ip -4 addr add %s dev %s' % (ip, self.defaultIntf()))
if gw:
self.cmd('ip -4 route add default via %s' % gw)
# Disable offload
for attr in ["rx", "tx", "sg"]:
cmd = "/sbin/ethtool --offload %s %s off" % (
self.defaultIntf(), attr)
self.cmd(cmd)
def updateIP():
return ip.split('/')[0]
self.defaultIntf().updateIP = updateIP
class TutorialTopo(Topo):
"""Basic Server-Client topology with IPv4 hosts"""
def __init__(self, *args, **kwargs):
Topo.__init__(self, *args, **kwargs)
# Switches
# gRPC port 50001
switch1 = self.addSwitch('switch1', cls=StratumBmv2Switch, cpuport=CPU_PORT)
# gRPC port 50002
switch2 = self.addSwitch('switch2', cls=StratumBmv2Switch, cpuport=CPU_PORT)
# gRPC port 50003
switch3 = self.addSwitch('switch3', cls=StratumBmv2Switch, cpuport=CPU_PORT)
# gRPC port 50004
switch4 = self.addSwitch('switch4', cls=StratumBmv2Switch, cpuport=CPU_PORT)
# gRPC port 50005
switch5 = self.addSwitch('switch5', cls=StratumBmv2Switch, cpuport=CPU_PORT)
# gRPC port 50006
switch6 = self.addSwitch('switch6', cls=StratumBmv2Switch, cpuport=CPU_PORT)
# Hosts
client = self.addHost('client', cls=IPv4Host, mac="aa:bb:cc:dd:ee:11",
ip='10.0.0.1/24', gw='10.0.0.100')
server = self.addHost('server', cls=IPv4Host, mac="aa:bb:cc:dd:ee:22",
ip='10.0.0.2/24', gw='10.0.0.100')
# Switch links
self.addLink(switch1, switch2) # Switch1:port 1, Switch2:port 1
self.addLink(switch1, switch3) # Switch1:port 2, Switch3:port 1
self.addLink(switch2, switch4) # Switch2:port 2, Switch4:port 1
self.addLink(switch3, switch5) # Switch3:port 2, Switch5:port 1
self.addLink(switch4, switch6) # Switch4:port 2, Switch6:port 1
self.addLink(switch5, switch6) # Switch5:port 2, Switch6:port 2
# Host links
self.addLink(client, switch1) # Switch1: port 3
self.addLink(server, switch6) # Switch6: port 3
def main():
net = Mininet(topo=TutorialTopo(), controller=None)
net.start()
client = net.hosts[0]
client.setARP('10.0.0.2', 'aa:bb:cc:dd:ee:22')
server = net.hosts[1]
server.setARP('10.0.0.1', 'aa:bb:cc:dd:ee:11')
CLI(net)
net.stop()
print '#' * 80
print 'ATTENTION: Mininet was stopped! Perhaps accidentally?'
print 'No worries, it will restart automatically in a few seconds...'
print 'To access again the Mininet CLI, use `make mn-cli`'
print 'To detach from the CLI (without stopping), press Ctrl-D'
print 'To permanently quit Mininet, use `make stop`'
print '#' * 80
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description='Mininet topology script for 2x2 fabric with stratum_bmv2 and IPv4 hosts')
args = parser.parse_args()
setLogLevel('info')
main()
{
"header_types" : [
{
"name" : "scalars_0",
"id" : 0,
"fields" : [
["local_metadata_t.is_multicast", 1, false],
["_padding_0", 7, false]
]
},
{
"name" : "standard_metadata",
"id" : 1,
"fields" : [
["ingress_port", 9, false],
["egress_spec", 9, false],
["egress_port", 9, false],
["clone_spec", 32, false],
["instance_type", 32, false],
["drop", 1, false],
["recirculate_port", 16, false],
["packet_length", 32, false],
["enq_timestamp", 32, false],
["enq_qdepth", 19, false],
["deq_timedelta", 32, false],
["deq_qdepth", 19, false],
["ingress_global_timestamp", 48, false],
["egress_global_timestamp", 48, false],
["lf_field_list", 32, false],
["mcast_grp", 16, false],
["resubmit_flag", 32, false],
["egress_rid", 16, false],
["recirculate_flag", 32, false],
["checksum_error", 1, false],
["parser_error", 32, false],
["priority", 3, false],
["_padding", 2, false]
]
},
{
"name" : "ethernet_t",
"id" : 2,
"fields" : [
["dst_addr", 48, false],
["src_addr", 48, false],
["ether_type", 16, false]
]
}
],
"headers" : [
{
"name" : "scalars",
"id" : 0,
"header_type" : "scalars_0",
"metadata" : true,
"pi_omit" : true
},
{
"name" : "standard_metadata",
"id" : 1,
"header_type" : "standard_metadata",
"metadata" : true,
"pi_omit" : true
},
{
"name" : "ethernet",
"id" : 2,
"header_type" : "ethernet_t",
"metadata" : false,
"pi_omit" : true
}
],
"header_stacks" : [],
"header_union_types" : [],
"header_unions" : [],
"header_union_stacks" : [],
"field_lists" : [],
"errors" : [
["NoError", 1],
["PacketTooShort", 2],
["NoMatch", 3],
["StackOutOfBounds", 4],
["HeaderTooShort", 5],
["ParserTimeout", 6],
["ParserInvalidArgument", 7]
],
"enums" : [],
"parsers" : [
{
"name" : "parser",
"id" : 0,
"init_state" : "start",
"parse_states" : [
{
"name" : "start",
"id" : 0,
"parser_ops" : [
{
"parameters" : [
{
"type" : "regular",
"value" : "ethernet"
}
],
"op" : "extract"
}
],
"transitions" : [
{
"value" : "default",
"mask" : null,
"next_state" : null
}
],
"transition_key" : []
}
]
}
],
"parse_vsets" : [],
"deparsers" : [
{
"name" : "deparser",
"id" : 0,
"source_info" : {
"filename" : "p4src/main.p4",
"line" : 130,
"column" : 8,
"source_fragment" : "DeparserImpl"
},
"order" : ["ethernet"]
}
],
"meter_arrays" : [],
"counter_arrays" : [],
"register_arrays" : [],
"calculations" : [],
"learn_lists" : [],
"actions" : [
{
"name" : "IngressPipeImpl.drop",
"id" : 0,
"runtime_data" : [],
"primitives" : [
{
"op" : "mark_to_drop",
"parameters" : [
{
"type" : "header",
"value" : "standard_metadata"
}
],
"source_info" : {
"filename" : "p4src/main.p4",
"line" : 77,
"column" : 8,
"source_fragment" : "mark_to_drop(standard_metadata)"
}
}
]
},
{
"name" : "IngressPipeImpl.set_egress_port",
"id" : 1,
"runtime_data" : [
{
"name" : "port",
"bitwidth" : 9
}
],
"primitives" : [
{
"op" : "assign",
"parameters" : [
{
"type" : "field",
"value" : ["standard_metadata", "egress_spec"]
},
{
"type" : "runtime_data",
"value" : 0
}
],
"source_info" : {
"filename" : "p4src/main.p4",
"line" : 81,
"column" : 8,
"source_fragment" : "standard_metadata.egress_spec = port"
}
}
]
},
{
"name" : "IngressPipeImpl.set_multicast_group",
"id" : 2,
"runtime_data" : [
{
"name" : "gid",
"bitwidth" : 16
}
],
"primitives" : [
{
"op" : "assign",
"parameters" : [
{
"type" : "field",
"value" : ["standard_metadata", "mcast_grp"]
},
{
"type" : "runtime_data",
"value" : 0
}
],
"source_info" : {
"filename" : "p4src/main.p4",
"line" : 89,
"column" : 8,
"source_fragment" : "standard_metadata.mcast_grp = gid"
}
},
{
"op" : "assign",
"parameters" : [
{
"type" : "field",
"value" : ["scalars", "local_metadata_t.is_multicast"]
},
{
"type" : "expression",
"value" : {
"type" : "expression",
"value" : {
"op" : "b2d",
"left" : null,
"right" : {
"type" : "bool",
"value" : true
}
}
}
}
],
"source_info" : {
"filename" : "p4src/main.p4",
"line" : 90,
"column" : 8,
"source_fragment" : "local_metadata.is_multicast = true"
}
}
]
}
],
"pipelines" : [
{
"name" : "ingress",
"id" : 0,
"source_info" : {
"filename" : "p4src/main.p4",
"line" : 71,
"column" : 8,
"source_fragment" : "IngressPipeImpl"
},
"init_table" : "IngressPipeImpl.l2_exact_table",
"tables" : [
{
"name" : "IngressPipeImpl.l2_exact_table",
"id" : 0,
"source_info" : {
"filename" : "p4src/main.p4",
"line" : 95,
"column" : 10,
"source_fragment" : "l2_exact_table"
},
"key" : [
{
"match_type" : "exact",
"name" : "standard_metadata.ingress_port",
"target" : ["standard_metadata", "ingress_port"],
"mask" : null
}
],
"match_type" : "exact",
"type" : "simple",
"max_size" : 1024,
"with_counters" : false,
"support_timeout" : false,
"direct_meters" : null,
"action_ids" : [1, 2, 0],
"actions" : ["IngressPipeImpl.set_egress_port", "IngressPipeImpl.set_multicast_group", "IngressPipeImpl.drop"],
"base_default_next" : null,
"next_tables" : {
"IngressPipeImpl.set_egress_port" : null,
"IngressPipeImpl.set_multicast_group" : null,
"IngressPipeImpl.drop" : null
},
"default_entry" : {
"action_id" : 0,
"action_const" : true,
"action_data" : [],
"action_entry_const" : true
}
}
],
"action_profiles" : [],
"conditionals" : []
},
{
"name" : "egress",
"id" : 1,
"source_info" : {
"filename" : "p4src/main.p4",
"line" : 116,
"column" : 8,
"source_fragment" : "EgressPipeImpl"
},
"init_table" : null,
"tables" : [],
"action_profiles" : [],
"conditionals" : []
}
],
"checksums" : [],
"force_arith" : [],
"extern_instances" : [],
"field_aliases" : [
[
"queueing_metadata.enq_timestamp",
["standard_metadata", "enq_timestamp"]
],
[
"queueing_metadata.enq_qdepth",
["standard_metadata", "enq_qdepth"]
],
[
"queueing_metadata.deq_timedelta",
["standard_metadata", "deq_timedelta"]
],
[
"queueing_metadata.deq_qdepth",
["standard_metadata", "deq_qdepth"]
],
[
"intrinsic_metadata.ingress_global_timestamp",
["standard_metadata", "ingress_global_timestamp"]
],
[
"intrinsic_metadata.egress_global_timestamp",
["standard_metadata", "egress_global_timestamp"]
],
[
"intrinsic_metadata.lf_field_list",
["standard_metadata", "lf_field_list"]
],
[
"intrinsic_metadata.mcast_grp",
["standard_metadata", "mcast_grp"]
],
[
"intrinsic_metadata.resubmit_flag",
["standard_metadata", "resubmit_flag"]
],
[
"intrinsic_metadata.egress_rid",
["standard_metadata", "egress_rid"]
],
[
"intrinsic_metadata.recirculate_flag",
["standard_metadata", "recirculate_flag"]
],
[
"intrinsic_metadata.priority",
["standard_metadata", "priority"]
]
],
"program" : "p4src/main.p4",
"__meta__" : {
"version" : [2, 18],
"compiler" : "https://github.com/p4lang/p4c"
}
}
\ No newline at end of file
/*
* Copyright 2019-present Open Networking Foundation
*
* 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.
*/
#include <core.p4>
#include <v1model.p4>
typedef bit<9> port_num_t;
typedef bit<48> mac_addr_t;
typedef bit<16> mcast_group_id_t;
//------------------------------------------------------------------------------
// HEADER DEFINITIONS
//------------------------------------------------------------------------------
header ethernet_t {
mac_addr_t dst_addr;
mac_addr_t src_addr;
bit<16> ether_type;
}
struct parsed_headers_t {
ethernet_t ethernet;
}
struct local_metadata_t {
bool is_multicast;
}
//------------------------------------------------------------------------------
// INGRESS PIPELINE
//------------------------------------------------------------------------------
parser ParserImpl (packet_in packet,
out parsed_headers_t hdr,
inout local_metadata_t local_metadata,
inout standard_metadata_t standard_metadata)
{
state start {
transition parse_ethernet;
}
state parse_ethernet {
packet.extract(hdr.ethernet);
transition accept;
}
}
control VerifyChecksumImpl(inout parsed_headers_t hdr,
inout local_metadata_t meta)
{
apply { /* EMPTY */ }
}
control IngressPipeImpl (inout parsed_headers_t hdr,
inout local_metadata_t local_metadata,
inout standard_metadata_t standard_metadata) {
// Drop action shared by many tables.
action drop() {
mark_to_drop(standard_metadata);
}
action set_egress_port(port_num_t port) {
standard_metadata.egress_spec = port;
}
action set_multicast_group(mcast_group_id_t gid) {
// gid will be used by the Packet Replication Engine (PRE) in the
// Traffic Manager--located right after the ingress pipeline, to
// replicate a packet to multiple egress ports, specified by the control
// plane by means of P4Runtime MulticastGroupEntry messages.
standard_metadata.mcast_grp = gid;
local_metadata.is_multicast = true;
}
// --- l2_exact_table ------------------
table l2_exact_table {
key = {
standard_metadata.ingress_port: exact;
}
actions = {
set_egress_port;
set_multicast_group;
@defaultonly drop;
}
const default_action = drop;
}
apply {
l2_exact_table.apply();
}
}
//------------------------------------------------------------------------------
// EGRESS PIPELINE
//------------------------------------------------------------------------------
control EgressPipeImpl (inout parsed_headers_t hdr,
inout local_metadata_t local_metadata,
inout standard_metadata_t standard_metadata) {
apply { /* EMPTY */ }
}
control ComputeChecksumImpl(inout parsed_headers_t hdr,
inout local_metadata_t local_metadata)
{
apply { /* EMPTY */ }
}
control DeparserImpl(packet_out packet, in parsed_headers_t hdr) {
apply {
packet.emit(hdr.ethernet);
}
}
V1Switch(
ParserImpl(),
VerifyChecksumImpl(),
IngressPipeImpl(),
EgressPipeImpl(),
ComputeChecksumImpl(),
DeparserImpl()
) main;
pkg_info {
arch: "v1model"
}
tables {
preamble {
id: 33605373
name: "IngressPipeImpl.l2_exact_table"
alias: "l2_exact_table"
}
match_fields {
id: 1
name: "standard_metadata.ingress_port"
bitwidth: 9
match_type: EXACT
}
action_refs {
id: 16812802
}
action_refs {
id: 16841371
}
action_refs {
id: 16796182
annotations: "@defaultonly"
scope: DEFAULT_ONLY
}
const_default_action_id: 16796182
size: 1024
}
actions {
preamble {
id: 16796182
name: "IngressPipeImpl.drop"
alias: "drop"
}
}
actions {
preamble {
id: 16812802
name: "IngressPipeImpl.set_egress_port"
alias: "set_egress_port"
}
params {
id: 1
name: "port"
bitwidth: 9
}
}
actions {
preamble {
id: 16841371
name: "IngressPipeImpl.set_multicast_group"
alias: "set_multicast_group"
}
params {
id: 1
name: "gid"
bitwidth: 16
}
}
type_info {
}
#!/bin/bash
# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# 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.
# make sure to source the following scripts:
# - my_deploy.sh
# - tfs_runtime_env_vars.sh
source tfs_runtime_env_vars.sh
python -m pytest --verbose src/tests/p4/tests/test_functional_bootstrap.py
#!/bin/bash
# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# 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
python -m pytest --verbose src/tests/p4/tests/test_functional_create_service.py
#!/bin/bash
# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# 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
python -m pytest --verbose src/tests/p4/tests/test_functional_delete_service.py
#!/bin/bash
# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# 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
python -m pytest --verbose src/tests/p4/tests/test_functional_cleanup.py
#! /bin/bash
export POD_NAME=$(kubectl get pods -n=tfs | grep device | awk '{print $1}')
kubectl exec ${POD_NAME} -n=tfs -- mkdir /root/p4
kubectl cp src/tests/p4/p4/p4info.txt tfs/${POD_NAME}:/root/p4
kubectl cp src/tests/p4/p4/bmv2.json tfs/${POD_NAME}:/root/p4