From a01e5df9e3e3a1d1d2d8496bc1ddeecad0395bfb Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:43:49 +0530 Subject: [PATCH 01/24] [Thinkit] Add member ports size check in VerifyGroupMembersFromReceiveTraffic, Add test params for watch port tests & Change neighbor entries to ipv6. (#703) Co-authored-by: kishanps --- tests/forwarding/BUILD.bazel | 5 +++ tests/forwarding/group_programming_util.cc | 20 +-------- tests/forwarding/group_programming_util.h | 15 +++---- tests/forwarding/packet_test_util.h | 6 +++ tests/forwarding/watch_port_test.cc | 47 ++++++++++++++++------ 5 files changed, 56 insertions(+), 37 deletions(-) diff --git a/tests/forwarding/BUILD.bazel b/tests/forwarding/BUILD.bazel index 3047b3b2..86dc7620 100644 --- a/tests/forwarding/BUILD.bazel +++ b/tests/forwarding/BUILD.bazel @@ -110,7 +110,10 @@ cc_library( "//p4_pdpi/netaddr:ipv4_address", "//p4_pdpi/netaddr:mac_address", "//p4_pdpi/packetlib", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/synchronization", ], ) cc_library( @@ -136,7 +139,9 @@ cc_library( "@com_google_absl//absl/random", "@com_google_absl//absl/random:distributions", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", "@com_google_protobuf//:protobuf", ], ) diff --git a/tests/forwarding/group_programming_util.cc b/tests/forwarding/group_programming_util.cc index 9835d8ce..55b2040b 100644 --- a/tests/forwarding/group_programming_util.cc +++ b/tests/forwarding/group_programming_util.cc @@ -36,7 +36,6 @@ absl::Status ProgramNextHops(thinkit::TestEnvironment& test_environment, pdpi::P4RuntimeSession& p4_session, const pdpi::IrP4Info& ir_p4info, std::vector& members) { - int index = 0; std::vector nexthops; std::vector pi_entries; std::vector pd_entries; @@ -61,7 +60,7 @@ absl::Status ProgramNextHops(thinkit::TestEnvironment& test_environment, pd_entries.push_back(router_interface); // Create neighbor entry. - std::string neighbor_id = absl::StrCat("10.0.0.", index++); + std::string neighbor_id = "fe80::2"; auto neighbor_entry = gutil::ParseProtoOrDie(absl::Substitute( R"pb( @@ -87,7 +86,7 @@ absl::Status ProgramNextHops(thinkit::TestEnvironment& test_environment, nexthop_table_entry { match { nexthop_id: "$0" } action { - set_nexthop { router_interface_id: "$1" neighbor_id: "$2" } + set_ip_nexthop { router_interface_id: "$1" neighbor_id: "$2" } } })pb", nexthop_id, absl::StrCat("rif-", member.port), neighbor_id)); @@ -267,21 +266,6 @@ absl::Status VerifyGroupMembersFromP4Read( return absl::OkStatus(); } -// Verifies the actual members inferred from receive traffic match the -// expected members. -absl::Status VerifyGroupMembersFromReceiveTraffic( - const absl::flat_hash_map& actual_packets_received_per_port, - const absl::flat_hash_set& expected_member_ports) { - // Check we only saw expected ports. - for (const auto& [port, packets] : actual_packets_received_per_port) { - bool is_member_port = expected_member_ports.contains(port); - if (!is_member_port) { - return gutil::UnknownErrorBuilder() << "Unexpected port: " << port; - } - } - return absl::OkStatus(); -} - absl::StatusOr> GenerateNRandomWeights(int n, int total_weight) { absl::BitGen gen; diff --git a/tests/forwarding/group_programming_util.h b/tests/forwarding/group_programming_util.h index 9570dbfc..c6d383d6 100644 --- a/tests/forwarding/group_programming_util.h +++ b/tests/forwarding/group_programming_util.h @@ -15,8 +15,15 @@ #ifndef PINS_TESTS_FORWARDING_GROUP_PROGRAMMING_H_ #define PINS_TESTS_FORWARDING_GROUP_PROGRAMMING_H_ +#include +#include + #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" #include "p4/v1/p4runtime.pb.h" #include "p4_pdpi/p4_runtime_session.h" #include "p4_pdpi/ir.pb.h" @@ -65,17 +72,11 @@ absl::Status VerifyGroupMembersFromP4Read( pdpi::P4RuntimeSession& p4_session, const pdpi::IrP4Info& ir_p4info, absl::string_view group_id, absl::Span expected_members); -// Verifies the actual members inferred from receive traffic matches the -// expected members. -// actual_ports is a map of the number of packets received(value) per port(key). -absl::Status VerifyGroupMembersFromReceiveTraffic( - const absl::flat_hash_map& actual_packets_received_per_port, - const absl::flat_hash_set& expected_member_ports); - // Generates N random weights that add up to total_weight, with at least 1 in // each bucket. absl::StatusOr> GenerateNRandomWeights(int n, int total_weight); + // Returns a human-readable description of the actual vs expected // distribution of packets on the group member ports. // expect_single_port specifies whether all packets are expected on a single diff --git a/tests/forwarding/packet_test_util.h b/tests/forwarding/packet_test_util.h index b0207cca..b0826581 100644 --- a/tests/forwarding/packet_test_util.h +++ b/tests/forwarding/packet_test_util.h @@ -18,8 +18,14 @@ #include #include +#include +#include + +#include "absl/base/thread_annotations.h" #include "absl/container/flat_hash_map.h" #include "dvaas/test_vector.h" +#include "absl/status/statusor.h" +#include "absl/synchronization/mutex.h" #include "p4_pdpi/netaddr/ipv4_address.h" #include "p4_pdpi/netaddr/mac_address.h" #include "p4_pdpi/packetlib/packetlib.h" diff --git a/tests/forwarding/watch_port_test.cc b/tests/forwarding/watch_port_test.cc index f9d78f56..743e812e 100644 --- a/tests/forwarding/watch_port_test.cc +++ b/tests/forwarding/watch_port_test.cc @@ -72,6 +72,13 @@ namespace pins { namespace { + +using ::testing::UnorderedPointwise; + +MATCHER(KeyEq, "") { + return ::testing::get<0>(arg).first == ::testing::get<1>(arg); +} + // Admin down/up state used for interfaces. enum class AdminState { kDown, @@ -615,8 +622,11 @@ TEST_P(WatchPortTestFixture, VerifyBasicWcmpPacketDistribution) { ASSERT_OK(VerifyGroupMembersFromP4Read(*sut_p4_session_, GetIrP4Info(), kGroupId, members)); - ASSERT_OK(VerifyGroupMembersFromReceiveTraffic(num_packets_per_port, - expected_member_ports)); + + // Verifies the actual members inferred from receive traffic matches the + // expected members. + ASSERT_THAT(num_packets_per_port, + UnorderedPointwise(KeyEq(), expected_member_ports)); PrettyPrintDistribution(test_config, test, test_data_, members, num_packets_per_port); } @@ -724,8 +734,10 @@ TEST_P(WatchPortTestFixture, VerifyBasicWatchPortAction) { ASSERT_OK(VerifyGroupMembersFromP4Read(*sut_p4_session_, GetIrP4Info(), kGroupId, members)); - ASSERT_OK(VerifyGroupMembersFromReceiveTraffic(num_packets_per_port, - expected_member_ports)); + // Verifies the actual members inferred from receive traffic matches the + // expected members. + ASSERT_THAT(num_packets_per_port, + UnorderedPointwise(KeyEq(), expected_member_ports)); PrettyPrintDistribution(test_config, test, test_data_, members, num_packets_per_port); } @@ -838,9 +850,11 @@ TEST_P(WatchPortTestFixture, VerifyWatchPortActionInCriticalState) { absl::flat_hash_set expected_member_ports = CreateExpectedMemberPorts(members); expected_member_ports.erase(selected_port_id); - ASSERT_OK(VerifyGroupMembersFromReceiveTraffic(num_packets_per_port, - expected_member_ports)); + // Verifies the actual members inferred from receive traffic matches the + // expected members. + ASSERT_THAT(num_packets_per_port, + UnorderedPointwise(KeyEq(), expected_member_ports)); PrettyPrintDistribution(test_config, test, test_data_, members, num_packets_per_port); } @@ -938,6 +952,7 @@ TEST_P(WatchPortTestFixture, VerifyWatchPortActionForSingleMember) { << "Expected all packets to be lost for single member group watch " "port down action, but received " << test.output.size() << " actual packets"; + expected_member_ports.erase(single_member_port_id); } else { expected_member_ports.insert(single_member_port_id); EXPECT_EQ(test.output.size(), test_data_.total_packets_sent) @@ -950,8 +965,11 @@ TEST_P(WatchPortTestFixture, VerifyWatchPortActionForSingleMember) { ASSERT_OK(VerifyGroupMembersFromP4Read(*sut_p4_session_, GetIrP4Info(), kGroupId, members)); - ASSERT_OK(VerifyGroupMembersFromReceiveTraffic(num_packets_per_port, - expected_member_ports)); + + // Verifies the actual members inferred from receive traffic matches the + // expected members. + ASSERT_THAT(num_packets_per_port, + UnorderedPointwise(KeyEq(), expected_member_ports)); PrettyPrintDistribution(test_config, test, test_data_, members, num_packets_per_port); } @@ -1056,8 +1074,11 @@ TEST_P(WatchPortTestFixture, VerifyWatchPortActionForMemberModify) { ASSERT_OK(VerifyGroupMembersFromP4Read(*sut_p4_session_, GetIrP4Info(), kGroupId, members)); - ASSERT_OK(VerifyGroupMembersFromReceiveTraffic(num_packets_per_port, - expected_member_ports)); + + // Verifies the actual members inferred from receive traffic matches the + // expected members. + ASSERT_THAT(num_packets_per_port, + UnorderedPointwise(KeyEq(), expected_member_ports)); PrettyPrintDistribution(test_config, test, test_data_, members, num_packets_per_port); } @@ -1171,8 +1192,10 @@ TEST_P(WatchPortTestFixture, VerifyWatchPortActionForDownPortMemberInsert) { ASSERT_OK(VerifyGroupMembersFromP4Read(*sut_p4_session_, GetIrP4Info(), kGroupId, members)); - ASSERT_OK(VerifyGroupMembersFromReceiveTraffic(num_packets_per_port, - expected_member_ports)); + // Verifies the actual members inferred from receive traffic matches the + // expected members. + ASSERT_THAT(num_packets_per_port, + UnorderedPointwise(KeyEq(), expected_member_ports)); PrettyPrintDistribution(test_config, test, test_data_, members, num_packets_per_port); } From de9325aa911b4c9d974086db56976b55c8819a09 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:14:51 +0000 Subject: [PATCH 02/24] [P4-Symbolic] Simplify and generalize API for obtaining ConcreteContext, useful for debugging. Provide partially static translation for vrf_id_t. Rename StaticTranslationPerType to TranslationPerType. Constrain egress_spec and egress_port to be equal. Add reserved CPU port. Expose reserved port values. Fix failing test after p4c upgrade. Fix failing test after p4c upgrade. (#706) Co-authored-by: kheradmandG Co-authored-by: smolkaj Co-authored-by: kishanps --- p4_symbolic/bmv2/test.bzl | 5 ++ p4_symbolic/ir/test.bzl | 5 ++ p4_symbolic/sai/sai.cc | 25 ++++++++- p4_symbolic/sai/sai.h | 5 +- p4_symbolic/sai/sai_test.cc | 18 +++++- p4_symbolic/symbolic/BUILD.bazel | 1 + p4_symbolic/symbolic/control.cc | 9 +++ p4_symbolic/symbolic/expected/reflector.smt2 | 4 +- .../symbolic/expected/string_optional.smt2 | 40 +++++++------- p4_symbolic/symbolic/expected/table.smt2 | 4 +- p4_symbolic/symbolic/symbolic.cc | 55 ++++++++++++------- p4_symbolic/symbolic/symbolic.h | 21 ++++++- 12 files changed, 140 insertions(+), 52 deletions(-) diff --git a/p4_symbolic/bmv2/test.bzl b/p4_symbolic/bmv2/test.bzl index b5230034..9a5323dc 100644 --- a/p4_symbolic/bmv2/test.bzl +++ b/p4_symbolic/bmv2/test.bzl @@ -164,6 +164,11 @@ def bmv2_protobuf_parsing_test(name, p4_program, golden_file, p4_deps = []): ("--bmv2=$(location %s)" % bmv2_json), ("--protobuf=$(location %s)" % proto_filename), ("--json=$(location %s)" % json_filename), + # The following line makes sure hexstrings are lowercase in the protobuf file. + # This is needed because the hexstring representation of boost::multiprecision::cpp_int + # seems to behave differently accross different versions of boost (although the root + # cause has not been fully investigated). + "&& sed -i 's/0x[0-9A-F]\\+/\\L&/g' $(location %s)" % proto_filename, ]), ) diff --git a/p4_symbolic/ir/test.bzl b/p4_symbolic/ir/test.bzl index 54d12ef0..dbbd7227 100644 --- a/p4_symbolic/ir/test.bzl +++ b/p4_symbolic/ir/test.bzl @@ -75,6 +75,11 @@ def ir_parsing_test(name, p4_program, golden_file, table_entries = None, p4_deps ("--p4info=$(location %s)" % p4info_file), ("--entries=$(location %s)" % table_entries if table_entries else ""), "&> $(OUTS) || true", + # The following line makes sure hexstrings are lowercase in the protobuf file. + # This is needed because the hexstring representation of boost::multiprecision::cpp_int + # seems to behave differently accross different versions of boost (although the root + # cause has not been fully investigated). + "&& sed -i 's/0x[0-9A-F]\\+/\\L&/g' $(location %s)" % test_output_file, ]), ) diff --git a/p4_symbolic/sai/sai.cc b/p4_symbolic/sai/sai.cc index 4f4ba146..b09f984e 100644 --- a/p4_symbolic/sai/sai.cc +++ b/p4_symbolic/sai/sai.cc @@ -38,7 +38,7 @@ namespace p4_symbolic { // passed as the static mapping for "port_id_t". absl::Status CheckPhysicalPortAndPortIdTypeValueConsistency( const std::vector& physical_ports, - const symbolic::StaticTranslationPerType& translation_per_type) { + const symbolic::TranslationPerType& translation_per_type) { absl::flat_hash_set physical_port_set(physical_ports.begin(), physical_ports.end()); absl::flat_hash_set numeric_value_set; @@ -57,15 +57,36 @@ absl::Status CheckPhysicalPortAndPortIdTypeValueConsistency( return absl::OkStatus(); } +// Adds partially static mapping for "vrf_id_t". +absl::Status AddVrfIdTypeTranslation( + symbolic::TranslationPerType& translation_per_type) { + if (translation_per_type.contains(kVrfIdTypeName)) { + return absl::InvalidArgumentError(absl::StrCat( + "Did not expect user defined translation for ", kVrfIdTypeName)); + } + + // TODO: A temporary workaround until the issue is fixed. + // Map the string "" to value 0. The rest of the mapping is done dynamically. + translation_per_type[kVrfIdTypeName] = symbolic::values::TranslationData{ + .static_mapping = {{"", 0}}, + .dynamic_translation = true, + }; + + return absl::OkStatus(); +} + absl::StatusOr> EvaluateSaiPipeline( const p4::v1::ForwardingPipelineConfig& config, const std::vector& entries, const std::vector& physical_ports, - const symbolic::StaticTranslationPerType& translation_per_type) { + symbolic::TranslationPerType translation_per_type) { // Check inputs for consistency. RETURN_IF_ERROR(CheckPhysicalPortAndPortIdTypeValueConsistency( physical_ports, translation_per_type)); + // Add translation for vrf_id_t. + RETURN_IF_ERROR(AddVrfIdTypeTranslation(translation_per_type)); + ASSIGN_OR_RETURN(symbolic::Dataplane dataplane, ParseToIr(config, entries)); ASSIGN_OR_RETURN(std::unique_ptr state, symbolic::EvaluateP4Pipeline(dataplane, physical_ports, diff --git a/p4_symbolic/sai/sai.h b/p4_symbolic/sai/sai.h index 6d09ec5e..6fda61b5 100644 --- a/p4_symbolic/sai/sai.h +++ b/p4_symbolic/sai/sai.h @@ -25,18 +25,21 @@ namespace p4_symbolic { constexpr char kPortIdTypeName[] = "port_id_t"; +constexpr char kVrfIdTypeName[] = "vrf_id_t"; // Symbolically evaluates the SAI P4 program for the given forwarding pipeline // config with the given table entries. If `physical_ports` is non-empty, any // solution is guaranteed to only use ports from the list. Note that the set of // `physical_ports` should be consistent with the numeric values used in the // static translation of "port_id_t" in `translation_per_type`, otherwise +// returns an error. Also adds the partially static mapping for "vrf_id_t" and +// expects such mapping to not be present in `translation_per_type`. Otherwise, // returns an error. absl::StatusOr> EvaluateSaiPipeline( const p4::v1::ForwardingPipelineConfig& config, const std::vector& entries, const std::vector& physical_ports = {}, - const symbolic::StaticTranslationPerType& translation_per_type = {}); + symbolic::TranslationPerType translation_per_type = {}); absl::StatusOr ExtractLocalMetadataIngressPortFromModel( const symbolic::SolverState& solver_state); diff --git a/p4_symbolic/sai/sai_test.cc b/p4_symbolic/sai/sai_test.cc index 54fb144a..b1be0bfe 100644 --- a/p4_symbolic/sai/sai_test.cc +++ b/p4_symbolic/sai/sai_test.cc @@ -49,7 +49,7 @@ TEST(EvaluateSaiPipeline, FailsForInconsistentPortAndPortIdTypeTranslation) { const auto config = sai::GetNonstandardForwardingPipelineConfig( sai::Instantiation::kFabricBorderRouter, sai::NonstandardPlatform::kP4Symbolic); - symbolic::StaticTranslationPerType translations; + symbolic::TranslationPerType translations; translations[kPortIdTypeName] = symbolic::values::TranslationData{ .static_mapping = {{"a", 1}, {"b", 2}}, .dynamic_translation = false, @@ -65,7 +65,7 @@ TEST(EvaluateSaiPipeline, PassForConsistentPortAndPortIdTypeTranslation) { const auto config = sai::GetNonstandardForwardingPipelineConfig( sai::Instantiation::kFabricBorderRouter, sai::NonstandardPlatform::kP4Symbolic); - symbolic::StaticTranslationPerType translations; + symbolic::TranslationPerType translations; translations[kPortIdTypeName] = symbolic::values::TranslationData{ .static_mapping = {{"a", 1}, {"b", 2}}, .dynamic_translation = false, @@ -77,6 +77,18 @@ TEST(EvaluateSaiPipeline, PassForConsistentPortAndPortIdTypeTranslation) { EXPECT_EQ((*state)->solver->check(), z3::check_result::sat); } +TEST(EvaluateSaiPipeline, FailsIfInputContainsTranslationForVrfIdType) { + const auto config = sai::GetNonstandardForwardingPipelineConfig( + sai::Instantiation::kFabricBorderRouter, + sai::NonstandardPlatform::kP4Symbolic); + symbolic::TranslationPerType translations; + translations[kVrfIdTypeName] = symbolic::values::TranslationData{}; + absl::StatusOr> state = + EvaluateSaiPipeline(config, /*entries=*/{}, /*ports=*/{}, translations); + ASSERT_THAT(state.status(), + gutil::StatusIs(absl::StatusCode::kInvalidArgument)); +} + TEST(EvaluateSaiPipeline, IngressPortIsAmongPassedValues) { // Get config. const auto config = sai::GetNonstandardForwardingPipelineConfig( @@ -103,7 +115,7 @@ TEST(EvaluateSaiPipeline, IngressPortIsAmongPassedValues) { } // Evaluate the SAI pipeline. - symbolic::StaticTranslationPerType translations; + symbolic::TranslationPerType translations; translations[kPortIdTypeName] = symbolic::values::TranslationData{ .static_mapping = {{"a", 1}, {"b", 2}}, .dynamic_translation = false, diff --git a/p4_symbolic/symbolic/BUILD.bazel b/p4_symbolic/symbolic/BUILD.bazel index 084398c6..d7d0c4ea 100644 --- a/p4_symbolic/symbolic/BUILD.bazel +++ b/p4_symbolic/symbolic/BUILD.bazel @@ -61,6 +61,7 @@ cc_library( "@com_github_p4lang_p4runtime//:p4info_cc_proto", "@com_github_z3prover_z3//:api", "@com_gnu_gmp//:gmp", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/cleanup", "@com_google_absl//absl/status", "@com_google_absl//absl/strings", diff --git a/p4_symbolic/symbolic/control.cc b/p4_symbolic/symbolic/control.cc index 2bbc56fe..e98afa69 100644 --- a/p4_symbolic/symbolic/control.cc +++ b/p4_symbolic/symbolic/control.cc @@ -41,10 +41,19 @@ absl::StatusOr EvaluateV1model( // cloning, digests, resubmit, and multicast. The full semantics we should // implement is documented here: // https://github.com/p4lang/behavioral-model/blob/main/docs/simple_switch.md#pseudocode-for-what-happens-at-the-end-of-ingress-and-egress-processing + // Evaluate the ingress pipeline. ASSIGN_OR_RETURN(SymbolicTableMatches matches, EvaluatePipeline(data_plane, "ingress", state, translator, /*guard=*/Z3Context().bool_val(true))); ASSIGN_OR_RETURN(z3::expr dropped, IsDropped(*state)); + + // Constrain egress_port to be equal to egress_spec. + ASSIGN_OR_RETURN(z3::expr egress_spec, + state->Get("standard_metadata.egress_spec")); + RETURN_IF_ERROR(state->Set("standard_metadata.egress_port", egress_spec, + /*guard=*/Z3Context().bool_val(true))); + + // Evaluate the egress pipeline. ASSIGN_OR_RETURN(SymbolicTableMatches egress_matches, EvaluatePipeline(data_plane, "egress", state, translator, /*guard=*/!dropped)); diff --git a/p4_symbolic/symbolic/expected/reflector.smt2 b/p4_symbolic/symbolic/expected/reflector.smt2 index a563443c..b23fbdc8 100644 --- a/p4_symbolic/symbolic/expected/reflector.smt2 +++ b/p4_symbolic/symbolic/expected/reflector.smt2 @@ -3,8 +3,8 @@ (declare-fun standard_metadata.ingress_port () (_ BitVec 9)) (declare-fun standard_metadata.egress_spec () (_ BitVec 9)) (assert - (let (($x34 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x34)))) + (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) + (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35)))) (assert (let ((?x25 (ite true standard_metadata.ingress_port standard_metadata.egress_spec))) (let (($x27 (= ?x25 (_ bv511 9)))) diff --git a/p4_symbolic/symbolic/expected/string_optional.smt2 b/p4_symbolic/symbolic/expected/string_optional.smt2 index f2c4473f..4a2aaf72 100644 --- a/p4_symbolic/symbolic/expected/string_optional.smt2 +++ b/p4_symbolic/symbolic/expected/string_optional.smt2 @@ -6,8 +6,8 @@ (assert (let (($x38 (= standard_metadata.ingress_port (_ bv2 9)))) (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) - (let (($x83 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x83))))) + (let (($x84 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) + (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x84))))) (assert (let ((?x65 (concat (_ bv0 7) (_ bv2 2)))) (let ((?x29 (concat (_ bv0 8) (_ bv0 1)))) @@ -59,8 +59,8 @@ (assert (let (($x38 (= standard_metadata.ingress_port (_ bv2 9)))) (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) - (let (($x83 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x83))))) + (let (($x84 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) + (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x84))))) (assert (let ((?x65 (concat (_ bv0 7) (_ bv2 2)))) (let ((?x29 (concat (_ bv0 8) (_ bv0 1)))) @@ -101,8 +101,8 @@ (let ((?x78 (ite (and (and true (not $x68)) true) (_ bv1 9) standard_metadata.egress_spec))) (let ((?x80 (ite $x69 (_ bv0 9) ?x78))) (let (($x49 (= ?x80 (_ bv511 9)))) - (let (($x141 (and (not $x49) true))) - (and $x141 (= ?x79 0))))))))))))))))))))))) + (let (($x142 (and (not $x49) true))) + (and $x142 (= ?x79 0))))))))))))))))))))))) (check-sat) ; @@ -113,8 +113,8 @@ (assert (let (($x38 (= standard_metadata.ingress_port (_ bv2 9)))) (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) - (let (($x83 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x83))))) + (let (($x84 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) + (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x84))))) (assert (let ((?x65 (concat (_ bv0 7) (_ bv2 2)))) (let ((?x29 (concat (_ bv0 8) (_ bv0 1)))) @@ -166,8 +166,8 @@ (assert (let (($x38 (= standard_metadata.ingress_port (_ bv2 9)))) (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) - (let (($x83 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x83))))) + (let (($x84 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) + (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x84))))) (assert (let ((?x65 (concat (_ bv0 7) (_ bv2 2)))) (let ((?x29 (concat (_ bv0 8) (_ bv0 1)))) @@ -221,8 +221,8 @@ (assert (let (($x38 (= standard_metadata.ingress_port (_ bv2 9)))) (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) - (let (($x83 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x83))))) + (let (($x84 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) + (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x84))))) (assert (let ((?x65 (concat (_ bv0 7) (_ bv2 2)))) (let ((?x29 (concat (_ bv0 8) (_ bv0 1)))) @@ -265,8 +265,8 @@ (let (($x69 (and true $x68))) (let ((?x80 (ite $x69 (_ bv0 9) ?x78))) (let (($x49 (= ?x80 (_ bv511 9)))) - (let (($x141 (and (not $x49) true))) - (and $x141 (= ?x62 0))))))))))))))))))))))))) + (let (($x142 (and (not $x49) true))) + (and $x142 (= ?x62 0))))))))))))))))))))))))) (check-sat) ; @@ -277,8 +277,8 @@ (assert (let (($x38 (= standard_metadata.ingress_port (_ bv2 9)))) (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) - (let (($x83 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x83))))) + (let (($x84 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) + (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x84))))) (assert (let ((?x65 (concat (_ bv0 7) (_ bv2 2)))) (let ((?x29 (concat (_ bv0 8) (_ bv0 1)))) @@ -332,8 +332,8 @@ (assert (let (($x38 (= standard_metadata.ingress_port (_ bv2 9)))) (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) - (let (($x83 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x83))))) + (let (($x84 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) + (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x84))))) (assert (let ((?x65 (concat (_ bv0 7) (_ bv2 2)))) (let ((?x29 (concat (_ bv0 8) (_ bv0 1)))) @@ -387,8 +387,8 @@ (assert (let (($x38 (= standard_metadata.ingress_port (_ bv2 9)))) (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) - (let (($x83 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x83))))) + (let (($x84 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) + (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x84))))) (assert (let ((?x65 (concat (_ bv0 7) (_ bv2 2)))) (let ((?x29 (concat (_ bv0 8) (_ bv0 1)))) diff --git a/p4_symbolic/symbolic/expected/table.smt2 b/p4_symbolic/symbolic/expected/table.smt2 index 608c2a73..ff909030 100644 --- a/p4_symbolic/symbolic/expected/table.smt2 +++ b/p4_symbolic/symbolic/expected/table.smt2 @@ -57,8 +57,8 @@ (let ((?x41 (ite (and (and true (not $x26)) $x29) (_ bv0 9) standard_metadata.egress_spec))) (let ((?x44 (ite $x30 (_ bv1 9) ?x41))) (let (($x35 (= ?x44 (_ bv511 9)))) - (let (($x111 (and (not $x35) true))) - (and $x111 (= ?x43 0)))))))))))))) + (let (($x112 (and (not $x35) true))) + (and $x112 (= ?x43 0)))))))))))))) (check-sat) ; diff --git a/p4_symbolic/symbolic/symbolic.cc b/p4_symbolic/symbolic/symbolic.cc index f4f912c8..7f9e2fb5 100644 --- a/p4_symbolic/symbolic/symbolic.cc +++ b/p4_symbolic/symbolic/symbolic.cc @@ -32,12 +32,6 @@ namespace p4_symbolic { namespace symbolic { -// A port reserved to encode dropping packets. -// The value is arbitrary; we choose the same value as BMv2: -// https://github.com/p4lang/behavioral-model/blob/main/docs/simple_switch.md#standard-metadata -constexpr int kDropPort = 511; // 2^9 - 1. -constexpr int kPortBitwidth = 9; - z3::expr EgressSpecDroppedValue() { return Z3Context().bv_val(kDropPort, kPortBitwidth); } @@ -65,9 +59,21 @@ absl::Status CheckPhysicalPortsConformanceToV1Model( return absl::InvalidArgumentError(absl::Substitute( "Cannot use the value $0 as a physical port as the value does not " "fit into PortId_t (bit<9>), the type of " - "standard_metadata.{ingress/egress}_port in v1model.p4", + "standard_metadata.{ingress/egress}_port in v1model.p4.", port)); } + if (port == kDropPort) { + return absl::InvalidArgumentError( + absl::Substitute("Cannot use the value $0 as a physical port as the " + "value is reserved for dropping packets.", + port)); + } + if (port == kCpuPort) { + return absl::InvalidArgumentError( + absl::Substitute("Cannot use the value $0 as a physical port as the " + "value is reserved for the CPU port.", + port)); + } } return absl::OkStatus(); @@ -75,7 +81,7 @@ absl::Status CheckPhysicalPortsConformanceToV1Model( absl::StatusOr> EvaluateP4Pipeline( const Dataplane &data_plane, const std::vector &physical_ports, - const StaticTranslationPerType &translation_per_type) { + const TranslationPerType &translation_per_type) { // Check input physical ports. if (absl::Status status = CheckPhysicalPortsConformanceToV1Model(physical_ports); @@ -112,7 +118,7 @@ absl::StatusOr> EvaluateP4Pipeline( // Restrict the value of all fields with (purely static, i.e. // dynamic_translation = false) P4RT translated types to what has been used in - // StaticTranslationPerType. This should be done after the symbolic execution + // TranslationPerType. This should be done after the symbolic execution // since P4Symbolic does not initially know which fields have translated // types. for (const auto &[field, type] : translator.fields_p4runtime_type) { @@ -181,25 +187,19 @@ absl::StatusOr> EvaluateP4Pipeline( } absl::StatusOr> Solve( - const std::unique_ptr &solver_state, - const Assertion &assertion) { - z3::expr constraint = assertion(solver_state->context); - - solver_state->solver->push(); - solver_state->solver->add(constraint); - auto pop = absl::Cleanup([&] { solver_state->solver->pop(); }); - z3::check_result check_result = solver_state->solver->check(); + const SolverState &solver_state) { + z3::check_result check_result = solver_state.solver->check(); switch (check_result) { case z3::unsat: case z3::unknown: return absl::nullopt; case z3::sat: - z3::model packet_model = solver_state->solver->get_model(); + z3::model packet_model = solver_state.solver->get_model(); ASSIGN_OR_RETURN( ConcreteContext result, - util::ExtractFromModel(solver_state->context, packet_model, - solver_state->translator)); + util::ExtractFromModel(solver_state.context, packet_model, + solver_state.translator)); return result; } LOG(DFATAL) << "invalid Z3 check() result: " @@ -207,6 +207,21 @@ absl::StatusOr> Solve( return absl::nullopt; } +absl::StatusOr> Solve( + SolverState &solver_state, const Assertion &assertion) { + z3::expr constraint = assertion(solver_state.context); + solver_state.solver->push(); + solver_state.solver->add(constraint); + auto pop = absl::Cleanup([&] { solver_state.solver->pop(); }); + return Solve(solver_state); +} + +absl::StatusOr> Solve( + const std::unique_ptr &solver_state, + const Assertion &assertion) { + return Solve(*solver_state, assertion); +} + std::string DebugSMT(const std::unique_ptr &solver_state, const Assertion &assertion) { solver_state->solver->push(); diff --git a/p4_symbolic/symbolic/symbolic.h b/p4_symbolic/symbolic/symbolic.h index 25b773b3..9fd320d4 100644 --- a/p4_symbolic/symbolic/symbolic.h +++ b/p4_symbolic/symbolic/symbolic.h @@ -24,6 +24,8 @@ #include #include +#include "absl/base/attributes.h" +#include "absl/base/macros.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "gutil/status.h" @@ -36,6 +38,14 @@ namespace p4_symbolic { namespace symbolic { +// A port reserved to encode dropping packets. +// The value is arbitrary; we choose the same value as BMv2: +// https://github.com/p4lang/behavioral-model/blob/main/docs/simple_switch.md#standard-metadata +constexpr int kDropPort = 511; // 2^9 - 1. +// An arbitrary port we reserve for the CPU port (for PacketIO packets). +constexpr int kCpuPort = 510; // 2^9 - 2. +constexpr int kPortBitwidth = 9; + // Boolean pseudo header field that is set to true by p4-symbolic if the packet // gets cloned. Not an actual header field, but convenient for analysis. constexpr absl::string_view kGotClonedPseudoField = "$got_cloned$"; @@ -212,7 +222,7 @@ using Assertion = std::function; // TranslationData is used. For other types, if runtime translated (i.e. have // @p4runtime_translation("", string) annotation), // TranslationData{.static_mapping = {}, .dynamic_translation = true} is used. -using StaticTranslationPerType = +using TranslationPerType = absl::btree_map; // Symbolically evaluates/interprets the given program against the given @@ -223,10 +233,17 @@ using StaticTranslationPerType = // inferred dynamically for such types. absl::StatusOr> EvaluateP4Pipeline( const Dataplane &data_plane, const std::vector &physical_ports = {}, - const StaticTranslationPerType &translation_per_type = {}); + const TranslationPerType &translation_per_type = {}); // Finds a concrete packet and flow in the program that satisfies the given // assertion and meets the structure constrained by solver_state. +absl::StatusOr> Solve( + SolverState &solver_state, const Assertion &assertion); +absl::StatusOr> Solve( + const SolverState &solver_state); + +ABSL_DEPRECATED( + "Use the overload Solve(SolverState&, const Assertion&) instead.") absl::StatusOr> Solve( const std::unique_ptr &solver_state, const Assertion &assertion); From 7371f01fcfa8a42cb9c137d933daa36bef82b1ad Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:46:15 +0530 Subject: [PATCH 03/24] [Thinkit] Setting test case ids for hashing and smoke tests, BERT tests & PRBS BERT tests. (#707) Co-authored-by: jonathan-dilorenzo Co-authored-by: kishanps --- .../scripts/p4rt_route_measurement_test.cc | 72 +++--- tests/forwarding/p4_blackbox_fixture.h | 4 +- tests/forwarding/smoke_test.cc | 115 +++++++++- tests/forwarding/smoke_test.h | 4 +- tests/gnoi/BUILD.bazel | 3 +- tests/gnoi/bert_tests.cc | 216 ++++++++++++------ .../p4rt_fixed_table_programming_helper.cc | 4 +- .../lib/p4rt_fixed_table_programming_helper.h | 4 +- 8 files changed, 302 insertions(+), 120 deletions(-) diff --git a/p4rt_app/scripts/p4rt_route_measurement_test.cc b/p4rt_app/scripts/p4rt_route_measurement_test.cc index a2ed5295..8f3c4da2 100644 --- a/p4rt_app/scripts/p4rt_route_measurement_test.cc +++ b/p4rt_app/scripts/p4rt_route_measurement_test.cc @@ -72,9 +72,9 @@ DEFINE_string(server_address, "unix:/sock/p4rt.sock", DEFINE_bool(insecure, true, "Use insecure connection"); DEFINE_string(ca_cert, "/keys/ca_cert.lnk", "CA bundle file. Used when insecure is false"); -DEFINE_string(cert, "/keys/gpins_test_user.cert", +DEFINE_string(cert, "/keys/pins_test_user.cert", "Cert file. Used when insecure is false"); -DEFINE_string(key, "/keys/gpins_test_user.key", +DEFINE_string(key, "/keys/pins_test_user.key", "Key file. Used when insecure is false"); DEFINE_string(host_name, "", "Host name of the switch for validating the switch cert. Used " @@ -479,7 +479,7 @@ absl::Status GenerateRandomVrfs(absl::BitGen& bitgen, RouteEntryInfo& routes, std::string name = absl::StrCat("vrf-", vrf); ASSIGN_OR_RETURN( routes.vrfs_by_name[name], - gpins::VrfTableUpdate(ir_p4info, p4::v1::Update::INSERT, name)); + pins::VrfTableUpdate(ir_p4info, p4::v1::Update::INSERT, name)); } return absl::OkStatus(); } @@ -504,7 +504,7 @@ absl::Status GenerateRandomRIFs(absl::BitGen& bitgen, RouteEntryInfo& routes, std::string port_name = absl::StrCat(routes.port_ids[port]); ASSIGN_OR_RETURN( routes.router_interfaces_by_name[rif_name], - gpins::RouterInterfaceTableUpdate(ir_p4info, p4::v1::Update::INSERT, + pins::RouterInterfaceTableUpdate(ir_p4info, p4::v1::Update::INSERT, rif_name, port_name, mac.ToString())); routes.port_by_rif_name[rif_name] = port_name; } @@ -530,11 +530,11 @@ absl::Status GenerateRandomNextHops(absl::BitGen& bitgen, ASSIGN_OR_RETURN( routes.neighbors_by_name[neighbor_name], - gpins::NeighborTableUpdate(ir_p4info, p4::v1::Update::INSERT, rif, + pins::NeighborTableUpdate(ir_p4info, p4::v1::Update::INSERT, rif, neighbor_name, mac.ToString())); ASSIGN_OR_RETURN( routes.next_hops_by_name[nexthop_name], - gpins::NexthopTableUpdate(ir_p4info, p4::v1::Update::INSERT, + pins::NexthopTableUpdate(ir_p4info, p4::v1::Update::INSERT, nexthop_name, rif, neighbor_name)); std::string* port_name = gutil::FindOrNull(routes.port_by_rif_name, rif); @@ -575,7 +575,7 @@ absl::StatusOr ComputeEncapNeighbors( ASSIGN_OR_RETURN( route_entry.neighbors_by_name[neighbor_name], - gpins::NeighborTableUpdate(ir_p4info, p4::v1::Update::INSERT, rif, + pins::NeighborTableUpdate(ir_p4info, p4::v1::Update::INSERT, rif, neighbor_name, mac.ToString())); *requests.inserts.back().add_updates() = route_entry.neighbors_by_name[neighbor_name]; @@ -584,7 +584,7 @@ absl::StatusOr ComputeEncapNeighbors( // DELETE the entry. ASSIGN_OR_RETURN( *requests.deletes.back().add_updates(), - gpins::NeighborTableUpdate(ir_p4info, p4::v1::Update::DELETE, rif, + pins::NeighborTableUpdate(ir_p4info, p4::v1::Update::DELETE, rif, neighbor_name, mac.ToString())); } return requests; @@ -620,12 +620,12 @@ absl::StatusOr ComputeIpv4WriteRequests( nexthops[absl::Uniform(bitgen, 0, nexthops.size())]; ASSIGN_OR_RETURN( *requests.inserts.back().add_updates(), - gpins::Ipv4TableUpdate( + pins::Ipv4TableUpdate( ir_p4info, p4::v1::Update::INSERT, - gpins::IpTableOptions{ + pins::IpTableOptions{ .vrf_id = vrf, .dst_addr_lpm = std::make_pair(ip.ToString(), 32), - .action = gpins::IpTableOptions::Action::kSetNextHopId, + .action = pins::IpTableOptions::Action::kSetNextHopId, .nexthop_id = nexthop, })); @@ -633,24 +633,24 @@ absl::StatusOr ComputeIpv4WriteRequests( nexthop = nexthops[absl::Uniform(bitgen, 0, nexthops.size())]; ASSIGN_OR_RETURN( *requests.modifies.back().add_updates(), - gpins::Ipv4TableUpdate( + pins::Ipv4TableUpdate( ir_p4info, p4::v1::Update::MODIFY, - gpins::IpTableOptions{ + pins::IpTableOptions{ .vrf_id = vrf, .dst_addr_lpm = std::make_pair(ip.ToString(), 32), - .action = gpins::IpTableOptions::Action::kSetNextHopId, + .action = pins::IpTableOptions::Action::kSetNextHopId, .nexthop_id = nexthop, })); // DELETE the entry. ASSIGN_OR_RETURN( *requests.deletes.back().add_updates(), - gpins::Ipv4TableUpdate( + pins::Ipv4TableUpdate( ir_p4info, p4::v1::Update::DELETE, - gpins::IpTableOptions{ + pins::IpTableOptions{ .vrf_id = vrf, .dst_addr_lpm = std::make_pair(ip.ToString(), 32), - .action = gpins::IpTableOptions::Action::kSetNextHopId, + .action = pins::IpTableOptions::Action::kSetNextHopId, .nexthop_id = nexthop, })); } @@ -686,11 +686,11 @@ std::vector RandmizeWeights(absl::BitGen& bitgen, int size, return weights; } -absl::StatusOr> ComputeWcmpGroupAction( +absl::StatusOr> ComputeWcmpGroupAction( absl::BitGen& bitgen, int members_per_group, bool set_watch_port, const std::vector& nexthops, const absl::flat_hash_map& port_by_nexthop_name) { - std::vector actions(members_per_group); + std::vector actions(members_per_group); // Get a random set of next hops, but don't allow duplicates. ASSIGN_OR_RETURN(auto nexthop_indices, @@ -748,7 +748,7 @@ absl::StatusOr ComputeWcmpWriteRequests( // The initial INSERT request. ASSIGN_OR_RETURN( - std::vector actions, + std::vector actions, ComputeWcmpGroupAction(bitgen, members_per_group, set_watch_port, nexthops, routes.port_by_next_hop_name)); @@ -764,7 +764,7 @@ absl::StatusOr ComputeWcmpWriteRequests( ASSIGN_OR_RETURN( *requests.inserts[batch_num].add_updates(), - gpins::WcmpGroupTableUpdate(ir_p4info, p4::v1::Update::INSERT, + pins::WcmpGroupTableUpdate(ir_p4info, p4::v1::Update::INSERT, group_name, actions)); VLOG(1) << "WCMP insert: " << requests.inserts[batch_num].ShortDebugString(); @@ -783,7 +783,7 @@ absl::StatusOr ComputeWcmpWriteRequests( } ASSIGN_OR_RETURN( *requests.modifies[batch_num].add_updates(), - gpins::WcmpGroupTableUpdate(ir_p4info, p4::v1::Update::MODIFY, + pins::WcmpGroupTableUpdate(ir_p4info, p4::v1::Update::MODIFY, group_name, actions)); VLOG(1) << "WCMP modify: " << requests.modifies[batch_num].ShortDebugString(); @@ -791,7 +791,7 @@ absl::StatusOr ComputeWcmpWriteRequests( // DELETE the entries. ASSIGN_OR_RETURN( *requests.deletes[batch_num].add_updates(), - gpins::WcmpGroupTableUpdate(ir_p4info, p4::v1::Update::DELETE, + pins::WcmpGroupTableUpdate(ir_p4info, p4::v1::Update::DELETE, group_name, actions)); VLOG(1) << "WCMP delete: " << requests.deletes[batch_num].ShortDebugString(); @@ -843,12 +843,12 @@ absl::StatusOr ComputeIpv6WriteRequests( nexthops[absl::Uniform(bitgen, 0, nexthops.size())]; ASSIGN_OR_RETURN( *requests.inserts.back().add_updates(), - gpins::Ipv6TableUpdate( + pins::Ipv6TableUpdate( ir_p4info, p4::v1::Update::INSERT, - gpins::IpTableOptions{ + pins::IpTableOptions{ .vrf_id = vrf, .dst_addr_lpm = std::make_pair(ip.ToString(), 64), - .action = gpins::IpTableOptions::Action::kSetNextHopId, + .action = pins::IpTableOptions::Action::kSetNextHopId, .nexthop_id = nexthop, })); @@ -856,24 +856,24 @@ absl::StatusOr ComputeIpv6WriteRequests( nexthop = nexthops[absl::Uniform(bitgen, 0, nexthops.size())]; ASSIGN_OR_RETURN( *requests.modifies.back().add_updates(), - gpins::Ipv6TableUpdate( + pins::Ipv6TableUpdate( ir_p4info, p4::v1::Update::MODIFY, - gpins::IpTableOptions{ + pins::IpTableOptions{ .vrf_id = vrf, .dst_addr_lpm = std::make_pair(ip.ToString(), 64), - .action = gpins::IpTableOptions::Action::kSetNextHopId, + .action = pins::IpTableOptions::Action::kSetNextHopId, .nexthop_id = nexthop, })); // DELETE the entry. ASSIGN_OR_RETURN( *requests.deletes.back().add_updates(), - gpins::Ipv6TableUpdate( + pins::Ipv6TableUpdate( ir_p4info, p4::v1::Update::DELETE, - gpins::IpTableOptions{ + pins::IpTableOptions{ .vrf_id = vrf, .dst_addr_lpm = std::make_pair(ip.ToString(), 64), - .action = gpins::IpTableOptions::Action::kSetNextHopId, + .action = pins::IpTableOptions::Action::kSetNextHopId, .nexthop_id = nexthop, })); } @@ -926,13 +926,13 @@ absl::StatusOr ComputeEncapWriteRequests( std::string nexthop_name = absl::StrCat("nh-", tunnel_name); ASSIGN_OR_RETURN(*requests.inserts.back().add_updates(), - gpins::TunnelTableUpdate( + pins::TunnelTableUpdate( ir_p4info, p4::v1::Update::INSERT, tunnel_name, neighbor, src_ip6.ToString(), router_interface_id)); ASSIGN_OR_RETURN( route_entry.next_hops_by_name[nexthop_name], - gpins::NexthopTunnelTableUpdate(ir_p4info, p4::v1::Update::INSERT, + pins::NexthopTunnelTableUpdate(ir_p4info, p4::v1::Update::INSERT, nexthop_name, tunnel_name)); *requests.inserts.back().add_updates() = route_entry.next_hops_by_name[nexthop_name]; @@ -942,11 +942,11 @@ absl::StatusOr ComputeEncapWriteRequests( // DELETE the entry. ASSIGN_OR_RETURN( *requests.deletes.back().add_updates(), - gpins::NexthopTunnelTableUpdate(ir_p4info, p4::v1::Update::DELETE, + pins::NexthopTunnelTableUpdate(ir_p4info, p4::v1::Update::DELETE, nexthop_name, tunnel_name)); ASSIGN_OR_RETURN(*requests.deletes.back().add_updates(), - gpins::TunnelTableUpdate( + pins::TunnelTableUpdate( ir_p4info, p4::v1::Update::DELETE, tunnel_name, neighbor, src_ip6.ToString(), router_interface_id)); diff --git a/tests/forwarding/p4_blackbox_fixture.h b/tests/forwarding/p4_blackbox_fixture.h index 57bbcc82..f86f2f92 100644 --- a/tests/forwarding/p4_blackbox_fixture.h +++ b/tests/forwarding/p4_blackbox_fixture.h @@ -26,7 +26,7 @@ #include "thinkit/mirror_testbed.h" #include "thinkit/mirror_testbed_fixture.h" -namespace gpins { +namespace pins { // Fixture for P4 blackbox testing. It performs test specific setup and // teardown: Creates an initializes a P4RT channel, to get the switch ready to @@ -94,6 +94,6 @@ class P4BlackboxFixture : public thinkit::MirrorTestbedFixture { sai::GetIrP4Info(sai::Instantiation::kMiddleblock); }; -} // namespace gpins +} // namespace pins #endif // PINS_TESTS_FORWARDING_P4_BLACKBOX_FIXTURE_H_ diff --git a/tests/forwarding/smoke_test.cc b/tests/forwarding/smoke_test.cc index 3c72795f..4432271d 100644 --- a/tests/forwarding/smoke_test.cc +++ b/tests/forwarding/smoke_test.cc @@ -28,10 +28,12 @@ #include "sai_p4/instantiations/google/sai_pd.pb.h" #include "tests/forwarding/test_data.h" -namespace gpins { +namespace pins { namespace { TEST_P(SmokeTestFixture, ModifyWorks) { + GetMirrorTestbed().Environment().SetTestCaseID( + "3b18d5dc-3881-42a5-b667-d2ca0362ab3a"); const sai::WriteRequest pd_insert = gutil::ParseProtoOrDie( R"pb( updates { @@ -71,7 +73,112 @@ TEST_P(SmokeTestFixture, ModifyWorks) { ASSERT_OK(pdpi::ClearTableEntries(SutP4RuntimeSession())); } +// TODO: Enable once the bug is fixed. +TEST_P(SmokeTestFixture, + DISABLED_InstallDefaultRouteForEmptyStringVrfShouldSucceed) { + GetMirrorTestbed().Environment().SetTestCaseID( + "2d67413c-9b6e-4187-84d4-c9313b84cab3"); + const sai::TableEntry pd_entry = gutil::ParseProtoOrDie( + R"pb( + ipv4_table_entry { + match { vrf_id: "" } + action { drop {} } + } + )pb"); + + ASSERT_OK_AND_ASSIGN(const p4::v1::TableEntry pi_entry, + pdpi::PartialPdTableEntryToPiTableEntry(IrP4Info(), pd_entry)); + ASSERT_OK(pdpi::InstallPiTableEntry(SutP4RuntimeSession(), pi_entry)); +} + +// TODO: Enable once the bug is fixed. +TEST_P(SmokeTestFixture, DISABLED_Bug181149419) { + GetMirrorTestbed().Environment().SetTestCaseID( + "e6ba12b7-18e0-4681-9562-87e2fc01d429"); + // Adding 8 mirror sessions should succeed. + for (int i = 0; i < 8; i++) { + sai::TableEntry pd_entry = gutil::ParseProtoOrDie( + R"pb( + mirror_session_table_entry { + match { mirror_session_id: "session" } + action { + mirror_as_ipv4_erspan { + port: "1" + src_ip: "10.206.196.0" + dst_ip: "172.20.0.202" + src_mac: "00:02:03:04:05:06" + dst_mac: "00:1a:11:17:5f:80" + ttl: "0x40" + tos: "0x00" + } + } + } + )pb"); + pd_entry.mutable_mirror_session_table_entry() + ->mutable_match() + ->set_mirror_session_id(absl::StrCat("session-", i)); + + ASSERT_OK_AND_ASSIGN(const p4::v1::TableEntry pi_entry, + pdpi::PartialPdTableEntryToPiTableEntry(IrP4Info(), pd_entry)); + EXPECT_OK(pdpi::InstallPiTableEntry(SutP4RuntimeSession(), pi_entry)); + } + // Adding one entry above the limit will fail. + { + sai::TableEntry pd_entry = gutil::ParseProtoOrDie( + R"pb( + mirror_session_table_entry { + match { mirror_session_id: "session-9" } + action { + mirror_as_ipv4_erspan { + port: "1" + src_ip: "10.206.196.0" + dst_ip: "172.20.0.202" + src_mac: "00:02:03:04:05:06" + dst_mac: "00:1a:11:17:5f:80" + ttl: "0x40" + tos: "0x00" + } + } + } + )pb"); + + ASSERT_OK_AND_ASSIGN(const p4::v1::TableEntry pi_entry, + pdpi::PartialPdTableEntryToPiTableEntry(IrP4Info(), pd_entry)); + EXPECT_FALSE( + pdpi::InstallPiTableEntry(SutP4RuntimeSession(), pi_entry).ok()); + } + // Adding ACL entries that use the 8 mirrors should all succeed. + for (int i = 0; i < 8; i++) { + sai::TableEntry pd_entry = gutil::ParseProtoOrDie( + R"pb( + acl_ingress_table_entry { + match { + is_ipv4 { value: "0x1" } + src_ip { value: "10.0.0.0" mask: "255.255.255.255" } + dscp { value: "0x1c" mask: "0x3c" } + } + action { mirror { mirror_session_id: "session-1" } } + priority: 2100 + } + )pb"); + pd_entry.mutable_acl_ingress_table_entry() + ->mutable_action() + ->mutable_acl_mirror() + ->set_mirror_session_id(absl::StrCat("session-", i)); + pd_entry.mutable_acl_ingress_table_entry() + ->mutable_match() + ->mutable_src_ip() + ->set_value(absl::StrCat("10.0.0.", i)); + + ASSERT_OK_AND_ASSIGN(const p4::v1::TableEntry pi_entry, + pdpi::PartialPdTableEntryToPiTableEntry(IrP4Info(), pd_entry)); + ASSERT_OK(pdpi::InstallPiTableEntry(SutP4RuntimeSession(), pi_entry)); + } +} + TEST_P(SmokeTestFixture, InsertTableEntry) { + GetMirrorTestbed().Environment().SetTestCaseID( + "da103fbb-8fd4-4385-b997-34e12a41004b"); const sai::TableEntry pd_entry = gutil::ParseProtoOrDie( R"pb( router_interface_table_entry { @@ -88,6 +195,8 @@ TEST_P(SmokeTestFixture, InsertTableEntry) { } TEST_P(SmokeTestFixture, InsertTableEntryWithRandomCharacterId) { + GetMirrorTestbed().Environment().SetTestCaseID( + "bd22f5fe-4103-4729-91d0-cb2bc8258940"); sai::TableEntry pd_entry = gutil::ParseProtoOrDie( R"pb( router_interface_table_entry { @@ -108,6 +217,8 @@ TEST_P(SmokeTestFixture, InsertTableEntryWithRandomCharacterId) { } TEST_P(SmokeTestFixture, InsertAndReadTableEntries) { + GetMirrorTestbed().Environment().SetTestCaseID( + "8bdacde4-b261-4242-b65d-462c828a427d"); pdpi::P4RuntimeSession* session = SutP4RuntimeSession(); const pdpi::IrP4Info& ir_p4info = IrP4Info(); std::vector write_pd_entries = @@ -156,4 +267,4 @@ TEST_P(SmokeTestFixture, InsertAndReadTableEntries) { } } // namespace -} // namespace gpins +} // namespace pins diff --git a/tests/forwarding/smoke_test.h b/tests/forwarding/smoke_test.h index 700b2171..c2584fe1 100644 --- a/tests/forwarding/smoke_test.h +++ b/tests/forwarding/smoke_test.h @@ -18,10 +18,10 @@ #include "tests/forwarding/p4_blackbox_fixture.h" #include "thinkit/mirror_testbed_fixture.h" -namespace gpins { +namespace pins { class SmokeTestFixture : public P4BlackboxFixture {}; -} // namespace gpins +} // namespace pins #endif // PINS_TESTS_FORWARDING_SMOKE_TEST_H_ diff --git a/tests/gnoi/BUILD.bazel b/tests/gnoi/BUILD.bazel index 78897c9f..fa89d2b2 100644 --- a/tests/gnoi/BUILD.bazel +++ b/tests/gnoi/BUILD.bazel @@ -35,9 +35,10 @@ cc_library( "//thinkit:test_environment", "@com_github_gnoi//diag:diag_cc_proto", "@com_github_google_glog//:glog", + "@com_github_grpc_grpc//:grpc++", "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/flags:flag", - "@com_github_grpc_grpc//:grpc++", + "@com_google_absl//absl/random", "@com_google_absl//absl/strings", "@com_google_absl//absl/time", "@com_google_googletest//:gtest", diff --git a/tests/gnoi/bert_tests.cc b/tests/gnoi/bert_tests.cc index 403988d0..efbd3657 100644 --- a/tests/gnoi/bert_tests.cc +++ b/tests/gnoi/bert_tests.cc @@ -17,7 +17,7 @@ #include #include "absl/container/flat_hash_set.h" #include "absl/flags/flag.h" - +#include "absl/random/random.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" #include "absl/strings/substitute.h" @@ -25,6 +25,7 @@ #include "absl/time/time.h" #include "diag/diag.pb.h" #include "glog/logging.h" +#include "gmock/gmock.h" #include "google/protobuf/text_format.h" #include "google/protobuf/util/message_differencer.h" #include "gtest/gtest.h" @@ -37,6 +38,14 @@ ABSL_FLAG(uint32_t, idx_seed, static_cast(std::time(nullptr)), "Seed to randomly generate interface index."); +ABSL_FLAG(std::string, interface, "", + "Interface to run qualification on. If unspecified, random port " + "will be chosen."); + +ABSL_FLAG(std::vector, interfaces, std::vector(), + "Interfaces to run qualification on. If unspecified, random ports " + "will be chosen."); + namespace bert { using ::google::protobuf::util::MessageDifferencer; @@ -91,6 +100,15 @@ const std::string BuildOpenConfigInterface(absl::string_view interface_name) { interface_name); } +void SendStartBertRequestSuccessfully( + gnoi::diag::StartBERTRequest& request, + gnoi::diag::Diag::StubInterface& gnoi_diag_stub) { + gnoi::diag::StartBERTResponse response; + grpc::ClientContext context; + LOG(INFO) << "StartBERT request: " << request.ShortDebugString(); + ASSERT_OK(gnoi_diag_stub.StartBERT(&context, request, &response)); +} + absl::StatusOr GetInterfaceNameFromOcInterfacePath( const gnoi::types::Path& interface) { // Validate if interface is in valid format(either in OpenConfig format or @@ -133,7 +151,7 @@ absl::StatusOr GetInterfaceNameFromOcInterfacePath( absl::StrCat("Interface name is missing in: ", interface.DebugString())); } -void SetAdminStateOnInterfaceList(gnmi::gNMI::Stub& gnmi_stub, +void SetAdminStateOnInterfaceList(gnmi::gNMI::StubInterface& gnmi_stub, std::vector& interfaces, absl::string_view value) { for (const std::string& interface : interfaces) { @@ -182,7 +200,8 @@ void VerifyBertResultForSuccess( // the port, forces admin status down by disabling it. It also modifies the // list of ports in request by removing the port that was running BERT. void CheckRunningBertAndForceAdminDownHelper( - gnoi::diag::Diag::Stub& gnoi_diag_stub, gnmi::gNMI::Stub& gnmi_stub, + gnoi::diag::Diag::StubInterface& gnoi_diag_stub, + gnmi::gNMI::StubInterface& gnmi_stub, gnoi::diag::GetBERTResultRequest* request) { gnoi::diag::GetBERTResultResponse response; grpc::ClientContext context; @@ -215,9 +234,10 @@ void CheckRunningBertAndForceAdminDownHelper( // Checks if BERT is running on the ports where we are supposed to force admin // status DOWN. If BERT is running, force admin status to DOWN on port. void CheckRunningBertAndForceAdminDown( - gnoi::diag::Diag::Stub& sut_gnoi_diag_stub, - gnoi::diag::Diag::Stub& control_switch_gnoi_diag_stub, - gnmi::gNMI::Stub& sut_gnmi_stub, gnmi::gNMI::Stub& control_switch_gnmi_stub, + gnoi::diag::Diag::StubInterface& sut_gnoi_diag_stub, + gnoi::diag::Diag::StubInterface& control_switch_gnoi_diag_stub, + gnmi::gNMI::StubInterface& sut_gnmi_stub, + gnmi::gNMI::StubInterface& control_switch_gnmi_stub, absl::string_view op_id, std::vector& sut_interfaces, std::vector& control_switch_interfaces) { gnoi::diag::GetBERTResultRequest sut_request; @@ -269,7 +289,7 @@ void CheckRunningBertAndForceAdminDown( // failure result on ports where admin status was forced to DOWN. Other ports // are expected to have successful BERT results. void GetAndVerifyBertResultsWithAdminDownInterfaces( - gnoi::diag::Diag::Stub& gnoi_diag_stub, + gnoi::diag::Diag::StubInterface& gnoi_diag_stub, gnoi::diag::StartBERTRequest& bert_request, std::vector& sut_admin_down_interfaces, std::vector& control_switch_admin_down_interfaces) { @@ -314,7 +334,17 @@ void GetAndVerifyBertResultsWithAdminDownInterfaces( } } - +void SelectNUpInterfaces(int port_count_to_select, + gnmi::gNMI::StubInterface& gnmi_stub, + std::vector* interfaces) { + // Get all the interfaces that are operational status "UP". + ASSERT_OK_AND_ASSIGN(*interfaces, + pins_test::GetUpInterfacesOverGnmi(gnmi_stub)); + // Choose random ports. + ASSERT_GE(interfaces->size(), port_count_to_select); + std::shuffle(interfaces->begin(), interfaces->end(), absl::BitGen()); + interfaces->resize(port_count_to_select); +} // Test StartBERT with invalid request parameters. TEST_P(BertTest, StartBertFailsIfRequestParametersInvalid) { @@ -322,12 +352,24 @@ TEST_P(BertTest, StartBertFailsIfRequestParametersInvalid) { "c1dcb1cc-4806-45cc-8f8a-676beafde103"); thinkit::Switch& sut = GetMirrorTestbed().Sut(); // TODO (b/176913347): Enable the all ports UP check. - // ASSERT_OK(pins_test::PortsUp(sut)); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr sut_gnoi_diag_stub, - sut.CreateGnoiDiagStub()); + ASSERT_OK(pins_test::PortsUp(sut)); + ASSERT_OK_AND_ASSIGN(auto sut_gnoi_diag_stub, sut.CreateGnoiDiagStub()); + + // Select one operational state "up" port. + std::string interface = absl::GetFlag(FLAGS_interface); + if (interface.empty()) { + std::vector interfaces; + ASSERT_OK_AND_ASSIGN( + std::unique_ptr sut_gnmi_stub, + sut.CreateGnmiStub()); + ASSERT_NO_FATAL_FAILURE( + SelectNUpInterfaces(1, *sut_gnmi_stub, &interfaces)); + interface = interfaces[0]; + } + LOG(INFO) << "Selected interface: " + << interface << ". To repeat the test with same interface, use " + << "--test_arg=--interface=" << interface << " in test arguments."; - // TODO (b/182417612) : Select one operational state "up" port. gnoi::diag::StartBERTRequest valid_request; // Create the BERT request. valid_request.set_bert_operation_id( @@ -411,10 +453,8 @@ TEST_P(BertTest, StopBertfailsIfRequestParametersInvalid) { "224db9cf-c709-486d-a0d3-6ab64c1a1e1f"); thinkit::Switch& sut = GetMirrorTestbed().Sut(); // TODO (b/176913347): Enable the all ports UP check. - // ASSERT_OK(pins_test::PortsUp(sut)); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr sut_gnoi_diag_stub, - sut.CreateGnoiDiagStub()); + ASSERT_OK(pins_test::PortsUp(sut)); + ASSERT_OK_AND_ASSIGN(auto sut_gnoi_diag_stub, sut.CreateGnoiDiagStub()); // Request StopBERT RPC on an invalid port, RPC should fail. { @@ -439,14 +479,26 @@ TEST_P(BertTest, StopBertfailsIfRequestParametersInvalid) { // Request StopBERT RPC on a port that is not running BERT, RPC should fail. { - // TODO (b/182417612) : Select one operational state "up" port. - constexpr char kInterface[] = "Ethernet0"; + // Select one operational state "up" port. + std::string interface = absl::GetFlag(FLAGS_interface); + if (interface.empty()) { + std::vector interfaces; + ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); + ASSERT_NO_FATAL_FAILURE( + SelectNUpInterfaces(1, *sut_gnmi_stub, &interfaces)); + interface = interfaces[0]; + } + LOG(INFO) << "Selected interface: " + << interface << ". To repeat the test with same interface, use " + << "--test_arg=--interface=" + << interface << " in test arguments."; + gnoi::diag::StopBERTRequest request; request.set_bert_operation_id( absl::StrCat("OpId-", absl::ToUnixMillis(absl::Now()))); *(request.add_per_port_requests()->mutable_interface()) = gutil::ParseProtoOrDie( - BuildOpenConfigInterface(kInterface)); + BuildOpenConfigInterface(interface)); gnoi::diag::StopBERTResponse response; grpc::ClientContext context; LOG(INFO) << "Sending StopBERT request: " << request.ShortDebugString(); @@ -470,10 +522,8 @@ TEST_P(BertTest, GetBertResultFailsIfRequestParametersInvalid) { "4f837d7a-ab44-4694-9ca9-399d576757f4"); thinkit::Switch& sut = GetMirrorTestbed().Sut(); // TODO (b/176913347): Enable the all ports UP check. - // ASSERT_OK(pins_test::PortsUp(sut)); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr sut_gnoi_diag_stub, - sut.CreateGnoiDiagStub()); + ASSERT_OK(pins_test::PortsUp(sut)); + ASSERT_OK_AND_ASSIGN(auto sut_gnoi_diag_stub, sut.CreateGnoiDiagStub()); // Request GetBERTResult RPC on an invalid port, RPC should fail. { @@ -497,14 +547,26 @@ TEST_P(BertTest, GetBertResultFailsIfRequestParametersInvalid) { // Request GetBERTResult RPC on a port that never ran BERT before, RPC should // fail. { - // TODO (b/182417612) : Select one operational state "up" port. - constexpr char kInterface[] = "Ethernet0"; + // Select one operational state "up" port. + std::string interface = absl::GetFlag(FLAGS_interface); + if (interface.empty()) { + std::vector interfaces; + ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); + ASSERT_NO_FATAL_FAILURE( + SelectNUpInterfaces(1, *sut_gnmi_stub, &interfaces)); + interface = interfaces[0]; + } + LOG(INFO) << "Selected interface: " + << interface << ". To repeat the test with same interface, use " + << "--test_arg=--interface=" + << interface << " in test arguments."; + gnoi::diag::GetBERTResultRequest result_request; result_request.set_bert_operation_id( absl::StrCat("OpId-", absl::ToUnixMillis(absl::Now()))); *(result_request.add_per_port_requests()->mutable_interface()) = gutil::ParseProtoOrDie( - BuildOpenConfigInterface(kInterface)); + BuildOpenConfigInterface(interface)); gnoi::diag::GetBERTResultResponse result_response; grpc::ClientContext context; @@ -514,7 +576,7 @@ TEST_P(BertTest, GetBertResultFailsIfRequestParametersInvalid) { &context, result_request, &result_response)), gutil::StatusIs(absl::StatusCode::kInvalidArgument, AllOf(HasSubstr("Result is not found for intf"), - HasSubstr(kInterface)))); + HasSubstr(interface)))); } // TODO (b/176913347): Enable the all ports UP check. @@ -527,23 +589,30 @@ TEST_P(BertTest, StartBertfailsIfPeerPortNotRunningBert) { GetMirrorTestbed().Environment().SetTestCaseID( "37e48922-0616-4d16-8fd3-975897491956"); thinkit::Switch& sut = GetMirrorTestbed().Sut(); - ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_gnmi_stub, - sut.CreateGnmiStub()); // TODO (b/176913347): Enable the all ports UP check. - // ASSERT_OK(pins_test::PortsUp(sut)); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr sut_gnoi_diag_stub, - sut.CreateGnoiDiagStub()); + ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); + ASSERT_OK(pins_test::PortsUp(sut)); + ASSERT_OK_AND_ASSIGN(auto sut_gnoi_diag_stub, sut.CreateGnoiDiagStub()); + + // Select one operational state "up" port. + std::string interface = absl::GetFlag(FLAGS_interface); + if (interface.empty()) { + std::vector interfaces; + ASSERT_NO_FATAL_FAILURE( + SelectNUpInterfaces(1, *sut_gnmi_stub, &interfaces)); + interface = interfaces[0]; + } + LOG(INFO) << "Selected interface: " + << interface << ". To repeat the test with same interface, use " + << "--test_arg=--interface=" << interface << " in test arguments."; - // TODO (b/182417612) : Select one operational state "up" port. - constexpr char kInterface[] = "Ethernet0"; gnoi::diag::StartBERTRequest bert_request; // Create the BERT request. bert_request.set_bert_operation_id( absl::StrCat("OpId-", absl::ToUnixMillis(absl::Now()))); *(bert_request.add_per_port_requests()) = gutil::ParseProtoOrDie( - BuildPerPortStartBertRequest(kInterface)); + BuildPerPortStartBertRequest(interface)); gnoi::diag::StartBERTResponse bert_response; grpc::ClientContext context; LOG(INFO) << "Sending StartBERT request: " << bert_request.ShortDebugString(); @@ -558,7 +627,7 @@ TEST_P(BertTest, StartBertfailsIfPeerPortNotRunningBert) { absl::SleepFor(kPollInterval); ASSERT_OK_AND_ASSIGN( pins_test::OperStatus oper_status, - pins_test::GetInterfaceOperStatusOverGnmi(*sut_gnmi_stub, kInterface)); + pins_test::GetInterfaceOperStatusOverGnmi(*sut_gnmi_stub, interface)); // If port is "UP" and no longer in "TESTING" oper state, BERT has completed // on the port and full BERT result is ready for read. if (oper_status == pins_test::OperStatus::kUp) { @@ -601,26 +670,31 @@ TEST_P(BertTest, StartBertSucceeds) { GetMirrorTestbed().Environment().SetTestCaseID( "b31a796a-d078-4d45-b785-f09ec598e05a"); thinkit::Switch& sut = GetMirrorTestbed().Sut(); - ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_gnmi_stub, - sut.CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); // TODO (b/176913347): Enable the all ports UP check. - // ASSERT_OK(pins_test::PortsUp(sut)); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr sut_gnoi_diag_stub, - sut.CreateGnoiDiagStub()); + ASSERT_OK(pins_test::PortsUp(sut)); + ASSERT_OK_AND_ASSIGN(auto sut_gnoi_diag_stub, sut.CreateGnoiDiagStub()); thinkit::Switch& control_switch = GetMirrorTestbed().ControlSwitch(); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr control_switch_gnmi_stub, - control_switch.CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN(auto control_switch_gnmi_stub, + control_switch.CreateGnmiStub()); // TODO (b/176913347): Enable the all ports UP check. - // ASSERT_OK(pins_test::PortsUp(control_switch)); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr control_switch_gnoi_diag_stub, - control_switch.CreateGnoiDiagStub()); + ASSERT_OK(pins_test::PortsUp(control_switch)); + ASSERT_OK_AND_ASSIGN(auto control_switch_gnoi_diag_stub, + control_switch.CreateGnoiDiagStub()); + + // Select 2 operational state "up" ports. + std::vector interfaces = absl::GetFlag(FLAGS_interfaces); + if (interfaces.empty()) { + ASSERT_NO_FATAL_FAILURE( + SelectNUpInterfaces(2, *sut_gnmi_stub, &interfaces)); + } + ASSERT_THAT(interfaces, SizeIs(2)); + LOG(INFO) << "Selected interfaces: " << absl::StrJoin(interfaces, ",") + << ". To repeat the test with same interfaces, " + << "use --test_arg=--interfaces=" << absl::StrJoin(interfaces, ",") + << " in test arguments."; - // TODO (b/182417612) : Select 2 operational state "up" ports. - std::vector interfaces = {"Ethernet0", "Ethernet8"}; gnoi::diag::StartBERTRequest bert_request; // Create the BERT request. bert_request.set_bert_operation_id( @@ -812,21 +886,16 @@ TEST_P(BertTest, RunBertOnMaximumAllowedPorts) { GetMirrorTestbed().Environment().SetTestCaseID( "ce526e97-2a62-4044-9dce-8fc74b232e4b"); thinkit::Switch& sut = GetMirrorTestbed().Sut(); - ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_gnmi_stub, - sut.CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); ASSERT_OK(pins_test::PortsUp(sut)); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr sut_gnoi_diag_stub, - sut.CreateGnoiDiagStub()); + ASSERT_OK_AND_ASSIGN(auto sut_gnoi_diag_stub, sut.CreateGnoiDiagStub()); thinkit::Switch& control_switch = GetMirrorTestbed().ControlSwitch(); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr control_switch_gnmi_stub, - control_switch.CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN(auto control_switch_gnmi_stub, + control_switch.CreateGnmiStub()); ASSERT_OK(pins_test::PortsUp(control_switch)); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr control_switch_gnoi_diag_stub, - control_switch.CreateGnoiDiagStub()); + ASSERT_OK_AND_ASSIGN(auto control_switch_gnoi_diag_stub, + control_switch.CreateGnoiDiagStub()); // Get all the interfaces that are operational status "UP". ASSERT_OK_AND_ASSIGN(std::vector interfaces, @@ -902,30 +971,31 @@ TEST_P(BertTest, RunBertOnMaximumAllowedPorts) { kTestDuration - (end_time - start_time) - absl::Seconds(1)) / ToInt64Seconds(kPollInterval)); - std::vector interfaces_not_up = interfaces; + std::vector interfaces_in_testing = interfaces; for (int count = 0; count < max_poll_count; ++count) { absl::SleepFor(kPollInterval); - // If port is "UP" and no longer in "TESTING" oper state on both sides of - // link, BERT has completed on the link and full BERT result is ready. - for (auto it = interfaces_not_up.begin(); it != interfaces_not_up.end();) { + // If port is no longer in "TESTING" oper state on both sides of the link, + // linkqual has completed on the link and full result is ready. + for (auto it = interfaces_in_testing.begin(); + it != interfaces_in_testing.end();) { ASSERT_OK_AND_ASSIGN( pins_test::OperStatus oper_status, pins_test::GetInterfaceOperStatusOverGnmi(*sut_gnmi_stub, *it)); - if (oper_status == pins_test::OperStatus::kUp) { + if (oper_status != pins_test::OperStatus::kTesting) { ASSERT_OK_AND_ASSIGN(oper_status, pins_test::GetInterfaceOperStatusOverGnmi( *control_switch_gnmi_stub, *it)); - if (oper_status == pins_test::OperStatus::kUp) { - it = interfaces_not_up.erase(it); + if (oper_status != pins_test::OperStatus::kTesting) { + it = interfaces_in_testing.erase(it); continue; } } ++it; } - if (interfaces_not_up.empty()) break; + if (interfaces_in_testing.empty()) break; } - EXPECT_THAT(interfaces_not_up, testing::IsEmpty()); + EXPECT_THAT(interfaces_in_testing, testing::IsEmpty()); // Wait for some time before checking the port status. absl::SleepFor(absl::Seconds(10)); diff --git a/tests/lib/p4rt_fixed_table_programming_helper.cc b/tests/lib/p4rt_fixed_table_programming_helper.cc index 40e4249d..dd841bd1 100644 --- a/tests/lib/p4rt_fixed_table_programming_helper.cc +++ b/tests/lib/p4rt_fixed_table_programming_helper.cc @@ -22,7 +22,7 @@ #include "p4_pdpi/ir.h" #include "p4_pdpi/ir.pb.h" -namespace gpins { +namespace pins { namespace { @@ -392,4 +392,4 @@ absl::StatusOr WcmpGroupTableUpdate( return pdpi::IrUpdateToPi(ir_p4_info, ir_update); } -} // namespace gpins +} // namespace pins diff --git a/tests/lib/p4rt_fixed_table_programming_helper.h b/tests/lib/p4rt_fixed_table_programming_helper.h index f3c92509..4bf8507b 100644 --- a/tests/lib/p4rt_fixed_table_programming_helper.h +++ b/tests/lib/p4rt_fixed_table_programming_helper.h @@ -22,7 +22,7 @@ #include "p4/v1/p4runtime.pb.h" #include "p4_pdpi/ir.pb.h" -namespace gpins { +namespace pins { absl::StatusOr RouterInterfaceTableUpdate( const pdpi::IrP4Info& ir_p4_info, p4::v1::Update::Type type, @@ -103,6 +103,6 @@ absl::StatusOr WcmpGroupTableUpdate( const pdpi::IrP4Info& ir_p4_info, p4::v1::Update::Type type, absl::string_view wcmp_group_id, const std::vector& actions); -} // namespace gpins +} // namespace pins #endif // GOOGLE_TESTS_LIB_P4RT_FIXED_TABLE_PROGRAMMING_HELPER_H_ From 5e9a601aa63c4a702367564761b19549e77e79b5 Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:48:51 +0530 Subject: [PATCH 04/24] [Thinkit] Add StopBert test, BERT tests, Re-enable default VRF test case, now that it has been fixed & Increase wait time after re-enabling admin down ports. (#708) Co-authored-by: jonathan-dilorenzo Co-authored-by: kishanps --- tests/forwarding/smoke_test.cc | 4 +- tests/gnoi/bert_tests.cc | 349 +++++++++++++++++++++++---------- 2 files changed, 249 insertions(+), 104 deletions(-) diff --git a/tests/forwarding/smoke_test.cc b/tests/forwarding/smoke_test.cc index 4432271d..cb8b70f4 100644 --- a/tests/forwarding/smoke_test.cc +++ b/tests/forwarding/smoke_test.cc @@ -73,9 +73,7 @@ TEST_P(SmokeTestFixture, ModifyWorks) { ASSERT_OK(pdpi::ClearTableEntries(SutP4RuntimeSession())); } -// TODO: Enable once the bug is fixed. -TEST_P(SmokeTestFixture, - DISABLED_InstallDefaultRouteForEmptyStringVrfShouldSucceed) { +TEST_P(SmokeTestFixture, InstallDefaultRouteForEmptyStringVrfShouldSucceed) { GetMirrorTestbed().Environment().SetTestCaseID( "2d67413c-9b6e-4187-84d4-c9313b84cab3"); const sai::TableEntry pd_entry = gutil::ParseProtoOrDie( diff --git a/tests/gnoi/bert_tests.cc b/tests/gnoi/bert_tests.cc index efbd3657..2e63fd8a 100644 --- a/tests/gnoi/bert_tests.cc +++ b/tests/gnoi/bert_tests.cc @@ -65,6 +65,8 @@ constexpr absl::Duration kWaitToReadOperStatus = absl::Seconds(30); constexpr absl::Duration kPollInterval = absl::Seconds(30); // Minimum wait time after the BERT request to read the BERT result. constexpr absl::Duration kWaitTime = absl::Seconds(30); +// Wait time for ports to be up after re-enabling admin down ports. +constexpr absl::Duration kPortsUpWaitTime = absl::Seconds(30); constexpr uint8_t kMaxAllowedInterfacesToRunBert = 96; @@ -107,6 +109,21 @@ void SendStartBertRequestSuccessfully( grpc::ClientContext context; LOG(INFO) << "StartBERT request: " << request.ShortDebugString(); ASSERT_OK(gnoi_diag_stub.StartBERT(&context, request, &response)); + + // Verify response. + ASSERT_THAT(response.per_port_responses(), + SizeIs(request.per_port_requests_size())); + EXPECT_EQ(response.bert_operation_id(), request.bert_operation_id()); + + for (int idx = 0; idx < response.per_port_responses_size(); ++idx) { + auto per_port_response = response.per_port_responses(idx); + SCOPED_TRACE(absl::StrCat("Verifying StartBERT port response: ", + per_port_response.ShortDebugString())); + EXPECT_EQ(per_port_response.status(), gnoi::diag::BERT_STATUS_OK); + EXPECT_TRUE( + MessageDifferencer::Equals(per_port_response.interface(), + request.per_port_requests(idx).interface())); + } } absl::StatusOr GetInterfaceNameFromOcInterfacePath( @@ -168,6 +185,35 @@ bool IsInterfaceInList(absl::string_view interface, interfaces.end(); } +void WaitForBertToCompleteOnInterfaces( + gnmi::gNMI::StubInterface& sut_gnmi_stub, + gnmi::gNMI::StubInterface& control_switch_gnmi_stub, + std::vector& interfaces, int max_poll_count) { + for (int count = 0; count < max_poll_count; ++count) { + absl::SleepFor(kPollInterval); + // If port is no longer in "TESTING" oper state on both sides of the link, + // linkqual has completed on the link and full result is ready. + for (auto it = interfaces.begin(); it != interfaces.end();) { + ASSERT_OK_AND_ASSIGN( + pins_test::OperStatus oper_status, + pins_test::GetInterfaceOperStatusOverGnmi(sut_gnmi_stub, *it)); + if (oper_status != pins_test::OperStatus::kTesting) { + ASSERT_OK_AND_ASSIGN(oper_status, + pins_test::GetInterfaceOperStatusOverGnmi( + control_switch_gnmi_stub, *it)); + if (oper_status != pins_test::OperStatus::kTesting) { + it = interfaces.erase(it); + continue; + } + } + ++it; + } + if (interfaces.empty()) break; + } + + EXPECT_THAT(interfaces, testing::IsEmpty()); +} + void VerifyBertResultForSuccess( const gnoi::diag::GetBERTResultResponse::PerPortResponse& bert_result, absl::string_view op_id, const gnoi::types::Path& interface, @@ -613,12 +659,11 @@ TEST_P(BertTest, StartBertfailsIfPeerPortNotRunningBert) { *(bert_request.add_per_port_requests()) = gutil::ParseProtoOrDie( BuildPerPortStartBertRequest(interface)); - gnoi::diag::StartBERTResponse bert_response; - grpc::ClientContext context; - LOG(INFO) << "Sending StartBERT request: " << bert_request.ShortDebugString(); - EXPECT_OK( - sut_gnoi_diag_stub->StartBERT(&context, bert_request, &bert_response)); + LOG(INFO) << "Sending StartBERT request on SUT:"; + ASSERT_NO_FATAL_FAILURE( + SendStartBertRequestSuccessfully(bert_request, *sut_gnoi_diag_stub)); + // Poll for allowed BERT delay duration. int max_poll_count = ceil(ToInt64Seconds(kDelayDuration) / ToInt64Seconds(kPollInterval)); @@ -705,26 +750,16 @@ TEST_P(BertTest, StartBertSucceeds) { BuildPerPortStartBertRequest(interface)); } - // Request StartBert on the SUT switch. - { - gnoi::diag::StartBERTResponse bert_response; - grpc::ClientContext context; - LOG(INFO) << "Sending StartBERT request: " - << bert_request.ShortDebugString(); - EXPECT_OK( - sut_gnoi_diag_stub->StartBERT(&context, bert_request, &bert_response)); - } + // Request StartBert on the SUT switch. + LOG(INFO) << "Sending StartBERT request on SUT:"; + ASSERT_NO_FATAL_FAILURE( + SendStartBertRequestSuccessfully(bert_request, *sut_gnoi_diag_stub)); // Request StartBert on the control switch. - { - gnoi::diag::StartBERTResponse bert_response; - grpc::ClientContext context; - LOG(INFO) << "Sending StartBERT request: " - << bert_request.ShortDebugString(); - EXPECT_OK(control_switch_gnoi_diag_stub->StartBERT(&context, bert_request, - &bert_response)); - } - + LOG(INFO) << "Sending StartBERT request on control switch:"; + ASSERT_NO_FATAL_FAILURE(SendStartBertRequestSuccessfully( + bert_request, *control_switch_gnoi_diag_stub)); + // Get start timestamp. absl::Time start_time = absl::Now(); // Wait before reading the oper status. @@ -749,12 +784,10 @@ TEST_P(BertTest, StartBertSucceeds) { gnoi::diag::StartBERTRequest second_bert_request = bert_request; second_bert_request.set_bert_operation_id( absl::StrCat("OpId-", absl::ToUnixMillis(absl::Now()))); - gnoi::diag::StartBERTResponse bert_response; - grpc::ClientContext context; - LOG(INFO) << "Sending second StartBERT request: " - << second_bert_request.ShortDebugString(); - EXPECT_OK(sut_gnoi_diag_stub->StartBERT(&context, second_bert_request, - &bert_response)); + // Request StartBert on the SUT switch. + LOG(INFO) << "Sending second StartBERT request on SUT:"; + ASSERT_NO_FATAL_FAILURE(SendStartBertRequestSuccessfully( + second_bert_request, *sut_gnoi_diag_stub)); // Wait some time before querying the result. absl::SleepFor(kWaitTime); @@ -780,37 +813,15 @@ TEST_P(BertTest, StartBertSucceeds) { } } + // Wait for BERT to finish on test interfaces. + int max_poll_count = 1 + (kDelayDuration + kWaitTime + kTestDuration - + (absl::Now() - start_time) - absl::Seconds(1)) / + kPollInterval; - // Poll for remaining BERT duration. - int max_poll_count = - 1 + (absl::ToInt64Seconds(kDelayDuration + kTestDuration - - (absl::Now() - start_time) - absl::Seconds(1)) / - ToInt64Seconds(kPollInterval)); - std::vector interfaces_in_testing = interfaces; - for (int count = 0; count < max_poll_count; ++count) { - absl::SleepFor(kPollInterval); - // If port is no longer in "TESTING" oper state on both sides of the link, - // linkqual has completed on the link and full result is ready. - for (auto it = interfaces_in_testing.begin(); - it != interfaces_in_testing.end();) { - ASSERT_OK_AND_ASSIGN( - pins_test::OperStatus oper_status, - pins_test::GetInterfaceOperStatusOverGnmi(*sut_gnmi_stub, *it)); - if (oper_status != pins_test::OperStatus::kTesting) { - ASSERT_OK_AND_ASSIGN(oper_status, - pins_test::GetInterfaceOperStatusOverGnmi( - *control_switch_gnmi_stub, *it)); - if (oper_status != pins_test::OperStatus::kTesting) { - it = interfaces_in_testing.erase(it); - continue; - } - } - ++it; - } - if (interfaces_in_testing.empty()) break; - } - - EXPECT_THAT(interfaces_in_testing, testing::IsEmpty()); + std::vector interfaces_running_bert = interfaces; + ASSERT_NO_FATAL_FAILURE(WaitForBertToCompleteOnInterfaces( + *sut_gnmi_stub, *control_switch_gnmi_stub, interfaces_running_bert, + max_poll_count)); gnoi::diag::GetBERTResultRequest result_request; result_request.set_bert_operation_id(bert_request.bert_operation_id()); @@ -944,61 +955,197 @@ TEST_P(BertTest, RunBertOnMaximumAllowedPorts) { } // Request StartBert on the SUT switch. - { - gnoi::diag::StartBERTResponse bert_response; - grpc::ClientContext context; - EXPECT_OK( - sut_gnoi_diag_stub->StartBERT(&context, bert_request, &bert_response)); - } + LOG(INFO) << "Sending StartBERT request on SUT:"; + ASSERT_NO_FATAL_FAILURE( + SendStartBertRequestSuccessfully(bert_request, *sut_gnoi_diag_stub)); // Request StartBert on the control switch. - { - gnoi::diag::StartBERTResponse bert_response; - grpc::ClientContext context; - EXPECT_OK(control_switch_gnoi_diag_stub->StartBERT(&context, bert_request, - &bert_response)); - } + LOG(INFO) << "Sending StartBERT request on control switch:"; + ASSERT_NO_FATAL_FAILURE(SendStartBertRequestSuccessfully( + bert_request, *control_switch_gnoi_diag_stub)); absl::Time start_time = absl::Now(); // Give some time to BERT to move in SYNC state. absl::SleepFor(absl::Seconds(90)); absl::Time end_time = absl::Now(); - - // Poll for remaining BERT duration. - int max_poll_count = - 1 + (absl::ToInt64Seconds(kDelayDuration + kWaitTime + kSyncDuration + - kTestDuration - (end_time - start_time) - - absl::Seconds(1)) / - ToInt64Seconds(kPollInterval)); - std::vector interfaces_in_testing = interfaces; - for (int count = 0; count < max_poll_count; ++count) { - absl::SleepFor(kPollInterval); - // If port is no longer in "TESTING" oper state on both sides of the link, - // linkqual has completed on the link and full result is ready. - for (auto it = interfaces_in_testing.begin(); - it != interfaces_in_testing.end();) { - ASSERT_OK_AND_ASSIGN( - pins_test::OperStatus oper_status, - pins_test::GetInterfaceOperStatusOverGnmi(*sut_gnmi_stub, *it)); - if (oper_status != pins_test::OperStatus::kTesting) { - ASSERT_OK_AND_ASSIGN(oper_status, - pins_test::GetInterfaceOperStatusOverGnmi( - *control_switch_gnmi_stub, *it)); - if (oper_status != pins_test::OperStatus::kTesting) { - it = interfaces_in_testing.erase(it); - continue; - } + + // Wait for BERT to finish on test interfaces. + int max_poll_count = 1 + (kDelayDuration + kWaitTime + kTestDuration - + (end_time - start_time) - absl::Seconds(1)) / + kPollInterval; + + std::vector interfaces_running_bert = interfaces; + ASSERT_NO_FATAL_FAILURE(WaitForBertToCompleteOnInterfaces( + *sut_gnmi_stub, *control_switch_gnmi_stub, interfaces_running_bert, + max_poll_count)); + + // Wait for some time before checking the port status. + absl::SleepFor(kPortsUpWaitTime); + + ASSERT_OK(pins_test::PortsUp(sut)); + ASSERT_OK(pins_test::PortsUp(control_switch)); +} + +// Run BERT on a port. Issue StopBERT on the SUT port, this causes BERT to +// stop on SUT and this will cause BERT failure on control switch as control +// switch side port will lose lock with its peer port on SUT side. +TEST_P(BertTest, StopBertSucceeds) { + GetMirrorTestbed().Environment().SetTestCaseID( + "be7b6653-51b9-4231-a438-d9589bbcb677"); + thinkit::Switch& sut = GetMirrorTestbed().Sut(); + ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); + ASSERT_OK(pins_test::PortsUp(sut)); + ASSERT_OK_AND_ASSIGN(auto sut_gnoi_diag_stub, sut.CreateGnoiDiagStub()); + + thinkit::Switch& control_switch = GetMirrorTestbed().ControlSwitch(); + ASSERT_OK_AND_ASSIGN(auto control_switch_gnmi_stub, + control_switch.CreateGnmiStub()); + ASSERT_OK(pins_test::PortsUp(control_switch)); + ASSERT_OK_AND_ASSIGN(auto control_switch_gnoi_diag_stub, + control_switch.CreateGnoiDiagStub()); + + // Select one operational state "up" port. + std::string interface = absl::GetFlag(FLAGS_interface); + if (interface.empty()) { + std::vector interfaces; + ASSERT_NO_FATAL_FAILURE(SelectNUpInterfaces(/*port_count_to_select=*/1, + *sut_gnmi_stub, &interfaces)); + interface = interfaces[0]; + } + LOG(INFO) << "Selected interface: " + << interface << ". To repeat the test with same interface, use " + << "--test_arg=--interface=" << interface << " in test arguments."; + + gnoi::diag::StartBERTRequest bert_request; + // Create the BERT request. + bert_request.set_bert_operation_id( + absl::StrCat("OpId-", absl::ToUnixMillis(absl::Now()))); + *(bert_request.add_per_port_requests()) = + gutil::ParseProtoOrDie( + BuildPerPortStartBertRequest(interface)); + + // Request StartBert on the SUT switch. + LOG(INFO) << "Sending StartBERT request on SUT:"; + ASSERT_NO_FATAL_FAILURE( + SendStartBertRequestSuccessfully(bert_request, *sut_gnoi_diag_stub)); + + // Request StartBert on the control switch. + LOG(INFO) << "Sending StartBERT request on control switch:"; + ASSERT_NO_FATAL_FAILURE(SendStartBertRequestSuccessfully( + bert_request, *control_switch_gnoi_diag_stub)); + + absl::Time start_time = absl::Now(); + // Wait before reading the oper status. + absl::SleepFor(kWaitToReadOperStatus); + + // Verify that port should be in TESTING mode now. + { + ASSERT_OK_AND_ASSIGN( + pins_test::OperStatus oper_status, + pins_test::GetInterfaceOperStatusOverGnmi(*sut_gnmi_stub, interface)); + ASSERT_EQ(oper_status, pins_test::OperStatus::kTesting); + ASSERT_OK_AND_ASSIGN(oper_status, + pins_test::GetInterfaceOperStatusOverGnmi( + *control_switch_gnmi_stub, interface)); + ASSERT_EQ(oper_status, pins_test::OperStatus::kTesting); + } + + gnoi::diag::GetBERTResultRequest result_request; + result_request.set_bert_operation_id(bert_request.bert_operation_id()); + *(result_request.add_per_port_requests()->mutable_interface()) = + bert_request.per_port_requests(0).interface(); + + // Verify control switch side BERT has acquired the lock and is running now. + { + int remaining_poll_count = + 1 + (kDelayDuration - absl::Seconds(1)) / kPollInterval; + + while (remaining_poll_count > 0) { + absl::SleepFor(kPollInterval); + gnoi::diag::GetBERTResultResponse response; + grpc::ClientContext context; + EXPECT_OK(control_switch_gnoi_diag_stub->GetBERTResult( + &context, result_request, &response)); + ASSERT_THAT(response.per_port_responses(), + SizeIs(bert_request.per_port_requests_size())); + ASSERT_EQ(response.per_port_responses(0).status(), + gnoi::diag::BERT_STATUS_OK); + if (response.per_port_responses(0).peer_lock_established()) { + break; } - ++it; + --remaining_poll_count; } - if (interfaces_in_testing.empty()) break; + // Assert if timed out to get the lock. + ASSERT_GT(remaining_poll_count, 0); } - EXPECT_THAT(interfaces_in_testing, testing::IsEmpty()); + // Request StopBERT on SUT. + { + gnoi::diag::StopBERTRequest stop_request; + stop_request.set_bert_operation_id(bert_request.bert_operation_id()); + *(stop_request.add_per_port_requests()->mutable_interface()) = + bert_request.per_port_requests(0).interface(); + gnoi::diag::StopBERTResponse response; + grpc::ClientContext context; + LOG(INFO) << "Sending StopBERT request on SUT: " + << stop_request.ShortDebugString(); + EXPECT_OK(sut_gnoi_diag_stub->StopBERT(&context, stop_request, &response)); - // Wait for some time before checking the port status. - absl::SleepFor(absl::Seconds(10)); + // Verify response. + ASSERT_THAT(response.per_port_responses(), + SizeIs(stop_request.per_port_requests_size())); + EXPECT_EQ(response.bert_operation_id(), stop_request.bert_operation_id()); + + for (int idx = 0; idx < response.per_port_responses_size(); ++idx) { + auto per_port_response = response.per_port_responses(idx); + SCOPED_TRACE(absl::StrCat("Verifying StopBERT port response: ", + per_port_response.ShortDebugString())); + EXPECT_EQ(per_port_response.status(), gnoi::diag::BERT_STATUS_OK); + EXPECT_TRUE(MessageDifferencer::Equals( + per_port_response.interface(), + stop_request.per_port_requests(idx).interface())); + } + } + + // Wait for BERT to finish on test interfaces. + int max_poll_count = 1 + (kDelayDuration + kWaitTime + kTestDuration - + (absl::Now() - start_time) - absl::Seconds(1)) / + kPollInterval; + + std::vector interfaces_running_bert = {interface}; + ASSERT_NO_FATAL_FAILURE(WaitForBertToCompleteOnInterfaces( + *sut_gnmi_stub, *control_switch_gnmi_stub, interfaces_running_bert, + max_poll_count)); + + // Get the BERT result from the SUT. BERT status should be OK. + { + gnoi::diag::GetBERTResultResponse response; + grpc::ClientContext context; + EXPECT_OK( + sut_gnoi_diag_stub->GetBERTResult(&context, result_request, &response)); + LOG(INFO) << "SUT BERT Result: " << response.ShortDebugString(); + ASSERT_THAT(response.per_port_responses(), + SizeIs(bert_request.per_port_requests_size())); + EXPECT_EQ(response.per_port_responses(0).status(), + gnoi::diag::BERT_STATUS_OK); + } + + // Get the BERT result from the control switch. It should have failed due to + // peer lock loss. + { + gnoi::diag::GetBERTResultResponse response; + grpc::ClientContext context; + EXPECT_OK(control_switch_gnoi_diag_stub->GetBERTResult( + &context, result_request, &response)); + LOG(INFO) << "Control switch BERT Result: " << response.ShortDebugString(); + ASSERT_THAT(response.per_port_responses(), + SizeIs(bert_request.per_port_requests_size())); + EXPECT_EQ(response.per_port_responses(0).status(), + gnoi::diag::BERT_STATUS_PEER_LOCK_LOST); + EXPECT_TRUE(response.per_port_responses(0).peer_lock_established()); + EXPECT_TRUE(response.per_port_responses(0).peer_lock_lost()); + } ASSERT_OK(pins_test::PortsUp(sut)); ASSERT_OK(pins_test::PortsUp(control_switch)); From e6b6008993600590b48bb98d981e6a3d8a22305c Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:19:44 +0000 Subject: [PATCH 05/24] [P4_Symbolic] Add support for conditionals on table hit/miss. (#709) Co-authored-by: kheradmandG Co-authored-by: smolkaj Co-authored-by: kishanps --- p4_symbolic/bmv2/expected/table_hit_1.pb.txt | 20 +- p4_symbolic/bmv2/expected/table_hit_2.pb.txt | 812 ++++-------------- p4_symbolic/ir/expected/table_hit_1.txt | 34 +- p4_symbolic/ir/expected/table_hit_2.txt | 489 +++++------ p4_symbolic/ir/ir.h | 10 + p4_symbolic/symbolic/BUILD.bazel | 18 + p4_symbolic/symbolic/expected/table_hit_1.txt | 8 +- p4_symbolic/symbolic/expected/table_hit_2.txt | 40 + p4_symbolic/symbolic/table.cc | 65 +- .../testdata/conditional/table_hit_1.p4 | 5 +- .../testdata/conditional/table_hit_2.p4 | 98 +-- 11 files changed, 571 insertions(+), 1028 deletions(-) create mode 100644 p4_symbolic/symbolic/expected/table_hit_2.txt diff --git a/p4_symbolic/bmv2/expected/table_hit_1.pb.txt b/p4_symbolic/bmv2/expected/table_hit_1.pb.txt index 16f35585..508e64b5 100644 --- a/p4_symbolic/bmv2/expected/table_hit_1.pb.txt +++ b/p4_symbolic/bmv2/expected/table_hit_1.pb.txt @@ -257,7 +257,7 @@ parsers { deparsers { name: "deparser" source_info { - line: 102 + line: 105 column: 8 source_fragment: "MyDeparser" } @@ -757,6 +757,10 @@ actions { } } } +actions { + name: "MyIngress.nothing" + id: 4 +} pipelines { name: "ingress" source_info { @@ -768,7 +772,7 @@ pipelines { tables { name: "MyIngress.t_1" source_info { - line: 64 + line: 66 column: 10 source_fragment: "t_1" } @@ -797,7 +801,7 @@ pipelines { name: "MyIngress.t_2" id: 1 source_info { - line: 75 + line: 77 column: 10 source_fragment: "t_2" } @@ -809,10 +813,12 @@ pipelines { max_size: 1024 action_ids: 1 action_ids: 3 + action_ids: 4 actions: "MyIngress.drop" actions: "MyIngress.forward" + actions: "MyIngress.nothing" default_entry { - action_id: 1 + action_id: 4 } next_tables { key: "MyIngress.drop" @@ -822,13 +828,17 @@ pipelines { key: "MyIngress.forward" value: "" } + next_tables { + key: "MyIngress.nothing" + value: "" + } } } pipelines { name: "egress" id: 1 source_info { - line: 94 + line: 97 column: 8 source_fragment: "MyEgress" } diff --git a/p4_symbolic/bmv2/expected/table_hit_2.pb.txt b/p4_symbolic/bmv2/expected/table_hit_2.pb.txt index ed13deb6..ba3f44aa 100644 --- a/p4_symbolic/bmv2/expected/table_hit_2.pb.txt +++ b/p4_symbolic/bmv2/expected/table_hit_2.pb.txt @@ -193,14 +193,14 @@ header_types { } } header_types { - name: "ethernet_t" + name: "header_t" id: 2 fields { values { - string_value: "dst_addr" + string_value: "f1" } values { - number_value: 48 + number_value: 8 } values { bool_value: false @@ -208,10 +208,10 @@ header_types { } fields { values { - string_value: "src_addr" + string_value: "f2" } values { - number_value: 48 + number_value: 8 } values { bool_value: false @@ -219,501 +219,62 @@ header_types { } fields { values { - string_value: "ether_type" + string_value: "f3" } values { - number_value: 16 + number_value: 8 } values { bool_value: false } } -} -headers { - name: "scalars" - header_type: "scalars_0" - metadata: true -} -headers { - id: 1 - name: "standard_metadata" - header_type: "standard_metadata" - metadata: true -} -headers { - id: 2 - name: "ethernet" - header_type: "ethernet_t" -} -parsers { - name: "parser" - init_state: "start" - parse_states { - name: "start" - transitions { - } - } -} -deparsers { - name: "deparser" - source_info { - line: 128 - column: 8 - source_fragment: "MyDeparser" - } -} -actions { - name: "MyIngress.drop" - primitives { - fields { - key: "op" - value { - string_value: "mark_to_drop" - } - } - fields { - key: "parameters" - value { - list_value { - values { - struct_value { - fields { - key: "type" - value { - string_value: "header" - } - } - fields { - key: "value" - value { - string_value: "standard_metadata" - } - } - } - } - } - } - } - fields { - key: "source_info" - value { - struct_value { - fields { - key: "column" - value { - number_value: 8 - } - } - fields { - key: "filename" - value { - string_value: "" - } - } - fields { - key: "line" - value { - number_value: 56 - } - } - fields { - key: "source_fragment" - value { - string_value: "mark_to_drop(standard_metadata)" - } - } - } - } - } - } -} -actions { - name: "MyIngress.drop" - id: 1 - primitives { - fields { - key: "op" - value { - string_value: "mark_to_drop" - } - } - fields { - key: "parameters" - value { - list_value { - values { - struct_value { - fields { - key: "type" - value { - string_value: "header" - } - } - fields { - key: "value" - value { - string_value: "standard_metadata" - } - } - } - } - } - } - } - fields { - key: "source_info" - value { - struct_value { - fields { - key: "column" - value { - number_value: 8 - } - } - fields { - key: "filename" - value { - string_value: "" - } - } - fields { - key: "line" - value { - number_value: 56 - } - } - fields { - key: "source_fragment" - value { - string_value: "mark_to_drop(standard_metadata)" - } - } - } - } - } - } -} -actions { - name: "MyIngress.drop" - id: 2 - primitives { - fields { - key: "op" - value { - string_value: "mark_to_drop" - } - } - fields { - key: "parameters" - value { - list_value { - values { - struct_value { - fields { - key: "type" - value { - string_value: "header" - } - } - fields { - key: "value" - value { - string_value: "standard_metadata" - } - } - } - } - } - } - } - fields { - key: "source_info" - value { - struct_value { - fields { - key: "column" - value { - number_value: 8 - } - } - fields { - key: "filename" - value { - string_value: "" - } - } - fields { - key: "line" - value { - number_value: 56 - } - } - fields { - key: "source_fragment" - value { - string_value: "mark_to_drop(standard_metadata)" - } - } - } - } - } - } -} -actions { - name: "MyIngress.drop" - id: 3 - primitives { - fields { - key: "op" - value { - string_value: "mark_to_drop" - } - } - fields { - key: "parameters" - value { - list_value { - values { - struct_value { - fields { - key: "type" - value { - string_value: "header" - } - } - fields { - key: "value" - value { - string_value: "standard_metadata" - } - } - } - } - } - } - } - fields { - key: "source_info" - value { - struct_value { - fields { - key: "column" - value { - number_value: 8 - } - } - fields { - key: "filename" - value { - string_value: "" - } - } - fields { - key: "line" - value { - number_value: 56 - } - } - fields { - key: "source_fragment" - value { - string_value: "mark_to_drop(standard_metadata)" - } - } - } - } - } - } -} -actions { - name: "MyIngress.forward" - id: 4 - runtime_data { - name: "dst_addr" - bitwidth: 48 - } - runtime_data { - name: "port" - bitwidth: 9 - } - primitives { - fields { - key: "op" - value { - string_value: "assign" - } - } - fields { - key: "parameters" - value { - list_value { - values { - struct_value { - fields { - key: "type" - value { - string_value: "field" - } - } - fields { - key: "value" - value { - list_value { - values { - string_value: "standard_metadata" - } - values { - string_value: "egress_spec" - } - } - } - } - } - } - values { - struct_value { - fields { - key: "type" - value { - string_value: "runtime_data" - } - } - fields { - key: "value" - value { - number_value: 1 - } - } - } - } - } - } - } - fields { - key: "source_info" - value { - struct_value { - fields { - key: "column" - value { - number_value: 8 - } - } - fields { - key: "filename" - value { - string_value: "" - } - } - fields { - key: "line" - value { - number_value: 60 - } - } - fields { - key: "source_fragment" - value { - string_value: "standard_metadata.egress_spec = port" - } - } - } - } + fields { + values { + string_value: "f4" } - } - primitives { - fields { - key: "op" - value { - string_value: "assign" - } + values { + number_value: 8 } - fields { - key: "parameters" - value { - list_value { - values { - struct_value { - fields { - key: "type" - value { - string_value: "field" - } - } - fields { - key: "value" - value { - list_value { - values { - string_value: "ethernet" - } - values { - string_value: "dst_addr" - } - } - } - } - } - } - values { - struct_value { - fields { - key: "type" - value { - string_value: "runtime_data" - } - } - fields { - key: "value" - value { - number_value: 0 - } - } - } - } - } - } + values { + bool_value: false } - fields { - key: "source_info" - value { - struct_value { - fields { - key: "column" - value { - number_value: 8 - } - } - fields { - key: "filename" - value { - string_value: "" - } - } - fields { - key: "line" - value { - number_value: 61 - } - } - fields { - key: "source_fragment" - value { - string_value: "hdr.ethernet.dst_addr = dst_addr" - } - } - } - } + } +} +headers { + name: "scalars" + header_type: "scalars_0" + metadata: true +} +headers { + id: 1 + name: "standard_metadata" + header_type: "standard_metadata" + metadata: true +} +headers { + id: 2 + name: "h1" + header_type: "header_t" +} +parsers { + name: "parser" + init_state: "start" + parse_states { + name: "start" + transitions { } } } +deparsers { + name: "deparser" + source_info { + line: 100 + column: 8 + source_fragment: "MyDeparser" + } +} actions { name: "MyIngress.forward" - id: 5 - runtime_data { - name: "dst_addr" - bitwidth: 48 - } runtime_data { name: "port" bitwidth: 9 @@ -752,91 +313,6 @@ actions { } } } - values { - struct_value { - fields { - key: "type" - value { - string_value: "runtime_data" - } - } - fields { - key: "value" - value { - number_value: 1 - } - } - } - } - } - } - } - fields { - key: "source_info" - value { - struct_value { - fields { - key: "column" - value { - number_value: 8 - } - } - fields { - key: "filename" - value { - string_value: "" - } - } - fields { - key: "line" - value { - number_value: 60 - } - } - fields { - key: "source_fragment" - value { - string_value: "standard_metadata.egress_spec = port" - } - } - } - } - } - } - primitives { - fields { - key: "op" - value { - string_value: "assign" - } - } - fields { - key: "parameters" - value { - list_value { - values { - struct_value { - fields { - key: "type" - value { - string_value: "field" - } - } - fields { - key: "value" - value { - list_value { - values { - string_value: "ethernet" - } - values { - string_value: "dst_addr" - } - } - } - } - } - } values { struct_value { fields { @@ -863,7 +339,7 @@ actions { fields { key: "column" value { - number_value: 8 + number_value: 7 } } fields { @@ -875,13 +351,13 @@ actions { fields { key: "line" value { - number_value: 61 + number_value: 56 } } fields { key: "source_fragment" value { - string_value: "hdr.ethernet.dst_addr = dst_addr" + string_value: "standard_metadata.egress_spec = port" } } } @@ -891,11 +367,7 @@ actions { } actions { name: "MyIngress.forward" - id: 6 - runtime_data { - name: "dst_addr" - bitwidth: 48 - } + id: 1 runtime_data { name: "port" bitwidth: 9 @@ -945,7 +417,7 @@ actions { fields { key: "value" value { - number_value: 1 + number_value: 0 } } } @@ -960,7 +432,7 @@ actions { fields { key: "column" value { - number_value: 8 + number_value: 7 } } fields { @@ -972,7 +444,7 @@ actions { fields { key: "line" value { - number_value: 60 + number_value: 56 } } fields { @@ -985,6 +457,14 @@ actions { } } } +} +actions { + name: "MyIngress.forward" + id: 2 + runtime_data { + name: "port" + bitwidth: 9 + } primitives { fields { key: "op" @@ -1009,10 +489,10 @@ actions { value { list_value { values { - string_value: "ethernet" + string_value: "standard_metadata" } values { - string_value: "dst_addr" + string_value: "egress_spec" } } } @@ -1045,7 +525,7 @@ actions { fields { key: "column" value { - number_value: 8 + number_value: 7 } } fields { @@ -1057,13 +537,13 @@ actions { fields { key: "line" value { - number_value: 61 + number_value: 56 } } fields { key: "source_fragment" value { - string_value: "hdr.ethernet.dst_addr = dst_addr" + string_value: "standard_metadata.egress_spec = port" } } } @@ -1073,11 +553,7 @@ actions { } actions { name: "MyIngress.forward" - id: 7 - runtime_data { - name: "dst_addr" - bitwidth: 48 - } + id: 3 runtime_data { name: "port" bitwidth: 9 @@ -1127,7 +603,7 @@ actions { fields { key: "value" value { - number_value: 1 + number_value: 0 } } } @@ -1142,7 +618,7 @@ actions { fields { key: "column" value { - number_value: 8 + number_value: 7 } } fields { @@ -1154,7 +630,7 @@ actions { fields { key: "line" value { - number_value: 60 + number_value: 56 } } fields { @@ -1167,6 +643,26 @@ actions { } } } +} +actions { + name: "MyIngress.nothing" + id: 4 +} +actions { + name: "MyIngress.nothing" + id: 5 +} +actions { + name: "MyIngress.nothing" + id: 6 +} +actions { + name: "MyIngress.nothing" + id: 7 +} +actions { + name: "table_hit_2l83" + id: 8 primitives { fields { key: "op" @@ -1191,10 +687,10 @@ actions { value { list_value { values { - string_value: "ethernet" + string_value: "standard_metadata" } values { - string_value: "dst_addr" + string_value: "egress_spec" } } } @@ -1206,13 +702,13 @@ actions { fields { key: "type" value { - string_value: "runtime_data" + string_value: "hexstr" } } fields { key: "value" value { - number_value: 0 + string_value: "0x0000" } } } @@ -1239,13 +735,13 @@ actions { fields { key: "line" value { - number_value: 61 + number_value: 83 } } fields { key: "source_fragment" value { - string_value: "hdr.ethernet.dst_addr = dst_addr" + string_value: "standard_metadata.egress_spec = 0" } } } @@ -1260,122 +756,142 @@ pipelines { column: 8 source_fragment: "MyIngress" } - init_table: "MyIngress.t_0" + init_table: "tbl_table_hit_2l83" + tables { + name: "tbl_table_hit_2l83" + source_info { + line: 83 + column: 38 + source_fragment: "=" + } + max_size: 1024 + action_ids: 8 + actions: "table_hit_2l83" + default_entry { + action_id: 8 + } + next_tables { + key: "table_hit_2l83" + value: "MyIngress.t1" + } + } tables { - name: "MyIngress.t_0" + name: "MyIngress.t1" + id: 1 source_info { - line: 64 + line: 60 column: 10 - source_fragment: "t_0" + source_fragment: "t1" } key { - name: "hdr.ethernet.ether_type" - target: "ethernet" - target: "ether_type" + name: "hdr.h1.f1" + target: "h1" + target: "f1" } max_size: 1024 action_ids: 0 action_ids: 4 - actions: "MyIngress.drop" actions: "MyIngress.forward" + actions: "MyIngress.nothing" default_entry { + action_id: 4 } next_tables { key: "__HIT__" - value: "MyIngress.t_1" + value: "MyIngress.t2" } next_tables { key: "__MISS__" - value: "MyIngress.t_2" + value: "MyIngress.t3" } } tables { - name: "MyIngress.t_1" - id: 1 + name: "MyIngress.t2" + id: 2 source_info { - line: 76 + line: 65 column: 10 - source_fragment: "t_1" + source_fragment: "t2" } key { - name: "hdr.ethernet.ether_type" - target: "ethernet" - target: "ether_type" + name: "hdr.h1.f2" + target: "h1" + target: "f2" } max_size: 1024 action_ids: 1 action_ids: 5 - actions: "MyIngress.drop" actions: "MyIngress.forward" + actions: "MyIngress.nothing" default_entry { - action_id: 1 + action_id: 5 } next_tables { - key: "MyIngress.drop" - value: "MyIngress.t_3" + key: "MyIngress.forward" + value: "MyIngress.t4" } next_tables { - key: "MyIngress.forward" - value: "MyIngress.t_3" + key: "MyIngress.nothing" + value: "MyIngress.t4" } } tables { - name: "MyIngress.t_2" - id: 2 + name: "MyIngress.t3" + id: 3 source_info { - line: 87 + line: 70 column: 10 - source_fragment: "t_2" + source_fragment: "t3" } key { - name: "hdr.ethernet.src_addr" - target: "ethernet" - target: "src_addr" + name: "hdr.h1.f3" + target: "h1" + target: "f3" } max_size: 1024 action_ids: 2 action_ids: 6 - actions: "MyIngress.drop" actions: "MyIngress.forward" + actions: "MyIngress.nothing" default_entry { - action_id: 2 + action_id: 6 } next_tables { - key: "MyIngress.drop" - value: "MyIngress.t_3" + key: "MyIngress.forward" + value: "MyIngress.t4" } next_tables { - key: "MyIngress.forward" - value: "MyIngress.t_3" + key: "MyIngress.nothing" + value: "MyIngress.t4" } } tables { - name: "MyIngress.t_3" - id: 3 + name: "MyIngress.t4" + id: 4 source_info { - line: 98 + line: 75 column: 10 - source_fragment: "t_3" + source_fragment: "t4" } key { - name: "hdr.ethernet.dst_addr" - target: "ethernet" - target: "dst_addr" + name: "hdr.h1.f4" + target: "h1" + target: "f4" } max_size: 1024 action_ids: 3 action_ids: 7 - actions: "MyIngress.drop" actions: "MyIngress.forward" + actions: "MyIngress.nothing" default_entry { - action_id: 3 + action_id: 7 } next_tables { - key: "MyIngress.drop" + key: "MyIngress.forward" value: "" } next_tables { - key: "MyIngress.forward" + key: "MyIngress.nothing" value: "" } } @@ -1384,7 +900,7 @@ pipelines { name: "egress" id: 1 source_info { - line: 120 + line: 92 column: 8 source_fragment: "MyEgress" } diff --git a/p4_symbolic/ir/expected/table_hit_1.txt b/p4_symbolic/ir/expected/table_hit_1.txt index 1b45a1f2..d04c3744 100644 --- a/p4_symbolic/ir/expected/table_hit_1.txt +++ b/p4_symbolic/ir/expected/table_hit_1.txt @@ -286,6 +286,20 @@ actions { } } } +actions { + key: "MyIngress.nothing" + value { + action_definition { + preamble { + id: 27514245 + name: "MyIngress.nothing" + alias: "nothing" + } + } + action_implementation { + } + } +} tables { key: "MyIngress.t_1" value { @@ -510,10 +524,24 @@ tables { } proto_id: 2 } + entry_actions { + ref { + id: 27514245 + annotations: "@proto_id(3)" + } + action { + preamble { + id: 27514245 + name: "MyIngress.nothing" + alias: "nothing" + } + } + proto_id: 3 + } size: 1024 } table_implementation { - default_action: "MyIngress.drop" + default_action: "MyIngress.nothing" action_to_next_control { key: "MyIngress.drop" value: "__END_OF_PIPELINE__" @@ -522,6 +550,10 @@ tables { key: "MyIngress.forward" value: "__END_OF_PIPELINE__" } + action_to_next_control { + key: "MyIngress.nothing" + value: "__END_OF_PIPELINE__" + } match_name_to_field { key: "hdr.ethernet.src_addr" value { diff --git a/p4_symbolic/ir/expected/table_hit_2.txt b/p4_symbolic/ir/expected/table_hit_2.txt index 2c26be5d..e5d555db 100644 --- a/p4_symbolic/ir/expected/table_hit_2.txt +++ b/p4_symbolic/ir/expected/table_hit_2.txt @@ -1,27 +1,34 @@ headers { - key: "ethernet" + key: "h1" value { - name: "ethernet_t" + name: "header_t" id: 2 fields { - key: "dst_addr" + key: "f1" value { - name: "dst_addr" - bitwidth: 48 + name: "f1" + bitwidth: 8 } } fields { - key: "ether_type" + key: "f2" value { - name: "ether_type" - bitwidth: 16 + name: "f2" + bitwidth: 8 } } fields { - key: "src_addr" + key: "f3" value { - name: "src_addr" - bitwidth: 48 + name: "f3" + bitwidth: 8 + } + } + fields { + key: "f4" + value { + name: "f4" + bitwidth: 8 } } } @@ -158,32 +165,6 @@ headers { } } } -actions { - key: "MyIngress.drop" - value { - action_definition { - preamble { - id: 25652968 - name: "MyIngress.drop" - alias: "drop" - } - } - action_implementation { - action_body { - source_info { - line: 56 - column: 8 - source_fragment: "mark_to_drop(standard_metadata)" - } - drop { - header { - header_name: "standard_metadata" - } - } - } - } - } -} actions { key: "MyIngress.forward" value { @@ -198,36 +179,16 @@ actions { value { param { id: 1 - name: "dst_addr" - bitwidth: 48 - } - } - } - params_by_id { - key: 2 - value { - param { - id: 2 name: "port" bitwidth: 9 } } } - params_by_name { - key: "dst_addr" - value { - param { - id: 1 - name: "dst_addr" - bitwidth: 48 - } - } - } params_by_name { key: "port" value { param { - id: 2 + id: 1 name: "port" bitwidth: 9 } @@ -235,18 +196,14 @@ actions { } } action_implementation { - parameter_to_bitwidth { - key: "dst_addr" - value: 48 - } parameter_to_bitwidth { key: "port" value: 9 } action_body { source_info { - line: 60 - column: 8 + line: 56 + column: 7 source_fragment: "standard_metadata.egress_spec = port" } assignment { @@ -263,22 +220,49 @@ actions { } } } + } + } +} +actions { + key: "MyIngress.nothing" + value { + action_definition { + preamble { + id: 27514245 + name: "MyIngress.nothing" + alias: "nothing" + } + } + action_implementation { + } + } +} +actions { + key: "table_hit_2l83" + value { + action_definition { + preamble { + id: 8 + name: "table_hit_2l83" + } + } + action_implementation { action_body { source_info { - line: 61 + line: 83 column: 8 - source_fragment: "hdr.ethernet.dst_addr = dst_addr" + source_fragment: "standard_metadata.egress_spec = 0" } assignment { left { field_value { - header_name: "ethernet" - field_name: "dst_addr" + header_name: "standard_metadata" + field_name: "egress_spec" } } right { - variable_value { - name: "dst_addr" + hexstr_value { + value: "0x0000" } } } @@ -287,54 +271,40 @@ actions { } } tables { - key: "MyIngress.t_0" + key: "MyIngress.t1" value { table_definition { preamble { - id: 33597527 - name: "MyIngress.t_0" - alias: "t_0" + id: 44659798 + name: "MyIngress.t1" + alias: "t1" } match_fields_by_id { key: 1 value { match_field { id: 1 - name: "hdr.ethernet.ether_type" - bitwidth: 16 + name: "hdr.h1.f1" + bitwidth: 8 match_type: EXACT } } } match_fields_by_name { - key: "hdr.ethernet.ether_type" + key: "hdr.h1.f1" value { match_field { id: 1 - name: "hdr.ethernet.ether_type" - bitwidth: 16 + name: "hdr.h1.f1" + bitwidth: 8 match_type: EXACT } } } - entry_actions { - ref { - id: 25652968 - annotations: "@proto_id(1)" - } - action { - preamble { - id: 25652968 - name: "MyIngress.drop" - alias: "drop" - } - } - proto_id: 1 - } entry_actions { ref { id: 29683729 - annotations: "@proto_id(2)" + annotations: "@proto_id(1)" } action { preamble { @@ -347,119 +317,99 @@ tables { value { param { id: 1 - name: "dst_addr" - bitwidth: 48 - } - } - } - params_by_id { - key: 2 - value { - param { - id: 2 name: "port" bitwidth: 9 } } } - params_by_name { - key: "dst_addr" - value { - param { - id: 1 - name: "dst_addr" - bitwidth: 48 - } - } - } params_by_name { key: "port" value { param { - id: 2 + id: 1 name: "port" bitwidth: 9 } } } } + proto_id: 1 + } + entry_actions { + ref { + id: 27514245 + annotations: "@proto_id(2)" + } + action { + preamble { + id: 27514245 + name: "MyIngress.nothing" + alias: "nothing" + } + } proto_id: 2 } size: 1024 } table_implementation { - default_action: "MyIngress.drop" + default_action: "MyIngress.nothing" action_to_next_control { key: "__HIT__" - value: "MyIngress.t_1" + value: "MyIngress.t2" } action_to_next_control { key: "__MISS__" - value: "MyIngress.t_2" + value: "MyIngress.t3" } match_name_to_field { - key: "hdr.ethernet.ether_type" + key: "hdr.h1.f1" value { - header_name: "ethernet" - field_name: "ether_type" + header_name: "h1" + field_name: "f1" } } optimized_symbolic_execution_info { - merge_point: "MyIngress.t_3" + merge_point: "MyIngress.t4" continue_to_merge_point: true } } } } tables { - key: "MyIngress.t_1" + key: "MyIngress.t2" value { table_definition { preamble { - id: 41698643 - name: "MyIngress.t_1" - alias: "t_1" + id: 40554969 + name: "MyIngress.t2" + alias: "t2" } match_fields_by_id { key: 1 value { match_field { id: 1 - name: "hdr.ethernet.ether_type" - bitwidth: 16 + name: "hdr.h1.f2" + bitwidth: 8 match_type: EXACT } } } match_fields_by_name { - key: "hdr.ethernet.ether_type" + key: "hdr.h1.f2" value { match_field { id: 1 - name: "hdr.ethernet.ether_type" - bitwidth: 16 + name: "hdr.h1.f2" + bitwidth: 8 match_type: EXACT } } } - entry_actions { - ref { - id: 25652968 - annotations: "@proto_id(1)" - } - action { - preamble { - id: 25652968 - name: "MyIngress.drop" - alias: "drop" - } - } - proto_id: 1 - } entry_actions { ref { id: 29683729 - annotations: "@proto_id(2)" + annotations: "@proto_id(1)" } action { preamble { @@ -472,118 +422,98 @@ tables { value { param { id: 1 - name: "dst_addr" - bitwidth: 48 - } - } - } - params_by_id { - key: 2 - value { - param { - id: 2 name: "port" bitwidth: 9 } } } - params_by_name { - key: "dst_addr" - value { - param { - id: 1 - name: "dst_addr" - bitwidth: 48 - } - } - } params_by_name { key: "port" value { param { - id: 2 + id: 1 name: "port" bitwidth: 9 } } } } + proto_id: 1 + } + entry_actions { + ref { + id: 27514245 + annotations: "@proto_id(2)" + } + action { + preamble { + id: 27514245 + name: "MyIngress.nothing" + alias: "nothing" + } + } proto_id: 2 } size: 1024 } table_implementation { - default_action: "MyIngress.drop" + default_action: "MyIngress.nothing" action_to_next_control { - key: "MyIngress.drop" - value: "MyIngress.t_3" + key: "MyIngress.forward" + value: "MyIngress.t4" } action_to_next_control { - key: "MyIngress.forward" - value: "MyIngress.t_3" + key: "MyIngress.nothing" + value: "MyIngress.t4" } match_name_to_field { - key: "hdr.ethernet.ether_type" + key: "hdr.h1.f2" value { - header_name: "ethernet" - field_name: "ether_type" + header_name: "h1" + field_name: "f2" } } optimized_symbolic_execution_info { - merge_point: "MyIngress.t_3" + merge_point: "MyIngress.t4" } } } } tables { - key: "MyIngress.t_2" + key: "MyIngress.t3" value { table_definition { preamble { - id: 45015480 - name: "MyIngress.t_2" - alias: "t_2" + id: 36319071 + name: "MyIngress.t3" + alias: "t3" } match_fields_by_id { key: 1 value { match_field { id: 1 - name: "hdr.ethernet.src_addr" - bitwidth: 48 + name: "hdr.h1.f3" + bitwidth: 8 match_type: EXACT } } } match_fields_by_name { - key: "hdr.ethernet.src_addr" + key: "hdr.h1.f3" value { match_field { id: 1 - name: "hdr.ethernet.src_addr" - bitwidth: 48 + name: "hdr.h1.f3" + bitwidth: 8 match_type: EXACT } } } - entry_actions { - ref { - id: 25652968 - annotations: "@proto_id(1)" - } - action { - preamble { - id: 25652968 - name: "MyIngress.drop" - alias: "drop" - } - } - proto_id: 1 - } entry_actions { ref { id: 29683729 - annotations: "@proto_id(2)" + annotations: "@proto_id(1)" } action { preamble { @@ -596,118 +526,98 @@ tables { value { param { id: 1 - name: "dst_addr" - bitwidth: 48 - } - } - } - params_by_id { - key: 2 - value { - param { - id: 2 name: "port" bitwidth: 9 } } } - params_by_name { - key: "dst_addr" - value { - param { - id: 1 - name: "dst_addr" - bitwidth: 48 - } - } - } params_by_name { key: "port" value { param { - id: 2 + id: 1 name: "port" bitwidth: 9 } } } } + proto_id: 1 + } + entry_actions { + ref { + id: 27514245 + annotations: "@proto_id(2)" + } + action { + preamble { + id: 27514245 + name: "MyIngress.nothing" + alias: "nothing" + } + } proto_id: 2 } size: 1024 } table_implementation { - default_action: "MyIngress.drop" + default_action: "MyIngress.nothing" action_to_next_control { - key: "MyIngress.drop" - value: "MyIngress.t_3" + key: "MyIngress.forward" + value: "MyIngress.t4" } action_to_next_control { - key: "MyIngress.forward" - value: "MyIngress.t_3" + key: "MyIngress.nothing" + value: "MyIngress.t4" } match_name_to_field { - key: "hdr.ethernet.src_addr" + key: "hdr.h1.f3" value { - header_name: "ethernet" - field_name: "src_addr" + header_name: "h1" + field_name: "f3" } } optimized_symbolic_execution_info { - merge_point: "MyIngress.t_3" + merge_point: "MyIngress.t4" } } } } tables { - key: "MyIngress.t_3" + key: "MyIngress.t4" value { table_definition { preamble { - id: 44506558 - name: "MyIngress.t_3" - alias: "t_3" + id: 38046612 + name: "MyIngress.t4" + alias: "t4" } match_fields_by_id { key: 1 value { match_field { id: 1 - name: "hdr.ethernet.dst_addr" - bitwidth: 48 + name: "hdr.h1.f4" + bitwidth: 8 match_type: EXACT } } } match_fields_by_name { - key: "hdr.ethernet.dst_addr" + key: "hdr.h1.f4" value { match_field { id: 1 - name: "hdr.ethernet.dst_addr" - bitwidth: 48 + name: "hdr.h1.f4" + bitwidth: 8 match_type: EXACT } } } - entry_actions { - ref { - id: 25652968 - annotations: "@proto_id(1)" - } - action { - preamble { - id: 25652968 - name: "MyIngress.drop" - alias: "drop" - } - } - proto_id: 1 - } entry_actions { ref { id: 29683729 - annotations: "@proto_id(2)" + annotations: "@proto_id(1)" } action { preamble { @@ -720,61 +630,55 @@ tables { value { param { id: 1 - name: "dst_addr" - bitwidth: 48 - } - } - } - params_by_id { - key: 2 - value { - param { - id: 2 name: "port" bitwidth: 9 } } } - params_by_name { - key: "dst_addr" - value { - param { - id: 1 - name: "dst_addr" - bitwidth: 48 - } - } - } params_by_name { key: "port" value { param { - id: 2 + id: 1 name: "port" bitwidth: 9 } } } } + proto_id: 1 + } + entry_actions { + ref { + id: 27514245 + annotations: "@proto_id(2)" + } + action { + preamble { + id: 27514245 + name: "MyIngress.nothing" + alias: "nothing" + } + } proto_id: 2 } size: 1024 } table_implementation { - default_action: "MyIngress.drop" + default_action: "MyIngress.nothing" action_to_next_control { - key: "MyIngress.drop" + key: "MyIngress.forward" value: "__END_OF_PIPELINE__" } action_to_next_control { - key: "MyIngress.forward" + key: "MyIngress.nothing" value: "__END_OF_PIPELINE__" } match_name_to_field { - key: "hdr.ethernet.dst_addr" + key: "hdr.h1.f4" value { - header_name: "ethernet" - field_name: "dst_addr" + header_name: "h1" + field_name: "f4" } } optimized_symbolic_execution_info { @@ -784,6 +688,27 @@ tables { } } } +tables { + key: "tbl_table_hit_2l83" + value { + table_definition { + preamble { + name: "tbl_table_hit_2l83" + } + } + table_implementation { + default_action: "table_hit_2l83" + action_to_next_control { + key: "table_hit_2l83" + value: "MyIngress.t1" + } + optimized_symbolic_execution_info { + merge_point: "MyIngress.t1" + continue_to_merge_point: true + } + } + } +} pipeline { key: "egress" value { @@ -795,7 +720,7 @@ pipeline { key: "ingress" value { name: "ingress" - initial_control: "MyIngress.t_0" + initial_control: "tbl_table_hit_2l83" } } diff --git a/p4_symbolic/ir/ir.h b/p4_symbolic/ir/ir.h index c02b55a5..bfab9c4b 100644 --- a/p4_symbolic/ir/ir.h +++ b/p4_symbolic/ir/ir.h @@ -31,6 +31,16 @@ namespace ir { // in P4-Symbolic's IR. inline std::string EndOfPipeline() { return "__END_OF_PIPELINE__"; } +// For any table application, BMv2 JSON (and P4-Symbolic IR) use a map from +// actions that may be executed as a result of table application to the next +// table/control that must be evaluated if the corresponding action is executed. +// This encodes conditionals on the result of table applications in P4 code. +// There are also two special action names that refer to whether any table +// entry was hit (table hit) or no table entry was hit (table miss). The +// following two functions return those special action names. +inline std::string TableHitAction() { return "__HIT__"; } +inline std::string TableMissAction() { return "__MISS__"; } + // Transforms bmv2 protobuf and pdpi protobuf into our IR protobuf. absl::StatusOr Bmv2AndP4infoToIr(const bmv2::P4Program& bmv2, const pdpi::IrP4Info& pdpi); diff --git a/p4_symbolic/symbolic/BUILD.bazel b/p4_symbolic/symbolic/BUILD.bazel index d7d0c4ea..2caf68e3 100644 --- a/p4_symbolic/symbolic/BUILD.bazel +++ b/p4_symbolic/symbolic/BUILD.bazel @@ -133,6 +133,24 @@ end_to_end_test( table_entries = "//p4_symbolic/testdata:conditional/conditional_sequence_entries.pb.txt", ) +end_to_end_test( + name = "table_hit_1_test", + output_golden_file = "expected/table_hit_1.txt", + p4_program = "//p4_symbolic/testdata:conditional/table_hit_1.p4", + port_count = 8, + smt_golden_file = "expected/table_hit_1.smt2", + table_entries = "//p4_symbolic/testdata:conditional/table_hit_1_entries.pb.txt", +) + +end_to_end_test( + name = "table_hit_2_test", + output_golden_file = "expected/table_hit_2.txt", + p4_program = "//p4_symbolic/testdata:conditional/table_hit_2.p4", + port_count = 8, + smt_golden_file = "expected/table_hit_2.smt2", + table_entries = "//p4_symbolic/testdata:conditional/table_hit_2_entries.pb.txt", +) + cc_test( name = "values_test", srcs = ["values_test.cc"], diff --git a/p4_symbolic/symbolic/expected/table_hit_1.txt b/p4_symbolic/symbolic/expected/table_hit_1.txt index 5c1521ac..8b0d0026 100644 --- a/p4_symbolic/symbolic/expected/table_hit_1.txt +++ b/p4_symbolic/symbolic/expected/table_hit_1.txt @@ -3,16 +3,16 @@ Cannot find solution! Finding packet for table MyIngress.t_1 and row 0 Dropped = 0 - standard_metadata.ingress_port = #b000000001 - standard_metadata.egress_spec = #b000000010 + standard_metadata.ingress_port = #b000000000 + standard_metadata.egress_spec = #b000000011 Finding packet for table MyIngress.t_2 and row -1 Dropped = 0 - standard_metadata.ingress_port = #b000000001 + standard_metadata.ingress_port = #b000000000 standard_metadata.egress_spec = #b000000010 Finding packet for table MyIngress.t_2 and row 0 Dropped = 0 - standard_metadata.ingress_port = #b000000001 + standard_metadata.ingress_port = #b000000000 standard_metadata.egress_spec = #b000000011 diff --git a/p4_symbolic/symbolic/expected/table_hit_2.txt b/p4_symbolic/symbolic/expected/table_hit_2.txt new file mode 100644 index 00000000..31310abc --- /dev/null +++ b/p4_symbolic/symbolic/expected/table_hit_2.txt @@ -0,0 +1,40 @@ +Finding packet for table MyIngress.t1 and row -1 + Dropped = 0 + standard_metadata.ingress_port = #b000000010 + standard_metadata.egress_spec = #b000000000 + +Finding packet for table MyIngress.t1 and row 0 + Dropped = 0 + standard_metadata.ingress_port = #b000000010 + standard_metadata.egress_spec = #b000000010 + +Finding packet for table MyIngress.t2 and row -1 + Dropped = 0 + standard_metadata.ingress_port = #b000000010 + standard_metadata.egress_spec = #b000000001 + +Finding packet for table MyIngress.t2 and row 0 + Dropped = 0 + standard_metadata.ingress_port = #b000000010 + standard_metadata.egress_spec = #b000000010 + +Finding packet for table MyIngress.t3 and row -1 + Dropped = 0 + standard_metadata.ingress_port = #b000000010 + standard_metadata.egress_spec = #b000000000 + +Finding packet for table MyIngress.t3 and row 0 + Dropped = 0 + standard_metadata.ingress_port = #b000000010 + standard_metadata.egress_spec = #b000000011 + +Finding packet for table MyIngress.t4 and row -1 + Dropped = 0 + standard_metadata.ingress_port = #b000000010 + standard_metadata.egress_spec = #b000000011 + +Finding packet for table tbl_table_hit_2l83 and row -1 + Dropped = 0 + standard_metadata.ingress_port = #b000000010 + standard_metadata.egress_spec = #b000000011 + diff --git a/p4_symbolic/symbolic/table.cc b/p4_symbolic/symbolic/table.cc index 32d20ce3..948ed37c 100644 --- a/p4_symbolic/symbolic/table.cc +++ b/p4_symbolic/symbolic/table.cc @@ -23,6 +23,7 @@ #include #include +#include "absl/container/btree_map.h" #include "absl/status/status.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" @@ -37,6 +38,7 @@ #include "p4_symbolic/symbolic/action.h" #include "p4_symbolic/symbolic/operators.h" #include "p4_symbolic/symbolic/symbolic.h" +#include "p4_symbolic/symbolic/util.h" #include "p4_symbolic/symbolic/values.h" #include "p4_symbolic/z3_util.h" #include "z3++.h" @@ -470,24 +472,35 @@ absl::StatusOr EvaluateTable( .optimized_symbolic_execution_info() .merge_point(); - // We currenlt only support tables that always have the same next control - // construct regardless of the table's matches. - // - // This can be supported by calling EvaluateControl(...) - // inside the above for loop for control found at - // () - // and passing it the complete entry guard. - // - // As an optimization, the loop should not call EvaluateControl - // when all actions have the same next_control, and should instead - // execute the call once outside the loop as below. - for (const auto &[_, next_control] : - table.table_implementation().action_to_next_control()) { + SymbolicTableMatches merged_matches; + + // We use a sorted map to keep the result (i.e. the SMT formula) + // deterministic. + absl::btree_map sorted_action_to_next_control( + table.table_implementation().action_to_next_control().begin(), + table.table_implementation().action_to_next_control().end()); + + // We currently do not support conditionals on which action was executed as + // a result of table application. We do support conditional on table + // hit/miss though. + for (const auto &[action, next_control] : sorted_action_to_next_control) { if (next_control != merge_point) { - return absl::UnimplementedError( - absl::Substitute("Table '$0' invokes different control constructs " - "based on match results", - table_name)); + if (action == ir::TableHitAction() || action == ir::TableMissAction()) { + z3::expr branch_guard = action == ir::TableHitAction() + ? (match_index != kDefaultActionEntryIndex) + : (match_index == kDefaultActionEntryIndex); + ASSIGN_OR_RETURN( + SymbolicTableMatches branch_matches, + control::EvaluateControl(data_plane, next_control, state, + translator, guard && branch_guard)); + ASSIGN_OR_RETURN(merged_matches, util::MergeDisjointTableMatches( + merged_matches, branch_matches)); + } else { + return absl::UnimplementedError( + absl::Substitute("Conditional on executed table action is not " + "supported (table '$0', action '$1')", + table_name, action)); + } } } @@ -501,19 +514,23 @@ absl::StatusOr EvaluateTable( ASSIGN_OR_RETURN(SymbolicTableMatches result, control::EvaluateControl(data_plane, continuation, state, translator, guard)); - + // Merge the result of execution from the merge point with the result of + // execution from action_to_next_control (up to the merge point). + ASSIGN_OR_RETURN(result, + util::MergeDisjointTableMatches(result, merged_matches)); + // Add the table match for the current table to the result. + auto [_, inserted] = + result.insert({table_name, SymbolicTableMatch{ + .matched = guard, + .entry_index = match_index, + }}); // The trace should not contain information for this table, otherwise, it // means we visited the table twice in the same execution path! - if (result.contains(table_name)) { + if (!inserted) { return absl::InternalError(absl::Substitute( "Table '$0' was executed twice in the same path.", table_name)); } - // Add this table's match to the trace, and return it. - result.insert({table_name, SymbolicTableMatch{ - .matched = guard, - .entry_index = match_index, - }}); return result; } diff --git a/p4_symbolic/testdata/conditional/table_hit_1.p4 b/p4_symbolic/testdata/conditional/table_hit_1.p4 index e12f3573..a0baa8fb 100644 --- a/p4_symbolic/testdata/conditional/table_hit_1.p4 +++ b/p4_symbolic/testdata/conditional/table_hit_1.p4 @@ -60,6 +60,8 @@ control MyIngress(inout headers hdr, standard_metadata.egress_spec = port; hdr.ethernet.dst_addr = dst_addr; } + action nothing() { + } table t_1 { key = { @@ -79,9 +81,10 @@ control MyIngress(inout headers hdr, actions = { @proto_id(1) drop; @proto_id(2) forward; + @proto_id(3) nothing; } size = 1024; - default_action = drop(); + default_action = nothing(); } apply { diff --git a/p4_symbolic/testdata/conditional/table_hit_2.p4 b/p4_symbolic/testdata/conditional/table_hit_2.p4 index d51d3f22..e12112fe 100644 --- a/p4_symbolic/testdata/conditional/table_hit_2.p4 +++ b/p4_symbolic/testdata/conditional/table_hit_2.p4 @@ -17,12 +17,12 @@ #include typedef bit<9> egress_spec_t; -typedef bit<48> mac_addr_t; -header ethernet_t { - mac_addr_t dst_addr; - mac_addr_t src_addr; - bit<16> ether_type; +header header_t { + bit<8> f1; + bit<8> f2; + bit<8> f3; + bit<8> f4; } struct metadata { @@ -30,7 +30,7 @@ struct metadata { } struct headers { - ethernet_t ethernet; + header_t h1; } parser MyParser(packet_in packet, @@ -39,11 +39,11 @@ parser MyParser(packet_in packet, inout standard_metadata_t standard_metadata) { state start { - transition parse_ethernet; + transition parse_h1; } - state parse_ethernet { - packet.extract(hdr.ethernet); + state parse_h1 { + packet.extract(hdr.h1); transition accept; } } @@ -52,71 +52,43 @@ parser MyParser(packet_in packet, control MyIngress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) { - action drop() { - mark_to_drop(standard_metadata); + action forward(egress_spec_t port) { + standard_metadata.egress_spec = port; } + action nothing() {} - action forward(mac_addr_t dst_addr, egress_spec_t port) { - standard_metadata.egress_spec = port; - hdr.ethernet.dst_addr = dst_addr; + table t1 { + key = {hdr.h1.f1: exact;} + actions = {@proto_id(1) forward; @proto_id(2) nothing;} + default_action = nothing(); } - - table t_0 { - key = { - hdr.ethernet.ether_type: exact; - } - actions = { - @proto_id(1) drop; - @proto_id(2) forward; - } - size = 1024; - default_action = drop(); + table t2 { + key = {hdr.h1.f2: exact;} + actions = {@proto_id(1) forward; @proto_id(2) nothing;} + default_action = nothing(); } - - table t_1 { - key = { - hdr.ethernet.ether_type: exact; - } - actions = { - @proto_id(1) drop; - @proto_id(2) forward; - } - size = 1024; - default_action = drop(); + table t3 { + key = {hdr.h1.f3: exact;} + actions = {@proto_id(1) forward; @proto_id(2) nothing;} + default_action = nothing(); } - table t_2 { - key = { - hdr.ethernet.src_addr: exact; - } - actions = { - @proto_id(1) drop; - @proto_id(2) forward; - } - size = 1024; - default_action = drop(); - } - table t_3 { - key = { - hdr.ethernet.dst_addr: exact; - } - actions = { - @proto_id(1) drop; - @proto_id(2) forward; - } - size = 1024; - default_action = drop(); + table t4 { + key = {hdr.h1.f4: exact;} + actions = {@proto_id(1) forward; @proto_id(2) nothing;} + default_action = nothing(); } + apply { - if (t_0.apply().hit) { - t_1.apply(); + standard_metadata.egress_spec = 0; + if (t1.apply().hit) { + t2.apply(); } else { - t_2.apply(); + t3.apply(); } - t_3.apply(); + t4.apply(); } } - control MyEgress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) { @@ -127,7 +99,7 @@ control MyEgress(inout headers hdr, control MyDeparser(packet_out packet, in headers hdr) { apply { - packet.emit(hdr.ethernet); + packet.emit(hdr.h1); } } From 1111b84d3aba1e3d2b3b0c2a7f0514aad78e0a55 Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:50:27 +0530 Subject: [PATCH 06/24] gNMI port breakout tests (#710) Co-authored-by: kishanps --- tests/BUILD.bazel | 3 +- tests/thinkit_gnmi_interface_tests.cc | 140 +++++++++++++++++++++++++- tests/thinkit_gnmi_interface_tests.h | 18 +++- tests/thinkit_gnmi_interface_util.cc | 2 +- tests/thinkit_gnmi_interface_util.h | 2 +- 5 files changed, 160 insertions(+), 5 deletions(-) diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 5bca0289..20ec5063 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -78,8 +78,9 @@ cc_library( "@com_github_gnmi//proto/gnmi:gnmi_cc_grpc_proto", "@com_github_google_glog//:glog", "@com_github_nlohmann_json//:nlohmann_json", - "@com_google_absl//absl/status", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/time", diff --git a/tests/thinkit_gnmi_interface_tests.cc b/tests/thinkit_gnmi_interface_tests.cc index c76033b4..98ef01b6 100644 --- a/tests/thinkit_gnmi_interface_tests.cc +++ b/tests/thinkit_gnmi_interface_tests.cc @@ -21,21 +21,29 @@ #include #include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/match.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" +#include "absl/strings/substitute.h" #include "absl/time/clock.h" #include "absl/time/time.h" #include "glog/logging.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "gutil/status.h" #include "gutil/status_matchers.h" +#include "gutil/testing.h" +#include "include/nlohmann/json.hpp" #include "lib/gnmi/gnmi_helper.h" +#include "p4_pdpi/pd.h" #include "proto/gnmi/gnmi.grpc.pb.h" -#include "include/nlohmann/json.hpp" +#include "sai_p4/instantiations/google/sai_pd.pb.h" +#include "tests/thinkit_gnmi_interface_util.h" #include "tests/thinkit_util.h" #include "thinkit/ssh_client.h" #include "thinkit/switch.h" @@ -309,4 +317,134 @@ void TestGnmiInterfaceConfigSetId(thinkit::Switch& sut, GetGnmiStatePathInfo(sut_gnmi_stub.get(), if_state_path, resp_parse_str)); EXPECT_THAT(state_path_response, HasSubstr(std::to_string(id))); } + +void BreakoutDuringPortInUse(thinkit::Switch& sut, + gnmi::gNMI::StubInterface* sut_gnmi_stub, + RandomPortBreakoutInfo port_info, + const std::string& platform_json_contents, + bool test_child_port_in_use) { + // Get the original breakout info on the port. + // This contains the state values of physical channels and + // operational status information for ports in original breakout mode. + ASSERT_OK_AND_ASSIGN( + auto orig_breakout_info, + GetBreakoutStateInfoForPort(sut_gnmi_stub, port_info.port_name, + port_info.curr_breakout_mode)); + + // Verify that all ports for the selected port are operationally up. + auto resp_parse_str = "openconfig-interfaces:oper-status"; + for (const auto& p : orig_breakout_info) { + auto if_state_path = absl::StrCat("interfaces/interface[name=", p.first, + "]/state/oper-status"); + ASSERT_OK_AND_ASSIGN( + auto state_path_response, + GetGnmiStatePathInfo(sut_gnmi_stub, if_state_path, resp_parse_str)); + EXPECT_THAT(state_path_response, HasSubstr(kStateUp)); + } + + // Determine port to install router interface on. + auto in_use_port = port_info.port_name; + if (test_child_port_in_use) { + for (const auto& p : orig_breakout_info) { + if (p.first != port_info.port_name) { + in_use_port = p.first; + break; + } + } + ASSERT_NE(in_use_port, port_info.port_name); + } + + // Configure router interface on selected port to install port dependency. + ASSERT_OK_AND_ASSIGN(auto in_use_port_index, + GetPortIndex(platform_json_contents, in_use_port)); + std::unique_ptr sut_p4_session; + ASSERT_OK_AND_ASSIGN(sut_p4_session, pdpi::P4RuntimeSession::Create(sut)); + const sai::TableEntry pd_entry = + gutil::ParseProtoOrDie(absl::Substitute( + R"pb( + router_interface_table_entry { + match { router_interface_id: "router-interface-1" } + action { + set_port_and_src_mac { port: "$0" src_mac: "02:2a:10:00:00:03" } + } + } + )pb", + in_use_port_index)); + ASSERT_OK_AND_ASSIGN(const p4::v1::TableEntry pi_entry, + pdpi::PartialPdTableEntryToPiTableEntry(pdpi::IrP4Info(), pd_entry)); + ASSERT_OK(pdpi::InstallPiTableEntry(sut_p4_session.get(), pi_entry)); + + // Get breakout config for the new breakout mode. + gnmi::SetRequest req; + gnmi::SetResponse resp; + grpc::ClientContext context; + ASSERT_OK_AND_ASSIGN(auto port_index, GetPortIndex(platform_json_contents, + port_info.port_name)); + ASSERT_OK(GetBreakoutModeConfigFromString(req, port_index, + port_info.supported_breakout_mode)); + + // Apply breakout config on port. Expect the set operation to fail since the + // port/its child is in use. + auto status = sut_gnmi_stub->Set(&context, req, &resp); + ASSERT_NE(status.ok(), true); + + // Get expected port information for new breakout mode. + ASSERT_OK_AND_ASSIGN( + auto new_breakout_info, + GetExpectedPortInfoForBreakoutMode(port_info.port_name, + port_info.supported_breakout_mode)); + auto non_existing_port_list = GetNonExistingPortsAfterBreakout( + orig_breakout_info, new_breakout_info, false); + // Verify breakout related state paths. + ASSERT_OK(ValidateBreakoutState(sut_gnmi_stub, new_breakout_info, + non_existing_port_list)); + + // Delete the created router interface on the port using P4 interface. + ASSERT_OK(pdpi::ClearTableEntries(sut_p4_session.get())); + + // Wait for port dependency to be deleted and breakout config to go through. + absl::SleepFor(absl::Seconds(10)); + + // Verify that the config is successfully applied now. + non_existing_port_list = GetNonExistingPortsAfterBreakout( + orig_breakout_info, new_breakout_info, true); + ASSERT_OK(ValidateBreakoutState(sut_gnmi_stub, new_breakout_info, + non_existing_port_list)); + + // Restore original port breakout config on port under test. + ASSERT_OK(GetBreakoutModeConfigFromString(req, port_index, + port_info.curr_breakout_mode)); + ASSERT_OK(sut_gnmi_stub->Set(&context, req, &resp)); + absl::SleepFor(absl::Seconds(5)); + + // Verify that the config is successfully applied. + non_existing_port_list = GetNonExistingPortsAfterBreakout( + orig_breakout_info, new_breakout_info, false); + ASSERT_OK(ValidateBreakoutState(sut_gnmi_stub, new_breakout_info, + non_existing_port_list)); +} + +void TestGNMIParentPortInUseDuringBreakout( + thinkit::Switch& sut, std::string& platform_json_contents) { + ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); + // Get a random port from list of front panel ports that supports at least + // one breakout mode of required type other than its current mode. + ASSERT_OK_AND_ASSIGN(auto port, GetRandomPortWithSupportedBreakoutModes( + *sut_gnmi_stub, platform_json_contents, + BreakoutType::kAny)); + BreakoutDuringPortInUse(sut, sut_gnmi_stub.get(), port, + platform_json_contents, false); +} + +void TestGNMIChildPortInUseDuringBreakout(thinkit::Switch& sut, + std::string& platform_json_contents) { + ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); + // Get a random port from list of front panel ports that supports at least + // one breakout mode of required type other than its current mode. + ASSERT_OK_AND_ASSIGN(auto port, GetRandomPortWithSupportedBreakoutModes( + *sut_gnmi_stub, platform_json_contents, + BreakoutType::kChannelized)); + BreakoutDuringPortInUse(sut, sut_gnmi_stub.get(), port, + platform_json_contents, true); +} } // namespace pins_test diff --git a/tests/thinkit_gnmi_interface_tests.h b/tests/thinkit_gnmi_interface_tests.h index 488c156a..0c08a6ab 100644 --- a/tests/thinkit_gnmi_interface_tests.h +++ b/tests/thinkit_gnmi_interface_tests.h @@ -17,6 +17,7 @@ #include "absl/container/flat_hash_map.h" #include "absl/strings/string_view.h" +#include "tests/thinkit_gnmi_interface_util.h" #include "thinkit/ssh_client.h" #include "thinkit/switch.h" @@ -40,6 +41,21 @@ void TestGnmiInterfaceConfigSetPortSpeed( // Test port Id GNMI config and state paths. void TestGnmiInterfaceConfigSetId(thinkit::Switch& sut, absl::string_view if_name, const int id); -} // namespace pins_test +// Test port breakout during parent port in use. +void TestGNMIParentPortInUseDuringBreakout(thinkit::Switch& sut, + std::string& platform_json_contents); + +// Test port breakout during child port in use. +void TestGNMIChildPortInUseDuringBreakout(thinkit::Switch& sut, + std::string& platform_json_contents); + +// Helper function to test port in use. +void BreakoutDuringPortInUse(thinkit::Switch& sut, + gnmi::gNMI::StubInterface* sut_gnmi_stub, + RandomPortBreakoutInfo port_info, + const std::string& platform_json_contents, + bool test_child_port_in_use); + +} // namespace pins_test #endif // GOOGLE_TESTS_THINKIT_GNMI_INTERFACE_TESTS_H_ diff --git a/tests/thinkit_gnmi_interface_util.cc b/tests/thinkit_gnmi_interface_util.cc index 0778feb9..3c8e6e64 100644 --- a/tests/thinkit_gnmi_interface_util.cc +++ b/tests/thinkit_gnmi_interface_util.cc @@ -21,9 +21,9 @@ #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 "include/nlohmann/json.hpp" #include "tests/thinkit_util.h" #include "thinkit/ssh_client.h" #include "thinkit/switch.h" diff --git a/tests/thinkit_gnmi_interface_util.h b/tests/thinkit_gnmi_interface_util.h index 668af0d7..729b5867 100644 --- a/tests/thinkit_gnmi_interface_util.h +++ b/tests/thinkit_gnmi_interface_util.h @@ -21,11 +21,11 @@ #include "absl/strings/string_view.h" #include "absl/strings/substitute.h" #include "gutil/testing.h" +#include "include/nlohmann/json.hpp" #include "lib/gnmi/gnmi_helper.h" #include "p4_pdpi/pd.h" #include "proto/gnmi/gnmi.grpc.pb.h" #include "sai_p4/instantiations/google/sai_pd.pb.h" -#include "include/nlohmann/json.hpp" #include "thinkit/ssh_client.h" #include "thinkit/switch.h" From 85cfeb654d8a914219bceb81ae3a5d8fea9d40f1 Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:51:27 +0530 Subject: [PATCH 07/24] [Thinkit] Enable Sflow test, Implemented P4Info push test using thinkit::MirrorTestbed & gNMI port interface tests. (#711) Co-authored-by: kishanps --- tests/forwarding/BUILD.bazel | 20 ++++++++++ tests/forwarding/p4info_push_test.cc | 56 +++++++++++++++++++++++++++ tests/forwarding/p4info_push_test.h | 52 +++++++++++++++++++++++++ tests/sflow/BUILD.bazel | 29 ++++++++++++++ tests/sflow/sflow_test.h | 15 +++++++ tests/thinkit_gnmi_interface_tests.cc | 32 +++++++++------ tests/thinkit_gnmi_interface_tests.h | 3 +- tests/thinkit_util.h | 1 + 8 files changed, 195 insertions(+), 13 deletions(-) create mode 100644 tests/forwarding/p4info_push_test.cc create mode 100644 tests/forwarding/p4info_push_test.h create mode 100644 tests/sflow/BUILD.bazel create mode 100644 tests/sflow/sflow_test.h diff --git a/tests/forwarding/BUILD.bazel b/tests/forwarding/BUILD.bazel index 86dc7620..5023d3b8 100644 --- a/tests/forwarding/BUILD.bazel +++ b/tests/forwarding/BUILD.bazel @@ -60,6 +60,26 @@ cc_library( ], ) +cc_library( + name = "p4info_push_test", + testonly = True, + srcs = ["p4info_push_test.cc"], + hdrs = ["p4info_push_test.h"], + deps = [ + "//gutil:proto_matchers", + "//gutil:status_matchers", + "//lib/gnmi:gnmi_helper", + "//p4_pdpi:p4_runtime_session", + "//tests:thinkit_sanity_tests", + "//tests/lib:switch_test_setup_helpers", + "//thinkit:mirror_testbed", + "//thinkit:mirror_testbed_fixture", + "@com_github_p4lang_p4runtime//:p4runtime_cc_proto", + "@com_google_googletest//:gtest", + ], + alwayslink = True, +) + cc_library( name = "p4_blackbox_fixture", testonly = True, diff --git a/tests/forwarding/p4info_push_test.cc b/tests/forwarding/p4info_push_test.cc new file mode 100644 index 00000000..88759028 --- /dev/null +++ b/tests/forwarding/p4info_push_test.cc @@ -0,0 +1,56 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tests/forwarding/p4info_push_test.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "gutil/proto_matchers.h" +#include "gutil/status_matchers.h" +#include "lib/gnmi/gnmi_helper.h" +#include "p4/v1/p4runtime.pb.h" +#include "p4_pdpi/p4_runtime_session.h" +#include "tests/lib/switch_test_setup_helpers.h" + +// Note: "gutil/status_matchers.h" is needed for GitHub builds to succeed. + +namespace pins { +namespace { + +// Sends P4Info to the switch and makes sure it works. +TEST_P(P4InfoPushTestFixture, P4InfoPushTest) { + LOG(INFO) << "Test started"; + + // Push the gNMI configuration and P4Info to the SUT. + LOG(INFO) << "Pushing gNMI config & P4info"; + ASSERT_OK_AND_ASSIGN( + std::unique_ptr sut_p4rt_session, + pins_test::ConfigureSwitchAndReturnP4RuntimeSession( + GetTestbed().Sut(), GetParam().gnmi_config, GetParam().p4info)); + + // Pull P4Info, make sure it is the same as the pushed one. + LOG(INFO) << "Pulling P4Info"; + ASSERT_OK_AND_ASSIGN(const auto response, + pdpi::GetForwardingPipelineConfig( + sut_p4rt_session.get(), + p4::v1::GetForwardingPipelineConfigRequest::ALL)); + ASSERT_THAT(response.config().p4info(), + gutil::EqualsProto(GetParam().p4info)); + + LOG(INFO) << "Test finished successfully"; +} + +// TODO: implement a negative test. +} // namespace +} // namespace pins diff --git a/tests/forwarding/p4info_push_test.h b/tests/forwarding/p4info_push_test.h new file mode 100644 index 00000000..760f5a99 --- /dev/null +++ b/tests/forwarding/p4info_push_test.h @@ -0,0 +1,52 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PINS_TESTS_FORWARDING_P4INFO_PUSH_TEST_H_ +#define PINS_TESTS_FORWARDING_P4INFO_PUSH_TEST_H_ + +#include + +#include "gutil/status_matchers.h" +#include "tests/thinkit_sanity_tests.h" +#include "thinkit/mirror_testbed.h" +#include "thinkit/mirror_testbed_fixture.h" + +namespace pins { + +struct P4InfoPushTestParams { + std::string description; + // TODO: use GenericTestbed instead. + thinkit::MirrorTestbedInterface* testbed_interface; + std::string gnmi_config; + p4::config::v1::P4Info p4info; +}; + +class P4InfoPushTestFixture + : public testing::TestWithParam { + protected: + void SetUp() override { testbed_interface_->SetUp(); } + void TearDown() override { testbed_interface_->TearDown(); } + + thinkit::MirrorTestbed& GetTestbed() { + return testbed_interface_->GetMirrorTestbed(); + } + + private: + std::unique_ptr testbed_interface_ = + absl::WrapUnique(GetParam().testbed_interface); +}; + +} // namespace pins + +#endif // PINS_TESTS_FORWARDING_P4INFO_PUSH_TEST_H_ diff --git a/tests/sflow/BUILD.bazel b/tests/sflow/BUILD.bazel new file mode 100644 index 00000000..befd8ac5 --- /dev/null +++ b/tests/sflow/BUILD.bazel @@ -0,0 +1,29 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package( + default_visibility = ["//visibility:public"], + licenses = ["notice"], +) + +cc_library( + name = "sflow_test", + testonly = True, + hdrs = ["sflow_test.h"], + deps = [ + "//thinkit:mirror_testbed_fixture", + "@com_github_grpc_grpc//:grpc++", + ], + alwayslink = True, +) diff --git a/tests/sflow/sflow_test.h b/tests/sflow/sflow_test.h new file mode 100644 index 00000000..fd461cd3 --- /dev/null +++ b/tests/sflow/sflow_test.h @@ -0,0 +1,15 @@ +#ifndef PINS_TESTS_SFLOW_SFLOW_TEST_H_ +#define PINS_TESTS_SFLOW_SFLOW_TEST_H_ + +#include "thinkit/mirror_testbed_fixture.h" + +namespace pins { + +// TODO: to be implemented +class SflowTestFixture : public thinkit::MirrorTestbedFixture { + protected: +}; + +} // namespace pins + +#endif // PINS_TESTS_SFLOW_SFLOW_TEST_H_ diff --git a/tests/thinkit_gnmi_interface_tests.cc b/tests/thinkit_gnmi_interface_tests.cc index 98ef01b6..3dd240e6 100644 --- a/tests/thinkit_gnmi_interface_tests.cc +++ b/tests/thinkit_gnmi_interface_tests.cc @@ -22,7 +22,6 @@ #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" -#include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/match.h" #include "absl/strings/numbers.h" @@ -100,9 +99,27 @@ void TestGnmiInterfaceConfigSetAdminStatus(thinkit::Switch& sut, LOG(INFO) << "Enabling front panel port: " << if_name; ASSERT_OK(SetGnmiConfigPath(sut_gnmi_stub.get(), if_enabled_config_path, GnmiSetType::kUpdate, kEnabledTrue)); - absl::SleepFor(absl::Seconds(5)); // Perform state path verifications. + // Verify /interfaces/interface[name=]/state/oper-status = UP using + // polling with timeout. + if_state_path = absl::StrCat("interfaces/interface[name=", if_name, + "]/state/oper-status"); + resp_parse_str = "openconfig-interfaces:oper-status"; + auto oper_status_check = false; + auto start_time = absl::Now(); + auto timeout = absl::Seconds(60); + while (absl::Now() < (start_time + timeout)) { + ASSERT_OK_AND_ASSIGN(state_path_response, + GetGnmiStatePathInfo(sut_gnmi_stub.get(), + if_state_path, resp_parse_str)); + if (absl::StrContains(state_path_response, kStateUp)) { + oper_status_check = true; + break; + } + } + EXPECT_TRUE(oper_status_check); + // Verify /interfaces/interface[name=]/state/enabled = true. if_state_path = absl::StrCat("interfaces/interface[name=", if_name, "]/state/enabled"); @@ -120,15 +137,6 @@ void TestGnmiInterfaceConfigSetAdminStatus(thinkit::Switch& sut, state_path_response, GetGnmiStatePathInfo(sut_gnmi_stub.get(), if_state_path, resp_parse_str)); EXPECT_THAT(state_path_response, HasSubstr(kStateUp)); - - // Verify /interfaces/interface[name=]/state/oper-status = UP. - if_state_path = absl::StrCat("interfaces/interface[name=", if_name, - "]/state/oper-status"); - resp_parse_str = "openconfig-interfaces:oper-status"; - ASSERT_OK_AND_ASSIGN( - state_path_response, - GetGnmiStatePathInfo(sut_gnmi_stub.get(), if_state_path, resp_parse_str)); - EXPECT_THAT(state_path_response, HasSubstr(kStateUp)); } void TestGnmiPortComponentPaths( @@ -191,7 +199,7 @@ void TestGnmiPortComponentPaths( void TestGnmiInterfaceConfigSetPortSpeed( thinkit::Switch& sut, absl::string_view if_name, - const std::vector& supported_speeds) { + const absl::flat_hash_set& supported_speeds) { ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); // Get current configured port speed for the port. diff --git a/tests/thinkit_gnmi_interface_tests.h b/tests/thinkit_gnmi_interface_tests.h index 0c08a6ab..a18746ea 100644 --- a/tests/thinkit_gnmi_interface_tests.h +++ b/tests/thinkit_gnmi_interface_tests.h @@ -16,6 +16,7 @@ #define GOOGLE_TESTS_THINKIT_GNMI_INTERFACE_TESTS_H_ #include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" #include "absl/strings/string_view.h" #include "tests/thinkit_gnmi_interface_util.h" #include "thinkit/ssh_client.h" @@ -36,7 +37,7 @@ void TestGnmiPortComponentPaths( // The test expects that auto-negotiation has been disabled for the given port. void TestGnmiInterfaceConfigSetPortSpeed( thinkit::Switch& sut, absl::string_view if_name, - const std::vector& supported_speeds); + const absl::flat_hash_set& supported_speeds); // Test port Id GNMI config and state paths. void TestGnmiInterfaceConfigSetId(thinkit::Switch& sut, diff --git a/tests/thinkit_util.h b/tests/thinkit_util.h index 986f1f0e..369bd24d 100644 --- a/tests/thinkit_util.h +++ b/tests/thinkit_util.h @@ -15,6 +15,7 @@ #ifndef GOOGLE_TESTS_THINKIT_UTIL_H_ #define GOOGLE_TESTS_THINKIT_UTIL_H_ +#include "absl/time/time.h" namespace pins_test { constexpr char kEnabledFalse[] = "{\"enabled\":false}"; From ea4722ce4d57b77bf008d3ced4d72e2f2cdfe23f Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:24:21 +0000 Subject: [PATCH 08/24] [P4-Symbolic] Update ingress port constraints to rule out packet-out generation for now. (#713) Co-authored-by: kheradmandG Co-authored-by: smolkaj Co-authored-by: kishanps --- p4_symbolic/symbolic/BUILD.bazel | 9 -- p4_symbolic/symbolic/expected/basic.smt2 | 14 +-- .../symbolic/expected/conditional.smt2 | 12 +-- .../expected/conditional_sequence.smt2 | 100 +++++++++--------- p4_symbolic/symbolic/expected/hardcoded.smt2 | 4 +- p4_symbolic/symbolic/expected/reflector.smt2 | 2 +- p4_symbolic/symbolic/expected/reflector.txt | 4 +- .../symbolic/expected/string_optional.smt2 | 32 +++--- .../symbolic/expected/string_optional.txt | 4 +- p4_symbolic/symbolic/expected/table.smt2 | 10 +- p4_symbolic/symbolic/expected/table_hit_2.txt | 20 ++-- p4_symbolic/symbolic/expected/vrf.smt2 | 23 ++-- p4_symbolic/symbolic/expected/vrf.txt | 22 ++-- p4_symbolic/symbolic/symbolic.cc | 6 +- 14 files changed, 125 insertions(+), 137 deletions(-) diff --git a/p4_symbolic/symbolic/BUILD.bazel b/p4_symbolic/symbolic/BUILD.bazel index 2caf68e3..75e349c8 100644 --- a/p4_symbolic/symbolic/BUILD.bazel +++ b/p4_symbolic/symbolic/BUILD.bazel @@ -104,15 +104,6 @@ end_to_end_test( table_entries = "//p4_symbolic/testdata:ipv4-routing/entries.pb.txt", ) -end_to_end_test( - name = "string_optional_test", - output_golden_file = "expected/string_optional.txt", - p4_program = "//p4_symbolic/testdata:string-optional/program.p4", - port_count = 3, - smt_golden_file = "expected/string_optional.smt2", - table_entries = "//p4_symbolic/testdata:string-optional/entries.pb.txt", -) - # Checks the behavior of symbolic execution when there is a table application after a conditional. # Before go/optimized-symbolic-execution, p4-symbolic was executing t3 twice (once from the if # branch and once from the else branch). Now it executed each table exactly once, leading to diff --git a/p4_symbolic/symbolic/expected/basic.smt2 b/p4_symbolic/symbolic/expected/basic.smt2 index 590780c9..6fc6090c 100644 --- a/p4_symbolic/symbolic/expected/basic.smt2 +++ b/p4_symbolic/symbolic/expected/basic.smt2 @@ -6,7 +6,7 @@ (declare-fun ipv4.$valid$ () Bool) (assert (let (($x123 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x123)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x123))) (assert (let (($x45 (= ipv4.dstAddr (_ bv168427520 32)))) (let (($x46 (and true $x45))) @@ -57,7 +57,7 @@ (declare-fun ipv4.$valid$ () Bool) (assert (let (($x123 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x123)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x123))) (assert (let (($x45 (= ipv4.dstAddr (_ bv168427520 32)))) (let (($x46 (and true $x45))) @@ -97,8 +97,8 @@ (let (($x63 (and (and $x42 $x61) $x50))) (let ((?x113 (ite $x60 (_ bv1 9) (ite $x63 (_ bv0 9) (ite $x67 (_ bv1 9) (ite $x71 (_ bv1 9) ?x77)))))) (let (($x41 (= ?x113 (_ bv511 9)))) - (let (($x243 (and (not $x41) $x42))) - (and $x243 (= ?x112 0))))))))))))))))))))))) + (let (($x233 (and (not $x41) $x42))) + (and $x233 (= ?x112 0))))))))))))))))))))))) (check-sat) ; @@ -109,7 +109,7 @@ (declare-fun ipv4.$valid$ () Bool) (assert (let (($x123 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x123)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x123))) (assert (let (($x45 (= ipv4.dstAddr (_ bv168427520 32)))) (let (($x46 (and true $x45))) @@ -160,7 +160,7 @@ (declare-fun ipv4.$valid$ () Bool) (assert (let (($x123 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x123)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x123))) (assert (let (($x45 (= ipv4.dstAddr (_ bv168427520 32)))) (let (($x46 (and true $x45))) @@ -211,7 +211,7 @@ (declare-fun ipv4.$valid$ () Bool) (assert (let (($x123 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x123)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x123))) (assert (let (($x45 (= ipv4.dstAddr (_ bv168427520 32)))) (let (($x46 (and true $x45))) diff --git a/p4_symbolic/symbolic/expected/conditional.smt2 b/p4_symbolic/symbolic/expected/conditional.smt2 index 2f412bb9..f4fd807a 100644 --- a/p4_symbolic/symbolic/expected/conditional.smt2 +++ b/p4_symbolic/symbolic/expected/conditional.smt2 @@ -5,7 +5,7 @@ (declare-fun ethernet.dst_addr () (_ BitVec 48)) (assert (let (($x59 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x59)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x59))) (assert (let (($x31 (= standard_metadata.ingress_port (concat (_ bv0 8) (_ bv0 1))))) (let (($x33 (and true $x31))) @@ -37,7 +37,7 @@ (declare-fun ethernet.dst_addr () (_ BitVec 48)) (assert (let (($x59 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x59)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x59))) (assert (let (($x31 (= standard_metadata.ingress_port (concat (_ bv0 8) (_ bv0 1))))) (let (($x33 (and true $x31))) @@ -69,7 +69,7 @@ (declare-fun ethernet.dst_addr () (_ BitVec 48)) (assert (let (($x59 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x59)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x59))) (assert (let (($x31 (= standard_metadata.ingress_port (concat (_ bv0 8) (_ bv0 1))))) (let (($x33 (and true $x31))) @@ -102,7 +102,7 @@ (declare-fun ethernet.dst_addr () (_ BitVec 48)) (assert (let (($x59 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x59)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x59))) (assert (let (($x31 (= standard_metadata.ingress_port (concat (_ bv0 8) (_ bv0 1))))) (let (($x33 (and true $x31))) @@ -125,7 +125,7 @@ (let ((?x45 (ite (and true (not $x41)) (_ bv511 9) (ite $x34 (_ bv511 9) (ite $x33 (_ bv511 9) standard_metadata.egress_spec))))) (let ((?x50 (ite $x42 (_ bv1 9) ?x45))) (let (($x52 (= ?x50 (_ bv511 9)))) - (let (($x157 (and (not $x52) true))) - (and $x157 (= ?x47 0)))))))))))))) + (let (($x147 (and (not $x52) true))) + (and $x147 (= ?x47 0)))))))))))))) (check-sat) diff --git a/p4_symbolic/symbolic/expected/conditional_sequence.smt2 b/p4_symbolic/symbolic/expected/conditional_sequence.smt2 index 8d3160c9..a2d50bc8 100644 --- a/p4_symbolic/symbolic/expected/conditional_sequence.smt2 +++ b/p4_symbolic/symbolic/expected/conditional_sequence.smt2 @@ -5,7 +5,7 @@ (declare-fun h1.f1 () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) @@ -13,8 +13,8 @@ (assert (let (($x41 (and true (not (= h1.f1 (concat (_ bv0 7) (_ bv0 1))))))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x143 (not $x76))) - (and (and $x143 $x41) (= (- 1) (- 1))))))) + (let (($x131 (not $x76))) + (and (and $x131 $x41) (= (- 1) (- 1))))))) (check-sat) ; @@ -24,7 +24,7 @@ (declare-fun h1.f2 () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) @@ -32,8 +32,8 @@ (assert (let (($x46 (and true (not (= h1.f2 (concat (_ bv0 7) (_ bv0 1))))))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x143 (not $x76))) - (and (and $x143 $x46) (= (- 1) (- 1))))))) + (let (($x131 (not $x76))) + (and (and $x131 $x46) (= (- 1) (- 1))))))) (check-sat) ; @@ -43,7 +43,7 @@ (declare-fun h1.f3 () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) @@ -51,8 +51,8 @@ (assert (let (($x50 (and true (not (= h1.f3 (concat (_ bv0 7) (_ bv0 1))))))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x143 (not $x76))) - (and (and $x143 $x50) (= (- 1) (- 1))))))) + (let (($x131 (not $x76))) + (and (and $x131 $x50) (= (- 1) (- 1))))))) (check-sat) ; @@ -62,7 +62,7 @@ (declare-fun h1.f4 () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) @@ -70,8 +70,8 @@ (assert (let (($x54 (and true (not (= h1.f4 (concat (_ bv0 7) (_ bv0 1))))))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x143 (not $x76))) - (and (and $x143 $x54) (= (- 1) (- 1))))))) + (let (($x131 (not $x76))) + (and (and $x131 $x54) (= (- 1) (- 1))))))) (check-sat) ; @@ -81,7 +81,7 @@ (declare-fun h1.f5 () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) @@ -89,8 +89,8 @@ (assert (let (($x58 (and true (not (= h1.f5 (concat (_ bv0 7) (_ bv0 1))))))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x143 (not $x76))) - (and (and $x143 $x58) (= (- 1) (- 1))))))) + (let (($x131 (not $x76))) + (and (and $x131 $x58) (= (- 1) (- 1))))))) (check-sat) ; @@ -100,7 +100,7 @@ (declare-fun h1.f6 () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) @@ -108,8 +108,8 @@ (assert (let (($x62 (and true (not (= h1.f6 (concat (_ bv0 7) (_ bv0 1))))))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x143 (not $x76))) - (and (and $x143 $x62) (= (- 1) (- 1))))))) + (let (($x131 (not $x76))) + (and (and $x131 $x62) (= (- 1) (- 1))))))) (check-sat) ; @@ -119,7 +119,7 @@ (declare-fun h1.f7 () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) @@ -127,8 +127,8 @@ (assert (let (($x66 (and true (not (= h1.f7 (concat (_ bv0 7) (_ bv0 1))))))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x143 (not $x76))) - (and (and $x143 $x66) (= (- 1) (- 1))))))) + (let (($x131 (not $x76))) + (and (and $x131 $x66) (= (- 1) (- 1))))))) (check-sat) ; @@ -138,7 +138,7 @@ (declare-fun h1.f8 () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) @@ -146,8 +146,8 @@ (assert (let (($x70 (and true (not (= h1.f8 (concat (_ bv0 7) (_ bv0 1))))))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x143 (not $x76))) - (and (and $x143 $x70) (= (- 1) (- 1))))))) + (let (($x131 (not $x76))) + (and (and $x131 $x70) (= (- 1) (- 1))))))) (check-sat) ; @@ -157,7 +157,7 @@ (declare-fun h1.f1 () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) @@ -167,8 +167,8 @@ (let (($x38 (= h1.f1 ?x37))) (let (($x40 (and true $x38))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x143 (not $x76))) - (and (and $x143 $x40) (= (- 1) (- 1))))))))) + (let (($x131 (not $x76))) + (and (and $x131 $x40) (= (- 1) (- 1))))))))) (check-sat) ; @@ -178,7 +178,7 @@ (declare-fun h1.f2 () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) @@ -188,8 +188,8 @@ (let (($x43 (= h1.f2 ?x37))) (let (($x45 (and true $x43))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x143 (not $x76))) - (and (and $x143 $x45) (= (- 1) (- 1))))))))) + (let (($x131 (not $x76))) + (and (and $x131 $x45) (= (- 1) (- 1))))))))) (check-sat) ; @@ -199,7 +199,7 @@ (declare-fun h1.f3 () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) @@ -209,8 +209,8 @@ (let (($x47 (= h1.f3 ?x37))) (let (($x49 (and true $x47))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x143 (not $x76))) - (and (and $x143 $x49) (= (- 1) (- 1))))))))) + (let (($x131 (not $x76))) + (and (and $x131 $x49) (= (- 1) (- 1))))))))) (check-sat) ; @@ -220,7 +220,7 @@ (declare-fun h1.f4 () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) @@ -230,8 +230,8 @@ (let (($x51 (= h1.f4 ?x37))) (let (($x53 (and true $x51))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x143 (not $x76))) - (and (and $x143 $x53) (= (- 1) (- 1))))))))) + (let (($x131 (not $x76))) + (and (and $x131 $x53) (= (- 1) (- 1))))))))) (check-sat) ; @@ -241,7 +241,7 @@ (declare-fun h1.f5 () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) @@ -251,8 +251,8 @@ (let (($x55 (= h1.f5 ?x37))) (let (($x57 (and true $x55))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x143 (not $x76))) - (and (and $x143 $x57) (= (- 1) (- 1))))))))) + (let (($x131 (not $x76))) + (and (and $x131 $x57) (= (- 1) (- 1))))))))) (check-sat) ; @@ -262,7 +262,7 @@ (declare-fun h1.f6 () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) @@ -272,8 +272,8 @@ (let (($x59 (= h1.f6 ?x37))) (let (($x61 (and true $x59))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x143 (not $x76))) - (and (and $x143 $x61) (= (- 1) (- 1))))))))) + (let (($x131 (not $x76))) + (and (and $x131 $x61) (= (- 1) (- 1))))))))) (check-sat) ; @@ -283,7 +283,7 @@ (declare-fun h1.f7 () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) @@ -293,8 +293,8 @@ (let (($x63 (= h1.f7 ?x37))) (let (($x65 (and true $x63))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x143 (not $x76))) - (and (and $x143 $x65) (= (- 1) (- 1))))))))) + (let (($x131 (not $x76))) + (and (and $x131 $x65) (= (- 1) (- 1))))))))) (check-sat) ; @@ -304,7 +304,7 @@ (declare-fun h1.f8 () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) @@ -314,8 +314,8 @@ (let (($x67 (= h1.f8 ?x37))) (let (($x69 (and true $x67))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x143 (not $x76))) - (and (and $x143 $x69) (= (- 1) (- 1))))))))) + (let (($x131 (not $x76))) + (and (and $x131 $x69) (= (- 1) (- 1))))))))) (check-sat) ; @@ -325,7 +325,7 @@ (declare-fun h1.fr () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) @@ -343,7 +343,7 @@ (declare-fun h1.fr () (_ BitVec 8)) (assert (let (($x90 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x90))) (assert (let (($x92 (= standard_metadata.egress_spec (_ bv1 9)))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) diff --git a/p4_symbolic/symbolic/expected/hardcoded.smt2 b/p4_symbolic/symbolic/expected/hardcoded.smt2 index 0e4b312a..d9199890 100644 --- a/p4_symbolic/symbolic/expected/hardcoded.smt2 +++ b/p4_symbolic/symbolic/expected/hardcoded.smt2 @@ -4,7 +4,7 @@ (declare-fun standard_metadata.egress_spec () (_ BitVec 9)) (assert (let (($x45 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x45)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x45))) (assert (let ((?x26 (concat (_ bv0 8) (_ bv0 1)))) (let (($x27 (= standard_metadata.ingress_port ?x26))) @@ -29,7 +29,7 @@ (declare-fun standard_metadata.egress_spec () (_ BitVec 9)) (assert (let (($x45 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x45)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x45))) (assert (let ((?x26 (concat (_ bv0 8) (_ bv0 1)))) (let (($x27 (= standard_metadata.ingress_port ?x26))) diff --git a/p4_symbolic/symbolic/expected/reflector.smt2 b/p4_symbolic/symbolic/expected/reflector.smt2 index b23fbdc8..13fb49fd 100644 --- a/p4_symbolic/symbolic/expected/reflector.smt2 +++ b/p4_symbolic/symbolic/expected/reflector.smt2 @@ -4,7 +4,7 @@ (declare-fun standard_metadata.egress_spec () (_ BitVec 9)) (assert (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35))) (assert (let ((?x25 (ite true standard_metadata.ingress_port standard_metadata.egress_spec))) (let (($x27 (= ?x25 (_ bv511 9)))) diff --git a/p4_symbolic/symbolic/expected/reflector.txt b/p4_symbolic/symbolic/expected/reflector.txt index d2c79b26..fd7908fd 100644 --- a/p4_symbolic/symbolic/expected/reflector.txt +++ b/p4_symbolic/symbolic/expected/reflector.txt @@ -1,5 +1,5 @@ Finding packet for table tbl_reflector54 and row -1 Dropped = 0 - standard_metadata.ingress_port = #b000000000 - standard_metadata.egress_spec = #b000000000 + standard_metadata.ingress_port = #b000000001 + standard_metadata.egress_spec = #b000000001 diff --git a/p4_symbolic/symbolic/expected/string_optional.smt2 b/p4_symbolic/symbolic/expected/string_optional.smt2 index 4a2aaf72..cc1de54a 100644 --- a/p4_symbolic/symbolic/expected/string_optional.smt2 +++ b/p4_symbolic/symbolic/expected/string_optional.smt2 @@ -6,8 +6,7 @@ (assert (let (($x38 (= standard_metadata.ingress_port (_ bv2 9)))) (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) - (let (($x84 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x84))))) + (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38)))) (assert (let ((?x65 (concat (_ bv0 7) (_ bv2 2)))) (let ((?x29 (concat (_ bv0 8) (_ bv0 1)))) @@ -59,8 +58,7 @@ (assert (let (($x38 (= standard_metadata.ingress_port (_ bv2 9)))) (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) - (let (($x84 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x84))))) + (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38)))) (assert (let ((?x65 (concat (_ bv0 7) (_ bv2 2)))) (let ((?x29 (concat (_ bv0 8) (_ bv0 1)))) @@ -101,8 +99,8 @@ (let ((?x78 (ite (and (and true (not $x68)) true) (_ bv1 9) standard_metadata.egress_spec))) (let ((?x80 (ite $x69 (_ bv0 9) ?x78))) (let (($x49 (= ?x80 (_ bv511 9)))) - (let (($x142 (and (not $x49) true))) - (and $x142 (= ?x79 0))))))))))))))))))))))) + (let (($x132 (and (not $x49) true))) + (and $x132 (= ?x79 0))))))))))))))))))))))) (check-sat) ; @@ -113,8 +111,7 @@ (assert (let (($x38 (= standard_metadata.ingress_port (_ bv2 9)))) (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) - (let (($x84 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x84))))) + (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38)))) (assert (let ((?x65 (concat (_ bv0 7) (_ bv2 2)))) (let ((?x29 (concat (_ bv0 8) (_ bv0 1)))) @@ -166,8 +163,7 @@ (assert (let (($x38 (= standard_metadata.ingress_port (_ bv2 9)))) (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) - (let (($x84 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x84))))) + (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38)))) (assert (let ((?x65 (concat (_ bv0 7) (_ bv2 2)))) (let ((?x29 (concat (_ bv0 8) (_ bv0 1)))) @@ -221,8 +217,7 @@ (assert (let (($x38 (= standard_metadata.ingress_port (_ bv2 9)))) (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) - (let (($x84 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x84))))) + (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38)))) (assert (let ((?x65 (concat (_ bv0 7) (_ bv2 2)))) (let ((?x29 (concat (_ bv0 8) (_ bv0 1)))) @@ -265,8 +260,8 @@ (let (($x69 (and true $x68))) (let ((?x80 (ite $x69 (_ bv0 9) ?x78))) (let (($x49 (= ?x80 (_ bv511 9)))) - (let (($x142 (and (not $x49) true))) - (and $x142 (= ?x62 0))))))))))))))))))))))))) + (let (($x132 (and (not $x49) true))) + (and $x132 (= ?x62 0))))))))))))))))))))))))) (check-sat) ; @@ -277,8 +272,7 @@ (assert (let (($x38 (= standard_metadata.ingress_port (_ bv2 9)))) (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) - (let (($x84 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x84))))) + (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38)))) (assert (let ((?x65 (concat (_ bv0 7) (_ bv2 2)))) (let ((?x29 (concat (_ bv0 8) (_ bv0 1)))) @@ -332,8 +326,7 @@ (assert (let (($x38 (= standard_metadata.ingress_port (_ bv2 9)))) (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) - (let (($x84 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x84))))) + (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38)))) (assert (let ((?x65 (concat (_ bv0 7) (_ bv2 2)))) (let ((?x29 (concat (_ bv0 8) (_ bv0 1)))) @@ -387,8 +380,7 @@ (assert (let (($x38 (= standard_metadata.ingress_port (_ bv2 9)))) (let (($x35 (= standard_metadata.ingress_port (_ bv1 9)))) - (let (($x84 (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) $x84))))) + (or (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x35) $x38)))) (assert (let ((?x65 (concat (_ bv0 7) (_ bv2 2)))) (let ((?x29 (concat (_ bv0 8) (_ bv0 1)))) diff --git a/p4_symbolic/symbolic/expected/string_optional.txt b/p4_symbolic/symbolic/expected/string_optional.txt index 38887d46..49dd2cf9 100644 --- a/p4_symbolic/symbolic/expected/string_optional.txt +++ b/p4_symbolic/symbolic/expected/string_optional.txt @@ -9,9 +9,9 @@ Finding packet for table MyIngress.optional_match and row 0 Finding packet for table MyIngress.optional_match and row 1 Dropped = 0 - standard_metadata.ingress_port = #b000000010 + standard_metadata.ingress_port = #b000000001 standard_metadata.egress_spec = #b000000001 - scalars.metadata.string_field = VALUE-2 + scalars.metadata.string_field = VALUE-1 Finding packet for table MyIngress.set_field_table and row -1 Cannot find solution! diff --git a/p4_symbolic/symbolic/expected/table.smt2 b/p4_symbolic/symbolic/expected/table.smt2 index ff909030..0374cc85 100644 --- a/p4_symbolic/symbolic/expected/table.smt2 +++ b/p4_symbolic/symbolic/expected/table.smt2 @@ -4,7 +4,7 @@ (declare-fun standard_metadata.egress_spec () (_ BitVec 9)) (assert (let (($x28 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x28)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x28))) (assert (let (($x28 (= standard_metadata.ingress_port (_ bv1 9)))) (let (($x29 (and true $x28))) @@ -35,7 +35,7 @@ (declare-fun standard_metadata.egress_spec () (_ BitVec 9)) (assert (let (($x28 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x28)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x28))) (assert (let (($x28 (= standard_metadata.ingress_port (_ bv1 9)))) (let (($x29 (and true $x28))) @@ -57,8 +57,8 @@ (let ((?x41 (ite (and (and true (not $x26)) $x29) (_ bv0 9) standard_metadata.egress_spec))) (let ((?x44 (ite $x30 (_ bv1 9) ?x41))) (let (($x35 (= ?x44 (_ bv511 9)))) - (let (($x112 (and (not $x35) true))) - (and $x112 (= ?x43 0)))))))))))))) + (let (($x102 (and (not $x35) true))) + (and $x102 (= ?x43 0)))))))))))))) (check-sat) ; @@ -67,7 +67,7 @@ (declare-fun standard_metadata.egress_spec () (_ BitVec 9)) (assert (let (($x28 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x28)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x28))) (assert (let (($x28 (= standard_metadata.ingress_port (_ bv1 9)))) (let (($x29 (and true $x28))) diff --git a/p4_symbolic/symbolic/expected/table_hit_2.txt b/p4_symbolic/symbolic/expected/table_hit_2.txt index 31310abc..963d47c4 100644 --- a/p4_symbolic/symbolic/expected/table_hit_2.txt +++ b/p4_symbolic/symbolic/expected/table_hit_2.txt @@ -1,40 +1,40 @@ Finding packet for table MyIngress.t1 and row -1 Dropped = 0 - standard_metadata.ingress_port = #b000000010 - standard_metadata.egress_spec = #b000000000 + standard_metadata.ingress_port = #b000000000 + standard_metadata.egress_spec = #b000000011 Finding packet for table MyIngress.t1 and row 0 Dropped = 0 - standard_metadata.ingress_port = #b000000010 - standard_metadata.egress_spec = #b000000010 + standard_metadata.ingress_port = #b000000000 + standard_metadata.egress_spec = #b000000001 Finding packet for table MyIngress.t2 and row -1 Dropped = 0 - standard_metadata.ingress_port = #b000000010 + standard_metadata.ingress_port = #b000000000 standard_metadata.egress_spec = #b000000001 Finding packet for table MyIngress.t2 and row 0 Dropped = 0 - standard_metadata.ingress_port = #b000000010 + standard_metadata.ingress_port = #b000000000 standard_metadata.egress_spec = #b000000010 Finding packet for table MyIngress.t3 and row -1 Dropped = 0 - standard_metadata.ingress_port = #b000000010 + standard_metadata.ingress_port = #b000000000 standard_metadata.egress_spec = #b000000000 Finding packet for table MyIngress.t3 and row 0 Dropped = 0 - standard_metadata.ingress_port = #b000000010 + standard_metadata.ingress_port = #b000000000 standard_metadata.egress_spec = #b000000011 Finding packet for table MyIngress.t4 and row -1 Dropped = 0 - standard_metadata.ingress_port = #b000000010 + standard_metadata.ingress_port = #b000000000 standard_metadata.egress_spec = #b000000011 Finding packet for table tbl_table_hit_2l83 and row -1 Dropped = 0 - standard_metadata.ingress_port = #b000000010 + standard_metadata.ingress_port = #b000000000 standard_metadata.egress_spec = #b000000011 diff --git a/p4_symbolic/symbolic/expected/vrf.smt2 b/p4_symbolic/symbolic/expected/vrf.smt2 index 2ecf2049..423eab16 100644 --- a/p4_symbolic/symbolic/expected/vrf.smt2 +++ b/p4_symbolic/symbolic/expected/vrf.smt2 @@ -9,7 +9,7 @@ (declare-fun scalars.local_metadata_t.vrf_is_valid () (_ BitVec 1)) (assert (let (($x86 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86))) (assert (let ((?x50 (concat (_ bv0 9) (_ bv0 1)))) (let (($x64 (and true (= (bvand ipv4.srcAddr (_ bv555813129 32)) (_ bv555810816 32))))) @@ -81,7 +81,7 @@ (declare-fun scalars.local_metadata_t.vrf_is_valid () (_ BitVec 1)) (assert (let (($x86 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86))) (assert (let ((?x50 (concat (_ bv0 9) (_ bv0 1)))) (let (($x64 (and true (= (bvand ipv4.srcAddr (_ bv555813129 32)) (_ bv555810816 32))))) @@ -139,8 +139,8 @@ (let (($x112 (and (and $x85 (not $x91)) $x96))) (let ((?x158 (ite $x109 (_ bv1 9) (ite $x112 (_ bv0 9) ?x142)))) (let (($x52 (= ?x158 (_ bv511 9)))) - (let (($x318 (and (not $x52) $x85))) - (and $x318 (= ?x157 0))))))))))))))))))))))))))))))) + (let (($x308 (and (not $x52) $x85))) + (and $x308 (= ?x157 0))))))))))))))))))))))))))))))) (check-sat) ; @@ -154,7 +154,7 @@ (declare-fun scalars.local_metadata_t.vrf_is_valid () (_ BitVec 1)) (assert (let (($x86 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86))) (assert (let ((?x50 (concat (_ bv0 9) (_ bv0 1)))) (let (($x64 (and true (= (bvand ipv4.srcAddr (_ bv555813129 32)) (_ bv555810816 32))))) @@ -226,7 +226,7 @@ (declare-fun scalars.local_metadata_t.vrf_is_valid () (_ BitVec 1)) (assert (let (($x86 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86))) (assert (let ((?x50 (concat (_ bv0 9) (_ bv0 1)))) (let (($x64 (and true (= (bvand ipv4.srcAddr (_ bv555813129 32)) (_ bv555810816 32))))) @@ -298,7 +298,7 @@ (declare-fun scalars.local_metadata_t.vrf_is_valid () (_ BitVec 1)) (assert (let (($x86 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86))) (assert (let ((?x50 (concat (_ bv0 9) (_ bv0 1)))) (let (($x64 (and true (= (bvand ipv4.srcAddr (_ bv555813129 32)) (_ bv555810816 32))))) @@ -370,7 +370,7 @@ (declare-fun scalars.local_metadata_t.vrf_is_valid () (_ BitVec 1)) (assert (let (($x86 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86))) (assert (let ((?x50 (concat (_ bv0 9) (_ bv0 1)))) (let (($x64 (and true (= (bvand ipv4.srcAddr (_ bv555813129 32)) (_ bv555810816 32))))) @@ -443,7 +443,7 @@ (declare-fun scalars.local_metadata_t.vrf_is_valid () (_ BitVec 1)) (assert (let (($x86 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86))) (assert (let ((?x50 (concat (_ bv0 9) (_ bv0 1)))) (let (($x64 (and true (= (bvand ipv4.srcAddr (_ bv555813129 32)) (_ bv555810816 32))))) @@ -516,7 +516,7 @@ (declare-fun scalars.local_metadata_t.vrf_is_valid () (_ BitVec 1)) (assert (let (($x86 (= standard_metadata.ingress_port (_ bv1 9)))) - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86))) (assert (let ((?x50 (concat (_ bv0 9) (_ bv0 1)))) (let (($x64 (and true (= (bvand ipv4.srcAddr (_ bv555813129 32)) (_ bv555810816 32))))) @@ -588,7 +588,8 @@ (declare-fun ipv4.dstAddr () (_ BitVec 32)) (declare-fun scalars.local_metadata_t.vrf_is_valid () (_ BitVec 1)) (assert - (and (and (distinct standard_metadata.ingress_port (_ bv511 9)) true) (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86)))) + (let (($x86 (= standard_metadata.ingress_port (_ bv1 9)))) + (or (or false (= standard_metadata.ingress_port (_ bv0 9))) $x86))) (assert (let ((?x50 (concat (_ bv0 9) (_ bv0 1)))) (let (($x64 (and true (= (bvand ipv4.srcAddr (_ bv555813129 32)) (_ bv555810816 32))))) diff --git a/p4_symbolic/symbolic/expected/vrf.txt b/p4_symbolic/symbolic/expected/vrf.txt index 925baf3c..00de6e1b 100644 --- a/p4_symbolic/symbolic/expected/vrf.txt +++ b/p4_symbolic/symbolic/expected/vrf.txt @@ -3,17 +3,17 @@ Cannot find solution! Finding packet for table packet_ingress.ipv4_lpm_table and row 0 Dropped = 0 - standard_metadata.ingress_port = #b000000000 + standard_metadata.ingress_port = #b000000001 standard_metadata.egress_spec = #b000000000 ipv4.srcAddr = #x29210000 - ipv4.dstAddr = #x0a0a0001 + ipv4.dstAddr = #x0a0a0002 ethernet.dstAddr = #x000000000000 scalars.local_metadata_t.vrf = VRF1 scalars.local_metadata_t.vrf_is_valid = #b1 Finding packet for table packet_ingress.ipv4_lpm_table and row 1 Dropped = 0 - standard_metadata.ingress_port = #b000000000 + standard_metadata.ingress_port = #b000000001 standard_metadata.egress_spec = #b000000001 ipv4.srcAddr = #x29210000 ipv4.dstAddr = #x0a0a0000 @@ -23,17 +23,17 @@ Finding packet for table packet_ingress.ipv4_lpm_table and row 1 Finding packet for table packet_ingress.ipv4_lpm_table and row 2 Dropped = 0 - standard_metadata.ingress_port = #b000000000 + standard_metadata.ingress_port = #b000000001 standard_metadata.egress_spec = #b000000001 ipv4.srcAddr = #x29210000 - ipv4.dstAddr = #x0a080000 + ipv4.dstAddr = #x0a0e0000 ethernet.dstAddr = #x00000000000a scalars.local_metadata_t.vrf = VRF1 scalars.local_metadata_t.vrf_is_valid = #b1 Finding packet for table packet_ingress.ipv4_lpm_table and row 3 Dropped = 0 - standard_metadata.ingress_port = #b000000000 + standard_metadata.ingress_port = #b000000001 standard_metadata.egress_spec = #b000000001 ipv4.srcAddr = #x20210100 ipv4.dstAddr = #x14140000 @@ -43,17 +43,17 @@ Finding packet for table packet_ingress.ipv4_lpm_table and row 3 Finding packet for table packet_ingress.set_vrf_table and row -1 Dropped = 0 - standard_metadata.ingress_port = #b000000000 + standard_metadata.ingress_port = #b000000001 standard_metadata.egress_spec = #b000000001 ipv4.srcAddr = #x08000909 - ipv4.dstAddr = #x00000000 + ipv4.dstAddr = #x14140000 ethernet.dstAddr = #x000000000000 scalars.local_metadata_t.vrf = VRF1 scalars.local_metadata_t.vrf_is_valid = #b0 Finding packet for table packet_ingress.set_vrf_table and row 0 Dropped = 0 - standard_metadata.ingress_port = #b000000000 + standard_metadata.ingress_port = #b000000001 standard_metadata.egress_spec = #b000000001 ipv4.srcAddr = #x20210100 ipv4.dstAddr = #x14140000 @@ -63,7 +63,7 @@ Finding packet for table packet_ingress.set_vrf_table and row 0 Finding packet for table packet_ingress.set_vrf_table and row 1 Dropped = 0 - standard_metadata.ingress_port = #b000000000 + standard_metadata.ingress_port = #b000000001 standard_metadata.egress_spec = #b000000001 ipv4.srcAddr = #x21210000 ipv4.dstAddr = #x0a140000 @@ -73,7 +73,7 @@ Finding packet for table packet_ingress.set_vrf_table and row 1 Finding packet for table tbl_vrf133 and row -1 Dropped = 0 - standard_metadata.ingress_port = #b000000000 + standard_metadata.ingress_port = #b000000001 standard_metadata.egress_spec = #b000000001 ipv4.srcAddr = #x21210000 ipv4.dstAddr = #x0a140000 diff --git a/p4_symbolic/symbolic/symbolic.cc b/p4_symbolic/symbolic/symbolic.cc index 7f9e2fb5..4eb75653 100644 --- a/p4_symbolic/symbolic/symbolic.cc +++ b/p4_symbolic/symbolic/symbolic.cc @@ -150,7 +150,9 @@ absl::StatusOr> EvaluateP4Pipeline( ingress_headers.Get("standard_metadata.ingress_port")); ASSIGN_OR_RETURN(z3::expr egress_port, egress_headers.Get("standard_metadata.egress_spec")); + // TODO: Support generating packet-out packets from the CPU port. if (physical_ports.empty()) { + z3_solver->add(ingress_port != kCpuPort); z3_solver->add(ingress_port != kDropPort); } else { z3::expr ingress_port_is_physical = Z3Context().bool_val(false); @@ -160,7 +162,9 @@ absl::StatusOr> EvaluateP4Pipeline( ingress_port_is_physical || ingress_port == port; egress_port_is_physical = egress_port_is_physical || egress_port == port; } - z3_solver->add(ingress_port != kDropPort && ingress_port_is_physical); + z3_solver->add(ingress_port_is_physical); + // TODO: Lift this constraint, it should not be necessary and + // prevents generation of packet-ins. z3_solver->add(dropped || egress_port_is_physical); } From 9569c4123e11dc9047e15f296eca89bfa5074afa Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:25:16 +0000 Subject: [PATCH 09/24] [Dvaas]: Make DVaaS API take P4RT port ID mapping between SUT and control switch for non-standard mirror testbeds. (#714) Co-authored-by: kishanps --- dvaas/BUILD.bazel | 29 +++++++ dvaas/port_id_map.cc | 85 +++++++++++++++++++ dvaas/port_id_map.h | 83 +++++++++++++++++++ dvaas/port_id_map_test.cc | 166 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 363 insertions(+) create mode 100644 dvaas/port_id_map.cc create mode 100644 dvaas/port_id_map.h create mode 100644 dvaas/port_id_map_test.cc diff --git a/dvaas/BUILD.bazel b/dvaas/BUILD.bazel index 9685d277..ed30a5fd 100644 --- a/dvaas/BUILD.bazel +++ b/dvaas/BUILD.bazel @@ -160,3 +160,32 @@ cc_library( hdrs = ["switch_api.h"], deps = ["//p4_pdpi:p4_runtime_session"], ) + +cc_library( + name = "port_id_map", + srcs = ["port_id_map.cc"], + hdrs = ["port_id_map.h"], + deps = [ + "//gutil:status", + "//lib/p4rt:p4rt_port", + "@com_github_gnmi//proto/gnmi:gnmi_cc_grpc_proto", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + ], +) + +cc_test( + name = "port_id_map_test", + srcs = ["port_id_map_test.cc"], + deps = [ + ":port_id_map", + "//gutil:status_matchers", + "//lib/p4rt:p4rt_port", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/status", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/dvaas/port_id_map.cc b/dvaas/port_id_map.cc new file mode 100644 index 00000000..54abecbf --- /dev/null +++ b/dvaas/port_id_map.cc @@ -0,0 +1,85 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dvaas/port_id_map.h" + +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/substitute.h" +#include "gutil/status.h" +#include "lib/p4rt/p4rt_port.h" + +namespace dvaas { + +using pins_test::P4rtPortId; + +absl::StatusOr +MirrorTestbedP4rtPortIdMap::CreateFromSutToControlSwitchPortMap( + absl::flat_hash_map + sut_to_control_port_map) { + absl::flat_hash_map + control_to_sut_port_map; + for (const auto& [sut_port, control_port] : sut_to_control_port_map) { + if (control_to_sut_port_map.contains(control_port)) { + return gutil::InvalidArgumentErrorBuilder() << absl::Substitute( + "Both SUT P4RT port IDs '$0' and '$1' are mapped to control " + "switch P4RT port ID '$2'", + sut_port, control_to_sut_port_map[control_port], control_port); + } + control_to_sut_port_map[control_port] = sut_port; + } + + return MirrorTestbedP4rtPortIdMap(control_to_sut_port_map); +} + +absl::StatusOr +MirrorTestbedP4rtPortIdMap::CreateFromControlSwitchToSutPortMap( + absl::flat_hash_map + control_to_sut_port_map) { + absl::flat_hash_map + sut_to_control_port_map; + for (const auto& [control_port, sut_port] : control_to_sut_port_map) { + if (sut_to_control_port_map.contains(sut_port)) { + return gutil::InvalidArgumentErrorBuilder() << absl::Substitute( + "Both control switch P4RT port IDs '$0' and '$1' are mapped " + "to SUT P4RT port ID '$2'", + control_port, sut_to_control_port_map[sut_port], sut_port); + } + sut_to_control_port_map[sut_port] = control_port; + } + + return MirrorTestbedP4rtPortIdMap(control_to_sut_port_map); +} + +absl::StatusOr +MirrorTestbedP4rtPortIdMap::GetSutPortConnectedToControlSwitchPort( + const pins_test::P4rtPortId& control_port) const { + // Handle implicit identity map. + if (!control_to_sut_port_map_.has_value()) return control_port; + + // Handle explicit map. + const auto it = control_to_sut_port_map_->find(control_port); + if (it == control_to_sut_port_map_->end()) { + return absl::NotFoundError( + absl::Substitute("Control port '$0' was not found in control switch " + "to SUT P4RT port ID map.", + control_port)); + } else { + return it->second; + } +} + +} // namespace dvaas diff --git a/dvaas/port_id_map.h b/dvaas/port_id_map.h new file mode 100644 index 00000000..0d9080ab --- /dev/null +++ b/dvaas/port_id_map.h @@ -0,0 +1,83 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PINS_DVAAS_PORT_ID_MAP_H_ +#define PINS_DVAAS_PORT_ID_MAP_H_ + +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "lib/p4rt/p4rt_port.h" +#include "proto/gnmi/gnmi.grpc.pb.h" + +namespace dvaas { + +// Keeps a map between the P4RT port IDs of connected interfaces between the +// control switch and the SUT in a mirror testbed. +class MirrorTestbedP4rtPortIdMap { + public: + // Creates a port mapping from SUT -> control switch port map. + static absl::StatusOr + CreateFromSutToControlSwitchPortMap( + absl::flat_hash_map + sut_to_control_port_map); + + // Creates a port mapping from control switch -> SUT port map. + static absl::StatusOr + CreateFromControlSwitchToSutPortMap( + absl::flat_hash_map + control_to_sut_port_map); + + // Creates an implicit map in which any port ID is mapped to itself. + static MirrorTestbedP4rtPortIdMap CreateIdentityMap() { + return MirrorTestbedP4rtPortIdMap(); + } + + // Creates a map in which the P4RT port IDs of interfaces of SUT and control + // switch with the same OpenConfig interface name are mapped to each other. + static absl::StatusOr + CreateFromMatchingInterfaceNames(gnmi::gNMI::StubInterface& sut, + gnmi::gNMI::StubInterface& control_switch) { + // TODO: Implement inferring port ID mapping from mirror + // testbed interface names. + return absl::UnimplementedError( + "Inferring port ID mapping from mirror testbed interface names is not " + "supported yet."); + } + + // Returns the P4RT port ID of the SUT interface connected to the interface on + // the control switch with the given P4RT port ID according to the port + // mapping. + absl::StatusOr GetSutPortConnectedToControlSwitchPort( + const pins_test::P4rtPortId& control_port) const; + + private: + MirrorTestbedP4rtPortIdMap( + absl::flat_hash_map + control_to_sut_port_map) + : control_to_sut_port_map_(std::move(control_to_sut_port_map)) {} + MirrorTestbedP4rtPortIdMap() = default; + + // If nullopt, an implicit identity map is assumed. + std::optional< + absl::flat_hash_map> + control_to_sut_port_map_; +}; + +} // namespace dvaas + +#endif // PINS_DVAAS_PORT_ID_MAP_H_ diff --git a/dvaas/port_id_map_test.cc b/dvaas/port_id_map_test.cc new file mode 100644 index 00000000..8d023d53 --- /dev/null +++ b/dvaas/port_id_map_test.cc @@ -0,0 +1,166 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dvaas/port_id_map.h" + +#include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "gutil/status_matchers.h" +#include "lib/p4rt/p4rt_port.h" + +namespace dvaas { +namespace { + +using gutil::IsOkAndHolds; +using gutil::StatusIs; +using pins_test::P4rtPortId; +using testing::Eq; + +TEST(MirrorTestbedP4rtPortIdMap, + ReturnsErrorWhenPortNotFoundGivenControlToSutMapping) { + ASSERT_OK_AND_ASSIGN(const auto port_1, + P4rtPortId::MakeFromP4rtEncoding("1")); + ASSERT_OK_AND_ASSIGN(const auto port_2, + P4rtPortId::MakeFromP4rtEncoding("2")); + ASSERT_OK_AND_ASSIGN(const auto port_3, + P4rtPortId::MakeFromP4rtEncoding("3")); + + ASSERT_OK_AND_ASSIGN( + const auto port_id_map, + MirrorTestbedP4rtPortIdMap::CreateFromControlSwitchToSutPortMap( + {{port_1, port_2}})); + + ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_2), + StatusIs(absl::StatusCode::kNotFound)); + ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_3), + StatusIs(absl::StatusCode::kNotFound)); +} + +TEST(MirrorTestbedP4rtPortIdMap, + ReturnsErrorWhenGivenControlToSutMappingIsNotBijective) { + ASSERT_OK_AND_ASSIGN(const auto port_1, + P4rtPortId::MakeFromP4rtEncoding("1")); + ASSERT_OK_AND_ASSIGN(const auto port_2, + P4rtPortId::MakeFromP4rtEncoding("2")); + ASSERT_OK_AND_ASSIGN(const auto port_3, + P4rtPortId::MakeFromP4rtEncoding("3")); + + ASSERT_THAT(MirrorTestbedP4rtPortIdMap::CreateFromControlSwitchToSutPortMap({ + {port_1, port_3}, + {port_2, port_3}, + }), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(MirrorTestbedP4rtPortIdMap, + ReturnsErrorWhenPortNotFoundGivenSutToControlMapping) { + ASSERT_OK_AND_ASSIGN(const auto port_1, + P4rtPortId::MakeFromP4rtEncoding("1")); + ASSERT_OK_AND_ASSIGN(const auto port_2, + P4rtPortId::MakeFromP4rtEncoding("2")); + ASSERT_OK_AND_ASSIGN(const auto port_3, + P4rtPortId::MakeFromP4rtEncoding("3")); + + ASSERT_OK_AND_ASSIGN( + const auto port_id_map, + MirrorTestbedP4rtPortIdMap::CreateFromSutToControlSwitchPortMap( + {{port_1, port_2}})); + + ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_1), + StatusIs(absl::StatusCode::kNotFound)); + ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_3), + StatusIs(absl::StatusCode::kNotFound)); +} + +TEST(MirrorTestbedP4rtPortIdMap, + ReturnsErrorWhenGivenSutToControlMappingIsNotBijective) { + ASSERT_OK_AND_ASSIGN(const auto port_1, + P4rtPortId::MakeFromP4rtEncoding("1")); + ASSERT_OK_AND_ASSIGN(const auto port_2, + P4rtPortId::MakeFromP4rtEncoding("2")); + ASSERT_OK_AND_ASSIGN(const auto port_3, + P4rtPortId::MakeFromP4rtEncoding("3")); + + ASSERT_THAT(MirrorTestbedP4rtPortIdMap::CreateFromSutToControlSwitchPortMap({ + {port_1, port_3}, + {port_2, port_3}, + }), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(MirrorTestbedP4rtPortIdMap, + RetrunsCorrectSutPortGivenControlPortGivenControlToSutMapping) { + ASSERT_OK_AND_ASSIGN(const auto port_1, + P4rtPortId::MakeFromP4rtEncoding("1")); + ASSERT_OK_AND_ASSIGN(const auto port_2, + P4rtPortId::MakeFromP4rtEncoding("2")); + ASSERT_OK_AND_ASSIGN(const auto port_3, + P4rtPortId::MakeFromP4rtEncoding("3")); + ASSERT_OK_AND_ASSIGN(const auto port_4, + P4rtPortId::MakeFromP4rtEncoding("4")); + + ASSERT_OK_AND_ASSIGN( + const auto port_id_map, + MirrorTestbedP4rtPortIdMap::CreateFromControlSwitchToSutPortMap({ + {port_1, port_2}, + {port_3, port_4}, + })); + + ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_1), + IsOkAndHolds(Eq(port_2))); + ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_3), + IsOkAndHolds(Eq(port_4))); +} + +TEST(MirrorTestbedP4rtPortIdMap, + RetrunsCorrectSutPortGivenControlPortGivenSutToControlMapping) { + ASSERT_OK_AND_ASSIGN(const auto port_1, + P4rtPortId::MakeFromP4rtEncoding("1")); + ASSERT_OK_AND_ASSIGN(const auto port_2, + P4rtPortId::MakeFromP4rtEncoding("2")); + ASSERT_OK_AND_ASSIGN(const auto port_3, + P4rtPortId::MakeFromP4rtEncoding("3")); + ASSERT_OK_AND_ASSIGN(const auto port_4, + P4rtPortId::MakeFromP4rtEncoding("4")); + + ASSERT_OK_AND_ASSIGN( + const auto port_id_map, + MirrorTestbedP4rtPortIdMap::CreateFromSutToControlSwitchPortMap({ + {port_1, port_2}, + {port_3, port_4}, + })); + + ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_2), + IsOkAndHolds(Eq(port_1))); + ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_4), + IsOkAndHolds(Eq(port_3))); +} + +TEST(MirrorTestbedP4rtPortIdMap, RetrunsCorrectSutPortWithImplicitIdentityMap) { + const auto port_id_map = MirrorTestbedP4rtPortIdMap::CreateIdentityMap(); + ASSERT_OK_AND_ASSIGN(const auto port_1, + P4rtPortId::MakeFromP4rtEncoding("1")); + ASSERT_OK_AND_ASSIGN(const auto port_2, + P4rtPortId::MakeFromP4rtEncoding("2")); + + ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_1), + IsOkAndHolds(Eq(port_1))); + ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_2), + IsOkAndHolds(Eq(port_2))); +} + +} // namespace +} // namespace dvaas From 790fd34785c92d68ed2f41a4addcad0e4c0c12d6 Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:55:50 +0530 Subject: [PATCH 10/24] Queue rate limit test (#715) Co-authored-by: kishanps --- tests/qos/BUILD.bazel | 12 ++- tests/qos/cpu_qos_test.cc | 183 ++++++++++++++++++++++++++++++++++++-- tests/qos/cpu_qos_test.h | 1 + 3 files changed, 186 insertions(+), 10 deletions(-) diff --git a/tests/qos/BUILD.bazel b/tests/qos/BUILD.bazel index 52f766b2..15264e98 100644 --- a/tests/qos/BUILD.bazel +++ b/tests/qos/BUILD.bazel @@ -23,17 +23,24 @@ package( cc_library( name = "cpu_qos_test", testonly = True, - srcs = ["cpu_qos_test.cc"], - hdrs = ["cpu_qos_test.h"], + srcs = [ + "cpu_qos_test.cc", + ], + hdrs = [ + "cpu_qos_test.h", + ], deps = [ "//gutil:status", "//gutil:status_matchers", "//gutil:testing", + "//lib:ixia_helper", "//lib/gnmi:gnmi_helper", "//lib/p4rt:packet_listener", "//lib/validator:validator_lib", "//p4_pdpi:ir", "//p4_pdpi:pd", + "//p4_pdpi/netaddr:ipv4_address", + "//p4_pdpi/netaddr:mac_address", "//p4_pdpi/packetlib", "//p4_pdpi:p4_runtime_session", "//p4_pdpi/packetlib:packetlib_cc_proto", @@ -49,6 +56,7 @@ cc_library( "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/status", "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", "@com_google_googletest//:gtest", ], alwayslink = True, diff --git a/tests/qos/cpu_qos_test.cc b/tests/qos/cpu_qos_test.cc index ef42821a..32ea0773 100644 --- a/tests/qos/cpu_qos_test.cc +++ b/tests/qos/cpu_qos_test.cc @@ -23,9 +23,11 @@ #include "absl/status/status.h" #include "absl/strings/escaping.h" #include "absl/strings/match.h" +#include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/strings/substitute.h" +#include "absl/time/time.h" #include "glog/logging.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -34,9 +36,12 @@ #include "gutil/testing.h" #include "include/nlohmann/json.hpp" #include "lib/gnmi/gnmi_helper.h" +#include "lib/ixia_helper.h" #include "lib/p4rt/packet_listener.h" #include "lib/validator/validator_lib.h" #include "p4_pdpi/ir.h" +#include "p4_pdpi/netaddr/ipv4_address.h" +#include "p4_pdpi/netaddr/mac_address.h" #include "p4_pdpi/p4_runtime_session.h" #include "p4_pdpi/packetlib/packetlib.h" #include "p4_pdpi/packetlib/packetlib.pb.h" @@ -48,9 +53,137 @@ #include "thinkit/switch.h" namespace pins_test { +namespace { + +struct QueueInfo { + std::string gnmi_queue_name; // Openconfig queue name. + std::string p4_queue_name; // P4 queue name. + int rate_packets_per_second = 0; // Rate of packets in packets per second. +}; + +absl::StatusOr> +GetDefaultQueueInfo() { + return absl::flat_hash_map{ + {"BE1", QueueInfo{"BE1", "0x2", 120}}, + {"AF1", QueueInfo{"AF1", "0x3", 120}}, + {"AF2", QueueInfo{"AF2", "0x4", 800}}, + {"AF3", QueueInfo{"AF3", "0x5", 120}}, + {"AF4", QueueInfo{"AF4", "0x6", 4000}}, + {"LLQ1", QueueInfo{"LLQ1", "0x0", 800}}, + {"LLQ2", QueueInfo{"LLQ2", "0x1", 800}}, + {"NC1", QueueInfo{"NC1", "0x7", 16000}}, + }; +} + +// Set up the switch to punt packets to CPU. +absl::Status SetUpPuntToCPU(const netaddr::MacAddress &dmac, + const netaddr::Ipv4Address &src_ip, + const netaddr::Ipv4Address &dst_ip, + absl::string_view p4_queue, + const p4::config::v1::P4Info &p4info, + pdpi::P4RuntimeSession &p4_session) { + ASSIGN_OR_RETURN(auto ir_p4info, pdpi::CreateIrP4Info(p4info)); + RETURN_IF_ERROR(pdpi::SetMetadataAndSetForwardingPipelineConfig( + &p4_session, + p4::v1::SetForwardingPipelineConfigRequest::RECONCILE_AND_COMMIT, p4info)) + << "SetForwardingPipelineConfig: Failed to push P4Info: "; + + RETURN_IF_ERROR(pdpi::ClearTableEntries(&p4_session)); + auto acl_entry = gutil::ParseProtoOrDie(absl::Substitute( + R"pb( + acl_ingress_table_entry { + match { + dst_mac { value: "$0" mask: "ff:ff:ff:ff:ff:ff" } + is_ipv4 { value: "0x1" } + src_ip { value: "$1" mask: "255.255.255.255" } + dst_ip { value: "$2" mask: "255.255.255.255" } + } + action { trap { qos_queue: "$3" } } + priority: 1 + } + )pb", + dmac.ToString(), src_ip.ToString(), dst_ip.ToString(), p4_queue)); + std::vector pi_entries; + ASSIGN_OR_RETURN( + pi_entries.emplace_back(), pdpi::PartialPdTableEntryToPiTableEntry(ir_p4info, acl_entry), + _.SetPrepend() << "Failed in PD table conversion to PI, entry: " + << acl_entry.DebugString() << " error: "); + + LOG(INFO) << "InstallPiTableEntries"; + return pdpi::InstallPiTableEntries(&p4_session, ir_p4info, pi_entries); +} + +// These are the counters we track in these tests. +struct QueueCounters { + int64_t num_packets_transmitted = 0; + int64_t num_packet_dropped = 0; +}; + +// TODO: Move this to a helper library. +absl::StatusOr GetGnmiQueueStat( + absl::string_view port, absl::string_view queue, + gnmi::gNMI::StubInterface &gnmi_stub) { + QueueCounters counters; + const std::string openconfig_transmit_count_state_path = absl::Substitute( + "qos/interfaces/interface[interface-id=$0]" + "/output/queues/queue[name=$1]/state/transmit-pkts", + port, queue); + + ASSIGN_OR_RETURN( + std::string transmit_counter_response, + GetGnmiStatePathInfo(&gnmi_stub, openconfig_transmit_count_state_path, + "openconfig-qos:transmit-pkts")); + + if (!absl::SimpleAtoi(StripQuotes(transmit_counter_response), + &counters.num_packets_transmitted)) { + return absl::InternalError(absl::StrCat("Unable to parse counter from ", + transmit_counter_response)); + } + + const std::string openconfig_drop_count_state_path = absl::Substitute( + "qos/interfaces/interface[interface-id=$0]" + "/output/queues/queue[name=$1]/state/dropped-pkts", + port, queue); + + ASSIGN_OR_RETURN( + std::string drop_counter_response, + GetGnmiStatePathInfo(&gnmi_stub, openconfig_drop_count_state_path, + "openconfig-qos:dropped-pkts")); + + if (!absl::SimpleAtoi(StripQuotes(drop_counter_response), + &counters.num_packet_dropped)) { + return absl::InternalError( + absl::StrCat("Unable to parse counter from ", drop_counter_response)); + } + + return counters; +} + +absl::Status SetPortSpeed(const std::string &port_speed, + const std::string &iface, + gnmi::gNMI::StubInterface &gnmi_stub) { + std::string ops_config_path = absl::StrCat( + "interfaces/interface[name=", iface, "]/ethernet/config/port-speed"); + std::string ops_val = + absl::StrCat("{\"openconfig-if-ethernet:port-speed\":", port_speed, "}"); + RETURN_IF_ERROR(pins_test::SetGnmiConfigPath(&gnmi_stub, ops_config_path, + GnmiSetType::kUpdate, ops_val)); + return absl::OkStatus(); +} + +absl::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\""; +} TEST_P(CpuQosIxiaTestFixture, TestCPUQueueRateLimit) { - // Pick a testbed with an Ixia Traffic Generator. A SUT is assumed. + // Pick a testbed with an Ixia Traffic Generator. auto requirements = gutil::ParseProtoOrDie( R"pb(interface_requirements { @@ -62,10 +195,13 @@ TEST_P(CpuQosIxiaTestFixture, TestCPUQueueRateLimit) { std::unique_ptr generic_testbed, GetParam().testbed_interface->GetTestbedWithRequirements(requirements)); + ASSERT_OK(generic_testbed->Environment().StoreTestArtifact( + "gnmi_config.txt", GetParam().gnmi_config)); + thinkit::Switch& sut = generic_testbed->Sut(); // Connect to TestTracker for test status. - if (auto& id = GetParam().test_case_id; id.has_value()) { + if (auto &id = GetParam().test_case_id; id.has_value()) { generic_testbed->Environment().SetTestCaseID(*id); } @@ -75,7 +211,12 @@ TEST_P(CpuQosIxiaTestFixture, TestCPUQueueRateLimit) { // Hook up to GNMI. ASSERT_OK_AND_ASSIGN(auto gnmi_stub, sut.CreateGnmiStub()); - // Set up P4Runtime session.. + // Get Queues + // TODO: Extract Queue info from config instead of hardcoded + // default. + ASSERT_OK_AND_ASSIGN(auto queues, GetDefaultQueueInfo()); + + // Set up P4Runtime session. // TODO: Use `CreateWithP4InfoAndClearTables` cl/397193959 when // its available. ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_p4_session, @@ -83,13 +224,39 @@ TEST_P(CpuQosIxiaTestFixture, TestCPUQueueRateLimit) { auto clear_table_entries = absl::Cleanup( [&]() { ASSERT_OK(pdpi::ClearTableEntries(sut_p4_session.get())); }); - // TODO: Set Up single P4RT punt flow. + // Flow details. + const auto dest_mac = netaddr::MacAddress(02, 02, 02, 02, 02, 02); + const auto source_mac = netaddr::MacAddress(00, 01, 02, 03, 04, 05); + const auto source_ip = netaddr::Ipv4Address(192, 168, 10, 1); + const auto dest_ip = netaddr::Ipv4Address(172, 0, 0, 1); + + // BE1 is guaranteed to exist in the map which is currently hardocoded + // and we will test for BE1 queue. + // TODO: When we replace hardcoding with extraction of members + // from the config, we need to add iteration logic to go over the configured + // queues. + QueueInfo queue_under_test = queues["BE1"]; - // TODO: - // Setup Ixia traffic. - // Send Ixia traffic. - // Stop Ixia traffic. + ASSERT_OK(SetUpPuntToCPU(dest_mac, source_ip, dest_ip, + queue_under_test.p4_queue_name, GetParam().p4info, + *sut_p4_session)); + static constexpr absl::Duration kPollInterval = absl::Seconds(5); + static constexpr absl::Duration kTotalTime = absl::Seconds(30); + static const int kIterations = kTotalTime / kPollInterval; + + QueueCounters final_counters; + QueueCounters delta_counters; + // Check for counters every 5 seconds upto 30 seconds till they match. + for (int gnmi_counters_check = 0; gnmi_counters_check < kIterations; + gnmi_counters_check++) { + absl::SleepFor(kPollInterval); + + ASSERT_OK_AND_ASSIGN( + final_counters, + GetGnmiQueueStat("CPU", queue_under_test.gnmi_queue_name, *gnmi_stub)); + } } +} // namespace } // namespace pins_test diff --git a/tests/qos/cpu_qos_test.h b/tests/qos/cpu_qos_test.h index 74650f9e..531ecdc2 100644 --- a/tests/qos/cpu_qos_test.h +++ b/tests/qos/cpu_qos_test.h @@ -26,6 +26,7 @@ struct QosTestArguments { thinkit::GenericTestbedInterface* testbed_interface; std::vector table_entries; std::string gnmi_config; + p4::config::v1::P4Info p4info; absl::optional test_case_id; }; From 4d634b5b26416e35904950b62322dfc436a1e3c7 Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:59:00 +0530 Subject: [PATCH 11/24] [Thinkit] Add first version of test verifying DSCP-to-queue mapping for packets to CPU. (#716) Co-authored-by: kishanps Co-authored-by: smolkaj --- tests/qos/BUILD.bazel | 12 ++ tests/qos/cpu_qos_test.cc | 330 +++++++++++++++++++++++++++++++++++++- tests/qos/cpu_qos_test.h | 66 +++++++- 3 files changed, 399 insertions(+), 9 deletions(-) diff --git a/tests/qos/BUILD.bazel b/tests/qos/BUILD.bazel index 15264e98..bddf3903 100644 --- a/tests/qos/BUILD.bazel +++ b/tests/qos/BUILD.bazel @@ -30,6 +30,7 @@ cc_library( "cpu_qos_test.h", ], deps = [ + "//gutil:collections", "//gutil:status", "//gutil:status_matchers", "//gutil:testing", @@ -40,23 +41,34 @@ cc_library( "//p4_pdpi:ir", "//p4_pdpi:pd", "//p4_pdpi/netaddr:ipv4_address", + "//p4_pdpi/netaddr:ipv6_address", "//p4_pdpi/netaddr:mac_address", "//p4_pdpi/packetlib", "//p4_pdpi:p4_runtime_session", "//p4_pdpi/packetlib:packetlib_cc_proto", + "//p4_pdpi/string_encodings:decimal_string", "//sai_p4/instantiations/google:sai_p4info_cc", "//sai_p4/instantiations/google:sai_pd_cc_proto", + "//tests/forwarding:util", + "//thinkit:control_device", "//thinkit:generic_testbed", "//thinkit:generic_testbed_fixture", + "//thinkit:mirror_testbed", + "//thinkit:mirror_testbed_fixture", "//thinkit:switch", "//thinkit/proto:generic_testbed_cc_proto", "@com_github_google_glog//:glog", "@com_github_nlohmann_json//:nlohmann_json", + "@com_github_p4lang_p4runtime//:p4info_cc_proto", "@com_google_absl//absl/cleanup", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", "@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", + "@com_google_absl//absl/types:optional", "@com_google_googletest//:gtest", ], alwayslink = True, diff --git a/tests/qos/cpu_qos_test.cc b/tests/qos/cpu_qos_test.cc index 32ea0773..3be84b33 100644 --- a/tests/qos/cpu_qos_test.cc +++ b/tests/qos/cpu_qos_test.cc @@ -15,22 +15,30 @@ #include "tests/qos/cpu_qos_test.h" #include +#include +#include #include #include #include "absl/cleanup/cleanup.h" #include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" #include "absl/status/status.h" +#include "absl/status/statusor.h" #include "absl/strings/escaping.h" #include "absl/strings/match.h" #include "absl/strings/numbers.h" #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/clock.h" #include "absl/time/time.h" +#include "absl/types/optional.h" #include "glog/logging.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "gutil/collections.h" #include "gutil/status.h" #include "gutil/status_matchers.h" #include "gutil/testing.h" @@ -39,28 +47,43 @@ #include "lib/ixia_helper.h" #include "lib/p4rt/packet_listener.h" #include "lib/validator/validator_lib.h" +#include "p4/config/v1/p4info.pb.h" #include "p4_pdpi/ir.h" #include "p4_pdpi/netaddr/ipv4_address.h" +#include "p4_pdpi/netaddr/ipv6_address.h" #include "p4_pdpi/netaddr/mac_address.h" #include "p4_pdpi/p4_runtime_session.h" #include "p4_pdpi/packetlib/packetlib.h" #include "p4_pdpi/packetlib/packetlib.pb.h" #include "p4_pdpi/pd.h" +#include "p4_pdpi/string_encodings/decimal_string.h" #include "sai_p4/instantiations/google/sai_p4info.h" #include "sai_p4/instantiations/google/sai_pd.pb.h" +#include "tests/forwarding/util.h" +#include "thinkit/control_device.h" #include "thinkit/generic_testbed.h" +#include "thinkit/mirror_testbed.h" #include "thinkit/proto/generic_testbed.pb.h" #include "thinkit/switch.h" namespace pins_test { namespace { +using ::p4::config::v1::P4Info; + +// The maximum time the switch is allowed to take before queue counters read via +// gNMI have to be incremented after a packet hits a queue. +// Empirically, for PINS, queue counters currently seem to get updated every +// 10 seconds. +constexpr absl::Duration kMaxQueueCounterUpdateTime = absl::Seconds(15); + struct QueueInfo { std::string gnmi_queue_name; // Openconfig queue name. std::string p4_queue_name; // P4 queue name. int rate_packets_per_second = 0; // Rate of packets in packets per second. }; +// TODO: Parse QueueInfo from gNMI config. absl::StatusOr> GetDefaultQueueInfo() { return absl::flat_hash_map{ @@ -119,8 +142,17 @@ struct QueueCounters { int64_t num_packet_dropped = 0; }; +std::ostream &operator<<(std::ostream &os, const QueueCounters &counters) { + return os << absl::StreamFormat( + "QueueCounters{" + ".num_packets_transmitted = %d, " + ".num_packets_dropped = %d" + "}", + counters.num_packets_transmitted, counters.num_packet_dropped); +} + // TODO: Move this to a helper library. -absl::StatusOr GetGnmiQueueStat( +absl::StatusOr GetGnmiQueueCounters( absl::string_view port, absl::string_view queue, gnmi::gNMI::StubInterface &gnmi_stub) { QueueCounters counters; @@ -159,6 +191,12 @@ absl::StatusOr GetGnmiQueueStat( return counters; } +// Returns the total number of packets enqueued for the queue with the given +// `QueueCounters`. +int64_t CumulativeNumPacketsEnqueued(const QueueCounters &counters) { + return counters.num_packet_dropped + counters.num_packets_transmitted; +} + absl::Status SetPortSpeed(const std::string &port_speed, const std::string &iface, gnmi::gNMI::StubInterface &gnmi_stub) { @@ -182,7 +220,292 @@ absl::StatusOr CheckLinkUp(const std::string &iface, return ops_response == "\"UP\""; } -TEST_P(CpuQosIxiaTestFixture, TestCPUQueueRateLimit) { +absl::StatusOr MakeIpv4PacketWithDscp( + const netaddr::MacAddress &dst_mac, const netaddr::Ipv4Address &dst_ip, + int dscp) { + auto packet = gutil::ParseProtoOrDie(absl::Substitute( + R"pb( + headers { + ethernet_header { + ethernet_destination: "$0" + ethernet_source: "00:01:02:03:04:05" + ethertype: "0x0800" + } + } + headers { + ipv4_header { + dscp: "$1" + ecn: "0x0" + identification: "0xa3cd" + flags: "0x0" + fragment_offset: "0x0000" + ttl: "0x10" + protocol: "0x05" + ipv4_source: "10.0.0.2" + ipv4_destination: "$2" + } + } + payload: "Test packet to validate DSCP-to-queue mapping." + )pb", + dst_mac.ToString(), packetlib::IpDscp(dscp), dst_ip.ToString())); + RETURN_IF_ERROR(packetlib::PadPacketToMinimumSize(packet).status()); + RETURN_IF_ERROR(packetlib::UpdateAllComputedFields(packet).status()); + return packet; +} + +absl::StatusOr MakeIpv6PacketWithDscp( + const netaddr::MacAddress &dst_mac, const netaddr::Ipv6Address &dst_ip, + int dscp) { + auto packet = gutil::ParseProtoOrDie(absl::Substitute( + R"pb( + headers { + ethernet_header { + ethernet_destination: "$0" + ethernet_source: "00:01:02:03:04:05" + ethertype: "0x86dd" + } + } + headers { + ipv6_header { + dscp: "$1" + ecn: "0x0" + flow_label: "0x00000" + next_header: "0xfd" # Reserved for experimentation. + hop_limit: "0x40" + ipv6_source: "2001:db8:0:12::1" + ipv6_destination: "$2" + } + } + payload: "Test packet to validate DSCP-to-queue mapping." + )pb", + dst_mac.ToString(), packetlib::IpDscp(dscp), dst_ip.ToString())); + RETURN_IF_ERROR(packetlib::PadPacketToMinimumSize(packet).status()); + RETURN_IF_ERROR(packetlib::UpdateAllComputedFields(packet).status()); + return packet; +} + +absl::StatusOr> +ParseIpv4DscpToQueueMapping(absl::string_view gnmi_config) { + // TODO: Actually parse config -- hard-coded for now. + absl::flat_hash_map queue_by_dscp; + for (int dscp = 0; dscp < 64; ++dscp) queue_by_dscp[dscp] = "BE1"; + for (int dscp = 8; dscp <= 11; ++dscp) queue_by_dscp[dscp] = "AF1"; + queue_by_dscp[13] = "LLQ1"; + for (int dscp = 16; dscp <= 19; ++dscp) queue_by_dscp[dscp] = "AF2"; + queue_by_dscp[21] = "LLQ2"; + for (int dscp = 24; dscp <= 27; ++dscp) queue_by_dscp[dscp] = "AF3"; + for (int dscp = 32; dscp <= 35; ++dscp) queue_by_dscp[dscp] = "AF4"; + for (int dscp = 48; dscp <= 59; ++dscp) queue_by_dscp[dscp] = "NC1"; + return queue_by_dscp; +} + +absl::StatusOr> +ParseIpv6DscpToQueueMapping(absl::string_view gnmi_config) { + // TODO: Actually parse config -- hard-coded for now. + return ParseIpv4DscpToQueueMapping(gnmi_config); +} + +absl::StatusOr> ParseLoopbackIpv4( + absl::string_view gnmi_config) { + // TODO: Actually parse IP -- hard-coded for now. + return absl::nullopt; +} + +absl::StatusOr> ParseLoopbackIpv6( + absl::string_view gnmi_config) { + // TODO: Actually parse IP -- hard-coded for now. + ASSIGN_OR_RETURN(auto ip, + netaddr::Ipv6Address::OfString("2607:f8b0:8096:3125::")); + return ip; +} + +// Represents a link connecting the switch under test (SUT) to a control device. +struct SutToControlLink { + std::string sut_port_gnmi_name; + std::string sut_port_p4rt_name; + std::string control_device_port_gnmi_name; + std::string control_device_port_p4rt_name; +}; + +std::ostream &operator<<(std::ostream &os, const SutToControlLink &link) { + return os << absl::StreamFormat( + "SutToControlLink{" + ".sut_port_name = %s, .control_device_port_name = %s" + "}", + link.sut_port_gnmi_name, link.control_device_port_gnmi_name); +} +// Nondeterministically picks and returns a `SutToControlLink` that's up, or +// returns an error if no such port is found. +absl::StatusOr PickSutToControlDeviceLinkThatsUp( + thinkit::MirrorTestbed &testbed) { + // TODO: Pick dynamically instead of hard-coding. + return SutToControlLink{ + .sut_port_gnmi_name = "Ethernet28", + .sut_port_p4rt_name = "516", + .control_device_port_gnmi_name = "Ethernet28", + .control_device_port_p4rt_name = "516", + }; +} + +absl::StatusOr MakeRouterInterface( + absl::string_view router_interface_id, absl::string_view p4rt_port_name, + const netaddr::MacAddress &mac, const pdpi::IrP4Info &ir_p4info) { + ASSIGN_OR_RETURN( + auto pd_entry, + 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: "$2" } } + } + )pb", + router_interface_id, p4rt_port_name, mac.ToString()))); + return pdpi::PartialPdTableEntryToPiTableEntry(ir_p4info, pd_entry); +} + +// Purpose: Verify DSCP-to-queue mapping for traffic to switch loopback IP. +TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { + LOG(INFO) << "-- START OF TEST ---------------------------------------------"; + Testbed().Environment().SetTestCaseID("61bb0173-0c49-4067-b15a-5c3dd7823126"); + + // Setup: the testbed consists of a SUT connected to a control device + // that allows us to send and receive packets to/from the SUT. + thinkit::Switch &sut = Testbed().Sut(); + thinkit::Switch &control_device = Testbed().ControlSwitch(); + constexpr auto kSutMacAddress = + netaddr::MacAddress(0x00, 0x07, 0xE9, 0x42, 0xAC, 0x28); // Arbitrary. + const P4Info &p4info = GetParam().p4info; + ASSERT_OK_AND_ASSIGN(const pdpi::IrP4Info ir_p4info, + pdpi::CreateIrP4Info(p4info)); + + // Set up gNMI. + EXPECT_OK(Testbed().Environment().StoreTestArtifact("gnmi_config.json", + GetParam().gnmi_config)); + ASSERT_OK(pins_test::PushGnmiConfig(sut, GetParam().gnmi_config)); + ASSERT_OK(pins_test::PushGnmiConfig(control_device, GetParam().gnmi_config)); + ASSERT_OK_AND_ASSIGN(auto gnmi_stub, sut.CreateGnmiStub()); + // TODO: Poll for config to be applied, links to come up instead. + LOG(INFO) << "Sleeping 10 seconds to wait for config to be applied/links to " + "come up."; + absl::SleepFor(absl::Seconds(10)); + + // Pick a link to be used for packet injection. + ASSERT_OK_AND_ASSIGN(SutToControlLink link_used_for_test_packets, + PickSutToControlDeviceLinkThatsUp(Testbed())); + LOG(INFO) << "Link used to inject test packets: " + << link_used_for_test_packets; + + // Set up P4Runtime. + EXPECT_OK( + Testbed().Environment().StoreTestArtifact("p4info.textproto", p4info)); + ASSERT_OK_AND_ASSIGN( + std::unique_ptr p4rt_session, + pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables(sut, p4info)); + ASSERT_OK_AND_ASSIGN( + std::unique_ptr control_p4rt_session, + pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables(control_device, + p4info)); + // TODO: Unless a RIF exists at the test packet ingress port, + // packets will be dropped. Remove this once these RIFs are set up via gNMI. + if (Testbed().Environment().MaskKnownFailures()) { + ASSERT_OK_AND_ASSIGN( + p4::v1::TableEntry router_interface_entry, + MakeRouterInterface( + /*router_interface_id=*/"ingress-rif-to-workaround-b/190736007", + /*p4rt_port_name=*/link_used_for_test_packets.sut_port_p4rt_name, + /*mac=*/kSutMacAddress, + /*ir_p4info=*/ir_p4info)); + ASSERT_OK( + pdpi::InstallPiTableEntry(p4rt_session.get(), router_interface_entry)); + } + + // Extract DSCP-to-queue mapping from gNMI config. + using QueueNameByDscp = absl::flat_hash_map; + ASSERT_OK_AND_ASSIGN(std::optional queue_name_by_ipv4_dscp, + ParseIpv4DscpToQueueMapping(GetParam().gnmi_config)); + ASSERT_OK_AND_ASSIGN(std::optional queue_name_by_ipv6_dscp, + ParseIpv6DscpToQueueMapping(GetParam().gnmi_config)); + const std::string kDefaultQueueName = "BE1"; + + // Extract loopback IPs from gNMI config. + ASSERT_OK_AND_ASSIGN(std::optional loopback_ipv4, + ParseLoopbackIpv4(GetParam().gnmi_config)); + ASSERT_OK_AND_ASSIGN(std::optional loopback_ipv6, + ParseLoopbackIpv6(GetParam().gnmi_config)); + ASSERT_TRUE(loopback_ipv4.has_value() || loopback_ipv6.has_value()) + << "Expected a loopback IP to be configured via gNMI; nothing to test."; + + // Verify DSCP-to-queue mapping for all DSCPs using IP test packets destined + // to the loopback IP(s). + constexpr int kMaxDscp = 63; + for (int dscp = 0; dscp <= kMaxDscp; ++dscp) { + for (bool ipv4 : {true, false}) { + if (ipv4 && !loopback_ipv4.has_value()) continue; + if (!ipv4 && !loopback_ipv6.has_value()) continue; + LOG(INFO) << "Testing DSCP-to-queue mapping for " + << (ipv4 ? "IPv4" : "IPv6") << " packet with DSCP " << dscp; + + // Identify target queue for current DSCP. + // The algorithm for picking a queue is somewhat adhoc and PINS specific. + std::string target_queue = kDefaultQueueName; + if (queue_name_by_ipv4_dscp.has_value()) { + target_queue = gutil::FindOrDefault(*queue_name_by_ipv4_dscp, dscp, + kDefaultQueueName); + } else if (queue_name_by_ipv6_dscp.has_value()) { + target_queue = gutil::FindOrDefault(*queue_name_by_ipv6_dscp, dscp, + kDefaultQueueName); + } + LOG(INFO) << "Target queue: " << target_queue; + + // Read counters of the target queue. + ASSERT_OK_AND_ASSIGN( + const QueueCounters queue_counters_before_test_packet, + GetGnmiQueueCounters(/*port=*/"CPU", /*queue=*/target_queue, + *gnmi_stub)); + + // Inject test packet. + ASSERT_OK_AND_ASSIGN( + packetlib::Packet packet, + ipv4 + ? MakeIpv4PacketWithDscp(/*dst_mac=*/kSutMacAddress, + /*dst_ip=*/*loopback_ipv4, /*dscp=*/dscp) + : MakeIpv6PacketWithDscp(/*dst_mac=*/kSutMacAddress, + /*dst_ip=*/*loopback_ipv6, + /*dscp=*/dscp)); + ASSERT_OK_AND_ASSIGN(std::string raw_packet, + packetlib::SerializePacket(packet)); + ASSERT_OK(pins::InjectEgressPacket( + /*port=*/link_used_for_test_packets.control_device_port_p4rt_name, + /*packet=*/raw_packet, ir_p4info, control_p4rt_session.get())); + + // Read counter of the target queue again. + absl::Time time_packet_sent = absl::Now(); + QueueCounters queue_counters_after_test_packet; + do { + ASSERT_OK_AND_ASSIGN( + queue_counters_after_test_packet, + GetGnmiQueueCounters(/*port=*/"CPU", /*queue=*/target_queue, + *gnmi_stub)); + } while ( + // It may take several seconds for the queue counters to update. + CumulativeNumPacketsEnqueued(queue_counters_after_test_packet) == + CumulativeNumPacketsEnqueued(queue_counters_before_test_packet) && + absl::Now() - time_packet_sent < kMaxQueueCounterUpdateTime); + + EXPECT_EQ( + CumulativeNumPacketsEnqueued(queue_counters_after_test_packet), + CumulativeNumPacketsEnqueued(queue_counters_before_test_packet) + 1) + << "expected counter to increment for queue '" << target_queue + << "' targeted by the following test packet:\n" + << packet.DebugString() + << "\nBefore: " << queue_counters_before_test_packet + << "\nAfter : " << queue_counters_after_test_packet; + } + } + LOG(INFO) << "-- END OF TEST -----------------------------------------------"; +} + +TEST_P(CpuQosTestWithIxia, TestCPUQueueRateLimit) { // Pick a testbed with an Ixia Traffic Generator. auto requirements = gutil::ParseProtoOrDie( @@ -254,7 +577,8 @@ TEST_P(CpuQosIxiaTestFixture, TestCPUQueueRateLimit) { ASSERT_OK_AND_ASSIGN( final_counters, - GetGnmiQueueStat("CPU", queue_under_test.gnmi_queue_name, *gnmi_stub)); + GetGnmiQueueCounters("CPU", queue_under_test.gnmi_queue_name, + *gnmi_stub)); } } diff --git a/tests/qos/cpu_qos_test.h b/tests/qos/cpu_qos_test.h index 531ecdc2..2b5e3fdb 100644 --- a/tests/qos/cpu_qos_test.h +++ b/tests/qos/cpu_qos_test.h @@ -12,31 +12,85 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Contains tests of the QoS features of the switch (rate limits, +// classification, scheduling) related to protecting the CPU from overload. +// +// Some tests rely on an Ixia to generate test packets at line rate. + #ifndef PINS_TESTS_CPU_QOS_TEST_H_ #define PINS_TESTS_CPU_QOS_TEST_H_ +#include "gtest/gtest.h" +#include "gutil/status_matchers.h" +#include "gutil/testing.h" #include "sai_p4/instantiations/google/sai_pd.pb.h" #include "thinkit/generic_testbed.h" #include "thinkit/generic_testbed_fixture.h" +#include "thinkit/mirror_testbed.h" +#include "thinkit/mirror_testbed_fixture.h" namespace pins_test { -struct QosTestArguments { - // Description of the test instantation given by this set of arguments. - std::string description; +// Parameters used by the tests that don't require an Ixia. +struct ParamsForTestsWithoutIxia { + thinkit::MirrorTestbedInterface* testbed_interface; + std::string gnmi_config; + p4::config::v1::P4Info p4info; +}; + +// Fixture of tests that do not require an Ixia. These test must be run on a +// testbed where the SUT is connected to a "control device" that can send and +// received packets. +class CpuQosTestWithoutIxia + : public testing::TestWithParam { + protected: + void SetUp() override { + GetParam().testbed_interface->SetUp(); + + // TODO: Port to thinkit::GenericTestbed. + // // Pick a testbed where the SUT is connected to another device that can + // send + // // and receive packets. + // ASSERT_OK_AND_ASSIGN( + // testbed_, GetParam().testbed_interface->GetTestbedWithRequirements( + // gutil::ParseProtoOrDie(R"pb( + // interface_requirements { + // interface_mode: CONTROL_INTERFACE + // count: 1 + // } + // )pb"))); + // ASSERT_OK_AND_ASSIGN( + // testbed_, GetParam().testbed_interface->GetMirrorTestbed()); + } + + thinkit::MirrorTestbed& Testbed() { + return GetParam().testbed_interface->GetMirrorTestbed(); + } + + void TearDown() override { GetParam().testbed_interface->TearDown(); } + + ~CpuQosTestWithoutIxia() override { delete GetParam().testbed_interface; } + + private: + std::unique_ptr testbed_; +}; + +// Parameters used by the tests that require an Ixia. +struct ParamsForTestsWithIxia { thinkit::GenericTestbedInterface* testbed_interface; - std::vector table_entries; std::string gnmi_config; p4::config::v1::P4Info p4info; absl::optional test_case_id; }; -class CpuQosIxiaTestFixture : public testing::TestWithParam { +class CpuQosTestWithIxia + : public testing::TestWithParam { protected: void SetUp() override { GetParam().testbed_interface->SetUp(); } void TearDown() override { GetParam().testbed_interface->TearDown(); } - ~CpuQosIxiaTestFixture() override { delete GetParam().testbed_interface; } + ~CpuQosTestWithIxia() override { delete GetParam().testbed_interface; } }; + } // namespace pins_test #endif // PINS_TESTS_CPU_QOS_TEST_H_ From d438f2743b7121d9455e7436fc969adbbe05e016 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:29:33 +0000 Subject: [PATCH 12/24] [Dvaas]: Adding Test_vector Stats files. (#717) Co-authored-by: kishanps --- dvaas/BUILD.bazel | 37 ++++++ dvaas/test_vector_stats.cc | 108 +++++++++++++++++ dvaas/test_vector_stats.h | 44 +++++++ dvaas/test_vector_stats_test.cc | 161 ++++++++++++++++++++++++++ dvaas/test_vector_stats_test.expected | 5 + 5 files changed, 355 insertions(+) create mode 100644 dvaas/test_vector_stats.cc create mode 100644 dvaas/test_vector_stats.h create mode 100644 dvaas/test_vector_stats_test.cc create mode 100644 dvaas/test_vector_stats_test.expected diff --git a/dvaas/BUILD.bazel b/dvaas/BUILD.bazel index ed30a5fd..d674b687 100644 --- a/dvaas/BUILD.bazel +++ b/dvaas/BUILD.bazel @@ -139,6 +139,43 @@ cc_library( ], ) +cc_library( + name = "test_vector_stats", + srcs = ["test_vector_stats.cc"], + hdrs = ["test_vector_stats.h"], + deps = [ + ":test_vector_cc_proto", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + ], +) + +# go/golden-test-with-coverage +cc_test( + name = "test_vector_stats_test", + srcs = ["test_vector_stats_test.cc"], + linkstatic = True, + deps = [ + ":test_vector_cc_proto", + ":test_vector_stats", + "//gutil:testing", + "@com_google_googletest//:gtest_main", + ], +) + +cmd_diff_test( + name = "test_vector_stats_diff_test", + actual_cmd = " | ".join([ + "$(execpath :test_vector_stats_test)", + # Strip unnecessary lines for golden testing. + "sed '1,/^\\[ RUN/d'", # Strip everything up to a line beginning with '[ RUN'. + "sed '/^\\[/d'", # Strip every line beginning with '['. + ]), + expected = "test_vector_stats_test.expected", + tools = [":test_vector_stats_test"], +) + cc_library( name = "mirror_testbed_config", testonly = True, diff --git a/dvaas/test_vector_stats.cc b/dvaas/test_vector_stats.cc new file mode 100644 index 00000000..0b814e40 --- /dev/null +++ b/dvaas/test_vector_stats.cc @@ -0,0 +1,108 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dvaas/test_vector_stats.h" + +#include + +#include "absl/algorithm/container.h" +#include "absl/strings/str_format.h" +#include "dvaas/test_vector.pb.h" + +namespace dvaas { + +namespace { + +void AddTestVectorStats(const PacketTestOutcome& outcome, + TestVectorStats& stats) { + stats.num_vectors += 1; + + if (!outcome.test_result().has_failure()) { + stats.num_vectors_passed += 1; + } + + const SwitchOutput& actual_output = outcome.test_run().actual_output(); + const int num_forwarded = actual_output.packets_size(); + const int num_punted = actual_output.packet_ins_size(); + + bool has_correct_num_outputs = absl::c_any_of( + outcome.test_run().test_vector().acceptable_outputs(), + [&](const SwitchOutput& acceptable_output) { + return num_forwarded == acceptable_output.packets_size() && + num_punted == acceptable_output.packet_ins_size(); + }); + if (has_correct_num_outputs) { + stats.num_vectors_with_correct_number_of_outputs += 1; + } + + stats.num_vectors_forwarding += num_forwarded > 0 ? 1 : 0; + stats.num_vectors_punting += num_punted > 0 ? 1 : 0; + stats.num_vectors_dropping += num_forwarded == 0 && num_punted == 0; + stats.num_packets_forwarded += num_forwarded; + stats.num_packets_punted += num_punted; +} + +} // namespace + +TestVectorStats ComputeTestVectorStats( + const PacketTestOutcomes& test_outcomes) { + TestVectorStats stats; + for (const auto& outcome : test_outcomes.outcomes()) { + AddTestVectorStats(outcome, stats); + } + return stats; +} + +namespace { + +std::string ExplainPercent(double fraction) { + return absl::StrFormat("%.2f%%", fraction * 100); +} +std::string ExplainPercent(int numerator, int denominator) { + if (denominator == 0) return "100%"; + return ExplainPercent(static_cast(numerator) / denominator); +} + +std::string ExplainFraction(int numerator, int denominator) { + return absl::StrFormat("%s of %d", ExplainPercent(numerator, denominator), + denominator); +} + +} // namespace + +std::string ExplainTestVectorStats(const TestVectorStats& stats) { + std::string result; + + absl::StrAppendFormat( + &result, "%s test vectors passed\n", + ExplainFraction(stats.num_vectors_passed, stats.num_vectors)); + absl::StrAppendFormat( + &result, + "%s test vectors produced the correct number and type of output " + "packets\n", + ExplainFraction(stats.num_vectors_with_correct_number_of_outputs, + stats.num_vectors)); + absl::StrAppendFormat( + &result, + "%d test vectors forwarded, producing %d forwarded output packets\n", + stats.num_vectors_forwarding, stats.num_packets_forwarded); + absl::StrAppendFormat( + &result, "%d test vectors punted, producing %d punted output packets\n", + stats.num_vectors_punting, stats.num_packets_punted); + absl::StrAppendFormat(&result, "%d test vectors produced no output packets\n", + stats.num_vectors_dropping); + return result; +} + +} // namespace dvaas diff --git a/dvaas/test_vector_stats.h b/dvaas/test_vector_stats.h new file mode 100644 index 00000000..89431f13 --- /dev/null +++ b/dvaas/test_vector_stats.h @@ -0,0 +1,44 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PINS_DVAAS_TEST_VECTOR_STATS_H_ +#define PINS_DVAAS_TEST_VECTOR_STATS_H_ + +#include + +#include "dvaas/test_vector.pb.h" + +namespace dvaas { + +struct TestVectorStats { + int num_vectors = 0; + int num_vectors_passed = 0; + // Can be higher than `num_vectors_passed`, e.g. because the outputs + // could have incorrect header field values. + int num_vectors_with_correct_number_of_outputs = 0; + int num_vectors_forwarding = 0; + int num_vectors_punting = 0; + int num_vectors_dropping = 0; + + int num_packets_forwarded = 0; + int num_packets_punted = 0; +}; + +TestVectorStats ComputeTestVectorStats(const PacketTestOutcomes& test_outcomes); + +std::string ExplainTestVectorStats(const TestVectorStats& stats); + +} // namespace dvaas + +#endif // PINS_DVAAS_TEST_VECTOR_STATS_H_ diff --git a/dvaas/test_vector_stats_test.cc b/dvaas/test_vector_stats_test.cc new file mode 100644 index 00000000..fa5ed953 --- /dev/null +++ b/dvaas/test_vector_stats_test.cc @@ -0,0 +1,161 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dvaas/test_vector_stats.h" + +#include + +#include "dvaas/test_vector.pb.h" +#include "gtest/gtest.h" +#include "gutil/testing.h" + +namespace dvaas { +namespace { + +PacketTestOutcomes GetPacketTestOutcomees() { + return gutil::ParseProtoOrDie(R"pb( + # Correct drop. + outcomes { + test_run { + test_vector { + input {} + # Deterministic drop. + acceptable_outputs { + packets: [] + packet_ins: [] + } + } + actual_output { + packets: [] + packet_ins: [] + } + } + # Passed. + test_result {} + } + + # Incorrect forward. + outcomes { + test_run { + test_vector { + input {} + # Deterministic forward. + acceptable_outputs { + packets {} + packet_ins: [] + } + } + actual_output { + packets {} + packet_ins: [] + } + } + # Failed. + test_result { failure {} } + } + + # Correct forward. + outcomes { + test_run { + test_vector { + input {} + # Deterministic forward. + acceptable_outputs { + packets {} + packet_ins: [] + } + } + actual_output { + packets {} + packet_ins: [] + } + } + # Passed. + test_result {} + } + + # Correct multi forward. + outcomes { + test_run { + test_vector { + input {} + # Deterministic multi forward. + acceptable_outputs { + packets {} + packets {} + packets {} + packet_ins: [] + } + } + actual_output { + packets {} + packets {} + packets {} + packet_ins: [] + } + } + # Passed. + test_result {} + } + + # Incorrect punt. + outcomes { + test_run { + test_vector { + input {} + # Deterministic trap. + acceptable_outputs { + packets: [] + packet_ins {} + } + } + actual_output { + packets: [] + packet_ins: {} + } + } + # Failed. + test_result { failure {} } + } + + # Correct forward & copy. + outcomes { + test_run { + test_vector { + input {} + # Deterministic forward & copy. + acceptable_outputs { + packets {} + packet_ins {} + } + } + actual_output { + packets {} + packet_ins: {} + } + } + # Passed. + test_result {} + } + )pb"); +} + +TEST(TestVectorStatsGoldenTest, + ComputeTestVectorStatsAndExplainTestVectorStats) { + PacketTestOutcomes outcomes = GetPacketTestOutcomees(); + TestVectorStats stats = ComputeTestVectorStats(outcomes); + std::cout << ExplainTestVectorStats(stats); +} +} // namespace +} // namespace dvaas diff --git a/dvaas/test_vector_stats_test.expected b/dvaas/test_vector_stats_test.expected new file mode 100644 index 00000000..27eeee3b --- /dev/null +++ b/dvaas/test_vector_stats_test.expected @@ -0,0 +1,5 @@ +66.67% of 6 test vectors passed +100.00% of 6 test vectors produced the correct number and type of output packets +4 test vectors forwarded, producing 6 forwarded output packets +2 test vectors punted, producing 2 punted output packets +1 test vectors produced no output packets From 7082388d9ac15d3f9e5df6c4a90ab2d5a4772fe0 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:30:09 +0000 Subject: [PATCH 13/24] [Dvaas]: Validating test stats. (#718) Co-authored-by: kishanps --- dvaas/BUILD.bazel | 18 ++++++++ dvaas/validation_result.cc | 91 ++++++++++++++++++++++++++++++++++++++ dvaas/validation_result.h | 75 +++++++++++++++++++++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 dvaas/validation_result.cc create mode 100644 dvaas/validation_result.h diff --git a/dvaas/BUILD.bazel b/dvaas/BUILD.bazel index d674b687..15f7b43f 100644 --- a/dvaas/BUILD.bazel +++ b/dvaas/BUILD.bazel @@ -20,6 +20,24 @@ package( licenses = ["notice"], ) +cc_library( + name = "validation_result", + srcs = ["validation_result.cc"], + hdrs = ["validation_result.h"], + deps = [ + ":test_run_validation", + ":test_vector_cc_proto", + ":test_vector_stats", + "//gutil:status", + "@com_github_google_glog//:glog", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/status", + "@com_google_absl//absl/types:span", + "@com_google_protobuf//:protobuf", + ], +) + cc_library( name = "output_writer", hdrs = ["output_writer.h"], diff --git a/dvaas/validation_result.cc b/dvaas/validation_result.cc new file mode 100644 index 00000000..029da0ba --- /dev/null +++ b/dvaas/validation_result.cc @@ -0,0 +1,91 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dvaas/validation_result.h" + +#include +#include + +#include "absl/algorithm/container.h" +#include "absl/container/flat_hash_set.h" +#include "absl/status/status.h" +#include "dvaas/test_run_validation.h" +#include "dvaas/test_vector.pb.h" +#include "dvaas/test_vector_stats.h" +#include "glog/logging.h" +#include "google/protobuf/descriptor.h" +#include "gutil/status.h" + +namespace dvaas { + +ValidationResult::ValidationResult( + const PacketTestRuns& test_runs, + std::vector ignored_fields, + const absl::flat_hash_set& ignored_metadata) { + test_outcomes_.mutable_outcomes()->Reserve(test_runs.test_runs_size()); + for (const auto& test_run : test_runs.test_runs()) { + PacketTestOutcome& outcome = *test_outcomes_.add_outcomes(); + *outcome.mutable_test_run() = test_run; + *outcome.mutable_test_result() = + ValidateTestRun(test_run, ignored_metadata, ignored_fields); + } + + test_vector_stats_ = ComputeTestVectorStats(test_outcomes_); +} + +absl::Status ValidationResult::HasSuccessRateOfAtLeast( + double expected_success_rate) const { + // Avoid division by 0. + if (test_vector_stats_.num_vectors == 0) return absl::OkStatus(); + + double success_rate = + static_cast(test_vector_stats_.num_vectors_passed) / + static_cast(test_vector_stats_.num_vectors); + if (success_rate < expected_success_rate) { + auto it = + absl::c_find_if(test_outcomes_.outcomes(), [](const auto& outcome) { + return outcome.test_result().has_failure(); + }); + if (it == test_outcomes_.outcomes().end()) return absl::OkStatus(); + return gutil::OutOfRangeErrorBuilder() + << ExplainTestVectorStats(test_vector_stats_) + << "\nShowing the first failure only. Refer to the test artifacts " + "for the full list of errors.\n" + << it->test_result().failure().description(); + } + return absl::OkStatus(); +} + +const ValidationResult& ValidationResult::LogStatistics() const { + LOG(INFO) << ExplainTestVectorStats(test_vector_stats_); + return *this; +} +ValidationResult& ValidationResult::LogStatistics() { + LOG(INFO) << ExplainTestVectorStats(test_vector_stats_); + return *this; +} + +std::vector ValidationResult::GetAllFailures() const { + std::vector failures; + failures.reserve(test_vector_stats_.num_vectors - + test_vector_stats_.num_vectors_passed); + for (const auto& outcome : test_outcomes_.outcomes()) { + if (outcome.test_result().has_failure()) { + failures.push_back(outcome.test_result().failure().description()); + } + } + return failures; +} + +} // namespace dvaas diff --git a/dvaas/validation_result.h b/dvaas/validation_result.h new file mode 100644 index 00000000..fcc02017 --- /dev/null +++ b/dvaas/validation_result.h @@ -0,0 +1,75 @@ +// Tools to analyze and consume the result of dataplane validation, as returned +// to DVaaS users. + +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PINS_DVAAS_VALIDATION_RESULT_H_ +#define PINS_DVAAS_VALIDATION_RESULT_H_ + +#include +#include + +#include "absl/container/flat_hash_set.h" +#include "absl/status/status.h" +#include "absl/types/span.h" +#include "dvaas/test_vector.pb.h" +#include "dvaas/test_vector_stats.h" +#include "google/protobuf/descriptor.h" + +namespace dvaas { + +// The result of dataplane validation, as returned to DVaaS users. +class [[nodiscard]] ValidationResult { + public: + // Asserts that at least an `expected_success_rate` fraction of test vectors + // succeeded, returning either: + // * an `Ok` status if that is the case, or + // * an `OutOfRange` error with details to assist debugging otherwise. + // + // Example uses: + // ``` + // // Expect all tests to pass, e.g. when the switch is stable. + // EXPECT_OK(validation_result.HasSuccessRateOfAtLeast(1.0)); + // + // // Expect at least 70% of tests to pass, e.g. during development. + // EXPECT_OK(validation_result.HasSuccessRateOfAtLeast(0.75)); + // ``` + absl::Status HasSuccessRateOfAtLeast(double expected_success_rate) const; + + // Logs various statistics about the number of test vectors and how many of + // them passed. + const ValidationResult& LogStatistics() const; + ValidationResult& LogStatistics(); + + // Returns a list of all test failures. Prefer using `HasSuccessRateOfAtLeast` + // as it includes additional information to ease debugging. + std::vector GetAllFailures() const; + + // Constructs a `ValidationResult` from the given `test_vectors`. Ignores + // `ignored_fields` and `ignored_metadata` during validation, see + // `test_run_validation.h` for details. + ValidationResult( + const PacketTestRuns& test_runs, + std::vector ignored_fields, + const absl::flat_hash_set& ignored_metadata); + + private: + PacketTestOutcomes test_outcomes_; + TestVectorStats test_vector_stats_; +}; + +} // namespace dvaas + +#endif // PINS_DVAAS_VALIDATION_RESULT_H_ From 63311396ea266e1b464b6fa3aeb01a6a4082083c Mon Sep 17 00:00:00 2001 From: bibhuprasad-hcl <161687009+bibhuprasad-hcl@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:32:00 +0000 Subject: [PATCH 14/24] BERT tests. (#719) Co-authored-by: smolkaj Co-authored-by: kishanps --- tests/gnoi/BUILD.bazel | 1 + tests/gnoi/bert_tests.cc | 60 ++++++++++++++++++++++------------------ 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/tests/gnoi/BUILD.bazel b/tests/gnoi/BUILD.bazel index fa89d2b2..d86c11c4 100644 --- a/tests/gnoi/BUILD.bazel +++ b/tests/gnoi/BUILD.bazel @@ -39,6 +39,7 @@ cc_library( "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/flags:flag", "@com_google_absl//absl/random", + "@com_github_grpc_grpc//:grpc++", "@com_google_absl//absl/strings", "@com_google_absl//absl/time", "@com_google_googletest//:gtest", diff --git a/tests/gnoi/bert_tests.cc b/tests/gnoi/bert_tests.cc index 2e63fd8a..3a7cdef8 100644 --- a/tests/gnoi/bert_tests.cc +++ b/tests/gnoi/bert_tests.cc @@ -380,8 +380,7 @@ void GetAndVerifyBertResultsWithAdminDownInterfaces( } } -void SelectNUpInterfaces(int port_count_to_select, - gnmi::gNMI::StubInterface& gnmi_stub, +void SelectNUpInterfaces(int port_count_to_select, gnmi::gNMI::StubInterface& gnmi_stub, std::vector* interfaces) { // Get all the interfaces that are operational status "UP". ASSERT_OK_AND_ASSIGN(*interfaces, @@ -397,17 +396,17 @@ TEST_P(BertTest, StartBertFailsIfRequestParametersInvalid) { GetMirrorTestbed().Environment().SetTestCaseID( "c1dcb1cc-4806-45cc-8f8a-676beafde103"); thinkit::Switch& sut = GetMirrorTestbed().Sut(); - // TODO (b/176913347): Enable the all ports UP check. ASSERT_OK(pins_test::PortsUp(sut)); - ASSERT_OK_AND_ASSIGN(auto sut_gnoi_diag_stub, sut.CreateGnoiDiagStub()); + ASSERT_OK_AND_ASSIGN( + std::unique_ptr sut_gnoi_diag_stub, + sut.CreateGnoiDiagStub()); // Select one operational state "up" port. std::string interface = absl::GetFlag(FLAGS_interface); if (interface.empty()) { std::vector interfaces; - ASSERT_OK_AND_ASSIGN( - std::unique_ptr sut_gnmi_stub, - sut.CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_gnmi_stub, + sut.CreateGnmiStub()); ASSERT_NO_FATAL_FAILURE( SelectNUpInterfaces(1, *sut_gnmi_stub, &interfaces)); interface = interfaces[0]; @@ -422,7 +421,7 @@ TEST_P(BertTest, StartBertFailsIfRequestParametersInvalid) { absl::StrCat("OpId-", absl::ToUnixMillis(absl::Now()))); *(valid_request.add_per_port_requests()) = gutil::ParseProtoOrDie( - BuildPerPortStartBertRequest("Ethernet0")); + BuildPerPortStartBertRequest("interface")); gnoi::diag::StartBERTResponse response; // Case 1: BERT test duration is 0 secs. @@ -529,7 +528,8 @@ TEST_P(BertTest, StopBertfailsIfRequestParametersInvalid) { std::string interface = absl::GetFlag(FLAGS_interface); if (interface.empty()) { std::vector interfaces; - ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_gnmi_stub, + sut.CreateGnmiStub()); ASSERT_NO_FATAL_FAILURE( SelectNUpInterfaces(1, *sut_gnmi_stub, &interfaces)); interface = interfaces[0]; @@ -597,7 +597,8 @@ TEST_P(BertTest, GetBertResultFailsIfRequestParametersInvalid) { std::string interface = absl::GetFlag(FLAGS_interface); if (interface.empty()) { std::vector interfaces; - ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_gnmi_stub, + sut.CreateGnmiStub()); ASSERT_NO_FATAL_FAILURE( SelectNUpInterfaces(1, *sut_gnmi_stub, &interfaces)); interface = interfaces[0]; @@ -625,8 +626,7 @@ TEST_P(BertTest, GetBertResultFailsIfRequestParametersInvalid) { HasSubstr(interface)))); } - // TODO (b/176913347): Enable the all ports UP check. - // ASSERT_OK(pins_test::PortsUp(sut)); + ASSERT_OK(pins_test::PortsUp(sut)); } @@ -635,10 +635,12 @@ TEST_P(BertTest, StartBertfailsIfPeerPortNotRunningBert) { GetMirrorTestbed().Environment().SetTestCaseID( "37e48922-0616-4d16-8fd3-975897491956"); thinkit::Switch& sut = GetMirrorTestbed().Sut(); - // TODO (b/176913347): Enable the all ports UP check. - ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_gnmi_stub, + sut.CreateGnmiStub()); ASSERT_OK(pins_test::PortsUp(sut)); - ASSERT_OK_AND_ASSIGN(auto sut_gnoi_diag_stub, sut.CreateGnoiDiagStub()); + ASSERT_OK_AND_ASSIGN( + std::unique_ptr sut_gnoi_diag_stub, + sut.CreateGnoiDiagStub()); // Select one operational state "up" port. std::string interface = absl::GetFlag(FLAGS_interface); @@ -659,10 +661,9 @@ TEST_P(BertTest, StartBertfailsIfPeerPortNotRunningBert) { *(bert_request.add_per_port_requests()) = gutil::ParseProtoOrDie( BuildPerPortStartBertRequest(interface)); - - LOG(INFO) << "Sending StartBERT request on SUT:"; - ASSERT_NO_FATAL_FAILURE( - SendStartBertRequestSuccessfully(bert_request, *sut_gnoi_diag_stub)); + gnoi::diag::StartBERTResponse bert_response; + grpc::ClientContext context; + LOG(INFO) << "Sending StartBERT request: " << bert_request.ShortDebugString(); // Poll for allowed BERT delay duration. int max_poll_count = @@ -715,22 +716,27 @@ TEST_P(BertTest, StartBertSucceeds) { GetMirrorTestbed().Environment().SetTestCaseID( "b31a796a-d078-4d45-b785-f09ec598e05a"); thinkit::Switch& sut = GetMirrorTestbed().Sut(); - ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); - // TODO (b/176913347): Enable the all ports UP check. + ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_gnmi_stub, + sut.CreateGnmiStub()); ASSERT_OK(pins_test::PortsUp(sut)); - ASSERT_OK_AND_ASSIGN(auto sut_gnoi_diag_stub, sut.CreateGnoiDiagStub()); + ASSERT_OK_AND_ASSIGN( + std::unique_ptr sut_gnoi_diag_stub, + sut.CreateGnoiDiagStub()); thinkit::Switch& control_switch = GetMirrorTestbed().ControlSwitch(); - ASSERT_OK_AND_ASSIGN(auto control_switch_gnmi_stub, - control_switch.CreateGnmiStub()); - // TODO (b/176913347): Enable the all ports UP check. + ASSERT_OK_AND_ASSIGN( + std::unique_ptr control_switch_gnmi_stub, + control_switch.CreateGnmiStub()); ASSERT_OK(pins_test::PortsUp(control_switch)); - ASSERT_OK_AND_ASSIGN(auto control_switch_gnoi_diag_stub, - control_switch.CreateGnoiDiagStub()); + ASSERT_OK_AND_ASSIGN( + std::unique_ptr control_switch_gnoi_diag_stub, + control_switch.CreateGnoiDiagStub()); // Select 2 operational state "up" ports. std::vector interfaces = absl::GetFlag(FLAGS_interfaces); if (interfaces.empty()) { + ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_gnmi_stub, + sut.CreateGnmiStub()); ASSERT_NO_FATAL_FAILURE( SelectNUpInterfaces(2, *sut_gnmi_stub, &interfaces)); } From cbd9b0a2d4205bc34b8924aabdca37d1dd8eda2b Mon Sep 17 00:00:00 2001 From: bibhuprasad-hcl <161687009+bibhuprasad-hcl@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:33:45 +0000 Subject: [PATCH 15/24] [Thinkit] Change Stub to StubInterface or auto. (#720) Co-authored-by: smolkaj Co-authored-by: kishanps --- tests/gnoi/bert_tests.cc | 45 +++++++++++++++------------------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/tests/gnoi/bert_tests.cc b/tests/gnoi/bert_tests.cc index 3a7cdef8..b12fed12 100644 --- a/tests/gnoi/bert_tests.cc +++ b/tests/gnoi/bert_tests.cc @@ -380,7 +380,8 @@ void GetAndVerifyBertResultsWithAdminDownInterfaces( } } -void SelectNUpInterfaces(int port_count_to_select, gnmi::gNMI::StubInterface& gnmi_stub, +void SelectNUpInterfaces(int port_count_to_select, + gnmi::gNMI::StubInterface& gnmi_stub, std::vector* interfaces) { // Get all the interfaces that are operational status "UP". ASSERT_OK_AND_ASSIGN(*interfaces, @@ -397,16 +398,15 @@ TEST_P(BertTest, StartBertFailsIfRequestParametersInvalid) { "c1dcb1cc-4806-45cc-8f8a-676beafde103"); thinkit::Switch& sut = GetMirrorTestbed().Sut(); ASSERT_OK(pins_test::PortsUp(sut)); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr sut_gnoi_diag_stub, - sut.CreateGnoiDiagStub()); + ASSERT_OK_AND_ASSIGN(auto sut_gnoi_diag_stub, sut.CreateGnoiDiagStub()); // Select one operational state "up" port. std::string interface = absl::GetFlag(FLAGS_interface); if (interface.empty()) { std::vector interfaces; - ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_gnmi_stub, - sut.CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN( + std::unique_ptr sut_gnmi_stub, + sut.CreateGnmiStub()); ASSERT_NO_FATAL_FAILURE( SelectNUpInterfaces(1, *sut_gnmi_stub, &interfaces)); interface = interfaces[0]; @@ -497,7 +497,7 @@ TEST_P(BertTest, StopBertfailsIfRequestParametersInvalid) { GetMirrorTestbed().Environment().SetTestCaseID( "224db9cf-c709-486d-a0d3-6ab64c1a1e1f"); thinkit::Switch& sut = GetMirrorTestbed().Sut(); - // TODO (b/176913347): Enable the all ports UP check. + ASSERT_OK(pins_test::PortsUp(sut)); ASSERT_OK_AND_ASSIGN(auto sut_gnoi_diag_stub, sut.CreateGnoiDiagStub()); @@ -528,8 +528,7 @@ TEST_P(BertTest, StopBertfailsIfRequestParametersInvalid) { std::string interface = absl::GetFlag(FLAGS_interface); if (interface.empty()) { std::vector interfaces; - ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_gnmi_stub, - sut.CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); ASSERT_NO_FATAL_FAILURE( SelectNUpInterfaces(1, *sut_gnmi_stub, &interfaces)); interface = interfaces[0]; @@ -567,7 +566,6 @@ TEST_P(BertTest, GetBertResultFailsIfRequestParametersInvalid) { GetMirrorTestbed().Environment().SetTestCaseID( "4f837d7a-ab44-4694-9ca9-399d576757f4"); thinkit::Switch& sut = GetMirrorTestbed().Sut(); - // TODO (b/176913347): Enable the all ports UP check. ASSERT_OK(pins_test::PortsUp(sut)); ASSERT_OK_AND_ASSIGN(auto sut_gnoi_diag_stub, sut.CreateGnoiDiagStub()); @@ -597,8 +595,7 @@ TEST_P(BertTest, GetBertResultFailsIfRequestParametersInvalid) { std::string interface = absl::GetFlag(FLAGS_interface); if (interface.empty()) { std::vector interfaces; - ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_gnmi_stub, - sut.CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); ASSERT_NO_FATAL_FAILURE( SelectNUpInterfaces(1, *sut_gnmi_stub, &interfaces)); interface = interfaces[0]; @@ -635,12 +632,9 @@ TEST_P(BertTest, StartBertfailsIfPeerPortNotRunningBert) { GetMirrorTestbed().Environment().SetTestCaseID( "37e48922-0616-4d16-8fd3-975897491956"); thinkit::Switch& sut = GetMirrorTestbed().Sut(); - ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_gnmi_stub, - sut.CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); ASSERT_OK(pins_test::PortsUp(sut)); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr sut_gnoi_diag_stub, - sut.CreateGnoiDiagStub()); + ASSERT_OK_AND_ASSIGN(auto sut_gnoi_diag_stub, sut.CreateGnoiDiagStub()); // Select one operational state "up" port. std::string interface = absl::GetFlag(FLAGS_interface); @@ -716,21 +710,16 @@ TEST_P(BertTest, StartBertSucceeds) { GetMirrorTestbed().Environment().SetTestCaseID( "b31a796a-d078-4d45-b785-f09ec598e05a"); thinkit::Switch& sut = GetMirrorTestbed().Sut(); - ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_gnmi_stub, - sut.CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub()); ASSERT_OK(pins_test::PortsUp(sut)); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr sut_gnoi_diag_stub, - sut.CreateGnoiDiagStub()); + ASSERT_OK_AND_ASSIGN(auto sut_gnoi_diag_stub, sut.CreateGnoiDiagStub()); thinkit::Switch& control_switch = GetMirrorTestbed().ControlSwitch(); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr control_switch_gnmi_stub, - control_switch.CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN(auto control_switch_gnmi_stub, + control_switch.CreateGnmiStub()); ASSERT_OK(pins_test::PortsUp(control_switch)); - ASSERT_OK_AND_ASSIGN( - std::unique_ptr control_switch_gnoi_diag_stub, - control_switch.CreateGnoiDiagStub()); + ASSERT_OK_AND_ASSIGN(auto control_switch_gnoi_diag_stub, + control_switch.CreateGnoiDiagStub()); // Select 2 operational state "up" ports. std::vector interfaces = absl::GetFlag(FLAGS_interfaces); From 5ff5f3ae671d7b821310d481a17e4cc533ba3d9b Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:34:16 +0000 Subject: [PATCH 16/24] [P4_Symbolic] Adding p4_symbolic/sai/criteria_generator_test.cc (#721) Co-authored-by: kishanps --- p4_symbolic/sai/criteria_generator_test.cc | 328 +++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 p4_symbolic/sai/criteria_generator_test.cc diff --git a/p4_symbolic/sai/criteria_generator_test.cc b/p4_symbolic/sai/criteria_generator_test.cc new file mode 100644 index 00000000..8adf0f1c --- /dev/null +++ b/p4_symbolic/sai/criteria_generator_test.cc @@ -0,0 +1,328 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "p4_symbolic/packet_synthesizer/criteria_generator.h" + +#include +#include + +#include "absl/types/optional.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "gutil/proto_matchers.h" +#include "gutil/status_matchers.h" +#include "gutil/testing.h" +#include "p4/v1/p4runtime.pb.h" +#include "p4_pdpi/ir.pb.h" +#include "p4_pdpi/pd.h" +#include "p4_symbolic/packet_synthesizer/coverage_goal.pb.h" +#include "p4_symbolic/packet_synthesizer/packet_synthesis_criteria.pb.h" +#include "p4_symbolic/packet_synthesizer/packet_synthesizer.h" +#include "p4_symbolic/packet_synthesizer/packet_synthesizer.pb.h" +#include "sai_p4/instantiations/google/instantiations.h" +#include "sai_p4/instantiations/google/sai_nonstandard_platforms.h" +#include "sai_p4/instantiations/google/sai_p4info.h" +#include "sai_p4/instantiations/google/sai_pd.pb.h" + +namespace p4_symbolic::packet_synthesizer { +namespace { + +using ::gutil::ParseProtoOrDie; + +// Returns packet synthesis parameters with the pipeline config for FBR and a +// simple table entry. +// If `in_port_match` is not null, the added entry matches on the provided +// ingress port. +PacketSynthesisParams GetParams( + // TODO: Use non-SAI program and move test out of SAI + // directory. + std::optional in_port_match = std::nullopt) { + const sai::Instantiation instantiation = + sai::Instantiation::kFabricBorderRouter; + const pdpi::IrP4Info ir_p4info = sai::GetIrP4Info(instantiation); + const p4::v1::ForwardingPipelineConfig pipeline_config = + sai::GetNonstandardForwardingPipelineConfig( + instantiation, sai::NonstandardPlatform::kP4Symbolic); + + PacketSynthesisParams params; + *params.mutable_pipeline_config() = pipeline_config; + auto pd_entry = ParseProtoOrDie(R"pb( + acl_pre_ingress_table_entry { + match {} + action { set_vrf { vrf_id: "vrf-80" } } + priority: 1 + })pb"); + if (in_port_match.has_value()) + pd_entry.mutable_acl_pre_ingress_table_entry() + ->mutable_match() + ->mutable_in_port() + ->set_value(*in_port_match); + const auto pi_entry = pdpi::PdTableEntryToPi(ir_p4info, pd_entry); + + CHECK_OK(pi_entry.status()); + *params.mutable_pi_entries()->Add() = *pi_entry; + + return params; +} + +void ExpectEqualCriteriaList( + const std::vector& result, + const std::vector& expected) { + ASSERT_EQ(result.size(), expected.size()); + for (int i = 0; i < result.size(); i++) { + ASSERT_THAT(result[i], gutil::EqualsProto(expected[i])); + } +} + +TEST(GenerateSynthesisCriteriaTest, + EntryCoverageGoalWithWildcardIncludeYieldsCorrectCriteriaList) { + // Get a packet synthesizer object. + ASSERT_OK_AND_ASSIGN(auto synthesizer, + PacketSynthesizer::Create(GetParams())); + + ASSERT_OK_AND_ASSIGN( + auto criteria_list, + GenerateSynthesisCriteriaFor( + ParseProtoOrDie(R"pb( + coverage_goals { entry_coverage { tables { patterns: [ "*" ] } } } + )pb"), + synthesizer->SolverState())); + + ExpectEqualCriteriaList( + criteria_list, + { + ParseProtoOrDie( + R"pb( + table_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + } + table_entry_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + match_index: 0 + } + )pb"), + }); +} + +TEST(GenerateSynthesisCriteriaTest, + EntryCoverageGoalWithWildcardExcludeYieldsEmptyCriteriaList) { + // Get a packet synthesizer object. + ASSERT_OK_AND_ASSIGN(auto synthesizer, + PacketSynthesizer::Create(GetParams())); + + ASSERT_OK_AND_ASSIGN( + auto criteria_list, + GenerateSynthesisCriteriaFor(ParseProtoOrDie(R"pb( + coverage_goals { + entry_coverage { + tables { patterns: [ "*" ] } + table_exclusions { patterns: [ "*" ] } + } + } + )pb"), + synthesizer->SolverState())); + + ASSERT_EQ(criteria_list.size(), 0); +} + +TEST(GenerateSynthesisCriteriaTest, + EntryCoverageGoalWithCoverDefaultActionsIncludesEmptyTables) { + // Get a packet synthesizer object. + ASSERT_OK_AND_ASSIGN(auto synthesizer, + PacketSynthesizer::Create(GetParams())); + + ASSERT_OK_AND_ASSIGN( + auto criteria_list, + GenerateSynthesisCriteriaFor(ParseProtoOrDie(R"pb( + coverage_goals { + entry_coverage { + tables { patterns: [ "*" ] } + cover_default_actions: true + } + } + )pb"), + synthesizer->SolverState())); + + ASSERT_GT(criteria_list.size(), 1); +} + +TEST(GenerateSynthesisCriteriaTest, + EntryCoverageGoalWithCoverDefaultActionsYieldsCorrectCriteriaList) { + // Get a packet synthesizer object. + ASSERT_OK_AND_ASSIGN(auto synthesizer, + PacketSynthesizer::Create(GetParams())); + + ASSERT_OK_AND_ASSIGN( + auto criteria_list, + GenerateSynthesisCriteriaFor( + ParseProtoOrDie(R"pb( + coverage_goals { + entry_coverage { + tables { + patterns: [ "ingress.acl_pre_ingress.acl_pre_ingress_table" ] + } + cover_default_actions: true + } + } + )pb"), + synthesizer->SolverState())); + + ExpectEqualCriteriaList( + criteria_list, + { + ParseProtoOrDie( + R"pb( + table_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + } + table_entry_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + match_index: 0 + } + )pb"), + ParseProtoOrDie( + R"pb( + table_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + } + table_entry_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + match_index: -1 + } + )pb"), + }); +} + +TEST(GenerateSynthesisCriteriaTest, + PacketFateCoverageGoalYieldsCorrectCriteriaList) { + // Get a packet synthesizer object. + ASSERT_OK_AND_ASSIGN(auto synthesizer, + PacketSynthesizer::Create(GetParams())); + + ASSERT_OK_AND_ASSIGN( + auto criteria_list, + GenerateSynthesisCriteriaFor( + ParseProtoOrDie( + R"pb( + coverage_goals { + packet_fate_coverage { fates: [ DROP, NOT_DROP ] } + } + )pb"), + synthesizer->SolverState())); + + ExpectEqualCriteriaList(criteria_list, + { + ParseProtoOrDie( + R"pb( + output_criteria { drop_expected: true } + )pb"), + ParseProtoOrDie( + R"pb( + output_criteria { drop_expected: false } + )pb"), + }); +} + +TEST(GenerateSynthesisCriteriaTest, + CoverageGoalWithEntryAndFateCoverageYieldsCorrectResults) { + // Get a packet synthesizer object. + ASSERT_OK_AND_ASSIGN(auto synthesizer, + PacketSynthesizer::Create(GetParams())); + + ASSERT_OK_AND_ASSIGN( + auto criteria_list, + GenerateSynthesisCriteriaFor( + ParseProtoOrDie( + R"pb( + coverage_goals { + entry_coverage { tables { patterns: [ "*" ] } } + packet_fate_coverage { fates: [ DROP, NOT_DROP ] } + } + )pb"), + synthesizer->SolverState())); + + ExpectEqualCriteriaList( + criteria_list, + { + ParseProtoOrDie( + R"pb( + table_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + } + table_entry_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + match_index: 0 + } + output_criteria { drop_expected: true } + )pb"), + ParseProtoOrDie( + R"pb( + table_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + } + table_entry_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + match_index: 0 + } + output_criteria { drop_expected: false } + )pb"), + }); +} + +TEST(GenerateSynthesisCriteriaTest, CoverageGoalsYieldsCorrectResults) { + // Get a packet synthesizer object. + ASSERT_OK_AND_ASSIGN(auto synthesizer, + PacketSynthesizer::Create(GetParams())); + + ASSERT_OK_AND_ASSIGN( + auto criteria_list, + GenerateSynthesisCriteriaFor( + ParseProtoOrDie( + R"pb( + coverage_goals { + entry_coverage { tables { patterns: [ "*" ] } } + + } + coverage_goals { + packet_fate_coverage { fates: [ DROP, NOT_DROP ] } + } + )pb"), + synthesizer->SolverState())); + + ExpectEqualCriteriaList( + criteria_list, + { + ParseProtoOrDie( + R"pb( + table_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + } + table_entry_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + match_index: 0 + } + )pb"), + ParseProtoOrDie( + R"pb( + output_criteria { drop_expected: true } + )pb"), + ParseProtoOrDie( + R"pb( + output_criteria { drop_expected: false } + )pb"), + }); +} + +} // namespace +} // namespace p4_symbolic::packet_synthesizer From a181728395a195a761758e7e6827868d02ed8c29 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:35:22 +0000 Subject: [PATCH 17/24] [P4_Symbolic] Modified p4_symbolic/sai/criteria_generator_test.cc (#722) Co-authored-by: kishanps --- p4_symbolic/sai/criteria_generator_test.cc | 335 +++++++++++++++++++++ 1 file changed, 335 insertions(+) diff --git a/p4_symbolic/sai/criteria_generator_test.cc b/p4_symbolic/sai/criteria_generator_test.cc index 8adf0f1c..de4f4f6e 100644 --- a/p4_symbolic/sai/criteria_generator_test.cc +++ b/p4_symbolic/sai/criteria_generator_test.cc @@ -324,5 +324,340 @@ TEST(GenerateSynthesisCriteriaTest, CoverageGoalsYieldsCorrectResults) { }); } +TEST(GenerateSynthesisCriteriaTest, + HeaderCoverageGoalYieldsCorrectCriteriaList) { + // Get a packet synthesizer object. + ASSERT_OK_AND_ASSIGN(auto synthesizer, + PacketSynthesizer::Create(GetParams())); + + ASSERT_OK_AND_ASSIGN( + auto criteria_list, + GenerateSynthesisCriteriaFor( + ParseProtoOrDie( + R"pb( + coverage_goals { + header_coverage { headers { patterns: [ "ipv4", "ipv6" ] } } + } + )pb"), + synthesizer->SolverState())); + + ExpectEqualCriteriaList(criteria_list, + { + ParseProtoOrDie( + R"pb( + input_packet_header_criteria { + field_criteria { + field_match { + name: "ipv4.$valid$" + exact { hex_str: "0x1" } + } + } + } + )pb"), + ParseProtoOrDie( + R"pb( + input_packet_header_criteria { + field_criteria { + field_match { + name: "ipv6.$valid$" + exact { hex_str: "0x1" } + } + } + } + )pb"), + }); +} + +TEST(GenerateSynthesisCriteriaTest, + HeaderCoverageGoalDoesNotYieldMetadataValidtyCriteria) { + // Get a packet synthesizer object. + ASSERT_OK_AND_ASSIGN(auto synthesizer, + PacketSynthesizer::Create(GetParams())); + + ASSERT_OK_AND_ASSIGN( + auto criteria_list, + GenerateSynthesisCriteriaFor( + ParseProtoOrDie( + R"pb( + coverage_goals { + header_coverage { + headers { patterns: [ "standard_metadata", "scalars" ] } + } + } + )pb"), + synthesizer->SolverState())); + + ExpectEqualCriteriaList(criteria_list, {}); +} + +TEST(GenerateSynthesisCriteriaTest, + HeaderCoverageGoalWithWildcardExcludeYieldsEmptyCriteriaList) { + // Get a packet synthesizer object. + ASSERT_OK_AND_ASSIGN(auto synthesizer, + PacketSynthesizer::Create(GetParams())); + + ASSERT_OK_AND_ASSIGN(auto criteria_list, + GenerateSynthesisCriteriaFor( + ParseProtoOrDie( + R"pb( + coverage_goals { + header_coverage { + headers { patterns: [ "ipv4", "ipv6" ] } + header_exclusions { patterns: [ "*" ] } + } + } + )pb"), + synthesizer->SolverState())); + + ExpectEqualCriteriaList(criteria_list, {}); +} + +TEST(GenerateSynthesisCriteriaTest, + HeaderCoverageGoalWithHeaderExcludeYieldsCorrectCriteriaList) { + // Get a packet synthesizer object. + ASSERT_OK_AND_ASSIGN(auto synthesizer, + PacketSynthesizer::Create(GetParams())); + + ASSERT_OK_AND_ASSIGN(auto criteria_list, + GenerateSynthesisCriteriaFor( + ParseProtoOrDie( + R"pb( + coverage_goals { + header_coverage { + headers { patterns: [ "ipv4", "ipv6" ] } + header_exclusions { patterns: [ "ipv4" ] } + } + } + )pb"), + synthesizer->SolverState())); + + ExpectEqualCriteriaList(criteria_list, + { + ParseProtoOrDie( + R"pb( + input_packet_header_criteria { + field_criteria { + field_match { + name: "ipv6.$valid$" + exact { hex_str: "0x1" } + } + } + } + )pb"), + }); +} + +TEST(GenerateSynthesisCriteriaTest, + HeaderCoverageGoalWithHeaderPreventationYieldCorrectResults) { + // Get a packet synthesizer object. + ASSERT_OK_AND_ASSIGN(auto synthesizer, + PacketSynthesizer::Create(GetParams())); + + ASSERT_OK_AND_ASSIGN( + auto criteria_list, + GenerateSynthesisCriteriaFor( + ParseProtoOrDie( + R"pb( + coverage_goals { + header_coverage { + headers { patterns: [ "ipv4", "vlan" ] } + headers_to_prevent_unless_explicitly_covered { + patterns: [ "vlan" ] + } + } + } + )pb"), + synthesizer->SolverState())); + + ExpectEqualCriteriaList(criteria_list, + { + ParseProtoOrDie( + R"pb( + input_packet_header_criteria { + field_criteria { + field_match { + name: "ipv4.$valid$" + exact { hex_str: "0x1" } + } + } + field_criteria { + field_match { + name: "vlan.$valid$" + exact { hex_str: "0x0" } + } + } + } + )pb"), + ParseProtoOrDie( + R"pb( + input_packet_header_criteria { + field_criteria { + field_match { + name: "vlan.$valid$" + exact { hex_str: "0x1" } + } + } + } + )pb"), + }); +} + +TEST(GenerateSynthesisCriteriaTest, + HeaderCoverageGoalWithIncludeWildcardHeaderYieldCorrectCriteriaList) { + // Get a packet synthesizer object. + ASSERT_OK_AND_ASSIGN(auto synthesizer, + PacketSynthesizer::Create(GetParams())); + + ASSERT_OK_AND_ASSIGN( + auto criteria_list, + GenerateSynthesisCriteriaFor( + ParseProtoOrDie( + R"pb( + coverage_goals { + header_coverage { + headers { patterns: [ "ipv4" ] } + headers_to_prevent_unless_explicitly_covered { + patterns: [ "vlan" ] + } + include_wildcard_header: true + } + } + )pb"), + synthesizer->SolverState())); + + ExpectEqualCriteriaList(criteria_list, + { + ParseProtoOrDie( + R"pb( + input_packet_header_criteria { + field_criteria { + field_match { + name: "vlan.$valid$" + exact { hex_str: "0x0" } + } + } + } + )pb"), + ParseProtoOrDie( + R"pb( + input_packet_header_criteria { + field_criteria { + field_match { + name: "ipv4.$valid$" + exact { hex_str: "0x1" } + } + } + field_criteria { + field_match { + name: "vlan.$valid$" + exact { hex_str: "0x0" } + } + } + } + )pb"), + }); +} + +TEST(GenerateSynthesisCriteriaTest, + CoverageGoalWithHeaderAndEntryAndFateCoverageYieldsCorrectResults) { + // Get a packet synthesizer object. + ASSERT_OK_AND_ASSIGN(auto synthesizer, + PacketSynthesizer::Create(GetParams())); + + ASSERT_OK_AND_ASSIGN( + auto criteria_list, + GenerateSynthesisCriteriaFor( + ParseProtoOrDie( + R"pb( + coverage_goals { + header_coverage { headers { patterns: [ "ipv4", "ipv6" ] } } + packet_fate_coverage { fates: [ DROP, NOT_DROP ] } + entry_coverage { tables { patterns: [ "*" ] } } + } + )pb"), + synthesizer->SolverState())); + + ExpectEqualCriteriaList( + criteria_list, + { + ParseProtoOrDie( + R"pb( + input_packet_header_criteria { + field_criteria { + field_match { + name: "ipv4.$valid$" + exact { hex_str: "0x1" } + } + } + } + output_criteria { drop_expected: true } + table_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + } + table_entry_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + match_index: 0 + } + )pb"), + ParseProtoOrDie( + R"pb( + input_packet_header_criteria { + field_criteria { + field_match { + name: "ipv4.$valid$" + exact { hex_str: "0x1" } + } + } + } + output_criteria { drop_expected: false } + table_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + } + table_entry_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + match_index: 0 + } + )pb"), + ParseProtoOrDie( + R"pb( + input_packet_header_criteria { + field_criteria { + field_match { + name: "ipv6.$valid$" + exact { hex_str: "0x1" } + } + } + } + output_criteria { drop_expected: true } + table_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + } + table_entry_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + match_index: 0 + } + )pb"), + ParseProtoOrDie( + R"pb( + input_packet_header_criteria { + field_criteria { + field_match { + name: "ipv6.$valid$" + exact { hex_str: "0x1" } + } + } + } + output_criteria { drop_expected: false } + table_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + } + table_entry_reachability_criteria { + table_name: "ingress.acl_pre_ingress.acl_pre_ingress_table" + match_index: 0 + } + )pb"), + }); +} + } // namespace } // namespace p4_symbolic::packet_synthesizer From 909edada01c4d232bcec61ce4d6df6a1419fe419 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:36:03 +0000 Subject: [PATCH 18/24] [P4_Symbolic] Adding criteria_generator files to p4_symbolic/packet_synthesizer/ (#723) Co-authored-by: kishanps --- .../packet_synthesizer/criteria_generator.cc | 268 ++++++++++++++++++ .../packet_synthesizer/criteria_generator.h | 36 +++ 2 files changed, 304 insertions(+) create mode 100644 p4_symbolic/packet_synthesizer/criteria_generator.cc create mode 100644 p4_symbolic/packet_synthesizer/criteria_generator.h diff --git a/p4_symbolic/packet_synthesizer/criteria_generator.cc b/p4_symbolic/packet_synthesizer/criteria_generator.cc new file mode 100644 index 00000000..545607e9 --- /dev/null +++ b/p4_symbolic/packet_synthesizer/criteria_generator.cc @@ -0,0 +1,268 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "absl/container/btree_set.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "gutil/status.h" +#include "p4_pdpi/ir.pb.h" +#include "p4_symbolic/packet_synthesizer/coverage_goal.pb.h" +#include "p4_symbolic/packet_synthesizer/packet_synthesis_criteria.h" +#include "p4_symbolic/packet_synthesizer/packet_synthesis_criteria.pb.h" +#include "p4_symbolic/symbolic/symbolic.h" +#include "p4_symbolic/symbolic/table.h" + +namespace p4_symbolic::packet_synthesizer { +namespace { + +// Checks if the given `target` string matches the given string matching +// `pattern`. A string s matches pattern p iff s == p or p == "*" (we can expand +// this in the future if needed). +bool MatchesPattern(absl::string_view target, absl::string_view pattern) { + return pattern == "*" || target == pattern; +} + +// Checks if the given `target` string matches at least one of the given +// `patterns`. +bool MatchesPatterns(absl::string_view target, const Patterns& patterns) { + for (const auto& pattern : patterns.patterns()) { + if (MatchesPattern(target, pattern)) return true; + } + return false; +} + +// Generates a list of packet synthesis criteria for the given entry coverage +// `goal`, consistent with the semantics defined in +// coverage_goal.proto:EntryCoverageGoal. +absl::StatusOr> +GenerateSynthesisCriteriaFor(const EntryCoverageGoal& goal, + const symbolic::SolverState& solver_state) { + // Determine selected tables, sort by name. + absl::btree_set target_tables; + for (const auto& [table_name, table] : solver_state.program.tables()) { + // Exclude BMv2's internal tables. + if (table.table_definition().preamble().id() == 0) { + continue; + } + + // TODO: Check if each pattern matches at least one table (and + // other possible pitfalls). + if (MatchesPatterns(table_name, goal.tables()) && + !MatchesPatterns(table_name, goal.table_exclusions())) { + target_tables.insert(table_name); + } + } + + std::vector criteria_list; + for (const auto& table_name : target_tables) { + PacketSynthesisCriteria criteria; + criteria.mutable_table_reachability_criteria()->set_table_name(table_name); + criteria.mutable_table_entry_reachability_criteria()->set_table_name( + table_name); + + // Add one synthesis criteria with table entry reachability constraint per + // each table entry. + auto it = solver_state.entries.find(table_name); + if (it != solver_state.entries.end()) { + const std::vector& entries = it->second; + for (int i = 0; i < entries.size(); i++) { + criteria.mutable_table_entry_reachability_criteria()->set_match_index( + i); + criteria_list.push_back(criteria); + } + } + + // If configured, add one synthesis criteria covering the table's default + // action. + if (goal.cover_default_actions()) { + criteria.mutable_table_entry_reachability_criteria()->set_match_index( + symbolic::table::kDefaultActionEntryIndex); + criteria_list.push_back(criteria); + } + } + + return criteria_list; +} + +// Generates a list of packet synthesis criteria for the given packet fate +// coverage `goal`, consistent with the semantics defined in +// coverage_goal.proto:PacketFateCoverageGoal. +absl::StatusOr> +GenerateSynthesisCriteriaFor(const PacketFateCoverageGoal& goal, + const symbolic::SolverState& solver_state) { + std::vector criteria_list; + + // Add one synthesis criteria with packet output constraints per each selected + // fate. + for (const auto fate : goal.fates()) { + PacketSynthesisCriteria criteria; + switch (fate) { + case PacketFateCoverageGoal::PacketFate:: + PacketFateCoverageGoal_PacketFate_UNSPECIFIED: + // Add no constraint. + break; + case PacketFateCoverageGoal::PacketFate:: + PacketFateCoverageGoal_PacketFate_DROP: + criteria.mutable_output_criteria()->set_drop_expected(true); + break; + case PacketFateCoverageGoal::PacketFate:: + PacketFateCoverageGoal_PacketFate_NOT_DROP: + criteria.mutable_output_criteria()->set_drop_expected(false); + break; + default: + return absl::UnimplementedError( + absl::StrCat("Unsupported packet fate: ", fate)); + } + criteria_list.push_back(criteria); + } + + return criteria_list; +} + +HeaderCriteria::FieldCriterion MakeHeaderValidityCriterion( + absl::string_view header_name, bool valid) { + HeaderCriteria::FieldCriterion field_criterion; + field_criterion.mutable_field_match()->set_name( + absl::StrCat(header_name, ".$valid$")); + field_criterion.mutable_field_match()->mutable_exact()->set_hex_str( + valid ? "0x1" : "0x0"); + return field_criterion; +} + +// Generates a list of packet synthesis criteria for the given input packet +// header coverage `goal`, consistent with the semantics defined in +// coverage_goal.proto:HeaderCoverageGoal. +absl::StatusOr> +GenerateSynthesisCriteriaFor(const HeaderCoverageGoal& goal, + const symbolic::SolverState& solver_state) { + // Determine selected headers, sort by name. + absl::btree_set target_headers; + absl::btree_set headers_to_prevent_unless_explicitly_covered; + for (const auto& [header_name, _] : solver_state.program.headers()) { + // Exclude metadata headers. + if (absl::StartsWith(header_name, "standard_metadata") || + // The user defined metadata in BMv2 are all part of a header named + // `scalars`. + absl::StartsWith(header_name, "scalars")) { + continue; + } + + if (MatchesPatterns(header_name, goal.headers()) && + !MatchesPatterns(header_name, goal.header_exclusions())) { + target_headers.insert(header_name); + } + + if (MatchesPatterns(header_name, + goal.headers_to_prevent_unless_explicitly_covered())) { + headers_to_prevent_unless_explicitly_covered.insert(header_name); + } + } + + if (goal.include_wildcard_header()) { + // Empty table name means no particular header is targeted (see below). + target_headers.insert(""); + } + + std::vector criteria_list; + + for (const auto& header_name : target_headers) { + // Add constraints to make the targeted header valid. + PacketSynthesisCriteria criteria; + if (!header_name.empty()) { + *criteria.mutable_input_packet_header_criteria()->add_field_criteria() = + MakeHeaderValidityCriterion(header_name, /*valid=*/true); + } + + // Add constraints to make headers in + // `headers_to_prevent_unless_explicitly_covered` (except the currently + // targeted header itself) invalid. + for (const auto& prevented_header_name : + headers_to_prevent_unless_explicitly_covered) { + if (prevented_header_name != header_name) { + *criteria.mutable_input_packet_header_criteria()->add_field_criteria() = + MakeHeaderValidityCriterion(prevented_header_name, /*valid=*/false); + } + } + + criteria_list.push_back(criteria); + } + + return criteria_list; +} + +// Generates a list of packet synthesis criteria for a given (composite) +// coverage `goal`, consistent with the semantics defined in +// coverage_goal.proto:CoverageGoal. +absl::StatusOr> +GenerateSynthesisCriteriaFor(const CoverageGoal& goal, + const symbolic::SolverState& solver_state) { + // Use a wildcard synthesis criteria for any missing goal. + PacketSynthesisCriteria wildcard; + std::vector entry_criteria = {wildcard}; + std::vector fate_criteria = {wildcard}; + std::vector header_criteria = {wildcard}; + + // Generate criteria for individual goals. + if (goal.has_entry_coverage()) { + ASSIGN_OR_RETURN(entry_criteria, GenerateSynthesisCriteriaFor( + goal.entry_coverage(), solver_state)); + } + + if (goal.has_packet_fate_coverage()) { + ASSIGN_OR_RETURN(fate_criteria, + GenerateSynthesisCriteriaFor(goal.packet_fate_coverage(), + solver_state)); + } + + if (goal.has_header_coverage()) { + ASSIGN_OR_RETURN( + header_criteria, + GenerateSynthesisCriteriaFor(goal.header_coverage(), solver_state)); + } + + // Combine criteria across goals. + ASSIGN_OR_RETURN( + auto fate_and_entry_criteria, + MakeCartesianProductConjunction(fate_criteria, entry_criteria)); + + return MakeCartesianProductConjunction(header_criteria, + fate_and_entry_criteria); +} + +} // namespace + +// Generates a list of packet synthesis criteria for the given list of +// (composite) coverage `goals`, consistent with the semantics defined in +// coverage_goal.proto:CoverageGoals. +absl::StatusOr> +GenerateSynthesisCriteriaFor(const CoverageGoals& goals, + const symbolic::SolverState& solver_state) { + std::vector criteria_list; + for (const auto& goal : goals.coverage_goals()) { + ASSIGN_OR_RETURN(auto criteria_for_goal, + GenerateSynthesisCriteriaFor(goal, solver_state)); + criteria_list.insert(criteria_list.end(), criteria_for_goal.begin(), + criteria_for_goal.end()); + } + + return criteria_list; +} + +} // namespace p4_symbolic::packet_synthesizer diff --git a/p4_symbolic/packet_synthesizer/criteria_generator.h b/p4_symbolic/packet_synthesizer/criteria_generator.h new file mode 100644 index 00000000..481d4873 --- /dev/null +++ b/p4_symbolic/packet_synthesizer/criteria_generator.h @@ -0,0 +1,36 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PINS_P4_SYMBOLIC_PACKET_SYNTHESIZER_CRITERIA_GENERATOR_H_ +#define PINS_P4_SYMBOLIC_PACKET_SYNTHESIZER_CRITERIA_GENERATOR_H_ + +#include + +#include "absl/status/statusor.h" +#include "p4_symbolic/packet_synthesizer/coverage_goal.pb.h" +#include "p4_symbolic/packet_synthesizer/packet_synthesis_criteria.pb.h" +#include "p4_symbolic/symbolic/symbolic.h" + +namespace p4_symbolic::packet_synthesizer { + +// Generates a list of packet synthesis criteria for the given list of +// (composite) coverage `goals`, consistent with the semantics defined in +// coverage_goal.proto:CoverageGoals. +absl::StatusOr> +GenerateSynthesisCriteriaFor(const CoverageGoals& goals, + const symbolic::SolverState& solver_state); + +} // namespace p4_symbolic::packet_synthesizer + +#endif // PINS_P4_SYMBOLIC_PACKET_SYNTHESIZER_CRITERIA_GENERATOR_H_ From 9c2540bd943c7ee2fb559716877ddfa95413191d Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:36:32 +0000 Subject: [PATCH 19/24] [P4_Symbolic] Adding p4_symbolic/packet_synthesizer/coverage_goal.proto (#724) Co-authored-by: kishanps --- .../packet_synthesizer/coverage_goal.proto | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 p4_symbolic/packet_synthesizer/coverage_goal.proto diff --git a/p4_symbolic/packet_synthesizer/coverage_goal.proto b/p4_symbolic/packet_synthesizer/coverage_goal.proto new file mode 100644 index 00000000..c180c6ce --- /dev/null +++ b/p4_symbolic/packet_synthesizer/coverage_goal.proto @@ -0,0 +1,113 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package p4_symbolic.packet_synthesizer; + +// A list of patterns for string matching. +// A string s matches pattern p iff s == p or p == "*" (we can expand this in +// the future if needed). +// A string s matches Patterns ps, if it matches at least one of patterns in it. +message Patterns { + repeated string patterns = 1; +} + +// A goal corresponding to generating various input packets hitting each +// individual entry in a select set of tables in the target P4 program. +// Roughly results in [EntryCoverageCriteria(e) for e in t.entries +// for t in selected tables]. See the parameters for more precise semantics. +message EntryCoverageGoal { + // String matching patterns for the fully qualified name of tables to be + // included. + Patterns tables = 1; + // String matching patterns for the fully qualified name of tables to be + // excluded. If a table name matches `table_exclusions` the table will be + // excluded even if included in `tables. + Patterns table_exclusions = 2; + // Whether to generate a packet covering the default action of the table. + bool cover_default_actions = 3; +} + +// A goal corresponding to generating various input packets each containing a +// header (more precisely, resulting in the corresponding P4 header being valid +// after parsing the input packets) from a select set of headers in the target +// P4 program. +// Roughly results in [InputPacketHeaderValidityCriteria(h) for h +// in selected headers]. See the parameters for more precise semantics. +message HeaderCoverageGoal { + // String matching patterns for the P4 name of the headers to be included. + Patterns headers = 1; + // String matching patterns for the P4 name of the headers to be excluded. If + // a header names matches `header_exclusions` it gets excluded even if it + // matches `headers`. + Patterns header_exclusions = 2; + // If a header named "h" is matched by the following patterns, all synthesis + // criteria generated for this goal for other headers (not "h" itself) get + // constrained to prevent header "h" from being valid. + // For example, if we have "ipv4", "ipv6", and "vlan" among selected headers + // and "vlan" matches `headers_to_prevent_unless_explicitly_covered`, the + // criteria generated for this goal are: + // Criteria 1: Input packet must contain "ipv4" and NOT contain "vlan". + // Criteria 2: Input packet must contain "ipv6" and NOT contain "vlan". + // Criteria 3: Input packet must contain "vlan". + // This feature is particularly useful for things such as VLAN header. We + // generally prefer our generated test packets to not include a VLAN header + // unless we explicitly want to generate test packets that contain the VLAN + // header. + Patterns headers_to_prevent_unless_explicitly_covered = 3; + // If true, the result will include an additional synthesis criteria with no + // particular header targeted (the headers in + // `headers_to_prevent_unless_explicitly_covered` are still prevented). + bool include_wildcard_header = 4; +} + +// A goal corresponding to generating various input packets that exercise +// certain output behavior. +// Results in [(OutputCriteria(f) f in selected fates]. +message PacketFateCoverageGoal { + enum PacketFate { + // No constraints on the packet fate. + UNSPECIFIED = 0; + // The input packet must be marked to drop. + // Note: In the current implementation of SAI-P4, a punted packet may also + // be marked to be dropped (e.g. in trap). + DROP = 1; + // The input packet must not be marked to drop. + NOT_DROP = 2; + } + + // Packet fates to be exercised. + repeated PacketFate fates = 1; +} + +// A composite coverage goal. +// Results in [GenerateCriteria(header_goal) x GenerateCriteria(entry_goal) x +// GenerateCriteria(fate_goal)] where `x` corresponds to cartesian product +// conjunction of lists of synthesis criteria (see packet_synthesis_criteria.h). +// If a certain field is missing, a list containing only a single synthesis +// criteria with no constraints (i.e. [WildcardCriteria()]) is used as its +// criteria list. +message CoverageGoal { + HeaderCoverageGoal header_coverage = 2; + PacketFateCoverageGoal packet_fate_coverage = 3; + EntryCoverageGoal entry_coverage = 1; +} + +// A list of coverage goals. +// GenerateCriteria([g1, g2, ...]) = GenerateCriteria(g1) + GenerateCriteria(g2) +// + ... +message CoverageGoals { + repeated CoverageGoal coverage_goals = 1; +} From 55383ad2696f93860dfa1e963fb7967eb6d1cc82 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:37:08 +0000 Subject: [PATCH 20/24] [Comb] Add helper functions for creating text protos (#725) Co-authored-by: kishanps --- p4_symbolic/bmv2/BUILD.bazel | 1 + p4_symbolic/bmv2/test.cc | 6 +- p4_symbolic/ir/BUILD.bazel | 1 + p4_symbolic/ir/test.cc | 7 +- ...h_test_setup_helpers_golden_test_runner.cc | 654 ++++++++++++++++++ 5 files changed, 666 insertions(+), 3 deletions(-) create mode 100644 tests/lib/switch_test_setup_helpers_golden_test_runner.cc diff --git a/p4_symbolic/bmv2/BUILD.bazel b/p4_symbolic/bmv2/BUILD.bazel index 97d00524..d91a192b 100644 --- a/p4_symbolic/bmv2/BUILD.bazel +++ b/p4_symbolic/bmv2/BUILD.bazel @@ -56,6 +56,7 @@ cc_binary( deps = [ ":bmv2", "//gutil:io", + "//gutil:proto", "//gutil:status", "@com_google_absl//absl/flags:flag", "@com_google_absl//absl/flags:parse", diff --git a/p4_symbolic/bmv2/test.cc b/p4_symbolic/bmv2/test.cc index 0de25489..2764747a 100644 --- a/p4_symbolic/bmv2/test.cc +++ b/p4_symbolic/bmv2/test.cc @@ -31,6 +31,7 @@ #include "google/protobuf/text_format.h" #include "google/protobuf/util/json_util.h" #include "gutil/io.h" +#include "gutil/proto.h" #include "gutil/status.h" #include "p4_symbolic/bmv2/bmv2.h" @@ -41,6 +42,8 @@ ABSL_FLAG(std::string, json, "", "The path to the output json file (required)"); namespace { +using ::gutil::PrintTextProto; + absl::Status Test() { const std::string &bmv2_path = absl::GetFlag(FLAGS_bmv2); const std::string &protobuf_path = absl::GetFlag(FLAGS_protobuf); @@ -55,7 +58,8 @@ absl::Status Test() { p4_symbolic::bmv2::ParseBmv2JsonFile(bmv2_path.c_str())); // Dumping protobuf. - RETURN_IF_ERROR(gutil::WriteFile(bmv2.DebugString(), protobuf_path.c_str())); + RETURN_IF_ERROR( + gutil::WriteFile(PrintTextProto(bmv2), protobuf_path.c_str())); // Dumping JSON. google::protobuf::util::JsonPrintOptions dumping_options; diff --git a/p4_symbolic/ir/BUILD.bazel b/p4_symbolic/ir/BUILD.bazel index 21875e54..0bafd3ff 100644 --- a/p4_symbolic/ir/BUILD.bazel +++ b/p4_symbolic/ir/BUILD.bazel @@ -135,6 +135,7 @@ cc_binary( "test.cc", ], deps = [ + "//gutil:proto", "//gutil:status", "//p4_symbolic:parser", "@com_google_absl//absl/flags:flag", diff --git a/p4_symbolic/ir/test.cc b/p4_symbolic/ir/test.cc index c092d55d..41361873 100644 --- a/p4_symbolic/ir/test.cc +++ b/p4_symbolic/ir/test.cc @@ -22,6 +22,7 @@ #include "absl/flags/usage.h" #include "absl/status/status.h" #include "absl/strings/str_format.h" +#include "gutil/proto.h" #include "gutil/status.h" #include "p4_symbolic/parser.h" @@ -35,6 +36,8 @@ ABSL_FLAG(std::string, entries, "", namespace { +using ::gutil::PrintTextProto; + absl::Status Test() { const std::string &p4info_path = absl::GetFlag(FLAGS_p4info); const std::string &bmv2_path = absl::GetFlag(FLAGS_bmv2); @@ -49,12 +52,12 @@ absl::Status Test() { p4_symbolic::ParseToIr(bmv2_path, p4info_path, entries_path)); // Dump string representation to stdout. - std::cout << dataplane.program.DebugString() << std::endl; + std::cout << PrintTextProto(dataplane.program) << std::endl; for (const auto &[name, entries] : dataplane.entries) { std::cout << "=====" << name << " Entries=====" << std::endl; std::cout << std::endl; for (const pdpi::IrTableEntry &entry : entries) { - std::cout << entry.DebugString() << std::endl; + std::cout << PrintTextProto(entry) << std::endl; } } diff --git a/tests/lib/switch_test_setup_helpers_golden_test_runner.cc b/tests/lib/switch_test_setup_helpers_golden_test_runner.cc new file mode 100644 index 00000000..2c32e80d --- /dev/null +++ b/tests/lib/switch_test_setup_helpers_golden_test_runner.cc @@ -0,0 +1,654 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" +#include "google/protobuf/util/message_differencer.h" +#include "gutil/proto.h" +#include "gutil/status_matchers.h" +#include "gutil/testing.h" +#include "p4_pdpi/ir.pb.h" +#include "sai_p4/instantiations/google/instantiations.h" +#include "sai_p4/instantiations/google/sai_p4info.h" +#include "tests/lib/switch_test_setup_helpers.h" + +namespace { + +using ::gutil::PrintTextProto; + +constexpr char kBanner[] = + "==========================================================================" + "======\n"; +constexpr char kInputHeader[] = + "-- ORIGINAL ENTRY --------------------------------------------------------" + "------\n"; +constexpr char kOutputHeader[] = + "-- MODIFICATIONS ---------------------------------------------------------" + "------\n"; + +std::string TestHeader(absl::string_view test_name, + absl::Span ports) { + return absl::StrCat(kBanner, test_name, "\n", + "Available ports: ", absl::StrJoin(ports, ","), "\n", + kBanner); +} + +void CompareTableEntry(const pdpi::IrTableEntry& original_entry, + const pdpi::IrTableEntry& rewritten_entry) { + std::cout << kInputHeader; + std::cout << PrintTextProto(original_entry); + std::string diff; + google::protobuf::util::MessageDifferencer differencer; + differencer.ReportDifferencesToString(&diff); + if (differencer.Compare(original_entry, rewritten_entry)) { + std::cout << kOutputHeader << "NONE\n\n"; + } else { + std::cout << kOutputHeader << diff << "\n"; + } +} + +void CompareTableEntries( + absl::Span original_entries, + absl::Span rewritten_entries) { + CHECK_EQ(rewritten_entries.size(), original_entries.size()); // Crash OK + for (int i = 0; i < rewritten_entries.size(); ++i) { + CompareTableEntry(original_entries[i], rewritten_entries[i]); + } +} + +struct PortRewriterTestCase { + const pdpi::IrP4Info& info; + std::string test_name; + std::vector new_ports_available; + std::vector original_entries; +}; + +void RunPortRewriterTest(const PortRewriterTestCase& test_case) { + std::vector entries_to_rewrite = + test_case.original_entries; + ASSERT_OK(pins_test::RewritePortsInTableEntries( + test_case.info, test_case.new_ports_available, entries_to_rewrite)); + std::cout << TestHeader(test_case.test_name, test_case.new_ports_available); + CompareTableEntries(test_case.original_entries, entries_to_rewrite); +} + +std::vector TableEntriesFromProtos( + absl::Span protos) { + std::vector entries; + + for (const auto& proto : protos) { + entries.push_back(gutil::ParseProtoOrDie(proto)); + } + + return entries; +} + +} // namespace + +int main(int argc, char** argv) { + std::vector test_cases; + + test_cases.push_back(PortRewriterTestCase{ + .info = sai::GetIrP4Info(sai::Instantiation::kFabricBorderRouter), + .test_name = "1,2,some_port -> a_port", + .new_ports_available = {"a_port"}, + .original_entries = TableEntriesFromProtos({ + R"pb( + table_name: "router_interface_table" + matches { + name: "router_interface_id" + exact { str: "router-interface-1" } + } + action { + name: "set_port_and_src_mac" + params { + name: "port" + value { str: "1" } + } + params { + name: "src_mac" + value { mac: "00:02:03:04:05:06" } + } + } + )pb", + R"pb( + table_name: "wcmp_group_table" + matches { + name: "wcmp_group_id" + exact { str: "group-4294934545" } + } + action_set { + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nexthop-1" } + } + } + weight: 1 + watch_port: "2" + } + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nexthop-2" } + } + } + weight: 1 + watch_port: "some_port" + } + } + )pb", + }), + }); + + test_cases.push_back(PortRewriterTestCase{ + .info = sai::GetIrP4Info(sai::Instantiation::kFabricBorderRouter), + .test_name = "1 -> Exactly one of {2,3}", + .new_ports_available = {"2", "3"}, + .original_entries = TableEntriesFromProtos({ + R"pb( + table_name: "router_interface_table" + matches { + name: "router_interface_id" + exact { str: "router-interface-1" } + } + action { + name: "set_port_and_src_mac" + params { + name: "port" + value { str: "1" } + } + params { + name: "src_mac" + value { mac: "00:02:03:04:05:06" } + } + } + )pb", + R"pb( + table_name: "wcmp_group_table" + matches { + name: "wcmp_group_id" + exact { str: "group-4294934545" } + } + action_set { + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nexthop-1" } + } + } + weight: 1 + watch_port: "1" + } + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nexthop-2" } + } + } + weight: 1 + watch_port: "1" + } + } + )pb", + }), + }); + + test_cases.push_back(PortRewriterTestCase{ + .info = sai::GetIrP4Info(sai::Instantiation::kFabricBorderRouter), + .test_name = "Unchanged due to only existing ports", + .new_ports_available = {"2", "3"}, + .original_entries = TableEntriesFromProtos({ + R"pb( + table_name: "router_interface_table" + matches { + name: "router_interface_id" + exact { str: "router-interface-1" } + } + action { + name: "set_port_and_src_mac" + params { + name: "port" + value { str: "2" } + } + params { + name: "src_mac" + value { mac: "00:02:03:04:05:06" } + } + } + )pb", + R"pb( + table_name: "wcmp_group_table" + matches { + name: "wcmp_group_id" + exact { str: "group-4294934545" } + } + action_set { + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nexthop-1" } + } + } + weight: 1 + watch_port: "2" + } + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nexthop-2" } + } + } + weight: 1 + watch_port: "3" + } + } + )pb", + }), + }); + + test_cases.push_back(PortRewriterTestCase{ + .info = sai::GetIrP4Info(sai::Instantiation::kFabricBorderRouter), + .test_name = "All unchanged due to no ports", + .new_ports_available = {"1"}, + .original_entries = TableEntriesFromProtos({ + R"pb( + table_name: "neighbor_table" + matches { + name: "router_interface_id" + exact { str: "router-interface-1" } + } + matches { + name: "neighbor_id" + exact { str: "fe80::3e28:6dff:fe34:c002" } + } + action { + name: "set_dst_mac" + params { + name: "dst_mac" + value { mac: "3c:28:6d:34:c0:02" } + } + } + )pb", + R"pb( + table_name: "nexthop_table" + matches { + name: "nexthop_id" + exact { str: "nexthop-1" } + } + action { + name: "set_nexthop" + params { + name: "router_interface_id" + value { str: "router-interface-1" } + } + params { + name: "neighbor_id" + value { str: "fe80::3e28:6dff:fe34:c002" } + } + } + )pb", + R"pb( + table_name: "ipv4_table" + matches { + name: "vrf_id" + exact { str: "vrf-80" } + } + action { + name: "set_wcmp_group_id" + params { + name: "wcmp_group_id" + value { str: "group-4294934531" } + } + } + )pb", + R"pb( + table_name: "ipv6_table" + matches { + name: "vrf_id" + exact { str: "vrf-80" } + } + action { + name: "set_wcmp_group_id" + params { + name: "wcmp_group_id" + value { str: "group-4294934531" } + } + } + )pb", + R"pb( + table_name: "vrf_table" + matches { + name: "vrf_id" + exact { str: "vrf-80" } + } + action { name: "no_action" } + )pb", + }), + }); + + test_cases.push_back(PortRewriterTestCase{ + .info = sai::GetIrP4Info(sai::Instantiation::kFabricBorderRouter), + .test_name = + "Roughly even number of 1, 2, and 'a_port', with existing 'a_port' " + "and port-less entries unchanged", + .new_ports_available = {"1", "2", "a_port"}, + .original_entries = TableEntriesFromProtos({ + R"pb( + table_name: "neighbor_table" + matches { + name: "router_interface_id" + exact { str: "router-interface-1" } + } + matches { + name: "neighbor_id" + exact { str: "fe80::3e28:6dff:fe34:c002" } + } + action { + name: "set_dst_mac" + params { + name: "dst_mac" + value { mac: "3c:28:6d:34:c0:02" } + } + } + )pb", + R"pb( + table_name: "router_interface_table" + matches { + name: "router_interface_id" + exact { str: "router-interface-1" } + } + action { + name: "set_port_and_src_mac" + params { + name: "port" + value { str: "a_port" } + } + params { + name: "src_mac" + value { mac: "00:02:03:04:05:06" } + } + } + )pb", + R"pb( + table_name: "wcmp_group_table" + matches { + name: "wcmp_group_id" + exact { str: "group-4294934545" } + } + action_set { + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nexthop-1" } + } + } + weight: 1 + watch_port: "4" + } + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nexthop-2" } + } + } + weight: 1 + watch_port: "5" + } + } + )pb", + R"pb( + table_name: "mirror_session_table" + matches { + name: "mirror_session_id" + exact { str: "mirror-session-201326593" } + } + action { + name: "mirror_as_ipv4_erspan" + params { + name: "port" + value { str: "6" } + } + params { + name: "src_ip" + value { ipv4: "10.206.196.0" } + } + params { + name: "dst_ip" + value { ipv4: "172.20.0.202" } + } + params { + name: "src_mac" + value { mac: "00:02:03:04:05:06" } + } + params { + name: "dst_mac" + value { mac: "00:1a:11:17:5f:80" } + } + params { + name: "ttl" + value { hex_str: "0x40" } + } + params { + name: "tos" + value { hex_str: "0x00" } + } + } + )pb", + R"pb( + table_name: "l3_admit_table" + matches { + name: "dst_mac" + ternary { + value { mac: "02:32:0a:ce:c4:04" } + mask { mac: "ff:ff:ff:ff:ff:ff" } + } + } + matches { + name: "in_port" + optional { value { str: "7" } } + } + priority: 2030 + action { name: "admit_to_l3" } + )pb", + R"pb( + table_name: "acl_ingress_table" + matches { + name: "is_ipv4" + optional { value { hex_str: "0x1" } } + } + matches { + name: "ip_protocol" + ternary { + value { hex_str: "0x11" } + mask { hex_str: "0xff" } + } + } + matches { + name: "l4_dst_port" + ternary { + value { hex_str: "0x0223" } + mask { hex_str: "0xffff" } + } + } + matches { + name: "in_port" + optional { value { str: "8" } } + } + priority: 3100 + action { name: "acl_drop" } + )pb", + R"pb( + table_name: "acl_pre_ingress_table" + matches { + name: "in_port" + optional { value { str: "9" } } + } + priority: 1151 + action { + name: "set_vrf" + params { + name: "vrf_id" + value { str: "vrf-210" } + } + } + )pb", + R"pb( + table_name: "acl_egress_table" + matches { + name: "ether_type" + ternary { + value { hex_str: "0x0800" } + mask { hex_str: "" } + } + } + matches { + name: "ip_protocol" + ternary { + value { hex_str: "0x11" } + mask { hex_str: "0xff" } + } + } + matches { + name: "l4_dst_port" + ternary { + value { hex_str: "0x0223" } + mask { hex_str: "0xffff" } + } + } + matches { + name: "out_port" + optional { value { str: "10" } } + } + priority: 3101 + action { name: "acl_drop" } + )pb", + R"pb( + table_name: "wcmp_group_table" + matches { + name: "wcmp_group_id" + exact { str: "group-4294934545" } + } + action_set { + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nexthop-1" } + } + } + weight: 1 + watch_port: "11" + } + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nexthop-2" } + } + } + weight: 1 + watch_port: "12" + } + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nexthop-2" } + } + } + weight: 1 + watch_port: "13" + } + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nexthop-2" } + } + } + weight: 1 + watch_port: "14" + } + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nexthop-2" } + } + } + weight: 1 + watch_port: "15" + } + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nexthop-2" } + } + } + weight: 1 + watch_port: "16" + } + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nexthop-2" } + } + } + weight: 1 + watch_port: "17" + } + } + )pb", + }), + }); + + for (const PortRewriterTestCase& test_case : test_cases) { + RunPortRewriterTest(test_case); + } + + return 0; +} // NOLINT(readability/fn_size) From 35733545abde375c21288b41ba9d5ce6bb63f0ee Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:37:51 +0000 Subject: [PATCH 21/24] [Comb] Add missing license comment to files under p4_symbolic/ lib/ and gutil/ (#726) Co-authored-by: kishanps Co-authored-by: rhalstea --- p4_symbolic/sai/deparser.cc | 13 +++++++++++++ p4_symbolic/sai/deparser.h | 13 +++++++++++++ p4_symbolic/sai/deparser_test.cc | 13 +++++++++++++ p4_symbolic/tests/sai_p4_component_test.cc | 13 +++++++++++++ p4_symbolic/z3_util.cc | 13 +++++++++++++ p4_symbolic/z3_util.h | 13 +++++++++++++ 6 files changed, 78 insertions(+) diff --git a/p4_symbolic/sai/deparser.cc b/p4_symbolic/sai/deparser.cc index e2a1d422..2ebcc158 100644 --- a/p4_symbolic/sai/deparser.cc +++ b/p4_symbolic/sai/deparser.cc @@ -1,3 +1,16 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. #include "p4_symbolic/sai/deparser.h" #include diff --git a/p4_symbolic/sai/deparser.h b/p4_symbolic/sai/deparser.h index 2870923b..ee2ee89c 100644 --- a/p4_symbolic/sai/deparser.h +++ b/p4_symbolic/sai/deparser.h @@ -1,3 +1,16 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. #ifndef PINS_P4_SYMBOLIC_SAI_DEPARSER_H_ #define PINS_P4_SYMBOLIC_SAI_DEPARSER_H_ diff --git a/p4_symbolic/sai/deparser_test.cc b/p4_symbolic/sai/deparser_test.cc index 17f4fc2a..09276a13 100644 --- a/p4_symbolic/sai/deparser_test.cc +++ b/p4_symbolic/sai/deparser_test.cc @@ -1,3 +1,16 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. #include "p4_symbolic/sai/deparser.h" #include diff --git a/p4_symbolic/tests/sai_p4_component_test.cc b/p4_symbolic/tests/sai_p4_component_test.cc index 4e973433..705468c8 100644 --- a/p4_symbolic/tests/sai_p4_component_test.cc +++ b/p4_symbolic/tests/sai_p4_component_test.cc @@ -1,3 +1,16 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. #include #include "absl/strings/string_view.h" diff --git a/p4_symbolic/z3_util.cc b/p4_symbolic/z3_util.cc index cccb0174..a09186ca 100644 --- a/p4_symbolic/z3_util.cc +++ b/p4_symbolic/z3_util.cc @@ -1,3 +1,16 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. #include "p4_symbolic/z3_util.h" #include "absl/status/statusor.h" diff --git a/p4_symbolic/z3_util.h b/p4_symbolic/z3_util.h index 59a8d1c9..f516f81f 100644 --- a/p4_symbolic/z3_util.h +++ b/p4_symbolic/z3_util.h @@ -1,3 +1,16 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. #ifndef PINS_P4_SYMBOLIC_Z3_UTIL_H_ #define PINS_P4_SYMBOLIC_Z3_UTIL_H_ From 1f632cc464f50f5a71c71a1a4c9318127ecbf7e9 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:38:45 +0000 Subject: [PATCH 22/24] [Dvaas]: Added test library that allows to test user-provided test vectors. (#727) Co-authored-by: kishanps --- dvaas/BUILD.bazel | 12 ++ dvaas/test_vector.cc | 387 ++------------------------------------ dvaas/test_vector.h | 43 ++--- dvaas/test_vector_test.cc | 23 +++ 4 files changed, 66 insertions(+), 399 deletions(-) create mode 100644 dvaas/test_vector_test.cc diff --git a/dvaas/BUILD.bazel b/dvaas/BUILD.bazel index 15f7b43f..c38cc3f0 100644 --- a/dvaas/BUILD.bazel +++ b/dvaas/BUILD.bazel @@ -244,3 +244,15 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + + +cc_test( + name = "test_vector_test", + srcs = ["test_vector_test.cc"], + deps = [ + ":test_vector", + "//gutil:status_matchers", + "//p4_pdpi/packetlib:packetlib_cc_proto", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/dvaas/test_vector.cc b/dvaas/test_vector.cc index 4116b1e9..3f02e55f 100644 --- a/dvaas/test_vector.cc +++ b/dvaas/test_vector.cc @@ -14,392 +14,43 @@ #include "dvaas/test_vector.h" -#include #include -#include -#include -#include +#include -#include "absl/container/flat_hash_set.h" -#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/str_join.h" -#include "absl/strings/str_replace.h" -#include "absl/strings/string_view.h" -#include "absl/strings/strip.h" -#include "absl/strings/substitute.h" -#include "absl/types/optional.h" -#include "absl/types/span.h" -#include "dvaas/test_vector.pb.h" -#include "gmock/gmock.h" -#include "google/protobuf/descriptor.h" -#include "google/protobuf/util/message_differencer.h" -#include "gtest/gtest.h" #include "gutil/proto.h" #include "gutil/status.h" -#include "p4_pdpi/ir.pb.h" #include "p4_pdpi/packetlib/packetlib.pb.h" #include "re2/re2.h" namespace dvaas { -namespace { -using ::google::protobuf::util::MessageDifferencer; -using ::gutil::PrintTextProto; -using ::testing::MatchResultListener; +// All test packets (incl. input & output packets) must have payloads that +// contain an ID that matches this regular expression. This ID must be: +// * Uniform across all packets within a packet test vector. +// * Unique across different packet test vectors. +inline constexpr LazyRE2 kTestPacketIdRegexp{R"(test packet #([0-9]+))"}; -// -- Detailed comparison of actual vs expected `SwitchOutput`s ---------------- - -bool PacketLessThan(const Packet* a, const Packet* b) { - return a->hex() < b->hex(); -} - -bool PacketInLessThan(const PacketIn* a, const PacketIn* b) { - return a->hex() < b->hex(); -} - -// Returns a copy of the given `string` with all newlines indented by -// (an additional) `indentation` number of spaces. Empty lines are not indented. -std::string Indent(int indentation, absl::string_view string) { - // Avoid indenting empty lines: remove here, then add back at the end. - bool stripped_trailing_newline = absl::ConsumeSuffix(&string, "\n"); - std::string result = absl::StrReplaceAll( - string, { - { - "\n", - absl::StrCat("\n", std::string(indentation, ' ')), - }, - }); - if (stripped_trailing_newline) absl::StrAppend(&result, "\n"); - return result; -} - -std::optional GetPacketInMetadataByName( - const PacketIn& packet_in, absl::string_view target) { - for (const auto& metadata : packet_in.metadata()) { - if (metadata.name() == target) return metadata; - } - return std::nullopt; +std::string MakeTestPacketTagFromUniqueId(int unique_test_packet_id) { + return absl::StrCat("test packet #", unique_test_packet_id); } -bool CompareSwitchOutputs( - SwitchOutput actual_output, SwitchOutput expected_output, - const absl::flat_hash_set& ignored_metadata, - const std::vector& ignored_fields, - MatchResultListener* listener) { - if (actual_output.packets_size() != expected_output.packets_size()) { - *listener << "has mismatched number of packets (actual: " - << actual_output.packets_size() - << " expected: " << expected_output.packets_size() << ")\n"; - return false; - } - - if (actual_output.packet_ins_size() != expected_output.packet_ins_size()) { - *listener << "has mismatched number of packet ins (actual: " - << actual_output.packet_ins_size() - << " expected: " << expected_output.packet_ins_size() << ")\n"; - return false; - } - - std::sort(actual_output.mutable_packets()->pointer_begin(), - actual_output.mutable_packets()->pointer_end(), PacketLessThan); - std::sort(expected_output.mutable_packets()->pointer_begin(), - expected_output.mutable_packets()->pointer_end(), PacketLessThan); - std::sort(actual_output.mutable_packet_ins()->pointer_begin(), - actual_output.mutable_packet_ins()->pointer_end(), - PacketInLessThan); - std::sort(expected_output.mutable_packet_ins()->pointer_begin(), - expected_output.mutable_packet_ins()->pointer_end(), - PacketInLessThan); - - for (int i = 0; i < expected_output.packets_size(); ++i) { - const Packet& actual_packet = actual_output.packets(i); - const Packet& expected_packet = expected_output.packets(i); - MessageDifferencer differ; - for (auto* field : ignored_fields) differ.IgnoreField(field); - std::string diff; - differ.ReportDifferencesToString(&diff); - if (!differ.Compare(expected_packet.parsed(), actual_packet.parsed())) { - *listener << "has packet " << i << " with mismatched header fields:\n " - << Indent(2, diff); - return false; - } - } - - for (int i = 0; i < expected_output.packet_ins_size(); ++i) { - const PacketIn& actual_packet_in = actual_output.packet_ins(i); - const PacketIn& expected_packet_in = expected_output.packet_ins(i); - - MessageDifferencer differ; - for (auto* field : ignored_fields) differ.IgnoreField(field); - std::string diff; - differ.ReportDifferencesToString(&diff); - if (!differ.Compare(expected_packet_in.parsed(), - actual_packet_in.parsed())) { - *listener << "has packet in " << i - << " with mismatched header fields:\n " << Indent(2, diff); - return false; - } - - // Check that received packet in has desired metadata (except for ignored - // metadata). - for (const auto& expected_metadata : expected_packet_in.metadata()) { - if (ignored_metadata.contains(expected_metadata.name())) continue; - - std::optional actual_metadata = - GetPacketInMetadataByName(actual_packet_in, expected_metadata.name()); - if (!actual_metadata.has_value()) { - *listener << "has packet in " << i << " with missing metadata field '" - << expected_metadata.name() << "':\n " << Indent(2, diff); - return false; - } - - if (!differ.Compare(expected_metadata.value(), - actual_metadata->value())) { - *listener << "has packet in " << i - << " with mismatched value for metadata field '" - << expected_metadata.name() << "':\n " << Indent(2, diff); - return false; - } - } - - // Check that received packet in does not have additional metadata (except - // for ignored metadata). - for (const auto& actual_metadata : actual_packet_in.metadata()) { - if (ignored_metadata.contains(actual_metadata.name())) continue; - - std::optional expected_metadata = - GetPacketInMetadataByName(expected_packet_in, actual_metadata.name()); - if (!expected_metadata.has_value()) { - *listener << "has packet in " << i - << " with additional metadata field '" - << actual_metadata.name() << "':\n " << Indent(2, diff); - return false; - } - } - } - - *listener << "matches\n"; - return true; -} - -// Compares the `actual_output` to the `acceptable_outputs` in the given -// `packet_test_vector`, returning `absl::nullopt` if the actual output -// is acceptable, or an explanation of why it is not otherwise. -absl::optional CompareSwitchOutputs( - const PacketTestVector packet_test_vector, - const SwitchOutput& actual_output, - const absl::flat_hash_set& ignored_metadata, - const std::vector& - ignored_fields) { - testing::StringMatchResultListener listener; - for (int i = 0; i < packet_test_vector.acceptable_outputs_size(); ++i) { - const SwitchOutput& expected_output = - packet_test_vector.acceptable_outputs(i); - listener << "- acceptable output #" << (i + 1) << " "; - if (CompareSwitchOutputs(actual_output, expected_output, ignored_metadata, - ignored_fields, &listener)) { - return absl::nullopt; - } - } - return listener.str(); -} - -// -- Simplified switch output characterizations ------------------------------- - -// Characterization of a `SwitchOutput` in terms of two key metrics: how many -// packets got forwarded and how many got punted? -// The purpose of this struct is to give a compact summary of a switch output -// that is easy to understand. This is useful in error messages, because actual -// `SwitchOutput`s are complex (they specify packet header fields and payloads, -// for example) and so dumping them directly is overwhelming. -struct SwitchOutputCharacterization { - int num_forwarded; - int num_punted; - - // https://abseil.io/docs/cpp/guides/hash#tldr-how-do-i-make-my-type-hashable - template - friend H AbslHashValue(H h, const SwitchOutputCharacterization& x) { - return H::combine(std::move(h), x.num_forwarded, x.num_punted); - } -}; - -bool operator==(const SwitchOutputCharacterization& x, - const SwitchOutputCharacterization& y) { - return x.num_forwarded == y.num_forwarded && x.num_punted == y.num_punted; -} - -// Returns a simple characterization of the given `output`. -SwitchOutputCharacterization CharacterizeSwitchOutput( - const SwitchOutput& output) { - return SwitchOutputCharacterization{ - .num_forwarded = output.packets_size(), - .num_punted = output.packet_ins_size(), - }; -} - -// Returns a human-readable description of the given `output`, for use in error -// messages. -std::string DescribeSwitchOutput(const SwitchOutputCharacterization& output) { - const bool forwarded = output.num_forwarded > 0; - const bool punted = output.num_punted > 0; - if (forwarded && punted) - return absl::Substitute("forwarded ($0 copies) and punted ($1 copies)", - output.num_forwarded, output.num_punted); - if (forwarded && !punted) - return absl::Substitute("forwarded ($0 copies)", output.num_forwarded); - if (!forwarded && punted) - return absl::Substitute("punted ($0 copies)", output.num_punted); - return "dropped"; -} - -// Returns a human-readable description of the expectation encoded by the given -// `acceptable_output_characterizations`, for use in error messages. -std::string DescribeExpectation( - const SwitchInput& input, - const absl::flat_hash_set& - acceptable_output_characterizations) { - // This should never happen, but it is convenient for this function to be pure - // and so we handle the case gracefully and without erroring. - if (acceptable_output_characterizations.empty()) - return "false (will always fail)"; - // In practice, while there are often multiple acceptable outputs - // (e.g., due to WCMP), all of them tend to have the same *output - // characterization*. So this function is optimized for the case - // `acceptable_output_characterizations.size() == 1` and doesn't try to be - // clever otherwise. - return absl::StrJoin(acceptable_output_characterizations, ", or ", - [&](std::string* output, auto& acceptable_output) { - absl::StrAppend( - output, SwitchInput::Type_Name(input.type()), - " packet gets ", - DescribeSwitchOutput(acceptable_output)); - }); -} - -// Returns a human-readable description of the given `actual_output`, for use in -// error messages. -std::string DescribeActual(const SwitchInput& input, - const SwitchOutputCharacterization& actual_output) { - return absl::StrCat(SwitchInput::Type_Name(input.type()), " packet got ", - DescribeSwitchOutput(actual_output)); -} - -// Returns whether the packet with the given `characterization` got dropped. -bool IsCharacterizedAsDrop( - const SwitchOutputCharacterization& characterization) { - return characterization.num_forwarded == 0 && - characterization.num_punted == 0; -} - -// Returns whether the packet with the given possible `characterizations` -// surely (according to all characterizations) got dropped. -bool IsCharacterizedAsDrop( - const absl::flat_hash_set& - characterizations) { - return characterizations.size() == 1 && - IsCharacterizedAsDrop(*characterizations.cbegin()); -} - -static constexpr absl::string_view kInputBanner = - "== INPUT " - "======================================================================="; -static constexpr absl::string_view kActualBanner = - "== ACTUAL OUTPUT " - "==============================================================="; -static constexpr absl::string_view kExpectationBanner = - "== EXPECTED OUTPUT " - "============================================================="; - -} // namespace - absl::StatusOr ExtractTestPacketTag(const packetlib::Packet& packet) { - // Regexp to extract the ID from a test packet's payload. - constexpr LazyRE2 kTestPacketIdRegexp{R"(test packet #([0-9]+):)"}; int tag; - if (RE2::PartialMatch(packet.payload(), *kTestPacketIdRegexp, &tag)) - return tag; - return absl::InvalidArgumentError(absl::StrCat( - "Payload does not contain a packet id: ", packet.DebugString())); -} - -absl::StatusOr GetIngressPortFromIrPacketIn( - const pdpi::IrPacketIn& packet_in) { - for (const auto& metadata : packet_in.metadata()) { - if (metadata.name() == "ingress_port") return metadata.value().str(); + if (!RE2::PartialMatch(packet.payload(), *kTestPacketIdRegexp, &tag)) { + return gutil::InvalidArgumentErrorBuilder() + << "test packets must contain a tag of the form '" + << kTestPacketIdRegexp->pattern() + << "' in their payload, but the given packet with payload '" + << packet.payload() << "' does not:\n" + << gutil::PrintTextProto(packet); } - return absl::InvalidArgumentError( - "IrPacketIn does not contain 'ingress_port' metadata."); + return tag; } -absl::optional CheckForPacketTestVectorFailure( - const PacketTestVector& packet_test_vector, - const SwitchOutput& actual_output, - const absl::flat_hash_set& ignored_packet_in_metadata, - const std::vector& - ignored_fields) { - const absl::optional diff = - CompareSwitchOutputs(packet_test_vector, actual_output, - ignored_packet_in_metadata, ignored_fields); - if (!diff.has_value()) return absl::nullopt; - - // To make the diff more digestible, we first give an abstract - // characterization of the expected and actual outputs. - absl::flat_hash_set - acceptable_output_characterizations; -for (auto& acceptable_output : packet_test_vector.acceptable_outputs()) { - acceptable_output_characterizations.insert( - CharacterizeSwitchOutput(acceptable_output)); - } - const SwitchOutputCharacterization actual_output_characterization = - CharacterizeSwitchOutput(actual_output); - const bool actual_matches_expected = - acceptable_output_characterizations.contains( - actual_output_characterization); - - std::string expectation = DescribeExpectation( - packet_test_vector.input(), acceptable_output_characterizations); - if (!ignored_fields.empty()) { - absl::StrAppend( - &expectation, "\n (ignoring the field(s) ", - absl::StrJoin(ignored_fields, ",", - [](std::string* out, - const google::protobuf::FieldDescriptor* field) { - absl::StrAppend(out, "'", field->name(), "'"); - }), - ")"); - } - std::string actual = DescribeActual(packet_test_vector.input(), - actual_output_characterization); - if (actual_matches_expected) { - absl::StrAppend(&actual, ", but with unexpected modifications"); - } - std::string failure = absl::Substitute( - "Expected: $0\n Actual: $1\n$2\nDetails dumped below.\n\n", expectation, - actual, *diff); - - // Dump input. - absl::StrAppend(&failure, kInputBanner, "\n", - PrintTextProto(packet_test_vector.input())); - - // Dump actual output, if any. - if (!IsCharacterizedAsDrop(actual_output_characterization)) { - absl::StrAppend(&failure, kActualBanner, "\n", - PrintTextProto(actual_output)); - } - - // Dump expected output, if any. - if (!IsCharacterizedAsDrop(acceptable_output_characterizations)) { - absl::StrAppend(&failure, kExpectationBanner, "\n"); - for (int i = 0; i < packet_test_vector.acceptable_outputs_size(); ++i) { - absl::StrAppendFormat( - &failure, "-- Acceptable output: Alternative #%d --\n%s", (i + 1), - PrintTextProto(packet_test_vector.acceptable_outputs(i))); - } - } - - return failure; +std::ostream& operator<<(std::ostream& os, const SwitchOutput& output) { + return os << output.DebugString(); } } // namespace dvaas diff --git a/dvaas/test_vector.h b/dvaas/test_vector.h index 398d20a3..ffbcad09 100644 --- a/dvaas/test_vector.h +++ b/dvaas/test_vector.h @@ -26,48 +26,29 @@ #include "dvaas/test_vector.pb.h" #include "google/protobuf/descriptor.h" #include "p4_pdpi/ir.pb.h" +#include "re2/re2.h" namespace dvaas { -// Given a tagged packet, extracts the tag in its payload. Returns an -// error if the payload has an unexpected format, e.g. for untagged packets. +// Returns a string that must be included in the payload of test packets. +// This "tag" encodes the given test packet ID, which must be: +// * Uniform across all packets within a packet test vector, incl. input & +// output packets. +// * Unique across different packet test vectors. +std::string MakeTestPacketTagFromUniqueId(int unique_test_packet_id); + +// Given a tagged packet (according to `MakeTestPacketTag`), extracts the ID +// from the tag in its payload. Returns an error if the payload has an +// unexpected format, e.g. for untagged packets. // TODO: Implement and use a unified (open-source) API for test // packet tag embedding and extraction. absl::StatusOr ExtractTestPacketTag(const packetlib::Packet& packet); // Needed to make gUnit produce human-readable output in open source. -inline std::ostream& operator<<(std::ostream& os, const SwitchOutput& output) { - return os << output.DebugString(); -} +std::ostream& operator<<(std::ostream& os, const SwitchOutput& output); using PacketTestVectorById = absl::btree_map; -// Holds a PacketTestVector along with the actual SUT output generated in -// response to the test vector's input. The actual output may be empty, if the -// switch drops the input packet. The test vector may be empty, if the switch -// generates packets that do not correspond to an input, or if the output cannot -// be mapped to a test input. -struct PacketTestVectorAndActualOutput { - PacketTestVector packet_test_vector; - SwitchOutput actual_output; -}; - -// Gets 'ingress_port' value from metadata in `packet_in`. Returns -// InvalidArgumentError if 'ingress_port' metadata is missing. -absl::StatusOr GetIngressPortFromIrPacketIn( - const pdpi::IrPacketIn& packet_in); - -// Checks if the `actual_output` conforms to the `packet_test_vector` -// when ignoring the given `ignored_fields` and 'ignored_packet_in_metadata', if -// any. All `ignored_fields` should belong to packetlib::Packet. Returns a -// failure description in case of a mismatch, or `absl::nullopt` otherwise. -absl::optional CheckForPacketTestVectorFailure( - const PacketTestVector& packet_test_vector, - const SwitchOutput& actual_output, - const absl::flat_hash_set& ignored_packet_in_metadata = {}, - const std::vector& - ignored_fields = {}); - } // namespace dvaas #endif // PINS_TESTS_FORWARDING_TEST_VECTOR_H_ diff --git a/dvaas/test_vector_test.cc b/dvaas/test_vector_test.cc new file mode 100644 index 00000000..28dd28d6 --- /dev/null +++ b/dvaas/test_vector_test.cc @@ -0,0 +1,23 @@ +#include "dvaas/test_vector.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "gutil/status_matchers.h" +#include "p4_pdpi/packetlib/packetlib.pb.h" + +namespace dvaas { +namespace { + +using ::gutil::IsOkAndHolds; +using ::testing::Eq; + +TEST(MakeTestPacketTag, RoundTripsWithExtractTestPacketTag) { + for (int test_packet_id : {0, 1, 2, 42, 1234}) { + packetlib::Packet packet; + packet.set_payload(MakeTestPacketTagFromUniqueId(test_packet_id)); + EXPECT_THAT(ExtractTestPacketTag(packet), IsOkAndHolds(Eq(test_packet_id))); + } +} + +} // namespace +} // namespace dvaas From c7a79e801f5fb77a2a5ef904ae8f39a82310a192 Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Tue, 19 Nov 2024 12:09:36 +0530 Subject: [PATCH 23/24] [Thinkit] Move where the DeviceId mock call happens in testing, Cleanup Switch Test Helper Tests, Fix ASAN error in test, Generalize setup helpers to make config push optional & Disable failing smoke_test while SAI fix is investigated. (#728) Co-authored-by: kishanps Co-authored-by: jonathan-dilorenzo --- tests/forwarding/smoke_test.cc | 5 +- tests/lib/switch_test_setup_helpers_test.cc | 235 +++++++++----------- 2 files changed, 113 insertions(+), 127 deletions(-) diff --git a/tests/forwarding/smoke_test.cc b/tests/forwarding/smoke_test.cc index cb8b70f4..b1b84837 100644 --- a/tests/forwarding/smoke_test.cc +++ b/tests/forwarding/smoke_test.cc @@ -31,9 +31,8 @@ namespace pins { namespace { -TEST_P(SmokeTestFixture, ModifyWorks) { - GetMirrorTestbed().Environment().SetTestCaseID( - "3b18d5dc-3881-42a5-b667-d2ca0362ab3a"); +// TODO: modify failing because of policer attributes. +TEST_P(SmokeTestFixture, DISABLED_ModifyWorks) { const sai::WriteRequest pd_insert = gutil::ParseProtoOrDie( R"pb( updates { diff --git a/tests/lib/switch_test_setup_helpers_test.cc b/tests/lib/switch_test_setup_helpers_test.cc index 88b9c1b1..4f4812ab 100644 --- a/tests/lib/switch_test_setup_helpers_test.cc +++ b/tests/lib/switch_test_setup_helpers_test.cc @@ -1,7 +1,9 @@ #include "tests/lib/switch_test_setup_helpers.h" +#include #include +#include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "absl/strings/substitute.h" #include "absl/time/time.h" @@ -9,6 +11,7 @@ #include "grpcpp/test/mock_stream.h" #include "gtest/gtest.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/p4_runtime_session.h" @@ -20,6 +23,7 @@ namespace pins_test { namespace { using ::testing::_; +using ::testing::AnyNumber; using ::testing::ByMove; using ::testing::EqualsProto; using ::testing::InSequence; @@ -146,7 +150,7 @@ void MockCheckNoEntries(p4::v1::MockP4RuntimeStub& stub, const p4::config::v1::P4Info& p4info) { // We need to return a valid p4info to get to the stage where we read tables. EXPECT_CALL(stub, GetForwardingPipelineConfig) - .WillOnce([&](auto, auto, + .WillOnce([=](auto, auto, p4::v1::GetForwardingPipelineConfigResponse* get_pipeline_response) { *get_pipeline_response->mutable_config()->mutable_p4info() = p4info; @@ -165,7 +169,7 @@ void MockClearTableEntries(p4::v1::MockP4RuntimeStub& stub, 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, + .WillOnce([=](auto, auto, p4::v1::GetForwardingPipelineConfigResponse* get_pipeline_response) { *get_pipeline_response->mutable_config()->mutable_p4info() = p4info; @@ -197,38 +201,19 @@ void MockGnmiPush(gnmi::MockgNMIStub& mock_gnmi_stub) { EXPECT_CALL(mock_gnmi_stub, Set).WillOnce(Return(grpc::Status::OK)); } -// Generates an OpenConfig JSON string with the interface and p4rt port ID. -std::string OpenConfigInterface(absl::string_view field_type, - absl::string_view port_name, - absl::string_view port_id) { - return absl::Substitute(R"( - { - "openconfig-interfaces:interfaces":{ - "interface" : [ - { - "name" : "$0", - "$1" : { - "openconfig-p4rt:id" : $2 - } - } - ] - } - })", - port_name, field_type, port_id); -} - -// Mocks a successful `WaitForGnmiPortIdConvergence` call where the port given -// by `port_name` and `port_id` has converged. -void MockGnmiConvergence(gnmi::MockgNMIStub& mock_gnmi_stub, - absl::string_view port_name, absl::string_view port_id, - const absl::Duration& gnmi_timeout) { +// Mocks a successful `WaitForGnmiPortIdConvergence` call where the ports given +// by `interfaces` have converged. +void MockGnmiConvergence( + gnmi::MockgNMIStub& mock_gnmi_stub, + const std::vector& interfaces, + const absl::Duration& gnmi_timeout) { EXPECT_CALL(mock_gnmi_stub, Get) .WillOnce([=](auto, auto, gnmi::GetResponse* response) { *response->add_notification() ->add_update() ->mutable_val() ->mutable_json_ietf_val() = - OpenConfigInterface("state", port_name, port_id); + OpenConfigWithInterfaces(GnmiFieldType::kState, interfaces); return grpc::Status::OK; }); } @@ -254,14 +239,17 @@ ConstructForwardingPipelineConfigRequest( // Tests that ConfigureSwitchAndReturnP4RuntimeSession creates a // P4RuntimeSession, clears all table entries currently on the switch (mocked -// to be one), pushes a gNMI config, converges, and pushes a new p4info, if one -// is given. +// to be one), pushes a gNMI config and converges (if config is given), +// and pushes a new P4Info (if P4Info is given). void MockConfigureSwitchAndReturnP4RuntimeSession( - thinkit::MockSwitch& mock_switch, absl::string_view port_name, - absl::string_view port_id, - const p4::config::v1::P4Info& p4info_returned_by_get_forwarding_pipeline, - const p4::config::v1::P4Info* p4info_used_to_set_forwarding_pipeline, - const pdpi::P4RuntimeSessionOptionalArgs& metadata) { + thinkit::MockSwitch& mock_switch, + // Using optional& against style-guide advice to avoid memory leak; these + // arguments are used in setting up expectations, and need to outlive the + // function call. + const std::optional& gnmi_config, + const std::optional& p4info, + const pdpi::P4RuntimeSessionOptionalArgs& metadata, + const std::vector& interfaces) { // The stub that will be returned when CreateP4RuntimeStub is called on // mock_switch. auto stub = absl::make_unique(); @@ -271,8 +259,18 @@ void MockConfigureSwitchAndReturnP4RuntimeSession( // mock_switch. auto mock_gnmi_stub_push = absl::make_unique(); auto mock_gnmi_stub_converge = absl::make_unique(); - ASSERT_NE(mock_gnmi_stub_push, nullptr); - ASSERT_NE(mock_gnmi_stub_converge, nullptr); + + // DeviceId and ChassisName may get called multiple times. The only important + // point is that they always return the same response. + ON_CALL(mock_switch, DeviceId).WillByDefault(Return(kDeviceId)); + EXPECT_CALL(mock_switch, DeviceId).Times(AnyNumber()); + + // Required so that the reference below is not to a local variable. + static const std::string* const kChassisNameString = + new std::string("some_chassis_name"); + ON_CALL(mock_switch, ChassisName) + .WillByDefault(ReturnRef(*kChassisNameString)); + EXPECT_CALL(mock_switch, ChassisName).Times(AnyNumber()); { InSequence s; @@ -284,33 +282,33 @@ void MockConfigureSwitchAndReturnP4RuntimeSession( // Mocks a `ClearTableEntries` call. // Pulls the p4info from the switch, then reads a table entry, deletes it, // and reads again ensuring that there are no table entries remaining. - MockClearTableEntries(*stub, p4info_returned_by_get_forwarding_pipeline, - metadata); + MockClearTableEntries(*stub, pdpi::GetTestP4Info(), metadata); // Mocks a `CheckNoEntries` call. - MockCheckNoEntries(*stub, p4info_returned_by_get_forwarding_pipeline); - - // Mocks a `PushGnmiConfig` call. - MockGnmiPush(*mock_gnmi_stub_push); - - // Mocks a `WaitForGnmiPortIdConvergence` call. - MockGnmiConvergence(*mock_gnmi_stub_converge, port_name, port_id, - /*gnmi_timeout=*/absl::Minutes(3)); + MockCheckNoEntries(*stub, pdpi::GetTestP4Info()); + + if (gnmi_config.has_value()) { + // Mocks a `PushGnmiConfig` call. + MockGnmiPush(*mock_gnmi_stub_push); + // Mocks a `WaitForGnmiPortIdConvergence` call. + MockGnmiConvergence(*mock_gnmi_stub_converge, interfaces, + /*gnmi_timeout=*/absl::Minutes(3)); + } // When there is a P4Info to set, we mock a `SetForwardingPipelineConfig` // call and a `CheckNoEntries` call. - if (p4info_used_to_set_forwarding_pipeline != nullptr) { + if (p4info.has_value()) { // Mocks a `SetForwardingPipelineConfig` call. EXPECT_CALL(*stub, SetForwardingPipelineConfig( _, EqualsProto(ConstructForwardingPipelineConfigRequest( - metadata, *p4info_used_to_set_forwarding_pipeline)), + metadata, *p4info)), _)) .Times(1); // Mocks a `CheckNoEntries` call. - MockCheckNoEntries(*stub, *p4info_used_to_set_forwarding_pipeline); + MockCheckNoEntries(*stub, *p4info); } } @@ -319,21 +317,16 @@ void MockConfigureSwitchAndReturnP4RuntimeSession( // Mocks the first part of a P4RuntimeSession `Create` call. EXPECT_CALL(mock_switch, CreateP4RuntimeStub()) .WillOnce(Return(ByMove(std::move(stub)))); - EXPECT_CALL(mock_switch, DeviceId).WillOnce(Return(kDeviceId)); - // Mocks the first part of a `PushGnmiConfig` call. - EXPECT_CALL(mock_switch, CreateGnmiStub) - .WillOnce(Return(ByMove(std::move(mock_gnmi_stub_push)))); + if (gnmi_config.has_value()) { + // Mocks the first part of a `PushGnmiConfig` call. + EXPECT_CALL(mock_switch, CreateGnmiStub) + .WillOnce(Return(ByMove(std::move(mock_gnmi_stub_push)))); - // Required so that the reference below is not to a local variable. - static const std::string* const kChassisNameString = - new std::string("some_chassis_name"); - EXPECT_CALL(mock_switch, ChassisName) - .WillOnce(ReturnRef(*kChassisNameString)); - - // Mocks the first part of a `WaitForGnmiPortIdConvergence` - EXPECT_CALL(mock_switch, CreateGnmiStub) - .WillOnce(Return(ByMove(std::move(mock_gnmi_stub_converge)))); + // Mocks the first part of a `WaitForGnmiPortIdConvergence` + EXPECT_CALL(mock_switch, CreateGnmiStub) + .WillOnce(Return(ByMove(std::move(mock_gnmi_stub_converge)))); + } } } @@ -344,44 +337,31 @@ TEST(TestHelperLibTest, const p4::config::v1::P4Info& p4info = pdpi::GetTestP4Info(); const pdpi::P4RuntimeSessionOptionalArgs metadata; thinkit::MockSwitch mock_switch; - const std::string port_name = "Ethernet0"; - const std::string port_id = "1"; - - const std::string gnmi_config = - OpenConfigInterface("config", port_name, port_id); - - EXPECT_CALL(mock_switch, DeviceId).WillOnce(Return(123456)); - MockConfigureSwitchAndReturnP4RuntimeSession( - mock_switch, port_name, port_id, - /*p4info_returned_by_get_forwarding_pipeline=*/p4info, - /*p4info_used_to_set_forwarding_pipeline=*/&p4info, metadata); - - ASSERT_OK_AND_ASSIGN(auto session, - ConfigureSwitchAndReturnP4RuntimeSession( - mock_switch, gnmi_config, p4info, metadata)); -} - -// Tests that ConfigureSwitchAndReturnP4RuntimeSession works without a P4Info. -TEST(TestHelperLibTest, - ConfigureSwitchAndReturnP4RuntimeSessionWithoutP4InfoPush) { - const p4::config::v1::P4Info& p4info = pdpi::GetTestP4Info(); - const pdpi::P4RuntimeSessionOptionalArgs metadata; - thinkit::MockSwitch mock_switch; - const std::string port_name = "Ethernet0"; - const std::string port_id = "1"; - - const std::string gnmi_config = - OpenConfigInterface("config", port_name, port_id); - - EXPECT_CALL(mock_switch, DeviceId).WillOnce(Return(123456)); - MockConfigureSwitchAndReturnP4RuntimeSession( - mock_switch, port_name, port_id, - /*p4info_returned_by_get_forwarding_pipeline=*/p4info, - /*p4info_used_to_set_forwarding_pipeline=*/nullptr, metadata); - - ASSERT_OK_AND_ASSIGN( - auto session, ConfigureSwitchAndReturnP4RuntimeSessionWithoutP4InfoPush( - mock_switch, gnmi_config, metadata)); + OpenConfigInterfaceDescription interface { + .port_name = "Ethernet0", .port_id = 1, + }; + const std::string gnmi_config = OpenConfigWithInterfaces( + GnmiFieldType::kConfig, /*interfaces=*/{interface}); + + for (bool push_gnmi_config : {true, false}) { + for (bool push_p4info : {true, false}) { + SCOPED_TRACE(absl::StrCat("push_gnmi_config: ", push_gnmi_config)); + SCOPED_TRACE(absl::StrCat("push_p4info: ", push_gnmi_config)); + std::optional optional_gnmi_config = + push_gnmi_config ? std::make_optional(gnmi_config) : std::nullopt; + std::optional optional_p4info = + push_p4info ? std::make_optional(p4info) : std::nullopt; + + MockConfigureSwitchAndReturnP4RuntimeSession( + mock_switch, optional_gnmi_config, optional_p4info, metadata, + /*interfaces=*/{interface}); + ASSERT_OK_AND_ASSIGN( + auto session, + ConfigureSwitchAndReturnP4RuntimeSession( + mock_switch, optional_gnmi_config, optional_p4info, metadata)); + testing::Mock::VerifyAndClearExpectations(&mock_switch); + } + } } TEST(TestHelperLibTest, ConfigureSwitchPairAndReturnP4RuntimeSessionPair) { @@ -389,29 +369,36 @@ TEST(TestHelperLibTest, ConfigureSwitchPairAndReturnP4RuntimeSessionPair) { const pdpi::P4RuntimeSessionOptionalArgs metadata; thinkit::MockSwitch mock_switch1; thinkit::MockSwitch mock_switch2; - const std::string port_name = "Ethernet0"; - const std::string port_id = "1"; - - const std::string gnmi_config = - OpenConfigInterface("config", port_name, port_id); - - // Mock two configurings, skipping the final call. - EXPECT_CALL(mock_switch1, DeviceId).WillOnce(Return(10005)); - MockConfigureSwitchAndReturnP4RuntimeSession( - mock_switch1, port_name, port_id, - /*p4info_returned_by_get_forwarding_pipeline=*/p4info, - /*p4info_used_to_set_forwarding_pipeline=*/&p4info, metadata); - EXPECT_CALL(mock_switch2, DeviceId).WillOnce(Return(10006)); - MockConfigureSwitchAndReturnP4RuntimeSession( - mock_switch2, port_name, port_id, - /*p4info_returned_by_get_forwarding_pipeline=*/p4info, - /*p4info_used_to_set_forwarding_pipeline=*/&p4info, metadata); - - // Mock the final call. - ASSERT_OK_AND_ASSIGN( - (auto [session1, session2]), - ConfigureSwitchPairAndReturnP4RuntimeSessionPair( - mock_switch1, mock_switch2, gnmi_config, p4info, metadata)); + OpenConfigInterfaceDescription interface { + .port_name = "Ethernet0", .port_id = 1, + }; + const std::string gnmi_config = OpenConfigWithInterfaces( + GnmiFieldType::kConfig, /*interfaces=*/{interface}); + + for (bool push_gnmi_config : {true, false}) { + for (bool push_p4info : {true, false}) { + SCOPED_TRACE(absl::StrCat("push_gnmi_config: ", push_gnmi_config)); + SCOPED_TRACE(absl::StrCat("push_p4info: ", push_gnmi_config)); + std::optional optional_gnmi_config = + push_gnmi_config ? std::make_optional(gnmi_config) : std::nullopt; + std::optional optional_p4info = + push_p4info ? std::make_optional(p4info) : std::nullopt; + + // Mock two configurings. + MockConfigureSwitchAndReturnP4RuntimeSession( + mock_switch1, optional_gnmi_config, optional_p4info, metadata, + /*interfaces=*/{interface}); + MockConfigureSwitchAndReturnP4RuntimeSession( + mock_switch2, optional_gnmi_config, optional_p4info, metadata, + /*interfaces=*/{interface}); + ASSERT_OK_AND_ASSIGN((auto [session1, session2]), + ConfigureSwitchPairAndReturnP4RuntimeSessionPair( + mock_switch1, mock_switch2, optional_gnmi_config, + optional_p4info, metadata)); + testing::Mock::VerifyAndClearExpectations(&mock_switch1); + testing::Mock::VerifyAndClearExpectations(&mock_switch2); + } + } } } // namespace From 2bc31b559a43f246cac8d69f117bca61345ca187 Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Tue, 19 Nov 2024 12:11:42 +0530 Subject: [PATCH 24/24] [Thinkit] Add P4RT ACL counter test & Configure the VRF before using it. (#729) Co-authored-by: kishanps Co-authored-by: smolkaj --- tests/gnmi/ethcounter_ixia_test.cc | 10 +- tests/qos/BUILD.bazel | 2 + tests/qos/cpu_qos_test.cc | 142 +++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 1 deletion(-) diff --git a/tests/gnmi/ethcounter_ixia_test.cc b/tests/gnmi/ethcounter_ixia_test.cc index 9efc0e79..09a39bea 100644 --- a/tests/gnmi/ethcounter_ixia_test.cc +++ b/tests/gnmi/ethcounter_ixia_test.cc @@ -152,6 +152,14 @@ absl::Status ForwardToEgress(uint32_t in_port, uint32_t out_port, bool is_ipv6, if (is_ipv6) nborid = kNborIdv6; + auto vrf_entry = gutil::ParseProtoOrDie(absl::Substitute( + R"pb( + vrf_table_entry { + match { vrf_id: "$0" } + action { no_action {} } + })pb", + kVrfId)); + auto rif_out_entry = gutil::ParseProtoOrDie(absl::Substitute( R"pb( router_interface_table_entry { @@ -255,7 +263,7 @@ absl::Status ForwardToEgress(uint32_t in_port, uint32_t out_port, bool is_ipv6, LOG(INFO) << "for loop"; std::vector pi_entries; for (const auto &pd_entry : - {rif_out_entry, rif_in_entry, nbor_entry, nhop_entry, + {vrf_entry, rif_out_entry, rif_in_entry, nbor_entry, nhop_entry, is_ipv6 ? ipv6_entry : ipv4_entry, acl_entry}) { LOG(INFO) << "loop"; ASSIGN_OR_RETURN( diff --git a/tests/qos/BUILD.bazel b/tests/qos/BUILD.bazel index bddf3903..5b560947 100644 --- a/tests/qos/BUILD.bazel +++ b/tests/qos/BUILD.bazel @@ -31,6 +31,8 @@ cc_library( ], deps = [ "//gutil:collections", + "//gutil:proto", + "//gutil:proto_matchers", "//gutil:status", "//gutil:status_matchers", "//gutil:testing", diff --git a/tests/qos/cpu_qos_test.cc b/tests/qos/cpu_qos_test.cc index 3be84b33..a2219b14 100644 --- a/tests/qos/cpu_qos_test.cc +++ b/tests/qos/cpu_qos_test.cc @@ -39,6 +39,8 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "gutil/collections.h" +#include "gutil/proto.h" +#include "gutil/proto_matchers.h" #include "gutil/status.h" #include "gutil/status_matchers.h" #include "gutil/testing.h" @@ -69,8 +71,14 @@ namespace pins_test { namespace { +using ::gutil::EqualsProto; +using ::gutil::IsOkAndHolds; using ::p4::config::v1::P4Info; +// Size of the "frame check sequence" (FCS) that is part of Layer 2 Ethernet +// frames. +constexpr int kFrameCheckSequenceSize = 4; + // The maximum time the switch is allowed to take before queue counters read via // gNMI have to be incremented after a packet hits a queue. // Empirically, for PINS, queue counters currently seem to get updated every @@ -363,6 +371,140 @@ absl::StatusOr MakeRouterInterface( return pdpi::PartialPdTableEntryToPiTableEntry(ir_p4info, pd_entry); } +// Purpose: Verify that P4Runtime per-entry ACL counters increment. +TEST_P(CpuQosTestWithoutIxia, PerEntryAclCounterIncrementsWhenEntryIsHit) { + LOG(INFO) << "-- START OF TEST ---------------------------------------------"; + Testbed().Environment().SetTestCaseID("cfd7e8aa-6521-4683-9c07-038ea146934d"); + + // Setup: the testbed consists of a SUT connected to a control device + // that allows us to send and receive packets to/from the SUT. + thinkit::Switch &sut = Testbed().Sut(); + thinkit::Switch &control_device = Testbed().ControlSwitch(); + const P4Info &p4info = GetParam().p4info; + ASSERT_OK_AND_ASSIGN(const pdpi::IrP4Info ir_p4info, + pdpi::CreateIrP4Info(p4info)); + + // Set up gNMI. + EXPECT_OK(Testbed().Environment().StoreTestArtifact("gnmi_config.json", + GetParam().gnmi_config)); + ASSERT_OK(pins_test::PushGnmiConfig(sut, GetParam().gnmi_config)); + ASSERT_OK(pins_test::PushGnmiConfig(control_device, GetParam().gnmi_config)); + ASSERT_OK_AND_ASSIGN(auto gnmi_stub, sut.CreateGnmiStub()); + + // TODO: Poll for config to be applied, links to come up instead. + LOG(INFO) << "Sleeping 10 seconds to wait for config to be applied/links to " + "come up."; + absl::SleepFor(absl::Seconds(10)); + + // Pick a link to be used for packet injection. + ASSERT_OK_AND_ASSIGN(SutToControlLink link_used_for_test_packets, + PickSutToControlDeviceLinkThatsUp(Testbed())); + LOG(INFO) << "Link used to inject test packets: " + << link_used_for_test_packets; + + // Set up P4Runtime. + EXPECT_OK( + Testbed().Environment().StoreTestArtifact("p4info.textproto", p4info)); + ASSERT_OK_AND_ASSIGN( + std::unique_ptr sut_p4rt_session, + pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables(sut, p4info)); + ASSERT_OK_AND_ASSIGN( + std::unique_ptr control_p4rt_session, + pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables(control_device, + p4info)); + // We install a RIF to make this test non-trivial, as all packets are dropped + // by default if no RIF exists (b/190736007). + ASSERT_OK_AND_ASSIGN( + p4::v1::TableEntry router_interface_entry, + MakeRouterInterface( + /*router_interface_id=*/"ingress-rif-to-workaround-b/190736007", + /*p4rt_port_name=*/link_used_for_test_packets.sut_port_p4rt_name, + // An arbitrary MAC address will do. + /*mac=*/netaddr::MacAddress(0x00, 0x07, 0xE9, 0x42, 0xAC, 0x28), + /*ir_p4info=*/ir_p4info)); + ASSERT_OK(pdpi::InstallPiTableEntry(sut_p4rt_session.get(), + router_interface_entry)); + + // Install ACL table entry to be hit with a test packet. + ASSERT_OK_AND_ASSIGN(const sai::TableEntry pd_acl_entry, + gutil::ParseTextProto(R"pb( + acl_ingress_table_entry { + priority: 1 + match { + is_ipv6 { value: "0x1" } + ttl { value: "0xff" mask: "0xff" } + } + action { acl_drop {} } + } + )pb")); + ASSERT_OK_AND_ASSIGN(const p4::v1::TableEntry pi_acl_entry, + pdpi::PartialPdTableEntryToPiTableEntry(ir_p4info, pd_acl_entry)); + ASSERT_OK(pdpi::InstallPiTableEntry(sut_p4rt_session.get(), pi_acl_entry)); + + // Check that the counters are initially zero. + ASSERT_THAT( + pdpi::ReadPiCounterData(sut_p4rt_session.get(), pi_acl_entry), + IsOkAndHolds(EqualsProto(R"pb(byte_count: 0 packet_count: 0)pb"))); + + // Send test packet hitting the ACL table entry. + ASSERT_OK_AND_ASSIGN( + packetlib::Packet test_packet, + gutil::ParseTextProto(R"pb( + headers { + ethernet_header { + ethernet_destination: "00:01:02:02:02:02" + ethernet_source: "00:01:02:03:04:05" + ethertype: "0x86dd" + } + } + headers { + ipv6_header { + dscp: "0x00" + ecn: "0x0" + flow_label: "0x00000" + next_header: "0xfd" # Reserved for experimentation. + hop_limit: "0xff" + ipv6_source: "2001:db8:0:12::1" + ipv6_destination: "2001:db8:0:12::2" + } + } + payload: "IPv6 packet with TTL 0xff (255)." + )pb")); + // The ACL entry should match the test packet. + ASSERT_EQ(test_packet.headers().at(1).ipv6_header().hop_limit(), + pd_acl_entry.acl_ingress_table_entry().match().ttl().value()); + ASSERT_OK(packetlib::PadPacketToMinimumSize(test_packet)); + ASSERT_OK(packetlib::UpdateAllComputedFields(test_packet)); + ASSERT_OK_AND_ASSIGN(const std::string raw_packet, + packetlib::SerializePacket(test_packet)); + ASSERT_OK(pins::InjectEgressPacket( + /*port=*/link_used_for_test_packets.control_device_port_p4rt_name, + /*packet=*/raw_packet, ir_p4info, control_p4rt_session.get())); + + // Check that the counters increment within kMaxQueueCounterUpdateTime. + absl::Time time_packet_sent = absl::Now(); + p4::v1::CounterData counter_data; + do { + ASSERT_OK_AND_ASSIGN( + counter_data, + pdpi::ReadPiCounterData(sut_p4rt_session.get(), pi_acl_entry)); + } while (counter_data.packet_count() == 0 && + absl::Now() - time_packet_sent < kMaxQueueCounterUpdateTime); + p4::v1::CounterData expected_counter_data; + expected_counter_data.set_packet_count(1); + expected_counter_data.set_byte_count(raw_packet.size() + + kFrameCheckSequenceSize); + ASSERT_THAT(counter_data, EqualsProto(expected_counter_data)) + << "Counter for the table entry given below did not match expectation " + "within " + << kMaxQueueCounterUpdateTime + << " after injecting the following test packet:\n-- test packet--\n" + << test_packet.DebugString() << "-- table entry --\n" + << pd_acl_entry.DebugString(); + + LOG(INFO) << "-- END OF TEST -----------------------------------------------"; +} + // Purpose: Verify DSCP-to-queue mapping for traffic to switch loopback IP. TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { LOG(INFO) << "-- START OF TEST ---------------------------------------------";