From c0510aa1fe4250366c8a47c2efba7d98b0ca6565 Mon Sep 17 00:00:00 2001 From: kishanps Date: Thu, 16 Jan 2025 13:13:58 +0000 Subject: [PATCH 1/3] [Thinkit] Add provision to pass in ingress and egress ports to tests. --- tests/qos/frontpanel_qos_test.cc | 540 +++++++++++++++++-------------- tests/qos/frontpanel_qos_test.h | 26 +- 2 files changed, 318 insertions(+), 248 deletions(-) diff --git a/tests/qos/frontpanel_qos_test.cc b/tests/qos/frontpanel_qos_test.cc index 2e2d4e68..b690c4d0 100644 --- a/tests/qos/frontpanel_qos_test.cc +++ b/tests/qos/frontpanel_qos_test.cc @@ -198,6 +198,203 @@ ConstructEntriesToForwardAllTrafficToGivenPort(absl::string_view p4rt_port_id) { kNeighborId, p4rt_port_id)); } +// Returns a set of table entries that will cause a switch to forward all L3 +// packets arriving at one of the two given ingress ports out of the given +// loopback port and also program a flow to punt ECN marked packets which are +// looped back to the CPU. Specific Requirements: +// - L3 admit entries to match on in ports as we do not want to reforward looped +// back packets +// - Punt all ECN marked packets which are looped back from egress port. +absl::StatusOr +ConstructEntriesToForwardAllTrafficToLoopbackPortAndCopyEcnPacketsToCPU( + absl::string_view p4rt_out_port_id, absl::string_view p4rt_in_port1_id, + absl::string_view p4rt_in_port2_id) { + // By convention, we use link local IPv6 addresses as neighbor IDs. + const std::string kNeighborId = + netaddr::MacAddress(2, 2, 2, 2, 2, 2).ToLinkLocalIpv6Address().ToString(); + // L3 admit packets coming in only on the ingress ports. We do not want to + // reforward looped back packets. + return gutil::ParseTextProto(absl::Substitute( + R"pb( + entries { + l3_admit_table_entry { + match { in_port { value: "$2" } } + action { admit_to_l3 {} } + priority: 1 + } + } + entries { + l3_admit_table_entry { + match { in_port { value: "$3" } } + action { admit_to_l3 {} } + priority: 1 + } + } + entries { + acl_pre_ingress_table_entry { + match {} # Wildcard. + action { set_vrf { vrf_id: "vrf" } } + priority: 1 + } + } + entries { + vrf_table_entry { + match { vrf_id: "vrf" } + action { no_action {} } + } + } + entries { + ipv4_table_entry { + match { vrf_id: "vrf" } + action { set_nexthop_id { nexthop_id: "nexthop" } } + } + } + entries { + ipv6_table_entry { + match { vrf_id: "vrf" } + action { set_nexthop_id { nexthop_id: "nexthop" } } + } + } + entries { + nexthop_table_entry { + match { nexthop_id: "nexthop" } + action { + set_ip_nexthop { router_interface_id: "rif" neighbor_id: "$0" } + } + } + } + entries { + router_interface_table_entry { + match { router_interface_id: "rif" } + action { + set_port_and_src_mac { port: "$1" src_mac: "66:55:44:33:22:11" } + } + } + } + entries { + neighbor_table_entry { + match { router_interface_id: "rif" neighbor_id: "$0" } + action { set_dst_mac { dst_mac: "02:02:02:02:02:02" } } + } + } + entries { + acl_ingress_table_entry { + match { + dst_mac { value: "02:02:02:02:02:02" mask: "ff:ff:ff:ff:ff:ff" } + is_ip { value: "0x1" } + ecn { value: "0x3" mask: "0x3" } + } + action { acl_copy { qos_queue: "2" } } + priority: 1 + } + } + )pb", + kNeighborId, p4rt_out_port_id, p4rt_in_port1_id, p4rt_in_port2_id)); +} + +// Structure represents SUTs connections to Ixia. +// The QoS tests expects 2 Ixia `ingress_links` connected to Ixia to which +// traffic will be injected in order to oversubscribe the single `egress_link`. +struct IxiaLinks { + ixia::IxiaLink ingress_links[2]; + ixia::IxiaLink egress_link; +}; + +// Function returns Ixia connections to use for ingress and egress traffic +// If the device ports are specified as part of test parameters passed in, +// the function will use those ports to fetch Ixia link information, +// else we will try and find 2 Ixia links for ingress and 1 for egress such +// that ingress ports have speed at least that of egress port. +absl::StatusOr +GetIxiaLinks(thinkit::GenericTestbed &testbed, + gnmi::gNMI::StubInterface &gnmi_stub, + const pins_test::QosTestParams &qos_params) { + IxiaLinks links; + + if (qos_params.ingress_ports[0].empty() || + qos_params.ingress_ports[1].empty() || + qos_params.egress_port_under_test.empty()) { + // Pick 3 SUT ports connected to the Ixia, 2 for receiving test packets and + // 1 for forwarding them back. We use the faster links for injecting packets + // so we can oversubsribe the egress port. + LOG(INFO) << "picking test packet links"; + ASSIGN_OR_RETURN(std::vector ready_links, + ixia::GetReadyIxiaLinks(testbed, gnmi_stub)); + absl::c_sort(ready_links, [&](auto &x, auto &y) -> bool { + return x.sut_interface_bits_per_second < y.sut_interface_bits_per_second; + }); + RET_CHECK(ready_links.size() >= 3) + << "Test requires at least 3 SUT ports connected to an Ixia"; + const auto [kEgressLink, kIngressLink1, kIngressLink2] = + std::make_tuple(ready_links[0], ready_links[1], ready_links[2]); + RET_CHECK(kEgressLink.sut_interface_bits_per_second <= + kIngressLink1.sut_interface_bits_per_second); + RET_CHECK(kEgressLink.sut_interface_bits_per_second <= + kIngressLink2.sut_interface_bits_per_second); + links.ingress_links[0] = kIngressLink1; + links.ingress_links[1] = kIngressLink2; + links.egress_link = kEgressLink; + } else { + ASSIGN_OR_RETURN( + links.ingress_links[0], + ixia::GetIxiaLink(testbed, gnmi_stub, qos_params.ingress_ports[0])); + ASSIGN_OR_RETURN( + links.ingress_links[1], + ixia::GetIxiaLink(testbed, gnmi_stub, qos_params.ingress_ports[1])); + ASSIGN_OR_RETURN(links.egress_link, + ixia::GetIxiaLink(testbed, gnmi_stub, + qos_params.egress_port_under_test)); + } + return links; +} + +// Like the function above but overloaded for BufferTestParams +absl::StatusOr +GetIxiaLinks(thinkit::GenericTestbed &testbed, + gnmi::gNMI::StubInterface &gnmi_stub, + const pins_test::BufferTestParams &qos_params) { + IxiaLinks links; + + if (qos_params.default_params.ingress_ports[0].empty() || + qos_params.default_params.ingress_ports[1].empty() || + qos_params.default_params.egress_port_under_test.empty()) { + // Pick 3 SUT ports connected to the Ixia, 2 for receiving test packets and + // 1 for forwarding them back. We use the faster links for injecting packets + // so we can oversubsribe the egress port. + LOG(INFO) << "picking test packet links"; + ASSIGN_OR_RETURN(std::vector ready_links, + ixia::GetReadyIxiaLinks(testbed, gnmi_stub)); + absl::c_sort(ready_links, [&](auto &x, auto &y) -> bool { + return x.sut_interface_bits_per_second < y.sut_interface_bits_per_second; + }); + RET_CHECK(ready_links.size() >= 3) + << "Test requires at least 3 SUT ports connected to an Ixia"; + const auto [kEgressLink, kIngressLink1, kIngressLink2] = + std::make_tuple(ready_links[0], ready_links[1], ready_links[2]); + RET_CHECK(kEgressLink.sut_interface_bits_per_second <= + kIngressLink1.sut_interface_bits_per_second); + RET_CHECK(kEgressLink.sut_interface_bits_per_second <= + kIngressLink2.sut_interface_bits_per_second); + links.ingress_links[0] = kIngressLink1; + links.ingress_links[1] = kIngressLink2; + links.egress_link = kEgressLink; + } else { + ASSIGN_OR_RETURN( + links.ingress_links[0], + ixia::GetIxiaLink(testbed, gnmi_stub, + qos_params.default_params.ingress_ports[0])); + ASSIGN_OR_RETURN( + links.ingress_links[1], + ixia::GetIxiaLink(testbed, gnmi_stub, + qos_params.default_params.ingress_ports[1])); + ASSIGN_OR_RETURN( + links.egress_link, + ixia::GetIxiaLink(testbed, gnmi_stub, + qos_params.default_params.egress_port_under_test)); + } + return links; +} + // The purpose of this test is to verify that: // - Incoming IP packets are mapped to queues according to their DSCP. // - Queue peak information rates (PIRs) are enforced. @@ -223,14 +420,16 @@ TEST_P(FrontpanelQosTest, // Pick 2 SUT ports connected to the Ixia, one for receiving test packets and // one for forwarding them back. ASSERT_OK_AND_ASSIGN(auto gnmi_stub, testbed->Sut().CreateGnmiStub()); - ASSERT_OK_AND_ASSIGN(std::vector ready_links, - GetReadyIxiaLinks(*testbed, *gnmi_stub)); - ASSERT_GE(ready_links.size(), 2) - << "Test requires at least 2 SUT ports connected to an Ixia"; - const std::string kIxiaSrcPort = ready_links[0].ixia_interface; - const std::string kIxiaDstPort = ready_links[1].ixia_interface; - const std::string kSutIngressPort = ready_links[0].sut_interface; - const std::string kSutEgressPort = ready_links[1].sut_interface; + // Get Ixia connected links. + ASSERT_OK_AND_ASSIGN(IxiaLinks links, + GetIxiaLinks(*testbed, *gnmi_stub, GetParam())); + + const std::string &kSutIngressPort = links.ingress_links[0].sut_interface; + const std::string &kSutEgressPort = links.egress_link.sut_interface; + + const std::string &kIxiaSrcPort = links.ingress_links[0].ixia_interface; + const std::string &kIxiaDstPort = links.egress_link.ixia_interface; + LOG(INFO) << absl::StrFormat( "Test packet route: [Ixia: %s] => [SUT: %s] -> [SUT: %s] => [Ixia: %s]", kIxiaSrcPort, kSutIngressPort, kSutEgressPort, kIxiaDstPort); @@ -519,25 +718,19 @@ TEST_P(FrontpanelQosTest, WeightedRoundRobinWeightsAreRespected) { // strictly-prioritized queue via another ingress port. LOG(INFO) << "picking test packet links"; ASSERT_OK_AND_ASSIGN(auto gnmi_stub, testbed->Sut().CreateGnmiStub()); - ASSERT_OK_AND_ASSIGN(std::vector ready_links, - GetReadyIxiaLinks(*testbed, *gnmi_stub)); - absl::c_sort(ready_links, [&](auto &x, auto &y) -> bool { - return x.sut_interface_bits_per_second < y.sut_interface_bits_per_second; - }); - ASSERT_GE(ready_links.size(), 3) - << "Test requires at least 3 SUT ports connected to an Ixia"; - const auto [kEgressLink, kIngressLink1, kIngressLink2] = - std::make_tuple(ready_links[0], ready_links[1], ready_links[2]); - ASSERT_LE(kEgressLink.sut_interface_bits_per_second, - kIngressLink1.sut_interface_bits_per_second); - ASSERT_LE(kEgressLink.sut_interface_bits_per_second, - kIngressLink2.sut_interface_bits_per_second); - const std::string kIxiaMainSrcPort = kIngressLink1.ixia_interface; - const std::string kIxiaAuxiliarySrcPort = kIngressLink2.ixia_interface; - const std::string kSutMainIngressPort = kIngressLink1.sut_interface; - const std::string kSutAuxiliayIngressPort = kIngressLink2.sut_interface; - const std::string kSutEgressPort = kEgressLink.sut_interface; - const std::string kIxiaDstPort = kEgressLink.ixia_interface; + // Get Ixia connected links. + ASSERT_OK_AND_ASSIGN(IxiaLinks links, + GetIxiaLinks(*testbed, *gnmi_stub, GetParam())); + + const std::string &kSutMainIngressPort = links.ingress_links[0].sut_interface; + const std::string &kSutAuxiliayIngressPort = + links.ingress_links[1].sut_interface; + const std::string &kSutEgressPort = links.egress_link.sut_interface; + const std::string &kIxiaMainSrcPort = links.ingress_links[0].ixia_interface; + const std::string &kIxiaAuxiliarySrcPort = + links.ingress_links[1].ixia_interface; + const std::string &kIxiaDstPort = links.egress_link.ixia_interface; + LOG(INFO) << absl::StrFormat( "Test packet routes:" "\n- Main traffic: " @@ -547,13 +740,14 @@ TEST_P(FrontpanelQosTest, WeightedRoundRobinWeightsAreRespected) { "[Ixia: %s] == %.1f Gbps => [SUT: %s] -> [SUT: %s] == %.1f Gbps => " "[Ixia: %s]", kIxiaMainSrcPort, - kIngressLink1.sut_interface_bits_per_second / 1'000'000'000., + links.ingress_links[0].sut_interface_bits_per_second / 1'000'000'000., kSutMainIngressPort, kSutEgressPort, - kEgressLink.sut_interface_bits_per_second / 1'000'000'000., kIxiaDstPort, - kIxiaAuxiliarySrcPort, - kIngressLink2.sut_interface_bits_per_second / 1'000'000'000., + links.egress_link.sut_interface_bits_per_second / 1'000'000'000., + kIxiaDstPort, kIxiaAuxiliarySrcPort, + links.ingress_links[1].sut_interface_bits_per_second / 1'000'000'000., kSutAuxiliayIngressPort, kSutEgressPort, - kEgressLink.sut_interface_bits_per_second / 1'000'000'000., kIxiaDstPort); + links.egress_link.sut_interface_bits_per_second / 1'000'000'000., + kIxiaDstPort); ASSERT_OK_AND_ASSIGN( const std::string kSutEgressPortSchedulerPolicy, GetSchedulerPolicyNameByEgressPort(kSutEgressPort, *gnmi_stub)); @@ -721,7 +915,7 @@ TEST_P(FrontpanelQosTest, WeightedRoundRobinWeightsAreRespected) { LOG(INFO) << "configuring scheduler parameters"; // Rates are in bytes/second. const int64_t kEgressLineRateInBytesPerSecond = - kEgressLink.sut_interface_bits_per_second / 8; + links.egress_link.sut_interface_bits_per_second / 8; const int64_t kStrictlyPrioritizedPir = .95 * kEgressLineRateInBytesPerSecond; { absl::flat_hash_map params_by_queue_name; @@ -917,26 +1111,20 @@ TEST_P(FrontpanelQosTest, StrictQueuesAreStrictlyPrioritized) { // strictly-prioritized queue via another ingress port. LOG(INFO) << "picking test packet links"; ASSERT_OK_AND_ASSIGN(auto gnmi_stub, testbed->Sut().CreateGnmiStub()); - ASSERT_OK_AND_ASSIGN(std::vector ready_links, - GetReadyIxiaLinks(*testbed, *gnmi_stub)); - absl::c_sort(ready_links, [&](auto &x, auto &y) -> bool { - return x.sut_interface_bits_per_second < y.sut_interface_bits_per_second; - }); - ASSERT_GE(ready_links.size(), 3) - << "Test requires at least 3 SUT ports connected to an Ixia"; - const auto [kEgressLink, kIngressLink1, kIngressLink2] = - std::make_tuple(ready_links[0], ready_links[1], ready_links[2]); - ASSERT_LE(kEgressLink.sut_interface_bits_per_second, - kIngressLink1.sut_interface_bits_per_second); - ASSERT_LE(kEgressLink.sut_interface_bits_per_second, - kIngressLink2.sut_interface_bits_per_second); - const std::string kIxiaMainTrafficSrcPort = kIngressLink1.ixia_interface; - const std::string kIxiaBackgroundTrafficSrcPort = - kIngressLink2.ixia_interface; - const std::string kSutMainTrafficInPort = kIngressLink1.sut_interface; - const std::string kSutBackgroundTrafficInPort = kIngressLink2.sut_interface; - const std::string kSutEgressPort = kEgressLink.sut_interface; - const std::string kIxiaDstPort = kEgressLink.ixia_interface; + // Get Ixia connected links. + ASSERT_OK_AND_ASSIGN(IxiaLinks links, + GetIxiaLinks(*testbed, *gnmi_stub, GetParam())); + + const std::string &kSutMainTrafficInPort = + links.ingress_links[0].sut_interface; + const std::string &kSutBackgroundTrafficInPort = + links.ingress_links[1].sut_interface; + const std::string &kSutEgressPort = links.egress_link.sut_interface; + const std::string &kIxiaMainTrafficSrcPort = + links.ingress_links[0].ixia_interface; + const std::string &kIxiaBackgroundTrafficSrcPort = + links.ingress_links[1].ixia_interface; + const std::string &kIxiaDstPort = links.egress_link.ixia_interface; LOG(INFO) << absl::StrFormat( "Test packet routes:" "\n- Main traffic: " @@ -946,13 +1134,14 @@ TEST_P(FrontpanelQosTest, StrictQueuesAreStrictlyPrioritized) { "[Ixia: %s] == %.1f Gbps => [SUT: %s] -> [SUT: %s] == %.1f Gbps => " "[Ixia: %s]", kIxiaMainTrafficSrcPort, - kIngressLink1.sut_interface_bits_per_second / 1'000'000'000., + links.ingress_links[0].sut_interface_bits_per_second / 1'000'000'000., kSutMainTrafficInPort, kSutEgressPort, - kEgressLink.sut_interface_bits_per_second / 1'000'000'000., kIxiaDstPort, - kIxiaBackgroundTrafficSrcPort, - kIngressLink2.sut_interface_bits_per_second / 1'000'000'000., + links.egress_link.sut_interface_bits_per_second / 1'000'000'000., + kIxiaDstPort, kIxiaBackgroundTrafficSrcPort, + links.ingress_links[1].sut_interface_bits_per_second / 1'000'000'000., kSutBackgroundTrafficInPort, kSutEgressPort, - kEgressLink.sut_interface_bits_per_second / 1'000'000'000., kIxiaDstPort); + links.egress_link.sut_interface_bits_per_second / 1'000'000'000., + kIxiaDstPort); ASSERT_OK_AND_ASSIGN( const std::string kSutEgressPortSchedulerPolicy, GetSchedulerPolicyNameByEgressPort(kSutEgressPort, *gnmi_stub)); @@ -1110,7 +1299,7 @@ TEST_P(FrontpanelQosTest, StrictQueuesAreStrictlyPrioritized) { const int kNumBackgroundTrafficItems = 2 * kNumQueueus; // IPv4 & IPv6 constexpr int kFrameSizeInBytes = 1514; const int64_t kEgressLineRateInBytesPerSecond = - kEgressLink.sut_interface_bits_per_second / 8; + links.egress_link.sut_interface_bits_per_second / 8; const int kEgressLineRateInFramesPerSecond = .99 * kEgressLineRateInBytesPerSecond / kFrameSizeInBytes; const int kFramesPerSecondPerTrafficItem = @@ -1329,144 +1518,6 @@ void ResetEcnTestPacketCounters(EcnTestPacketCounters &packet_receive_info) { packet_receive_info.num_packets_ecn_marked = 0; } -// Set up the switch to forward inbound packets to the egress port using -// default route in VRF. The rules will forward all matching packets matching -// source MAC address to the egress port specified. -// -// Also set up a Copy rule to CPU to punt egress packets to test for -// any inspection. -// -absl::Status SetUpForwardingAndCopyEgressToCpu( - absl::string_view out_port, absl::string_view source_mac, - absl::string_view dest_mac, const p4::config::v1::P4Info &p4info, - pdpi::P4RuntimeSession &p4_session) { - constexpr absl::string_view kVrfId = "vrf-80"; - constexpr absl::string_view kRifOutId = "router-interface"; - constexpr absl::string_view kNextHopId = "nexthop-1"; - constexpr absl::string_view kNeighborIdv6 = "fe80::002:02ff:fe02:0202"; - const absl::string_view kNeighborId = kNeighborIdv6; - - std::vector pd_entries; - ASSIGN_OR_RETURN(pd_entries.emplace_back(), - gutil::ParseTextProto(absl::Substitute( - R"pb( - vrf_table_entry { - match { vrf_id: "$0" } - action { no_action {} } - })pb", - kVrfId))); - ASSIGN_OR_RETURN( - pd_entries.emplace_back(), - gutil::ParseTextProto(absl::Substitute( - R"pb( - router_interface_table_entry { - match { router_interface_id: "$0" } - action { - set_port_and_src_mac { port: "$1" src_mac: "66:55:44:33:22:11" } - } - } - )pb", - kRifOutId, out_port))); - - ASSIGN_OR_RETURN( - pd_entries.emplace_back(), - gutil::ParseTextProto(absl::Substitute( - R"pb( - neighbor_table_entry { - match { router_interface_id: "$0" neighbor_id: "$1" } - action { set_dst_mac { dst_mac: "02:02:02:02:02:02" } } - } - )pb", - kRifOutId, kNeighborId))); - - ASSIGN_OR_RETURN( - pd_entries.emplace_back(), - gutil::ParseTextProto(absl::Substitute( - R"pb( - nexthop_table_entry { - match { nexthop_id: "$2" } - action { - set_ip_nexthop { router_interface_id: "$0" neighbor_id: "$1" } - } - } - )pb", - kRifOutId, kNeighborId, kNextHopId))); - - ASSIGN_OR_RETURN(pd_entries.emplace_back(), - gutil::ParseTextProto(absl::Substitute( - R"pb( - ipv4_table_entry { - match { vrf_id: "$0" } - action { set_nexthop_id { nexthop_id: "$1" } } - } - )pb", - kVrfId, kNextHopId))); - - ASSIGN_OR_RETURN(pd_entries.emplace_back(), - gutil::ParseTextProto(absl::Substitute( - R"pb( - ipv6_table_entry { - match { vrf_id: "$0" } - action { set_nexthop_id { nexthop_id: "$1" } } - } - )pb", - kVrfId, kNextHopId))); - - ASSIGN_OR_RETURN( - pd_entries.emplace_back(), - gutil::ParseTextProto(absl::Substitute( - R"pb( - acl_pre_ingress_table_entry { - match { src_mac { value: "$0" mask: "ff:ff:ff:ff:ff:ff" } } - action { set_vrf { vrf_id: "$1" } } - priority: 1 - } - )pb", - source_mac, kVrfId))); - - ASSIGN_OR_RETURN(pd_entries.emplace_back(), - gutil::ParseTextProto( - R"pb( - acl_ingress_table_entry { - match { - dst_mac { value: "02:02:02:02:02:02" mask: "ff:ff:ff:ff:ff:ff" } - is_ip { value: "0x1" } - ecn { value: "0x3" mask: "0x3" } - } - action { acl_copy { qos_queue: "2" } } - priority: 1 - } - )pb")); - - ASSIGN_OR_RETURN( - pd_entries.emplace_back(), - gutil::ParseTextProto(absl::Substitute( - R"pb( - l3_admit_table_entry { - match { dst_mac { value: "$0" mask: "FF:FF:FF:FF:FF:FF" } } - action { admit_to_l3 {} } - priority: 1 - } - )pb", - dest_mac))); - - // Clear table entries - RETURN_IF_ERROR(pdpi::ClearTableEntries(&p4_session)); - - ASSIGN_OR_RETURN(auto ir_p4info, pdpi::CreateIrP4Info(p4info)); - std::vector pi_entries; - for (const auto &pd_entry : pd_entries) { - ASSIGN_OR_RETURN(p4::v1::TableEntry pi_entry, - pdpi::PartialPdTableEntryToPiTableEntry(ir_p4info, pd_entry), - _.SetPrepend() - << "Failed in PD table conversion to PI, entry: " - << pd_entry.DebugString() << " error: "); - pi_entries.push_back(pi_entry); - } - - return (pdpi::InstallPiTableEntries(&p4_session, ir_p4info, pi_entries)); -} - bool IsEcnMarked(const packetlib::Packet &packet) { if (packet.headers_size() >= 2 && ((packet.headers(1).has_ipv4_header() && @@ -1562,22 +1613,24 @@ TEST_P(FrontpanelQosTest, TestWredEcnMarking) { ASSERT_OK_AND_ASSIGN(auto gnmi_stub, testbed->Sut().CreateGnmiStub()); // Get Ixia connected links. - const absl::flat_hash_map - interface_info = testbed->GetSutInterfaceInfo(); - ASSERT_OK_AND_ASSIGN(std::vector ready_links, - GetReadyIxiaLinks(*testbed, *gnmi_stub)); - ASSERT_GE(ready_links.size(), 3) - << "Test requires at least 3 SUT ports connected to an Ixia"; - - const std::string kIxiaTxPort1 = ready_links[0].ixia_interface; - const std::string kSutInPort1 = ready_links[0].sut_interface; - - const std::string kIxiaTxPort2 = ready_links[1].ixia_interface; - const std::string kSutInPort2 = ready_links[1].sut_interface; + ASSERT_OK_AND_ASSIGN(IxiaLinks links, + GetIxiaLinks(*testbed, *gnmi_stub, GetParam())); + + const std::string &kSutInPort1 = links.ingress_links[0].sut_interface; + const std::string &kIxiaTxPort1 = links.ingress_links[0].ixia_interface; + + const std::string &kSutInPort2 = links.ingress_links[1].sut_interface; + const std::string &kIxiaTxPort2 = links.ingress_links[1].ixia_interface; + + const std::string &kSutOutPort = links.egress_link.sut_interface; + const std::string &kIxiaRxPort = links.egress_link.ixia_interface; + + LOG(INFO) << absl::StrFormat( + "Test packet route: [Ixia: %s]/[Ixia: %s] => [SUT: %s]/[SUT: %s] -> " + "[SUT: %s] => [Ixia: %s]", + kIxiaTxPort1, kIxiaTxPort2, kSutInPort1, kSutInPort2, kSutOutPort, + kIxiaRxPort); - const std::string kIxiaRxPort = ready_links[2].ixia_interface; - const std::string kSutOutPort = ready_links[2].sut_interface; - // Set the egress port to loopback mode // Configure the switch egress port as loopback as we want to loopback the // egress packets to test receiver for inspecting the packets. @@ -1704,9 +1757,19 @@ TEST_P(FrontpanelQosTest, TestWredEcnMarking) { const std::string kDefaultQueueName = "BE1"; // Set up the switch to forward inbound packets to the egress port - ASSERT_OK(SetUpForwardingAndCopyEgressToCpu( - kSutOutPortId, kSourceMac.ToString(), kDestMac.ToString(), - GetParam().p4info, *sut_p4_session)); + // Configure the switch to send all incoming packets out of the chosen egress + // port. + ASSERT_OK_AND_ASSIGN( + const sai::TableEntries kTableEntries, + ConstructEntriesToForwardAllTrafficToLoopbackPortAndCopyEcnPacketsToCPU( + /*p4rt_out_port_id=*/kSutOutPortId, + /*p4rt_in_port1_id=*/kSutInPort1Id, + /*p4rt_in_port2_id=*/kSutInPort2Id)); + ASSERT_OK(testbed->Environment().StoreTestArtifact("pd_entries.textproto", + kTableEntries)); + + ASSERT_OK( + InstallPdTableEntries(kTableEntries, GetParam().p4info, *sut_p4_session)); // Test ECN marking for IPv4 then IPv6 traffic. for (bool is_ipv4 : {false, true}) { @@ -1877,32 +1940,24 @@ TEST_P(FrontpanelBufferTest, BufferCarving) { )pb"); ASSERT_OK_AND_ASSIGN( std::unique_ptr testbed, - GetParam().testbed_interface->GetTestbedWithRequirements(requirements)); + GetParam().default_params.testbed_interface->GetTestbedWithRequirements( + requirements)); // Pick 3 SUT ports connected to the Ixia, 2 for receiving test packets and // 1 for forwarding them back. We use the faster links for injecting packets // so we can oversubsribe the egress port. LOG(INFO) << "picking test packet links"; ASSERT_OK_AND_ASSIGN(auto gnmi_stub, testbed->Sut().CreateGnmiStub()); - ASSERT_OK_AND_ASSIGN(std::vector ready_links, - GetReadyIxiaLinks(*testbed, *gnmi_stub)); - absl::c_sort(ready_links, [&](auto &x, auto &y) -> bool { - return x.sut_interface_bits_per_second < y.sut_interface_bits_per_second; - }); - ASSERT_GE(ready_links.size(), 3) - << "Test requires at least 3 SUT ports connected to an Ixia"; - const auto [kEgressLink, kIngressLink1, kIngressLink2] = - std::make_tuple(ready_links[0], ready_links[1], ready_links[2]); - ASSERT_LE(kEgressLink.sut_interface_bits_per_second, - kIngressLink1.sut_interface_bits_per_second); - ASSERT_LE(kEgressLink.sut_interface_bits_per_second, - kIngressLink2.sut_interface_bits_per_second); - const std::string kIxiaIpv4SrcPort = kIngressLink1.ixia_interface; - const std::string kIxiaIpv6SrcPort = kIngressLink2.ixia_interface; - const std::string kIxiaDstPort = kEgressLink.ixia_interface; - const std::string kSutIpv4IngressPort = kIngressLink1.sut_interface; - const std::string kSutIpv6IngressPort = kIngressLink2.sut_interface; - const std::string kSutEgressPort = kEgressLink.sut_interface; + // Get Ixia connected links. + ASSERT_OK_AND_ASSIGN(IxiaLinks links, + GetIxiaLinks(*testbed, *gnmi_stub, GetParam())); + + const std::string &kSutIpv4IngressPort = links.ingress_links[0].sut_interface; + const std::string &kSutIpv6IngressPort = links.ingress_links[1].sut_interface; + const std::string &kSutEgressPort = links.egress_link.sut_interface; + const std::string &kIxiaIpv4SrcPort = links.ingress_links[0].ixia_interface; + const std::string &kIxiaIpv6SrcPort = links.ingress_links[1].ixia_interface; + const std::string &kIxiaDstPort = links.egress_link.ixia_interface; LOG(INFO) << absl::StrFormat( "Test packet routes:" "\n- IPv4: [Ixia: %s] => [SUT: %s] -> [SUT: %s] => [Ixia: %s]" @@ -1927,17 +1982,18 @@ TEST_P(FrontpanelBufferTest, BufferCarving) { // Configure the switch to send all incomming packets out of the chosen egress // port. - ASSERT_OK_AND_ASSIGN( - std::unique_ptr sut_p4rt, - ConfigureSwitchAndReturnP4RuntimeSession( - testbed->Sut(), /*gnmi_config=*/std::nullopt, GetParam().p4info)); + ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_p4rt, + ConfigureSwitchAndReturnP4RuntimeSession( + testbed->Sut(), /*gnmi_config=*/std::nullopt, + GetParam().default_params.p4info)); ASSERT_OK_AND_ASSIGN( const sai::TableEntries kTableEntries, ConstructEntriesToForwardAllTrafficToGivenPort(kSutEgressPortP4rtId)); ASSERT_OK(testbed->Environment().StoreTestArtifact("pd_entries.textproto", kTableEntries)); - ASSERT_OK(InstallPdTableEntries(kTableEntries, GetParam().p4info, *sut_p4rt)); + ASSERT_OK(InstallPdTableEntries(kTableEntries, + GetParam().default_params.p4info, *sut_p4rt)); // Before we update the scheduler config, save the current config and // prepare to restore it at the end of th test. @@ -1964,7 +2020,7 @@ TEST_P(FrontpanelBufferTest, BufferCarving) { params_by_queue_name[queue_name].committed_information_rate = 0; // Limit peak rate to 10% of line rate so queue is always full. params_by_queue_name[queue_name].peak_information_rate = - 0.1 * kEgressLink.sut_interface_bits_per_second / 8; + 0.1 * links.egress_link.sut_interface_bits_per_second / 8; } ASSERT_OK(SetSchedulerPolicyParameters(kSutEgressPortSchedulerPolicy, params_by_queue_name, *gnmi_stub)); @@ -2021,7 +2077,7 @@ TEST_P(FrontpanelBufferTest, BufferCarving) { ixia::IxiaVport(kIxiaHandle, kIxiaDstPort, *testbed)); constexpr int kFrameSizeInBytes = 1000; const int kTotalFramesPerSecond = - kEgressLink.sut_interface_bits_per_second / (kFrameSizeInBytes * 8); + links.egress_link.sut_interface_bits_per_second / (kFrameSizeInBytes * 8); const int kNumTrafficItemsPerLink = kBufferParametersByQueueName.size(); const auto kTrafficItemSpeed = ixia::FramesPerSecond{kTotalFramesPerSecond / kNumTrafficItemsPerLink}; diff --git a/tests/qos/frontpanel_qos_test.h b/tests/qos/frontpanel_qos_test.h index 3bba090d..04340d0e 100644 --- a/tests/qos/frontpanel_qos_test.h +++ b/tests/qos/frontpanel_qos_test.h @@ -24,7 +24,6 @@ #include "p4/config/v1/p4info.pb.h" #include "tests/qos/qos_test_util.h" -#include "thinkit/generic_testbed.h" #include "thinkit/generic_testbed_fixture.h" namespace pins_test { @@ -32,9 +31,19 @@ namespace pins_test { // Parameters used by the tests. The fixture is *not* parameterized over a gNMI // config and assumes that the switch is preconfigured and the testbed ports // are up. +// We can optionally pass in `ingress_ports` and `egress_port_under_test` if +// we would like the test to use specific ports. +// `ingress_ports`: 2 Ingress port connected to traffic generator for injecting +// traffic +// `egress_port_under_test`: Egress port under test to validate egress QoS. +// Each `ingress_port` speed needs to be at least the speed of `egress_port` +// as the tests require oversubscription of egress port. + struct QosTestParams { thinkit::GenericTestbedInterface* testbed_interface; p4::config::v1::P4Info p4info; + std::string ingress_ports[2]; + std::string egress_port_under_test; }; class FrontpanelQosTest : public testing::TestWithParam { @@ -52,8 +61,7 @@ enum BufferConfigToBeTested { // Parameters used by the tests. struct BufferTestParams { - thinkit::GenericTestbedInterface *testbed_interface; - p4::config::v1::P4Info p4info; + QosTestParams default_params; // Buffer configurations to be applied on queues before test is run. absl::flat_hash_map buffer_parameters_by_queue_name; @@ -62,9 +70,15 @@ struct BufferTestParams { class FrontpanelBufferTest : public testing::TestWithParam { protected: - void SetUp() override { GetParam().testbed_interface->SetUp(); } - void TearDown() override { GetParam().testbed_interface->TearDown(); } - ~FrontpanelBufferTest() override { delete GetParam().testbed_interface; } + void SetUp() override { + GetParam().default_params.testbed_interface->SetUp(); + } + void TearDown() override { + GetParam().default_params.testbed_interface->TearDown(); + } + ~FrontpanelBufferTest() override { + delete GetParam().default_params.testbed_interface; + } }; } // namespace pins_test From f28e0df0329331fe4d60035fbe041dc759b68bed Mon Sep 17 00:00:00 2001 From: kishanps Date: Thu, 16 Jan 2025 14:23:56 +0000 Subject: [PATCH 2/3] [Comb] Move utilities to common libraries, Pull logic for buffer configuration into helper function, Migrate some users to ReadStreamChannelResponsesAndFinish, Toggle port speed before test, Adding packet, Add tolerance for Queue stats check, Renamed Packet to PacketAtPort, L3 admit tests should choose only ports that are UP & Update L3 Admit Test to not rely on a custom GNMI Config. --- tests/forwarding/BUILD.bazel | 14 +- tests/forwarding/l3_admit_test.cc | 295 +++++++++++++++++------------- tests/forwarding/l3_admit_test.h | 52 ++++-- tests/forwarding/packet_at_port.h | 47 +++++ tests/qos/BUILD.bazel | 2 + tests/qos/cpu_qos_test.cc | 79 ++++---- tests/qos/frontpanel_qos_test.cc | 286 +++++++++++------------------ tests/qos/qos_test_util.cc | 97 ---------- tests/qos/qos_test_util.h | 43 ----- 9 files changed, 413 insertions(+), 502 deletions(-) create mode 100644 tests/forwarding/packet_at_port.h diff --git a/tests/forwarding/BUILD.bazel b/tests/forwarding/BUILD.bazel index 5255b4ec..25f695bc 100644 --- a/tests/forwarding/BUILD.bazel +++ b/tests/forwarding/BUILD.bazel @@ -205,7 +205,6 @@ cc_library( srcs = ["l3_admit_test.cc"], hdrs = ["l3_admit_test.h"], deps = [ - ":mirror_blackbox_test_fixture", ":util", "//gutil:proto", "//gutil:status_matchers", @@ -215,16 +214,17 @@ cc_library( "//p4_pdpi:p4_runtime_session", "//p4_pdpi/packetlib", "//p4_pdpi/packetlib:packetlib_cc_proto", - "//sai_p4/instantiations/google:instantiations", - "//sai_p4/instantiations/google:sai_p4info_cc", "//tests/lib:p4info_helper", "//tests/lib:p4rt_fixed_table_programming_helper", "//tests/lib:packet_in_helper", + "//tests/lib:switch_test_setup_helpers", "//thinkit:mirror_testbed_fixture", + "//thinkit:switch", "@com_github_google_glog//:glog", "@com_github_p4lang_p4runtime//:p4info_cc_proto", "@com_github_p4lang_p4runtime//:p4runtime_cc_proto", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/time", @@ -348,3 +348,11 @@ cc_library( "@com_google_googletest//:gtest", ], ) + +cc_library( + name = "packet_at_port", + hdrs = [ + "packet_at_port.h", + ], + deps = ["@com_google_absl//absl/strings"], +) diff --git a/tests/forwarding/l3_admit_test.cc b/tests/forwarding/l3_admit_test.cc index 9b50e4cd..2c0449bc 100644 --- a/tests/forwarding/l3_admit_test.cc +++ b/tests/forwarding/l3_admit_test.cc @@ -19,6 +19,7 @@ #include #include "absl/status/status.h" +#include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" @@ -26,9 +27,7 @@ #include "absl/time/clock.h" #include "absl/time/time.h" #include "glog/logging.h" -#include "gmock/gmock.h" #include "gutil/proto.h" -#include "gtest/gtest.h" #include "gutil/status_matchers.h" #include "lib/gnmi/gnmi_helper.h" #include "p4/v1/p4runtime.pb.h" @@ -42,6 +41,9 @@ #include "tests/lib/p4rt_fixed_table_programming_helper.h" #include "tests/lib/packet_in_helper.h" #include "thinkit/mirror_testbed_fixture.h" +#include "thinkit/switch.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" namespace pins { namespace { @@ -265,41 +267,63 @@ absl::Status SendUdpPacket(pdpi::P4RuntimeSession& session, return absl::OkStatus(); } +absl::StatusOr> +GetNUpInterfaceIDs(thinkit::Switch &device, int num_interfaces) { + // The test fixture pushes a new config during setup so we give the switch a + // few minutes to converge before failing to report no valid ports. + auto stop_time = absl::Now() + absl::Minutes(3); + absl::StatusOr> result; + do { + ASSIGN_OR_RETURN(auto gnmi_stub, device.CreateGnmiStub()); + result = pins_test::GetNUpInterfacePortIds(*gnmi_stub, num_interfaces); + } while (!result.ok() && absl::Now() < stop_time); + return result; +} + } // namespace -TEST_P(L3AdmitTestFixture, - DISABLED_L3PacketsAreRoutedOnlyWhenMacAddressIsInMyStation) { +TEST_P(L3AdmitTestFixture, L3PacketsAreRoutedOnlyWhenMacAddressIsInMyStation) { + // Get SUT and control ports to test on. + ASSERT_OK_AND_ASSIGN( + auto sut_ports, + GetNUpInterfaceIDs(GetParam().testbed_interface->GetMirrorTestbed().Sut(), + 1)); + ASSERT_OK_AND_ASSIGN( + auto control_ports, + GetNUpInterfaceIDs( + GetParam().testbed_interface->GetMirrorTestbed().ControlSwitch(), 1)); + // Punt all traffic arriving at the control switch, and collect them to verify // forwarding. std::unique_ptr packetio_control = - std::make_unique(&GetControlP4RuntimeSession(), + std::make_unique(control_switch_p4rt_session_.get(), PacketInHelper::NoFilter); ASSERT_OK( - PuntAllPacketsToController(GetControlP4RuntimeSession(), sai::GetIrP4Info(sai::Instantiation::kMiddleblock))); - + PuntAllPacketsToController(*control_switch_p4rt_session_, ir_p4info_)); + // Add an L3 route to enable forwarding. L3Route l3_route{ .vrf_id = "vrf-1", .switch_mac = "00:00:00:00:00:01", .switch_ip = std::make_pair("10.0.0.1", 32), - .peer_port = "1", + .peer_port = sut_ports[0], .peer_mac = "00:00:00:00:00:02", .peer_ip = "fe80::2", .router_interface_id = "rif-1", .nexthop_id = "nexthop-1", }; - ASSERT_OK(AddAndSetDefaultVrf(GetSutP4RuntimeSession(), sai::GetIrP4Info(sai::Instantiation::kMiddleblock), - l3_route.vrf_id)); - ASSERT_OK(AddL3Route(GetSutP4RuntimeSession(), sai::GetIrP4Info(sai::Instantiation::kMiddleblock), l3_route)); - + ASSERT_OK( + AddAndSetDefaultVrf(*sut_p4rt_session_, ir_p4info_, l3_route.vrf_id)); + ASSERT_OK(AddL3Route(*sut_p4rt_session_, ir_p4info_, l3_route)); + // Admit only 1 MAC address to the forwaring pipeline. ASSERT_OK(AdmitL3Route( - GetSutP4RuntimeSession(), sai::GetIrP4Info(sai::Instantiation::kMiddleblock), + *sut_p4rt_session_, ir_p4info_, L3AdmitOptions{ .priority = 2070, .dst_mac = std ::make_pair("00:01:02:03:04:05", "FF:FF:FF:FF:FF:FF"), })); - + // Send 2 sets of packets to the switch. The first set of packets should not // match the L3 admit MAC and therefore will be dropped. The second set of // packet should match the L3 admit MAC and therefore get forwarded. @@ -308,21 +332,19 @@ TEST_P(L3AdmitTestFixture, // Send the "bad" packets first to give them the most time. const std::string kBadPayload = "Testing L3 forwarding. This packet should be dropped."; - ASSERT_OK(SendUdpPacket(GetControlP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), - /*port_id=*/"1", kNumberOfTestPacket, + ASSERT_OK(SendUdpPacket(*control_switch_p4rt_session_, ir_p4info_, + control_ports[0], kNumberOfTestPacket, /*dst_mac=*/"00:aa:bb:cc:cc:dd", /*dst_ip=*/"10.0.0.1", kBadPayload)); - + // Then send the "good" packets. const std::string kGoodPayload = "Testing L3 forwarding. This packet should arrive to packet in."; - ASSERT_OK(SendUdpPacket(GetControlP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), - /*port_id=*/"1", kNumberOfTestPacket, + ASSERT_OK(SendUdpPacket(*control_switch_p4rt_session_, ir_p4info_, + control_ports[0], kNumberOfTestPacket, /*dst_mac=*/"00:01:02:03:04:05", /*dst_ip=*/"10.0.0.1", kGoodPayload)); - + absl::Time timeout = absl::Now() + absl::Minutes(1); int good_packet_count = 0; int bad_packet_count = 0; @@ -357,37 +379,42 @@ TEST_P(L3AdmitTestFixture, } TEST_P(L3AdmitTestFixture, L3AdmitCanUseMaskToAllowMultipleMacAddresses) { + // Get SUT and control ports to test on. + ASSERT_OK_AND_ASSIGN( + auto sut_ports, + GetNUpInterfaceIDs(GetParam().testbed_interface->GetMirrorTestbed().Sut(), + 1)); + ASSERT_OK_AND_ASSIGN( + auto control_ports, + GetNUpInterfaceIDs( + GetParam().testbed_interface->GetMirrorTestbed().ControlSwitch(), 1)); + // Punt all traffic arriving at the control switch, and collect them to verify // forwarding. std::unique_ptr packetio_control = - std::make_unique(&GetControlP4RuntimeSession(), + std::make_unique(control_switch_p4rt_session_.get(), PacketInHelper::NoFilter); ASSERT_OK( - PuntAllPacketsToController(GetControlP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock))); + PuntAllPacketsToController(*control_switch_p4rt_session_, ir_p4info_)); // Add an L3 route to enable forwarding. L3Route l3_route{ .vrf_id = "vrf-1", .switch_mac = "00:00:00:00:00:01", .switch_ip = std::make_pair("10.0.0.1", 32), - .peer_port = "1", + .peer_port = sut_ports[0], .peer_mac = "00:00:00:00:00:02", .peer_ip = "fe80::2", .router_interface_id = "rif-1", .nexthop_id = "nexthop-1", }; - - ASSERT_OK(AddAndSetDefaultVrf(GetSutP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), - l3_route.vrf_id)); - ASSERT_OK(AddL3Route(GetSutP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), - l3_route)); + ASSERT_OK( + AddAndSetDefaultVrf(*sut_p4rt_session_, ir_p4info_, l3_route.vrf_id)); + ASSERT_OK(AddL3Route(*sut_p4rt_session_, ir_p4info_, l3_route)); // Admit multiple MAC addresses into L3 routing with a single L3 admit rule. ASSERT_OK(AdmitL3Route( - GetSutP4RuntimeSession(), sai::GetIrP4Info(sai::Instantiation::kMiddleblock), + *sut_p4rt_session_, ir_p4info_, L3AdmitOptions{ .priority = 2070, .dst_mac = std ::make_pair("00:01:02:03:00:05", "FF:FF:FF:FF:F0:FF"), @@ -400,9 +427,8 @@ TEST_P(L3AdmitTestFixture, L3AdmitCanUseMaskToAllowMultipleMacAddresses) { "Testing L3 forwarding. This packet should arrive to packet in."; for (int i = 0; i < 5; ++i) { std::string dst_mac = absl::StrFormat("00:01:02:03:%02d:05", i); - ASSERT_OK(SendUdpPacket(GetControlP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), - /*port_id=*/"1", kNumberOfTestPacket, dst_mac, + ASSERT_OK(SendUdpPacket(*control_switch_p4rt_session_, ir_p4info_, + control_ports[0], kNumberOfTestPacket, dst_mac, /*dst_ip=*/"10.0.0.1", kGoodPayload)); } @@ -435,42 +461,47 @@ TEST_P(L3AdmitTestFixture, L3AdmitCanUseMaskToAllowMultipleMacAddresses) { EXPECT_EQ(good_packet_count, 5 * kNumberOfTestPacket); } -TEST_P(L3AdmitTestFixture, DISABLED_L3AdmitCanUseInPortToRestrictMacAddresses) { +TEST_P(L3AdmitTestFixture, L3AdmitCanUseInPortToRestrictMacAddresses) { + // Get SUT and control ports to test on. + ASSERT_OK_AND_ASSIGN( + auto sut_ports, + GetNUpInterfaceIDs(GetParam().testbed_interface->GetMirrorTestbed().Sut(), + 1)); + ASSERT_OK_AND_ASSIGN( + auto control_ports, + GetNUpInterfaceIDs( + GetParam().testbed_interface->GetMirrorTestbed().ControlSwitch(), 2)); + // Punt all traffic arriving at the control switch, and collect them to verify // forwarding. std::unique_ptr packetio_control = - std::make_unique(&GetControlP4RuntimeSession(), + std::make_unique(control_switch_p4rt_session_.get(), PacketInHelper::NoFilter); ASSERT_OK( - PuntAllPacketsToController(GetControlP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock))); + PuntAllPacketsToController(*control_switch_p4rt_session_, ir_p4info_)); // Add an L3 route to enable forwarding. L3Route l3_route{ .vrf_id = "vrf-1", .switch_mac = "00:00:00:00:00:01", .switch_ip = std::make_pair("10.0.0.1", 32), - .peer_port = "1", + .peer_port = sut_ports[0], .peer_mac = "00:00:00:00:00:02", .peer_ip = "fe80::2", .router_interface_id = "rif-1", .nexthop_id = "nexthop-1", }; - ASSERT_OK(AddAndSetDefaultVrf(GetSutP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), - l3_route.vrf_id)); - ASSERT_OK(AddL3Route(GetSutP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), - l3_route)); + ASSERT_OK( + AddAndSetDefaultVrf(*sut_p4rt_session_, ir_p4info_, l3_route.vrf_id)); + ASSERT_OK(AddL3Route(*sut_p4rt_session_, ir_p4info_, l3_route)); // Admit the MAC addresses only on port XYZ ASSERT_OK(AdmitL3Route( - GetSutP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), + *sut_p4rt_session_, ir_p4info_, L3AdmitOptions{ .priority = 2070, .dst_mac = std ::make_pair("00:01:02:03:00:05", "FF:FF:FF:FF:F0:FF"), - .in_port = "2", + .in_port = control_ports[0], })); // Send 2 sets of packets to the switch. The first set of packets should not @@ -481,18 +512,16 @@ TEST_P(L3AdmitTestFixture, DISABLED_L3AdmitCanUseInPortToRestrictMacAddresses) { // Send the "bad" packets first to give them the most time. const std::string kBadPayload = "Testing L3 forwarding. This packet should be dropped."; - ASSERT_OK(SendUdpPacket(GetControlP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), - /*port_id=*/"1", kNumberOfTestPacket, + ASSERT_OK(SendUdpPacket(*control_switch_p4rt_session_, ir_p4info_, + control_ports[1], kNumberOfTestPacket, /*dst_mac=*/"00:01:02:03:04:05", /*dst_ip=*/"10.0.0.1", kBadPayload)); - + // Then send the "good" packets. const std::string kGoodPayload = "Testing L3 forwarding. This packet should arrive to packet in."; - ASSERT_OK(SendUdpPacket(GetControlP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), - /*port_id=*/"2", kNumberOfTestPacket, + ASSERT_OK(SendUdpPacket(*control_switch_p4rt_session_, ir_p4info_, + control_ports[0], kNumberOfTestPacket, /*dst_mac=*/"00:01:02:03:04:05", /*dst_ip=*/"10.0.0.1", kGoodPayload)); @@ -531,67 +560,69 @@ TEST_P(L3AdmitTestFixture, DISABLED_L3AdmitCanUseInPortToRestrictMacAddresses) { } TEST_P(L3AdmitTestFixture, L3PacketsCanBeRoutedWithOnlyARouterInterface) { + // Only use 1 port because for the router interface L3 admit behavior to work + // the incomming packet needs to match the outgoing port. + ASSERT_OK_AND_ASSIGN( + auto sut_ports, + GetNUpInterfaceIDs(GetParam().testbed_interface->GetMirrorTestbed().Sut(), + 1)); + // Punt all traffic arriving at the control switch, and collect them to verify // forwarding. std::unique_ptr packetio_control = - std::make_unique(&GetControlP4RuntimeSession(), + std::make_unique(control_switch_p4rt_session_.get(), PacketInHelper::NoFilter); -ASSERT_OK( - PuntAllPacketsToController(GetControlP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock))); + ASSERT_OK( + PuntAllPacketsToController(*control_switch_p4rt_session_, ir_p4info_)); -// Add an L3 route to enable forwarding, but do not add an explicit L3Admit + // Add an L3 route to enable forwarding, but do not add an explicit L3Admit // rule. -L3Route l3_route{ - .vrf_id = "vrf-1", - .switch_mac = "00:00:00:00:00:01", - .switch_ip = std::make_pair("10.0.0.1", 32), - .peer_port = "1", - .peer_mac = "00:00:00:00:00:02", - .peer_ip = "fe80::2", - .router_interface_id = "rif-1", - .nexthop_id = "nexthop-1", -}; -ASSERT_OK(AddAndSetDefaultVrf( - GetSutP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), l3_route.vrf_id)); -ASSERT_OK(AddL3Route(GetSutP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), - l3_route)); - -// Send 1 set of packets to the switch using the switch's MAC address from the -// L3 route. -const int kNumberOfTestPacket = 100; -const std::string kGoodPayload = - "Testing L3 forwarding. This packet should arrive to packet in."; -ASSERT_OK(SendUdpPacket(GetControlP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), - /*port_id=*/"1", kNumberOfTestPacket, - /*dst_mac=*/"00:00:00:00:00:01", - /*dst_ip=*/"10.0.0.1", kGoodPayload)); - -absl::Time timeout = absl::Now() + absl::Minutes(1); -int good_packet_count = 0; -while (good_packet_count < kNumberOfTestPacket) { - if (packetio_control->HasPacketInMessage()) { - ASSERT_OK_AND_ASSIGN(p4::v1::StreamMessageResponse response, - packetio_control->GetNextPacketInMessage()); - - // Verify this is the packet we expect. - packetlib::Packet packet_in = - packetlib::ParsePacket(response.packet().payload()); - if (response.update_case() == p4::v1::StreamMessageResponse::kPacket && - absl::StrContains(packet_in.payload(), kGoodPayload)) { - ++good_packet_count; - } else { - LOG(WARNING) << "Unexpected response: " << response.DebugString(); + L3Route l3_route{ + .vrf_id = "vrf-1", + .switch_mac = "00:00:00:00:00:01", + .switch_ip = std::make_pair("10.0.0.1", 32), + .peer_port = sut_ports[0], + .peer_mac = "00:00:00:00:00:02", + .peer_ip = "fe80::2", + .router_interface_id = "rif-1", + .nexthop_id = "nexthop-1", + }; + ASSERT_OK( + AddAndSetDefaultVrf(*sut_p4rt_session_, ir_p4info_, l3_route.vrf_id)); + ASSERT_OK(AddL3Route(*sut_p4rt_session_, ir_p4info_, l3_route)); + + // Send 1 set of packets to the switch using the switch's MAC address from the + // L3 route. + const int kNumberOfTestPacket = 100; + const std::string kGoodPayload = + "Testing L3 forwarding. This packet should arrive to packet in."; + ASSERT_OK(SendUdpPacket(*control_switch_p4rt_session_, ir_p4info_, + sut_ports[0], kNumberOfTestPacket, + /*dst_mac=*/"00:00:00:00:00:01", + /*dst_ip=*/"10.0.0.1", kGoodPayload)); + + absl::Time timeout = absl::Now() + absl::Minutes(1); + int good_packet_count = 0; + while (good_packet_count < kNumberOfTestPacket) { + if (packetio_control->HasPacketInMessage()) { + ASSERT_OK_AND_ASSIGN(p4::v1::StreamMessageResponse response, + packetio_control->GetNextPacketInMessage()); + + // Verify this is the packet we expect. + packetlib::Packet packet_in = + packetlib::ParsePacket(response.packet().payload()); + if (response.update_case() == p4::v1::StreamMessageResponse::kPacket && + absl::StrContains(packet_in.payload(), kGoodPayload)) { + ++good_packet_count; + } else { + LOG(WARNING) << "Unexpected response: " << response.DebugString(); + } } - } - if (absl::Now() > timeout) { - LOG(ERROR) << "Reached timeout waiting on packets to arrive."; - break; - } + if (absl::Now() > timeout) { + LOG(ERROR) << "Reached timeout waiting on packets to arrive."; + break; + } } LOG(INFO) << "Done collecting packets."; @@ -601,20 +632,28 @@ while (good_packet_count < kNumberOfTestPacket) { TEST_P(L3AdmitTestFixture, L3PacketsCanBeClassifiedByDestinationMac) { // Only run this test if the ACL_PRE_INGRESS table supports matching on // DST_MAC. - if (!TableHasMatchField(sai::GetIrP4Info(sai::Instantiation::kMiddleblock), - "acl_pre_ingress_table", "dst_mac")) { + if (!TableHasMatchField(ir_p4info_, "acl_pre_ingress_table", "dst_mac")) { GTEST_SKIP() << "Skipping because ACL_PRE_INGRESS table can not match on DST_MAC."; } + // Get SUT and control ports to test on. + ASSERT_OK_AND_ASSIGN( + auto sut_ports, + GetNUpInterfaceIDs(GetParam().testbed_interface->GetMirrorTestbed().Sut(), + 1)); + ASSERT_OK_AND_ASSIGN( + auto control_ports, + GetNUpInterfaceIDs( + GetParam().testbed_interface->GetMirrorTestbed().ControlSwitch(), 1)); + // Punt all traffic arriving at the control switch, and collect them to verify // forwarding. std::unique_ptr packetio_control = - std::make_unique(&GetControlP4RuntimeSession(), + std::make_unique(control_switch_p4rt_session_.get(), PacketInHelper::NoFilter); ASSERT_OK( - PuntAllPacketsToController(GetControlP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock))); + PuntAllPacketsToController(*control_switch_p4rt_session_, ir_p4info_)); // This test uses 2 MAC addresses. Both will be admitted to L3 routing, but // only one will get assigned a VRF ID. We expect packets receiving the @@ -623,19 +662,16 @@ TEST_P(L3AdmitTestFixture, L3PacketsCanBeClassifiedByDestinationMac) { std::string vrf_id = "vrf-1"; std::string good_dst_mac = "00:00:00:00:00:03"; std::string drop_dst_mac = "00:00:00:00:00:04"; - ASSERT_OK(AllowVrfTrafficToDstMac(GetSutP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), + ASSERT_OK(AllowVrfTrafficToDstMac(*sut_p4rt_session_, ir_p4info_, good_dst_mac, vrf_id)); ASSERT_OK(AdmitL3Route( - GetSutP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), + *sut_p4rt_session_, ir_p4info_, L3AdmitOptions{ .priority = 2070, .dst_mac = std ::make_pair(good_dst_mac, "FF:FF:FF:FF:FF:FF"), })); ASSERT_OK(AdmitL3Route( - GetSutP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), + *sut_p4rt_session_, ir_p4info_, L3AdmitOptions{ .priority = 2070, .dst_mac = std ::make_pair(drop_dst_mac, "FF:FF:FF:FF:FF:FF"), @@ -646,14 +682,13 @@ TEST_P(L3AdmitTestFixture, L3PacketsCanBeClassifiedByDestinationMac) { .vrf_id = vrf_id, .switch_mac = "00:00:00:00:00:01", .switch_ip = std::make_pair("10.0.0.1", 32), - .peer_port = "1", + .peer_port = sut_ports[0], .peer_mac = "00:00:00:00:00:02", .peer_ip = "fe80::2", .router_interface_id = "rif-1", .nexthop_id = "nexthop-1", }; - ASSERT_OK(AddL3Route(GetSutP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), l3_route)); + ASSERT_OK(AddL3Route(*sut_p4rt_session_, ir_p4info_, l3_route)); // Send 2 set of packets to the switch. One using the expected destination // MAC (gets forwarded), and another using an unexpected destination MAC @@ -665,13 +700,11 @@ TEST_P(L3AdmitTestFixture, L3PacketsCanBeClassifiedByDestinationMac) { "Testing L3 forwarding. This packet should be dropped."; // Send the "bad" packets first to give them the most time. - ASSERT_OK(SendUdpPacket(GetControlP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), - /*port_id=*/"1", kNumberOfTestPacket, drop_dst_mac, + ASSERT_OK(SendUdpPacket(*control_switch_p4rt_session_, ir_p4info_, + control_ports[0], kNumberOfTestPacket, drop_dst_mac, /*dst_ip=*/"10.0.0.1", kBadPayload)); - ASSERT_OK(SendUdpPacket(GetControlP4RuntimeSession(), - sai::GetIrP4Info(sai::Instantiation::kMiddleblock), - /*port_id=*/"1", kNumberOfTestPacket, good_dst_mac, + ASSERT_OK(SendUdpPacket(*control_switch_p4rt_session_, ir_p4info_, + control_ports[0], kNumberOfTestPacket, good_dst_mac, /*dst_ip=*/"10.0.0.1", kGoodPayload)); // Wait for all the good packets to get punted back on the control switch. diff --git a/tests/forwarding/l3_admit_test.h b/tests/forwarding/l3_admit_test.h index 631c7bb7..b5a8142e 100644 --- a/tests/forwarding/l3_admit_test.h +++ b/tests/forwarding/l3_admit_test.h @@ -15,28 +15,52 @@ #define PINS_TESTS_FORWARDING_L3_ADMIT_TEST_H_ #include +#include +#include +#include "gutil/status_matchers.h" #include "p4/config/v1/p4info.pb.h" +#include "p4_pdpi/ir.h" #include "p4_pdpi/ir.pb.h" -#include "p4_pdpi/p4_runtime_session.h" -#include "sai_p4/instantiations/google/instantiations.h" -#include "sai_p4/instantiations/google/sai_p4info.h" -#include "tests/forwarding/mirror_blackbox_test_fixture.h" -#include "tests/lib/packet_in_helper.h" +#include "tests/lib/switch_test_setup_helpers.h" #include "thinkit/mirror_testbed_fixture.h" +#include "gmock/gmock.h" namespace pins { -class L3AdmitTestFixture : public pins_test::MirrorBlackboxTestFixture { - protected: - void TearDown() override { - // MirrorBlackboxTestFixture unnecessarily clears tables at TearDown. This - // is not harmful for other tests but is problematic for l3_admit_tests - // since the P4RT session to the controller is closed during the tests (see - // lib/packet_in_helper.h). Therefore, We bypass table clearance in - // TearDown. - MirrorTestbedFixture::TearDown(); +struct L3AdmitTestParams { + thinkit::MirrorTestbedInterface *testbed_interface; + p4::config::v1::P4Info p4info; +}; + +// This test assumes that the switch is set up with a gNMI config. +class L3AdmitTestFixture : public testing::TestWithParam { +protected: + void SetUp() override { + GetParam().testbed_interface->SetUp(); + + // Initialize the connection and clear table entries for the SUT and Control + // switch. + ASSERT_OK_AND_ASSIGN( + std::tie(sut_p4rt_session_, control_switch_p4rt_session_), + pins_test::ConfigureSwitchPairAndReturnP4RuntimeSessionPair( + GetParam().testbed_interface->GetMirrorTestbed().Sut(), + GetParam().testbed_interface->GetMirrorTestbed().ControlSwitch(), + /*gnmi_config=*/std::nullopt, GetParam().p4info)); + + ASSERT_OK_AND_ASSIGN(ir_p4info_, pdpi::CreateIrP4Info(GetParam().p4info)); } + + void TearDown() override { GetParam().testbed_interface->TearDown(); } + + ~L3AdmitTestFixture() override { delete GetParam().testbed_interface; } + + // This test runs on a mirror testbed setup so we open a P4RT connection to + // both switches. + std::unique_ptr sut_p4rt_session_; + std::unique_ptr control_switch_p4rt_session_; + + pdpi::IrP4Info ir_p4info_; }; } // namespace pins diff --git a/tests/forwarding/packet_at_port.h b/tests/forwarding/packet_at_port.h new file mode 100644 index 00000000..42b56bf6 --- /dev/null +++ b/tests/forwarding/packet_at_port.h @@ -0,0 +1,47 @@ +#ifndef PINS_TESTS_FORWARDING_PACKET_H_ +#define PINS_TESTS_FORWARDING_PACKET_H_ + +#include +#include +#include +#include +#include + +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" + +namespace pins { + +// A packet, represented by the packet's data and the port that it was +// sent/received on. +struct PacketAtPort { + // TODO Change it to string type port. + int port; + std::string data; + + bool operator==(const PacketAtPort& other) const { + return port == other.port && data == other.data; + } + + bool operator<(const PacketAtPort& other) const { + return std::tie(port, data) < std::tie(other.port, other.data); + } + + template + friend H AbslHashValue(H h, const PacketAtPort& m); +}; + +inline std::ostream& operator<<(std::ostream& os, + const ::pins::PacketAtPort& packet) { + return os << "{port: " << packet.port << " length: " << packet.data.size() + << " data: \"" << absl::BytesToHexString(packet.data) << "\"}"; +} + +template +H AbslHashValue(H h, const PacketAtPort& m) { + return H::combine(std::move(h), m.port, m.data); +} + +} // namespace pins + +#endif // PINS_TESTS_FORWARDING_PACKET_H_ diff --git a/tests/qos/BUILD.bazel b/tests/qos/BUILD.bazel index ca585b1b..27affe5f 100644 --- a/tests/qos/BUILD.bazel +++ b/tests/qos/BUILD.bazel @@ -104,6 +104,7 @@ cc_library( "//lib:ixia_helper_cc_proto", "//lib/gnmi:gnmi_helper", "//lib/utils:json_utils", + "//lib/validator:validator_lib", "//p4_pdpi:ir", "//p4_pdpi:ir_cc_proto", "//p4_pdpi:p4_runtime_session", @@ -119,6 +120,7 @@ cc_library( "//tests/lib:switch_test_setup_helpers", "//thinkit:generic_testbed", "//thinkit:generic_testbed_fixture", + "//thinkit:mirror_testbed", "@com_github_gnmi//proto/gnmi:gnmi_cc_grpc_proto", "@com_github_p4lang_p4runtime//:p4info_cc_proto", "@com_github_p4lang_p4runtime//:p4runtime_cc_proto", diff --git a/tests/qos/cpu_qos_test.cc b/tests/qos/cpu_qos_test.cc index f9f8256d..d1602726 100644 --- a/tests/qos/cpu_qos_test.cc +++ b/tests/qos/cpu_qos_test.cc @@ -715,6 +715,21 @@ TEST_P(CpuQosTestWithoutIxia, << "for injected test packet: " << packet.DebugString(); initial_cpu_queue_state = cpu_queue_state; } + + // Ensure tha the switch did not punt packets to the controller via P4RT. + ASSERT_OK_AND_ASSIGN(std::vector pi_responses, + sut_p4rt_session->ReadStreamChannelResponsesAndFinish()); + for (const auto &pi_response : pi_responses) { + sai::PacketIn pd_packet; + EXPECT_OK(pdpi::PiPacketInToPd(ir_p4info, pi_response.packet(), &pd_packet)) + << "where packet = " << pi_response.packet().DebugString(); + ADD_FAILURE() << "SUT punted the following packet to the controller " + "via P4Runtime: " + << (pd_packet.ByteSizeLong() == 0 // Translation failed. + ? pi_response.packet().DebugString() + : pd_packet.DebugString()); + } + LOG(INFO) << "-- END OF TEST -----------------------------------------------"; } @@ -840,38 +855,15 @@ TEST_P(CpuQosTestWithoutIxia, PuntToCpuWithVlanTag) { pdpi::PartialPdTableEntryToPiTableEntry(ir_p4info, pd_acl_entry)); ASSERT_OK(pdpi::InstallPiTableEntry(sut_p4rt_session.get(), pi_acl_entry)); - struct VlanTestPacketCounters { - absl::Mutex mutex; - int num_vlan_packets_received ABSL_GUARDED_BY(mutex) = 0; - }; - - VlanTestPacketCounters packet_receive_info; - PacketInReceiver sut_packet_receiver( - *sut_p4rt_session, [&](const p4::v1::StreamMessageResponse pi_response) { - sai::StreamMessageResponse pd_response; - ASSERT_OK(pdpi::PiStreamMessageResponseToPd(ir_p4info, pi_response, - &pd_response)) - << " packet in PI to PD failed: " << pi_response.DebugString(); - ASSERT_TRUE(pd_response.has_packet()) - << " received unexpected stream message for packet in: " - << pd_response.DebugString(); - packetlib::Packet packet = - packetlib::ParsePacket(pd_response.packet().payload()); - EXPECT_EQ(packet.headers(1).vlan_header().vlan_identifier(), - ipv4_packet.headers(1).vlan_header().vlan_identifier()); - absl::MutexLock lock(&packet_receive_info.mutex); - packet_receive_info.num_vlan_packets_received++; - }); - - for (auto test_packet : test_packets) { - { - absl::MutexLock lock(&packet_receive_info.mutex); - packet_receive_info.num_vlan_packets_received = 0; - } + for (packetlib::Packet &test_packet : test_packets) { + // Start from fresh P4RT session. + ASSERT_OK_AND_ASSIGN(sut_p4rt_session, pdpi::P4RuntimeSession::Create(sut)); + // Send packets. + ASSERT_OK(packetlib::PadPacketToMinimumSize(test_packet)); + ASSERT_OK(packetlib::UpdateAllComputedFields(test_packet)); ASSERT_OK_AND_ASSIGN(const std::string raw_packet, packetlib::SerializePacket(test_packet)); - const int kPacketCount = 10; for (int iter = 0; iter < kPacketCount; iter++) { ASSERT_OK(pins::InjectEgressPacket( @@ -880,15 +872,26 @@ TEST_P(CpuQosTestWithoutIxia, PuntToCpuWithVlanTag) { /*packet_delay=*/std::nullopt)); } - ASSERT_OK(pins::TryUpToNTimes(10, /*delay=*/absl::Seconds(1), [&] { - absl::MutexLock lock(&packet_receive_info.mutex); - if (packet_receive_info.num_vlan_packets_received == kPacketCount) { - return absl::OkStatus(); - } - return absl::InternalError(absl::StrCat( - "Received packets: ", packet_receive_info.num_vlan_packets_received, - "Expected packets", kPacketCount)); - })); + // Verify we receive expected packets back. + absl::SleepFor(absl::Seconds(1)); + int num_vlan_packets_received = 0; + ASSERT_OK_AND_ASSIGN( + std::vector pi_responses, + sut_p4rt_session->ReadStreamChannelResponsesAndFinish()); + for (const auto &pi_response : pi_responses) { + sai::StreamMessageResponse pd_response; + ASSERT_OK(pdpi::PiStreamMessageResponseToPd(ir_p4info, pi_response, + &pd_response)) + << " packet in PI to PD failed: " << pi_response.DebugString(); + ASSERT_TRUE(pd_response.has_packet()) + << " received unexpected stream message for packet in: " + << pd_response.DebugString(); + packetlib::Packet punted_packet = + packetlib::ParsePacket(pd_response.packet().payload()); + EXPECT_THAT(punted_packet, EqualsProto(test_packet)); + num_vlan_packets_received += 1; + } + EXPECT_EQ(num_vlan_packets_received, kPacketCount); } LOG(INFO) << "-- END OF TEST -----------------------------------------------"; } diff --git a/tests/qos/frontpanel_qos_test.cc b/tests/qos/frontpanel_qos_test.cc index b690c4d0..df322306 100644 --- a/tests/qos/frontpanel_qos_test.cc +++ b/tests/qos/frontpanel_qos_test.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include // NOLINT #include +#include #include #include "absl/algorithm/container.h" @@ -53,6 +55,7 @@ #include "lib/ixia_helper.h" #include "lib/ixia_helper.pb.h" #include "lib/utils/json_utils.h" +#include "lib/validator/validator_lib.h" #include "p4/v1/p4runtime.pb.h" #include "p4_pdpi/internal/ordered_map.h" #include "p4_pdpi/ir.h" @@ -71,6 +74,7 @@ #include "tests/qos/packet_in_receiver.h" #include "tests/qos/qos_test_util.h" #include "thinkit/generic_testbed.h" +#include "thinkit/mirror_testbed.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -93,22 +97,48 @@ template std::string ToString(const T &t) { return ss.str(); } -// Connects to Ixia on the given testbed and returns a string handle identifying -// the connection (aka "topology ref"). -absl::StatusOr ConnectToIxia(thinkit::GenericTestbed &testbed) { - ASSIGN_OR_RETURN(auto gnmi_stub, testbed.Sut().CreateGnmiStub()); - ASSIGN_OR_RETURN(std::vector ready_links, - GetReadyIxiaLinks(testbed, *gnmi_stub)); - if (ready_links.empty()) { - return gutil::UnavailableErrorBuilder() << "no Ixia-to-SUT link up"; +// Type of an object that performs some cleanup logic in its destructor. +using Cleanup = decltype(absl::Cleanup{std::function()}); + +// Modifies the buffer config of the given `egress_port` such that all ports +// fairly share all space dynamically, and return a `Cleanup` object that +// restores the previous config when the object is destructed. +absl::StatusOr ConfigureFairBufferConfigForPortUntilEndOfScope( + absl::string_view egress_port, gnmi::gNMI::StubInterface &gnmi) { + ASSIGN_OR_RETURN(const std::string kBufferProfileName, + GetBufferAllocationProfileByEgressPort(egress_port, gnmi)); + + // Before we update the buffer config, save the current config and + // prepare to restore it. + ASSIGN_OR_RETURN(const std::string kInitialBufferConfig, + GetBufferAllocationProfileConfig(kBufferProfileName, gnmi)); + Cleanup cleanup = absl::Cleanup(std::function([=, &gnmi] { + EXPECT_OK(UpdateBufferAllocationProfileConfig(kBufferProfileName, + kInitialBufferConfig, gnmi)) + << "failed to restore initial buffer config -- switch config may be " + "corrupted, causing subsequent tests to fail"; + })); + + // Set fair buffer config. + absl::flat_hash_map + buffer_config_by_queue_name; + // TODO: Read queue names from switch instead of + // hard-coding them here. + for (absl::string_view queue_name : + {"LLQ1", "LLQ2", "BE1", "AF1", "AF2", "AF3", "AF4", "NC1"}) { + buffer_config_by_queue_name[queue_name] = BufferParameters{ + .dedicated_buffer = 0, + .use_shared_buffer = true, + .shared_buffer_limit_type = + "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", + .dynamic_limit_scaling_factor = -3, + .shared_static_limit = 0, + }; } - absl::string_view ixia_interface = ready_links[0].ixia_interface; - ASSIGN_OR_RETURN(ixia::IxiaPortInfo ixia_port_info, - ixia::ExtractPortInfo(ixia_interface)); - ASSIGN_OR_RETURN( - std::string ixia_connection_handle, - pins_test::ixia::IxiaConnect(ixia_port_info.hostname, testbed)); - return ixia_connection_handle; + RETURN_IF_ERROR(SetBufferConfigParameters(kBufferProfileName, + buffer_config_by_queue_name, gnmi)); + + return std::move(cleanup); } // Installs the given table `entries` using the given P4Runtime session, @@ -395,6 +425,20 @@ GetIxiaLinks(thinkit::GenericTestbed &testbed, return links; } +// Returns a function that logs port debug information for both switches in a +// mirror testbed if the ports are down. +std::function +MakeLogPortDebugInfoFunction(thinkit::GenericTestbed &testbed, + absl::Span enabled_interfaces) { + return [&testbed, enabled_interfaces] { + if (absl::Status sut_port_status = + pins_test::PortsUp(testbed.Sut(), enabled_interfaces); + !sut_port_status.ok()) { + LOG(WARNING) << sut_port_status; + } + }; +} + // The purpose of this test is to verify that: // - Incoming IP packets are mapped to queues according to their DSCP. // - Queue peak information rates (PIRs) are enforced. @@ -544,12 +588,40 @@ TEST_P(FrontpanelQosTest, FormatJsonBestEffort(updated_scheduler_config))); // Connect to Ixia and fix global parameters. - ASSERT_OK_AND_ASSIGN(const std::string kIxiaHandle, ConnectToIxia(*testbed)); + ASSERT_OK_AND_ASSIGN(const std::string kIxiaHandle, + ixia::ConnectToIxia(*testbed)); ASSERT_OK_AND_ASSIGN(const std::string kIxiaSrcPortHandle, ixia::IxiaVport(kIxiaHandle, kIxiaSrcPort, *testbed)); ASSERT_OK_AND_ASSIGN(const std::string kIxiaDstPortHandle, ixia::IxiaVport(kIxiaHandle, kIxiaDstPort, *testbed)); + constexpr int64_t kSpeed400g = 400000000000; + constexpr int64_t kSpeed200g = 200000000000; + // Toggle speed to add coverage for b/246290651. + if (kSutEgressPortSpeedInBitsPerSecond != kSpeed200g) { + ASSERT_OK(SetPortSpeedInBitsPerSecond(PortSpeed(kSpeed200g), kSutEgressPort, + *gnmi_stub)); + ASSERT_OK(SetPortSpeedInBitsPerSecond( + PortSpeed(kSutEgressPortSpeedInBitsPerSecond), kSutEgressPort, + *gnmi_stub)); + } else { + ASSERT_OK(SetPortSpeedInBitsPerSecond(PortSpeed(kSpeed400g), kSutEgressPort, + *gnmi_stub)); + ASSERT_OK(SetPortSpeedInBitsPerSecond( + PortSpeed(kSutEgressPortSpeedInBitsPerSecond), kSutEgressPort, + *gnmi_stub)); + } + + std::vector required_interfaces = {kSutEgressPort}; + // Wait for all enabled interfaces to be up before sending packets. + ASSERT_OK(pins_test::OnFailure( + pins_test::WaitForCondition(pins_test::PortsUp, absl::Minutes(10), + testbed->Sut(), required_interfaces, + /*with_healthz=*/false), + MakeLogPortDebugInfoFunction(*testbed, required_interfaces))) + << "all required ports must be initialized on the SUT before sending " + "test packets"; + // Actual testing -- inject test IPv4 and IPv6 packets for each DSCP, and // check the behavior is as eexpted. constexpr int kMaxDscp = 63; @@ -674,15 +746,18 @@ TEST_P(FrontpanelQosTest, EXPECT_THAT(delta_counters, ResultOf(TotalPacketsForQueue, Ge(kIxiaTrafficStats.num_tx_frames()))); - // Protocol packets such as LLDP can be transmitted via queue during test. - // Add some tolerance to account for these packets. + // Protocol packets such as LLDP/NDv6 can be transmitted via queue during + // test. Add some tolerance to account for these packets. constexpr int kMaxToleratedExtraPackets = 5; EXPECT_THAT( delta_counters, ResultOf(TotalPacketsForQueue, Le(kIxiaTrafficStats.num_tx_frames() + kMaxToleratedExtraPackets))); EXPECT_THAT(delta_counters, Field(&QueueCounters::num_packets_transmitted, - Eq(kIxiaTrafficStats.num_rx_frames()))); + Ge(kIxiaTrafficStats.num_rx_frames()))); + EXPECT_THAT(delta_counters, Field(&QueueCounters::num_packets_transmitted, + Le(kIxiaTrafficStats.num_rx_frames() + + kMaxToleratedExtraPackets))); } } LOG(INFO) << "-- Test done -------------------------------------------------"; @@ -830,83 +905,11 @@ TEST_P(FrontpanelQosTest, WeightedRoundRobinWeightsAreRespected) { "corrupted, causing subsequent test to fail"; }); - // Save Buffer config and restore at end of the test. - ASSERT_OK_AND_ASSIGN( - const std::string kSutEgressPortBufferProfile, - GetBufferAllocationProfileByEgressPort(kSutEgressPort, *gnmi_stub)); - // Before we update the buffer config, save the current config and - // prepare to restore it at the end of the test. - ASSERT_OK_AND_ASSIGN(const std::string kInitialBufferConfig, - GetBufferAllocationProfileConfig( - kSutEgressPortBufferProfile, *gnmi_stub)); - const auto kRestoreBufferConfig = absl::Cleanup([&] { - EXPECT_OK(UpdateBufferAllocationProfileConfig( - kSutEgressPortBufferProfile, kInitialBufferConfig, *gnmi_stub)) - << "failed to restore initial buffer config -- switch config may be " - "corrupted, causing subsequent tests to fail"; - }); - - // Set equal bufer for all queues. - absl::flat_hash_map bufferConfigByQueueName = { - {"LLQ1", - {/*.dedicated_buffer =*/0, - /*.use_shared_buffer =*/true, - /*.shared_buffer_type =*/ - "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", - /*.dynamic_limit_scaling_factor =*/-3, - /*.shared_static_limit =*/0}}, - {"LLQ2", - {/*.dedicated_buffer =*/0, - /*.use_shared_buffer =*/true, - /*.shared_buffer_type =*/ - "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", - /*.dynamic_limit_scaling_factor =*/-3, - /*.shared_static_limit =*/0}}, - {"BE1", - {/*.dedicated_buffer =*/0, - /*.use_shared_buffer =*/true, - /*.shared_buffer_type =*/ - "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", - /*.dynamic_limit_scaling_factor =*/-3, - /*.shared_static_limit =*/0}}, - {"AF1", - {/*.dedicated_buffer =*/0, - /*.use_shared_buffer =*/true, - /*.shared_buffer_type =*/ - "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", - /*.dynamic_limit_scaling_factor =*/-3, - /*.shared_static_limit =*/0}}, - {"AF2", - {/*.dedicated_buffer =*/0, - /*.use_shared_buffer =*/true, - /*.shared_buffer_type =*/ - "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", - /*.dynamic_limit_scaling_factor =*/-3, - /*.shared_static_limit =*/0}}, - {"AF3", - {/*.dedicated_buffer =*/0, - /*.use_shared_buffer =*/true, - /*.shared_buffer_type =*/ - "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", - /*.dynamic_limit_scaling_factor =*/-3, - /*.shared_static_limit =*/0}}, - {"AF4", - {/*.dedicated_buffer =*/0, - /*.use_shared_buffer =*/true, - /*.shared_buffer_type =*/ - "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", - /*.dynamic_limit_scaling_factor =*/-3, - /*.shared_static_limit =*/0}}, - {"NC1", - {/*.dedicated_buffer =*/0, - /*.use_shared_buffer =*/true, - /*.shared_buffer_type =*/ - "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", - /*.dynamic_limit_scaling_factor =*/-3, - /*.shared_static_limit =*/0}}, - }; - ASSERT_OK(SetBufferConfigParameters(kSutEgressPortBufferProfile, - bufferConfigByQueueName, *gnmi_stub)); + // Configure fair buffer profile (until the end of the test) so we can ignore + // buffer carving effects. + ASSERT_OK_AND_ASSIGN(Cleanup restore_buffer_config, + ConfigureFairBufferConfigForPortUntilEndOfScope( + kSutEgressPort, *gnmi_stub)); // Set lower & upper bounds (CIRs/PIRs) such that: // - Round-robin-scheduled queues are not rate limited. @@ -942,7 +945,8 @@ TEST_P(FrontpanelQosTest, WeightedRoundRobinWeightsAreRespected) { // Connect to Ixia and fix some parameters. LOG(INFO) << "configuring Ixia traffic"; - ASSERT_OK_AND_ASSIGN(const std::string kIxiaHandle, ConnectToIxia(*testbed)); + ASSERT_OK_AND_ASSIGN(const std::string kIxiaHandle, + ixia::ConnectToIxia(*testbed)); ASSERT_OK_AND_ASSIGN( const std::string kIxiaMainSrcPortHandle, ixia::IxiaVport(kIxiaHandle, kIxiaMainSrcPort, *testbed)); @@ -1204,87 +1208,16 @@ TEST_P(FrontpanelQosTest, StrictQueuesAreStrictlyPrioritized) { "corrupted, causing subsequent test to fail"; }); - // Save Buffer config and restore at end of the test. - ASSERT_OK_AND_ASSIGN( - const std::string kSutEgressPortBufferProfile, - GetBufferAllocationProfileByEgressPort(kSutEgressPort, *gnmi_stub)); - // Before we update the buffer config, save the current config and - // prepare to restore it at the end of the test. - ASSERT_OK_AND_ASSIGN(const std::string kInitialBufferConfig, - GetBufferAllocationProfileConfig( - kSutEgressPortBufferProfile, *gnmi_stub)); - const auto kRestoreBufferConfig = absl::Cleanup([&] { - EXPECT_OK(UpdateBufferAllocationProfileConfig( - kSutEgressPortBufferProfile, kInitialBufferConfig, *gnmi_stub)) - << "failed to restore initial buffer config -- switch config may be " - "corrupted, causing subsequent tests to fail"; - }); - - // Set equal buffer for all queues. - absl::flat_hash_map bufferConfigByQueueName = { - {"LLQ1", - {/*.dedicated_buffer =*/0, - /*.use_shared_buffer =*/true, - /*.shared_buffer_type =*/ - "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", - /*.dynamic_limit_scaling_factor =*/-3, - /*.shared_static_limit =*/0}}, - {"LLQ2", - {/*.dedicated_buffer =*/0, - /*.use_shared_buffer =*/true, - /*.shared_buffer_type =*/ - "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", - /*.dynamic_limit_scaling_factor =*/-3, - /*.shared_static_limit =*/0}}, - {"BE1", - {/*.dedicated_buffer =*/0, - /*.use_shared_buffer =*/true, - /*.shared_buffer_type =*/ - "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", - /*.dynamic_limit_scaling_factor =*/-3, - /*.shared_static_limit =*/0}}, - {"AF1", - {/*.dedicated_buffer =*/0, - /*.use_shared_buffer =*/true, - /*.shared_buffer_type =*/ - "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", - /*.dynamic_limit_scaling_factor =*/-3, - /*.shared_static_limit =*/0}}, - {"AF2", - {/*.dedicated_buffer =*/0, - /*.use_shared_buffer =*/true, - /*.shared_buffer_type =*/ - "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", - /*.dynamic_limit_scaling_factor =*/-3, - /*.shared_static_limit =*/0}}, - {"AF3", - {/*.dedicated_buffer =*/0, - /*.use_shared_buffer =*/true, - /*.shared_buffer_type =*/ - "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", - /*.dynamic_limit_scaling_factor =*/-3, - /*.shared_static_limit =*/0}}, - {"AF4", - {/*.dedicated_buffer =*/0, - /*.use_shared_buffer =*/true, - /*.shared_buffer_type =*/ - "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", - /*.dynamic_limit_scaling_factor =*/-3, - /*.shared_static_limit =*/0}}, - {"NC1", - {/*.dedicated_buffer =*/0, - /*.use_shared_buffer =*/true, - /*.shared_buffer_type =*/ - "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", - /*.dynamic_limit_scaling_factor =*/-3, - /*.shared_static_limit =*/0}}, - }; - ASSERT_OK(SetBufferConfigParameters(kSutEgressPortBufferProfile, - bufferConfigByQueueName, *gnmi_stub)); + // Configure fair buffer profile (until the end of the test) so we can ignore + // buffer carving effects. + ASSERT_OK_AND_ASSIGN(Cleanup restore_buffer_config, + ConfigureFairBufferConfigForPortUntilEndOfScope( + kSutEgressPort, *gnmi_stub)); // Connect to Ixia and fix constant traffic parameters. LOG(INFO) << "connecting to Ixia"; - ASSERT_OK_AND_ASSIGN(const std::string kIxiaHandle, ConnectToIxia(*testbed)); + ASSERT_OK_AND_ASSIGN(const std::string kIxiaHandle, + ixia::ConnectToIxia(*testbed)); ASSERT_OK_AND_ASSIGN( const std::string kIxiaMainTrafficSrcPortHandle, ixia::IxiaVport(kIxiaHandle, kIxiaMainTrafficSrcPort, *testbed)); @@ -2066,7 +1999,8 @@ TEST_P(FrontpanelBufferTest, BufferCarving) { // Connect to Ixia and fix endpoints & parameters. LOG(INFO) << "configuring Ixia traffic"; - ASSERT_OK_AND_ASSIGN(const std::string kIxiaHandle, ConnectToIxia(*testbed)); + ASSERT_OK_AND_ASSIGN(const std::string kIxiaHandle, + ixia::ConnectToIxia(*testbed)); ASSERT_OK_AND_ASSIGN( const std::string kIxiaIpv4SrcPortHandle, ixia::IxiaVport(kIxiaHandle, kIxiaIpv4SrcPort, *testbed)); diff --git a/tests/qos/qos_test_util.cc b/tests/qos/qos_test_util.cc index 67369046..28a3796b 100644 --- a/tests/qos/qos_test_util.cc +++ b/tests/qos/qos_test_util.cc @@ -91,103 +91,6 @@ int64_t TotalPacketsForQueue(const QueueCounters &counters) { return counters.num_packets_dropped + counters.num_packets_transmitted; } -absl::Status SetPortSpeedInBitsPerSecond(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::StatusOr -GetPortSpeedInBitsPerSecond(const std::string &interface_name, - gnmi::gNMI::StubInterface &gnmi_stub) { - // Map keyed on openconfig speed string to value in bits per second. - // http://ops.openconfig.net/branches/models/master/docs/openconfig-interfaces.html#mod-openconfig-if-ethernet - const auto kPortSpeedTable = - absl::flat_hash_map({ - {"openconfig-if-ethernet:SPEED_100GB", 100000000000}, - {"openconfig-if-ethernet:SPEED_200GB", 200000000000}, - {"openconfig-if-ethernet:SPEED_400GB", 400000000000}, - }); - std::string speed_state_path = - absl::StrCat("interfaces/interface[name=", interface_name, - "]/ethernet/state/port-speed"); - - std::string parse_str = "openconfig-if-ethernet:port-speed"; - ASSIGN_OR_RETURN( - std::string response, - GetGnmiStatePathInfo(&gnmi_stub, speed_state_path, parse_str)); - - auto speed = kPortSpeedTable.find(StripQuotes(response)); - if (speed == kPortSpeedTable.end()) { - return absl::NotFoundError(response); - } - return speed->second; -} - -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) { - ASSIGN_OR_RETURN(int64_t bit_per_second, - GetPortSpeedInBitsPerSecond(interface, gnmi_stub)); - links.push_back(IxiaLink{ - .ixia_interface = info.peer_interface_name, - .sut_interface = interface, - .sut_interface_bits_per_second = bit_per_second, - }); - } - } - } - - return links; -} - absl::StatusOr> ParseIpv4DscpToQueueMapping(absl::string_view gnmi_config) { // TODO: Actually parse config -- hard-coded for now. diff --git a/tests/qos/qos_test_util.h b/tests/qos/qos_test_util.h index 2227d680..3f46dff9 100644 --- a/tests/qos/qos_test_util.h +++ b/tests/qos/qos_test_util.h @@ -56,49 +56,6 @@ absl::StatusOr GetGnmiQueueCounterWithTimestamp( // Get total packets (transmitted + dropped) for port queue. int64_t TotalPacketsForQueue(const QueueCounters &counters); -// Set port speed using gNMI. -absl::Status SetPortSpeedInBitsPerSecond(const std::string &port_speed, - const std::string &iface, - gnmi::gNMI::StubInterface &gnmi_stub); - -// Get configured port speed. -absl::StatusOr -GetPortSpeedInBitsPerSecond(const std::string &interface_name, - gnmi::gNMI::StubInterface &gnmi_stub); - -// Get configured port speed. -absl::StatusOr GetPortSpeed(const std::string &interface_name, - 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); - -// Set a port in loopback mode. -absl::Status SetPortLoopbackMode(bool port_loopback, - absl::string_view 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; - // Speed of the SUT interface in bits/second. - int64_t sut_interface_bits_per_second = 0; -}; - -// 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); From e9648ac6f45588934a3dda354204decd45e33bcc Mon Sep 17 00:00:00 2001 From: kishanps Date: Thu, 29 Sep 2022 11:53:12 -0700 Subject: [PATCH 3/3] [Thinkit] Re-mask packet-dropping bug, Added helper function which returns an `EK_PHYSICAL_PORT` name given an `EK_PORT` name, Add ingress and egress port parameters to QoS test, Enable modify in smoke test, gNMI port breakout tests, BERT test, Ensure that P4RT port IDs are properly reflected in the State path before considering switch configured, Ensure that P4RT port IDs in testbed are mirrored & Remove duplicate code from switch_test_setup_helpers test. --- tests/BUILD.bazel | 1 + tests/forwarding/BUILD.bazel | 2 + tests/forwarding/l3_admit_test.cc | 13 +- tests/forwarding/l3_admit_test.h | 26 ++- tests/forwarding/smoke_test.cc | 9 +- tests/gnoi/bert_tests.cc | 60 ++--- tests/lib/BUILD.bazel | 1 + tests/lib/switch_test_setup_helpers.cc | 27 +-- tests/lib/switch_test_setup_helpers_test.cc | 233 ++------------------ tests/qos/frontpanel_qos_test.h | 1 + tests/thinkit_gnmi_interface_util.cc | 68 ++++-- tests/thinkit_gnmi_interface_util.h | 4 + tests/thinkit_gnmi_interface_util_tests.cc | 157 ++++++++++++- 13 files changed, 314 insertions(+), 288 deletions(-) diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 6bdd1608..397e97d7 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -76,6 +76,7 @@ cc_library( "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_googletest//:gtest", + "@com_googlesource_code_re2//:re2", ], ) diff --git a/tests/forwarding/BUILD.bazel b/tests/forwarding/BUILD.bazel index 25f695bc..82e15ba6 100644 --- a/tests/forwarding/BUILD.bazel +++ b/tests/forwarding/BUILD.bazel @@ -209,6 +209,7 @@ cc_library( "//gutil:proto", "//gutil:status_matchers", "//lib/gnmi:gnmi_helper", + "//lib/gnmi:openconfig_cc_proto", "//p4_pdpi:ir", "//p4_pdpi:ir_cc_proto", "//p4_pdpi:p4_runtime_session", @@ -218,6 +219,7 @@ cc_library( "//tests/lib:p4rt_fixed_table_programming_helper", "//tests/lib:packet_in_helper", "//tests/lib:switch_test_setup_helpers", + "//thinkit:mirror_testbed", "//thinkit:mirror_testbed_fixture", "//thinkit:switch", "@com_github_google_glog//:glog", diff --git a/tests/forwarding/l3_admit_test.cc b/tests/forwarding/l3_admit_test.cc index 2c0449bc..29252369 100644 --- a/tests/forwarding/l3_admit_test.cc +++ b/tests/forwarding/l3_admit_test.cc @@ -555,7 +555,18 @@ TEST_P(L3AdmitTestFixture, L3AdmitCanUseInPortToRestrictMacAddresses) { } LOG(INFO) << "Done collecting packets."; - EXPECT_EQ(good_packet_count, kNumberOfTestPacket); + if (GetParam() + .testbed_interface->GetMirrorTestbed() + .Environment() + .MaskKnownFailures()) { + // TODO: Reduce expected count by tolerance level. + const int kDropTolerance = 1; + int adjusted_good_packets = kNumberOfTestPacket - kDropTolerance; + EXPECT_GE(good_packet_count, adjusted_good_packets); + EXPECT_LE(good_packet_count, kNumberOfTestPacket); + } else { + EXPECT_EQ(good_packet_count, kNumberOfTestPacket); + } EXPECT_EQ(bad_packet_count, 0); } diff --git a/tests/forwarding/l3_admit_test.h b/tests/forwarding/l3_admit_test.h index b5a8142e..16a4166d 100644 --- a/tests/forwarding/l3_admit_test.h +++ b/tests/forwarding/l3_admit_test.h @@ -19,10 +19,13 @@ #include #include "gutil/status_matchers.h" +#include "lib/gnmi/gnmi_helper.h" +#include "lib/gnmi/openconfig.pb.h" #include "p4/config/v1/p4info.pb.h" #include "p4_pdpi/ir.h" #include "p4_pdpi/ir.pb.h" #include "tests/lib/switch_test_setup_helpers.h" +#include "thinkit/mirror_testbed.h" #include "thinkit/mirror_testbed_fixture.h" #include "gmock/gmock.h" @@ -39,15 +42,34 @@ class L3AdmitTestFixture : public testing::TestWithParam { void SetUp() override { GetParam().testbed_interface->SetUp(); + thinkit::MirrorTestbed& testbed = + GetParam().testbed_interface->GetMirrorTestbed(); + // Initialize the connection and clear table entries for the SUT and Control // switch. ASSERT_OK_AND_ASSIGN( std::tie(sut_p4rt_session_, control_switch_p4rt_session_), pins_test::ConfigureSwitchPairAndReturnP4RuntimeSessionPair( - GetParam().testbed_interface->GetMirrorTestbed().Sut(), - GetParam().testbed_interface->GetMirrorTestbed().ControlSwitch(), + testbed.Sut(), testbed.ControlSwitch(), /*gnmi_config=*/std::nullopt, GetParam().p4info)); + // The L3Admit tests assume identical P4RT port IDs are used between the SUT + // and control switch. So sending a packet from a given port ID on the + // control switch means it will arrive on the same port ID on the SUT. To + // achieve this, we mirror the SUTs OpenConfig interfaces <-> P4RT Port ID + // mapping to the control switch. + ASSERT_OK_AND_ASSIGN( + std::unique_ptr sut_gnmi_stub, + testbed.Sut().CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN(const pins_test::openconfig::Interfaces sut_interfaces, + pins_test::GetInterfacesAsProto( + *sut_gnmi_stub, gnmi::GetRequest::CONFIG)); + ASSERT_OK_AND_ASSIGN( + std::unique_ptr control_gnmi_stub, + testbed.ControlSwitch().CreateGnmiStub()); + ASSERT_OK( + pins_test::SetInterfaceP4rtIds(*control_gnmi_stub, sut_interfaces)); + ASSERT_OK_AND_ASSIGN(ir_p4info_, pdpi::CreateIrP4Info(GetParam().p4info)); } diff --git a/tests/forwarding/smoke_test.cc b/tests/forwarding/smoke_test.cc index ce3575b5..04fba72c 100644 --- a/tests/forwarding/smoke_test.cc +++ b/tests/forwarding/smoke_test.cc @@ -43,7 +43,7 @@ TEST_P(SmokeTestFixture, SessionsAreNonNull) { ASSERT_NE(&GetControlP4RuntimeSession(), nullptr); } -TEST_P(SmokeTestFixture, AclTableAddDeleteOkButModifyFails) { +TEST_P(SmokeTestFixture, AclTableAddModifyDeleteOk) { const sai::WriteRequest pd_insert = gutil::ParseProtoOrDie( R"pb( updates { @@ -117,11 +117,8 @@ TEST_P(SmokeTestFixture, AclTableAddDeleteOkButModifyFails) { } } while (!pi_read_response.entities(0).table_entry().has_counter_data()); - // To avoid any test failures during the submission process (test running with - // the pre-7.1 image), skip this check for now. - // ASSERT_OK(pdpi::SetMetadataAndSendPiWriteRequest(&GetSutP4RuntimeSession(), - // pi_modify)); - + ASSERT_OK(pdpi::SetMetadataAndSendPiWriteRequest(&GetSutP4RuntimeSession(), + pi_modify)); // Delete works. ASSERT_OK(pdpi::SetMetadataAndSendPiWriteRequest(&GetSutP4RuntimeSession(), pi_delete)); diff --git a/tests/gnoi/bert_tests.cc b/tests/gnoi/bert_tests.cc index 41d335c6..ea44cbde 100644 --- a/tests/gnoi/bert_tests.cc +++ b/tests/gnoi/bert_tests.cc @@ -491,26 +491,22 @@ void VerifyOperStatusOnSetOfSutInterfaces(gnmi::gNMI::StubInterface& gnmi_stub, } } -absl::Status ValidateControlSwitchPortsUp( - thinkit::ControlDevice& control_device, - const std::vector& interfaces) { - ASSIGN_OR_RETURN( - const auto up_interfaces, - control_device.GetUpLinks(absl::Span(interfaces))); - - std::vector down_interfaces; - for (const std::string& interface : interfaces) { - if (!up_interfaces.contains(interface)) { - down_interfaces.push_back(interface); - } +absl::Status ValidatePortsUp( + thinkit::Switch& sut, thinkit::ControlDevice& control_device, + const std::vector& sut_interfaces, + const std::vector& control_device_interfaces) { + absl::Status sut_ports_up_status = + pins_test::PortsUp(sut, absl::Span(sut_interfaces)); + absl::Status control_switch_ports_up_status = control_device.ValidatePortsUp( + absl::Span(control_device_interfaces)); + + if (sut_ports_up_status.ok() && control_switch_ports_up_status.ok()) { + return absl::OkStatus(); } - if (!down_interfaces.empty()) { - return absl::InternalError( - absl::StrCat("Some interfaces are not up on control switch: ", - absl::StrJoin(down_interfaces, "\n"))); - } - return absl::OkStatus(); + EXPECT_OK(sut_ports_up_status); + EXPECT_OK(control_switch_ports_up_status); + return absl::InternalError("PortsUp validation failed."); } std::vector SelectNInterfacesFromList( @@ -864,11 +860,9 @@ TEST_P(BertTest, StartBertSucceeds) { GTEST_SKIP() << "No SUT interfaces to test"; } thinkit::Switch& sut = generic_testbed_->Sut(); - ASSERT_OK( - pins_test::PortsUp(sut, absl::Span(sut_interfaces_))); - thinkit::ControlDevice& control_device = generic_testbed_->ControlDevice(); - ASSERT_OK(ValidateControlSwitchPortsUp(control_device, peer_interfaces_)); + ASSERT_OK( + ValidatePortsUp(sut, control_device, sut_interfaces_, peer_interfaces_)); // Select 2 operational state "up" ports. sut_test_interfaces_ = absl::GetFlag(FLAGS_interfaces); @@ -1031,9 +1025,9 @@ TEST_P(BertTest, StartBertSucceeds) { HasSubstr("exists")))) << "Response: " << bert_response.ShortDebugString(); } + ASSERT_OK( - pins_test::PortsUp(sut, absl::Span(sut_interfaces_))); - ASSERT_OK(ValidateControlSwitchPortsUp(control_device, peer_interfaces_)); + ValidatePortsUp(sut, control_device, sut_interfaces_, peer_interfaces_)); } // Runs the BERT test on current maximum allowed number of interfaces. During @@ -1047,11 +1041,9 @@ TEST_P(BertTest, RunBertOnMaximumAllowedPorts) { GTEST_SKIP() << "No SUT interfaces to test"; } thinkit::Switch& sut = generic_testbed_->Sut(); - ASSERT_OK( - pins_test::PortsUp(sut, absl::Span(sut_interfaces_))); - thinkit::ControlDevice& control_device = generic_testbed_->ControlDevice(); - ASSERT_OK(ValidateControlSwitchPortsUp(control_device, peer_interfaces_)); + ASSERT_OK( + ValidatePortsUp(sut, control_device, sut_interfaces_, peer_interfaces_)); // Get all the interfaces that are operational status "UP". sut_test_interfaces_ = sut_interfaces_; @@ -1180,8 +1172,7 @@ TEST_P(BertTest, RunBertOnMaximumAllowedPorts) { absl::SleepFor(kPortsUpWaitTime); ASSERT_OK( - pins_test::PortsUp(sut, absl::Span(sut_interfaces_))); - ASSERT_OK(ValidateControlSwitchPortsUp(control_device, peer_interfaces_)); + ValidatePortsUp(sut, control_device, sut_interfaces_, peer_interfaces_)); } // Run BERT on a port. Issue StopBERT on the SUT port, this causes BERT to @@ -1192,11 +1183,9 @@ TEST_P(BertTest, StopBertSucceeds) { GTEST_SKIP() << "No SUT interfaces to test"; } thinkit::Switch& sut = generic_testbed_->Sut(); - ASSERT_OK( - pins_test::PortsUp(sut, absl::Span(sut_interfaces_))); - thinkit::ControlDevice& control_device = generic_testbed_->ControlDevice(); - ASSERT_OK(ValidateControlSwitchPortsUp(control_device, peer_interfaces_)); + ASSERT_OK( + ValidatePortsUp(sut, control_device, sut_interfaces_, peer_interfaces_)); // Select one operational state "up" port. std::string interface = absl::GetFlag(FLAGS_interface); @@ -1350,8 +1339,7 @@ TEST_P(BertTest, StopBertSucceeds) { } ASSERT_OK( - pins_test::PortsUp(sut, absl::Span(sut_interfaces_))); - ASSERT_OK(ValidateControlSwitchPortsUp(control_device, peer_interfaces_)); + ValidatePortsUp(sut, control_device, sut_interfaces_, peer_interfaces_)); } } // namespace bert diff --git a/tests/lib/BUILD.bazel b/tests/lib/BUILD.bazel index 2aa8bb46..b2ded6e4 100644 --- a/tests/lib/BUILD.bazel +++ b/tests/lib/BUILD.bazel @@ -95,6 +95,7 @@ cc_library( "//tests:thinkit_sanity_tests", "//thinkit:mirror_testbed", "//thinkit:switch", + "@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", diff --git a/tests/lib/switch_test_setup_helpers.cc b/tests/lib/switch_test_setup_helpers.cc index d8382fee..1e5bb33a 100644 --- a/tests/lib/switch_test_setup_helpers.cc +++ b/tests/lib/switch_test_setup_helpers.cc @@ -29,6 +29,7 @@ #include "p4/v1/p4runtime.pb.h" #include "p4_pdpi/ir_tools.h" #include "p4_pdpi/p4_runtime_session.h" +#include "proto/gnmi/gnmi.grpc.pb.h" #include "tests/thinkit_sanity_tests.h" namespace pins_test { @@ -61,14 +62,6 @@ absl::Status TryClearingTableEntries( return absl::OkStatus(); } -absl::Status PushGnmiAndWaitForConvergence(thinkit::Switch& thinkit_switch, - const std::string& gnmi_config, - absl::Duration gnmi_timeout) { - RETURN_IF_ERROR(PushGnmiConfig(thinkit_switch, gnmi_config)); - return WaitForGnmiPortIdConvergence(thinkit_switch, gnmi_config, - gnmi_timeout); -} - // Wrapper around `TestGnoiSystemColdReboot` that ensures we don't ignore fatal // failures. absl::Status Reboot(thinkit::Switch& thinkit_switch) { @@ -162,13 +155,21 @@ ConfigureSwitchAndReturnP4RuntimeSession( RETURN_IF_ERROR(TryClearingTableEntries(thinkit_switch, metadata)); if (gnmi_config.has_value()) { - RETURN_IF_ERROR( - PushGnmiAndWaitForConvergence(thinkit_switch, *gnmi_config, - /*gnmi_timeout=*/kGnmiTimeoutDefault)); + RETURN_IF_ERROR(PushGnmiConfig(thinkit_switch, *gnmi_config)); } - return CreateP4RuntimeSessionAndOptionallyPushP4Info(thinkit_switch, p4info, - metadata); + ASSIGN_OR_RETURN(auto p4rt_session, + CreateP4RuntimeSessionAndOptionallyPushP4Info( + thinkit_switch, p4info, metadata)); + + // Ensure that the P4RT port IDs configured on the switch are reflected in the + // state before returning. + ASSIGN_OR_RETURN(std::unique_ptr gnmi_stub, + thinkit_switch.CreateGnmiStub()); + RETURN_IF_ERROR( + WaitForGnmiPortIdConvergence(*gnmi_stub, + /*gnmi_timeout=*/kGnmiTimeoutDefault)); + return p4rt_session; } absl::StatusOr, diff --git a/tests/lib/switch_test_setup_helpers_test.cc b/tests/lib/switch_test_setup_helpers_test.cc index 502e114c..21715729 100644 --- a/tests/lib/switch_test_setup_helpers_test.cc +++ b/tests/lib/switch_test_setup_helpers_test.cc @@ -13,210 +13,39 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" -#include "absl/strings/substitute.h" -#include "absl/time/time.h" #include "absl/types/span.h" -#include "gmock/gmock.h" #include "grpcpp/test/mock_stream.h" -#include "gtest/gtest.h" #include "gutil/status.h" #include "gutil/testing.h" #include "lib/gnmi/gnmi_helper.h" #include "p4/config/v1/p4info.pb.h" #include "p4/v1/p4runtime.pb.h" -#include "p4/v1/p4runtime_mock.grpc.pb.h" #include "p4_pdpi/ir.pb.h" #include "p4_pdpi/p4_runtime_session.h" -#include "p4_pdpi/pd.h" +#include "p4_pdpi/p4_runtime_session_mocking.h" #include "p4_pdpi/testing/test_p4info.h" #include "proto/gnmi/gnmi_mock.grpc.pb.h" #include "sai_p4/instantiations/google/instantiations.h" #include "sai_p4/instantiations/google/sai_p4info.h" #include "sai_p4/instantiations/google/sai_pd.pb.h" #include "thinkit/mock_switch.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" namespace pins_test { namespace { -using ::testing::_; using ::testing::AnyNumber; -using ::testing::EqualsProto; using ::testing::InSequence; -using ::testing::NiceMock; using ::testing::Not; using ::testing::Return; using ::testing::ReturnRef; using ::testing::StrictMock; using ::testing::status::IsOk; -// One of the tables and actions from -// http://google3/blaze-out/genfiles/third_party/pins_infra/p4_pdpi/testing/test_p4info_embed.cc?l=13 -// These need to correspond to the values in our p4info because it is checked -// when sequencing updates to clear tables on the switch. -constexpr uint32_t kTableId = 33554433; -constexpr uint32_t kActionId = 16777217; - // Any constant is fine here. constexpr uint32_t kDeviceId = 100; -// This is the only action that will work everywhere. -constexpr p4::v1::SetForwardingPipelineConfigRequest::Action - kForwardingPipelineAction = - p4::v1::SetForwardingPipelineConfigRequest::RECONCILE_AND_COMMIT; - -p4::v1::Uint128 ConstructElectionId( - const pdpi::P4RuntimeSessionOptionalArgs& metadata) { - p4::v1::Uint128 election_id; - election_id.set_high(absl::Uint128High64(metadata.election_id)); - election_id.set_low(absl::Uint128Low64(metadata.election_id)); - return election_id; -} - -p4::v1::MasterArbitrationUpdate ConstructMasterArbitrationUpdate( - const pdpi::P4RuntimeSessionOptionalArgs& metadata) { - p4::v1::MasterArbitrationUpdate master_arbitration_update; - *master_arbitration_update.mutable_election_id() = - ConstructElectionId(metadata); - master_arbitration_update.set_device_id(kDeviceId); - master_arbitration_update.mutable_role()->set_name(metadata.role); - return master_arbitration_update; -} - -// Configures the given `MockP4RuntimeStub` such that the given sequence of -// table entries will be returned for the next P4RT Read request. -void SetNextReadResponse(p4::v1::MockP4RuntimeStub& mock_p4rt_stub, - std::vector read_entries) { - EXPECT_CALL(mock_p4rt_stub, ReadRaw) - .WillOnce([read_entries = std::move(read_entries)](auto*, auto&) { - auto* reader = - new grpc::testing::MockClientReader(); - InSequence s; - for (const auto& entry : read_entries) { - EXPECT_CALL(*reader, Read) - .WillOnce([=](p4::v1::ReadResponse* response) -> bool { - *response->add_entities()->mutable_table_entry() = entry; - return true; - }); - } - EXPECT_CALL(*reader, Read).WillOnce(Return(false)); - EXPECT_CALL(*reader, Finish).WillOnce(Return(grpc::Status::OK)); - return reader; - }); -} - -// Mocks a P4RuntimeSession::Create call with a stub by constructing a -// ReaderWriter mock stream and mocking an arbitration handshake. This function -// does not perform any of these operations, it only sets up expectations. -[[nodiscard]] grpc::testing::MockClientReaderWriter< - p4::v1::StreamMessageRequest, p4::v1::StreamMessageResponse> & -MockP4RuntimeSessionCreateAndReturnStreamChannel( - p4::v1::MockP4RuntimeStub &stub, - const pdpi::P4RuntimeSessionOptionalArgs &metadata) { - // The ReaderWriter stream constructed from the stub. This needs to be - // malloced as it is automatically freed when the unique pointer that it - // will be wrapped in is freed. The stream is wrapped in StreamChannel, - // which is the method of the stub that calls StreamChannelRaw, but is not - // itself mocked. - auto* stream = new NiceMock>(); - EXPECT_CALL(stub, StreamChannelRaw).WillOnce(Return(stream)); - - // A valid MasterArbitrationUpdate sent as request and response. - auto master_arbitration_update = ConstructMasterArbitrationUpdate(metadata); - - // Ensures that we write some sort of arbitration request... - p4::v1::StreamMessageRequest arbitration_request; - *arbitration_request.mutable_arbitration() = master_arbitration_update; - EXPECT_CALL(*stream, Write(EqualsProto(arbitration_request), _)) - .WillOnce(Return(true)); - - // ... and return a valid response. - EXPECT_CALL(*stream, Read) - .WillOnce([=](p4::v1::StreamMessageResponse* arbitration_response) { - *arbitration_response->mutable_arbitration() = - master_arbitration_update; - return true; - }); - return *stream; -} - -// Constructs a table entry using the predefined table id, kTableId, and action -// id, kActionId. -p4::v1::TableEntry ConstructTableEntry() { - p4::v1::TableEntry table_entry; - table_entry.set_table_id(kTableId); - table_entry.mutable_action()->mutable_action()->set_action_id(kActionId); - return table_entry; -} - -// Sets up a write request to delete the given table entry. -p4::v1::WriteRequest ConstructDeleteRequest( - const pdpi::P4RuntimeSessionOptionalArgs& metadata, - const p4::v1::TableEntry& table_entry) { - p4::v1::Update delete_update; - delete_update.set_type(p4::v1::Update::DELETE); - *delete_update.mutable_entity()->mutable_table_entry() = table_entry; - - p4::v1::WriteRequest delete_request; - *delete_request.add_updates() = delete_update; - delete_request.set_device_id(kDeviceId); - delete_request.set_role(metadata.role); - *delete_request.mutable_election_id() = ConstructElectionId(metadata); - return delete_request; -} - -// Mocks a `CheckNoEntries` call using the stub in a previously -// mocked P4RuntimeSession. -// Ensures that there are no table entries remaining. -void MockCheckNoEntries(p4::v1::MockP4RuntimeStub& stub) { - // We need to return a p4info to get to the stage where we read tables. - EXPECT_CALL(stub, GetForwardingPipelineConfig) - .WillOnce([=](auto, auto, - p4::v1::GetForwardingPipelineConfigResponse* - get_pipeline_response) { - get_pipeline_response->mutable_config()->mutable_p4info(); - return grpc::Status::OK; - }); - - SetNextReadResponse(stub, {}); -} - -// Mocks a `ClearTableEntries` call using the stub and p4info in a previously -// mocked P4RuntimeSession. -// Pulls the p4info from the switch, then reads a table entry, deletes it, and -// reads again ensuring that there are no table entries remaining. -void MockClearTableEntries(p4::v1::MockP4RuntimeStub& stub, - const p4::config::v1::P4Info& p4info, - const pdpi::P4RuntimeSessionOptionalArgs& metadata) { - // We need to return a valid p4info to get to the stage where we read tables. - EXPECT_CALL(stub, GetForwardingPipelineConfig) - .WillOnce([=](auto, auto, - p4::v1::GetForwardingPipelineConfigResponse* - get_pipeline_response) { - *get_pipeline_response->mutable_config()->mutable_p4info() = p4info; - return grpc::Status::OK; - }); - - { - InSequence s; - p4::v1::TableEntry table_entry = ConstructTableEntry(); - - // We return a table entry so that the function exercises the deletion - // portion of clearing table entries. - SetNextReadResponse(stub, {table_entry}); - - // Mocks the call to delete table entry that we have created. - EXPECT_CALL( - stub, - Write(_, EqualsProto(ConstructDeleteRequest(metadata, table_entry)), _)) - .Times(1); - - // Mocks a `CheckNoEntries` call, ensuring that the tables are really - // cleared. - MockCheckNoEntries(stub); - } -} - // Sets up expectation that `ClearTableEntries(mock_switch, metadata)` is // called. void ExpectCallToClearTableEntries( @@ -225,11 +54,8 @@ void ExpectCallToClearTableEntries( EXPECT_CALL(mock_switch, CreateP4RuntimeStub()).WillOnce([=] { InSequence s; auto stub = std::make_unique<::p4::v1::MockP4RuntimeStub>(); - auto &stream_channel = - MockP4RuntimeSessionCreateAndReturnStreamChannel(*stub, metadata); + MockP4RuntimeSessionCreate(*stub, metadata); MockClearTableEntries(*stub, p4info, metadata); - // From the call to Finish. - EXPECT_CALL(stream_channel, Read).WillOnce(Return(false)); return stub; }); } @@ -250,10 +76,21 @@ void ExpectCallToPushGnmiConfig(thinkit::MockSwitch& mock_switch) { } // Mocks a successful `WaitForGnmiPortIdConvergence` call where the ports given -// by `interfaces` have converged. +// by `interfaces` are retrieved from the config path and have converged in the +// state path. void MockGnmiConvergence( gnmi::MockgNMIStub& mock_gnmi_stub, const std::vector& interfaces) { + InSequence s; + EXPECT_CALL(mock_gnmi_stub, Get) + .WillOnce([=](auto, auto, gnmi::GetResponse* response) { + *response->add_notification() + ->add_update() + ->mutable_val() + ->mutable_json_ietf_val() = + OpenConfigWithInterfaces(GnmiFieldType::kConfig, interfaces); + return grpc::Status::OK; + }); EXPECT_CALL(mock_gnmi_stub, Get) .WillOnce([=](auto, auto, gnmi::GetResponse* response) { *response->add_notification() @@ -278,36 +115,6 @@ void ExpectCallToWaitForGnmiPortIdConvergence( }); } -// Sets up expectation that -// `pins_test::ExpectCallToPushGnmiAndWaitForConvergence(mock_switch, _, _)` is -// called, mocking response that the given`interfaces` have converged. -void ExpectCallToPushGnmiAndWaitForConvergence( - thinkit::MockSwitch& mock_switch, - const std::vector& interfaces) { - InSequence s; - ExpectCallToPushGnmiConfig(mock_switch); - ExpectCallToWaitForGnmiPortIdConvergence(mock_switch, interfaces); -} - -// Constructs a valid forwarding pipeline config request with the given p4info -// and metadata. -p4::v1::SetForwardingPipelineConfigRequest -ConstructForwardingPipelineConfigRequest( - const pdpi::P4RuntimeSessionOptionalArgs& metadata, - const p4::config::v1::P4Info& p4info, - absl::optional p4_device_config = absl::nullopt) { - p4::v1::SetForwardingPipelineConfigRequest request; - request.set_device_id(kDeviceId); - request.set_role(metadata.role); - *request.mutable_election_id() = ConstructElectionId(metadata); - request.set_action(kForwardingPipelineAction); - *request.mutable_config()->mutable_p4info() = p4info; - if (p4_device_config.has_value()) { - *request.mutable_config()->mutable_p4_device_config() = *p4_device_config; - } - return request; -} - void ExpectCallToCreateP4RuntimeSessionAndOptionallyPushP4Info( thinkit::MockSwitch& mock_switch, const std::optional& p4info, @@ -315,15 +122,14 @@ void ExpectCallToCreateP4RuntimeSessionAndOptionallyPushP4Info( EXPECT_CALL(mock_switch, CreateP4RuntimeStub).WillOnce([=] { InSequence s; auto stub = absl::make_unique>(); - // Ignore returned stream channel mock since we have no use for it. - (void)MockP4RuntimeSessionCreateAndReturnStreamChannel(*stub, metadata); + MockP4RuntimeSessionCreate(*stub, metadata); if (p4info.has_value()) { // TODO: Test the path where `GetForwardingPipelineConfig` // returns a non-empty P4Info. EXPECT_CALL(*stub, GetForwardingPipelineConfig).Times(1); EXPECT_CALL(*stub, SetForwardingPipelineConfig).Times(1); } - MockCheckNoEntries(*stub); + pdpi::MockCheckNoEntries(*stub, p4info); return stub; }); @@ -359,10 +165,11 @@ void MockConfigureSwitchAndReturnP4RuntimeSession( InSequence s; ExpectCallToClearTableEntries(mock_switch, pdpi::GetTestP4Info(), metadata); if (gnmi_config.has_value()) { - ExpectCallToPushGnmiAndWaitForConvergence(mock_switch, interfaces); + ExpectCallToPushGnmiConfig(mock_switch); } ExpectCallToCreateP4RuntimeSessionAndOptionallyPushP4Info(mock_switch, p4info, metadata); + ExpectCallToWaitForGnmiPortIdConvergence(mock_switch, interfaces); } } diff --git a/tests/qos/frontpanel_qos_test.h b/tests/qos/frontpanel_qos_test.h index 04340d0e..7480494f 100644 --- a/tests/qos/frontpanel_qos_test.h +++ b/tests/qos/frontpanel_qos_test.h @@ -44,6 +44,7 @@ struct QosTestParams { p4::config::v1::P4Info p4info; std::string ingress_ports[2]; std::string egress_port_under_test; + std::string name; }; class FrontpanelQosTest : public testing::TestWithParam { diff --git a/tests/thinkit_gnmi_interface_util.cc b/tests/thinkit_gnmi_interface_util.cc index 99e4a300..93c7dfe9 100644 --- a/tests/thinkit_gnmi_interface_util.cc +++ b/tests/thinkit_gnmi_interface_util.cc @@ -23,15 +23,16 @@ #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" #include "absl/strings/substitute.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" #include "gutil/status.h" #include "include/nlohmann/json.hpp" #include "lib/gnmi/gnmi_helper.h" #include "proto/gnmi/gnmi.grpc.pb.h" +#include "re2/re2.h" #include "tests/thinkit_util.h" #include "thinkit/ssh_client.h" #include "thinkit/switch.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" namespace pins_test { namespace { @@ -544,10 +545,40 @@ absl::StatusOr IsCopperPort(gnmi::gNMI::StubInterface* sut_gnmi_stub, return (ethernet_pmd.substr(pos + 1, 2) == "CR"); } -absl::StatusOr GenerateInterfaceBreakoutConfig( - absl::string_view port, const int id, absl::string_view breakout_speed, - const bool is_copper_port) { +absl::StatusOr +ComputePortIDForPort(gnmi::gNMI::StubInterface *sut_gnmi_stub, + absl::string_view port) { + // Try to get currently configured id for the port from the switch. + std::string if_state_path = + absl::StrCat("interfaces/interface[name=", port, "]/state/id"); + std::string resp_parse_str = "openconfig-interfaces:id"; + uint32_t id; + auto state_path_response = + GetGnmiStatePathInfo(sut_gnmi_stub, if_state_path, resp_parse_str); + if (state_path_response.ok() && !state_path_response.value().empty()) { + if (!absl::SimpleAtoi(state_path_response.value(), &id)) { + return gutil::InternalErrorBuilder().LogError() + << "Failed to convert string (" << state_path_response.value() + << ") to integer"; + } + return id; + } + // Generate ID same as that used by controller, if not found on switch. + ASSIGN_OR_RETURN(auto is_parent, IsParentPort(port)); + ASSIGN_OR_RETURN(auto slot_port_lane, GetSlotPortLaneForPort(port)); + // Port ID is same as port index/parent port number for parent ports. + if (is_parent) { + return slot_port_lane.port; + } + // Port ID is computed for child ports using + // (laneIndex*512 + parentPortNumber + 1) + return (slot_port_lane.lane * 512 + slot_port_lane.port + 1); +} +absl::StatusOr GenerateInterfaceBreakoutConfig( + gnmi::gNMI::StubInterface *sut_gnmi_stub, absl::string_view port, + absl::string_view breakout_speed, const bool is_copper_port) { + ASSIGN_OR_RETURN(auto id, ComputePortIDForPort(sut_gnmi_stub, port)); auto interface_config = absl::Substitute( R"pb({ "config": { @@ -555,7 +586,8 @@ absl::StatusOr GenerateInterfaceBreakoutConfig( "loopback-mode": false, "mtu": 9216, "name": "$0", - "type": "iana-if-type:ethernetCsmacd" + "type": "iana-if-type:ethernetCsmacd", + "id": $2 }, "name": "$0", "openconfig-if-ethernet:ethernet": { @@ -573,7 +605,7 @@ absl::StatusOr GenerateInterfaceBreakoutConfig( } } )pb", - port, breakout_speed); + port, breakout_speed, id); if (is_copper_port) { interface_config = absl::Substitute( R"pb({ @@ -582,7 +614,8 @@ absl::StatusOr GenerateInterfaceBreakoutConfig( "loopback-mode": false, "mtu": 9216, "name": "$0", - "type": "iana-if-type:ethernetCsmacd" + "type": "iana-if-type:ethernetCsmacd", + "id": $2 }, "name": "$0", "openconfig-if-ethernet:ethernet": { @@ -603,7 +636,7 @@ absl::StatusOr GenerateInterfaceBreakoutConfig( } } )pb", - port, breakout_speed); + port, breakout_speed, id); } return interface_config; } @@ -652,10 +685,10 @@ absl::StatusOr GetBreakoutModeConfigJson( auto port = absl::StrCat(kEthernet, slot_port_lane.slot, "/", slot_port_lane.port, "/", std::to_string(curr_lane_number)); - ASSIGN_OR_RETURN( - auto interface_config, - GenerateInterfaceBreakoutConfig(port, curr_lane_number, - breakout_speed, is_copper_port)); + ASSIGN_OR_RETURN(auto interface_config, + GenerateInterfaceBreakoutConfig(sut_gnmi_stub, port, + breakout_speed, + is_copper_port)); interface_configs.push_back(interface_config); int offset = max_channels_in_group / num_breakouts; curr_lane_number += offset; @@ -802,4 +835,13 @@ absl::StatusOr IsParentPort(absl::string_view port) { // Lane number for a master port is always 1. return slot_port_lane.lane == 1; } + +// Returns an EK_PHYSICAL_PORT name given an EK_PORT name. +absl::StatusOr DeriveEkPhysicalPort(absl::string_view ek_port) { + int slot; + int port; + RET_CHECK(RE2::FullMatch(ek_port, R"(\w+(\d+)\/(\d+)(\/\d+)*)", &slot, &port)) + << "no match found for " << ek_port; + return absl::StrCat("phy-", slot, "/", port); +} } // namespace pins_test diff --git a/tests/thinkit_gnmi_interface_util.h b/tests/thinkit_gnmi_interface_util.h index 862f1719..2587eace 100644 --- a/tests/thinkit_gnmi_interface_util.h +++ b/tests/thinkit_gnmi_interface_util.h @@ -176,5 +176,9 @@ absl::StatusOr IsParentPort(absl::string_view port); // channelized or no. absl::StatusOr IsChannelizedBreakoutMode(const std::string& mode); +// Returns an EK_PHYSICAL_PORT name given an EK_PORT name. +// For example, the call below would return "phy-1/2". +// DeriveEkPhysicalPort("Ethernet1/2/5") +absl::StatusOr DeriveEkPhysicalPort(absl::string_view ek_port); } // namespace pins_test #endif // PINS_TESTS_THINKIT_GNMI_INTERFACE_UTIL_H_ diff --git a/tests/thinkit_gnmi_interface_util_tests.cc b/tests/thinkit_gnmi_interface_util_tests.cc index ec84a7c3..a9c87d7d 100644 --- a/tests/thinkit_gnmi_interface_util_tests.cc +++ b/tests/thinkit_gnmi_interface_util_tests.cc @@ -357,6 +357,59 @@ class GNMIThinkitInterfaceUtilityTest : public ::testing::Test { return resp; } + absl::StatusOr portIDGetReq(absl::string_view port) { + gnmi::GetRequest req; + if (!google::protobuf::TextFormat::ParseFromString( + absl::Substitute( + R"pb(prefix { origin: "openconfig" } + path { + elem { name: "interfaces" } + elem { + name: "interface" + key { key: "name" value: "$0" } + } + elem { name: "state" } + elem { name: "id" } + } + type: STATE)pb", + port), + &req)) { + return gutil::InternalErrorBuilder().LogError() + << "Failed to parse request into proto."; + } + return req; + } + + absl::StatusOr portIDGetResp(absl::string_view port, + absl::string_view id) { + gnmi::GetResponse resp; + if (!google::protobuf::TextFormat::ParseFromString(absl::Substitute( + R"pb(notification { + timestamp: 1631864194292383538 + prefix { origin: "openconfig" } + update { + path { + elem { name: "openconfig-interfaces:interfaces" } + elem { + name: "interface" + key: { key: "name" value: "$0" } + } + elem { name: "state" } + elem { name: "id" } + } + val { + json_ietf_val: "{\"openconfig-interfaces:id\":$1}" + } + } + })pb", + port, id), + &resp)) { + return gutil::InternalErrorBuilder().LogError() + << "Failed to parse response into proto."; + } + return resp; + } + thinkit::MockSwitch mock_switch_; gnmi::MockgNMIStub mock_gnmi_stub_; }; @@ -1484,7 +1537,7 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, replace { path {} val { - json_ietf_val: "{\n \"openconfig-interfaces:interfaces\": { \"interface\": [ {\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/1\",\n \"type\": \"iana-if-type:ethernetCsmacd\"\n },\n \"name\": \"Ethernet1/1/1\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": { \"port-speed\": \"openconfig-if-ethernet:SPEED_400GB\" }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ] },\n \"openconfig-platform:components\": {\n \"component\":\n [ {\n \"name\": \"1/1\",\n \"config\": { \"name\": \"1/1\" },\n \"port\": {\n \"config\": { \"port-id\": 1 },\n \"breakout-mode\": { \"groups\": { \"group\": [ {\n \"config\": {\n \"breakout-speed\": \"openconfig-if-ethernet:SPEED_400GB\",\n \"index\": 0,\n \"num-breakouts\": 1,\n \"num-physical-channels\": 8\n },\n \"index\": 0\n } ] } }\n }\n }]\n }\n }" + json_ietf_val: "{\n \"openconfig-interfaces:interfaces\": { \"interface\": [ {\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/1\",\n \"type\": \"iana-if-type:ethernetCsmacd\",\n \"id\": 1\n },\n \"name\": \"Ethernet1/1/1\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": { \"port-speed\": \"openconfig-if-ethernet:SPEED_400GB\" }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ] },\n \"openconfig-platform:components\": {\n \"component\":\n [ {\n \"name\": \"1/1\",\n \"config\": { \"name\": \"1/1\" },\n \"port\": {\n \"config\": { \"port-id\": 1 },\n \"breakout-mode\": { \"groups\": { \"group\": [ {\n \"config\": {\n \"breakout-speed\": \"openconfig-if-ethernet:SPEED_400GB\",\n \"index\": 0,\n \"num-breakouts\": 1,\n \"num-physical-channels\": 8\n },\n \"index\": 0\n } ] } }\n }\n }]\n }\n }" } })pb"; ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( @@ -1508,6 +1561,10 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(ethernet_pmd_req), _)) .WillOnce( DoAll(SetArgPointee<2>(ethernet_pmd_resp), Return(grpc::Status::OK))); + ASSERT_OK_AND_ASSIGN(auto id_req, portIDGetReq("Ethernet1/1/1")); + ASSERT_OK_AND_ASSIGN(auto id_resp, portIDGetResp("Ethernet1/1/1", "1")); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(id_req), _)) + .WillOnce(DoAll(SetArgPointee<2>(id_resp), Return(grpc::Status::OK))); ASSERT_OK(pins_test::GetBreakoutModeConfigFromString( req, mock_gnmi_stub_ptr.get(), port_index, intf_name, breakout_mode)); EXPECT_THAT(req, EqualsProto(expected_breakout_config)); @@ -1524,7 +1581,56 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, replace { path {} val { - json_ietf_val: "{\n \"openconfig-interfaces:interfaces\": { \"interface\": [ {\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/1\",\n \"type\": \"iana-if-type:ethernetCsmacd\"\n },\n \"name\": \"Ethernet1/1/1\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": { \"port-speed\": \"openconfig-if-ethernet:SPEED_200GB\" }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ,{\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/5\",\n \"type\": \"iana-if-type:ethernetCsmacd\"\n },\n \"name\": \"Ethernet1/1/5\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": { \"port-speed\": \"openconfig-if-ethernet:SPEED_200GB\" }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ] },\n \"openconfig-platform:components\": {\n \"component\":\n [ {\n \"name\": \"1/1\",\n \"config\": { \"name\": \"1/1\" },\n \"port\": {\n \"config\": { \"port-id\": 1 },\n \"breakout-mode\": { \"groups\": { \"group\": [ {\n \"config\": {\n \"breakout-speed\": \"openconfig-if-ethernet:SPEED_200GB\",\n \"index\": 0,\n \"num-breakouts\": 2,\n \"num-physical-channels\": 4\n },\n \"index\": 0\n } ] } }\n }\n }]\n }\n }" + json_ietf_val: "{\n \"openconfig-interfaces:interfaces\": { \"interface\": [ {\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/1\",\n \"type\": \"iana-if-type:ethernetCsmacd\",\n \"id\": 1\n },\n \"name\": \"Ethernet1/1/1\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": { \"port-speed\": \"openconfig-if-ethernet:SPEED_200GB\" }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ,{\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/5\",\n \"type\": \"iana-if-type:ethernetCsmacd\",\n \"id\": 2562\n },\n \"name\": \"Ethernet1/1/5\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": { \"port-speed\": \"openconfig-if-ethernet:SPEED_200GB\" }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ] },\n \"openconfig-platform:components\": {\n \"component\":\n [ {\n \"name\": \"1/1\",\n \"config\": { \"name\": \"1/1\" },\n \"port\": {\n \"config\": { \"port-id\": 1 },\n \"breakout-mode\": { \"groups\": { \"group\": [ {\n \"config\": {\n \"breakout-speed\": \"openconfig-if-ethernet:SPEED_200GB\",\n \"index\": 0,\n \"num-breakouts\": 2,\n \"num-physical-channels\": 4\n },\n \"index\": 0\n } ] } }\n }\n }]\n }\n }" + } + } + )pb"; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + expected_breakout_config_str, &expected_breakout_config)); + auto mock_gnmi_stub_ptr = absl::make_unique(); + gnmi::GetRequest get_xcvrd_req; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(get_xcvrd_req_str, + &get_xcvrd_req)); + gnmi::GetResponse get_xcvrd_resp; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(get_xcvrd_resp_str, + &get_xcvrd_resp)); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(get_xcvrd_req), _)) + .WillOnce( + DoAll(SetArgPointee<2>(get_xcvrd_resp), Return(grpc::Status::OK))); + gnmi::GetRequest ethernet_pmd_req; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + ethernet_pmd_req_str, ðernet_pmd_req)); + gnmi::GetResponse ethernet_pmd_resp; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + ethernet_pmd_resp_optic_str, ðernet_pmd_resp)); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(ethernet_pmd_req), _)) + .WillOnce( + DoAll(SetArgPointee<2>(ethernet_pmd_resp), Return(grpc::Status::OK))); + ASSERT_OK_AND_ASSIGN(auto id_req1, portIDGetReq("Ethernet1/1/1")); + ASSERT_OK_AND_ASSIGN(auto id_resp1, portIDGetResp("Ethernet1/1/1", "1")); + ASSERT_OK_AND_ASSIGN(auto id_req2, portIDGetReq("Ethernet1/1/5")); + ASSERT_OK_AND_ASSIGN(auto id_resp2, portIDGetResp("Ethernet1/1/5", "2562")); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(id_req1), _)) + .WillOnce(DoAll(SetArgPointee<2>(id_resp1), Return(grpc::Status::OK))); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(id_req2), _)) + .WillOnce(DoAll(SetArgPointee<2>(id_resp2), Return(grpc::Status::OK))); + ASSERT_OK(pins_test::GetBreakoutModeConfigFromString( + req, mock_gnmi_stub_ptr.get(), port_index, intf_name, breakout_mode)); + EXPECT_THAT(req, EqualsProto(expected_breakout_config)); +} + +TEST_F(GNMIThinkitInterfaceUtilityTest, + TestGetBreakoutModeConfigFromStringWithComputedIDSuccess) { + const std::string port_index = "1"; + const std::string intf_name = "Ethernet1/1/1"; + const std::string breakout_mode = "2x200G"; + gnmi::SetRequest req, expected_breakout_config; + const std::string expected_breakout_config_str = R"pb( + prefix { origin: "openconfig" } + replace { + path {} + val { + json_ietf_val: "{\n \"openconfig-interfaces:interfaces\": { \"interface\": [ {\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/1\",\n \"type\": \"iana-if-type:ethernetCsmacd\",\n \"id\": 1\n },\n \"name\": \"Ethernet1/1/1\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": { \"port-speed\": \"openconfig-if-ethernet:SPEED_200GB\" }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ,{\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/5\",\n \"type\": \"iana-if-type:ethernetCsmacd\",\n \"id\": 2562\n },\n \"name\": \"Ethernet1/1/5\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": { \"port-speed\": \"openconfig-if-ethernet:SPEED_200GB\" }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ] },\n \"openconfig-platform:components\": {\n \"component\":\n [ {\n \"name\": \"1/1\",\n \"config\": { \"name\": \"1/1\" },\n \"port\": {\n \"config\": { \"port-id\": 1 },\n \"breakout-mode\": { \"groups\": { \"group\": [ {\n \"config\": {\n \"breakout-speed\": \"openconfig-if-ethernet:SPEED_200GB\",\n \"index\": 0,\n \"num-breakouts\": 2,\n \"num-physical-channels\": 4\n },\n \"index\": 0\n } ] } }\n }\n }]\n }\n }" } } )pb"; @@ -1549,6 +1655,12 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(ethernet_pmd_req), _)) .WillOnce( DoAll(SetArgPointee<2>(ethernet_pmd_resp), Return(grpc::Status::OK))); + ASSERT_OK_AND_ASSIGN(auto id_req1, portIDGetReq("Ethernet1/1/1")); + ASSERT_OK_AND_ASSIGN(auto id_req2, portIDGetReq("Ethernet1/1/5")); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(id_req1), _)) + .WillOnce(Return(grpc::Status(grpc::StatusCode::DEADLINE_EXCEEDED, ""))); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(id_req2), _)) + .WillOnce(Return(grpc::Status(grpc::StatusCode::DEADLINE_EXCEEDED, ""))); ASSERT_OK(pins_test::GetBreakoutModeConfigFromString( req, mock_gnmi_stub_ptr.get(), port_index, intf_name, breakout_mode)); EXPECT_THAT(req, EqualsProto(expected_breakout_config)); @@ -1565,7 +1677,7 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, replace { path {} val { - json_ietf_val: "{\n \"openconfig-interfaces:interfaces\": { \"interface\": [ {\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/1\",\n \"type\": \"iana-if-type:ethernetCsmacd\"\n },\n \"name\": \"Ethernet1/1/1\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": { \"port-speed\": \"openconfig-if-ethernet:SPEED_200GB\" }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ,{\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/5\",\n \"type\": \"iana-if-type:ethernetCsmacd\"\n },\n \"name\": \"Ethernet1/1/5\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": { \"port-speed\": \"openconfig-if-ethernet:SPEED_100GB\" }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ,{\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/7\",\n \"type\": \"iana-if-type:ethernetCsmacd\"\n },\n \"name\": \"Ethernet1/1/7\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": { \"port-speed\": \"openconfig-if-ethernet:SPEED_100GB\" }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ] },\n \"openconfig-platform:components\": {\n \"component\":\n [ {\n \"name\": \"1/1\",\n \"config\": { \"name\": \"1/1\" },\n \"port\": {\n \"config\": { \"port-id\": 1 },\n \"breakout-mode\": { \"groups\": { \"group\": [ {\n \"config\": {\n \"breakout-speed\": \"openconfig-if-ethernet:SPEED_200GB\",\n \"index\": 0,\n \"num-breakouts\": 1,\n \"num-physical-channels\": 4\n },\n \"index\": 0\n },{\n \"config\": {\n \"breakout-speed\": \"openconfig-if-ethernet:SPEED_100GB\",\n \"index\": 1,\n \"num-breakouts\": 2,\n \"num-physical-channels\": 2\n },\n \"index\": 1\n } ] } }\n }\n }]\n }\n }" + json_ietf_val: "{\n \"openconfig-interfaces:interfaces\": { \"interface\": [ {\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/1\",\n \"type\": \"iana-if-type:ethernetCsmacd\",\n \"id\": 1\n },\n \"name\": \"Ethernet1/1/1\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": { \"port-speed\": \"openconfig-if-ethernet:SPEED_200GB\" }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ,{\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/5\",\n \"type\": \"iana-if-type:ethernetCsmacd\",\n \"id\": 2562\n },\n \"name\": \"Ethernet1/1/5\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": { \"port-speed\": \"openconfig-if-ethernet:SPEED_100GB\" }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ,{\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/7\",\n \"type\": \"iana-if-type:ethernetCsmacd\",\n \"id\": 3586\n },\n \"name\": \"Ethernet1/1/7\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": { \"port-speed\": \"openconfig-if-ethernet:SPEED_100GB\" }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ] },\n \"openconfig-platform:components\": {\n \"component\":\n [ {\n \"name\": \"1/1\",\n \"config\": { \"name\": \"1/1\" },\n \"port\": {\n \"config\": { \"port-id\": 1 },\n \"breakout-mode\": { \"groups\": { \"group\": [ {\n \"config\": {\n \"breakout-speed\": \"openconfig-if-ethernet:SPEED_200GB\",\n \"index\": 0,\n \"num-breakouts\": 1,\n \"num-physical-channels\": 4\n },\n \"index\": 0\n },{\n \"config\": {\n \"breakout-speed\": \"openconfig-if-ethernet:SPEED_100GB\",\n \"index\": 1,\n \"num-breakouts\": 2,\n \"num-physical-channels\": 2\n },\n \"index\": 1\n } ] } }\n }\n }]\n }\n }" } } )pb"; @@ -1590,6 +1702,18 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(ethernet_pmd_req), _)) .WillOnce( DoAll(SetArgPointee<2>(ethernet_pmd_resp), Return(grpc::Status::OK))); + ASSERT_OK_AND_ASSIGN(auto id_req1, portIDGetReq("Ethernet1/1/1")); + ASSERT_OK_AND_ASSIGN(auto id_resp1, portIDGetResp("Ethernet1/1/1", "1")); + ASSERT_OK_AND_ASSIGN(auto id_req2, portIDGetReq("Ethernet1/1/5")); + ASSERT_OK_AND_ASSIGN(auto id_resp2, portIDGetResp("Ethernet1/1/5", "2562")); + ASSERT_OK_AND_ASSIGN(auto id_req3, portIDGetReq("Ethernet1/1/7")); + ASSERT_OK_AND_ASSIGN(auto id_resp3, portIDGetResp("Ethernet1/1/7", "3586")); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(id_req1), _)) + .WillOnce(DoAll(SetArgPointee<2>(id_resp1), Return(grpc::Status::OK))); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(id_req2), _)) + .WillOnce(DoAll(SetArgPointee<2>(id_resp2), Return(grpc::Status::OK))); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(id_req3), _)) + .WillOnce(DoAll(SetArgPointee<2>(id_resp3), Return(grpc::Status::OK))); ASSERT_OK(pins_test::GetBreakoutModeConfigFromString( req, mock_gnmi_stub_ptr.get(), port_index, intf_name, breakout_mode)); EXPECT_THAT(req, EqualsProto(expected_breakout_config)); @@ -1606,7 +1730,7 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, replace { path {} val { - json_ietf_val: "{\n \"openconfig-interfaces:interfaces\": { \"interface\": [ {\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/1\",\n \"type\": \"iana-if-type:ethernetCsmacd\"\n },\n \"name\": \"Ethernet1/1/1\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": {\n \"port-speed\": \"openconfig-if-ethernet:SPEED_200GB\",\n \"standalone-link-training\": true\n }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ,{\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/5\",\n \"type\": \"iana-if-type:ethernetCsmacd\"\n },\n \"name\": \"Ethernet1/1/5\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": {\n \"port-speed\": \"openconfig-if-ethernet:SPEED_100GB\",\n \"standalone-link-training\": true\n }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ,{\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/7\",\n \"type\": \"iana-if-type:ethernetCsmacd\"\n },\n \"name\": \"Ethernet1/1/7\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": {\n \"port-speed\": \"openconfig-if-ethernet:SPEED_100GB\",\n \"standalone-link-training\": true\n }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ] },\n \"openconfig-platform:components\": {\n \"component\":\n [ {\n \"name\": \"1/1\",\n \"config\": { \"name\": \"1/1\" },\n \"port\": {\n \"config\": { \"port-id\": 1 },\n \"breakout-mode\": { \"groups\": { \"group\": [ {\n \"config\": {\n \"breakout-speed\": \"openconfig-if-ethernet:SPEED_200GB\",\n \"index\": 0,\n \"num-breakouts\": 1,\n \"num-physical-channels\": 4\n },\n \"index\": 0\n },{\n \"config\": {\n \"breakout-speed\": \"openconfig-if-ethernet:SPEED_100GB\",\n \"index\": 1,\n \"num-breakouts\": 2,\n \"num-physical-channels\": 2\n },\n \"index\": 1\n } ] } }\n }\n }]\n }\n }" + json_ietf_val: "{\n \"openconfig-interfaces:interfaces\": { \"interface\": [ {\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/1\",\n \"type\": \"iana-if-type:ethernetCsmacd\",\n \"id\": 1\n },\n \"name\": \"Ethernet1/1/1\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": {\n \"port-speed\": \"openconfig-if-ethernet:SPEED_200GB\",\n \"standalone-link-training\": true\n }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ,{\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/5\",\n \"type\": \"iana-if-type:ethernetCsmacd\",\n \"id\": 2562\n },\n \"name\": \"Ethernet1/1/5\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": {\n \"port-speed\": \"openconfig-if-ethernet:SPEED_100GB\",\n \"standalone-link-training\": true\n }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ,{\n \"config\": {\n \"enabled\": true,\n \"loopback-mode\": false,\n \"mtu\": 9216,\n \"name\": \"Ethernet1/1/7\",\n \"type\": \"iana-if-type:ethernetCsmacd\",\n \"id\": 3586\n },\n \"name\": \"Ethernet1/1/7\",\n \"openconfig-if-ethernet:ethernet\": {\n \"config\": {\n \"port-speed\": \"openconfig-if-ethernet:SPEED_100GB\",\n \"standalone-link-training\": true\n }\n },\n \"subinterfaces\": {\n \"subinterface\":\n [ {\n \"config\": { \"index\": 0 },\n \"index\": 0,\n \"openconfig-if-ip:ipv6\": {\n \"unnumbered\": { \"config\": { \"enabled\": true } }\n }\n }]\n }\n }\n ] },\n \"openconfig-platform:components\": {\n \"component\":\n [ {\n \"name\": \"1/1\",\n \"config\": { \"name\": \"1/1\" },\n \"port\": {\n \"config\": { \"port-id\": 1 },\n \"breakout-mode\": { \"groups\": { \"group\": [ {\n \"config\": {\n \"breakout-speed\": \"openconfig-if-ethernet:SPEED_200GB\",\n \"index\": 0,\n \"num-breakouts\": 1,\n \"num-physical-channels\": 4\n },\n \"index\": 0\n },{\n \"config\": {\n \"breakout-speed\": \"openconfig-if-ethernet:SPEED_100GB\",\n \"index\": 1,\n \"num-breakouts\": 2,\n \"num-physical-channels\": 2\n },\n \"index\": 1\n } ] } }\n }\n }]\n }\n }" } } )pb"; @@ -1631,6 +1755,19 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(ethernet_pmd_req), _)) .WillOnce( DoAll(SetArgPointee<2>(ethernet_pmd_resp), Return(grpc::Status::OK))); + ASSERT_OK_AND_ASSIGN(auto id_req1, portIDGetReq("Ethernet1/1/1")); + ASSERT_OK_AND_ASSIGN(auto id_resp1, portIDGetResp("Ethernet1/1/1", "1")); + ASSERT_OK_AND_ASSIGN(auto id_req2, portIDGetReq("Ethernet1/1/5")); + ASSERT_OK_AND_ASSIGN(auto id_resp2, portIDGetResp("Ethernet1/1/5", "2562")); + ASSERT_OK_AND_ASSIGN(auto id_req3, portIDGetReq("Ethernet1/1/7")); + ASSERT_OK_AND_ASSIGN(auto id_resp3, portIDGetResp("Ethernet1/1/7", "3586")); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(id_req1), _)) + .WillOnce(DoAll(SetArgPointee<2>(id_resp1), Return(grpc::Status::OK))); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(id_req2), _)) + .WillOnce(DoAll(SetArgPointee<2>(id_resp2), Return(grpc::Status::OK))); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(id_req3), _)) + .WillOnce(DoAll(SetArgPointee<2>(id_resp3), Return(grpc::Status::OK))); + ASSERT_OK(pins_test::GetBreakoutModeConfigFromString( req, mock_gnmi_stub_ptr.get(), port_index, intf_name, breakout_mode)); EXPECT_THAT(req, EqualsProto(expected_breakout_config)); @@ -2676,4 +2813,16 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, TestIsParentPortIntConversionFailure) { HasSubstr("Failed to convert string (X) to integer"))); } +TEST_F(GNMIThinkitInterfaceUtilityTest, TestDeriveEkPhysicalPortSuccess) { + const std::string port = "Ethernet1/1/2"; + EXPECT_THAT(DeriveEkPhysicalPort(port), "phy-1/1"); +} + +TEST_F(GNMIThinkitInterfaceUtilityTest, TestDeriveEkPhysicalPortFailure) { + const std::string port = "Ethernet1/X"; + EXPECT_THAT( + DeriveEkPhysicalPort(port), + StatusIs(absl::StatusCode::kInternal, HasSubstr("no match found for "))); +} + } // namespace pins_test