Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Thinkit] Adding packet forwarding tests header file. #761

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions tests/integration/system/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,47 @@ cc_library(
],
alwayslink = True,
)

cc_library(
name = "packet_forwarding_tests",
testonly = True,
srcs = ["packet_forwarding_tests.cc"],
hdrs = ["packet_forwarding_tests.h"],
deps = [
"//gutil:collections",
"//gutil:status_matchers",
"//gutil:testing",
"//lib/basic_traffic",
"//lib/basic_traffic:basic_p4rt_util",
"//lib/gnmi:gnmi_helper",
"//lib/p4rt:p4rt_programming_context",
"//lib/utils:generic_testbed_utils",
"//p4_pdpi:ir",
"//p4_pdpi:ir_cc_proto",
"//p4_pdpi:p4_runtime_session",
"//p4_pdpi/packetlib",
"//p4_pdpi/packetlib:packetlib_cc_proto",
"//sai_p4/instantiations/google:sai_pd_cc_proto",
"//tests/lib:switch_test_setup_helpers",
"//thinkit:control_device",
"//thinkit:generic_testbed",
"//thinkit:generic_testbed_fixture",
"//thinkit:switch",
"//thinkit/proto:generic_testbed_cc_proto",
"@com_github_gnmi//proto/gnmi:gnmi_cc_grpc_proto",
"@com_github_google_glog//:glog",
"@com_github_p4lang_p4runtime//:p4info_cc_proto",
"@com_github_p4lang_p4runtime//:p4runtime_cc_proto",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/flags:flag",
"@com_google_absl//absl/numeric:int128",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/time",
"@com_google_absl//absl/types:span",
"@com_google_googletest//:gtest",
],
alwayslink = True,
)
342 changes: 342 additions & 0 deletions tests/integration/system/packet_forwarding_tests.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
// Copyright (c) 2024, Google Inc.
//
// 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 "tests/integration/system/packet_forwarding_tests.h"

#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "absl/container/flat_hash_map.h"
#include "absl/flags/flag.h"
#include "absl/numeric/int128.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/match.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "absl/types/span.h"
#include "glog/logging.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "gutil/collections.h"
#include "gutil/status.h"
#include "gutil/status_matchers.h"
#include "gutil/testing.h"
#include "lib/basic_traffic/basic_p4rt_util.h"
#include "lib/basic_traffic/basic_traffic.h"
#include "lib/gnmi/gnmi_helper.h"
#include "lib/p4rt/p4rt_programming_context.h"
#include "lib/utils/generic_testbed_utils.h"
#include "p4/config/v1/p4info.pb.h"
#include "p4/v1/p4runtime.pb.h"
#include "p4_pdpi/ir.h"
#include "p4_pdpi/ir.pb.h"
#include "p4_pdpi/p4_runtime_session.h"
#include "p4_pdpi/packetlib/packetlib.h"
#include "p4_pdpi/packetlib/packetlib.pb.h"
#include "proto/gnmi/gnmi.grpc.pb.h"
#include "sai_p4/instantiations/google/sai_pd.pb.h"
#include "tests/lib/switch_test_setup_helpers.h"
#include "thinkit/control_device.h"
#include "thinkit/generic_testbed.h"
#include "thinkit/proto/generic_testbed.pb.h"
#include "thinkit/switch.h"

ABSL_FLAG(bool, push_p4_info, true, "Push P4 info to SUT.");

namespace pins_test {
namespace {

constexpr int kMtu[] = {1500, 5000, 9000};

// Test packet proto message sent from control switch to sut.
constexpr absl::string_view kTestPacket = R"pb(
headers {
ethernet_header {
ethernet_destination: "02:03:04:05:06:07"
ethernet_source: "00:01:02:03:04:05"
ethertype: "0x0800"
}
}
headers {
ipv4_header {
version: "0x4"
ihl: "0x5"
dscp: "0x03"
ecn: "0x0"
identification: "0x0000"
flags: "0x0"
fragment_offset: "0x0000"
ttl: "0x20"
protocol: "0x11"
ipv4_source: "1.2.3.4"
ipv4_destination: "$0"
}
}
headers { udp_header { source_port: "0x0000" destination_port: "0x0000" } }
payload: "Basic L3 test packet")pb";

// Pushes the P4 Info to SUT if the flag push_p4_info is set to true and returns
// the P4 Runtime Session
absl::StatusOr<std::unique_ptr<pdpi::P4RuntimeSession>> P4InfoPush(
const p4::config::v1::P4Info& p4_info, thinkit::GenericTestbed& testbed) {
std::optional<p4::config::v1::P4Info> p4info = std::nullopt;
if (absl::GetFlag(FLAGS_push_p4_info)) {
p4info = p4_info;
}
return ConfigureSwitchAndReturnP4RuntimeSession(
testbed.Sut(), /*gnmi_config=*/std::nullopt, p4info);
}

TEST_P(PacketForwardingTestFixture, PacketForwardingTest) {
thinkit::TestRequirements requirements =
gutil::ParseProtoOrDie<thinkit::TestRequirements>(
R"pb(interface_requirements {
count: 2
interface_mode: CONTROL_INTERFACE
})pb");

ASSERT_OK_AND_ASSIGN(auto testbed, GetTestbedWithRequirements(requirements));
std::vector<InterfaceLink> control_links =
FromTestbed(GetAllControlLinks, *testbed);

ASSERT_OK_AND_ASSIGN(auto stub, testbed->Sut().CreateGnmiStub());
ASSERT_OK_AND_ASSIGN(auto port_id_by_interface,
GetAllInterfaceNameToPortId(*stub));

// Set the `source_link` to the first SUT control link.
const InterfaceLink& source_link = control_links[0];
ASSERT_OK_AND_ASSIGN(
std::string source_port_id_value,
gutil::FindOrStatus(port_id_by_interface, source_link.sut_interface));

// Set the `destination_link` to the second SUT control link.
const InterfaceLink& destination_link = control_links[1];
ASSERT_OK_AND_ASSIGN(std::string destination_port_id_value,
gutil::FindOrStatus(port_id_by_interface,
destination_link.sut_interface));

LOG(INFO) << "Source port: " << source_link.sut_interface
<< " port id: " << source_port_id_value;
LOG(INFO) << "Destination port: " << destination_link.sut_interface
<< " port id: " << destination_port_id_value;

ASSERT_OK_AND_ASSIGN(std::unique_ptr<pdpi::P4RuntimeSession> p4_session,
P4InfoPush(GetParam().p4_info, *testbed));
// Set up a route between the source and destination interfaces.
ASSERT_OK_AND_ASSIGN(auto port_id_from_sut_interface,
GetAllInterfaceNameToPortId(*stub));
ASSERT_OK_AND_ASSIGN(const pdpi::IrP4Info ir_p4info,
pdpi::CreateIrP4Info(GetParam().p4_info));
P4rtProgrammingContext p4rt_context(p4_session.get(),
pdpi::SetMetadataAndSendPiWriteRequest);
ASSERT_OK(basic_traffic::ProgramRoutes(
p4rt_context.GetWriteRequestFunction(), ir_p4info,
port_id_from_sut_interface,
{{.ingress_interface = source_link.sut_interface,
.egress_interface = destination_link.sut_interface}}));

int destination_port_id;
ASSERT_TRUE(
absl::SimpleAtoi(destination_port_id_value, &destination_port_id));

// Make test packet based on destination port ID.
const auto test_packet =
gutil::ParseProtoOrDie<packetlib::Packet>(absl::Substitute(
kTestPacket, basic_traffic::PortIdToIP(destination_port_id)));
ASSERT_OK_AND_ASSIGN(std::string test_packet_data,
packetlib::SerializePacket(test_packet));

absl::Mutex mutex;
std::vector<std::string> received_packets;
static constexpr int kPacketsToSend = 10;
{
ASSERT_OK_AND_ASSIGN(auto finalizer,
testbed.get()->ControlDevice().CollectPackets());

LOG(INFO) << "Sending Packet to " << source_link.peer_interface;
LOG(INFO) << "Test packet data: " << test_packet.DebugString();

for (int i = 0; i < kPacketsToSend; i++) {
// Send packet to SUT.
ASSERT_OK(testbed->ControlDevice().SendPacket(source_link.peer_interface,
test_packet_data))
<< "failed to inject the packet.";
LOG(INFO) << "SendPacket completed";
}
ASSERT_OK(finalizer->HandlePacketsFor(
absl::Seconds(30),
[&](absl::string_view interface, absl::string_view packet) {
if (interface != destination_link.peer_interface) {
return;
}
packetlib::Packet parsed_packet = packetlib::ParsePacket(packet);
if (!absl::StrContains(parsed_packet.payload(),
"Basic L3 test packet")) {
return;
}
absl::MutexLock lock(&mutex);
received_packets.push_back(std::string(packet));
}));
}
EXPECT_EQ(received_packets.size(), kPacketsToSend);
}

using InterfaceCounters = absl::flat_hash_map<std::string, Counters>;

TEST_P(PacketForwardingTestFixture, AllPortsPacketForwardingTest) {
thinkit::TestRequirements requirements =
gutil::ParseProtoOrDie<thinkit::TestRequirements>(
R"pb(interface_requirements {
count: 2
interface_mode: CONTROL_INTERFACE
})pb");

ASSERT_OK_AND_ASSIGN(auto testbed, GetTestbedWithRequirements(requirements));
ASSERT_OK_AND_ASSIGN(auto gnmi_stub, testbed->Sut().CreateGnmiStub());

std::vector<std::string> sut_interfaces =
GetSutInterfaces(FromTestbed(GetAllControlLinks, *testbed));
ASSERT_OK_AND_ASSIGN(std::unique_ptr<pdpi::P4RuntimeSession> p4_session,
P4InfoPush(GetParam().p4_info, *testbed));
ASSERT_OK_AND_ASSIGN(const pdpi::IrP4Info ir_p4info,
pdpi::CreateIrP4Info(GetParam().p4_info));

// Collect counters before the test.
ASSERT_OK_AND_ASSIGN(InterfaceCounters pretraffic_counters,
GetAllInterfaceCounters(*gnmi_stub));

const auto test_packet =
gutil::ParseProtoOrDie<packetlib::Packet>(kTestPacket);
ASSERT_OK_AND_ASSIGN(
auto statistics,
basic_traffic::SendTraffic(*testbed, p4_session.get(), ir_p4info,
basic_traffic::AllToAll(sut_interfaces),
{test_packet}, absl::Minutes(5)));

// Collate traffic statistics to dump to artifacts.
std::string packet_statistics_csv =
"ingress_interface,egress_interface,packets_sent,packets_received\n";
for (const basic_traffic::TrafficStatistic& statistic : statistics) {
absl::StrAppend(&packet_statistics_csv,
absl::StrJoin({statistic.interfaces.ingress_interface,
statistic.interfaces.egress_interface,
absl::StrCat(statistic.packets_sent),
absl::StrCat(statistic.packets_received)},
","),
"\n");
EXPECT_EQ(statistic.packets_sent, statistic.packets_received);
EXPECT_EQ(statistic.packets_routed_incorrectly, 0);
}
EXPECT_OK(testbed->Environment().StoreTestArtifact("traffic_statistics.csv",
packet_statistics_csv));

// Calculate counter differences and dump to artifacts.
std::string sut_counters_csv = "interface,in_packets,out_packets\n";
ASSERT_OK_AND_ASSIGN(InterfaceCounters posttraffic_counters,
GetAllInterfaceCounters(*gnmi_stub));
for (const std::string& interface : sut_interfaces) {
const Counters& pre = pretraffic_counters[interface];
const Counters& post = posttraffic_counters[interface];
absl::StrAppend(
&sut_counters_csv,
absl::StrJoin({interface, absl::StrCat(post.in_pkts - pre.in_pkts),
absl::StrCat(post.out_pkts - pre.out_pkts)},
","),
"\n");
}
EXPECT_OK(testbed->Environment().StoreTestArtifact("sut_counters.csv",
sut_counters_csv));
}

TEST_P(PacketForwardingTestFixture, MtuPacketForwardingTest) {
thinkit::TestRequirements requirements =
gutil::ParseProtoOrDie<thinkit::TestRequirements>(
R"pb(interface_requirements {
count: 2
interface_mode: CONTROL_INTERFACE
})pb");

ASSERT_OK_AND_ASSIGN(auto testbed, GetTestbedWithRequirements(requirements));
ASSERT_OK_AND_ASSIGN(auto gnmi_stub, testbed->Sut().CreateGnmiStub());
std::vector<std::string> sut_interfaces =
GetSutInterfaces(FromTestbed(GetAllControlLinks, *testbed));

ASSERT_OK_AND_ASSIGN(std::unique_ptr<pdpi::P4RuntimeSession> p4_session,
P4InfoPush(GetParam().p4_info, *testbed));
ASSERT_OK_AND_ASSIGN(const pdpi::IrP4Info ir_p4info,
pdpi::CreateIrP4Info(GetParam().p4_info));

for (int mtu : kMtu) {
// Collect counters before the test.
ASSERT_OK_AND_ASSIGN(InterfaceCounters pretraffic_counters,
GetAllInterfaceCounters(*gnmi_stub));

LOG(INFO) << "MTU: " << mtu;
auto test_packet = gutil::ParseProtoOrDie<packetlib::Packet>(kTestPacket);
ASSERT_OK(PadPacket(mtu, test_packet));
LOG(INFO) << "Packet: " << test_packet.DebugString();
ASSERT_OK_AND_ASSIGN(
auto statistics,
basic_traffic::SendTraffic(*testbed, p4_session.get(), ir_p4info,
basic_traffic::AllToAll(sut_interfaces),
{test_packet}, absl::Minutes(5)));

// Collate traffic statistics to dump to artifacts.
std::string packet_statistics_csv =
"ingress_interface,egress_interface,packets_sent,packets_received\n";
for (const basic_traffic::TrafficStatistic& statistic : statistics) {
absl::StrAppend(&packet_statistics_csv,
absl::StrJoin({statistic.interfaces.ingress_interface,
statistic.interfaces.egress_interface,
absl::StrCat(statistic.packets_sent),
absl::StrCat(statistic.packets_received)},
","),
"\n");
EXPECT_EQ(statistic.packets_sent, statistic.packets_received);
EXPECT_EQ(statistic.packets_routed_incorrectly, 0);
}
EXPECT_OK(testbed->Environment().StoreTestArtifact(
absl::StrCat("traffic_statistics_mtu", mtu, ".csv"),
packet_statistics_csv));

// Calculate counter differences and dump to artifacts.
std::string sut_counters_csv = "interface,in_packets,out_packets\n";
ASSERT_OK_AND_ASSIGN(InterfaceCounters posttraffic_counters,
GetAllInterfaceCounters(*gnmi_stub));
for (const std::string& interface : sut_interfaces) {
const Counters& pre = pretraffic_counters[interface];
const Counters& post = posttraffic_counters[interface];
absl::StrAppend(
&sut_counters_csv,
absl::StrJoin({interface, absl::StrCat(post.in_pkts - pre.in_pkts),
absl::StrCat(post.out_pkts - pre.out_pkts)},
","),
"\n");
}
EXPECT_OK(testbed->Environment().StoreTestArtifact(
absl::StrCat("sut_counters_mtu", mtu, ".csv"), sut_counters_csv));
}
}

} // namespace
} // namespace pins_test
Loading
Loading