diff --git a/tests/qos/BUILD.bazel b/tests/qos/BUILD.bazel index 24ac5725..c150554a 100644 --- a/tests/qos/BUILD.bazel +++ b/tests/qos/BUILD.bazel @@ -31,6 +31,8 @@ cc_library( ], deps = [ ":gnmi_parsers", + ":packet_in_receiver", + ":qos_test_util", "//gutil:collections", "//gutil:overload", "//gutil:proto", @@ -55,6 +57,7 @@ cc_library( "//sai_p4/instantiations/google:sai_p4info_cc", "//sai_p4/instantiations/google:sai_pd_cc_proto", "//tests/forwarding:util", + "//tests/lib:switch_test_setup_helpers", "//thinkit:control_device", "//thinkit:generic_testbed", "//thinkit:generic_testbed_fixture", @@ -136,3 +139,27 @@ cmd_diff_test( tools = [":gnmi_parsers_test_runner"], ) +cc_library( + name = "qos_test_util", + srcs = ["qos_test_util.cc"], + hdrs = ["qos_test_util.h"], + deps = [ + "//lib/gnmi:gnmi_helper", + "//thinkit:generic_testbed", + "//thinkit/proto:generic_testbed_cc_proto", + "@com_github_gnmi//proto/gnmi:gnmi_cc_proto", + "@com_github_gnmi//proto/gnmi:gnmi_cc_grpc_proto", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + ], +) + +cc_library( + name = "packet_in_receiver", + hdrs = ["packet_in_receiver.h"], + deps = [ + "//p4_pdpi:p4_runtime_session", + ], +) diff --git a/tests/qos/cpu_qos_test.cc b/tests/qos/cpu_qos_test.cc index b1b42d3b..50425f5f 100644 --- a/tests/qos/cpu_qos_test.cc +++ b/tests/qos/cpu_qos_test.cc @@ -70,7 +70,9 @@ #include "sai_p4/instantiations/google/sai_p4info.h" #include "sai_p4/instantiations/google/sai_pd.pb.h" #include "tests/forwarding/util.h" +#include "tests/lib/switch_test_setup_helpers.h" #include "tests/qos/gnmi_parsers.h" +#include "tests/qos/qos_test_util.h" #include "thinkit/control_device.h" #include "thinkit/generic_testbed.h" #include "thinkit/mirror_testbed.h" @@ -237,7 +239,7 @@ absl::StatusOr SetUpPuntToCPUWithRateLimit( src_ip { value: "$1" mask: "255.255.255.255" } dst_ip { value: "$2" mask: "255.255.255.255" } } - action { acl_trap { qos_queue: "$3" } } + action { acl_experimental_trap { qos_queue: "$3" } } priority: 1 meter_config { bytes_per_second: $4 burst_bytes: $5 } } @@ -252,100 +254,6 @@ absl::StatusOr SetUpPuntToCPUWithRateLimit( return pi_acl_entry; } -// These are the counters we track in these tests. -struct QueueCounters { - int64_t num_packets_transmitted = 0; - int64_t num_packet_dropped = 0; -}; - -std::ostream &operator<<(std::ostream &os, const QueueCounters &counters) { - return os << absl::StreamFormat( - "QueueCounters{" - ".num_packets_transmitted = %d, " - ".num_packets_dropped = %d" - "}", - counters.num_packets_transmitted, counters.num_packet_dropped); -} - -// TODO: Move this to a helper library. -absl::StatusOr GetGnmiQueueCounters( - absl::string_view port, absl::string_view queue, - gnmi::gNMI::StubInterface &gnmi_stub) { - QueueCounters counters; - const std::string openconfig_transmit_count_state_path = absl::Substitute( - "qos/interfaces/interface[interface-id=$0]" - "/output/queues/queue[name=$1]/state/transmit-pkts", - port, queue); - - ASSIGN_OR_RETURN( - std::string transmit_counter_response, - GetGnmiStatePathInfo(&gnmi_stub, openconfig_transmit_count_state_path, - "openconfig-qos:transmit-pkts")); - - if (!absl::SimpleAtoi(StripQuotes(transmit_counter_response), - &counters.num_packets_transmitted)) { - return absl::InternalError(absl::StrCat("Unable to parse counter from ", - transmit_counter_response)); - } - - const std::string openconfig_drop_count_state_path = absl::Substitute( - "qos/interfaces/interface[interface-id=$0]" - "/output/queues/queue[name=$1]/state/dropped-pkts", - port, queue); - - ASSIGN_OR_RETURN( - std::string drop_counter_response, - GetGnmiStatePathInfo(&gnmi_stub, openconfig_drop_count_state_path, - "openconfig-qos:dropped-pkts")); - - if (!absl::SimpleAtoi(StripQuotes(drop_counter_response), - &counters.num_packet_dropped)) { - return absl::InternalError( - absl::StrCat("Unable to parse counter from ", drop_counter_response)); - } - - return counters; -} - -// Returns the total number of packets enqueued for the queue with the given -// `QueueCounters`. -int64_t CumulativeNumPacketsEnqueued(const QueueCounters &counters) { - return counters.num_packet_dropped + counters.num_packets_transmitted; -} - -absl::Status SetPortSpeed(const std::string &port_speed, - const std::string &iface, - gnmi::gNMI::StubInterface &gnmi_stub) { - std::string ops_config_path = absl::StrCat( - "interfaces/interface[name=", iface, "]/ethernet/config/port-speed"); - std::string ops_val = - absl::StrCat("{\"openconfig-if-ethernet:port-speed\":", port_speed, "}"); - RETURN_IF_ERROR(pins_test::SetGnmiConfigPath(&gnmi_stub, ops_config_path, - GnmiSetType::kUpdate, ops_val)); - return absl::OkStatus(); -} - -absl::Status SetPortMtu(int port_mtu, const std::string &interface_name, - gnmi::gNMI::StubInterface &gnmi_stub) { - std::string config_path = absl::StrCat( - "interfaces/interface[name=", interface_name, "]/config/mtu"); - std::string value = absl::StrCat("{\"config:mtu\":", port_mtu, "}"); - RETURN_IF_ERROR(pins_test::SetGnmiConfigPath(&gnmi_stub, config_path, - GnmiSetType::kUpdate, value)); - return absl::OkStatus(); -} - -absl::StatusOr CheckLinkUp(const std::string &iface, - gnmi::gNMI::StubInterface &gnmi_stub) { - std::string oper_status_state_path = - absl::StrCat("interfaces/interface[name=", iface, "]/state/oper-status"); - std::string parse_str = "openconfig-interfaces:oper-status"; - ASSIGN_OR_RETURN( - std::string ops_response, - GetGnmiStatePathInfo(&gnmi_stub, oper_status_state_path, parse_str)); - return ops_response == "\"UP\""; -} - absl::StatusOr MakeIpv4PacketWithDscp( const netaddr::MacAddress &dst_mac, const netaddr::Ipv4Address &dst_ip, int dscp) { @@ -410,27 +318,6 @@ absl::StatusOr MakeIpv6PacketWithDscp( return packet; } -absl::StatusOr> -ParseIpv4DscpToQueueMapping(absl::string_view gnmi_config) { - // TODO: Actually parse config -- hard-coded for now. - absl::flat_hash_map queue_by_dscp; - for (int dscp = 0; dscp < 64; ++dscp) queue_by_dscp[dscp] = "BE1"; - for (int dscp = 8; dscp <= 11; ++dscp) queue_by_dscp[dscp] = "AF1"; - queue_by_dscp[13] = "LLQ1"; - for (int dscp = 16; dscp <= 19; ++dscp) queue_by_dscp[dscp] = "AF2"; - queue_by_dscp[21] = "LLQ2"; - for (int dscp = 24; dscp <= 27; ++dscp) queue_by_dscp[dscp] = "AF3"; - for (int dscp = 32; dscp <= 35; ++dscp) queue_by_dscp[dscp] = "AF4"; - for (int dscp = 48; dscp <= 59; ++dscp) queue_by_dscp[dscp] = "NC1"; - return queue_by_dscp; -} - -absl::StatusOr> -ParseIpv6DscpToQueueMapping(absl::string_view gnmi_config) { - // TODO: Actually parse config -- hard-coded for now. - return ParseIpv4DscpToQueueMapping(gnmi_config); -} - // Represents a link connecting the switch under test (SUT) to a control device. struct SutToControlLink { std::string sut_port_gnmi_name; @@ -487,28 +374,17 @@ TEST_P(CpuQosTestWithoutIxia, PerEntryAclCounterIncrementsWhenEntryIsHit) { ASSERT_OK_AND_ASSIGN(const pdpi::IrP4Info ir_p4info, pdpi::CreateIrP4Info(p4info)); - // Set up P4Runtime. + // Configure mirror testbed. + EXPECT_OK(Testbed().Environment().StoreTestArtifact("gnmi_config.json", + GetParam().gnmi_config)); EXPECT_OK( Testbed().Environment().StoreTestArtifact("p4info.textproto", p4info)); + std::unique_ptr sut_p4rt_session, + control_p4rt_session; ASSERT_OK_AND_ASSIGN( - std::unique_ptr sut_p4rt_session, - pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables(sut, p4info)); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr control_p4rt_session, - pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables(control_device, - p4info)); - - // Set up gNMI. - EXPECT_OK(Testbed().Environment().StoreTestArtifact("gnmi_config.json", - GetParam().gnmi_config)); - ASSERT_OK(pins_test::PushGnmiConfig(sut, GetParam().gnmi_config)); - ASSERT_OK(pins_test::PushGnmiConfig(control_device, GetParam().gnmi_config)); - ASSERT_OK_AND_ASSIGN(auto gnmi_stub, sut.CreateGnmiStub()); - - // TODO: Poll for config to be applied, links to come up instead. - LOG(INFO) << "Sleeping 10 seconds to wait for config to be applied/links to " - "come up."; - absl::SleepFor(absl::Seconds(10)); + std::tie(sut_p4rt_session, control_p4rt_session), + pins_test::ConfigureSwitchPairAndReturnP4RuntimeSessionPair( + sut, control_device, GetParam().gnmi_config, p4info)); // Pick a link to be used for packet injection. ASSERT_OK_AND_ASSIGN(SutToControlLink link_used_for_test_packets, @@ -536,7 +412,7 @@ TEST_P(CpuQosTestWithoutIxia, PerEntryAclCounterIncrementsWhenEntryIsHit) { priority: 1 match { is_ipv6 { value: "0x1" } - ttl { value: "0xff" mask: "0xff" } + ip_protocol { value: "0xfd" mask: "0xff" } } action { acl_drop {} } } @@ -572,11 +448,13 @@ TEST_P(CpuQosTestWithoutIxia, PerEntryAclCounterIncrementsWhenEntryIsHit) { ipv6_destination: "2001:db8:0:12::2" } } - payload: "IPv6 packet with TTL 0xff (255)." + payload: "IPv6 packet with next header 0xfd (253)." )pb")); // The ACL entry should match the test packet. - ASSERT_EQ(test_packet.headers().at(1).ipv6_header().hop_limit(), - pd_acl_entry.acl_ingress_table_entry().match().ttl().value()); + ASSERT_EQ( + test_packet.headers().at(1).ipv6_header().next_header(), + pd_acl_entry.acl_ingress_table_entry().match().ip_protocol().value()); + ASSERT_OK(packetlib::PadPacketToMinimumSize(test_packet)); ASSERT_OK(packetlib::UpdateAllComputedFields(test_packet)); ASSERT_OK_AND_ASSIGN(const std::string raw_packet, @@ -782,28 +660,17 @@ TEST_P(CpuQosTestWithoutIxia, ASSERT_OK_AND_ASSIGN(const pdpi::IrP4Info ir_p4info, pdpi::CreateIrP4Info(p4info)); - // Set up P4Runtime. + // Configure mirror testbed. + EXPECT_OK(Testbed().Environment().StoreTestArtifact("gnmi_config.json", + GetParam().gnmi_config)); EXPECT_OK( Testbed().Environment().StoreTestArtifact("p4info.textproto", p4info)); + std::unique_ptr sut_p4rt_session, + control_p4rt_session; ASSERT_OK_AND_ASSIGN( - std::unique_ptr sut_p4rt_session, - pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables(sut, p4info)); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr control_p4rt_session, - pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables(control_device, - p4info)); - - // Set up gNMI. - EXPECT_OK(Testbed().Environment().StoreTestArtifact("gnmi_config.json", - GetParam().gnmi_config)); - ASSERT_OK(pins_test::PushGnmiConfig(sut, GetParam().gnmi_config)); - ASSERT_OK(pins_test::PushGnmiConfig(control_device, GetParam().gnmi_config)); - ASSERT_OK_AND_ASSIGN(auto gnmi_stub, sut.CreateGnmiStub()); - - // TODO: Poll for config to be applied, links to come up instead. - LOG(INFO) << "Sleeping " << kTimeToWaitForGnmiConfigToApply - << " to wait for config to be applied/links to come up."; - absl::SleepFor(kTimeToWaitForGnmiConfigToApply); + std::tie(sut_p4rt_session, control_p4rt_session), + pins_test::ConfigureSwitchPairAndReturnP4RuntimeSessionPair( + sut, control_device, GetParam().gnmi_config, p4info)); // Pick a link to be used for packet injection. ASSERT_OK_AND_ASSIGN(SutToControlLink link_used_for_test_packets, @@ -832,6 +699,7 @@ TEST_P(CpuQosTestWithoutIxia, // Read CPU queue state prior to injecting test packets. The state should // remain unchanged when we inject test packets. + ASSERT_OK_AND_ASSIGN(auto gnmi_stub, sut.CreateGnmiStub()); ASSERT_OK_AND_ASSIGN(openconfig::QueuesByName initial_cpu_queue_state, GetCpuQueueStateViaGnmi(*gnmi_stub)); @@ -894,27 +762,17 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { ASSERT_OK_AND_ASSIGN(const pdpi::IrP4Info ir_p4info, pdpi::CreateIrP4Info(p4info)); - // Set up P4Runtime. + // Configure mirror testbed. + EXPECT_OK(Testbed().Environment().StoreTestArtifact("gnmi_config.json", + GetParam().gnmi_config)); EXPECT_OK( Testbed().Environment().StoreTestArtifact("p4info.textproto", p4info)); + std::unique_ptr sut_p4rt_session, + control_p4rt_session; ASSERT_OK_AND_ASSIGN( - std::unique_ptr p4rt_session, - pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables(sut, p4info)); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr control_p4rt_session, - pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables(control_device, - p4info)); - - // Set up gNMI. - EXPECT_OK(Testbed().Environment().StoreTestArtifact("gnmi_config.json", - GetParam().gnmi_config)); - ASSERT_OK(pins_test::PushGnmiConfig(sut, GetParam().gnmi_config)); - ASSERT_OK(pins_test::PushGnmiConfig(control_device, GetParam().gnmi_config)); - ASSERT_OK_AND_ASSIGN(auto gnmi_stub, sut.CreateGnmiStub()); - // TODO: Poll for config to be applied, links to come up instead. - LOG(INFO) << "Sleeping " << kTimeToWaitForGnmiConfigToApply - << " to wait for config to be applied/links to come up."; - absl::SleepFor(kTimeToWaitForGnmiConfigToApply); + std::tie(sut_p4rt_session, control_p4rt_session), + pins_test::ConfigureSwitchPairAndReturnP4RuntimeSessionPair( + sut, control_device, GetParam().gnmi_config, p4info)); // Pick a link to be used for packet injection. ASSERT_OK_AND_ASSIGN(SutToControlLink link_used_for_test_packets, @@ -934,8 +792,8 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { link_used_for_test_packets.sut_port_p4rt_name, /*mac=*/kSutMacAddress, /*ir_p4info=*/ir_p4info)); - ASSERT_OK( - pdpi::InstallPiTableEntry(p4rt_session.get(), router_interface_entry)); + ASSERT_OK(pdpi::InstallPiTableEntry(sut_p4rt_session.get(), + router_interface_entry)); } // Extract DSCP-to-queue mapping from gNMI config. @@ -983,10 +841,11 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { LOG(INFO) << "Target queue: " << target_queue; // Read counters of the target queue. + ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); ASSERT_OK_AND_ASSIGN( const QueueCounters queue_counters_before_test_packet, GetGnmiQueueCounters(/*port=*/"CPU", /*queue=*/target_queue, - *gnmi_stub)); + *sut_gnmi_stub)); // Inject test packet. ASSERT_OK_AND_ASSIGN( @@ -1011,7 +870,7 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { ASSERT_OK_AND_ASSIGN( queue_counters_after_test_packet, GetGnmiQueueCounters(/*port=*/"CPU", /*queue=*/target_queue, - *gnmi_stub)); + *sut_gnmi_stub)); } while ( // It may take several seconds for the queue counters to update. CumulativeNumPacketsEnqueued(queue_counters_after_test_packet) == @@ -1116,15 +975,16 @@ TEST_P(CpuQosTestWithIxia, TestCPUQueueAssignmentAndQueueRateLimit) { thinkit::Switch &sut = generic_testbed->Sut(); - // Set up P4Runtime session. - ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_p4_session, - pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables( - generic_testbed->Sut(), GetParam().p4info)); + // Configure SUT. + EXPECT_OK(generic_testbed->Environment().StoreTestArtifact( + "gnmi_config.json", GetParam().gnmi_config)); + EXPECT_OK(generic_testbed->Environment().StoreTestArtifact( + "p4info.textproto", GetParam().p4info)); - // Push GNMI config. - ASSERT_OK(pins_test::PushGnmiConfig(sut, GetParam().gnmi_config)); + ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_p4_session, + pins_test::ConfigureSwitchAndReturnP4RuntimeSession( + sut, GetParam().gnmi_config, GetParam().p4info)); - // Hook up to GNMI. ASSERT_OK_AND_ASSIGN(auto gnmi_stub, sut.CreateGnmiStub()); // Flow details. @@ -1170,15 +1030,15 @@ TEST_P(CpuQosTestWithIxia, TestPuntFlowRateLimitAndCounters) { thinkit::Switch &sut = generic_testbed->Sut(); - // Set up P4Runtime session. + // Configure SUT. + EXPECT_OK(generic_testbed->Environment().StoreTestArtifact( + "gnmi_config.json", GetParam().gnmi_config)); + EXPECT_OK(generic_testbed->Environment().StoreTestArtifact( + "p4info.textproto", GetParam().p4info)); ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_p4_session, - pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables( - generic_testbed->Sut(), GetParam().p4info)); + pins_test::ConfigureSwitchAndReturnP4RuntimeSession( + sut, GetParam().gnmi_config, GetParam().p4info)); - // Push GNMI config. - ASSERT_OK(pins_test::PushGnmiConfig(sut, GetParam().gnmi_config)); - - // Hook up to GNMI. ASSERT_OK_AND_ASSIGN(auto gnmi_stub, sut.CreateGnmiStub()); // Flow details. @@ -1205,6 +1065,9 @@ TEST_P(CpuQosTestWithIxia, TestPuntFlowRateLimitAndCounters) { LOG(INFO) << "Sleeping " << kTimeToWaitForGnmiConfigToApply << " to wait for config to be applied/links to come up."; absl::SleepFor(kTimeToWaitForGnmiConfigToApply); + ASSERT_OK( + pins_test::WaitForGnmiPortIdConvergence(sut, GetParam().gnmi_config, + /*timeout=*/absl::Minutes(3))); ASSERT_OK_AND_ASSIGN(std::vector ready_links, GetReadyIxiaLinks(*generic_testbed, *gnmi_stub)); @@ -1334,8 +1197,6 @@ TEST_P(CpuQosTestWithIxia, TestPuntFlowRateLimitAndCounters) { // Wait for Traffic to be sent. absl::SleepFor(kTrafficDuration); - ASSERT_OK(pins_test::ixia::StopTraffic(traffic_ref, *generic_testbed)); - // Check for counters every 5 seconds upto 30 seconds till the fetched gNMI // queue counter stats match packets and bytes sent by Ixia. // Check that the counters increment within kMaxQueueCounterUpdateTime. diff --git a/tests/qos/packet_in_receiver.h b/tests/qos/packet_in_receiver.h new file mode 100644 index 00000000..30357c6c --- /dev/null +++ b/tests/qos/packet_in_receiver.h @@ -0,0 +1,51 @@ +#ifndef PINS_TESTS_QOS_PACKET_IN_RECEIVER_H_ +#define PINS_TESTS_QOS_PACKET_IN_RECEIVER_H_ + +#include // NOLINT + +#include "p4_pdpi/p4_runtime_session.h" +namespace pins_test { + +// Packet receiver thread to receive punted packets from switch over a P4 +// session. The callback is invoked serially for every packet received. +// Example: +// PacketInReceiver receiver( +// p4_session, +// [&num_packets_punted]() -> absl::Status { +// num_packets_punted++; +// }); +// .. do stuff +// receiver.Destroy(); +class PacketInReceiver final { + public: + PacketInReceiver(pdpi::P4RuntimeSession &session, + std::function callback) + : session_(session), receiver_([this, callback = std::move(callback)]() { + p4::v1::StreamMessageResponse pi_response; + // To break out of this loop invoke Destroy(). + while (session_.StreamChannelRead(pi_response)) { + if (pi_response.has_packet()) { + callback(std::move(pi_response)); + } + } + }) {} + + PacketInReceiver() = delete; + + // It's ok to call this function multiple times. + void Destroy() { + session_.TryCancel(); + if (receiver_.joinable()) { + receiver_.join(); + } + } + + ~PacketInReceiver() { Destroy(); } + + private: + pdpi::P4RuntimeSession &session_; + std::thread receiver_; +}; + +} // namespace pins_test +#endif // PINS_TESTS_QOS_PACKET_IN_RECEIVER_H_ diff --git a/tests/qos/qos_test_util.cc b/tests/qos/qos_test_util.cc new file mode 100644 index 00000000..ad2af0cd --- /dev/null +++ b/tests/qos/qos_test_util.cc @@ -0,0 +1,143 @@ +#include "tests/qos/qos_test_util.h" + +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/strings/substitute.h" +#include "gutil/status.h" +#include "lib/gnmi/gnmi_helper.h" + +namespace pins_test { +absl::StatusOr GetGnmiQueueCounters( + absl::string_view port, absl::string_view queue, + gnmi::gNMI::StubInterface &gnmi_stub) { + QueueCounters counters; + + const std::string openconfig_transmit_count_state_path = absl::Substitute( + "qos/interfaces/interface[interface-id=$0]" + "/output/queues/queue[name=$1]/state/transmit-pkts", + port, queue); + + ASSIGN_OR_RETURN( + std::string transmit_counter_response, + GetGnmiStatePathInfo(&gnmi_stub, openconfig_transmit_count_state_path, + "openconfig-qos:transmit-pkts")); + + if (!absl::SimpleAtoi(StripQuotes(transmit_counter_response), + &counters.num_packets_transmitted)) { + return absl::InternalError(absl::StrCat("Unable to parse counter from ", + transmit_counter_response)); + } + + const std::string openconfig_drop_count_state_path = absl::Substitute( + "qos/interfaces/interface[interface-id=$0]" + "/output/queues/queue[name=$1]/state/dropped-pkts", + port, queue); + + ASSIGN_OR_RETURN( + std::string drop_counter_response, + GetGnmiStatePathInfo(&gnmi_stub, openconfig_drop_count_state_path, + "openconfig-qos:dropped-pkts")); + + if (!absl::SimpleAtoi(StripQuotes(drop_counter_response), + &counters.num_packet_dropped)) { + return absl::InternalError( + absl::StrCat("Unable to parse counter from ", drop_counter_response)); + } + return counters; +} + +// Returns the total number of packets enqueued for the queue with the given +// `QueueCounters`. +int64_t CumulativeNumPacketsEnqueued(const QueueCounters &counters) { + return counters.num_packet_dropped + counters.num_packets_transmitted; +} + +absl::Status SetPortSpeed(const std::string &port_speed, + const std::string &iface, + gnmi::gNMI::StubInterface &gnmi_stub) { + std::string ops_config_path = absl::StrCat( + "interfaces/interface[name=", iface, "]/ethernet/config/port-speed"); + std::string ops_val = + absl::StrCat("{\"openconfig-if-ethernet:port-speed\":", port_speed, "}"); + + RETURN_IF_ERROR(pins_test::SetGnmiConfigPath(&gnmi_stub, ops_config_path, + GnmiSetType::kUpdate, ops_val)); + + return absl::OkStatus(); +} + +absl::Status SetPortMtu(int port_mtu, const std::string &interface_name, + gnmi::gNMI::StubInterface &gnmi_stub) { + std::string config_path = absl::StrCat( + "interfaces/interface[name=", interface_name, "]/config/mtu"); + std::string value = absl::StrCat("{\"config:mtu\":", port_mtu, "}"); + + RETURN_IF_ERROR(pins_test::SetGnmiConfigPath(&gnmi_stub, config_path, + GnmiSetType::kUpdate, value)); + + return absl::OkStatus(); +} + +absl::StatusOr CheckLinkUp(const std::string &iface, + gnmi::gNMI::StubInterface &gnmi_stub) { + std::string oper_status_state_path = + absl::StrCat("interfaces/interface[name=", iface, "]/state/oper-status"); + + std::string parse_str = "openconfig-interfaces:oper-status"; + ASSIGN_OR_RETURN( + std::string ops_response, + GetGnmiStatePathInfo(&gnmi_stub, oper_status_state_path, parse_str)); + + return ops_response == "\"UP\""; +} + +// Go over the connections and return vector of connections +// whose links are up. +absl::StatusOr> GetReadyIxiaLinks( + thinkit::GenericTestbed &generic_testbed, + gnmi::gNMI::StubInterface &gnmi_stub) { + std::vector links; + + absl::flat_hash_map interface_info = + generic_testbed.GetSutInterfaceInfo(); + // Loop through the interface_info looking for Ixia/SUT interface pairs, + // checking if the link is up. Add the pair to connections. + for (const auto &[interface, info] : interface_info) { + bool sut_link_up = false; + if (info.interface_modes.contains(thinkit::TRAFFIC_GENERATOR)) + { + ASSIGN_OR_RETURN(sut_link_up, CheckLinkUp(interface, gnmi_stub)); + if (sut_link_up) { + links.push_back({ + .ixia_interface = info.peer_interface_name, + .sut_interface = interface, + }); + } + } + } + + return links; +} + +absl::StatusOr> +ParseIpv4DscpToQueueMapping(absl::string_view gnmi_config) { + // TODO: Actually parse config -- hard-coded for now. + absl::flat_hash_map queue_by_dscp; + for (int dscp = 0; dscp < 64; ++dscp) queue_by_dscp[dscp] = "BE1"; + for (int dscp = 8; dscp <= 11; ++dscp) queue_by_dscp[dscp] = "AF1"; + queue_by_dscp[13] = "LLQ1"; + for (int dscp = 16; dscp <= 19; ++dscp) queue_by_dscp[dscp] = "AF2"; + queue_by_dscp[21] = "LLQ2"; + for (int dscp = 24; dscp <= 27; ++dscp) queue_by_dscp[dscp] = "AF3"; + for (int dscp = 32; dscp <= 35; ++dscp) queue_by_dscp[dscp] = "AF4"; + for (int dscp = 48; dscp <= 59; ++dscp) queue_by_dscp[dscp] = "NC1"; + return queue_by_dscp; +} + +absl::StatusOr> +ParseIpv6DscpToQueueMapping(absl::string_view gnmi_config) { + // TODO: Actually parse config -- hard-coded for now. + return ParseIpv4DscpToQueueMapping(gnmi_config); +} + +} // namespace pins_test diff --git a/tests/qos/qos_test_util.h b/tests/qos/qos_test_util.h new file mode 100644 index 00000000..806dd7b3 --- /dev/null +++ b/tests/qos/qos_test_util.h @@ -0,0 +1,74 @@ +#ifndef PINS_TESTS_QOS_QOS_TEST_UTIL_H_ +#define PINS_TESTS_QOS_QOS_TEST_UTIL_H_ + +#include "absl/status/statusor.h" +#include "absl/strings/str_format.h" +#include "proto/gnmi/gnmi.grpc.pb.h" +#include "proto/gnmi/gnmi.pb.h" +#include "thinkit/generic_testbed.h" +#include "thinkit/proto/generic_testbed.pb.h" + +namespace pins_test { +// These are the counters we track in these tests. +struct QueueCounters { + int64_t num_packets_transmitted = 0; + int64_t num_packet_dropped = 0; +}; + +// Operator to pretty print Queue Counters. +inline std::ostream &operator<<(std::ostream &os, + const QueueCounters &counters) { + return os << absl::StreamFormat( + "QueueCounters{" + ".num_packets_transmitted = %d, " + ".num_packets_dropped = %d" + "}", + counters.num_packets_transmitted, counters.num_packet_dropped); +} + +// Get queue counters for a port queue. +absl::StatusOr GetGnmiQueueCounters( + absl::string_view port, absl::string_view queue, + gnmi::gNMI::StubInterface &gnmi_stub); + +// Get total packets (transmitted + dropped) for port queue. +int64_t CumulativeNumPacketsEnqueued(const QueueCounters &counters); + +// Set port speed using gNMI. +absl::Status SetPortSpeed(const std::string &port_speed, + const std::string &iface, + gnmi::gNMI::StubInterface &gnmi_stub); + +// Set port MTU using gNMI. +absl::Status SetPortMtu(int port_mtu, const std::string &interface_name, + gnmi::gNMI::StubInterface &gnmi_stub); + +// Check if switch port link is up. +absl::StatusOr CheckLinkUp(const std::string &iface, + gnmi::gNMI::StubInterface &gnmi_stub); + +// Structure represents a link between SUT and Ixia. +// This is represented by Ixia interface name and the SUT's gNMI interface +// name. +struct IxiaLink { + std::string ixia_interface; + std::string sut_interface; +}; + +// Go over the connections and return vector of connections +// whose links are up. +absl::StatusOr> GetReadyIxiaLinks( + thinkit::GenericTestbed &generic_testbed, + gnmi::gNMI::StubInterface &gnmi_stub); + +// Parse IPv4 DSCP to queue mapping from gnmi configuration. +absl::StatusOr> +ParseIpv4DscpToQueueMapping(absl::string_view gnmi_config); + +// Parse IPv6 DSCP to queue mapping from gnmi configuration. +absl::StatusOr> +ParseIpv6DscpToQueueMapping(absl::string_view gnmi_config); + +} // namespace pins_test + +#endif // PINS_TESTS_QOS_QOS_TEST_UTIL_H_ diff --git a/tests/thinkit_gnmi_interface_tests.cc b/tests/thinkit_gnmi_interface_tests.cc index c8e34e0b..d882bb24 100644 --- a/tests/thinkit_gnmi_interface_tests.cc +++ b/tests/thinkit_gnmi_interface_tests.cc @@ -71,6 +71,8 @@ void BreakoutDuringPortInUse(thinkit::Switch& sut, GetBreakoutStateInfoForPort(sut_gnmi_stub, port_info.port_name, port_info.curr_breakout_mode)); + LOG(INFO) << "Using port " << port_info.port_name + << " with current breakout mode " << port_info.curr_breakout_mode; // Verify that all ports for the selected port are operationally up. auto resp_parse_str = "openconfig-interfaces:oper-status"; for (const auto& p : orig_breakout_info) {