From b2efdf1c299d70b105bb02199b8e15bac2fb5ae4 Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Tue, 3 Dec 2024 01:44:09 +0000 Subject: [PATCH 01/23] [Thinkit] Add a test to ensure that table entries are successfully cleared. (#787) Co-authored-by: kishanps --- tests/forwarding/BUILD.bazel | 2 ++ tests/forwarding/p4_blackbox_fixture.h | 31 +++++++---------------- tests/forwarding/smoke_test.cc | 35 ++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/tests/forwarding/BUILD.bazel b/tests/forwarding/BUILD.bazel index 5023d3b8..eded229e 100644 --- a/tests/forwarding/BUILD.bazel +++ b/tests/forwarding/BUILD.bazel @@ -93,6 +93,7 @@ cc_library( "//sai_p4/instantiations/google:sai_pd_cc_proto", "//thinkit:mirror_testbed", "//thinkit:mirror_testbed_fixture", + "@com_github_p4lang_p4runtime//:p4info_cc_proto", "@com_google_googletest//:gtest", ], ) @@ -113,6 +114,7 @@ cc_library( "//sai_p4/instantiations/google:sai_p4info_cc", "//sai_p4/instantiations/google:sai_pd_cc_proto", "//thinkit:mirror_testbed_fixture", + "@com_github_p4lang_p4runtime//:p4info_cc_proto", "@com_github_p4lang_p4runtime//:p4runtime_cc_proto", "@com_google_absl//absl/strings", "@com_google_googletest//:gtest", diff --git a/tests/forwarding/p4_blackbox_fixture.h b/tests/forwarding/p4_blackbox_fixture.h index f86f2f92..812550ac 100644 --- a/tests/forwarding/p4_blackbox_fixture.h +++ b/tests/forwarding/p4_blackbox_fixture.h @@ -19,6 +19,7 @@ #include "gtest/gtest.h" #include "gutil/status_matchers.h" #include "lib/gnmi/gnmi_helper.h" +#include "p4/config/v1/p4info.pb.h" #include "p4_pdpi/p4_runtime_session.h" #include "p4_pdpi/pd.h" #include "sai_p4/instantiations/google/sai_p4info.h" @@ -50,25 +51,14 @@ class P4BlackboxFixture : public thinkit::MirrorTestbedFixture { ASSERT_OK(pins_test::PushGnmiConfig(GetMirrorTestbed().ControlSwitch(), sut_gnmi_config)); - // Initialize the connection. - ASSERT_OK_AND_ASSIGN(sut_p4rt_session_, pdpi::P4RuntimeSession::Create( - GetMirrorTestbed().Sut())); - - ASSERT_OK(pdpi::SetMetadataAndSetForwardingPipelineConfig(sut_p4rt_session_.get(), - p4::v1::SetForwardingPipelineConfigRequest::RECONCILE_AND_COMMIT, - sai::GetP4Info(sai::Instantiation::kMiddleblock))); - - // Clear entries here in case the previous test did not (e.g. because it - // crashed). - ASSERT_OK(pdpi::ClearTableEntries(sut_p4rt_session_.get())); - // Check that switch is in a clean state. - ASSERT_OK_AND_ASSIGN(auto read_back_entries, - pdpi::ReadPiTableEntries(sut_p4rt_session_.get())); - ASSERT_EQ(read_back_entries.size(), 0); + // Initialize the connection and clear table entries. + ASSERT_OK_AND_ASSIGN(sut_p4rt_session_, + pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables( + GetMirrorTestbed().Sut(), p4info_)); } void TearDown() override { - if (SutP4RuntimeSession() != nullptr && clear_table_entries_on_teardown_) { + if (SutP4RuntimeSession() != nullptr) { // Clear all table entries to leave the switch in a clean state. EXPECT_OK(pdpi::ClearTableEntries(SutP4RuntimeSession())); } @@ -81,17 +71,14 @@ class P4BlackboxFixture : public thinkit::MirrorTestbedFixture { } const pdpi::IrP4Info& IrP4Info() const { return ir_p4info_; } - - protected: - void DisableClearingTableEntriesOnTearDown() { - clear_table_entries_on_teardown_ = false; - } + const p4::config::v1::P4Info& P4Info() const { return p4info_; } private: - bool clear_table_entries_on_teardown_ = true; std::unique_ptr sut_p4rt_session_; pdpi::IrP4Info ir_p4info_ = sai::GetIrP4Info(sai::Instantiation::kMiddleblock); + p4::config::v1::P4Info p4info_ = + sai::GetP4Info(sai::Instantiation::kMiddleblock); }; } // namespace pins diff --git a/tests/forwarding/smoke_test.cc b/tests/forwarding/smoke_test.cc index 1bf37ba7..e45cfbd9 100644 --- a/tests/forwarding/smoke_test.cc +++ b/tests/forwarding/smoke_test.cc @@ -21,6 +21,7 @@ #include "gutil/proto_matchers.h" #include "gutil/status_matchers.h" #include "gutil/testing.h" +#include "p4/config/v1/p4info.pb.h" #include "p4/v1/p4runtime.pb.h" #include "p4_pdpi/p4_runtime_session.h" #include "p4_pdpi/pd.h" @@ -247,5 +248,39 @@ TEST_P(SmokeTestFixture, InsertAndReadTableEntries) { << "\nActual: " << read_response.DebugString(); } +// Ensures that both CreateWithP4InfoAndClearTables and ClearTableEntries +// properly clear the table entries of a table. +TEST_P(SmokeTestFixture, EnsureClearTables) { + // Sets up initial session. + ASSERT_OK_AND_ASSIGN(auto session, + pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables( + GetMirrorTestbed().Sut(), P4Info())); + // The table should be clear after setup. + ASSERT_OK(pdpi::CheckNoTableEntries(session.get())); + // Sets up an example table entry. + const sai::TableEntry pd_entry = gutil::ParseProtoOrDie( + R"pb( + router_interface_table_entry { + match { router_interface_id: "router-interface-1" } + action { + set_port_and_src_mac { port: "1" src_mac: "02:2a:10:00:00:03" } + } + } + )pb"); + ASSERT_OK_AND_ASSIGN(p4::v1::TableEntry pi_entry, + pdpi::PartialPdTableEntryToPiTableEntry(IrP4Info(), pd_entry)); + ASSERT_OK(pdpi::InstallPiTableEntries(session.get(), IrP4Info(), {pi_entry})); + ASSERT_OK(pdpi::ClearTableEntries(session.get())); + // The table should be clear after clearing. + ASSERT_OK(pdpi::CheckNoTableEntries(session.get())); + ASSERT_OK(pdpi::InstallPiTableEntries(session.get(), IrP4Info(), {pi_entry})); + ASSERT_OK_AND_ASSIGN(auto session2, + pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables( + GetMirrorTestbed().Sut(), P4Info())); + // The table should be clear for both sessions after setting up a new session. + ASSERT_OK(pdpi::CheckNoTableEntries(session.get())); + ASSERT_OK(pdpi::CheckNoTableEntries(session2.get())); +} + } // namespace } // namespace pins From 502f72e1bdfdf31181ce2af4dd28515d61b2d05d Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Tue, 3 Dec 2024 01:46:33 +0000 Subject: [PATCH 02/23] Fix hex string transition field types. (#790) Co-authored-by: kishanps --- p4_symbolic/ir/expected/basic.txt | 6 +- .../ir/expected/hex_string_transition.txt | 14 +- p4_symbolic/ir/expected/sai_parser.txt | 84 +++++++++-- .../ir/expected/set_parser_operation.txt | 6 +- p4_symbolic/ir/ir.cc | 133 ++++++++++-------- p4_symbolic/ir/ir.proto | 4 +- 6 files changed, 167 insertions(+), 80 deletions(-) diff --git a/p4_symbolic/ir/expected/basic.txt b/p4_symbolic/ir/expected/basic.txt index 2450de59..3459fecd 100644 --- a/p4_symbolic/ir/expected/basic.txt +++ b/p4_symbolic/ir/expected/basic.txt @@ -685,7 +685,11 @@ parsers { } transitions { hex_string_transition { - value: "0x0800" + value { + value: "0x0800" + } + mask { + } next_state: "parse_ipv4" } } diff --git a/p4_symbolic/ir/expected/hex_string_transition.txt b/p4_symbolic/ir/expected/hex_string_transition.txt index ea9b2f3d..e00dfc9f 100644 --- a/p4_symbolic/ir/expected/hex_string_transition.txt +++ b/p4_symbolic/ir/expected/hex_string_transition.txt @@ -326,14 +326,22 @@ parsers { } transitions { hex_string_transition { - value: "0x0000" - mask: "0xfe00" + value { + value: "0x0000" + } + mask { + value: "0xfe00" + } next_state: "__END_OF_PARSER__" } } transitions { hex_string_transition { - value: "0x0800" + value { + value: "0x0800" + } + mask { + } next_state: "parse_ipv4" } } diff --git a/p4_symbolic/ir/expected/sai_parser.txt b/p4_symbolic/ir/expected/sai_parser.txt index e22d0a0e..6789df92 100644 --- a/p4_symbolic/ir/expected/sai_parser.txt +++ b/p4_symbolic/ir/expected/sai_parser.txt @@ -1141,19 +1141,31 @@ parsers { } transitions { hex_string_transition { - value: "0x0800" + value { + value: "0x0800" + } + mask { + } next_state: "parse_ipv4" } } transitions { hex_string_transition { - value: "0x86dd" + value { + value: "0x86dd" + } + mask { + } next_state: "parse_ipv6" } } transitions { hex_string_transition { - value: "0x0806" + value { + value: "0x0806" + } + mask { + } next_state: "parse_arp" } } @@ -1201,25 +1213,41 @@ parsers { } transitions { hex_string_transition { - value: "0x0800" + value { + value: "0x0800" + } + mask { + } next_state: "parse_ipv4" } } transitions { hex_string_transition { - value: "0x86dd" + value { + value: "0x86dd" + } + mask { + } next_state: "parse_ipv6" } } transitions { hex_string_transition { - value: "0x0806" + value { + value: "0x0806" + } + mask { + } next_state: "parse_arp" } } transitions { hex_string_transition { - value: "0x8100" + value { + value: "0x8100" + } + mask { + } next_state: "parse_8021q_vlan" } } @@ -1267,19 +1295,31 @@ parsers { } transitions { hex_string_transition { - value: "0x01" + value { + value: "0x01" + } + mask { + } next_state: "parse_icmp" } } transitions { hex_string_transition { - value: "0x06" + value { + value: "0x06" + } + mask { + } next_state: "parse_tcp" } } transitions { hex_string_transition { - value: "0x11" + value { + value: "0x11" + } + mask { + } next_state: "parse_udp" } } @@ -1309,19 +1349,31 @@ parsers { } transitions { hex_string_transition { - value: "0x3a" + value { + value: "0x3a" + } + mask { + } next_state: "parse_icmp" } } transitions { hex_string_transition { - value: "0x06" + value { + value: "0x06" + } + mask { + } next_state: "parse_tcp" } } transitions { hex_string_transition { - value: "0x11" + value { + value: "0x11" + } + mask { + } next_state: "parse_udp" } } @@ -1555,7 +1607,11 @@ parsers { } transitions { hex_string_transition { - value: "0x01fe" + value { + value: "0x01fe" + } + mask { + } next_state: "parse_packet_out_header" } } diff --git a/p4_symbolic/ir/expected/set_parser_operation.txt b/p4_symbolic/ir/expected/set_parser_operation.txt index 834b535e..c85e8beb 100644 --- a/p4_symbolic/ir/expected/set_parser_operation.txt +++ b/p4_symbolic/ir/expected/set_parser_operation.txt @@ -433,7 +433,11 @@ parsers { } transitions { hex_string_transition { - value: "0x0800" + value { + value: "0x0800" + } + mask { + } next_state: "parse_ipv4" } } diff --git a/p4_symbolic/ir/ir.cc b/p4_symbolic/ir/ir.cc index c117e726..8d515452 100644 --- a/p4_symbolic/ir/ir.cc +++ b/p4_symbolic/ir/ir.cc @@ -373,6 +373,55 @@ absl::StatusOr ExtractRExpression( } } +// Extracts field value from BMv2 protobuf fields. +absl::StatusOr ExtractFieldValue( + const google::protobuf::ListValue &bmv2_field_value) { + FieldValue output; + + if (bmv2_field_value.values_size() != 2 || + !bmv2_field_value.values(0).has_string_value() || + !bmv2_field_value.values(1).has_string_value()) { + return gutil::InvalidArgumentErrorBuilder() + << "Field value must contain 2 strings. Found: " + << bmv2_field_value.DebugString(); + } + + output.set_header_name(bmv2_field_value.values(0).string_value()); + output.set_field_name(bmv2_field_value.values(1).string_value()); + return output; +} + +// Extracts hex string value from BMv2 protobuf fields. +HexstrValue ExtractHexstrValue(const std::string &bmv2_hexstr) { + HexstrValue output; + if (absl::StartsWith(bmv2_hexstr, "-")) { + output.set_value(std::string(absl::StripPrefix(bmv2_hexstr, "-"))); + output.set_negative(true); + } else { + output.set_value(bmv2_hexstr); + output.set_negative(false); + } + return output; +} + +// Extracts lookahead value from BMv2 protobuf fields. +absl::StatusOr ExtractLookaheadValue( + const google::protobuf::ListValue &bmv2_lookahead) { + LookaheadValue output; + + if (bmv2_lookahead.values_size() != 2 || + !bmv2_lookahead.values(0).has_number_value() || + !bmv2_lookahead.values(1).has_number_value()) { + return gutil::InvalidArgumentErrorBuilder() + << "Lookahead value must contain 2 numbers. Found: " + << bmv2_lookahead.DebugString(); + } + + output.set_offset(bmv2_lookahead.values(0).number_value()); + output.set_bitwidth(bmv2_lookahead.values(1).number_value()); + return output; +} + // Functions for translating values. absl::StatusOr ExtractLValue( const google::protobuf::Value &bmv2_value, @@ -389,21 +438,17 @@ absl::StatusOr ExtractLValue( const google::protobuf::Struct &struct_value = bmv2_value.struct_value(); const std::string &type = struct_value.fields().at("type").string_value(); + const google::protobuf::Value &value = struct_value.fields().at("value"); ASSIGN_OR_RETURN(bmv2::ExpressionType type_case, ExpressionTypeToEnum(type)); switch (type_case) { case bmv2::ExpressionType::field: { - const google::protobuf::ListValue &names = - struct_value.fields().at("value").list_value(); - - FieldValue *field_value = output.mutable_field_value(); - field_value->set_header_name(names.values(0).string_value()); - field_value->set_field_name(names.values(1).string_value()); + ASSIGN_OR_RETURN(*output.mutable_field_value(), + ExtractFieldValue(value.list_value())); return output; } case bmv2::ExpressionType::runtime_data: { - int variable_index = struct_value.fields().at("value").number_value(); - + int variable_index = value.number_value(); Variable *variable = output.mutable_variable_value(); variable->set_name(variables[variable_index]); return output; @@ -428,58 +473,42 @@ absl::StatusOr ExtractRValue( const google::protobuf::Struct &struct_value = bmv2_value.struct_value(); const std::string &type = struct_value.fields().at("type").string_value(); + const google::protobuf::Value &value = struct_value.fields().at("value"); ASSIGN_OR_RETURN(bmv2::ExpressionType type_case, ExpressionTypeToEnum(type)); switch (type_case) { case bmv2::ExpressionType::header: { - const std::string &header_name = - struct_value.fields().at("value").string_value(); - + const std::string &header_name = value.string_value(); HeaderValue *header_value = output.mutable_header_value(); header_value->set_header_name(header_name); return output; } case bmv2::ExpressionType::field: { - const google::protobuf::ListValue &names = - struct_value.fields().at("value").list_value(); - - FieldValue *field_value = output.mutable_field_value(); - field_value->set_header_name(names.values(0).string_value()); - field_value->set_field_name(names.values(1).string_value()); + ASSIGN_OR_RETURN(*output.mutable_field_value(), + ExtractFieldValue(value.list_value())); return output; } case bmv2::ExpressionType::runtime_data: { - int variable_index = struct_value.fields().at("value").number_value(); + int variable_index = value.number_value(); Variable *variable = output.mutable_variable_value(); variable->set_name(variables[variable_index]); return output; } case bmv2::ExpressionType::hexstr_: { - HexstrValue *hexstr_value = output.mutable_hexstr_value(); - std::string hexstr = struct_value.fields().at("value").string_value(); - if (absl::StartsWith(hexstr, "-")) { - hexstr_value->set_value(std::string(absl::StripPrefix(hexstr, "-"))); - hexstr_value->set_negative(true); - } else { - hexstr_value->set_value(hexstr); - hexstr_value->set_negative(false); - } + *output.mutable_hexstr_value() = ExtractHexstrValue(value.string_value()); return output; } case bmv2::ExpressionType::bool_: { - output.mutable_bool_value()->set_value( - struct_value.fields().at("value").bool_value()); + output.mutable_bool_value()->set_value(value.bool_value()); return output; } case bmv2::ExpressionType::string_: { - output.mutable_string_value()->set_value( - struct_value.fields().at("value").string_value()); + output.mutable_string_value()->set_value(value.string_value()); return output; } case bmv2::ExpressionType::expression: { - const google::protobuf::Struct &expression = - struct_value.fields().at("value").struct_value(); + const google::protobuf::Struct &expression = value.struct_value(); ASSIGN_OR_RETURN(*(output.mutable_expression_value()), ExtractRExpression(expression, variables)); return output; @@ -808,11 +837,9 @@ absl::StatusOr ExtractSetParserOp( "Parameter type must be 'field'. Found '%s'", bmv2_ltype)); } - FieldValue &lvalue = *result.mutable_lvalue(); - const ::google::protobuf::ListValue &bmv2_lvalue = + const google::protobuf::ListValue &bmv2_lvalue = bmv2_lparam.fields().at("value").list_value(); - lvalue.set_header_name(bmv2_lvalue.values(0).string_value()); - lvalue.set_field_name(bmv2_lvalue.values(1).string_value()); + ASSIGN_OR_RETURN(*result.mutable_lvalue(), ExtractFieldValue(bmv2_lvalue)); // Make sure the R-parameter struct contains the correct fields. if (!bmv2_rparam.fields().contains("type") || @@ -826,30 +853,20 @@ absl::StatusOr ExtractSetParserOp( // Translate the R-parameter of "set" parser operation. const std::string &bmv2_rtype = bmv2_rparam.fields().at("type").string_value(); + const google::protobuf::Value &bmv2_rvalue = bmv2_rparam.fields().at("value"); if (bmv2_rtype == "field") { - FieldValue &rvalue = *result.mutable_field_rvalue(); - const ::google::protobuf::ListValue &bmv2_field_value = - bmv2_rparam.fields().at("value").list_value(); - rvalue.set_header_name(bmv2_field_value.values(0).string_value()); - rvalue.set_field_name(bmv2_field_value.values(1).string_value()); + ASSIGN_OR_RETURN(*result.mutable_field_rvalue(), + ExtractFieldValue(bmv2_rvalue.list_value())); } else if (bmv2_rtype == "hexstr") { - HexstrValue &rvalue = *result.mutable_hexstr_rvalue(); const std::string &bmv2_hexstr = bmv2_rparam.fields().at("value").string_value(); - if (absl::StartsWith(bmv2_hexstr, "-")) { - rvalue.set_value(std::string(absl::StripPrefix(bmv2_hexstr, "-"))); - rvalue.set_negative(true); - } else { - rvalue.set_value(bmv2_hexstr); - rvalue.set_negative(false); - } + *result.mutable_hexstr_rvalue() = ExtractHexstrValue(bmv2_hexstr); } else if (bmv2_rtype == "lookahead") { - LookaheadValue &rvalue = *result.mutable_lookahead_rvalue(); const ::google::protobuf::ListValue &bmv2_lookahead = bmv2_rparam.fields().at("value").list_value(); - rvalue.set_offset(bmv2_lookahead.values(0).number_value()); - rvalue.set_bitwidth(bmv2_lookahead.values(1).number_value()); + ASSIGN_OR_RETURN(*result.mutable_lookahead_rvalue(), + ExtractLookaheadValue(bmv2_lookahead)); } else if (bmv2_rtype == "expression") { RExpression &rvalue = *result.mutable_expression_rvalue(); const google::protobuf::Struct &bmv2_expression = @@ -896,10 +913,8 @@ absl::StatusOr ExtractParserTransitionKeyField( switch (bmv2_key_field.type()) { case bmv2::ParserTransitionKeyField::field: { - FieldValue &field = *result.mutable_field(); - const ::google::protobuf::ListValue &bmv2_value = bmv2_key_field.value(); - field.set_header_name(bmv2_value.values(0).string_value()); - field.set_field_name(bmv2_value.values(1).string_value()); + ASSIGN_OR_RETURN(*result.mutable_field(), + ExtractFieldValue(bmv2_key_field.value())); break; } case bmv2::ParserTransitionKeyField::lookahead: @@ -945,8 +960,8 @@ absl::StatusOr ExtractParserTransition( "Empty hex string value: ", bmv2_transition.DebugString())); } - hexstr_transition.set_value(bmv2_value); - hexstr_transition.set_mask(bmv2_mask); + *hexstr_transition.mutable_value() = ExtractHexstrValue(bmv2_value); + *hexstr_transition.mutable_mask() = ExtractHexstrValue(bmv2_mask); hexstr_transition.set_next_state(Bmv2ToIrParseStateName(bmv2_next_state)); break; } diff --git a/p4_symbolic/ir/ir.proto b/p4_symbolic/ir/ir.proto index 2dd45b02..89f61a8f 100644 --- a/p4_symbolic/ir/ir.proto +++ b/p4_symbolic/ir/ir.proto @@ -169,9 +169,9 @@ message DefaultTransition { // Defines a hex string transition. message HexStringTransition { // Hex string value to be matched against the key. - string value = 1; + HexstrValue value = 1; // Hex string mask, to be ANDed with both the key and the value if not empty. - string mask = 2; + HexstrValue mask = 2; // The name of the next state. string next_state = 3; } From ae6984567cc34d1032ff0967f0face439aec6545 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Tue, 3 Dec 2024 01:48:40 +0000 Subject: [PATCH 03/23] [Dvaas] Add parameter to ignore packet-ins in switch output comparison. (#794) Co-authored-by: kishanps --- dvaas/test_run_validation.cc | 114 ++++++++++++++++-------------- dvaas/test_run_validation.h | 8 +++ dvaas/test_run_validation_test.cc | 101 ++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 52 deletions(-) diff --git a/dvaas/test_run_validation.cc b/dvaas/test_run_validation.cc index 55791394..a56089f0 100644 --- a/dvaas/test_run_validation.cc +++ b/dvaas/test_run_validation.cc @@ -99,11 +99,13 @@ bool CompareSwitchOutputs(SwitchOutput actual_output, 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; + if (!params.treat_expected_and_actual_outputs_as_having_no_packet_ins) { + 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(), @@ -139,58 +141,63 @@ bool CompareSwitchOutputs(SwitchOutput actual_output, } } - 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 : params.ignored_packetlib_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 (params.ignored_packet_in_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())) { + if (!params.treat_expected_and_actual_outputs_as_having_no_packet_ins) { + 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 : params.ignored_packetlib_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 value for metadata field '" - << expected_metadata.name() << "':\n " << Indent(2, diff); + << " with mismatched header fields:\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 (params.ignored_packet_in_metadata.contains(actual_metadata.name())) - continue; + // Check that received packet in has desired metadata (except for ignored + // metadata). + for (const auto& expected_metadata : expected_packet_in.metadata()) { + if (params.ignored_packet_in_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; + } + } - 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; + // Check that received packet in does not have additional metadata (except + // for ignored metadata). + for (const auto& actual_metadata : actual_packet_in.metadata()) { + if (params.ignored_packet_in_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; + } } } } @@ -370,6 +377,9 @@ PacketTestValidationResult ValidateTestRun( }), ")"); } + if (diff_params.treat_expected_and_actual_outputs_as_having_no_packet_ins) { + absl::StrAppend(&expectation, "\n (ignoring packet-ins)"); + } std::string actual = DescribeActual(test_run.test_vector().input(), actual_output_characterization); if (actual_matches_expected) { diff --git a/dvaas/test_run_validation.h b/dvaas/test_run_validation.h index 1d78f43d..f85894fb 100644 --- a/dvaas/test_run_validation.h +++ b/dvaas/test_run_validation.h @@ -43,6 +43,14 @@ struct SwitchOutputDiffParams { // in `ignored_metadata_for_validation`, the field is ignored during // comparison. absl::flat_hash_set ignored_packet_in_metadata; + + // If true, packet-ins (punted packets) are ignored while comparing expected + // and actual output. Note that packet-ins are completely ignored on both + // sides. Effectively the expected and actual outputs are compared assuming + // neither has any packet-ins. + // TODO: Remove and replace with + // `ignore_missing_packet_ins_in_actual_output`. + bool treat_expected_and_actual_outputs_as_having_no_packet_ins = false; }; // Validates the given `test_vector` using the parameters in `params` and diff --git a/dvaas/test_run_validation_test.cc b/dvaas/test_run_validation_test.cc index 0a166bf0..9b0c9d66 100644 --- a/dvaas/test_run_validation_test.cc +++ b/dvaas/test_run_validation_test.cc @@ -132,5 +132,106 @@ TEST(TestRunValidationTest, HasSubstr("mismatched ports:")); } +TEST(TestRunValidationTest, + MissingPacketInsAreIgnoredIfAndOnlyIfIgnorePacketInsIsSet) { + const PacketTestRun test_run = gutil::ParseProtoOrDie(R"pb( + test_vector { + acceptable_outputs { + packets { port: "1" } + packet_ins { + metadata { + name: "target_egress_port" + value { str: "1" } + } + } + } + } + actual_output { packets { port: "1" } } + )pb"); + + // Without ignoring packet-ins, validation must fail. + ASSERT_THAT(ValidateTestRun(test_run).failure().description(), + HasSubstr("packet in")); + + // Ignoring packet-ins, validation must succeed. + ASSERT_THAT( + ValidateTestRun( + test_run, + { + .treat_expected_and_actual_outputs_as_having_no_packet_ins = true, + }), + EqualsProto(R"pb()pb")); +} + +TEST(TestRunValidationTest, + PacketInDifferencesAreIgnoredIfAndOnlyIfIgnorePacketInsIsSet) { + const PacketTestRun test_run = gutil::ParseProtoOrDie(R"pb( + test_vector { + acceptable_outputs { + packets { port: "1" } + packet_ins { + metadata { + name: "target_egress_port" + value { str: "1" } + } + } + } + } + actual_output { + packets { port: "1" } + packet_ins { + metadata { + name: "target_egress_port" + value { str: "2" } + } + } + } + )pb"); + + // Without ignoring packet-ins, validation must fail. + ASSERT_THAT(ValidateTestRun(test_run).failure().description(), + HasSubstr("target_egress_port")); + + // Ignoring packet-ins, validation must succeed. + ASSERT_THAT( + ValidateTestRun( + test_run, + { + .treat_expected_and_actual_outputs_as_having_no_packet_ins = true, + }), + EqualsProto(R"pb()pb")); +} + +TEST(TestRunValidationTest, IgnorePacketInsHasNoEffectWhenPacketInsMatch) { + ASSERT_THAT( + ValidateTestRun( + gutil::ParseProtoOrDie(R"pb( + test_vector { + acceptable_outputs { + packets { port: "1" } + packet_ins { + metadata { + name: "target_egress_port" + value { str: "1" } + } + } + } + } + actual_output { + packets { port: "1" } + packet_ins { + metadata { + name: "target_egress_port" + value { str: "1" } + } + } + } + )pb"), + { + .treat_expected_and_actual_outputs_as_having_no_packet_ins = true, + }), + EqualsProto(R"pb()pb")); +} + } // namespace } // namespace dvaas From 3a44a1aa162767433c27ba090896a7b8a59a11c9 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Tue, 3 Dec 2024 01:49:37 +0000 Subject: [PATCH 04/23] [Dvaas] Add GetControlSwitchPortConnectedToSutPort to pord id map.Make packet injection map test packet's port to the corresponding control switch port. (#795) Co-authored-by: kishanps --- dvaas/packet_injection.cc | 13 +++++++-- dvaas/port_id_map.cc | 23 ++++++++++++++-- dvaas/port_id_map.h | 6 +++++ dvaas/port_id_map_test.cc | 57 ++++++++++++++++++++------------------- 4 files changed, 68 insertions(+), 31 deletions(-) diff --git a/dvaas/packet_injection.cc b/dvaas/packet_injection.cc index d108caac..57a78bc2 100644 --- a/dvaas/packet_injection.cc +++ b/dvaas/packet_injection.cc @@ -153,10 +153,19 @@ absl::StatusOr SendTestPacketsAndCollectOutputs( if (packet_test_vector.input().type() == SwitchInput::DATAPLANE) { const Packet& packet = packet_test_vector.input().packet(); + // Get corresponding control switch port for the packet's ingress port. + ASSIGN_OR_RETURN(const P4rtPortId sut_ingress_port, + P4rtPortId::MakeFromP4rtEncoding(packet.port())); + ASSIGN_OR_RETURN( + const P4rtPortId control_switch_port, + parameters.mirror_testbed_port_map + .GetControlSwitchPortConnectedToSutPort(sut_ingress_port)); + // Inject to egress of control switch. RETURN_IF_ERROR(pins::InjectEgressPacket( - packet.port(), absl::HexStringToBytes(packet.hex()), - control_ir_p4info, &control_switch, injection_delay)); + control_switch_port.GetP4rtEncoding(), + absl::HexStringToBytes(packet.hex()), control_ir_p4info, + &control_switch, injection_delay)); } else { return absl::UnimplementedError( absl::StrCat("Test vector input type not supported\n", diff --git a/dvaas/port_id_map.cc b/dvaas/port_id_map.cc index 0ca38f43..ac393fb3 100644 --- a/dvaas/port_id_map.cc +++ b/dvaas/port_id_map.cc @@ -88,9 +88,28 @@ MirrorTestbedP4rtPortIdMap::GetSutPortConnectedToControlSwitchPort( absl::Substitute("Control port '$0' was not found in control switch " "to SUT P4RT port ID map.", control_port)); - } else { - return it->second; } + return it->second; +} + +absl::StatusOr +MirrorTestbedP4rtPortIdMap::GetControlSwitchPortConnectedToSutPort( + const pins_test::P4rtPortId& sut_port) const { + // Handle implicit identity map. + if (!control_to_sut_port_map_.has_value()) return sut_port; + + // Handle explicit map. + const auto it = absl::c_find_if(*control_to_sut_port_map_, + [&](const auto& control_sut_port) { + return control_sut_port.second == sut_port; + }); + if (it == control_to_sut_port_map_->end()) { + return absl::NotFoundError( + absl::Substitute("SUT port '$0' was not found in control switch " + "to control switch P4RT port ID map.", + sut_port)); + } + return it->first; } absl::Status CheckAndStoreMappedAndUnmappedPortIds( diff --git a/dvaas/port_id_map.h b/dvaas/port_id_map.h index 0246b5c0..38922d83 100644 --- a/dvaas/port_id_map.h +++ b/dvaas/port_id_map.h @@ -66,6 +66,12 @@ class MirrorTestbedP4rtPortIdMap { absl::StatusOr GetSutPortConnectedToControlSwitchPort( const pins_test::P4rtPortId& control_port) const; + // Returns the P4RT port ID of the control switch interface connected to the + // interface on the SUT with the given P4RT port ID according to the port + // mapping. + absl::StatusOr GetControlSwitchPortConnectedToSutPort( + const pins_test::P4rtPortId& sut_port) const; + private: MirrorTestbedP4rtPortIdMap( absl::flat_hash_map diff --git a/dvaas/port_id_map_test.cc b/dvaas/port_id_map_test.cc index 8d023d53..3e265b9f 100644 --- a/dvaas/port_id_map_test.cc +++ b/dvaas/port_id_map_test.cc @@ -43,10 +43,17 @@ TEST(MirrorTestbedP4rtPortIdMap, MirrorTestbedP4rtPortIdMap::CreateFromControlSwitchToSutPortMap( {{port_1, port_2}})); + // Control -> SUT. ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_2), StatusIs(absl::StatusCode::kNotFound)); ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_3), StatusIs(absl::StatusCode::kNotFound)); + + // SUT -> Control. + ASSERT_THAT(port_id_map.GetControlSwitchPortConnectedToSutPort(port_1), + StatusIs(absl::StatusCode::kNotFound)); + ASSERT_THAT(port_id_map.GetControlSwitchPortConnectedToSutPort(port_3), + StatusIs(absl::StatusCode::kNotFound)); } TEST(MirrorTestbedP4rtPortIdMap, @@ -79,10 +86,17 @@ TEST(MirrorTestbedP4rtPortIdMap, MirrorTestbedP4rtPortIdMap::CreateFromSutToControlSwitchPortMap( {{port_1, port_2}})); + // Control -> SUT. ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_1), StatusIs(absl::StatusCode::kNotFound)); ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_3), StatusIs(absl::StatusCode::kNotFound)); + + // SUT -> Control. + ASSERT_THAT(port_id_map.GetControlSwitchPortConnectedToSutPort(port_2), + StatusIs(absl::StatusCode::kNotFound)); + ASSERT_THAT(port_id_map.GetControlSwitchPortConnectedToSutPort(port_3), + StatusIs(absl::StatusCode::kNotFound)); } TEST(MirrorTestbedP4rtPortIdMap, @@ -101,32 +115,7 @@ TEST(MirrorTestbedP4rtPortIdMap, 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) { +TEST(MirrorTestbedP4rtPortIdMap, ReturnsCorrectPortGivenSutToControlMapping) { ASSERT_OK_AND_ASSIGN(const auto port_1, P4rtPortId::MakeFromP4rtEncoding("1")); ASSERT_OK_AND_ASSIGN(const auto port_2, @@ -143,23 +132,37 @@ TEST(MirrorTestbedP4rtPortIdMap, {port_3, port_4}, })); + // Control -> SUT. ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_2), IsOkAndHolds(Eq(port_1))); ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_4), IsOkAndHolds(Eq(port_3))); + + // SUT -> Control. + ASSERT_THAT(port_id_map.GetControlSwitchPortConnectedToSutPort(port_1), + IsOkAndHolds(Eq(port_2))); + ASSERT_THAT(port_id_map.GetControlSwitchPortConnectedToSutPort(port_3), + IsOkAndHolds(Eq(port_4))); } -TEST(MirrorTestbedP4rtPortIdMap, RetrunsCorrectSutPortWithImplicitIdentityMap) { +TEST(MirrorTestbedP4rtPortIdMap, ReturnsCorrectPortWithImplicitIdentityMap) { 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")); + // Control -> SUT. ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_1), IsOkAndHolds(Eq(port_1))); ASSERT_THAT(port_id_map.GetSutPortConnectedToControlSwitchPort(port_2), IsOkAndHolds(Eq(port_2))); + + // SUT -> Control. + ASSERT_THAT(port_id_map.GetControlSwitchPortConnectedToSutPort(port_1), + IsOkAndHolds(Eq(port_1))); + ASSERT_THAT(port_id_map.GetControlSwitchPortConnectedToSutPort(port_2), + IsOkAndHolds(Eq(port_2))); } } // namespace From 791fc4c52c520fccd9b20489ee90e5489c93bf85 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Tue, 3 Dec 2024 01:50:28 +0000 Subject: [PATCH 05/23] [P4_Symbolic] Construct the CFG for parsers and set the optimized symbolic execution info. (#796) Co-authored-by: kishanps --- p4_symbolic/ir/BUILD.bazel | 3 - p4_symbolic/ir/cfg.cc | 183 +++++++--- p4_symbolic/ir/cfg.h | 73 ++-- p4_symbolic/ir/cfg_test.cc | 316 +++++++++++++++++- p4_symbolic/ir/expected/basic.txt | 7 + .../ir/expected/complex_conditional.txt | 4 + p4_symbolic/ir/expected/conditional.txt | 4 + .../ir/expected/conditional_sequence.txt | 4 + .../ir/expected/default_transition.txt | 4 + .../ir/expected/extract_parser_operation.txt | 4 + p4_symbolic/ir/expected/hardcoded.txt | 4 + .../ir/expected/hex_string_transition.txt | 7 + p4_symbolic/ir/expected/reflector.txt | 4 + p4_symbolic/ir/expected/sai_parser.txt | 32 ++ p4_symbolic/ir/expected/set_invalid.txt | 4 + .../ir/expected/set_parser_operation.txt | 7 + p4_symbolic/ir/expected/string_optional.txt | 4 + p4_symbolic/ir/expected/table.txt | 4 + p4_symbolic/ir/expected/table_hit_1.txt | 4 + p4_symbolic/ir/expected/table_hit_2.txt | 4 + p4_symbolic/ir/ir.cc | 6 + p4_symbolic/ir/table_entries.cc | 4 - p4_symbolic/ir/table_entries.h | 2 - 23 files changed, 604 insertions(+), 84 deletions(-) diff --git a/p4_symbolic/ir/BUILD.bazel b/p4_symbolic/ir/BUILD.bazel index 7a3595a5..3d491988 100644 --- a/p4_symbolic/ir/BUILD.bazel +++ b/p4_symbolic/ir/BUILD.bazel @@ -58,8 +58,6 @@ cc_library( "table_entries.h", ], deps = [ - ":ir_cc_proto", - "//gutil:io", "//gutil:status", "//p4_pdpi:ir", "//p4_pdpi:ir_cc_proto", @@ -106,7 +104,6 @@ cc_library( "//gutil:status", "//p4_symbolic/bmv2:bmv2_cc_proto", "@com_google_absl//absl/container:btree", - "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/container:node_hash_map", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", diff --git a/p4_symbolic/ir/cfg.cc b/p4_symbolic/ir/cfg.cc index b610221a..12492f4f 100644 --- a/p4_symbolic/ir/cfg.cc +++ b/p4_symbolic/ir/cfg.cc @@ -14,14 +14,12 @@ #include "p4_symbolic/ir/cfg.h" -#include #include #include #include -#include "absl/container/flat_hash_set.h" #include "absl/status/status.h" -#include "absl/strings/str_cat.h" +#include "absl/status/statusor.h" #include "absl/strings/str_join.h" #include "absl/strings/substitute.h" #include "gutil/status.h" @@ -54,7 +52,43 @@ absl::StatusOr> GetChildren( } } else { return absl::InvalidArgumentError( - absl::Substitute("Unrecognized control '$0'", control_name)); + absl::Substitute("Unknown control '$0'", control_name)); + } + + return children; +} + +// Returns the set of the children state names of the given `state_name` in the +// given parser. +absl::StatusOr> GetChildren( + const Parser &parser, const std::string &state_name) { + absl::btree_set children; + + if (state_name == EndOfParser()) return children; + + auto it = parser.parse_states().find(state_name); + if (it == parser.parse_states().end()) { + return gutil::InvalidArgumentErrorBuilder() + << "Unknown parse state: '" << state_name << "'"; + } + + const ParseState &state = it->second; + + for (const auto &transition : state.transitions()) { + switch (transition.transition_case()) { + case ParserTransition::kDefaultTransition: { + children.insert(transition.default_transition().next_state()); + break; + } + case ParserTransition::kHexStringTransition: { + children.insert(transition.hex_string_transition().next_state()); + break; + } + default: { + return gutil::InvalidArgumentErrorBuilder() + << "Unknown transition: " << transition.ShortDebugString(); + } + } } return children; @@ -77,7 +111,7 @@ std::string ToString(const CfgNode &node) { return absl::Substitute( "node: $0\n\tchildren: [$1]\n\tparents: [$2]\n\tpath_from_root: " "$3\n\trev_path_from_leaf: $4\n\tmerge_point: $5\n\tcontinue: $6\n", - node.control_name, absl::StrJoin(node.children, ","), + node.node_name, absl::StrJoin(node.children, ","), absl::StrJoin(node.parents, ","), absl::StrJoin(node.contracted_path_from_root, "->"), absl::StrJoin(node.contracted_reverse_path_from_leaf, "<-"), @@ -97,58 +131,77 @@ std::string ControlFlowGraph::ToString() { absl::StatusOr ControlFlowGraph::GetOptimizedSymbolicExecutionInfo( - absl::string_view control_name) { - absl::StatusOr cfg_node_ptr_or_status = - GetNode(control_name); - RETURN_IF_ERROR(cfg_node_ptr_or_status.status()); - const CfgNode &cfg_node = **cfg_node_ptr_or_status; + absl::string_view node_name) { + ASSIGN_OR_RETURN(const CfgNode *cfg_node, GetNode(node_name)); - if (!cfg_node.merge_point.has_value()) - return absl::InvalidArgumentError( - absl::Substitute("Control node '$0' does not have a merge point", - cfg_node.control_name)); + if (!cfg_node->merge_point.has_value()) { + return absl::InvalidArgumentError(absl::Substitute( + "Control node '$0' does not have a merge point", cfg_node->node_name)); + } OptimizedSymbolicExecutionInfo info; - info.set_merge_point(*cfg_node.merge_point); - info.set_continue_to_merge_point(cfg_node.continue_to_merge_point); + info.set_merge_point(*cfg_node->merge_point); + info.set_continue_to_merge_point(cfg_node->continue_to_merge_point); return info; } absl::StatusOr ControlFlowGraph::GetNode( - absl::string_view control_name) { - return GetMutableNode(control_name); + absl::string_view node_name) { + return GetMutableNode(node_name); } absl::StatusOr ControlFlowGraph::GetMutableNode( - absl::string_view control_name) { - auto it = node_by_name_.find(control_name); + absl::string_view node_name) { + auto it = node_by_name_.find(node_name); if (it == node_by_name_.end()) { return absl::NotFoundError(absl::Substitute( - "Control name '$0' does not correspond to any node in the CFG", - control_name)); + "Node name '$0' does not correspond to any node in the CFG", + node_name)); } return &(it->second); } -CfgNode &ControlFlowGraph::GetOrAddNode(absl::string_view control_name) { - CfgNode &node = node_by_name_[control_name]; - node.control_name = control_name; - return node; +absl::StatusOr ControlFlowGraph::GetOrAddNode( + absl::string_view node_name, CfgNodeType node_type) { + auto it = node_by_name_.find(node_name); + + if (it == node_by_name_.end()) { + // Create a new node. + CfgNode cfg_node = { + .node_name = std::string(node_name), + .node_type = node_type, + }; + it = + node_by_name_.insert(it, {std::string(node_name), std::move(cfg_node)}); + } else if (it->second.node_type != node_type) { + // Existing node. Returns an error if the node has the same type. + return gutil::InvalidArgumentErrorBuilder() + << "Nodes of different types have the same name: " << node_name; + } + + return &(it->second); } absl::Status ControlFlowGraph::ConstructSubgraph( const P4Program &program, const std::string &control_name) { - CfgNode &cfg_node = GetOrAddNode(control_name); + // Obtain the CFG node of the control and the names of its next states. + ASSIGN_OR_RETURN(CfgNode * cfg_node, + GetOrAddNode(control_name, CfgNodeType::kPipelineControl)); ASSIGN_OR_RETURN(absl::btree_set children, GetChildren(program, control_name)); + + // Add edges between the parent and child nodes. for (const std::string &child_control_name : children) { - CfgNode &child_cfg_node = GetOrAddNode(child_control_name); - bool new_node = child_cfg_node.parents.empty(); - cfg_node.children.insert(child_control_name); - child_cfg_node.parents.insert(control_name); - if (new_node) { - // The child_cfg_node is a new node. + ASSIGN_OR_RETURN( + CfgNode * child_cfg_node, + GetOrAddNode(child_control_name, CfgNodeType::kPipelineControl)); + bool is_new_node = child_cfg_node->parents.empty(); + cfg_node->children.insert(child_control_name); + child_cfg_node->parents.insert(control_name); + + // If the child is a new node, recursively construct the subgraph. + if (is_new_node) { RETURN_IF_ERROR(ConstructSubgraph(program, child_control_name)); } } @@ -156,6 +209,31 @@ absl::Status ControlFlowGraph::ConstructSubgraph( return absl::OkStatus(); } +absl::Status ControlFlowGraph::ConstructSubgraph( + const Parser &parser, const std::string &state_name) { + // Obtain the CFG node of the parse state and the names of its next states. + ASSIGN_OR_RETURN(CfgNode * cfg_node, + GetOrAddNode(state_name, CfgNodeType::kParseState)); + ASSIGN_OR_RETURN(absl::btree_set children, + GetChildren(parser, state_name)); + + // Add edges between the parent and child nodes. + for (const std::string &child_state_name : children) { + ASSIGN_OR_RETURN(CfgNode * child_cfg_node, + GetOrAddNode(child_state_name, CfgNodeType::kParseState)); + bool is_new_node = child_cfg_node->parents.empty(); + cfg_node->children.insert(child_state_name); + child_cfg_node->parents.insert(state_name); + + // If the child is a new node, recursively construct the subgraph. + if (is_new_node) { + RETURN_IF_ERROR(ConstructSubgraph(parser, child_state_name)); + } + } + + return absl::OkStatus(); +} + absl::Status ControlFlowGraph::SetContractedReversePathFromLeaf( CfgNode &cfg_node) { ControlPath &reverse_path_from_leaf = @@ -170,7 +248,7 @@ absl::Status ControlFlowGraph::SetContractedReversePathFromLeaf( RETURN_IF_ERROR(SetContractedReversePathFromLeaf(*child_cfg_node)); RET_CHECK(!child_reverse_path_from_leaf.empty()) << absl::Substitute( "Contracted reverse path from leaf of '$0' is empty", - child_cfg_node->control_name); + child_cfg_node->node_name); } // Set current node's reverse path from leaf to the longest common @@ -187,7 +265,7 @@ absl::Status ControlFlowGraph::SetContractedReversePathFromLeaf( } // Add self to the end of the reverse path from leaf. - reverse_path_from_leaf.push_back(cfg_node.control_name); + reverse_path_from_leaf.push_back(cfg_node.node_name); return absl::OkStatus(); } @@ -204,7 +282,7 @@ absl::Status ControlFlowGraph::SetContractedPathFromRoot(CfgNode &cfg_node) { RETURN_IF_ERROR(SetContractedPathFromRoot(*parent_cfg_node)); RET_CHECK(!parent_path_from_root.empty()) << absl::Substitute("Contracted path from root of '$0' is empty", - parent_cfg_node->control_name); + parent_cfg_node->node_name); } // Set current node's path from root to the longest common @@ -218,7 +296,7 @@ absl::Status ControlFlowGraph::SetContractedPathFromRoot(CfgNode &cfg_node) { } // Add self to the end of the path from root. - path_from_root.push_back(cfg_node.control_name); + path_from_root.push_back(cfg_node.node_name); return absl::OkStatus(); } @@ -228,6 +306,17 @@ absl::StatusOr> ControlFlowGraph::Create( // Using `new` to access a non-public constructor. auto cfg = absl::WrapUnique(new ControlFlowGraph()); + // Make sure there is exactly one parser. + if (program.parsers_size() != 1) { + return absl::InvalidArgumentError( + absl::StrCat("Invalid number of parsers: ", program.parsers_size())); + } + + // Build the basic CFG for parsers. + for (const auto &[_, parser] : program.parsers()) { + RETURN_IF_ERROR(cfg->ConstructSubgraph(parser, parser.initial_state())); + } + // Build the basic CFG. for (const auto &[name, pipeline] : program.pipeline()) { RETURN_IF_ERROR( @@ -241,13 +330,13 @@ absl::StatusOr> ControlFlowGraph::Create( RET_CHECK(!cfg_node.contracted_reverse_path_from_leaf.empty()) << absl::Substitute( "contracted_reverse_path_from_leaf of '$0' is empty", - cfg_node.control_name); + cfg_node.node_name); } if (cfg_node.contracted_path_from_root.empty()) { RETURN_IF_ERROR(cfg->SetContractedPathFromRoot(cfg_node)); RET_CHECK(!cfg_node.contracted_path_from_root.empty()) << absl::Substitute("contracted_path_from_root of '$0' is empty", - cfg_node.control_name); + cfg_node.node_name); } } @@ -288,13 +377,13 @@ absl::StatusOr> ControlFlowGraph::Create( std::optional merge_point_reverse_merge_point = get_merge_point(merge_point_cfg_node->contracted_path_from_root); - // May continue to the merge point iff the reverse merge point (i.e. merge - // point in reverse direction) of the merge point (if any) of the node is - // the node itself (i.e. the given node is the latest node + // May continue to the merge point if and only if the reverse merge point + // (i.e. merge point in reverse direction) of the merge point (if any) of + // the node is the node itself (i.e. the given node is the latest node // prior to the merge point such that all execution paths going through - // the merge point also go through the current node). - // This is to prevent executing shared merge points more than once. - // For example, in the following graph + // the merge point also go through the current node). This is to prevent + // executing shared merge points more than once. For example, in the + // following graph // c1 - implicit source // / \ // c2 t3 @@ -303,9 +392,9 @@ absl::StatusOr> ControlFlowGraph::Create( // \ / / // t4 - implicit sink // The merge points of c1, c2, t1, t2, t3 are t4. We must only continue to - // t4 from c1 because the revesrse merge point of t4 is c1. + // t4 from c1 because the reverse merge point of t4 is c1. if (merge_point_reverse_merge_point.has_value() && - *merge_point_reverse_merge_point == cfg_node.control_name) { + *merge_point_reverse_merge_point == cfg_node.node_name) { cfg_node.continue_to_merge_point = true; } } diff --git a/p4_symbolic/ir/cfg.h b/p4_symbolic/ir/cfg.h index f5c1c519..01587ae1 100644 --- a/p4_symbolic/ir/cfg.h +++ b/p4_symbolic/ir/cfg.h @@ -24,7 +24,6 @@ #include #include "absl/container/btree_set.h" -#include "absl/container/flat_hash_set.h" #include "absl/container/node_hash_map.h" #include "absl/status/statusor.h" #include "p4_symbolic/ir/ir.pb.h" @@ -35,18 +34,33 @@ namespace p4_symbolic::ir { // name along the path. using ControlPath = std::vector; +// Individual CFG node types that need to be separated and require different +// processing. +enum class CfgNodeType { + kPipelineControl, // A pipeline control (e.g., table, conditional). + kParseState, // A parse state of a parser. +}; + // A single node in the control flow graph along with various computed metadata. -// We make the following assuptions in the descriptions and definitions below. +// We make the following assumptions in the descriptions and definitions below. // 1. The collection of CfgNodes form a non-empty DAG. // 2. The DAG has an implicit and unique root/source node implicitly connected -// to all nodes with (explicit) in-degree 0. +// to all nodes with (explicit) in-degree 0 (i.e., the initial parse states of +// parsers and the initial controls of pipelines). // 3. The DAG has an implicit and unique leaf/sink node that all nodes with -// (explicit) out-degree 0 are implicitly connected to. +// (explicit) out-degree 0 (i.e., EndOfPipeline and EndOfParser) are implicitly +// connected to. // 4. We do not explicitly refer to these two implicit nodes in the code. E.g. // do not include them in paths that start from or end in them. struct CfgNode { - // Same as a the name of the control node in the IR. - std::string control_name; + // The name of the CFG node. May be the name of a pipeline control node in the + // IR or the name of a parse state in a parser. + std::string node_name; + + // The type of the CFG node. This is to ensure that nodes of different types + // will never have name conflicts, so that no node will ever be linked to + // another of a different type. + CfgNodeType node_type; // The children of the current node. // Using btree_set (here and below) for deterministic order, though not a @@ -106,7 +120,7 @@ struct CfgNode { bool continue_to_merge_point; }; -// Returns a string represting the information stored in the given `cfg_node`. +// Returns a string representing the information stored in the given `cfg_node`. // Useful for debugging purposes. std::string ToString(const CfgNode &cfg_node); @@ -130,14 +144,13 @@ class ControlFlowGraph { const P4Program &program); // Returns information used in optimized symbolic execution for the node - // correspnding to the given `control_name` in the graph, or error if no such - // node exists. Expects input to correspond to a node with an (explicit) - // merge point (i.e. a node with non-zero out-degree), otherwise returns - // error. + // corresponding to the given `node_name` in the graph, or error if no such + // node exists. Expects input to correspond to a node with an (explicit) merge + // point (i.e. a node with non-zero out-degree), otherwise returns error. absl::StatusOr - GetOptimizedSymbolicExecutionInfo(absl::string_view control_name); + GetOptimizedSymbolicExecutionInfo(absl::string_view node_name); - // Returns a string represting the constructred CFG. Useful for debugging + // Returns a string representing the constructed CFG. Useful for debugging // purposes. std::string ToString(); @@ -149,9 +162,9 @@ class ControlFlowGraph { ControlFlowGraph &operator=(ControlFlowGraph &&) = delete; private: - // Map of each control name to its correspodning CfgNode. + // Map of each control or parse state name to its corresponding CfgNode. // Must use node_hash_map for pointer stability. - // Note: Pointer stability is a necessariy condition in this case, otherwise + // Note: Pointer stability is a necessary condition in this case, otherwise // ConstructSubgraph as well as any code depending on pointers/references to // CfgNodes in node_by_name_ would break. absl::node_hash_map node_by_name_; @@ -159,25 +172,31 @@ class ControlFlowGraph { // Can only be constructed through a call to Create. ControlFlowGraph() = default; - // Returns a non-null immutable pointer to the node correspnding to the given - // `control_name` in the graph, or error if no such node exists. - absl::StatusOr GetNode(absl::string_view control_name); + // Returns a non-null immutable pointer to the node corresponding to the given + // `node_name` in the graph, or error if no such node exists. + absl::StatusOr GetNode(absl::string_view node_name); - // Returns a reference to the node correspnding to the given `control_name` in - // the graph. If such a node does not exist, creates and returns a new node - // with the given name. - CfgNode &GetOrAddNode(absl::string_view control_name); + // Returns a non-null mutable pointer to the node corresponding to the given + // `node_name` in the graph with the given `node_type`. If such a node does + // not exist, creates and returns a new node with the given name and type. If + // a node with the same name but of a different type exists, returns an error. + absl::StatusOr GetOrAddNode(absl::string_view node_name, + CfgNodeType node_type); - // Returns a non-null mutable pointer to the node correspnding to the given - // `control_name` in the graph, or error if no such node exists. - absl::StatusOr GetMutableNode(absl::string_view control_name); + // Returns a non-null mutable pointer to the node corresponding to the given + // `node_name` in the graph, or error if no such node exists. + absl::StatusOr GetMutableNode(absl::string_view node_name); // Recursively constructs the subgraph corresponding to the given - // `control_name`. Updates the children and parents of the involved - // nodes. + // `control_name`. Updates the children and parents of the involved nodes. absl::Status ConstructSubgraph(const P4Program &program, const std::string &control_name); + // Recursively constructs the subgraph for a parse state corresponding to the + // given `state_name`. Updates the children and parents of the involved nodes. + absl::Status ConstructSubgraph(const Parser &parser, + const std::string &state_name); + // Recursively computes the contracted reverse path from the (implicit) leaf // to the given `cfg_node`. Sets the `contracted_reverse_path_from_leaf` field // in the involved nodes (if not already set). diff --git a/p4_symbolic/ir/cfg_test.cc b/p4_symbolic/ir/cfg_test.cc index d57d089e..c88c1a87 100644 --- a/p4_symbolic/ir/cfg_test.cc +++ b/p4_symbolic/ir/cfg_test.cc @@ -42,7 +42,10 @@ absl::StatusOr ReplaceEopAndParseProgramTextProto( absl::string_view ir_program_text_proto) { return gutil::ParseTextProto(absl::StrReplaceAll( ir_program_text_proto, - {{"$eop", absl::Substitute("\"$0\"", EndOfPipeline())}})); + { + {"$eoparser", absl::Substitute("\"$0\"", EndOfParser())}, + {"$eop", absl::Substitute("\"$0\"", EndOfPipeline())}, + })); } using CfgTest = ::testing::TestWithParam; @@ -64,6 +67,10 @@ std::vector GetCfgTestInstances() { key: "ingress" value { name: "ingress" initial_control: "t1" } } + parsers { + key: "parser" + value { name: "parser" initial_state: $eoparser } + } tables { key: "t1" value { @@ -98,6 +105,10 @@ std::vector GetCfgTestInstances() { key: "ingress" value { name: "ingress" initial_control: "c1" } } + parsers { + key: "parser" + value { name: "parser" initial_state: $eoparser } + } conditionals { key: "c1" value { name: "c1" if_branch: "t1" else_branch: "t2" } @@ -141,6 +152,10 @@ std::vector GetCfgTestInstances() { key: "ingress" value { name: "ingress" initial_control: "c1" } } + parsers { + key: "parser" + value { name: "parser" initial_state: $eoparser } + } conditionals { key: "c1" value { name: "c1" if_branch: "t1" else_branch: "t2" } @@ -185,6 +200,10 @@ std::vector GetCfgTestInstances() { key: "ingress" value { name: "ingress" initial_control: "t1" } } + parsers { + key: "parser" + value { name: "parser" initial_state: $eoparser } + } tables { key: "t1" value { @@ -248,6 +267,10 @@ std::vector GetCfgTestInstances() { key: "ingress" value { name: "ingress" initial_control: "c1" } } + parsers { + key: "parser" + value { name: "parser" initial_state: $eoparser } + } conditionals { key: "c1" value { name: "c1" if_branch: "c2" else_branch: "t3" } @@ -312,6 +335,10 @@ std::vector GetCfgTestInstances() { key: "ingress" value { name: "ingress" initial_control: "c1" } } + parsers { + key: "parser" + value { name: "parser" initial_state: $eoparser } + } conditionals { key: "c1" value { name: "c1" if_branch: "c2" else_branch: "t3" } @@ -373,6 +400,10 @@ std::vector GetCfgTestInstances() { key: "ingress" value { name: "ingress" initial_control: "c1" } } + parsers { + key: "parser" + value { name: "parser" initial_state: $eoparser } + } conditionals { key: "c1" value { name: "c1" if_branch: "t1" else_branch: "t2" } @@ -413,6 +444,187 @@ std::vector GetCfgTestInstances() { } )pb", }, + { + /* eop - ingress + * + * start - parser + * | + * eoparser + */ + .test_name = "ParserWithDefaultTransition", + .control_to_expected_info = + { + {"start", {EndOfParser(), true}}, + }, + .ir_program_text_proto = R"pb( + pipeline { + key: "ingress" + value { name: "ingress" initial_control: $eop } + } + parsers { + key: "parser" + value { + name: "parser" + initial_state: "start" + parse_states { + key: "start" + value { + name: "start" + parser_ops { + extract { header { header_name: "ethernet" } } + } + transitions { default_transition { next_state: $eoparser } } + } + } + } + } + )pb", + }, + { + /* eop - ingress + * + * start - parser + * | \ + * | parse_ipv4 + * | / + * eoparser + */ + .test_name = "ParserWithHexStringTransition", + .control_to_expected_info = + { + {"start", {EndOfParser(), true}}, + {"parse_ipv4", {EndOfParser(), false}}, + }, + .ir_program_text_proto = R"pb( + pipeline { + key: "ingress" + value { name: "ingress" initial_control: $eop } + } + parsers { + key: "parser" + value { + name: "parser" + initial_state: "start" + parse_states { + key: "parse_ipv4" + value { + name: "parse_ipv4" + parser_ops { extract { header { header_name: "ipv4" } } } + transitions { default_transition { next_state: $eoparser } } + } + } + parse_states { + key: "start" + value { + name: "start" + parser_ops { + extract { header { header_name: "ethernet" } } + } + transition_key_fields { + field { header_name: "ethernet" field_name: "ether_type" } + } + transitions { + hex_string_transition { + value { value: "0x0800" } + mask {} + next_state: "parse_ipv4" + } + } + transitions { default_transition { next_state: $eoparser } } + } + } + } + } + )pb", + }, + { + /* + * start - parser + * | \ + * | parse_ipv4 + * | / + * eoparser + * + * c1 - ingress + * / \ + * t1 t2 + * \ / + * t3 - eop + */ + .test_name = "ProrgamWithBothParserAndIngressControl", + .control_to_expected_info = + { + {"start", {EndOfParser(), true}}, + {"parse_ipv4", {EndOfParser(), false}}, + {"c1", {"t3", true}}, + {"t1", {"t3", false}}, + {"t2", {"t3", false}}, + {"t3", {EndOfPipeline(), true}}, + }, + .ir_program_text_proto = R"pb( + pipeline { + key: "ingress" + value { name: "ingress" initial_control: "c1" } + } + parsers { + key: "parser" + value { + name: "parser" + initial_state: "start" + parse_states { + key: "start" + value { + name: "start" + parser_ops { + extract { header { header_name: "ethernet" } } + } + transition_key_fields { + field { header_name: "ethernet" field_name: "ether_type" } + } + transitions { + hex_string_transition { + value { value: "0x0800" } + mask {} + next_state: "parse_ipv4" + } + } + transitions { default_transition { next_state: $eoparser } } + } + } + parse_states { + key: "parse_ipv4" + value { + name: "parse_ipv4" + parser_ops { extract { header { header_name: "ipv4" } } } + transitions { default_transition { next_state: $eoparser } } + } + } + } + } + conditionals { + key: "c1" + value { name: "c1" if_branch: "t1" else_branch: "t2" } + } + tables { + key: "t1" + value { + table_implementation { action_to_next_control { value: "t3" } } + } + } + tables { + key: "t2" + value { + table_implementation { action_to_next_control { value: "t3" } } + } + } + tables { + key: "t3" + value { + table_implementation { action_to_next_control { value: $eop } } + } + } + )pb", + }, }; } @@ -421,6 +633,20 @@ constexpr absl::string_view kMinimalProgram = R"pb( key: "ingress" value { name: "ingress" initial_control: "c1" } } + parsers { + key: "parser" + value { + name: "parser" + initial_state: "start" + parse_states { + key: "start" + value { + name: "start" + transitions { default_transition { next_state: $eoparser } } + } + } + } + } conditionals { key: "c1" value { name: "c1" if_branch: "t1" else_branch: "t1" } @@ -431,6 +657,54 @@ constexpr absl::string_view kMinimalProgram = R"pb( } )pb"; +constexpr absl::string_view kUnknownParseStateProgram = R"pb( + pipeline { + key: "ingress" + value { name: "ingress" initial_control: $eop } + } + parsers { + key: "parser" + value { + name: "parser" + initial_state: "start" + parse_states { + key: "start" + value { + name: "start" + parser_ops { extract { header { header_name: "ethernet" } } } + transitions { default_transition { next_state: "unknown" } } + } + } + } + } +)pb"; + +constexpr absl::string_view kNodeConflictProgram = R"pb( + pipeline { + key: "ingress" + value { name: "ingress" initial_control: "start" } + } + parsers { + key: "parser" + value { + name: "parser" + initial_state: "start" + parse_states { + key: "start" + value { + name: "start" + parser_ops { extract { header { header_name: "ethernet" } } } + transitions { default_transition { next_state: $eoparser } } + } + } + } + } + tables { + key: "start" + value { table_implementation { action_to_next_control { value: $eop } } } + } +)pb"; + TEST(GetOptimizedSymbolicExecutionInfoTest, ReturnsErrorForEndOfPipeline) { ASSERT_OK_AND_ASSIGN(const P4Program program, ReplaceEopAndParseProgramTextProto(kMinimalProgram)); @@ -441,6 +715,16 @@ TEST(GetOptimizedSymbolicExecutionInfoTest, ReturnsErrorForEndOfPipeline) { StatusIs(absl::StatusCode::kInvalidArgument)); } +TEST(GetOptimizedSymbolicExecutionInfoTest, ReturnsErrorForEndOfParser) { + ASSERT_OK_AND_ASSIGN(const P4Program program, + ReplaceEopAndParseProgramTextProto(kMinimalProgram)); + ASSERT_OK_AND_ASSIGN(std::unique_ptr cfg, + ControlFlowGraph::Create(program)); + + ASSERT_THAT(cfg->GetOptimizedSymbolicExecutionInfo(EndOfParser()), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + TEST(GetOptimizedSymbolicExecutionInfoTest, ReturnsErrorForNonExistingNode) { ASSERT_OK_AND_ASSIGN(const P4Program program, ReplaceEopAndParseProgramTextProto(kMinimalProgram)); @@ -471,6 +755,36 @@ TEST(GetOptimizedSymbolicExecutionInfoTest, SucceedsForExistingTableNode) { StatusIs(absl::StatusCode::kOk)); } +TEST(GetOptimizedSymbolicExecutionInfoTest, SucceedsForExistingParseState) { + ASSERT_OK_AND_ASSIGN(const P4Program program, + ReplaceEopAndParseProgramTextProto(kMinimalProgram)); + ASSERT_OK_AND_ASSIGN(std::unique_ptr cfg, + ControlFlowGraph::Create(program)); + + ASSERT_THAT(cfg->GetOptimizedSymbolicExecutionInfo("start"), + StatusIs(absl::StatusCode::kOk)); +} + +TEST(CfgTest, ReturnsErrorForUnknownParseState) { + ASSERT_OK_AND_ASSIGN( + const P4Program program, + ReplaceEopAndParseProgramTextProto(kUnknownParseStateProgram)); + + ASSERT_THAT(ControlFlowGraph::Create(program), + StatusIs(absl::StatusCode::kInvalidArgument, + "Unknown parse state: 'unknown'")); +} + +TEST(CfgTest, ReturnsErrorForNodeConflict) { + ASSERT_OK_AND_ASSIGN( + const P4Program program, + ReplaceEopAndParseProgramTextProto(kNodeConflictProgram)); + + ASSERT_THAT(ControlFlowGraph::Create(program), + StatusIs(absl::StatusCode::kInvalidArgument, + "Nodes of different types have the same name: start")); +} + TEST_P(CfgTest, GetOptimizedSymbolicExecutionInfoReturnsExpectedInfo) { const CfgTestParam& param = GetParam(); diff --git a/p4_symbolic/ir/expected/basic.txt b/p4_symbolic/ir/expected/basic.txt index 3459fecd..a817b615 100644 --- a/p4_symbolic/ir/expected/basic.txt +++ b/p4_symbolic/ir/expected/basic.txt @@ -664,6 +664,9 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + } } } parse_states { @@ -698,6 +701,10 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } } } } diff --git a/p4_symbolic/ir/expected/complex_conditional.txt b/p4_symbolic/ir/expected/complex_conditional.txt index 43a08830..16b2da87 100644 --- a/p4_symbolic/ir/expected/complex_conditional.txt +++ b/p4_symbolic/ir/expected/complex_conditional.txt @@ -2092,6 +2092,10 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } } } } diff --git a/p4_symbolic/ir/expected/conditional.txt b/p4_symbolic/ir/expected/conditional.txt index e179bee4..eb4e9766 100644 --- a/p4_symbolic/ir/expected/conditional.txt +++ b/p4_symbolic/ir/expected/conditional.txt @@ -724,6 +724,10 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } } } } diff --git a/p4_symbolic/ir/expected/conditional_sequence.txt b/p4_symbolic/ir/expected/conditional_sequence.txt index 0a6f1a98..d7c57de3 100644 --- a/p4_symbolic/ir/expected/conditional_sequence.txt +++ b/p4_symbolic/ir/expected/conditional_sequence.txt @@ -2361,6 +2361,10 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } } } } diff --git a/p4_symbolic/ir/expected/default_transition.txt b/p4_symbolic/ir/expected/default_transition.txt index 7cb24ed3..c5457a1b 100644 --- a/p4_symbolic/ir/expected/default_transition.txt +++ b/p4_symbolic/ir/expected/default_transition.txt @@ -193,6 +193,10 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } } } } diff --git a/p4_symbolic/ir/expected/extract_parser_operation.txt b/p4_symbolic/ir/expected/extract_parser_operation.txt index 7cb24ed3..c5457a1b 100644 --- a/p4_symbolic/ir/expected/extract_parser_operation.txt +++ b/p4_symbolic/ir/expected/extract_parser_operation.txt @@ -193,6 +193,10 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } } } } diff --git a/p4_symbolic/ir/expected/hardcoded.txt b/p4_symbolic/ir/expected/hardcoded.txt index 68e34315..7a4a4290 100644 --- a/p4_symbolic/ir/expected/hardcoded.txt +++ b/p4_symbolic/ir/expected/hardcoded.txt @@ -293,6 +293,10 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } } } } diff --git a/p4_symbolic/ir/expected/hex_string_transition.txt b/p4_symbolic/ir/expected/hex_string_transition.txt index e00dfc9f..90beac85 100644 --- a/p4_symbolic/ir/expected/hex_string_transition.txt +++ b/p4_symbolic/ir/expected/hex_string_transition.txt @@ -305,6 +305,9 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + } } } parse_states { @@ -350,6 +353,10 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } } } } diff --git a/p4_symbolic/ir/expected/reflector.txt b/p4_symbolic/ir/expected/reflector.txt index 47a1d412..9b3cccca 100644 --- a/p4_symbolic/ir/expected/reflector.txt +++ b/p4_symbolic/ir/expected/reflector.txt @@ -212,6 +212,10 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } } } } diff --git a/p4_symbolic/ir/expected/sai_parser.txt b/p4_symbolic/ir/expected/sai_parser.txt index 6789df92..19e8a201 100644 --- a/p4_symbolic/ir/expected/sai_parser.txt +++ b/p4_symbolic/ir/expected/sai_parser.txt @@ -1174,6 +1174,9 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + } } } parse_states { @@ -1192,6 +1195,9 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + } } } parse_states { @@ -1256,6 +1262,10 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } } } parse_states { @@ -1274,6 +1284,9 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + } } } parse_states { @@ -1328,6 +1341,9 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + } } } parse_states { @@ -1382,6 +1398,9 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + } } } parse_states { @@ -1400,6 +1419,9 @@ parsers { next_state: "parse_ethernet" } } + optimized_symbolic_execution_info { + merge_point: "parse_ethernet" + } } } parse_states { @@ -1442,6 +1464,9 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + } } } parse_states { @@ -1484,6 +1509,9 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + } } } parse_states { @@ -1620,6 +1648,10 @@ parsers { next_state: "parse_ethernet" } } + optimized_symbolic_execution_info { + merge_point: "parse_ethernet" + continue_to_merge_point: true + } } } } diff --git a/p4_symbolic/ir/expected/set_invalid.txt b/p4_symbolic/ir/expected/set_invalid.txt index 527795d7..fdcd0217 100644 --- a/p4_symbolic/ir/expected/set_invalid.txt +++ b/p4_symbolic/ir/expected/set_invalid.txt @@ -367,6 +367,10 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } } } } diff --git a/p4_symbolic/ir/expected/set_parser_operation.txt b/p4_symbolic/ir/expected/set_parser_operation.txt index c85e8beb..b595647b 100644 --- a/p4_symbolic/ir/expected/set_parser_operation.txt +++ b/p4_symbolic/ir/expected/set_parser_operation.txt @@ -342,6 +342,9 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + } } } parse_states { @@ -446,6 +449,10 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } } } } diff --git a/p4_symbolic/ir/expected/string_optional.txt b/p4_symbolic/ir/expected/string_optional.txt index 89ebd7a0..bbb3f6c7 100644 --- a/p4_symbolic/ir/expected/string_optional.txt +++ b/p4_symbolic/ir/expected/string_optional.txt @@ -567,6 +567,10 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } } } } diff --git a/p4_symbolic/ir/expected/table.txt b/p4_symbolic/ir/expected/table.txt index 5562dc43..e19cbad8 100644 --- a/p4_symbolic/ir/expected/table.txt +++ b/p4_symbolic/ir/expected/table.txt @@ -337,6 +337,10 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } } } } diff --git a/p4_symbolic/ir/expected/table_hit_1.txt b/p4_symbolic/ir/expected/table_hit_1.txt index bf9d7767..a21d9246 100644 --- a/p4_symbolic/ir/expected/table_hit_1.txt +++ b/p4_symbolic/ir/expected/table_hit_1.txt @@ -602,6 +602,10 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } } } } diff --git a/p4_symbolic/ir/expected/table_hit_2.txt b/p4_symbolic/ir/expected/table_hit_2.txt index 57b2a433..b49b0915 100644 --- a/p4_symbolic/ir/expected/table_hit_2.txt +++ b/p4_symbolic/ir/expected/table_hit_2.txt @@ -744,6 +744,10 @@ parsers { next_state: "__END_OF_PARSER__" } } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } } } } diff --git a/p4_symbolic/ir/ir.cc b/p4_symbolic/ir/ir.cc index 8d515452..3e52de06 100644 --- a/p4_symbolic/ir/ir.cc +++ b/p4_symbolic/ir/ir.cc @@ -1219,6 +1219,12 @@ absl::StatusOr Bmv2AndP4infoToIr(const bmv2::P4Program &bmv2, ControlFlowGraph::Create(output)); // Set the optimized symbolic execution information in the IR program using // the result of CFG analysis. + for (auto &[_, parser] : *output.mutable_parsers()) { + for (auto &[name, parse_state] : *parser.mutable_parse_states()) { + ASSIGN_OR_RETURN(*parse_state.mutable_optimized_symbolic_execution_info(), + cfg->GetOptimizedSymbolicExecutionInfo(name)); + } + } for (auto &[name, conditional] : *output.mutable_conditionals()) { ASSIGN_OR_RETURN(*conditional.mutable_optimized_symbolic_execution_info(), cfg->GetOptimizedSymbolicExecutionInfo(name)); diff --git a/p4_symbolic/ir/table_entries.cc b/p4_symbolic/ir/table_entries.cc index 331ecfb5..5c8bf04e 100644 --- a/p4_symbolic/ir/table_entries.cc +++ b/p4_symbolic/ir/table_entries.cc @@ -15,10 +15,6 @@ #include "p4_symbolic/ir/table_entries.h" #include "absl/status/status.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_format.h" -#include "absl/strings/str_split.h" -#include "gutil/io.h" #include "gutil/status.h" #include "p4/v1/p4runtime.pb.h" #include "p4_pdpi/ir.h" diff --git a/p4_symbolic/ir/table_entries.h b/p4_symbolic/ir/table_entries.h index 10de47f9..286d11d2 100644 --- a/p4_symbolic/ir/table_entries.h +++ b/p4_symbolic/ir/table_entries.h @@ -20,12 +20,10 @@ #include #include -#include #include #include "gutil/status.h" #include "p4_pdpi/ir.pb.h" -#include "p4_symbolic/ir/ir.pb.h" namespace p4_symbolic { namespace ir { From 5092ead97ade9dc4157e0e6224238f2d879c03b5 Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Thu, 5 Dec 2024 03:20:38 +0000 Subject: [PATCH 06/23] [Thinkit] Add test case checking that no packets reach CPU in pristine state. (#788) Co-authored-by: smolkaj --- tests/qos/BUILD.bazel | 15 +- tests/qos/cpu_qos_test.cc | 311 +++++++++++++++++++++++++++++++++++-- tests/qos/gnmi_parsers.cc | 2 +- tests/qos/gnmi_parsers.h | 2 +- tests/qos/openconfig.proto | 85 ---------- 5 files changed, 304 insertions(+), 111 deletions(-) delete mode 100644 tests/qos/openconfig.proto diff --git a/tests/qos/BUILD.bazel b/tests/qos/BUILD.bazel index 5b560947..8765bc91 100644 --- a/tests/qos/BUILD.bazel +++ b/tests/qos/BUILD.bazel @@ -38,6 +38,7 @@ cc_library( "//gutil:testing", "//lib:ixia_helper", "//lib/gnmi:gnmi_helper", + "//lib/gnmi:openconfig_cc_proto", "//lib/p4rt:packet_listener", "//lib/validator:validator_lib", "//p4_pdpi:ir", @@ -59,9 +60,11 @@ cc_library( "//thinkit:mirror_testbed_fixture", "//thinkit:switch", "//thinkit/proto:generic_testbed_cc_proto", + "@com_github_gnmi//proto/gnmi:gnmi_cc_proto", "@com_github_google_glog//:glog", "@com_github_nlohmann_json//:nlohmann_json", "@com_github_p4lang_p4runtime//:p4info_cc_proto", + "@com_github_p4lang_p4runtime//:p4runtime_cc_proto", "@com_google_absl//absl/cleanup", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", @@ -72,6 +75,7 @@ cc_library( "@com_google_absl//absl/time", "@com_google_absl//absl/types:optional", "@com_google_googletest//:gtest", + "@com_google_protobuf//:protobuf", ], alwayslink = True, ) @@ -99,8 +103,8 @@ cc_library( srcs = ["gnmi_parsers.cc"], hdrs = ["gnmi_parsers.h"], deps = [ - ":openconfig_cc_proto", "//gutil:overload", + "//lib/gnmi:openconfig_cc_proto", "//p4_pdpi/netaddr:ipv4_address", "//p4_pdpi/netaddr:ipv6_address", "@com_google_absl//absl/status", @@ -128,12 +132,3 @@ cmd_diff_test( tools = [":gnmi_parsers_test_runner"], ) -proto_library( - name = "openconfig_proto", - srcs = ["openconfig.proto"], -) - -cc_proto_library( - name = "openconfig_cc_proto", - deps = [":openconfig_proto"], -) diff --git a/tests/qos/cpu_qos_test.cc b/tests/qos/cpu_qos_test.cc index a2219b14..5daaf5ee 100644 --- a/tests/qos/cpu_qos_test.cc +++ b/tests/qos/cpu_qos_test.cc @@ -37,6 +37,7 @@ #include "absl/types/optional.h" #include "glog/logging.h" #include "gmock/gmock.h" +#include "google/protobuf/util/json_util.h" #include "gtest/gtest.h" #include "gutil/collections.h" #include "gutil/proto.h" @@ -46,10 +47,12 @@ #include "gutil/testing.h" #include "include/nlohmann/json.hpp" #include "lib/gnmi/gnmi_helper.h" +#include "lib/gnmi/openconfig.pb.h" #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/v1/p4runtime.pb.h" #include "p4_pdpi/ir.h" #include "p4_pdpi/netaddr/ipv4_address.h" #include "p4_pdpi/netaddr/ipv6_address.h" @@ -59,6 +62,7 @@ #include "p4_pdpi/packetlib/packetlib.pb.h" #include "p4_pdpi/pd.h" #include "p4_pdpi/string_encodings/decimal_string.h" +#include "proto/gnmi/gnmi.pb.h" #include "sai_p4/instantiations/google/sai_p4info.h" #include "sai_p4/instantiations/google/sai_pd.pb.h" #include "tests/forwarding/util.h" @@ -217,6 +221,16 @@ absl::Status SetPortSpeed(const std::string &port_speed, return absl::OkStatus(); } +absl::Status SetPortMtu(int port_mtu, const std::string &interface_name, + gnmi::gNMI::StubInterface &gnmi_stub) { + std::string config_path = absl::StrCat( + "interfaces/interface[name=", interface_name, "]/config/mtu"); + std::string value = absl::StrCat("{\"config:mtu\":", port_mtu, "}"); + RETURN_IF_ERROR(pins_test::SetGnmiConfigPath(&gnmi_stub, config_path, + GnmiSetType::kUpdate, value)); + return absl::OkStatus(); +} + absl::StatusOr CheckLinkUp(const std::string &iface, gnmi::gNMI::StubInterface &gnmi_stub) { std::string oper_status_state_path = @@ -374,7 +388,6 @@ absl::StatusOr MakeRouterInterface( // 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. @@ -505,10 +518,276 @@ TEST_P(CpuQosTestWithoutIxia, PerEntryAclCounterIncrementsWhenEntryIsHit) { LOG(INFO) << "-- END OF TEST -----------------------------------------------"; } +// Returns vector of packets for which we will test that the packet does not +// reach the CPU (when we haven't explicitly configure the switch otherwise). +absl::StatusOr> +TestPacketsThatShouldNotGetPunted() { + std::vector packets; + + // IPv4/6 packets with low TTLs. + // TODO: TTL 0/1 packets currently *do* make it to the CPU by + // default on some of our targets, so we exclude them here for now. + for (int ttl : {/*0, 1,*/ 2, 3}) { + ASSIGN_OR_RETURN(packets.emplace_back(), + gutil::ParseTextProto(absl::Substitute( + R"pb( + headers { + ethernet_header { + ethernet_destination: "00:01:02:02:02:02" + ethernet_source: "00:01:02:03:04:05" + ethertype: "0x0800" + } + } + headers { + ipv4_header { + dscp: "0x00" + ecn: "0x0" + identification: "0xa3cd" + flags: "0x0" + fragment_offset: "0x0000" + ttl: "$0" + protocol: "0x05" + ipv4_source: "10.0.0.2" + ipv4_destination: "10.0.0.3" + } + } + payload: "IPv4 packet with TTL $0." + )pb", + packetlib::IpTtl(ttl)))); + ASSIGN_OR_RETURN( + packets.emplace_back(), + gutil::ParseTextProto(absl::Substitute( + 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: "$0" + ipv6_source: "2001:db8:0:12::1" + ipv6_destination: "2001:db8:0:12::2" + } + } + payload: "IPv6 packet with TTL $0." + )pb", + packetlib::IpTtl(ttl)))); + } + + // Ethernet broadcast packets (destination MAC ff:ff:ff:ff:ff:ff). + ASSIGN_OR_RETURN( + packets.emplace_back(), + gutil::ParseTextProto( + R"pb( + headers { + ethernet_header { + ethernet_destination: "ff:ff:ff:ff:ff:ff" + ethernet_source: "00:01:02:03:04:05" + # This means size(payload) = 0xff bytes = 255 bytes. + ethertype: "0x00ff" + } + } + payload: "Ethernet broadcast packet." + )pb")); + ASSIGN_OR_RETURN(packets.emplace_back(), + gutil::ParseTextProto( + R"pb( + headers { + ethernet_header { + ethernet_destination: "ff:ff:ff:ff:ff:ff" + ethernet_source: "00:11:22:33:44:55" + ethertype: "0x0806" + } + } + headers { + arp_header { + hardware_type: "0x0001" + protocol_type: "0x0800" + hardware_length: "0x06" + protocol_length: "0x04" + operation: "0x0001" + sender_hardware_address: "00:11:22:33:44:55" + sender_protocol_address: "10.0.0.1" + target_hardware_address: "00:00:00:00:00:00" + target_protocol_address: "10.0.0.2" + } + } + payload: "ARP broadcast packet." + )pb")); + + // LLDP multicast packet. + // TODO: If packetlib starts supporting LLDP, we can replace this + // LLDP packet hex dump with a readable protobuf. For now, we can verify that + // this is indeed a valid LLDP packet using, e.g., https://hpd.gasmi.net/. + static constexpr absl::string_view kLldpPacketHexDump = + "0180c200000ef40304321f6688cc02070402320af046030404073235330602007808266a" + "753166326d3168342e6d747631352e6e65742e676f6f676c652e636f6d3a62702d342f36" + "31100c05010af0460302000000fd000a1e6a753166326d3168342e6d747631352e6e6574" + "2e676f6f676c652e636f6dfe0c001a11041666534220c811b3fe05001a1105920000"; + packetlib::Packet packet = + packetlib::ParsePacket(absl::HexStringToBytes(kLldpPacketHexDump)); + if (packet.headers_size() < 1 || !packet.headers(0).has_ethernet_header()) { + return gutil::InternalErrorBuilder(); + } + packet.mutable_headers(0) + ->mutable_ethernet_header() + ->set_ethernet_destination("01:80:C2:00:00:0E"); // LLDP multicast. + packets.push_back(packet); + + // Post-process packets to ensure they are valid. + for (auto &packet : packets) { + RETURN_IF_ERROR(packetlib::PadPacketToMinimumSize(packet).status()); + RETURN_IF_ERROR(packetlib::UpdateAllComputedFields(packet).status()); + LOG(INFO) << packet.DebugString(); // TODO: remove. + } + return packets; +} + +// Queries via gNMI, parses, and returns as a proto the gNMI path +// `qos/interfaces/interface[interface-id=CPU]/output/queues`, which contains +// the state of all CPU queue counters. +absl::StatusOr GetCpuQueueStateViaGnmi( + gnmi::gNMI::StubInterface &gnmi_stub) { + ASSIGN_OR_RETURN( + std::string queues_json, + GetGnmiStatePathInfo( + &gnmi_stub, + "qos/interfaces/interface[interface-id=CPU]/output/queues", + "openconfig-qos:queues")); + + google::protobuf::util::JsonParseOptions options; + options.ignore_unknown_fields = true; + openconfig::Queues queues_proto; + RETURN_IF_ERROR( + gutil::ToAbslStatus(google::protobuf::util::JsonStringToMessage( + queues_json, &queues_proto, options))); + + // Convert `Queues` to `QueuesByName`, which is equivalent but more convenient + // for diffing. + openconfig::QueuesByName queues_by_name; + for (auto &queue : queues_proto.queues()) { + queues_by_name.mutable_queues()->insert({queue.name(), queue.state()}); + } + + return queues_by_name; +} + +// Purpose: Verify that the CPU is protected from packets by default. +TEST_P(CpuQosTestWithoutIxia, + NoUnexpectedPacketsReachCpuInPristineSwitchState) { + LOG(INFO) << "-- START OF TEST ---------------------------------------------"; + + // 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)); + + // Extract loopback IPs from gNMI config, to avoid using them in test packets. + ASSERT_OK_AND_ASSIGN(std::optional loopback_ipv4, + ParseLoopbackIpv4(GetParam().gnmi_config)); + ASSERT_OK_AND_ASSIGN(std::optional loopback_ipv6, + ParseLoopbackIpv6(GetParam().gnmi_config)); + + // Read CPU queue state prior to injecting test packets. The state should + // remain unchanged when we inject test packets. + ASSERT_OK_AND_ASSIGN(openconfig::QueuesByName initial_cpu_queue_state, + GetCpuQueueStateViaGnmi(*gnmi_stub)); + + // Inject test packets and verify that the CPU queue state remains + // unchanged. + ASSERT_OK_AND_ASSIGN(std::vector test_packets, + TestPacketsThatShouldNotGetPunted()); + for (const packetlib::Packet &packet : test_packets) { + // Ensure we are not hitting the loopback IP, as this would be a case in + // which we *do* expect the packet to arrive at the CPU. + for (const packetlib::Header &header : packet.headers()) { + if (header.has_ipv4_header() && loopback_ipv4.has_value()) { + ASSERT_NE(header.ipv4_header().ipv4_destination(), + loopback_ipv4->ToString()) + << "TODO: Implement logic to pick non-loopback IP " + "address."; + } + if (header.has_ipv6_header() && loopback_ipv6.has_value()) { + ASSERT_NE(header.ipv6_header().ipv6_destination(), + loopback_ipv6->ToString()) + << "TODO: Implement logic to pick non-loopback IP " + "address."; + } + } + + LOG(INFO) << "injecting test packet: " << packet.DebugString(); + 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())); + + LOG(INFO) << "Sleeping for " << kMaxQueueCounterUpdateTime + << " before checking for queue counter increment."; + absl::SleepFor(kMaxQueueCounterUpdateTime); + ASSERT_OK_AND_ASSIGN(openconfig::QueuesByName cpu_queue_state, + GetCpuQueueStateViaGnmi(*gnmi_stub)); + EXPECT_THAT(cpu_queue_state, EqualsProto(initial_cpu_queue_state)) + << "for injected test packet: " << packet.DebugString(); + initial_cpu_queue_state = cpu_queue_state; + } + 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 ---------------------------------------------"; - 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. @@ -548,13 +827,15 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { 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. + // 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, + /*p4rt_port_name=*/ + link_used_for_test_packets.sut_port_p4rt_name, /*mac=*/kSutMacAddress, /*ir_p4info=*/ir_p4info)); ASSERT_OK( @@ -575,10 +856,11 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { 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."; + << "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). + // 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}) { @@ -588,7 +870,8 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { << (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. + // 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, @@ -608,12 +891,12 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { // 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)); + 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( diff --git a/tests/qos/gnmi_parsers.cc b/tests/qos/gnmi_parsers.cc index 615e970c..0615ece6 100644 --- a/tests/qos/gnmi_parsers.cc +++ b/tests/qos/gnmi_parsers.cc @@ -7,9 +7,9 @@ #include "google/protobuf/util/json_util.h" #include "gutil/overload.h" #include "gutil/status.h" +#include "lib/gnmi/openconfig.pb.h" #include "p4_pdpi/netaddr/ipv4_address.h" #include "p4_pdpi/netaddr/ipv6_address.h" -#include "tests/qos/openconfig.pb.h" namespace pins_test { diff --git a/tests/qos/gnmi_parsers.h b/tests/qos/gnmi_parsers.h index 217e763d..68761512 100644 --- a/tests/qos/gnmi_parsers.h +++ b/tests/qos/gnmi_parsers.h @@ -8,9 +8,9 @@ #include "absl/status/statusor.h" #include "absl/strings/string_view.h" +#include "lib/gnmi/openconfig.pb.h" #include "p4_pdpi/netaddr/ipv4_address.h" #include "p4_pdpi/netaddr/ipv6_address.h" -#include "tests/qos/openconfig.pb.h" namespace pins_test { diff --git a/tests/qos/openconfig.proto b/tests/qos/openconfig.proto deleted file mode 100644 index 9c0473e7..00000000 --- a/tests/qos/openconfig.proto +++ /dev/null @@ -1,85 +0,0 @@ -// This file contains proto definitions mirroring OpenConfig YANG models, -// allowing us to parse JSON values following these YANG models automatically -// using proto2's json_util. -// -// We only model the fields we're interested in, since we can ignore other -// fields during parsing using json_util's `ignore_unknown_fields` option. -// -// This file also contains proto messages that do not mirror YANG models -// directly, but are more convenient representations, e.g. for diffing. - -syntax = "proto3"; - -package pins_test.openconfig; - -// -- Proto messages mirroring YANG models ------------------------------------- - -// Mirrors `container queues` in `openconfig-qos-elements.yang`. -message Queues { - repeated Queue queues = 1 [json_name = "queue"]; - - // Mirrors `queues/queue` in `openconfig-qos-elements.yang`. - message Queue { - string name = 1; - State state = 2; - - // Mirrors `queues/queue/state` in `openconfig-qos-elements.yang`. - message State { - string dropped_pkts = 1 [json_name = "dropped-pkts"]; - string transmit_pkts = 2 [json_name = "transmit-pkts"]; - } - } -} - -// Mirrors `container interfaces` in `openconfig-interfaces.yang`. -message Interfaces { - repeated Interface interfaces = 1 [json_name = "interface"]; - - // Mirrors `interfaces/interface` in `openconfig-interfaces.yang`. - message Interface { - string name = 1; - Config config = 2; - Subinterfaces subinterfaces = 3; - - message Config { - string name = 1; - string type = 2; - } - } -} - -// Mirrors `container subinterfaces` in `openconfig-interfaces.yang`. -message Subinterfaces { - repeated Subinterface subinterfaces = 1 [json_name = "subinterface"]; - - // Mirrors `subinterfaces/subinterface` in `openconfig-interfaces.yang`. - message Subinterface { - Ip ipv4 = 1 [json_name = "openconfig-if-ip:ipv4"]; - Ip ipv6 = 2 [json_name = "openconfig-if-ip:ipv6"]; - - message Ip { - Addresses addresses = 1; - } - } -} - -// Mirror `container addreses` in `openconfig-if-ip.yang` -message Addresses { - repeated Address addresses = 1 [json_name = "address"]; - - message Address { - string ip = 1; - } -} - -// Mirrors the root of the config tree. -message Config { - Interfaces interfaces = 1 [json_name = "openconfig-interfaces:interfaces"]; -} - -// -- Proto messages defined for convenience ----------------------------------- - -// Equivalent to `Queues`, but results in more readable diffs. -message QueuesByName { - map queues = 1; -} From 21211d8ae815ecfc0f75d6b36cbb6ee52813ba5a Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Thu, 5 Dec 2024 03:22:25 +0000 Subject: [PATCH 07/23] [Thinkit] Add parsers to extract loopback IPs from gNMI config, Fix IPv4/v6 typo causing test failure, Tweak test output, Increase sleep time after applying gNMI config as a temporary workaround & Update port name for fixing test failure. (#791) Co-authored-by: smolkaj Co-authored-by: kishanps --- tests/qos/BUILD.bazel | 3 + tests/qos/cpu_qos_test.cc | 113 ++++++++++++++++++++------------------ 2 files changed, 63 insertions(+), 53 deletions(-) diff --git a/tests/qos/BUILD.bazel b/tests/qos/BUILD.bazel index 8765bc91..9a226fb9 100644 --- a/tests/qos/BUILD.bazel +++ b/tests/qos/BUILD.bazel @@ -30,7 +30,9 @@ cc_library( "cpu_qos_test.h", ], deps = [ + ":gnmi_parsers", "//gutil:collections", + "//gutil:overload", "//gutil:proto", "//gutil:proto_matchers", "//gutil:status", @@ -74,6 +76,7 @@ cc_library( "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/time", "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:variant", "@com_google_googletest//:gtest", "@com_google_protobuf//:protobuf", ], diff --git a/tests/qos/cpu_qos_test.cc b/tests/qos/cpu_qos_test.cc index 5daaf5ee..89994c64 100644 --- a/tests/qos/cpu_qos_test.cc +++ b/tests/qos/cpu_qos_test.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "absl/cleanup/cleanup.h" @@ -35,11 +36,13 @@ #include "absl/time/clock.h" #include "absl/time/time.h" #include "absl/types/optional.h" +#include "absl/types/variant.h" #include "glog/logging.h" #include "gmock/gmock.h" #include "google/protobuf/util/json_util.h" #include "gtest/gtest.h" #include "gutil/collections.h" +#include "gutil/overload.h" #include "gutil/proto.h" #include "gutil/proto_matchers.h" #include "gutil/status.h" @@ -66,6 +69,7 @@ #include "sai_p4/instantiations/google/sai_p4info.h" #include "sai_p4/instantiations/google/sai_pd.pb.h" #include "tests/forwarding/util.h" +#include "tests/qos/gnmi_parsers.h" #include "thinkit/control_device.h" #include "thinkit/generic_testbed.h" #include "thinkit/mirror_testbed.h" @@ -78,6 +82,8 @@ namespace { using ::gutil::EqualsProto; using ::gutil::IsOkAndHolds; using ::p4::config::v1::P4Info; +using ::testing::Contains; +using ::testing::Not; // Size of the "frame check sequence" (FCS) that is part of Layer 2 Ethernet // frames. @@ -89,6 +95,12 @@ constexpr int kFrameCheckSequenceSize = 4; // 10 seconds. constexpr absl::Duration kMaxQueueCounterUpdateTime = absl::Seconds(15); +// After pushing gNMI config to a switch, the tests sleep for this duration +// assuming that the gNMI config will have been fully applied afterwards. +// TODO: Instead of hard-coding this time, tests should dynamically +// poll the state of the switch to ensure config has been applied. +constexpr absl::Duration kTimeToWaitForGnmiConfigToApply = absl::Seconds(15); + struct QueueInfo { std::string gnmi_queue_name; // Openconfig queue name. std::string p4_queue_name; // P4 queue name. @@ -133,7 +145,7 @@ absl::Status SetUpPuntToCPU(const netaddr::MacAddress &dmac, src_ip { value: "$1" mask: "255.255.255.255" } dst_ip { value: "$2" mask: "255.255.255.255" } } - action { trap { qos_queue: "$3" } } + action { acl_trap { qos_queue: "$3" } } priority: 1 } )pb", @@ -327,20 +339,6 @@ ParseIpv6DscpToQueueMapping(absl::string_view gnmi_config) { 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; @@ -362,10 +360,10 @@ 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", + .sut_port_gnmi_name = "Ethernet0", + .sut_port_p4rt_name = "1", + .control_device_port_gnmi_name = "Ethernet0", + .control_device_port_p4rt_name = "1", }; } @@ -645,7 +643,6 @@ TestPacketsThatShouldNotGetPunted() { for (auto &packet : packets) { RETURN_IF_ERROR(packetlib::PadPacketToMinimumSize(packet).status()); RETURN_IF_ERROR(packetlib::UpdateAllComputedFields(packet).status()); - LOG(INFO) << packet.DebugString(); // TODO: remove. } return packets; } @@ -700,9 +697,9 @@ TEST_P(CpuQosTestWithoutIxia, 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)); + LOG(INFO) << "Sleeping " << kTimeToWaitForGnmiConfigToApply + << " to wait for config to be applied/links to come up."; + absl::SleepFor(kTimeToWaitForGnmiConfigToApply); // Pick a link to be used for packet injection. ASSERT_OK_AND_ASSIGN(SutToControlLink link_used_for_test_packets, @@ -734,10 +731,10 @@ TEST_P(CpuQosTestWithoutIxia, router_interface_entry)); // Extract loopback IPs from gNMI config, to avoid using them in test packets. - ASSERT_OK_AND_ASSIGN(std::optional loopback_ipv4, - ParseLoopbackIpv4(GetParam().gnmi_config)); - ASSERT_OK_AND_ASSIGN(std::optional loopback_ipv6, - ParseLoopbackIpv6(GetParam().gnmi_config)); + using IpAddresses = + std::vector>; + ASSERT_OK_AND_ASSIGN(IpAddresses loopback_ips, + ParseLoopbackIps(GetParam().gnmi_config)); // Read CPU queue state prior to injecting test packets. The state should // remain unchanged when we inject test packets. @@ -752,15 +749,19 @@ TEST_P(CpuQosTestWithoutIxia, // Ensure we are not hitting the loopback IP, as this would be a case in // which we *do* expect the packet to arrive at the CPU. for (const packetlib::Header &header : packet.headers()) { - if (header.has_ipv4_header() && loopback_ipv4.has_value()) { - ASSERT_NE(header.ipv4_header().ipv4_destination(), - loopback_ipv4->ToString()) + if (header.has_ipv4_header()) { + ASSERT_OK_AND_ASSIGN(auto ip_dst, + netaddr::Ipv4Address::OfString( + header.ipv4_header().ipv4_destination())); + ASSERT_THAT(loopback_ips, Not(Contains(ip_dst))) << "TODO: Implement logic to pick non-loopback IP " "address."; } - if (header.has_ipv6_header() && loopback_ipv6.has_value()) { - ASSERT_NE(header.ipv6_header().ipv6_destination(), - loopback_ipv6->ToString()) + if (header.has_ipv6_header()) { + ASSERT_OK_AND_ASSIGN(auto ip_dst, + netaddr::Ipv6Address::OfString( + header.ipv6_header().ipv6_destination())); + ASSERT_THAT(loopback_ips, Not(Contains(ip_dst))) << "TODO: Implement logic to pick non-loopback IP " "address."; } @@ -806,9 +807,9 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { 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)); + LOG(INFO) << "Sleeping " << kTimeToWaitForGnmiConfigToApply + << " to wait for config to be applied/links to come up."; + absl::SleepFor(kTimeToWaitForGnmiConfigToApply); // Pick a link to be used for packet injection. ASSERT_OK_AND_ASSIGN(SutToControlLink link_used_for_test_packets, @@ -851,11 +852,11 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { 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()) + using IpAddresses = + std::vector>; + ASSERT_OK_AND_ASSIGN(IpAddresses loopback_ips, + ParseLoopbackIps(GetParam().gnmi_config)); + ASSERT_TRUE(!loopback_ips.empty()) << "Expected a loopback IP to be configured via gNMI; nothing to " "test."; @@ -863,11 +864,15 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { // 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; + for (auto &loopback_ip : loopback_ips) { + netaddr::Ipv4Address *loopback_ipv4 = + std::get_if(&loopback_ip); + netaddr::Ipv6Address *loopback_ipv6 = + std::get_if(&loopback_ip); + ASSERT_TRUE(loopback_ipv4 != nullptr || loopback_ipv6 != nullptr); LOG(INFO) << "Testing DSCP-to-queue mapping for " - << (ipv4 ? "IPv4" : "IPv6") << " packet with DSCP " << dscp; + << (loopback_ipv4 != nullptr ? "IPv4" : "IPv6") + << " packet with DSCP " << dscp; // Identify target queue for current DSCP. // The algorithm for picking a queue is somewhat adhoc and PINS @@ -891,12 +896,13 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { // 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)); + loopback_ipv4 != nullptr + ? 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( @@ -920,8 +926,9 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { 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" + << "Counters for queue " << target_queue + << " did not increment within " << kMaxQueueCounterUpdateTime + << " after injecting the following test packet:\n" << packet.DebugString() << "\nBefore: " << queue_counters_before_test_packet << "\nAfter : " << queue_counters_after_test_packet; From c157aa6c692f00801e37ca2897fef7359162068a Mon Sep 17 00:00:00 2001 From: bibhuprasad-hcl <161687009+bibhuprasad-hcl@users.noreply.github.com> Date: Thu, 5 Dec 2024 03:23:22 +0000 Subject: [PATCH 08/23] Enable FCS and IPV6 test. Fix test case id (#792) Co-authored-by: Srikishen Pondicherry Shanmugam --- tests/gnmi/ethcounter_ixia_test.cc | 157 ++++++++++++++--------------- 1 file changed, 74 insertions(+), 83 deletions(-) diff --git a/tests/gnmi/ethcounter_ixia_test.cc b/tests/gnmi/ethcounter_ixia_test.cc index 08e279d5..eafb4654 100644 --- a/tests/gnmi/ethcounter_ixia_test.cc +++ b/tests/gnmi/ethcounter_ixia_test.cc @@ -564,7 +564,6 @@ absl::StatusOr NameToP4Id(std::string iface, return std::stoul(id_str); } -#if 0 TEST_P(ExampleIxiaTestFixture, TestInFcsErrors) { LOG(INFO) << "\n\n\n\n\n\n\n\n\n\n---------- Starting TestInFcsErrors " "----------\n\n\n\n\n"; @@ -583,47 +582,46 @@ TEST_P(ExampleIxiaTestFixture, TestInFcsErrors) { absl::flat_hash_map interface_info = generic_testbed->GetSutInterfaceInfo(); - // Connect to TestTracker for test status - generic_testbed->Environment().SetTestCaseID( - "3da5c6f0-c85e-465f-9221-1e07523092d6"); - // Hook up to GNMI - ASSERT_OK_AND_ASSIGN( - std::unique_ptr gnmi_stub, - generic_testbed->Sut().CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN(std::unique_ptr gnmi_stub, + generic_testbed->Sut().CreateGnmiStub()); // go through all the ports that interface to the Ixia and set them // to 200GB since the Ixia ports are all 200GB. for (const auto &[interface, info] : interface_info) { if (info.interface_modes.contains(thinkit::TRAFFIC_GENERATOR)) { - LOG(INFO) << "gwc: Host Interface " << interface; + LOG(INFO) << "Host Interface " << interface; EXPECT_OK(SetPortSpeed(kSpeed200GB, interface, gnmi_stub.get())); } } // Wait to let the links come up - absl::SleepFor(absl::Seconds(20)); + absl::SleepFor(absl::Seconds(30)); // Loop through the interface_info looking for Ixia/SUT interface pairs, - // checking if the link is up. we need one pair with link up for the - // ingress interface/IXIA bad fcs traffic generation - std::string ixia_interface = ""; - std::string sut_interface = ""; + // checking if the link is up. We need one pair with link up for the + // ingress interface/IXIA traffic generation. + ASSERT_OK_AND_ASSIGN(std::vector ready_links, + GetReadyIxiaLinks(*generic_testbed, *gnmi_stub)); - for (const auto &[interface, info] : interface_info) { - if (info.interface_modes.contains(thinkit::TRAFFIC_GENERATOR)) { - auto sut_link_up = CheckLinkUp(interface, gnmi_stub.get()); - EXPECT_TRUE(sut_link_up.ok()); - if (sut_link_up.ok() && sut_link_up.value()) { - ixia_interface = info.peer_interface_name; - sut_interface = interface; - break; + // If links didn't come up, lets try 100GB as some testbeds have 100GB + // IXIA connections. + if (ready_links.empty()) { + for (const auto &[interface, info] : interface_info) { + if (info.interface_modes.contains(thinkit::TRAFFIC_GENERATOR)) { + ASSERT_OK(SetPortSpeed(kSpeed100GB, interface, gnmi_stub.get())); } } + absl::SleepFor(absl::Seconds(30)); + ASSERT_OK_AND_ASSIGN(ready_links, + GetReadyIxiaLinks(*generic_testbed, *gnmi_stub)); } - ASSERT_FALSE(ixia_interface.empty()); - ASSERT_FALSE(sut_interface.empty()); + ASSERT_GE(ready_links.size(), 1) << "Ixia link is not ready"; + + std::string ixia_interface = ready_links[0].ixia_interface; + std::string sut_interface = ready_links[0].sut_interface; + LOG(INFO) << "\n\nChose Ixia interface " << ixia_interface << " and SUT interface " << sut_interface << "\n\n"; @@ -694,16 +692,9 @@ TEST_P(ExampleIxiaTestFixture, TestInFcsErrors) { ASSERT_OK(ixia::StartTraffic(tref, ixref, *generic_testbed)); - // Wait until 10 seconds after the traffic started - absl::Time t1; - t1 = absl::Now(); - - absl::Time t2; - while (1) { - t2 = absl::Now(); - if (t2 >= t1 + absl::Seconds(10)) break; - absl::SleepFor(absl::Milliseconds(100)); - } + // Wait until 10 (traffic) + 25 (stats update) seconds after + // the traffic started. + absl::SleepFor(absl::Seconds(35)); ASSERT_OK(ixia::StopTraffic(tref, *generic_testbed)); @@ -750,9 +741,7 @@ TEST_P(ExampleIxiaTestFixture, TestInFcsErrors) { LOG(INFO) << "\n\n\n\n\n---------- Finished TestInFcsErrors " "----------\n\n\n\n\n\n\n\n\n\n"; } -#endif -#if 1 TEST_P(ExampleIxiaTestFixture, TestIPv4Pkts) { LOG(INFO) << "\n\n\n\n\n\n\n\n\n\n---------- Starting TestIPv4Pkts " "----------\n\n\n\n\n"; @@ -771,10 +760,6 @@ TEST_P(ExampleIxiaTestFixture, TestIPv4Pkts) { absl::flat_hash_map interface_info = generic_testbed->GetSutInterfaceInfo(); - // Connect to TestTracker for test status - generic_testbed->Environment().SetTestCaseID( - "2fac23ff-6794-4a31-8fce-a1aa76afe72e"); - // Hook up to GNMI ASSERT_OK_AND_ASSIGN(std::unique_ptr gnmi_stub, generic_testbed->Sut().CreateGnmiStub()); @@ -789,7 +774,7 @@ TEST_P(ExampleIxiaTestFixture, TestIPv4Pkts) { } // Wait to let the links come up - absl::SleepFor(absl::Seconds(20)); + absl::SleepFor(absl::Seconds(30)); // Loop through the interface_info looking for Ixia/SUT interface pairs, // checking if the link is up. we need one pair with link up for the @@ -797,7 +782,7 @@ TEST_P(ExampleIxiaTestFixture, TestIPv4Pkts) { ASSERT_OK_AND_ASSIGN(std::vector ready_links, GetReadyIxiaLinks(*generic_testbed, *gnmi_stub)); - // If links didnt come up, lets try 100GB as some testbeds have 100GB + // If links didn't come up, lets try 100GB as some testbeds have 100GB // IXIA connections. if (ready_links.empty()) { for (const auto &[interface, info] : interface_info) { @@ -805,22 +790,32 @@ TEST_P(ExampleIxiaTestFixture, TestIPv4Pkts) { ASSERT_OK(SetPortSpeed(kSpeed100GB, interface, gnmi_stub.get())); } } - absl::SleepFor(absl::Seconds(20)); + absl::SleepFor(absl::Seconds(30)); ASSERT_OK_AND_ASSIGN(ready_links, GetReadyIxiaLinks(*generic_testbed, *gnmi_stub)); } - ASSERT_GE(ready_links.size(), 2) << "Ixia links are not ready"; + ASSERT_GE(ready_links.size(), 1) << "Ixia link is not ready"; std::string ixia_interface = ready_links[0].ixia_interface; std::string sut_in_interface = ready_links[0].sut_interface; - std::string sut_out_interface = ready_links[1].sut_interface; - ASSERT_FALSE(ixia_interface.empty()); ASSERT_FALSE(sut_in_interface.empty()); - ASSERT_FALSE(sut_out_interface.empty()); + + // Now loop through again and pick an egress interface. This one doesn't + // have to be up, just a different interface. + std::string sut_out_interface = ""; + + for (const auto &[interface, info] : interface_info) { + if (info.interface_modes.contains(thinkit::TRAFFIC_GENERATOR)) { + if (interface != sut_in_interface) { + sut_out_interface = interface; + break; + } + } + } // Look up the port numbers for the ingress and egress interfaces ASSERT_OK_AND_ASSIGN(uint32_t in_id, @@ -1028,7 +1023,6 @@ TEST_P(ExampleIxiaTestFixture, TestIPv4Pkts) { LOG(INFO) << "\n\n\n\n\n---------- Finished TestIPv4Pkts " "----------\n\n\n\n\n\n\n\n\n\n"; } -#endif #if 0 TEST_P(ExampleIxiaTestFixture, TestOutDiscards) { @@ -1049,10 +1043,6 @@ TEST_P(ExampleIxiaTestFixture, TestOutDiscards) { absl::flat_hash_map interface_info = generic_testbed->GetSutInterfaceInfo(); - // Connect to TestTracker for test status - generic_testbed->Environment().SetTestCaseID( - "0539b74c-656f-4a0c-aa8f-39d7721520ad"); - // Hook up to GNMI ASSERT_OK_AND_ASSIGN(auto gnmi_stub, generic_testbed->Sut().CreateGnmiStub()); @@ -1312,7 +1302,6 @@ TEST_P(ExampleIxiaTestFixture, TestOutDiscards) { } #endif -#if 0 TEST_P(ExampleIxiaTestFixture, TestIPv6Pkts) { LOG(INFO) << "\n\n\n\n\n\n\n\n\n\n---------- Starting TestIPv6Pkts " "----------\n\n\n\n\n"; @@ -1331,15 +1320,11 @@ TEST_P(ExampleIxiaTestFixture, TestIPv6Pkts) { absl::flat_hash_map interface_info = generic_testbed->GetSutInterfaceInfo(); - // Connect to TestTracker for test status - generic_testbed->Environment().SetTestCaseID( - "3f3859b8-7602-479c-a2ad-ab964ded694b"); - // Hook up to GNMI ASSERT_OK_AND_ASSIGN(auto gnmi_stub, generic_testbed->Sut().CreateGnmiStub()); // go through all the ports that interface to the Ixia and set them - // to 100GB since the Ixia ports are all 100GB. + // to 200GB since the Ixia ports are all 200GB. for (const auto &[interface, info] : interface_info) { if (info.interface_modes.contains(thinkit::TRAFFIC_GENERATOR)) { LOG(INFO) << "Host Interface " << interface; @@ -1348,26 +1333,36 @@ TEST_P(ExampleIxiaTestFixture, TestIPv6Pkts) { } // Wait to let the links come up - absl::SleepFor(absl::Seconds(20)); + absl::SleepFor(absl::Seconds(30)); // Loop through the interface_info looking for Ixia/SUT interface pairs, // checking if the link is up. we need one pair with link up for the // ingress interface/IXIA traffic generation - std::string ixia_interface = ""; - std::string sut_in_interface = ""; + ASSERT_OK_AND_ASSIGN(std::vector ready_links, + GetReadyIxiaLinks(*generic_testbed, *gnmi_stub)); - for (const auto &[interface, info] : interface_info) { - if (info.interface_modes.contains(thinkit::TRAFFIC_GENERATOR)) { - auto sut_link_up = CheckLinkUp(interface, gnmi_stub.get()); - EXPECT_TRUE(sut_link_up.ok()); - if (sut_link_up.ok() && sut_link_up.value()) { - ixia_interface = info.peer_interface_name; - sut_in_interface = interface; - break; + // If links didn't come up, lets try 100GB as some testbeds have 100GB + // IXIA connections. + if (ready_links.empty()) { + for (const auto &[interface, info] : interface_info) { + if (info.interface_modes.contains(thinkit::TRAFFIC_GENERATOR)) { + ASSERT_OK(SetPortSpeed(kSpeed100GB, interface, gnmi_stub.get())); } } + absl::SleepFor(absl::Seconds(30)); + + ASSERT_OK_AND_ASSIGN(ready_links, + GetReadyIxiaLinks(*generic_testbed, *gnmi_stub)); } + ASSERT_GE(ready_links.size(), 1) << "Ixia link is not ready"; + + std::string ixia_interface = ready_links[0].ixia_interface; + std::string sut_in_interface = ready_links[0].sut_interface; + + ASSERT_FALSE(ixia_interface.empty()); + ASSERT_FALSE(sut_in_interface.empty()); + // Now loop through again and pick an egress interface. This one doesn't // have to be up, just a different interface. std::string sut_out_interface = ""; @@ -1381,10 +1376,6 @@ TEST_P(ExampleIxiaTestFixture, TestIPv6Pkts) { } } - ASSERT_FALSE(ixia_interface.empty()); - ASSERT_FALSE(sut_in_interface.empty()); - ASSERT_FALSE(sut_out_interface.empty()); - // Look up the port number for the egress interface ASSERT_OK_AND_ASSIGN(uint32_t in_id, NameToP4Id(sut_in_interface, gnmi_stub.get())); @@ -1562,12 +1553,17 @@ TEST_P(ExampleIxiaTestFixture, TestIPv6Pkts) { EXPECT_EQ(delta_out.out_ipv4_pkts, 0); EXPECT_GE(delta_out.in_ipv6_pkts, delta_out.out_pkts - 10); EXPECT_LE(delta_out.in_ipv6_pkts, delta_out.out_pkts + 10); - EXPECT_LE(delta_out.out_ipv6_pkts, delta_out.out_pkts + 10); - EXPECT_GE(delta_out.out_ipv6_pkts, delta_out.out_pkts - 10); - EXPECT_EQ(delta_out.out_ipv6_discarded_pkts, 0); - EXPECT_LE(delta_in.in_ipv6_pkts, delta_out.out_pkts + 10); - EXPECT_GE(delta_in.in_ipv6_pkts, delta_out.out_pkts - 10); + // TODO: Remove mask after bug is addressed. + if (!generic_testbed->Environment().MaskKnownFailures()) { + EXPECT_LE(delta_out.out_ipv6_pkts, delta_out.out_pkts + 10); + EXPECT_GE(delta_out.out_ipv6_pkts, delta_out.out_pkts - 10); + EXPECT_LE(delta_in.in_ipv6_pkts, delta_out.out_pkts + 10); + EXPECT_GE(delta_in.in_ipv6_pkts, delta_out.out_pkts - 10); + } + + EXPECT_EQ(delta_out.out_ipv6_discarded_pkts, 0); + LOG(INFO) << "\n\n\n\n\n---------- Restore ----------\n\n\n\n\n"; // go through all the ports that interface to the Ixia and set them @@ -1586,7 +1582,6 @@ TEST_P(ExampleIxiaTestFixture, TestIPv6Pkts) { LOG(INFO) << "\n\n\n\n\n---------- Finished TestIPv6Pkts " "----------\n\n\n\n\n\n\n\n\n\n"; } -#endif #if 0 TEST_P(ExampleIxiaTestFixture, TestCPUOutDiscards) { @@ -1607,10 +1602,6 @@ TEST_P(ExampleIxiaTestFixture, TestCPUOutDiscards) { absl::flat_hash_map interface_info = generic_testbed->GetSutInterfaceInfo(); - // Connect to TestTracker for test status - generic_testbed->Environment().SetTestCaseID( - "3c8823ab-1aba-48a5-8af6-90d2f268c699"); - // Hook up to GNMI ASSERT_OK_AND_ASSIGN(auto gnmi_stub, generic_testbed->Sut().CreateGnmiStub()); From 977c92bc41b06fb170d70f3d09a18f7001aedd2c Mon Sep 17 00:00:00 2001 From: bibhuprasad-hcl <161687009+bibhuprasad-hcl@users.noreply.github.com> Date: Thu, 5 Dec 2024 03:24:31 +0000 Subject: [PATCH 09/23] [Thinkit] Ixia test cleanup. Increase timeout to wait for config push. (#793) Co-authored-by: Srikishen Pondicherry Shanmugam --- tests/gnmi/ethcounter_ixia_test.cc | 69 ++++++++++-------------------- 1 file changed, 22 insertions(+), 47 deletions(-) diff --git a/tests/gnmi/ethcounter_ixia_test.cc b/tests/gnmi/ethcounter_ixia_test.cc index eafb4654..f019cdb9 100644 --- a/tests/gnmi/ethcounter_ixia_test.cc +++ b/tests/gnmi/ethcounter_ixia_test.cc @@ -137,10 +137,6 @@ absl::Status TrapToCPU(thinkit::Switch &sut) { // specified. Set is_ipv6 to true to get the IPv6 version. Otherwise it will // use IPv4. // -// Note: after seeing occasional problems with forwarding not working -// and following b/190736007 and chats with @kishanps I have added -// a RIF to the ingress port as well as one for the egress port jic. -// absl::Status ForwardToEgress(uint32_t in_port, uint32_t out_port, bool is_ipv6, thinkit::Switch &sut) { constexpr absl::string_view kVrfId = "vrf-80"; @@ -696,8 +692,6 @@ TEST_P(ExampleIxiaTestFixture, TestInFcsErrors) { // the traffic started. absl::SleepFor(absl::Seconds(35)); - ASSERT_OK(ixia::StopTraffic(tref, *generic_testbed)); - // Re-read the same counters via GNMI from the SUT ASSERT_OK_AND_ASSIGN(auto final_counters, ReadCounters(sut_interface, gnmi_stub.get())); @@ -774,7 +768,7 @@ TEST_P(ExampleIxiaTestFixture, TestIPv4Pkts) { } // Wait to let the links come up - absl::SleepFor(absl::Seconds(30)); + absl::SleepFor(absl::Seconds(60)); // Loop through the interface_info looking for Ixia/SUT interface pairs, // checking if the link is up. we need one pair with link up for the @@ -929,21 +923,12 @@ TEST_P(ExampleIxiaTestFixture, TestIPv4Pkts) { // absl::SleepFor(absl::Seconds(120)); ASSERT_OK(ixia::StartTraffic(full_tref, ixref, *generic_testbed)); - // Wait until 10 seconds after the traffic started + // Wait until 10 (traffic) + 25 (stats update) seconds after + // the traffic started. absl::Time t1; t1 = absl::Now(); - absl::Time t2; - while (1) { - t2 = absl::Now(); - if (t2 >= t1 + absl::Seconds(10)) break; - absl::SleepFor(absl::Milliseconds(100)); - } - - LOG(INFO) << "Time at stop is " << t2; - LOG(INFO) << "Delta is " << t2 - t1; - - ASSERT_OK(ixia::StopTraffic(full_tref, *generic_testbed)); + absl::SleepFor(absl::Seconds(35)); // Re-read the same counters via GNMI from the SUT ASSERT_OK_AND_ASSIGN(auto final_in_counters, @@ -952,11 +937,11 @@ TEST_P(ExampleIxiaTestFixture, TestIPv4Pkts) { ReadCounters(sut_out_interface, gnmi_stub.get())); // Check the time again - absl::Time t3 = absl::Now(); - LOG(INFO) << "Time after statistics read is " << t3; - LOG(INFO) << "Delta is " << t3 - t1; - uint64_t seconds = ((t3 - t1) / absl::Seconds(1)) + 1; - + absl::Time t2 = absl::Now(); + LOG(INFO) << "Time after statistics read is " << t2; + LOG(INFO) << "Delta is " << t2 - t1; + uint64_t seconds = absl::ToInt64Seconds(t2 - t1); + // Display the final counters LOG(INFO) << "\n\nTestIPv4Pkts:\n\n" << "\n\nFinal Ingress Counters (" << sut_in_interface << "):\n"; @@ -990,7 +975,7 @@ TEST_P(ExampleIxiaTestFixture, TestIPv4Pkts) { EXPECT_GE(delta_out.out_octets, delta_out.out_pkts * kMtu - 10000); EXPECT_LE(delta_out.out_octets, delta_out.out_pkts * kMtu + 10000); EXPECT_LE(delta_out.out_unicast_pkts, delta_out.out_pkts + 10); - EXPECT_LE(delta_out.out_multicast_pkts, 5); + EXPECT_LE(delta_out.out_multicast_pkts, 10); EXPECT_EQ(delta_out.out_broadcast_pkts, 0); EXPECT_EQ(delta_out.out_errors, 0); EXPECT_EQ(delta_out.out_discards, 0); @@ -1235,7 +1220,7 @@ TEST_P(ExampleIxiaTestFixture, TestOutDiscards) { absl::Time t3 = absl::Now(); LOG(INFO) << "Time after statistics read is " << t3; LOG(INFO) << "Delta is " << t3 - t1; - uint64_t seconds = ((t3 - t1) / absl::Seconds(1)) + 1; + uint64_t seconds = absl::ToInt64Seconds(t2 - t1); // Display the final counters LOG(INFO) << "\n\nTestOutDiscards:\n\n" @@ -1333,7 +1318,7 @@ TEST_P(ExampleIxiaTestFixture, TestIPv6Pkts) { } // Wait to let the links come up - absl::SleepFor(absl::Seconds(30)); + absl::SleepFor(absl::Seconds(60)); // Loop through the interface_info looking for Ixia/SUT interface pairs, // checking if the link is up. we need one pair with link up for the @@ -1403,7 +1388,7 @@ TEST_P(ExampleIxiaTestFixture, TestIPv6Pkts) { // Fetch the initial conditions for the egress interface. // Note: Not currently fetching the FEC mode since the gNMI get on that - // fails even if I populate redis first. See b/197778604. As a result + // fails even if I populate redis first. As a result // the link will not come up at the end of the test after I switch it // back to 100GB. TBD. ASSERT_OK_AND_ASSIGN(auto out_initial_loopback, @@ -1484,21 +1469,11 @@ TEST_P(ExampleIxiaTestFixture, TestIPv6Pkts) { // absl::SleepFor(absl::Seconds(15)); ASSERT_OK(ixia::StartTraffic(full_tref, ixref, *generic_testbed)); - // Wait until 10 seconds after the traffic started + // Wait until 10 (traffic) + 25 (stats update) seconds after + // the traffic started. absl::Time t1; t1 = absl::Now(); - - absl::Time t2; - while (1) { - t2 = absl::Now(); - if (t2 >= t1 + absl::Seconds(10)) break; - absl::SleepFor(absl::Milliseconds(100)); - } - - LOG(INFO) << "Time at stop is " << t2; - LOG(INFO) << "Delta is " << t2 - t1; - - ASSERT_OK(ixia::StopTraffic(full_tref, *generic_testbed)); + absl::SleepFor(absl::Seconds(35)); // Re-read the same counters via GNMI from the SUT ASSERT_OK_AND_ASSIGN(auto final_in_counters, @@ -1507,10 +1482,10 @@ TEST_P(ExampleIxiaTestFixture, TestIPv6Pkts) { ReadCounters(sut_out_interface, gnmi_stub.get())); // Check the time again - absl::Time t3 = absl::Now(); - LOG(INFO) << "Time after statistics read is " << t3; - LOG(INFO) << "Delta is " << t3 - t1; - uint64_t seconds = ((t3 - t1) / absl::Seconds(1)) + 1; + absl::Time t2 = absl::Now(); + LOG(INFO) << "Time after statistics read is " << t2; + LOG(INFO) << "Delta is " << t2 - t1; + uint64_t seconds = absl::ToInt64Seconds(t2 - t1); // Display the final counters LOG(INFO) << "\n\nTestIPv6Pkts:\n\n" @@ -1546,7 +1521,7 @@ TEST_P(ExampleIxiaTestFixture, TestIPv6Pkts) { EXPECT_GE(delta_out.out_octets, delta_out.out_pkts * kMtu - 10000); EXPECT_LE(delta_out.out_unicast_pkts, delta_out.out_pkts + 10); EXPECT_GE(delta_out.out_unicast_pkts, delta_out.out_pkts - 10); - EXPECT_LE(delta_out.out_multicast_pkts, 5); + EXPECT_LE(delta_out.out_multicast_pkts, 10); EXPECT_EQ(delta_out.out_broadcast_pkts, 0); EXPECT_EQ(delta_out.out_errors, 0); EXPECT_EQ(delta_out.out_discards, 0); @@ -1759,7 +1734,7 @@ TEST_P(ExampleIxiaTestFixture, TestCPUOutDiscards) { absl::Time t3 = absl::Now(); LOG(INFO) << "Time after statistics read is " << t3; LOG(INFO) << "Delta is " << t3 - t1; - uint64_t seconds = ((t3 - t1) / absl::Seconds(1)) + 1; + uint64_t seconds = absl::ToInt64Seconds(t2 - t1); // Display the final counters LOG(INFO) << "\n\nTestCPUOutDiscards:\n\n" From 11d9fc527e0e985c9d127b8084a7799e9bf9c623 Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Thu, 5 Dec 2024 03:25:23 +0000 Subject: [PATCH 10/23] [Thinkit] Extract queue configuration from gNMI config and test all the queues. (#797) Co-authored-by: smolkaj Co-authored-by: kishanps --- tests/qos/BUILD.bazel | 1 + tests/qos/cpu_qos_test.cc | 111 ++++++++++++++++++++++---------------- tests/qos/cpu_qos_test.h | 4 ++ 3 files changed, 70 insertions(+), 46 deletions(-) diff --git a/tests/qos/BUILD.bazel b/tests/qos/BUILD.bazel index 9a226fb9..24ac5725 100644 --- a/tests/qos/BUILD.bazel +++ b/tests/qos/BUILD.bazel @@ -74,6 +74,7 @@ cc_library( "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/synchronization", "@com_google_absl//absl/time", "@com_google_absl//absl/types:optional", "@com_google_absl//absl/types:variant", diff --git a/tests/qos/cpu_qos_test.cc b/tests/qos/cpu_qos_test.cc index 89994c64..09071f12 100644 --- a/tests/qos/cpu_qos_test.cc +++ b/tests/qos/cpu_qos_test.cc @@ -33,6 +33,7 @@ #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "absl/strings/substitute.h" +#include "absl/synchronization/mutex.h" #include "absl/time/clock.h" #include "absl/time/time.h" #include "absl/types/optional.h" @@ -107,19 +108,70 @@ struct QueueInfo { int rate_packets_per_second = 0; // Rate of packets in packets per second. }; -// TODO: Parse QueueInfo from gNMI config. +// Extract the queue configurations from the gNMI configuration. +// The function returns a map keyed on queue name and value +// holds queue configuration information. +// TODO: Need to handle exceptions cleanly for failures +// during json parsing which can crash the test run. +// Currently we are assuming validity of config json parameter passed into +// the test. 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}}, - }; +ExtractQueueInfoViaGnmiConfig(absl::string_view gnmi_config) { + nlohmann::json config = nlohmann::json::parse(gnmi_config); + if (!config.is_object()) { + return absl::InvalidArgumentError("Could not parse gnmi configuration."); + } + + absl::flat_hash_map queue_info_by_queue_name; + auto &qos_interfaces = + config["openconfig-qos:qos"]["interfaces"]["interface"]; + + std::string cpu_scheduler_policy; + for (auto &interface : qos_interfaces) { + if (interface["interface-id"].get() == "CPU") { + cpu_scheduler_policy = + interface["output"]["scheduler-policy"]["config"]["name"] + .get(); + break; + } + } + + auto &scheduler_policies = + config["openconfig-qos:qos"]["scheduler-policies"]["scheduler-policy"]; + for (auto &policy : scheduler_policies) { + if (policy["name"].get() == cpu_scheduler_policy) { + for (auto &scheduler : policy["schedulers"]["scheduler"]) { + std::string queue_name = + scheduler["inputs"]["input"][0]["config"]["queue"] + .get(); + queue_info_by_queue_name[queue_name].gnmi_queue_name = queue_name; + std::string peak_rate = scheduler["two-rate-three-color"]["config"] + ["google-pins-qos:pir-pkts"] + .get(); + if (!absl::SimpleAtoi(peak_rate, &queue_info_by_queue_name[queue_name] + .rate_packets_per_second)) { + return absl::InternalError( + absl::StrCat("Unable to parse rate as int ", peak_rate, + " for queue ", queue_name)); + } + LOG(INFO) << "Queue: " << queue_name + << ", configured rate:" << peak_rate; + } + break; + } + } + + // TODO: Remove these once P4 uses gnmi queue names + queue_info_by_queue_name["BE1"].p4_queue_name = "0x2"; + queue_info_by_queue_name["AF1"].p4_queue_name = "0x3"; + queue_info_by_queue_name["AF2"].p4_queue_name = "0x4"; + queue_info_by_queue_name["AF3"].p4_queue_name = "0x5"; + queue_info_by_queue_name["AF4"].p4_queue_name = "0x6"; + queue_info_by_queue_name["LLQ1"].p4_queue_name = "0x0"; + queue_info_by_queue_name["LLQ2"].p4_queue_name = "0x1"; + queue_info_by_queue_name["NC1"].p4_queue_name = "0x7"; + + return queue_info_by_queue_name; } // Set up the switch to punt packets to CPU. @@ -937,7 +989,7 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { LOG(INFO) << "-- END OF TEST -----------------------------------------------"; } -TEST_P(CpuQosTestWithIxia, TestCPUQueueRateLimit) { +TEST_P(CpuQosTestWithIxia, TestCPUQueueAssignmentAndRateLimit) { // Pick a testbed with an Ixia Traffic Generator. auto requirements = gutil::ParseProtoOrDie( @@ -955,47 +1007,18 @@ TEST_P(CpuQosTestWithIxia, TestCPUQueueRateLimit) { thinkit::Switch& sut = generic_testbed->Sut(); - // Connect to TestTracker for test status. - if (auto &id = GetParam().test_case_id; id.has_value()) { - generic_testbed->Environment().SetTestCaseID(*id); - } - // Push GNMI config. ASSERT_OK(pins_test::PushGnmiConfig(sut, GetParam().gnmi_config)); // Hook up to GNMI. ASSERT_OK_AND_ASSIGN(auto gnmi_stub, sut.CreateGnmiStub()); - // 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, - pdpi::P4RuntimeSession::Create(generic_testbed->Sut())); - auto clear_table_entries = absl::Cleanup( - [&]() { ASSERT_OK(pdpi::ClearTableEntries(sut_p4_session.get())); }); - // 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"]; - - 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; @@ -1007,10 +1030,6 @@ TEST_P(CpuQosTestWithIxia, TestCPUQueueRateLimit) { gnmi_counters_check++) { absl::SleepFor(kPollInterval); - ASSERT_OK_AND_ASSIGN( - final_counters, - 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 2b5e3fdb..9d58d266 100644 --- a/tests/qos/cpu_qos_test.h +++ b/tests/qos/cpu_qos_test.h @@ -80,6 +80,10 @@ struct ParamsForTestsWithIxia { std::string gnmi_config; p4::config::v1::P4Info p4info; absl::optional test_case_id; + // This is be the minimum guaranteed bandwidth for control path to Tester in + // the testbed. This is required to ensure the per queue rate limits to be + // tested are within this guaranteed end to end bandwidth. + int control_plane_bandwidth_bps; }; class CpuQosTestWithIxia From 581bbfdf5dc6bde77a92d6f134f7c200d46a7066 Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Thu, 5 Dec 2024 03:26:33 +0000 Subject: [PATCH 11/23] [Comb] Add test for flow rate limit & Adjust rate and burst. (#798) Co-authored-by: smolkaj Co-authored-by: kishanps --- tests/qos/cpu_qos_test.cc | 310 +++++++++++++++++++++++++++++++++++++- 1 file changed, 307 insertions(+), 3 deletions(-) diff --git a/tests/qos/cpu_qos_test.cc b/tests/qos/cpu_qos_test.cc index 09071f12..11badcc3 100644 --- a/tests/qos/cpu_qos_test.cc +++ b/tests/qos/cpu_qos_test.cc @@ -212,6 +212,46 @@ absl::Status SetUpPuntToCPU(const netaddr::MacAddress &dmac, return pdpi::InstallPiTableEntries(&p4_session, ir_p4info, pi_entries); } +// Set up the switch to punt packets to CPU with meter. +absl::StatusOr SetUpPuntToCPUWithRateLimit( + const netaddr::MacAddress &dmac, const netaddr::Ipv4Address &src_ip, + const netaddr::Ipv4Address &dst_ip, absl::string_view p4_queue, + int rate_bytes_per_second, int burst_in_bytes, + 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: "; + + // TODO (b/204954722): Remove after bug is fixed. + 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 { acl_trap { qos_queue: "$3" } } + priority: 1 + meter_config { bytes_per_second: $4 burst_bytes: $5 } + } + )pb", + dmac.ToString(), src_ip.ToString(), dst_ip.ToString(), p4_queue, + rate_bytes_per_second, burst_in_bytes)); + + LOG(INFO) << "InstallPiTableEntry"; + ASSIGN_OR_RETURN(const p4::v1::TableEntry pi_acl_entry, + pdpi::PartialPdTableEntryToPiTableEntry(ir_p4info, acl_entry)); + RETURN_IF_ERROR(pdpi::InstallPiTableEntry(&p4_session, pi_acl_entry)); + return pi_acl_entry; +} + // These are the counters we track in these tests. struct QueueCounters { int64_t num_packets_transmitted = 0; @@ -989,13 +1029,41 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { LOG(INFO) << "-- END OF TEST -----------------------------------------------"; } -TEST_P(CpuQosTestWithIxia, TestCPUQueueAssignmentAndRateLimit) { +// Buffering and software bottlenecks can cause +// some amount of variance in rate measured end to end. +// Level of tolerance for packet rate verification. +// This could be parameterized in future if this is platform +// dependent. +constexpr float kTolerancePercent = 3.0; + +// Ixia configurations: +// 1. Frames sent per second by Ixia. +// 2. Total frames sent by Ixia. +// 3. Default framesize. +// 4. Maximum framesize. +// 5. Minimum framesize. +constexpr int kFramesPerSecond = 1000000; +constexpr int kTotalFrames = 10000000; +constexpr absl::Duration kTrafficDuration = + absl::Seconds(kTotalFrames / kFramesPerSecond); +constexpr int kDefaultFrameSize = 1514; +constexpr int kMaxFrameSize = 9000; +constexpr int kMinFrameSize = 64; + +struct PacketReceiveInfo { + absl::Mutex mutex; + int num_packets_punted ABSL_GUARDED_BY(mutex) = 0; + absl::Time time_first_packet_punted ABSL_GUARDED_BY(mutex); + absl::Time time_last_packet_punted ABSL_GUARDED_BY(mutex); +}; + +TEST_P(CpuQosTestWithIxia, TestCPUQueueAssignmentAndQueueRateLimit) { // Pick a testbed with an Ixia Traffic Generator. auto requirements = gutil::ParseProtoOrDie( R"pb(interface_requirements { count: 1 - interface_mode: TRAFFIC_GENERATOR + interface_modes: TRAFFIC_GENERATOR })pb"); ASSERT_OK_AND_ASSIGN( @@ -1005,7 +1073,9 @@ TEST_P(CpuQosTestWithIxia, TestCPUQueueAssignmentAndRateLimit) { ASSERT_OK(generic_testbed->Environment().StoreTestArtifact( "gnmi_config.txt", GetParam().gnmi_config)); - thinkit::Switch& sut = generic_testbed->Sut(); + ASSERT_GT(GetParam().control_plane_bandwidth_bps, 0); + + thinkit::Switch &sut = generic_testbed->Sut(); // Push GNMI config. ASSERT_OK(pins_test::PushGnmiConfig(sut, GetParam().gnmi_config)); @@ -1033,5 +1103,239 @@ TEST_P(CpuQosTestWithIxia, TestCPUQueueAssignmentAndRateLimit) { } } +TEST_P(CpuQosTestWithIxia, TestPuntFlowRateLimitAndCounters) { + // Pick a testbed with an Ixia Traffic Generator. + auto requirements = + gutil::ParseProtoOrDie( + R"pb(interface_requirements { + count: 1 + interface_modes: TRAFFIC_GENERATOR + })pb"); + + ASSERT_OK_AND_ASSIGN( + std::unique_ptr generic_testbed, + GetParam().testbed_interface->GetTestbedWithRequirements(requirements)); + + ASSERT_OK(generic_testbed->Environment().StoreTestArtifact( + "gnmi_config.txt", GetParam().gnmi_config)); + + ASSERT_GT(GetParam().control_plane_bandwidth_bps, 0); + + thinkit::Switch &sut = generic_testbed->Sut(); + + // Push GNMI config. + ASSERT_OK(pins_test::PushGnmiConfig(sut, GetParam().gnmi_config)); + + // Hook up to GNMI. + ASSERT_OK_AND_ASSIGN(auto gnmi_stub, sut.CreateGnmiStub()); + + // Flow details. + 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); + + // Go through all the ports that interface to the Ixia and set them + // to 100GB since the Ixia ports are all 100GB. + const absl::flat_hash_map + interface_info = generic_testbed->GetSutInterfaceInfo(); + for (const auto &[interface, info] : interface_info) { + if (info.interface_modes.contains(thinkit::TRAFFIC_GENERATOR)) + { + ASSERT_OK(SetPortSpeed("\"openconfig-if-ethernet:SPEED_100GB\"", + interface, *gnmi_stub)); + } + } + + // Wait to let the links come up. Switch guarantees state paths to reflect + // in 10s. Lets wait for a bit more. + absl::SleepFor(kTimeToWaitForGnmiConfigToApply); + + // TODO: Move this to helper function. + // Loop through the interface_info looking for Ixia/SUT interface pairs, + // checking if the link is up. we need one pair with link up for the + // ingress interface/IXIA traffic generation. + std::string ixia_interface; + std::string sut_interface; + bool sut_link_up = false; + for (const auto &[interface, info] : interface_info) { + if (info.interface_modes.contains(thinkit::TRAFFIC_GENERATOR)) { + ASSERT_OK_AND_ASSIGN(sut_link_up, CheckLinkUp(interface, *gnmi_stub)); + if (sut_link_up) { + ixia_interface = info.peer_interface_name; + sut_interface = interface; + break; + } + } + } + + ASSERT_TRUE(sut_link_up); + + // We will perform the following steps with Ixia: + // Set up Ixia traffic. + // Send Ixia traffic. + // Stop Ixia traffic. + + ASSERT_OK_AND_ASSIGN(ixia::IxiaPortInfo ixia_port, + ixia::ExtractPortInfo(ixia_interface)); + + ASSERT_OK_AND_ASSIGN( + std::string topology_ref, + pins_test::ixia::IxiaConnect(ixia_port.hostname, *generic_testbed)); + + ASSERT_OK_AND_ASSIGN( + std::string vport_ref, + pins_test::ixia::IxiaVport(topology_ref, ixia_port.card, ixia_port.port, + *generic_testbed)); + + ASSERT_OK_AND_ASSIGN( + std::string traffic_ref, + pins_test::ixia::IxiaSession(vport_ref, *generic_testbed)); + + ASSERT_OK(pins_test::ixia::SetFrameRate(traffic_ref, kFramesPerSecond, + *generic_testbed)); + + ASSERT_OK(pins_test::ixia::SetFrameCount(traffic_ref, kTotalFrames, + *generic_testbed)); + + ASSERT_OK(pins_test::ixia::SetFrameSize(traffic_ref, kMaxFrameSize, + *generic_testbed)); + + ASSERT_OK(pins_test::ixia::SetSrcMac(traffic_ref, source_mac.ToString(), + *generic_testbed)); + + ASSERT_OK(pins_test::ixia::SetDestMac(traffic_ref, dest_mac.ToString(), + *generic_testbed)); + + ASSERT_OK(pins_test::ixia::AppendIPv4(traffic_ref, *generic_testbed)); + + ASSERT_OK(pins_test::ixia::SetSrcIPv4(traffic_ref, source_ip.ToString(), + *generic_testbed)); + + ASSERT_OK(pins_test::ixia::SetDestIPv4(traffic_ref, dest_ip.ToString(), + *generic_testbed)); + + // Set up P4Runtime session. + ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_p4_session, + pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables( + generic_testbed->Sut(), GetParam().p4info)); + + // Listen for punted packets from the SUT. + PacketReceiveInfo packet_receive_info; + { + absl::MutexLock lock(&packet_receive_info.mutex); + packet_receive_info.num_packets_punted = 0; + } + + // Get Queues. + ASSERT_OK_AND_ASSIGN(auto queues, + ExtractQueueInfoViaGnmiConfig(GetParam().gnmi_config)); + + for (auto &[queue_name, queue_info] : queues) { + // Lets set flow rate limit to be half of queue limit so that queue limit + // doesnt take effect. + int flow_rate_limit_in_bytes_per_second = + (kMaxFrameSize * queue_info.rate_packets_per_second) / 2; + + if (flow_rate_limit_in_bytes_per_second > + GetParam().control_plane_bandwidth_bps) { + flow_rate_limit_in_bytes_per_second = + GetParam().control_plane_bandwidth_bps / 2; + } + + // TODO : Need to fix supported CPU queues. Currently, punting + // to queue 0 is not supported by OA in SONiC. + if (generic_testbed->Environment().MaskKnownFailures() && + queue_info.p4_queue_name == "0x0") { + continue; + } + + LOG(INFO) << "\n\n\nTesting Queue : " << queue_info.gnmi_queue_name + << "\n===================\n\n\n"; + + ASSERT_OK_AND_ASSIGN( + p4::v1::TableEntry pi_acl_entry, + SetUpPuntToCPUWithRateLimit(dest_mac, source_ip, dest_ip, + queue_info.p4_queue_name, + flow_rate_limit_in_bytes_per_second, + /*burst_in_bytes=*/kMaxFrameSize, + GetParam().p4info, *sut_p4_session)); + + // Reset received packet count at tester for each iteration. + { + absl::MutexLock lock(&packet_receive_info.mutex); + packet_receive_info.num_packets_punted = 0; + } + + // Check that the counters are initially zero. + ASSERT_THAT( + pdpi::ReadPiCounterData(sut_p4_session.get(), pi_acl_entry), + IsOkAndHolds(EqualsProto(R"pb(byte_count: 0 packet_count: 0)pb"))); + + ASSERT_OK(pins_test::ixia::StartTraffic(traffic_ref, topology_ref, + *generic_testbed)); + + // Wait for Traffic to be sent. + absl::SleepFor(kTrafficDuration); + + ASSERT_OK(pins_test::ixia::StopTraffic(traffic_ref, *generic_testbed)); + + // Check for counters every 5 seconds upto 30 seconds till the fetched gNMI + // queue counter stats match packets and bytes sent by Ixia. + // Check that the counters increment within kMaxQueueCounterUpdateTime. + absl::Time time_packet_sent = absl::Now(); + p4::v1::CounterData counter_data; + do { + ASSERT_OK_AND_ASSIGN( + counter_data, + pdpi::ReadPiCounterData(sut_p4_session.get(), pi_acl_entry)); + } while (counter_data.packet_count() != kTotalFrames && + absl::Now() - time_packet_sent < kMaxQueueCounterUpdateTime); + p4::v1::CounterData expected_counter_data; + expected_counter_data.set_packet_count(kTotalFrames); + expected_counter_data.set_byte_count(static_cast(kMaxFrameSize) * + static_cast(kTotalFrames)); + EXPECT_THAT(counter_data, EqualsProto(expected_counter_data)) + << "Counter for the table entry given below did not match expectation " + "within " + << kMaxQueueCounterUpdateTime + << " after injecting the Ixia test packets via CPU queue " + << queue_name; + + // TODO: Remove masking this failure once bug is fixed. + if (!generic_testbed->Environment().MaskKnownFailures()) { + absl::MutexLock lock(&packet_receive_info.mutex); + + LOG(INFO) << "Packets received at Controller: " + << packet_receive_info.num_packets_punted; + LOG(INFO) << "Timestamp of first received packet: " + << packet_receive_info.time_first_packet_punted; + LOG(INFO) << "Timestamp of last received packet: " + << packet_receive_info.time_last_packet_punted; + + absl::Duration duration = packet_receive_info.time_last_packet_punted - + packet_receive_info.time_first_packet_punted; + LOG(INFO) << "Duration of packets received: " << duration; + LOG(INFO) << "Frame size: " << kMaxFrameSize; + int64_t rate_received_in_bytes_per_second = 0; + int64_t useconds = absl::ToInt64Microseconds(duration); + ASSERT_NE(useconds, 0); + int64_t num_bytes = + packet_receive_info.num_packets_punted * kMaxFrameSize; + LOG(INFO) << "Num bytes received: " << num_bytes; + rate_received_in_bytes_per_second = num_bytes * 1000000 / useconds; + LOG(INFO) << "Rate of packets received (bps): " + << rate_received_in_bytes_per_second; + EXPECT_LE( + rate_received_in_bytes_per_second, + flow_rate_limit_in_bytes_per_second * (1 + kTolerancePercent / 100)); + EXPECT_GE( + rate_received_in_bytes_per_second, + flow_rate_limit_in_bytes_per_second * (1 - kTolerancePercent / 100)); + } + } // for each queue. + +} + } // namespace } // namespace pins_test From d2b906d382da79d24d355f5c0d86eb5f44346a9f Mon Sep 17 00:00:00 2001 From: bibhuprasad-hcl <161687009+bibhuprasad-hcl@users.noreply.github.com> Date: Thu, 5 Dec 2024 03:27:09 +0000 Subject: [PATCH 12/23] [Thinkit] Increase tolerance for packet bytes (#799) Co-authored-by: Srikishen Pondicherry Shanmugam --- tests/gnmi/ethcounter_ixia_test.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/gnmi/ethcounter_ixia_test.cc b/tests/gnmi/ethcounter_ixia_test.cc index f019cdb9..788b048d 100644 --- a/tests/gnmi/ethcounter_ixia_test.cc +++ b/tests/gnmi/ethcounter_ixia_test.cc @@ -972,8 +972,8 @@ TEST_P(ExampleIxiaTestFixture, TestIPv4Pkts) { // during the test to account for this. EXPECT_GE(delta_out.out_pkts, 90000000); EXPECT_LE(delta_out.out_pkts, delta_out.out_pkts + 10); - EXPECT_GE(delta_out.out_octets, delta_out.out_pkts * kMtu - 10000); - EXPECT_LE(delta_out.out_octets, delta_out.out_pkts * kMtu + 10000); + EXPECT_GE(delta_out.out_octets, delta_out.out_pkts * kMtu - 15000); + EXPECT_LE(delta_out.out_octets, delta_out.out_pkts * kMtu + 15000); EXPECT_LE(delta_out.out_unicast_pkts, delta_out.out_pkts + 10); EXPECT_LE(delta_out.out_multicast_pkts, 10); EXPECT_EQ(delta_out.out_broadcast_pkts, 0); @@ -982,13 +982,13 @@ TEST_P(ExampleIxiaTestFixture, TestIPv4Pkts) { // TODO: Remove mask after bug is addressed. if (!generic_testbed->Environment().MaskKnownFailures()) { + EXPECT_LE(delta_in.in_ipv4_pkts, delta_out.out_pkts + 10); + EXPECT_GE(delta_in.in_ipv4_pkts, delta_out.out_pkts - 10); EXPECT_GE(delta_out.out_ipv4_pkts, delta_out.out_pkts - 10); EXPECT_LE(delta_out.out_ipv4_pkts, delta_out.out_pkts + 10); } EXPECT_EQ(delta_out.out_ipv6_pkts, 0); EXPECT_EQ(delta_out.out_ipv6_discarded_pkts, 0); - EXPECT_LE(delta_in.in_ipv4_pkts, delta_out.out_pkts + 10); - EXPECT_LE(delta_in.in_ipv4_pkts, delta_out.out_pkts - 10); LOG(INFO) << "\n\n\n\n\n---------- Restore ----------\n\n\n\n\n"; @@ -1253,8 +1253,8 @@ TEST_P(ExampleIxiaTestFixture, TestOutDiscards) { uint64_t expect = 90000000; EXPECT_GT(delta_out.out_pkts, ((expect * 35) / 100)); EXPECT_LT(delta_out.out_pkts, ((expect * 45) / 100)); - EXPECT_LE(delta_out.out_octets, delta_out.out_pkts * kMtu + 10000); - EXPECT_GE(delta_out.out_octets, delta_out.out_pkts * kMtu - 10000); + EXPECT_LE(delta_out.out_octets, delta_out.out_pkts * kMtu + 15000); + EXPECT_GE(delta_out.out_octets, delta_out.out_pkts * kMtu - 15000); EXPECT_LE(delta_out.out_unicast_pkts, delta_out.out_pkts + 10); EXPECT_GE(delta_out.out_unicast_pkts, delta_out.out_pkts - 10); EXPECT_LE(delta_out.out_multicast_pkts, 5); @@ -1517,8 +1517,8 @@ TEST_P(ExampleIxiaTestFixture, TestIPv6Pkts) { // during the test to account for this. EXPECT_GE(delta_out.out_pkts, 90000000); EXPECT_LE(delta_out.out_pkts, 90000000 + 10); - EXPECT_LE(delta_out.out_octets, delta_out.out_pkts * kMtu + 10000); - EXPECT_GE(delta_out.out_octets, delta_out.out_pkts * kMtu - 10000); + EXPECT_LE(delta_out.out_octets, delta_out.out_pkts * kMtu + 15000); + EXPECT_GE(delta_out.out_octets, delta_out.out_pkts * kMtu - 15000); EXPECT_LE(delta_out.out_unicast_pkts, delta_out.out_pkts + 10); EXPECT_GE(delta_out.out_unicast_pkts, delta_out.out_pkts - 10); EXPECT_LE(delta_out.out_multicast_pkts, 10); From fd98b847be98aa427602e4445a25be14727b0853 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Thu, 5 Dec 2024 03:27:52 +0000 Subject: [PATCH 13/23] [P4_Symbolic] Extend IR proto with error codes. (#800) Co-authored-by: kishanps --- p4_symbolic/ir/BUILD.bazel | 1 + p4_symbolic/ir/expected/basic.txt | 48 +++++++++++++++++++ .../ir/expected/complex_conditional.txt | 48 +++++++++++++++++++ p4_symbolic/ir/expected/conditional.txt | 48 +++++++++++++++++++ .../ir/expected/conditional_sequence.txt | 48 +++++++++++++++++++ .../ir/expected/default_transition.txt | 48 +++++++++++++++++++ .../ir/expected/extract_parser_operation.txt | 48 +++++++++++++++++++ p4_symbolic/ir/expected/hardcoded.txt | 48 +++++++++++++++++++ .../ir/expected/hex_string_transition.txt | 48 +++++++++++++++++++ p4_symbolic/ir/expected/reflector.txt | 48 +++++++++++++++++++ p4_symbolic/ir/expected/sai_parser.txt | 48 +++++++++++++++++++ p4_symbolic/ir/expected/set_invalid.txt | 48 +++++++++++++++++++ .../ir/expected/set_parser_operation.txt | 48 +++++++++++++++++++ p4_symbolic/ir/expected/string_optional.txt | 48 +++++++++++++++++++ p4_symbolic/ir/expected/table.txt | 48 +++++++++++++++++++ p4_symbolic/ir/expected/table_hit_1.txt | 48 +++++++++++++++++++ p4_symbolic/ir/expected/table_hit_2.txt | 48 +++++++++++++++++++ p4_symbolic/ir/ir.cc | 30 ++++++++++++ p4_symbolic/ir/ir.proto | 10 ++++ 19 files changed, 809 insertions(+) diff --git a/p4_symbolic/ir/BUILD.bazel b/p4_symbolic/ir/BUILD.bazel index 3d491988..75604cb2 100644 --- a/p4_symbolic/ir/BUILD.bazel +++ b/p4_symbolic/ir/BUILD.bazel @@ -89,6 +89,7 @@ cc_library( "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", + "@com_google_protobuf//:protobuf", ], ) diff --git a/p4_symbolic/ir/expected/basic.txt b/p4_symbolic/ir/expected/basic.txt index a817b615..08c4b038 100644 --- a/p4_symbolic/ir/expected/basic.txt +++ b/p4_symbolic/ir/expected/basic.txt @@ -709,6 +709,54 @@ parsers { } } } +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} =====MyIngress.ipv4_lpm Entries===== diff --git a/p4_symbolic/ir/expected/complex_conditional.txt b/p4_symbolic/ir/expected/complex_conditional.txt index 16b2da87..cd6ab5e6 100644 --- a/p4_symbolic/ir/expected/complex_conditional.txt +++ b/p4_symbolic/ir/expected/complex_conditional.txt @@ -2100,4 +2100,52 @@ parsers { } } } +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} diff --git a/p4_symbolic/ir/expected/conditional.txt b/p4_symbolic/ir/expected/conditional.txt index eb4e9766..135c206c 100644 --- a/p4_symbolic/ir/expected/conditional.txt +++ b/p4_symbolic/ir/expected/conditional.txt @@ -732,4 +732,52 @@ parsers { } } } +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} diff --git a/p4_symbolic/ir/expected/conditional_sequence.txt b/p4_symbolic/ir/expected/conditional_sequence.txt index d7c57de3..a123108f 100644 --- a/p4_symbolic/ir/expected/conditional_sequence.txt +++ b/p4_symbolic/ir/expected/conditional_sequence.txt @@ -2369,4 +2369,52 @@ parsers { } } } +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} diff --git a/p4_symbolic/ir/expected/default_transition.txt b/p4_symbolic/ir/expected/default_transition.txt index c5457a1b..72ff7d14 100644 --- a/p4_symbolic/ir/expected/default_transition.txt +++ b/p4_symbolic/ir/expected/default_transition.txt @@ -201,4 +201,52 @@ parsers { } } } +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} diff --git a/p4_symbolic/ir/expected/extract_parser_operation.txt b/p4_symbolic/ir/expected/extract_parser_operation.txt index c5457a1b..72ff7d14 100644 --- a/p4_symbolic/ir/expected/extract_parser_operation.txt +++ b/p4_symbolic/ir/expected/extract_parser_operation.txt @@ -201,4 +201,52 @@ parsers { } } } +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} diff --git a/p4_symbolic/ir/expected/hardcoded.txt b/p4_symbolic/ir/expected/hardcoded.txt index 7a4a4290..4b82aba5 100644 --- a/p4_symbolic/ir/expected/hardcoded.txt +++ b/p4_symbolic/ir/expected/hardcoded.txt @@ -301,4 +301,52 @@ parsers { } } } +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} diff --git a/p4_symbolic/ir/expected/hex_string_transition.txt b/p4_symbolic/ir/expected/hex_string_transition.txt index 90beac85..103f0dc0 100644 --- a/p4_symbolic/ir/expected/hex_string_transition.txt +++ b/p4_symbolic/ir/expected/hex_string_transition.txt @@ -361,4 +361,52 @@ parsers { } } } +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} diff --git a/p4_symbolic/ir/expected/reflector.txt b/p4_symbolic/ir/expected/reflector.txt index 9b3cccca..3e7fdf55 100644 --- a/p4_symbolic/ir/expected/reflector.txt +++ b/p4_symbolic/ir/expected/reflector.txt @@ -220,4 +220,52 @@ parsers { } } } +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} diff --git a/p4_symbolic/ir/expected/sai_parser.txt b/p4_symbolic/ir/expected/sai_parser.txt index 19e8a201..419203b3 100644 --- a/p4_symbolic/ir/expected/sai_parser.txt +++ b/p4_symbolic/ir/expected/sai_parser.txt @@ -1656,4 +1656,52 @@ parsers { } } } +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} diff --git a/p4_symbolic/ir/expected/set_invalid.txt b/p4_symbolic/ir/expected/set_invalid.txt index fdcd0217..6ac196d8 100644 --- a/p4_symbolic/ir/expected/set_invalid.txt +++ b/p4_symbolic/ir/expected/set_invalid.txt @@ -375,4 +375,52 @@ parsers { } } } +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} diff --git a/p4_symbolic/ir/expected/set_parser_operation.txt b/p4_symbolic/ir/expected/set_parser_operation.txt index b595647b..038d917b 100644 --- a/p4_symbolic/ir/expected/set_parser_operation.txt +++ b/p4_symbolic/ir/expected/set_parser_operation.txt @@ -457,4 +457,52 @@ parsers { } } } +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} diff --git a/p4_symbolic/ir/expected/string_optional.txt b/p4_symbolic/ir/expected/string_optional.txt index bbb3f6c7..f626b62d 100644 --- a/p4_symbolic/ir/expected/string_optional.txt +++ b/p4_symbolic/ir/expected/string_optional.txt @@ -575,4 +575,52 @@ parsers { } } } +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} diff --git a/p4_symbolic/ir/expected/table.txt b/p4_symbolic/ir/expected/table.txt index e19cbad8..d6807c96 100644 --- a/p4_symbolic/ir/expected/table.txt +++ b/p4_symbolic/ir/expected/table.txt @@ -345,6 +345,54 @@ parsers { } } } +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} =====MyIngress.ports_exact Entries===== diff --git a/p4_symbolic/ir/expected/table_hit_1.txt b/p4_symbolic/ir/expected/table_hit_1.txt index a21d9246..18c169e9 100644 --- a/p4_symbolic/ir/expected/table_hit_1.txt +++ b/p4_symbolic/ir/expected/table_hit_1.txt @@ -610,4 +610,52 @@ parsers { } } } +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} diff --git a/p4_symbolic/ir/expected/table_hit_2.txt b/p4_symbolic/ir/expected/table_hit_2.txt index b49b0915..d8e13181 100644 --- a/p4_symbolic/ir/expected/table_hit_2.txt +++ b/p4_symbolic/ir/expected/table_hit_2.txt @@ -752,4 +752,52 @@ parsers { } } } +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} diff --git a/p4_symbolic/ir/ir.cc b/p4_symbolic/ir/ir.cc index 3e52de06..0519900c 100644 --- a/p4_symbolic/ir/ir.cc +++ b/p4_symbolic/ir/ir.cc @@ -32,6 +32,7 @@ #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "absl/strings/strip.h" +#include "google/protobuf/struct.pb.h" #include "gutil/status.h" #include "p4/config/v1/p4info.pb.h" #include "p4_symbolic/bmv2/bmv2.pb.h" @@ -1020,6 +1021,29 @@ absl::StatusOr ExtractParser(const bmv2::Parser &bmv2_parser) { return parser; } +// Translate an error code definition from the BMv2 protobuf message. +absl::StatusOr ExtractError( + const google::protobuf::ListValue &bmv2_error) { + // A BMv2 error must have 2 elements. + if (bmv2_error.values_size() != 2) { + return gutil::InvalidArgumentErrorBuilder() + << "Error field must contain 2 elements. Found: " + << bmv2_error.DebugString(); + } + + if (!bmv2_error.values(0).has_string_value() || + !bmv2_error.values(1).has_number_value()) { + return gutil::InvalidArgumentErrorBuilder() + << "Error field must be [string, int]. Found: " + << bmv2_error.DebugString(); + } + + Error output; + output.set_name(bmv2_error.values(0).string_value()); + output.set_value(bmv2_error.values(1).number_value()); + return output; +} + } // namespace // Main Translation function. @@ -1213,6 +1237,12 @@ absl::StatusOr Bmv2AndP4infoToIr(const bmv2::P4Program &bmv2, } } + // Translate errors from BMv2. + for (const google::protobuf::ListValue &bmv2_error : bmv2.errors()) { + ASSIGN_OR_RETURN(Error error, ExtractError(bmv2_error)); + (*output.mutable_errors())[error.name()] = std::move(error); + } + // Create the Control Flow Graph (CFG) of the program and perform analysis for // optimized symbolic execution. ASSIGN_OR_RETURN(std::unique_ptr cfg, diff --git a/p4_symbolic/ir/ir.proto b/p4_symbolic/ir/ir.proto index 89f61a8f..40982e5b 100644 --- a/p4_symbolic/ir/ir.proto +++ b/p4_symbolic/ir/ir.proto @@ -50,6 +50,8 @@ message P4Program { map pipeline = 5; // Parsers, keyed by parser names. map parsers = 6; + // Errors, keyed by error names. + map errors = 7; // TODO: If needed in the future, action calls can be added here, for // action calls that are not wrapped in other control constructs. @@ -272,6 +274,14 @@ message Pipeline { string initial_control = 2; } +// Defines an error code that may be used in the program. +message Error { + // The name of the error. + string name = 1; + // The value of the error code. + uint32 value = 2; +} + // An abstract p4 statement corresponding to a top level operation within // an action body. message Statement { From a130752ca609d176f396bccb648234654591fd71 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Thu, 5 Dec 2024 03:28:29 +0000 Subject: [PATCH 14/23] [Dvaas] Allow packet_ins for custom test vectors. (#801) Co-authored-by: kishanps --- dvaas/BUILD.bazel | 4 +- dvaas/dataplane_validation.cc | 7 +- dvaas/user_provided_packet_test_vector.cc | 64 ++++- dvaas/user_provided_packet_test_vector.h | 13 +- .../user_provided_packet_test_vector_test.cc | 154 ++++++++++- ..._provided_packet_test_vector_test.expected | 258 ++++++++++++++++++ 6 files changed, 479 insertions(+), 21 deletions(-) diff --git a/dvaas/BUILD.bazel b/dvaas/BUILD.bazel index 2ca01581..8159b06c 100644 --- a/dvaas/BUILD.bazel +++ b/dvaas/BUILD.bazel @@ -252,7 +252,9 @@ cc_library( ":test_vector_cc_proto", "//gutil:proto", "//gutil:status", + "//p4_pdpi:ir", "//p4_pdpi/packetlib", + "@com_github_p4lang_p4runtime//:p4runtime_cc_proto", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", @@ -274,6 +276,7 @@ cc_test( "//gutil:status_matchers", "//gutil:testing", "//p4_pdpi/packetlib:packetlib_cc_proto", + "//p4_pdpi/testing:test_p4info_cc", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", @@ -376,7 +379,6 @@ cc_library( "//lib/gnmi:gnmi_helper", "//lib/gnmi:openconfig_cc_proto", "//lib/p4rt:p4rt_port", - "//p4_pdpi:ir", "//p4_pdpi:ir_cc_proto", "//p4_pdpi:p4_runtime_session", "//p4_pdpi:p4_runtime_session_extras", diff --git a/dvaas/dataplane_validation.cc b/dvaas/dataplane_validation.cc index 38b12642..a8a6b860 100644 --- a/dvaas/dataplane_validation.cc +++ b/dvaas/dataplane_validation.cc @@ -41,7 +41,6 @@ #include "lib/gnmi/openconfig.pb.h" #include "lib/p4rt/p4rt_port.h" #include "p4/v1/p4runtime.pb.h" -#include "p4_pdpi/ir.h" #include "p4_pdpi/ir.pb.h" #include "p4_pdpi/p4_runtime_session.h" #include "p4_pdpi/p4_runtime_session_extras.h" @@ -254,8 +253,10 @@ absl::StatusOr DataplaneValidator::ValidateDataplane( GenerateTestVectors(params, sut, *backend_, *writer)); } else { LOG(INFO) << "Checking user-provided test vectors for well-formedness"; - ASSIGN_OR_RETURN(test_vectors, LegitimizeUserProvidedTestVectors( - params.packet_test_vector_override)); + ASSIGN_OR_RETURN(pdpi::IrP4Info ir_info, pdpi::GetIrP4Info(*sut.p4rt)); + ASSIGN_OR_RETURN(test_vectors, + LegitimizeUserProvidedTestVectors( + params.packet_test_vector_override, ir_info)); } RETURN_IF_ERROR( writer->AppendToTestArtifact("test_vectors.txt", ToString(test_vectors))); diff --git a/dvaas/user_provided_packet_test_vector.cc b/dvaas/user_provided_packet_test_vector.cc index a2f11d7f..8bda97d2 100644 --- a/dvaas/user_provided_packet_test_vector.cc +++ b/dvaas/user_provided_packet_test_vector.cc @@ -11,6 +11,8 @@ #include "dvaas/test_vector.pb.h" #include "gutil/proto.h" #include "gutil/status.h" +#include "p4/v1/p4runtime.pb.h" +#include "p4_pdpi/ir.h" #include "p4_pdpi/packetlib/packetlib.h" namespace dvaas { @@ -30,10 +32,43 @@ absl::StatusOr LegitimizePacket(Packet packet) { return packet; } -// Checks that the given `vector` is well-formed and if so adds it to -// `legitimized_test_vectors_by_id`, or returns error otherwise. +// Checks that the given `packet_in`'s metadata is well-formed and if so +// returns it, or returns error otherwise. +absl::Status LegitimizePacketInMetadata(const PacketIn& packet_in, + const pdpi::IrP4Info& ir_info) { + // Check that PacketIn metadata is valid according to P4Runtime, in particular + // "Section 16.1: Packet I/O". + pdpi::IrPacketIn ir_packet_in; + for (const pdpi::IrPacketMetadata& metadata : packet_in.metadata()) { + *ir_packet_in.add_metadata() = metadata; + } + + // Translate IR `packet_in` into PI and translate resulting `packet_in` back + // into IR to validate its metadata is well-formed. If not, return an error. + ASSIGN_OR_RETURN(p4::v1::PacketIn pi_packet_in, + pdpi::IrPacketInToPi(ir_info, ir_packet_in)); + return absl::OkStatus(); +} + +absl::StatusOr LegitimizePacketIn(PacketIn packet_in, + const pdpi::IrP4Info& ir_info) { + RETURN_IF_ERROR( + packetlib::UpdateMissingComputedFields(*packet_in.mutable_parsed()) + .status()); + RETURN_IF_ERROR(packetlib::ValidatePacket(packet_in.parsed())); + ASSIGN_OR_RETURN(std::string raw_packet, + packetlib::RawSerializePacket(packet_in.parsed())); + packet_in.set_hex(absl::BytesToHexString(raw_packet)); + + RETURN_IF_ERROR(LegitimizePacketInMetadata(packet_in, ir_info)); + return packet_in; +} + +// Checks that the given input `vector` is well-formed using +// `ir_info` and if so adds it to `legitimized_test_vectors_by_id`, or returns +// error otherwise. absl::Status LegitimizeTestVector( - PacketTestVector vector, + PacketTestVector vector, const pdpi::IrP4Info& ir_info, PacketTestVectorById& legitimized_test_vectors_by_id) { if (vector.input().type() != SwitchInput::DATAPLANE) { return gutil::UnimplementedErrorBuilder() @@ -56,9 +91,21 @@ absl::Status LegitimizeTestVector( for (SwitchOutput& output : *vector.mutable_acceptable_outputs()) { // Punted output packets are not supported for now. if (!output.packet_ins().empty()) { - return gutil::UnimplementedErrorBuilder() - << "TODO: support vectors expecting `packet_ins` " - "(punting)"; + for (int i = 0; i < output.packet_ins().size(); ++i) { + PacketIn& output_packet_ins = *output.mutable_packet_ins(i); + ASSIGN_OR_RETURN( + int output_tag, ExtractTestPacketTag(output_packet_ins.parsed()), + _.SetPrepend() << "output packet in #" << (i + 1) << " invalid: "); + ASSIGN_OR_RETURN( + output_packet_ins, LegitimizePacketIn(output_packet_ins, ir_info), + _.SetPrepend() << "output packet in #" << (i + 1) << " invalid: "); + if (output_tag != tag) { + return gutil::InvalidArgumentErrorBuilder() + << "mismatch of input packet in tag vs output packet in tag " + "for output packet in #" + << (i + 1) << ": " << tag << " vs " << output_tag; + } + } } // Legitimize forwarded output packets. for (int i = 0; i < output.packets().size(); ++i) { @@ -95,11 +142,12 @@ absl::Status LegitimizeTestVector( } // namespace absl::StatusOr LegitimizeUserProvidedTestVectors( - absl::Span user_provided_test_vectors) { + absl::Span user_provided_test_vectors, + const pdpi::IrP4Info& ir_info) { PacketTestVectorById legitimized_test_vectors_by_id; for (const PacketTestVector& vector : user_provided_test_vectors) { absl::Status status = - LegitimizeTestVector(vector, legitimized_test_vectors_by_id); + LegitimizeTestVector(vector, ir_info, legitimized_test_vectors_by_id); if (!status.ok()) { return gutil::StatusBuilder(status.code()) << "problem in user-provided packet test vector: " diff --git a/dvaas/user_provided_packet_test_vector.h b/dvaas/user_provided_packet_test_vector.h index c368f0f8..778de862 100644 --- a/dvaas/user_provided_packet_test_vector.h +++ b/dvaas/user_provided_packet_test_vector.h @@ -15,10 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PINS_DVAAS_USER_PROVIDED_PACKET_TEST_VECTOR_H_ -#define PINS_DVAAS_USER_PROVIDED_PACKET_TEST_VECTOR_H_ - -#include +#ifndef PINS_USER_PROVIDED_PACKET_TEST_VECTOR_H_ +#define PINS_USER_PROVIDED_PACKET_TEST_VECTOR_H_ #include "absl/status/statusor.h" #include "absl/types/span.h" @@ -51,9 +49,12 @@ namespace dvaas { // * Unique among all test vectors. // * The input must be of type `DATAPLANE` (other types may be supported in the // future). +// * Switch output packet ins must have valid metadata conforming to P4Runtime +// "Section 16.1: Packet I/O". absl::StatusOr LegitimizeUserProvidedTestVectors( - absl::Span user_provided_test_vectors); + absl::Span user_provided_test_vectors, + const pdpi::IrP4Info& ir_info); } // namespace dvaas -#endif // PINS_DVAAS_USER_PROVIDED_PACKET_TEST_VECTOR_H_ +#endif // PINS_USER_PROVIDED_PACKET_TEST_VECTOR_H_ diff --git a/dvaas/user_provided_packet_test_vector_test.cc b/dvaas/user_provided_packet_test_vector_test.cc index 4b45c797..a7f502d6 100644 --- a/dvaas/user_provided_packet_test_vector_test.cc +++ b/dvaas/user_provided_packet_test_vector_test.cc @@ -33,6 +33,7 @@ #include "gutil/status_matchers.h" #include "gutil/testing.h" #include "p4_pdpi/packetlib/packetlib.pb.h" +#include "p4_pdpi/testing/test_p4info.h" namespace dvaas { namespace { @@ -63,8 +64,9 @@ void RunTestCase(const TestCase& test_case) { // Print output. std::cout << "-- Output --------------------------------------------------\n"; + pdpi::IrP4Info ir_p4info = pdpi::GetTestIrP4Info(); absl::StatusOr output = - LegitimizeUserProvidedTestVectors(test_case.vectors); + LegitimizeUserProvidedTestVectors(test_case.vectors, ir_p4info); if (!output.ok()) { // Print error without stack trace, for golden testing. std::cout << "ERROR: " @@ -236,6 +238,52 @@ std::vector GetPositiveTestCases() { }, }; + test_cases.emplace_back() = TestCase{ + .description = "packet_in with matching tag", + .vectors = + { + gutil::ParseProtoOrDie(R"pb( + input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "ff:ee:dd:cc:bb:aa" + ethernet_source: "55:44:33:22:11:00" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } + } + acceptable_outputs { + packet_ins { + metadata { + name: "ingress_port" + value: { hex_str: "0x00f" } + } + metadata { + name: "target_egress_port" + value: { str: "1" } + } + parsed { + headers { + ethernet_header { + ethernet_destination: "5:5:5:5:5:5" + ethernet_source: "5:5:5:5:5:5" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } + } + )pb"), + }, + }; return test_cases; } // namespace @@ -430,19 +478,119 @@ std::vector GetNegativeTestCases() { }, }; + test_cases.emplace_back() = TestCase{ + .description = "packet_in with mismatching tag", + .vectors = + { + gutil::ParseProtoOrDie(R"pb( + input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "ff:ee:dd:cc:bb:aa" + ethernet_source: "55:44:33:22:11:00" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } + } + acceptable_outputs { + packet_ins { + metadata { + name: "ingress_port" + value: { hex_str: "0x00f" } + } + metadata { + name: "target_egress_port" + value: { str: "1" } + } + parsed { + headers { + ethernet_header { + ethernet_destination: "5:5:5:5:5:5" + ethernet_source: "5:5:5:5:5:5" + ethertype: "0x000f" + } + } + payload: "test packet #24" + } + } + } + )pb"), + }, + }; + + test_cases.emplace_back() = TestCase{ + .description = "packet_in with malformed metadata", + .vectors = + { + gutil::ParseProtoOrDie(R"pb( + input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "ff:ee:dd:cc:bb:aa" + ethernet_source: "55:44:33:22:11:00" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } + } + acceptable_outputs { + packet_ins { + metadata { + name: "foobar" + value: { str: "foobar" } + } + metadata { + name: "ingress_port" + value: { hex_str: "0x00f" } + } + metadata { + name: "target_egress_port" + value: { str: "1" } + } + parsed { + headers { + ethernet_header { + ethernet_destination: "5:5:5:5:5:5" + ethernet_source: "5:5:5:5:5:5" + ethertype: "0x000f" + } + } + payload: "test packet #24" + } + } + } + )pb"), + }, + }; return test_cases; } TEST(InternalizeUserProvidedTestVectorsTest, PositiveTestCases) { + pdpi::IrP4Info ir_p4info = pdpi::GetTestIrP4Info(); for (TestCase test_case : GetPositiveTestCases()) { - EXPECT_THAT(LegitimizeUserProvidedTestVectors(test_case.vectors), IsOk()); + EXPECT_THAT(LegitimizeUserProvidedTestVectors(test_case.vectors, ir_p4info), + IsOk()); RunTestCase(test_case); } } TEST(InternalizeUserProvidedTestVectorsTest, NegativeTestCases) { + pdpi::IrP4Info ir_p4info = pdpi::GetTestIrP4Info(); for (TestCase test_case : GetNegativeTestCases()) { - EXPECT_THAT(LegitimizeUserProvidedTestVectors(test_case.vectors), + EXPECT_THAT(LegitimizeUserProvidedTestVectors(test_case.vectors, ir_p4info), Not(IsOk())); RunTestCase(test_case); } diff --git a/dvaas/user_provided_packet_test_vector_test.expected b/dvaas/user_provided_packet_test_vector_test.expected index 47929a7d..94c3d897 100644 --- a/dvaas/user_provided_packet_test_vector_test.expected +++ b/dvaas/user_provided_packet_test_vector_test.expected @@ -162,6 +162,62 @@ diff of internalized vector vs input vector: added: input.packet.hex: "050505050505050505050505000e74657374207061636b6574202335" +================================================================================ +InternalizeUserProvidedTestVectors Test: packet_in with matching tag +================================================================================ +-- Input --------------------------------------------------- +-- Input Packet Test Vector #1 -- +input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "ff:ee:dd:cc:bb:aa" + ethernet_source: "55:44:33:22:11:00" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } +} +acceptable_outputs { + packet_ins { + metadata { + name: "ingress_port" + value { + hex_str: "0x00f" + } + } + metadata { + name: "target_egress_port" + value { + str: "1" + } + } + parsed { + headers { + ethernet_header { + ethernet_destination: "5:5:5:5:5:5" + ethernet_source: "5:5:5:5:5:5" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } +} + +-- Output -------------------------------------------------- +-- Internalized Packet Test Vector #1 -- +test packet ID extracted from payload: 42 +diff of internalized vector vs input vector: +added: input.packet.hex: "ffeeddccbbaa554433221100000f74657374207061636b657420233432" +added: acceptable_outputs[0].packet_ins[0].hex: "050505050505050505050505000f74657374207061636b657420233432" + + ================================================================================ InternalizeUserProvidedTestVectors Test: input type != DATAPLANE ================================================================================ @@ -531,3 +587,205 @@ input { } acceptable_outputs { } + + +================================================================================ +InternalizeUserProvidedTestVectors Test: packet_in with mismatching tag +================================================================================ +-- Input --------------------------------------------------- +-- Input Packet Test Vector #1 -- +input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "ff:ee:dd:cc:bb:aa" + ethernet_source: "55:44:33:22:11:00" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } +} +acceptable_outputs { + packet_ins { + metadata { + name: "ingress_port" + value { + hex_str: "0x00f" + } + } + metadata { + name: "target_egress_port" + value { + str: "1" + } + } + parsed { + headers { + ethernet_header { + ethernet_destination: "5:5:5:5:5:5" + ethernet_source: "5:5:5:5:5:5" + ethertype: "0x000f" + } + } + payload: "test packet #24" + } + } +} + +-- Output -------------------------------------------------- +ERROR: INVALID_ARGUMENT: problem in user-provided packet test vector: mismatch of input packet in tag vs output packet in tag for output packet in #1: 42 vs 24 +Dumping offending test vector: +input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "ff:ee:dd:cc:bb:aa" + ethernet_source: "55:44:33:22:11:00" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } +} +acceptable_outputs { + packet_ins { + metadata { + name: "ingress_port" + value { + hex_str: "0x00f" + } + } + metadata { + name: "target_egress_port" + value { + str: "1" + } + } + parsed { + headers { + ethernet_header { + ethernet_destination: "5:5:5:5:5:5" + ethernet_source: "5:5:5:5:5:5" + ethertype: "0x000f" + } + } + payload: "test packet #24" + } + } +} + + +================================================================================ +InternalizeUserProvidedTestVectors Test: packet_in with malformed metadata +================================================================================ +-- Input --------------------------------------------------- +-- Input Packet Test Vector #1 -- +input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "ff:ee:dd:cc:bb:aa" + ethernet_source: "55:44:33:22:11:00" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } +} +acceptable_outputs { + packet_ins { + metadata { + name: "foobar" + value { + str: "foobar" + } + } + metadata { + name: "ingress_port" + value { + hex_str: "0x00f" + } + } + metadata { + name: "target_egress_port" + value { + str: "1" + } + } + parsed { + headers { + ethernet_header { + ethernet_destination: "5:5:5:5:5:5" + ethernet_source: "5:5:5:5:5:5" + ethertype: "0x000f" + } + } + payload: "test packet #24" + } + } +} + +-- Output -------------------------------------------------- +ERROR: INVALID_ARGUMENT: problem in user-provided packet test vector: output packet in #1 invalid: 'packet-in' message is invalid: Metadata with name foobar not defined. +Dumping offending test vector: +input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "ff:ee:dd:cc:bb:aa" + ethernet_source: "55:44:33:22:11:00" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } +} +acceptable_outputs { + packet_ins { + metadata { + name: "foobar" + value { + str: "foobar" + } + } + metadata { + name: "ingress_port" + value { + hex_str: "0x00f" + } + } + metadata { + name: "target_egress_port" + value { + str: "1" + } + } + parsed { + headers { + ethernet_header { + ethernet_destination: "5:5:5:5:5:5" + ethernet_source: "5:5:5:5:5:5" + ethertype: "0x000f" + } + } + payload: "test packet #24" + } + } +} From 3bcc079300f867acb0133760459e6b15c6f53f47 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Thu, 5 Dec 2024 03:31:47 +0000 Subject: [PATCH 15/23] [Dvaas] Follow-up CL to address comments after submit for cl/601288379: "Allow packet_ins for custom test vectors" (#802) Co-authored-by: kishanps --- dvaas/user_provided_packet_test_vector.cc | 66 +++++++++++------------ 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/dvaas/user_provided_packet_test_vector.cc b/dvaas/user_provided_packet_test_vector.cc index 8bda97d2..944ec813 100644 --- a/dvaas/user_provided_packet_test_vector.cc +++ b/dvaas/user_provided_packet_test_vector.cc @@ -19,16 +19,20 @@ namespace dvaas { namespace { +absl::StatusOr LegitimizeParsedPacketAndReturnAsHex( + packetlib::Packet& packet) { + RETURN_IF_ERROR(packetlib::UpdateMissingComputedFields(packet).status()); + RETURN_IF_ERROR(packetlib::ValidatePacket(packet)); + ASSIGN_OR_RETURN(std::string raw_packet, + packetlib::RawSerializePacket(packet)); + return absl::BytesToHexString(raw_packet); +} + // Checks that the given `input_packet` is well-formed, returning it with // omittable fields filled in if that is the case, or an error otherwise. absl::StatusOr LegitimizePacket(Packet packet) { - RETURN_IF_ERROR( - packetlib::UpdateMissingComputedFields(*packet.mutable_parsed()) - .status()); - RETURN_IF_ERROR(packetlib::ValidatePacket(packet.parsed())); - ASSIGN_OR_RETURN(std::string raw_packet, - packetlib::RawSerializePacket(packet.parsed())); - packet.set_hex(absl::BytesToHexString(raw_packet)); + ASSIGN_OR_RETURN(*packet.mutable_hex(), LegitimizeParsedPacketAndReturnAsHex( + *packet.mutable_parsed())); return packet; } @@ -43,24 +47,17 @@ absl::Status LegitimizePacketInMetadata(const PacketIn& packet_in, *ir_packet_in.add_metadata() = metadata; } - // Translate IR `packet_in` into PI and translate resulting `packet_in` back - // into IR to validate its metadata is well-formed. If not, return an error. - ASSIGN_OR_RETURN(p4::v1::PacketIn pi_packet_in, - pdpi::IrPacketInToPi(ir_info, ir_packet_in)); - return absl::OkStatus(); + // Translate IR `packet_in` into PI to validate its metadata is well-formed. + // If not, return an error. + return pdpi::IrPacketInToPi(ir_info, ir_packet_in).status(); } absl::StatusOr LegitimizePacketIn(PacketIn packet_in, const pdpi::IrP4Info& ir_info) { - RETURN_IF_ERROR( - packetlib::UpdateMissingComputedFields(*packet_in.mutable_parsed()) - .status()); - RETURN_IF_ERROR(packetlib::ValidatePacket(packet_in.parsed())); - ASSIGN_OR_RETURN(std::string raw_packet, - packetlib::RawSerializePacket(packet_in.parsed())); - packet_in.set_hex(absl::BytesToHexString(raw_packet)); - RETURN_IF_ERROR(LegitimizePacketInMetadata(packet_in, ir_info)); + ASSIGN_OR_RETURN( + *packet_in.mutable_hex(), + LegitimizeParsedPacketAndReturnAsHex(*packet_in.mutable_parsed())); return packet_in; } @@ -89,22 +86,19 @@ absl::Status LegitimizeTestVector( << "must specify at least 1 acceptable output, but 0 were found"; } for (SwitchOutput& output : *vector.mutable_acceptable_outputs()) { - // Punted output packets are not supported for now. - if (!output.packet_ins().empty()) { - for (int i = 0; i < output.packet_ins().size(); ++i) { - PacketIn& output_packet_ins = *output.mutable_packet_ins(i); - ASSIGN_OR_RETURN( - int output_tag, ExtractTestPacketTag(output_packet_ins.parsed()), - _.SetPrepend() << "output packet in #" << (i + 1) << " invalid: "); - ASSIGN_OR_RETURN( - output_packet_ins, LegitimizePacketIn(output_packet_ins, ir_info), - _.SetPrepend() << "output packet in #" << (i + 1) << " invalid: "); - if (output_tag != tag) { - return gutil::InvalidArgumentErrorBuilder() - << "mismatch of input packet in tag vs output packet in tag " - "for output packet in #" - << (i + 1) << ": " << tag << " vs " << output_tag; - } + for (int i = 0; i < output.packet_ins().size(); ++i) { + PacketIn& output_packet_ins = *output.mutable_packet_ins(i); + ASSIGN_OR_RETURN( + int output_tag, ExtractTestPacketTag(output_packet_ins.parsed()), + _.SetPrepend() << "output packet in #" << (i + 1) << " invalid: "); + ASSIGN_OR_RETURN( + output_packet_ins, LegitimizePacketIn(output_packet_ins, ir_info), + _.SetPrepend() << "output packet in #" << (i + 1) << " invalid: "); + if (output_tag != tag) { + return gutil::InvalidArgumentErrorBuilder() + << "mismatch of input packet in tag vs output packet in tag " + "for output packet in #" + << (i + 1) << ": " << tag << " vs " << output_tag; } } // Legitimize forwarded output packets. From 9f8746660dfa7a6cafeb91ac2a6335e1e4fb2918 Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Fri, 6 Dec 2024 02:55:01 +0000 Subject: [PATCH 16/23] [Thinkit] Skip test, Fix test on Brixia by increasing counter delay; fail early to avoid spurious errors, Try 100G and 200G for port speed, Increase tolerance level & Create P4 session and clear tables before pushing gNMI config. (#803) Co-authored-by: smolkaj Co-authored-by: kishanps --- tests/qos/cpu_qos_test.cc | 191 +++++++++++++++++++++++++++----------- 1 file changed, 137 insertions(+), 54 deletions(-) diff --git a/tests/qos/cpu_qos_test.cc b/tests/qos/cpu_qos_test.cc index 11badcc3..2011e722 100644 --- a/tests/qos/cpu_qos_test.cc +++ b/tests/qos/cpu_qos_test.cc @@ -94,7 +94,7 @@ constexpr int kFrameCheckSequenceSize = 4; // 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); +constexpr absl::Duration kMaxQueueCounterUpdateTime = absl::Seconds(20); // After pushing gNMI config to a switch, the tests sleep for this duration // assuming that the gNMI config will have been fully applied afterwards. @@ -487,6 +487,17 @@ TEST_P(CpuQosTestWithoutIxia, PerEntryAclCounterIncrementsWhenEntryIsHit) { ASSERT_OK_AND_ASSIGN(const pdpi::IrP4Info ir_p4info, pdpi::CreateIrP4Info(p4info)); + // 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)); + // Set up gNMI. EXPECT_OK(Testbed().Environment().StoreTestArtifact("gnmi_config.json", GetParam().gnmi_config)); @@ -505,16 +516,6 @@ TEST_P(CpuQosTestWithoutIxia, PerEntryAclCounterIncrementsWhenEntryIsHit) { 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( @@ -781,6 +782,17 @@ TEST_P(CpuQosTestWithoutIxia, ASSERT_OK_AND_ASSIGN(const pdpi::IrP4Info ir_p4info, pdpi::CreateIrP4Info(p4info)); + // 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)); + // Set up gNMI. EXPECT_OK(Testbed().Environment().StoreTestArtifact("gnmi_config.json", GetParam().gnmi_config)); @@ -799,16 +811,6 @@ TEST_P(CpuQosTestWithoutIxia, 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( @@ -892,6 +894,17 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { ASSERT_OK_AND_ASSIGN(const pdpi::IrP4Info ir_p4info, pdpi::CreateIrP4Info(p4info)); + // 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)); + // Set up gNMI. EXPECT_OK(Testbed().Environment().StoreTestArtifact("gnmi_config.json", GetParam().gnmi_config)); @@ -909,16 +922,6 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { 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. @@ -1015,7 +1018,10 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { CumulativeNumPacketsEnqueued(queue_counters_before_test_packet) && absl::Now() - time_packet_sent < kMaxQueueCounterUpdateTime); - EXPECT_EQ( + // We terminate early if this fails, as that can cause this loop to get + // out of sync when counters increment after a long delay, resulting in + // confusing error messages where counters increment by 2. + ASSERT_EQ( CumulativeNumPacketsEnqueued(queue_counters_after_test_packet), CumulativeNumPacketsEnqueued(queue_counters_before_test_packet) + 1) << "Counters for queue " << target_queue @@ -1034,7 +1040,7 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { // Level of tolerance for packet rate verification. // This could be parameterized in future if this is platform // dependent. -constexpr float kTolerancePercent = 3.0; +constexpr float kTolerancePercent = 4.0; // Ixia configurations: // 1. Frames sent per second by Ixia. @@ -1057,6 +1063,39 @@ struct PacketReceiveInfo { absl::Time time_last_packet_punted ABSL_GUARDED_BY(mutex); }; +// Structure represents a link between SUT and Ixia. +// This is represented by Ixia interface name and the SUT's gNMI interface +// name. +struct IxiaLink { + std::string ixia_interface; + std::string sut_interface; +}; +// Go over the connections and return vector of connections +// whose links are up. +absl::StatusOr> GetReadyIxiaLinks( + thinkit::GenericTestbed &generic_testbed, + gnmi::gNMI::StubInterface &gnmi_stub) { + std::vector links; + absl::flat_hash_map interface_info = + generic_testbed.GetSutInterfaceInfo(); + // Loop through the interface_info looking for Ixia/SUT interface pairs, + // checking if the link is up. Add the pair to connections. + for (const auto &[interface, info] : interface_info) { + bool sut_link_up = false; + if (info.interface_modes.contains(thinkit::TRAFFIC_GENERATOR)) + { + ASSIGN_OR_RETURN(sut_link_up, CheckLinkUp(interface, gnmi_stub)); + if (sut_link_up) { + links.push_back({ + .ixia_interface = info.peer_interface_name, + .sut_interface = interface, + }); + } + } + } + return links; +} + TEST_P(CpuQosTestWithIxia, TestCPUQueueAssignmentAndQueueRateLimit) { // Pick a testbed with an Ixia Traffic Generator. auto requirements = @@ -1116,6 +1155,9 @@ TEST_P(CpuQosTestWithIxia, TestPuntFlowRateLimitAndCounters) { std::unique_ptr generic_testbed, GetParam().testbed_interface->GetTestbedWithRequirements(requirements)); + // TODO: Skip test till known failure is fixed. + GTEST_SKIP() << "Skipping till b/203545459 is fixed"; + ASSERT_OK(generic_testbed->Environment().StoreTestArtifact( "gnmi_config.txt", GetParam().gnmi_config)); @@ -1136,40 +1178,46 @@ TEST_P(CpuQosTestWithIxia, TestPuntFlowRateLimitAndCounters) { const auto dest_ip = netaddr::Ipv4Address(172, 0, 0, 1); // Go through all the ports that interface to the Ixia and set them - // to 100GB since the Ixia ports are all 100GB. + // first to 200GB. const absl::flat_hash_map interface_info = generic_testbed->GetSutInterfaceInfo(); for (const auto &[interface, info] : interface_info) { if (info.interface_modes.contains(thinkit::TRAFFIC_GENERATOR)) { - ASSERT_OK(SetPortSpeed("\"openconfig-if-ethernet:SPEED_100GB\"", + ASSERT_OK(SetPortSpeed("\"openconfig-if-ethernet:SPEED_200GB\"", interface, *gnmi_stub)); + ASSERT_OK(SetPortMtu(kMaxFrameSize, interface, *gnmi_stub)); } } // Wait to let the links come up. Switch guarantees state paths to reflect // in 10s. Lets wait for a bit more. + LOG(INFO) << "Sleeping " << kTimeToWaitForGnmiConfigToApply + << " to wait for config to be applied/links to come up."; absl::SleepFor(kTimeToWaitForGnmiConfigToApply); - // TODO: Move this to helper function. - // Loop through the interface_info looking for Ixia/SUT interface pairs, - // checking if the link is up. we need one pair with link up for the - // ingress interface/IXIA traffic generation. - std::string ixia_interface; - std::string sut_interface; - bool sut_link_up = false; - for (const auto &[interface, info] : interface_info) { - if (info.interface_modes.contains(thinkit::TRAFFIC_GENERATOR)) { - ASSERT_OK_AND_ASSIGN(sut_link_up, CheckLinkUp(interface, *gnmi_stub)); - if (sut_link_up) { - ixia_interface = info.peer_interface_name; - sut_interface = interface; - break; + ASSERT_OK_AND_ASSIGN(std::vector ready_links, + GetReadyIxiaLinks(*generic_testbed, *gnmi_stub)); + // If links didnt come, lets try 100GB as some testbeds have 100GB + // IXIA connections. + if (ready_links.empty()) { + for (const auto &[interface, info] : interface_info) { + if (info.interface_modes.contains(thinkit::TRAFFIC_GENERATOR)) + ASSERT_OK(SetPortSpeed("\"openconfig-if-ethernet:SPEED_100GB\"", + interface, *gnmi_stub)); } } - } - - ASSERT_TRUE(sut_link_up); + // Wait to let the links come up. Switch guarantees state paths to reflect + // in 10s. Lets wait for a bit more. + LOG(INFO) << "Sleeping " << kTimeToWaitForGnmiConfigToApply + << " to wait for config to be applied/links to come up."; + absl::SleepFor(kTimeToWaitForGnmiConfigToApply); + ASSERT_OK_AND_ASSIGN(ready_links, + GetReadyIxiaLinks(*generic_testbed, *gnmi_stub)); + + ASSERT_FALSE(ready_links.empty()) << "Ixia links are not ready"; + std::string ixia_interface = ready_links[0].ixia_interface; + std::string sut_interface = ready_links[0].sut_interface; // We will perform the following steps with Ixia: // Set up Ixia traffic. @@ -1260,6 +1308,9 @@ TEST_P(CpuQosTestWithIxia, TestPuntFlowRateLimitAndCounters) { flow_rate_limit_in_bytes_per_second, /*burst_in_bytes=*/kMaxFrameSize, GetParam().p4info, *sut_p4_session)); + ASSERT_OK_AND_ASSIGN( + QueueCounters initial_counters, + GetGnmiQueueCounters("CPU", queue_info.gnmi_queue_name, *gnmi_stub)); // Reset received packet count at tester for each iteration. { @@ -1302,8 +1353,40 @@ TEST_P(CpuQosTestWithIxia, TestPuntFlowRateLimitAndCounters) { << " after injecting the Ixia test packets via CPU queue " << queue_name; - // TODO: Remove masking this failure once bug is fixed. - if (!generic_testbed->Environment().MaskKnownFailures()) { + // Verify GNMI queue stats match packets received. + static constexpr absl::Duration kPollInterval = absl::Seconds(5); + static constexpr absl::Duration kTotalTime = absl::Seconds(20); + static const int kIterations = kTotalTime / kPollInterval; + // Check for counters every 5 seconds upto 20 seconds till they match. + for (int gnmi_counters_check = 0; gnmi_counters_check < kIterations; + gnmi_counters_check++) { + absl::SleepFor(kPollInterval); + QueueCounters final_counters; + QueueCounters delta_counters; + ASSERT_OK_AND_ASSIGN( + final_counters, + GetGnmiQueueCounters("CPU", queue_info.gnmi_queue_name, *gnmi_stub)); + delta_counters = { + .num_packets_transmitted = final_counters.num_packets_transmitted - + initial_counters.num_packets_transmitted, + .num_packet_dropped = final_counters.num_packet_dropped - + initial_counters.num_packet_dropped, + }; + LOG(INFO) << delta_counters; + absl::MutexLock lock(&packet_receive_info.mutex); + if (delta_counters.num_packets_transmitted == + packet_receive_info.num_packets_punted) { + break; + } + ASSERT_NE(gnmi_counters_check, kIterations - 1) + << "GNMI packet count " + << delta_counters.num_packets_transmitted + + delta_counters.num_packet_dropped + << " != Packets received at controller " + << packet_receive_info.num_packets_punted; + } + + { absl::MutexLock lock(&packet_receive_info.mutex); LOG(INFO) << "Packets received at Controller: " From f484c2e69a4624f53fc3681bbdfd76ff91e697db Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Fri, 6 Dec 2024 02:56:04 +0000 Subject: [PATCH 17/23] [Comb] Add SAI packet-in header deparser test. (#804) Co-authored-by: kishanps Co-authored-by: smolkaj --- p4_symbolic/sai/deparser_test.cc | 78 +++++++++++++++++++++----------- p4_symbolic/sai/sai_test.cc | 2 +- pins_infra_deps.bzl | 6 +-- 3 files changed, 55 insertions(+), 31 deletions(-) diff --git a/p4_symbolic/sai/deparser_test.cc b/p4_symbolic/sai/deparser_test.cc index 5d09dc76..3f2f617a 100644 --- a/p4_symbolic/sai/deparser_test.cc +++ b/p4_symbolic/sai/deparser_test.cc @@ -19,19 +19,24 @@ #include #include "absl/status/statusor.h" +#include "absl/strings/str_format.h" #include "glog/logging.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "gutil/proto_matchers.h" #include "gutil/status.h" #include "gutil/status_matchers.h" #include "p4/v1/p4runtime.pb.h" #include "p4_pdpi/packetlib/packetlib.h" #include "p4_pdpi/packetlib/packetlib.pb.h" #include "p4_pdpi/string_encodings/bit_string.h" +#include "p4_pdpi/string_encodings/hex_string.h" +#include "p4_symbolic/parser.h" #include "p4_symbolic/sai/fields.h" #include "p4_symbolic/sai/parser.h" #include "p4_symbolic/sai/sai.h" #include "p4_symbolic/symbolic/symbolic.h" +#include "p4_symbolic/z3_util.h" #include "sai_p4/instantiations/google/instantiations.h" #include "sai_p4/instantiations/google/sai_nonstandard_platforms.h" #include "z3++.h" @@ -204,45 +209,64 @@ TEST_P(SaiDeparserTest, PacketInHeaderIsNeverParsedIntegrationTest) { { ASSERT_OK_AND_ASSIGN(SaiFields fields, GetSaiFields(state_->context.ingress_headers)); - // TODO: Execute the test unconditionally one we add the - // packet-in header to SAI P4. - if (!fields.headers.packet_in.has_value()) { - GTEST_SKIP() << "test does not apply, as program has no packet-in header"; - } - state_->solver->add(fields.headers.packet_in.value().valid); + EXPECT_TRUE(fields.headers.packet_in.has_value()); + state_->solver->add(fields.headers.packet_in->valid); } // Should be unsatisifiable, because we never parse a packet-in header. ASSERT_EQ(state_->solver->check(), z3::check_result::unsat); } -TEST_P(SaiDeparserTest, DISABLED_PacketInPacketParserIntegrationTest) { - // Add packet_in constraint. - { - ASSERT_OK_AND_ASSIGN(SaiFields fields, - GetSaiFields(state_->context.ingress_headers)); - // TODO: Execute the test unconditionally one we add the - // packet-in header to SAI P4. - if (!fields.headers.packet_in.has_value()) { - GTEST_SKIP() << "test does not apply, as program has no packet-in header"; - } - state_->solver->add(fields.headers.packet_in.value().valid); - } +using SimpleSaiDeparserTest = testing::TestWithParam; + +TEST_P(SimpleSaiDeparserTest, PacketInHeaderDeparsingIsCorrect) { + // Set up. + z3::solver solver = z3::solver(Z3Context()); + const auto config = sai::GetNonstandardForwardingPipelineConfig( + /*instantiation=*/GetParam(), sai::NonstandardPlatform::kP4Symbolic); + ASSERT_OK_AND_ASSIGN(symbolic::Dataplane dataplane, + ParseToIr(config, /*entries=*/{})); + ASSERT_OK_AND_ASSIGN(auto headers, + symbolic::SymbolicGuardedMap::CreateSymbolicGuardedMap( + dataplane.program.headers())); + ASSERT_OK_AND_ASSIGN(SaiFields fields, GetSaiFields(headers)); + + // Add packet_in constraints. + EXPECT_TRUE(fields.headers.packet_in.has_value()); + solver.add(fields.headers.packet_in->valid); + constexpr int kIngressPort = 42; + constexpr int kTargetEgpressPort = 17; + constexpr int kUnusedPad = 0; + solver.add(fields.headers.packet_in->ingress_port == kIngressPort); + solver.add(fields.headers.packet_in->target_egress_port == + kTargetEgpressPort); + solver.add(fields.headers.packet_in->unused_pad == kUnusedPad); + packetlib::Header expected_header; + packetlib::SaiP4BMv2PacketInHeader& packet_in_header = + *expected_header.mutable_sai_p4_bmv2_packet_in_header(); + packet_in_header.set_ingress_port(pdpi::BitsetToHexString<9>(kIngressPort)); + packet_in_header.set_target_egress_port( + pdpi::BitsetToHexString<9>(kTargetEgpressPort)); + packet_in_header.set_unused_pad(pdpi::BitsetToHexString<6>(kUnusedPad)); // Solve and deparse. - ASSERT_EQ(state_->solver->check(), z3::check_result::sat); - auto model = state_->solver->get_model(); - ASSERT_OK_AND_ASSIGN(std::string raw_packet, - SaiDeparser(state_->context.ingress_headers, model)); + ASSERT_EQ(solver.check(), z3::check_result::sat); + auto model = solver.get_model(); + ASSERT_OK_AND_ASSIGN(std::string raw_packet, SaiDeparser(headers, model)); - // Check we indeed got a packet_in packet. - packetlib::Packet packet = packetlib::ParsePacket(raw_packet); + // Check we indeed got a packet_in packet with the correct fields. + packetlib::Packet packet = packetlib::ParsePacket( + raw_packet, + /*first_header=*/packetlib::Header::kSaiP4Bmv2PacketInHeader); LOG(INFO) << "Z3-generated packet = " << packet.DebugString(); - ASSERT_GE(packet.headers_size(), 0); - ASSERT_TRUE(packet.headers(0).has_sai_p4_bmv2_packet_in_header()); + ASSERT_GT(packet.headers_size(), 0); + ASSERT_THAT(packet.headers(0), gutil::EqualsProto(expected_header)); } -INSTANTIATE_TEST_SUITE_P(Instantiation, SaiDeparserTest, +INSTANTIATE_TEST_SUITE_P(SaiDeparserTest, SaiDeparserTest, + testing::ValuesIn(sai::AllSaiInstantiations())); + +INSTANTIATE_TEST_SUITE_P(SimpleSaiDeparserTest, SimpleSaiDeparserTest, testing::ValuesIn(sai::AllSaiInstantiations())); } // namespace diff --git a/p4_symbolic/sai/sai_test.cc b/p4_symbolic/sai/sai_test.cc index a37d37a5..cdbe283c 100644 --- a/p4_symbolic/sai/sai_test.cc +++ b/p4_symbolic/sai/sai_test.cc @@ -111,7 +111,7 @@ TEST(EvaluateSaiPipeline, IngressPortIsAmongPassedValues) { std::vector pi_entries; for (auto& pd_entry : pd_entries.entries()) { ASSERT_OK_AND_ASSIGN(pi_entries.emplace_back(), - pdpi::PdTableEntryToPi(ir_p4info, pd_entry)); + pdpi::PartialPdTableEntryToPiTableEntry(ir_p4info, pd_entry)); } // Evaluate the SAI pipeline. diff --git a/pins_infra_deps.bzl b/pins_infra_deps.bzl index a8c500c6..fd2e4f6c 100644 --- a/pins_infra_deps.bzl +++ b/pins_infra_deps.bzl @@ -16,9 +16,9 @@ def pins_infra_deps(): if not native.existing_rule("com_github_bazelbuild_buildtools"): http_archive( name = "com_github_bazelbuild_buildtools", - sha256 = "44a6e5acc007e197d45ac3326e7f993f0160af9a58e8777ca7701e00501c0857", - strip_prefix = "buildtools-4.2.4", - url = "https://github.com/bazelbuild/buildtools/archive/4.2.4.tar.gz", + # sha256 = "44a6e5acc007e197d45ac3326e7f993f0160af9a58e8777ca7701e00501c0857", + strip_prefix = "buildtools-5.1.0", + url = "https://github.com/bazelbuild/buildtools/archive/refs/tags/5.1.0.tar.gz", ) if "boringssl" not in native.existing_rules(): http_archive( From fa47bcafd8d3a1191d56b8dc60243c8142f5f7b2 Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Fri, 6 Dec 2024 02:56:48 +0000 Subject: [PATCH 18/23] [Thinkit] Add thread safe PacketIO handler. (#805) Co-authored-by: rhalstea Co-authored-by: kishanps --- tests/forwarding/BUILD.bazel | 32 ++++++++++ tests/forwarding/l3_admit_test.cc | 98 +++++++++++++++++++++++++++++++ tests/forwarding/l3_admit_test.h | 40 +++++++++++++ tests/lib/BUILD.bazel | 14 +++++ tests/lib/packet_in_helper.cc | 89 ++++++++++++++++++++++++++++ tests/lib/packet_in_helper.h | 89 ++++++++++++++++++++++++++++ 6 files changed, 362 insertions(+) create mode 100644 tests/forwarding/l3_admit_test.cc create mode 100644 tests/forwarding/l3_admit_test.h create mode 100644 tests/lib/packet_in_helper.cc create mode 100644 tests/lib/packet_in_helper.h diff --git a/tests/forwarding/BUILD.bazel b/tests/forwarding/BUILD.bazel index eded229e..281e623c 100644 --- a/tests/forwarding/BUILD.bazel +++ b/tests/forwarding/BUILD.bazel @@ -168,6 +168,38 @@ cc_library( ], ) +cc_library( + name = "l3_admit_test", + testonly = True, + srcs = ["l3_admit_test.cc"], + hdrs = ["l3_admit_test.h"], + deps = [ + ":util", + "//gutil:proto", + "//gutil:status_matchers", + "//lib/gnmi:gnmi_helper", + "//p4_pdpi:ir", + "//p4_pdpi:ir_cc_proto", + "//p4_pdpi:p4_runtime_session", + "//p4_pdpi/packetlib", + "//p4_pdpi/packetlib:packetlib_cc_proto", + "//sai_p4/instantiations/google:instantiations", + "//sai_p4/instantiations/google:sai_p4info_cc", + "//tests/lib:p4rt_fixed_table_programming_helper", + "//tests/lib:packet_in_helper", + "//thinkit:mirror_testbed_fixture", + "@com_github_google_glog//:glog", + "@com_github_p4lang_p4runtime//:p4info_cc_proto", + "@com_github_p4lang_p4runtime//:p4runtime_cc_proto", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/time", + "@com_google_googletest//:gtest", + ], + alwayslink = True, +) + cc_library( name = "fuzzer_tests", testonly = True, diff --git a/tests/forwarding/l3_admit_test.cc b/tests/forwarding/l3_admit_test.cc new file mode 100644 index 00000000..97d6c8a1 --- /dev/null +++ b/tests/forwarding/l3_admit_test.cc @@ -0,0 +1,98 @@ +// 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/l3_admit_test.h" + +#include +#include +#include + +#include "absl/status/status.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 "glog/logging.h" +#include "gmock/gmock.h" +#include "gutil/proto.h" +#include "gutil/status_matchers.h" +#include "lib/gnmi/gnmi_helper.h" +#include "p4/v1/p4runtime.pb.h" +#include "p4_pdpi/ir.h" +#include "p4_pdpi/ir.pb.h" +#include "p4_pdpi/p4_runtime_session.h" +#include "p4_pdpi/packetlib/packetlib.h" +#include "p4_pdpi/packetlib/packetlib.pb.h" +#include "tests/forwarding/util.h" +#include "tests/lib/p4rt_fixed_table_programming_helper.h" +#include "tests/lib/packet_in_helper.h" +#include "thinkit/mirror_testbed_fixture.h" + +namespace pins { +namespace { + +absl::Status AddAndSetDefaultVrf(pdpi::P4RuntimeSession& session, + const pdpi::IrP4Info& ir_p4info, + const std::string& vrf_id) { + pdpi::IrWriteRequest ir_write_request; + RETURN_IF_ERROR(gutil::ReadProtoFromString( + absl::Substitute(R"pb( + updates { + type: INSERT + table_entry { + table_name: "vrf_table" + matches { + name: "vrf_id" + exact { str: "$0" } + } + action { name: "no_action" } + } + } + updates { + type: INSERT + table_entry { + table_name: "acl_pre_ingress_table" + priority: 2000 + action { + name: "set_vrf" + params { + name: "vrf_id" + value { str: "$0" } + } + } + } + } + )pb", + vrf_id), + &ir_write_request)); + ASSIGN_OR_RETURN(p4::v1::WriteRequest pi_write_request, + pdpi::IrWriteRequestToPi(ir_p4info, ir_write_request)); + return pdpi::SetMetadataAndSendPiWriteRequest(&session, pi_write_request); +} +} // namespace + +TEST_P(L3AdmitTestFixture, L3PacketsAreRoutedWhenMacAddressIsInMyStation) { + LOG(INFO) << "Starting test."; + + // PacketIO handlers for both the SUT and control switch. + std::unique_ptr packetio_sut = + std::make_unique(p4rt_sut_switch_session_.get(), + PacketInHelper::NoFilter); + std::unique_ptr packetio_control = + std::make_unique(p4rt_control_switch_session_.get(), + PacketInHelper::NoFilter); +} + +} // namespace pins diff --git a/tests/forwarding/l3_admit_test.h b/tests/forwarding/l3_admit_test.h new file mode 100644 index 00000000..b64889ff --- /dev/null +++ b/tests/forwarding/l3_admit_test.h @@ -0,0 +1,40 @@ +// 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_L3_ADMIT_TEST_H_ +#define PINS_TESTS_FORWARDING_L3_ADMIT_TEST_H_ + +#include + +#include "p4/config/v1/p4info.pb.h" +#include "p4_pdpi/ir.pb.h" +#include "p4_pdpi/p4_runtime_session.h" +#include "sai_p4/instantiations/google/instantiations.h" +#include "sai_p4/instantiations/google/sai_p4info.h" +#include "tests/lib/packet_in_helper.h" +#include "thinkit/mirror_testbed_fixture.h" + +namespace pins { + +class L3AdmitTestFixture : public thinkit::MirrorTestbedFixture { + protected: + + // This test runs on a mirror testbed setup so we open a P4RT connection to + // both switches. + std::unique_ptr p4rt_sut_switch_session_; + std::unique_ptr p4rt_control_switch_session_; +}; + +} // namespace pins + +#endif // PINS_TESTS_FORWARDING_L3_ADMIT_TEST_H_ diff --git a/tests/lib/BUILD.bazel b/tests/lib/BUILD.bazel index 9d930731..d6e7f410 100644 --- a/tests/lib/BUILD.bazel +++ b/tests/lib/BUILD.bazel @@ -112,3 +112,17 @@ cmd_diff_test( ":switch_test_setup_helpers_golden_test_runner", ], ) + +cc_library( + name = "packet_in_helper", + srcs = ["packet_in_helper.cc"], + hdrs = ["packet_in_helper.h"], + deps = [ + "//p4_pdpi:p4_runtime_session", + "@com_github_google_glog//:glog", + "@com_github_p4lang_p4runtime//:p4runtime_cc_proto", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/synchronization", + ], +) diff --git a/tests/lib/packet_in_helper.cc b/tests/lib/packet_in_helper.cc new file mode 100644 index 00000000..9a5623d5 --- /dev/null +++ b/tests/lib/packet_in_helper.cc @@ -0,0 +1,89 @@ +// 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/lib/packet_in_helper.h" + +#include "absl/status/status.h" +#include "absl/synchronization/mutex.h" +#include "glog/logging.h" +#include "p4/v1/p4runtime.pb.h" +#include "p4_pdpi/p4_runtime_session.h" + +namespace pins { + +PacketInHelper::PacketInHelper( + pdpi::P4RuntimeSession* p4rt_session, + std::function + packet_in_message_filter) + : p4rt_session_(*p4rt_session), + packet_in_message_filter_(std::move(packet_in_message_filter)) { + CHECK(p4rt_session != nullptr); // Crash OK in ctor. + + packet_in_thread_ = std::thread([&]() { + LOG(INFO) << "Start monitoring PacketIO events."; + p4::v1::StreamMessageResponse response; + while (p4rt_session_.StreamChannelRead(response)) { + if (packet_in_message_filter_(response)) { + PushBackPacketInMessage(response); + } + } + }); +} + +PacketInHelper::~PacketInHelper() { + // If the thread was never started then there isn't any other cleanup needed. + if (!packet_in_thread_.joinable()) return; + + // Otherwise we try to stop the P4RT session, and join back the thread. + absl::Status stop_session = p4rt_session_.Finish(); + if (!stop_session.ok()) { + LOG(ERROR) << "Problem stopping the P4RT session: " << stop_session; + return; + } + + packet_in_thread_.join(); + LOG(INFO) << "Stopped monitoring PacketIO events."; +} + +bool PacketInHelper::NoFilter(const p4::v1::StreamMessageResponse& response) { + return true; +} + +bool PacketInHelper::HasPacketInMessage() const { + absl::MutexLock l(&packet_in_lock_); + return !packet_in_messages_.empty(); +} + +absl::StatusOr +PacketInHelper::GetNextPacketInMessage() { + absl::MutexLock l(&packet_in_lock_); + + // If the queue is empty then we return an error. + if (packet_in_messages_.empty()) { + return absl::Status(absl::StatusCode::kOutOfRange, + "The PacketIn queue is empty."); + } + + // Otherwise, we return the next packet in the queue. + p4::v1::StreamMessageResponse message = packet_in_messages_.front(); + packet_in_messages_.pop(); + return message; +} + +void PacketInHelper::PushBackPacketInMessage( + const p4::v1::StreamMessageResponse& response) { + absl::MutexLock l(&packet_in_lock_); + packet_in_messages_.push(response); +} + +} // namespace pins diff --git a/tests/lib/packet_in_helper.h b/tests/lib/packet_in_helper.h new file mode 100644 index 00000000..89838d82 --- /dev/null +++ b/tests/lib/packet_in_helper.h @@ -0,0 +1,89 @@ +// 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_LIB_PACKET_IO_HELPER_H_ +#define PINS_TESTS_LIB_PACKET_IO_HELPER_H_ + +#include +#include +#include // NOLINT: third_party code. + +#include "absl/status/statusor.h" +#include "absl/synchronization/mutex.h" +#include "p4/v1/p4runtime.pb.h" +#include "p4_pdpi/p4_runtime_session.h" + +namespace pins { + +// A helper class for managing P4RT PacketIO requests. On construction it will +// spawn a thread to asynchronously collect PacketIn messages from the switch. +// +// NOTE: This class will not take ownership of a P4RT session, but on +// destruction it will close the gRPC stream channel to the switch. +class PacketInHelper final { + public: + // Spawns a thread to collect PacketIn messages sent from the switch. + // + // A filter can be used to limit the type of messages collected. The filter + // should return true for any packets the test wants to collect, and false for + // any packets the test wants to ignore. + explicit PacketInHelper( + pdpi::P4RuntimeSession* p4rt_session, + std::function + packet_in_message_filter); + + // Closes the P4RuntimeSession's stream, and joins the PacketIn thread. + ~PacketInHelper(); + + // Always returns true so no packet gets filtered out. + static bool NoFilter(const p4::v1::StreamMessageResponse& response); + + // Returns true if the PacketIn queue has packets. Otherwise it returns false. + bool HasPacketInMessage() const ABSL_LOCKS_EXCLUDED(packet_in_lock_); + + // Returns the next packet in the queue. If no packet exists in the queue it + // will return an OUT_OF_BOUNDS error. + absl::StatusOr GetNextPacketInMessage() + ABSL_LOCKS_EXCLUDED(packet_in_lock_); + + private: + // Helper method used by the PacketIn thread to update the PacketIn messages. + void PushBackPacketInMessage(const p4::v1::StreamMessageResponse& response) + ABSL_LOCKS_EXCLUDED(packet_in_lock_); + + pdpi::P4RuntimeSession& p4rt_session_; + + // Thread is spawned in ctor and joined in dtor. It will wait for a PacketIn + // message then update the PacketIn message queue. + std::thread packet_in_thread_; + + // Accessing the packet-in queue is lock protected because the P4RT server is + // sending new packets while the tests can be reading them back. + mutable absl::Mutex packet_in_lock_; + + // Hold all PacketIn messages until the test reads them out. + std::queue packet_in_messages_ + ABSL_GUARDED_BY(packet_in_lock_); + + // A filter to restrict which packets are actually collected by the + // PacketInHelper class. + // + // return true to collect the packet. + // return false to ignore the packet. + std::function + packet_in_message_filter_; +}; + +} // namespace pins + +#endif // PINS_TESTS_LIB_PACKET_IO_HELPER_H_ From 4f00ebb104f5e156573a70b481541dda6aaaf0d0 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Fri, 6 Dec 2024 02:57:24 +0000 Subject: [PATCH 19/23] [P4_Symbolic] Adding p4_symbolic/ir/expected/primitive_parser_operation.txt [Extend IR to support setValid, setInvalid in parsers.] (#806) Co-authored-by: kishanps Co-authored-by: smolkaj --- .../expected/primitive_parser_operation.txt | 551 ++++++++++++++++++ 1 file changed, 551 insertions(+) create mode 100644 p4_symbolic/ir/expected/primitive_parser_operation.txt diff --git a/p4_symbolic/ir/expected/primitive_parser_operation.txt b/p4_symbolic/ir/expected/primitive_parser_operation.txt new file mode 100644 index 00000000..2e3498c9 --- /dev/null +++ b/p4_symbolic/ir/expected/primitive_parser_operation.txt @@ -0,0 +1,551 @@ +headers { + key: "ethernet" + value { + name: "ethernet_t" + id: 2 + fields { + key: "dst_addr" + value { + name: "dst_addr" + bitwidth: 48 + } + } + fields { + key: "ether_type" + value { + name: "ether_type" + bitwidth: 16 + } + } + fields { + key: "src_addr" + value { + name: "src_addr" + bitwidth: 48 + } + } + } +} +headers { + key: "ipv4" + value { + name: "ipv4_t" + id: 3 + fields { + key: "do_not_fragment" + value { + name: "do_not_fragment" + bitwidth: 1 + } + } + fields { + key: "dscp" + value { + name: "dscp" + bitwidth: 6 + } + } + fields { + key: "dst_addr" + value { + name: "dst_addr" + bitwidth: 32 + } + } + fields { + key: "ecn" + value { + name: "ecn" + bitwidth: 2 + } + } + fields { + key: "frag_offset" + value { + name: "frag_offset" + bitwidth: 13 + } + } + fields { + key: "header_checksum" + value { + name: "header_checksum" + bitwidth: 16 + } + } + fields { + key: "identification" + value { + name: "identification" + bitwidth: 16 + } + } + fields { + key: "ihl" + value { + name: "ihl" + bitwidth: 4 + } + } + fields { + key: "more_fragments" + value { + name: "more_fragments" + bitwidth: 1 + } + } + fields { + key: "protocol" + value { + name: "protocol" + bitwidth: 8 + } + } + fields { + key: "reserved" + value { + name: "reserved" + bitwidth: 1 + } + } + fields { + key: "src_addr" + value { + name: "src_addr" + bitwidth: 32 + } + } + fields { + key: "total_len" + value { + name: "total_len" + bitwidth: 16 + } + } + fields { + key: "ttl" + value { + name: "ttl" + bitwidth: 8 + } + } + fields { + key: "version" + value { + name: "version" + bitwidth: 4 + } + } + } +} +headers { + key: "ipv6" + value { + name: "ipv6_t" + id: 4 + fields { + key: "dscp" + value { + name: "dscp" + bitwidth: 6 + } + } + fields { + key: "dst_addr" + value { + name: "dst_addr" + bitwidth: 128 + } + } + fields { + key: "ecn" + value { + name: "ecn" + bitwidth: 2 + } + } + fields { + key: "flow_label" + value { + name: "flow_label" + bitwidth: 20 + } + } + fields { + key: "hop_limit" + value { + name: "hop_limit" + bitwidth: 8 + } + } + fields { + key: "next_header" + value { + name: "next_header" + bitwidth: 8 + } + } + fields { + key: "payload_length" + value { + name: "payload_length" + bitwidth: 16 + } + } + fields { + key: "src_addr" + value { + name: "src_addr" + bitwidth: 128 + } + } + fields { + key: "version" + value { + name: "version" + bitwidth: 4 + } + } + } +} +headers { + key: "scalars" + value { + name: "scalars_0" + } +} +headers { + key: "standard_metadata" + value { + name: "standard_metadata" + id: 1 + fields { + key: "_padding" + value { + name: "_padding" + bitwidth: 3 + } + } + fields { + key: "checksum_error" + value { + name: "checksum_error" + bitwidth: 1 + } + } + fields { + key: "deq_qdepth" + value { + name: "deq_qdepth" + bitwidth: 19 + } + } + fields { + key: "deq_timedelta" + value { + name: "deq_timedelta" + bitwidth: 32 + } + } + fields { + key: "egress_global_timestamp" + value { + name: "egress_global_timestamp" + bitwidth: 48 + } + } + fields { + key: "egress_port" + value { + name: "egress_port" + bitwidth: 9 + } + } + fields { + key: "egress_rid" + value { + name: "egress_rid" + bitwidth: 16 + } + } + fields { + key: "egress_spec" + value { + name: "egress_spec" + bitwidth: 9 + } + } + fields { + key: "enq_qdepth" + value { + name: "enq_qdepth" + bitwidth: 19 + } + } + fields { + key: "enq_timestamp" + value { + name: "enq_timestamp" + bitwidth: 32 + } + } + fields { + key: "ingress_global_timestamp" + value { + name: "ingress_global_timestamp" + bitwidth: 48 + } + } + fields { + key: "ingress_port" + value { + name: "ingress_port" + bitwidth: 9 + } + } + fields { + key: "instance_type" + value { + name: "instance_type" + bitwidth: 32 + } + } + fields { + key: "mcast_grp" + value { + name: "mcast_grp" + bitwidth: 16 + } + } + fields { + key: "packet_length" + value { + name: "packet_length" + bitwidth: 32 + } + } + fields { + key: "parser_error" + value { + name: "parser_error" + bitwidth: 32 + } + } + fields { + key: "priority" + value { + name: "priority" + bitwidth: 3 + } + } + } +} +pipeline { + key: "egress" + value { + name: "egress" + initial_control: "__END_OF_PIPELINE__" + } +} +pipeline { + key: "ingress" + value { + name: "ingress" + initial_control: "__END_OF_PIPELINE__" + } +} +parsers { + key: "parser" + value { + name: "parser" + initial_state: "start" + parse_states { + key: "parse_ipv4" + value { + name: "parse_ipv4" + parser_ops { + extract { + header { + header_name: "ipv4" + } + } + } + parser_ops { + primitive { + assignment { + left { + field_value { + header_name: "ipv6" + field_name: "$valid$" + } + } + right { + bool_value { + } + } + } + } + } + transitions { + default_transition { + next_state: "__END_OF_PARSER__" + } + } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + } + } + } + parse_states { + key: "parse_ipv6" + value { + name: "parse_ipv6" + parser_ops { + extract { + header { + header_name: "ipv6" + } + } + } + parser_ops { + primitive { + assignment { + left { + field_value { + header_name: "ipv4" + field_name: "$valid$" + } + } + right { + bool_value { + } + } + } + } + } + transitions { + default_transition { + next_state: "__END_OF_PARSER__" + } + } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + } + } + } + parse_states { + key: "start" + value { + name: "start" + parser_ops { + extract { + header { + header_name: "ethernet" + } + } + } + parser_ops { + primitive { + assignment { + left { + field_value { + header_name: "ethernet" + field_name: "$valid$" + } + } + right { + bool_value { + value: true + } + } + } + } + } + transition_key_fields { + field { + header_name: "ethernet" + field_name: "ether_type" + } + } + transitions { + hex_string_transition { + value { + value: "0x0800" + } + mask { + } + next_state: "parse_ipv4" + } + } + transitions { + hex_string_transition { + value { + value: "0x86dd" + } + mask { + } + next_state: "parse_ipv6" + } + } + transitions { + default_transition { + next_state: "__END_OF_PARSER__" + } + } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } + } + } + } +} +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} + From 93cb757c952ba22fd34f32bdecddb511b5e0f52b Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Fri, 6 Dec 2024 02:58:36 +0000 Subject: [PATCH 20/23] Use `ActionSchemaByNameAndObjectType` to determine ACL schema format for redirect action parameters (#807) Co-authored-by: kishanps --- p4rt_app/sonic/app_db_acl_def_table_manager.cc | 7 ++++++- .../sonic/app_db_acl_def_table_manager_test.cc | 14 ++++++++------ pins_infra_deps.bzl | 7 ++----- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/p4rt_app/sonic/app_db_acl_def_table_manager.cc b/p4rt_app/sonic/app_db_acl_def_table_manager.cc index 96c3cac5..7509edd2 100644 --- a/p4rt_app/sonic/app_db_acl_def_table_manager.cc +++ b/p4rt_app/sonic/app_db_acl_def_table_manager.cc @@ -662,7 +662,12 @@ absl::Status VerifyActionParamAgainstSchema( swss::acl::ActionSchema schema; const auto& param_name = ir_param.param().name(); try { - schema = swss::acl::ActionSchemaByName(sai_param.action); + if (sai_param.object_type.empty()) { + schema = swss::acl::ActionSchemaByName(sai_param.action); + } else { + schema = swss::acl::ActionSchemaByNameAndObjectType( + sai_param.action, sai_param.object_type); + } } catch (...) { return InvalidArgumentErrorBuilder() << absl::Substitute( "Action '$0' Param '$1' references unknown SAI field '$2'.", diff --git a/p4rt_app/sonic/app_db_acl_def_table_manager_test.cc b/p4rt_app/sonic/app_db_acl_def_table_manager_test.cc index 61525280..77ea86ea 100644 --- a/p4rt_app/sonic/app_db_acl_def_table_manager_test.cc +++ b/p4rt_app/sonic/app_db_acl_def_table_manager_test.cc @@ -141,10 +141,11 @@ TEST(InsertAclTableDefinition, InsertsAclTableDefinition) { R"pb( id: 1 name: "multicast_group_id" + bitwidth: 16 annotations: "@sai_action_param(SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT)" annotations: "@sai_action_param_object_type(SAI_OBJECT_TYPE_IPMC_GROUP)" )pb", - pdpi::STRING)) + pdpi::HEX_STRING)) .entry_action( IrActionDefinitionBuilder() .preamble( @@ -155,10 +156,11 @@ TEST(InsertAclTableDefinition, InsertsAclTableDefinition) { R"pb( id: 1 name: "multicast_group_id" + bitwidth: 16 annotations: "@sai_action_param(SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT)" annotations: "@sai_action_param_object_type(SAI_OBJECT_TYPE_L2MC_GROUP)" )pb", - pdpi::STRING)) + pdpi::HEX_STRING)) .entry_action( IrActionDefinitionBuilder() .preamble( @@ -168,7 +170,7 @@ TEST(InsertAclTableDefinition, InsertsAclTableDefinition) { .param( R"pb( id: 1 - name: "multicast_group_id" + name: "nexthop_id" annotations: "@sai_action_param(SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT)" annotations: "@sai_action_param_object_type(SAI_OBJECT_TYPE_NEXT_HOP)" )pb", @@ -182,7 +184,7 @@ TEST(InsertAclTableDefinition, InsertsAclTableDefinition) { .param( R"pb( id: 1 - name: "multicast_group_id" + name: "port_id" annotations: "@sai_action_param(SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT)" annotations: "@sai_action_param_object_type(SAI_OBJECT_TYPE_PORT)" )pb", @@ -260,13 +262,13 @@ TEST(InsertAclTableDefinition, InsertsAclTableDefinition) { {"action/redirect_action_that_includes_next_hop_type", nlohmann::json::parse(R"JSON( [{"action": "SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT", - "param": "multicast_group_id", + "param": "nexthop_id", "object_type": "SAI_OBJECT_TYPE_NEXT_HOP"}])JSON") .dump()}, {"action/redirect_action_that_includes_port_type", nlohmann::json::parse(R"JSON( [{"action": "SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT", - "param": "multicast_group_id", + "param": "port_id", "object_type": "SAI_OBJECT_TYPE_PORT"}])JSON") .dump()}, {"size", "512"}, diff --git a/pins_infra_deps.bzl b/pins_infra_deps.bzl index fd2e4f6c..fd05c783 100644 --- a/pins_infra_deps.bzl +++ b/pins_infra_deps.bzl @@ -33,9 +33,6 @@ def pins_infra_deps(): if not native.existing_rule("com_github_grpc_grpc"): http_archive( name = "com_github_grpc_grpc", - # url = "https://github.com/grpc/grpc/archive/v1.46.0.zip", - # strip_prefix = "grpc-1.46.0", - # sha256 = "1cbd6d6dfc9b1235766fc6b1d66d4f1dbb87f877a44c2a799bc8ee6b383af0fa", url = "https://github.com/grpc/grpc/archive/v1.58.0.zip", strip_prefix = "grpc-1.58.0", sha256 = "aa329c7de707a03511c88206ef4483e9346ab6336b6be4378d294060aa7400b3", @@ -230,8 +227,8 @@ def pins_infra_deps(): if not native.existing_rule("sonic_swss_common"): http_archive( name = "sonic_swss_common", - url = "https://github.com/azure/sonic-swss-common/archive/e7917acd2d4a9c0121802437e3c899bd513ac888.zip", - strip_prefix = "sonic-swss-common-e7917acd2d4a9c0121802437e3c899bd513ac888", + url = "https://github.com/azure/sonic-swss-common/archive/f6c1614227f25dfa81ab5ccd0cb8cca265aaad7d.zip", + strip_prefix = "sonic-swss-common-f6c1614227f25dfa81ab5ccd0cb8cca265aaad7d", ) if not native.existing_rule("rules_pkg"): http_archive( From 632696a71efc103527a66525bd774c86e7448b3b Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Fri, 6 Dec 2024 02:59:15 +0000 Subject: [PATCH 21/23] [Thinkit] Removing TestCaseIDs. (#808) Co-authored-by: kishanps --- tests/forwarding/arbitration_test.cc | 14 -------------- tests/forwarding/smoke_test.cc | 8 -------- tests/forwarding/watch_port_test.cc | 6 ------ .../system/random_blackbox_events_tests.cc | 1 - tests/mtu_tests/mtu_tests.cc | 1 - tests/qos/frontpanel_qos_test.cc | 3 --- 6 files changed, 33 deletions(-) diff --git a/tests/forwarding/arbitration_test.cc b/tests/forwarding/arbitration_test.cc index 8ad11c7d..424927a3 100644 --- a/tests/forwarding/arbitration_test.cc +++ b/tests/forwarding/arbitration_test.cc @@ -105,31 +105,26 @@ WriteRequest GetWriteRequest(int num, absl::uint128 election_id, testing::Matcher NotPrimary() { return Not(gutil::IsOk()); } TEST_P(ArbitrationTestFixture, BecomePrimary) { - TestEnvironment().SetTestCaseID("c6506d76-5041-4f69-b398-a808ab473186"); ASSERT_OK_AND_ASSIGN(auto connection, BecomePrimary(0)); } TEST_P(ArbitrationTestFixture, FailToBecomePrimary) { - TestEnvironment().SetTestCaseID("60c56f72-96ca-4aea-8cdc-16e1b928d53a"); ASSERT_OK_AND_ASSIGN(auto connection, BecomePrimary(1)); ASSERT_THAT(BecomePrimary(0).status(), NotPrimary()); } TEST_P(ArbitrationTestFixture, ReplacePrimary) { - TestEnvironment().SetTestCaseID("03da98ad-c4c7-443f-bcc0-53f97103d0c3"); ASSERT_OK_AND_ASSIGN(auto connection1, BecomePrimary(1)); ASSERT_OK_AND_ASSIGN(auto connection2, BecomePrimary(2)); } TEST_P(ArbitrationTestFixture, ReplacePrimaryAfterFailure) { - TestEnvironment().SetTestCaseID("d5ffe4cc-ff0e-4d93-8334-a23f06c6232a"); ASSERT_OK_AND_ASSIGN(auto connection1, BecomePrimary(1)); ASSERT_THAT(BecomePrimary(0).status(), NotPrimary()); ASSERT_OK_AND_ASSIGN(auto connection2, BecomePrimary(2)); } TEST_P(ArbitrationTestFixture, FailToBecomePrimaryAfterPrimaryDisconnect) { - TestEnvironment().SetTestCaseID("53b4b886-c218-4c85-b212-13d32105c795"); { ASSERT_OK_AND_ASSIGN(auto connection, BecomePrimary(1)); ASSERT_OK(connection->Finish()); @@ -138,7 +133,6 @@ TEST_P(ArbitrationTestFixture, FailToBecomePrimaryAfterPrimaryDisconnect) { } TEST_P(ArbitrationTestFixture, ReconnectPrimary) { - TestEnvironment().SetTestCaseID("d95a4da4-139d-4bd6-a43c-dbdefb123fcf"); { ASSERT_OK_AND_ASSIGN(auto connection, BecomePrimary(0)); ASSERT_OK(connection->Finish()); @@ -147,13 +141,11 @@ TEST_P(ArbitrationTestFixture, ReconnectPrimary) { } TEST_P(ArbitrationTestFixture, DoublePrimary) { - TestEnvironment().SetTestCaseID("19614b15-ce8f-4832-9164-342c5585283a"); ASSERT_OK_AND_ASSIGN(auto connection, BecomePrimary(0)); ASSERT_THAT(BecomePrimary(0).status(), NotPrimary()); } TEST_P(ArbitrationTestFixture, LongEvolution) { - TestEnvironment().SetTestCaseID("a65deb93-e350-4322-a932-af699c4b583c"); { ASSERT_OK_AND_ASSIGN(auto connection1, BecomePrimary(1)); ASSERT_THAT(BecomePrimary(0).status(), NotPrimary()); @@ -182,7 +174,6 @@ TEST_P(ArbitrationTestFixture, LongEvolution) { } TEST_P(ArbitrationTestFixture, BackupCannotWrite) { - TestEnvironment().SetTestCaseID("64c714d8-73c6-48b1-ada6-8ac2e5267714"); ASSERT_OK_AND_ASSIGN(auto connection, BecomePrimary(2)); ASSERT_OK_AND_ASSIGN(auto stub, Stub()); @@ -200,7 +191,6 @@ TEST_P(ArbitrationTestFixture, BackupCannotWrite) { } TEST_P(ArbitrationTestFixture, BackupCanRead) { - TestEnvironment().SetTestCaseID("fb678921-d150-4535-b7b8-fc8cecb79a78"); ASSERT_OK_AND_ASSIGN(auto connection, BecomePrimary(1)); @@ -231,7 +221,6 @@ TEST_P(ArbitrationTestFixture, BackupCanRead) { } TEST_P(ArbitrationTestFixture, GetNotifiedOfActualPrimary) { - TestEnvironment().SetTestCaseID("46b83014-759b-4393-bb58-220c0ca38711"); ASSERT_OK_AND_ASSIGN(auto connection, BecomePrimary(1)); // Assemble arbitration request. @@ -265,7 +254,6 @@ TEST_P(ArbitrationTestFixture, GetNotifiedOfActualPrimary) { } TEST_P(ArbitrationTestFixture, NoIdControllerCannotBecomePrimary) { - TestEnvironment().SetTestCaseID("3699fc43-5ff8-44ee-8965-68f42c71c1ed"); // Assemble arbitration request. p4::v1::StreamMessageRequest request; @@ -295,7 +283,6 @@ TEST_P(ArbitrationTestFixture, NoIdControllerCannotBecomePrimary) { } TEST_P(ArbitrationTestFixture, OldPrimaryCannotWriteAfterNewPrimaryCameUp) { - TestEnvironment().SetTestCaseID("e4bc86a2-84f0-450a-888a-8a6f5f26fa8c"); int id1 = 1, id2 = 2; // Connects controller C1 with id=1 to become primary. @@ -321,7 +308,6 @@ TEST_P(ArbitrationTestFixture, OldPrimaryCannotWriteAfterNewPrimaryCameUp) { } TEST_P(ArbitrationTestFixture, PrimaryDowngradesItself) { - TestEnvironment().SetTestCaseID("3cb62c0f-4a1a-430c-978c-a3a2a11078cd"); int id1 = 1, id2 = 2; // Connects controller with id=2 to become primary. diff --git a/tests/forwarding/smoke_test.cc b/tests/forwarding/smoke_test.cc index e45cfbd9..53576bfb 100644 --- a/tests/forwarding/smoke_test.cc +++ b/tests/forwarding/smoke_test.cc @@ -75,8 +75,6 @@ TEST_P(SmokeTestFixture, DISABLED_ModifyWorks) { // 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( @@ -159,8 +157,6 @@ TEST_P(SmokeTestFixture, DISABLED_Bug181149419) { } 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 { @@ -177,8 +173,6 @@ 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 { @@ -199,8 +193,6 @@ 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 = diff --git a/tests/forwarding/watch_port_test.cc b/tests/forwarding/watch_port_test.cc index ff970274..caae646b 100644 --- a/tests/forwarding/watch_port_test.cc +++ b/tests/forwarding/watch_port_test.cc @@ -515,7 +515,6 @@ namespace { TEST_P(WatchPortTestFixture, VerifyBasicWcmpPacketDistribution) { thinkit::TestEnvironment& environment = GetParam().testbed->GetMirrorTestbed().Environment(); - environment.SetTestCaseID("9a4c3dac-44bd-489e-9237-d396b66c85f5"); absl::Span controller_port_ids = GetParam().port_ids; const int group_size = kNumWcmpMembersForTest; @@ -597,7 +596,6 @@ TEST_P(WatchPortTestFixture, VerifyBasicWcmpPacketDistribution) { TEST_P(WatchPortTestFixture, VerifyBasicWatchPortAction) { thinkit::TestEnvironment& environment = GetParam().testbed->GetMirrorTestbed().Environment(); - environment.SetTestCaseID("992725de-2051-49bb-928f-7b089643a9bd"); absl::Span controller_port_ids = GetParam().port_ids; const int group_size = kNumWcmpMembersForTest; @@ -712,7 +710,6 @@ TEST_P(WatchPortTestFixture, VerifyWatchPortActionInCriticalState) { } thinkit::MirrorTestbed& testbed = GetParam().testbed->GetMirrorTestbed(); thinkit::TestEnvironment& environment = testbed.Environment(); - environment.SetTestCaseID("964c7a38-b073-4296-85be-2bba1e33c6f9"); absl::Span controller_port_ids = GetParam().port_ids; const int group_size = kNumWcmpMembersForTest; @@ -821,7 +818,6 @@ TEST_P(WatchPortTestFixture, VerifyWatchPortActionInCriticalState) { TEST_P(WatchPortTestFixture, VerifyWatchPortActionForSingleMember) { thinkit::TestEnvironment& environment = GetParam().testbed->GetMirrorTestbed().Environment(); - environment.SetTestCaseID("60da7a07-1217-4d63-9716-1219d62065ff"); absl::Span controller_port_ids = GetParam().port_ids; const int group_size = 1; @@ -935,7 +931,6 @@ TEST_P(WatchPortTestFixture, VerifyWatchPortActionForSingleMember) { TEST_P(WatchPortTestFixture, VerifyWatchPortActionForMemberModify) { thinkit::TestEnvironment& environment = GetParam().testbed->GetMirrorTestbed().Environment(); - environment.SetTestCaseID("e93160fb-be64-495b-bb4d-f06a92c51e76"); absl::Span controller_port_ids = GetParam().port_ids; const int group_size = kNumWcmpMembersForTest; @@ -1048,7 +1043,6 @@ TEST_P(WatchPortTestFixture, VerifyWatchPortActionForMemberModify) { TEST_P(WatchPortTestFixture, VerifyWatchPortActionForDownPortMemberInsert) { thinkit::TestEnvironment& environment = GetParam().testbed->GetMirrorTestbed().Environment(); - environment.SetTestCaseID("e54da480-d2cc-42c6-bced-0354b5ab3329"); absl::Span controller_port_ids = GetParam().port_ids; const int group_size = kNumWcmpMembersForTest; ASSERT_OK_AND_ASSIGN(std::vector members, diff --git a/tests/integration/system/random_blackbox_events_tests.cc b/tests/integration/system/random_blackbox_events_tests.cc index bb797963..2fbc5bab 100644 --- a/tests/integration/system/random_blackbox_events_tests.cc +++ b/tests/integration/system/random_blackbox_events_tests.cc @@ -97,7 +97,6 @@ TEST_P(RandomBlackboxEventsTest, ControlPlaneWithTrafficWithoutValidation) { count: 2 interface_mode: CONTROL_INTERFACE })pb"))); - testbed->Environment().SetTestCaseID("491b3f60-1369-4099-9385-da5dd44a087d"); // Initial sanity check. ASSERT_OK(SwitchReady(testbed->Sut())); diff --git a/tests/mtu_tests/mtu_tests.cc b/tests/mtu_tests/mtu_tests.cc index 937eeabc..1e4af7e3 100644 --- a/tests/mtu_tests/mtu_tests.cc +++ b/tests/mtu_tests/mtu_tests.cc @@ -208,7 +208,6 @@ void SetPortMtu(gnmi::gNMI::StubInterface* stub, absl::string_view intf, } TEST_P(MtuRoutingTestFixture, MtuTest) { - testbed_->Environment().SetTestCaseID("cfb97855-fe79-494a-aef2-92821aef3b1f"); // Get original mtu on port under test on SUT. std::string if_state_path = absl::StrCat( "interfaces/interface[name=", destination_link_.sut_interface, diff --git a/tests/qos/frontpanel_qos_test.cc b/tests/qos/frontpanel_qos_test.cc index e5137f6d..ba4a8934 100644 --- a/tests/qos/frontpanel_qos_test.cc +++ b/tests/qos/frontpanel_qos_test.cc @@ -22,9 +22,6 @@ TEST_P(FrontpanelQosTest, PacketsGetMappedToCorrectQueuesBasedOnDscp) { std::unique_ptr testbed, GetParam().testbed_interface->GetTestbedWithRequirements(requirements)); - // Set test case ID. - testbed->Environment().SetTestCaseID("TODO: insert proper ID"); - // Switch set up. ASSERT_OK_AND_ASSIGN( std::unique_ptr sut_p4rt, From cae6689c1fe3e1ead82a21a188b3066f30657fef Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Fri, 6 Dec 2024 03:01:12 +0000 Subject: [PATCH 22/23] [P4_Symbolic] Extend IR to support `setValid`, `setInvalid` in parsers. (#809) Co-authored-by: kishanps Co-authored-by: smolkaj --- p4_symbolic/ir/BUILD.bazel | 7 ++ p4_symbolic/ir/ir.cc | 88 ++++++++++++++++++- p4_symbolic/ir/ir.proto | 20 ++++- .../parser/primitive_parser_operation.p4 | 79 +++++++++++++++++ 4 files changed, 189 insertions(+), 5 deletions(-) create mode 100644 p4_symbolic/testdata/parser/primitive_parser_operation.p4 diff --git a/p4_symbolic/ir/BUILD.bazel b/p4_symbolic/ir/BUILD.bazel index 75604cb2..8ae8aa81 100644 --- a/p4_symbolic/ir/BUILD.bazel +++ b/p4_symbolic/ir/BUILD.bazel @@ -223,6 +223,13 @@ ir_parsing_test( p4_program = "//p4_symbolic/testdata:parser/set_parser_operation.p4", ) +ir_parsing_test( + name = "primitive_parser_operation_test", + golden_file = "expected/primitive_parser_operation.txt", + p4_deps = ["//p4_symbolic/testdata:common/headers.p4"], + p4_program = "//p4_symbolic/testdata:parser/primitive_parser_operation.p4", +) + ir_parsing_test( name = "default_transition_test", golden_file = "expected/default_transition.txt", diff --git a/p4_symbolic/ir/ir.cc b/p4_symbolic/ir/ir.cc index 0519900c..9c0c3ee1 100644 --- a/p4_symbolic/ir/ir.cc +++ b/p4_symbolic/ir/ir.cc @@ -881,9 +881,88 @@ absl::StatusOr ExtractSetParserOp( return result; } +// Translates the "primitive" parser operation from the BMv2 protobuf message. +// Currently only "add_header" and "remove_header" primitives are supported, +// which correspond to "setValid" and "setInvalid" methods respectively. +absl::StatusOr ExtractPrimitiveParserOp( + const bmv2::ParserOperation &bmv2_op) { + ParserOperation::Primitive result; + + // The "primitive" parser operation must have exactly 1 parameter. + if (bmv2_op.parameters_size() != 1) { + return gutil::InvalidArgumentErrorBuilder() + << "Parser primitive op must have 1 parameter, found " + << bmv2_op.DebugString(); + } + + const ::google::protobuf::Struct &bmv2_param = bmv2_op.parameters(0); + + // Make sure the parameter struct contains the correct fields. + if (!bmv2_param.fields().contains("op") || + !bmv2_param.fields().at("op").has_string_value() || + !bmv2_param.fields().contains("parameters") || + !bmv2_param.fields().at("parameters").has_list_value()) { + return gutil::InvalidArgumentErrorBuilder() + << "Primitive operation has an invalid parameter: " + << bmv2_param.DebugString(); + } + + const std::string &bmv2_primitive_op = + bmv2_param.fields().at("op").string_value(); + const ::google::protobuf::ListValue &bmv2_primitive_parameters = + bmv2_param.fields().at("parameters").list_value(); + ASSIGN_OR_RETURN(bmv2::StatementOp op_case, + StatementOpToEnum(bmv2_primitive_op)); + + switch (op_case) { + case bmv2::StatementOp::add_header: + case bmv2::StatementOp::remove_header: { + // "add_header" or "remove_header" primitives must have exactly 1 + // parameter. + if (bmv2_primitive_parameters.values_size() != 1) { + return gutil::InvalidArgumentErrorBuilder() + << "setValid/setInvalid statements must contain 1 parameter, " + "found: " + << bmv2_primitive_parameters.DebugString(); + } + + ASSIGN_OR_RETURN(RValue header, + ExtractRValue(bmv2_primitive_parameters.values(0), {})); + + // "add_header" or "remove_header" primitives must have a header name as + // the parameter. + if (header.rvalue_case() != RValue::kHeaderValue) { + return gutil::InvalidArgumentErrorBuilder() + << "setValid/setInvalid statements must have header as the " + "parameter, found: " + << bmv2_primitive_parameters.DebugString(); + } + + const std::string &header_name = header.header_value().header_name(); + + // Set the field `
.$valid$` to true or false based on whether the + // primitive is `add_header` or `remove_header` respectively. + AssignmentStatement &assignment = *result.mutable_assignment(); + FieldValue &valid_field = + *assignment.mutable_left()->mutable_field_value(); + valid_field.set_header_name(header_name); + valid_field.set_field_name("$valid$"); + assignment.mutable_right()->mutable_bool_value()->set_value( + op_case == bmv2::StatementOp::add_header); + break; + } + default: { + return gutil::UnimplementedErrorBuilder() + << "Unsupported primitive op: " << bmv2_param.DebugString(); + } + } + + return result; +} + // Translates parser operations. -// Currently only "extract" and "set" parser operations are supported since -// others are not required at the moment. +// Currently only "extract", "set", and "primitive" parser operations are +// supported since others are not required at the moment. absl::StatusOr ExtractParserOperation( const bmv2::ParserOperation &bmv2_op) { ParserOperation result; @@ -898,6 +977,11 @@ absl::StatusOr ExtractParserOperation( ASSIGN_OR_RETURN(*result.mutable_set(), ExtractSetParserOp(bmv2_op)); break; } + case bmv2::ParserOperation::primitive: { + ASSIGN_OR_RETURN(*result.mutable_primitive(), + ExtractPrimitiveParserOp(bmv2_op)); + break; + } default: { return absl::UnimplementedError( absl::StrCat("Unsupported parser op: ", bmv2_op.DebugString())); diff --git a/p4_symbolic/ir/ir.proto b/p4_symbolic/ir/ir.proto index 40982e5b..215d3c0b 100644 --- a/p4_symbolic/ir/ir.proto +++ b/p4_symbolic/ir/ir.proto @@ -108,9 +108,9 @@ message ParseState { // Reference: // https://github.com/p4lang/behavioral-model/blob/main/docs/JSON_format.md#parser-operations. // -// Note that only "extract" and "set" operations are supported for now since -// others are not required at the moment. To support more complicated parsers, -// other operations may be supported in the future. +// Note that only "extract", "set", and "primitive" operations are supported for +// now since others are not required at the moment. To support more complicated +// parsers, other operations may be supported in the future. message ParserOperation { // Defines an "extract" parser operation, which performs extraction for a // fixed-width header. @@ -133,9 +133,23 @@ message ParserOperation { } } + // Defines a "primitive" parser operation. The exact list of primitives + // supported depends on the individual targets. The format is similar to + // actions. Reference: + // https://github.com/p4lang/behavioral-model/blob/main/docs/JSON_format.md#parser-operations + // https://github.com/p4lang/behavioral-model/blob/main/src/bm_sim/core/primitives.cpp + // https://github.com/p4lang/behavioral-model/blob/main/targets/simple_switch/primitives.cpp + message Primitive { + // The statement interpreted from the primitive. + oneof statement { + AssignmentStatement assignment = 1; + } + } + oneof operation { Extract extract = 1; Set set = 3; + Primitive primitive = 7; } } diff --git a/p4_symbolic/testdata/parser/primitive_parser_operation.p4 b/p4_symbolic/testdata/parser/primitive_parser_operation.p4 new file mode 100644 index 00000000..dfccd3f1 --- /dev/null +++ b/p4_symbolic/testdata/parser/primitive_parser_operation.p4 @@ -0,0 +1,79 @@ +#include +#include "../common/headers.p4" + +struct local_metadata_t { + /* empty */ +} + +struct headers_t { + ethernet_t ethernet; + ipv4_t ipv4; + ipv6_t ipv6; +} + +parser packet_parser(packet_in packet, out headers_t headers, + inout local_metadata_t local_metadata, + inout standard_metadata_t standard_metadata) { + state start { + transition parse_ethernet; + } + + state parse_ethernet { + packet.extract(headers.ethernet); + headers.ethernet.setValid(); + transition select(headers.ethernet.ether_type) { + ETHERTYPE_IPV4: parse_ipv4; + ETHERTYPE_IPV6: parse_ipv6; + default: accept; + } + } + + state parse_ipv4 { + packet.extract(headers.ipv4); + headers.ipv6.setInvalid(); + transition accept; + } + + state parse_ipv6 { + packet.extract(headers.ipv6); + headers.ipv4.setInvalid(); + transition accept; + } +} + +control empty_verify_checksum(inout headers_t headers, + inout local_metadata_t local_metadata) { + apply {} +} // control empty_verify_checksum + +control ingress(inout headers_t headers, + inout local_metadata_t local_metadata, + inout standard_metadata_t standard_metadata) { + apply {} +} // control ingress + +control egress(inout headers_t headers, + inout local_metadata_t local_metadata, + inout standard_metadata_t standard_metadata) { + apply {} +} // control egress + +control empty_compute_checksum(inout headers_t headers, + inout local_metadata_t local_metadata) { + apply {} +} // control empty_compute_checksum + +control packet_deparser(packet_out packet, in headers_t headers) { + apply { + packet.emit(headers.ethernet); + } +} // control packet_deparser + +V1Switch( + packet_parser(), + empty_verify_checksum(), + ingress(), + egress(), + empty_compute_checksum(), + packet_deparser() +) main; From 756827b95f4b2c1909a78bec11e2f19d444b54a9 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Fri, 6 Dec 2024 03:01:52 +0000 Subject: [PATCH 23/23] [Dvaas]: Adding Traffic generator code to dvaas. (#810) Co-authored-by: kishanps --- dvaas/traffic_generator.cc | 197 +++++++++++++++++++++++++++++++++++ dvaas/traffic_generator.h | 207 +++++++++++++++++++++++++++++++++++++ 2 files changed, 404 insertions(+) create mode 100644 dvaas/traffic_generator.cc create mode 100644 dvaas/traffic_generator.h diff --git a/dvaas/traffic_generator.cc b/dvaas/traffic_generator.cc new file mode 100644 index 00000000..229427ab --- /dev/null +++ b/dvaas/traffic_generator.cc @@ -0,0 +1,197 @@ +// Copyright (c) 2024, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dvaas/traffic_generator.h" + +#include +#include +#include // NOLINT: third_party code. +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "dvaas/dataplane_validation.h" +#include "dvaas/mirror_testbed_config.h" +#include "dvaas/packet_injection.h" +#include "dvaas/port_id_map.h" +#include "dvaas/switch_api.h" +#include "dvaas/test_vector.pb.h" +#include "dvaas/validation_result.h" +#include "glog/logging.h" +#include "gutil/status.h" +#include "gutil/test_artifact_writer.h" +#include "p4_pdpi/ir.pb.h" +#include "p4_pdpi/p4_runtime_session.h" +#include "p4_pdpi/p4_runtime_session_extras.h" +#include "p4_symbolic/packet_synthesizer/packet_synthesizer.pb.h" +#include "thinkit/mirror_testbed_fixture.h" + +// Crash if `status` is not okay. Only use in tests. +#define CHECK_OK(val) CHECK_EQ(::absl::OkStatus(), (val)) // Crash OK. + +namespace dvaas { + +SimpleTrafficGenerator::State SimpleTrafficGenerator::GetState() { + absl::MutexLock lock(&state_mutex_); + return state_; +} + +void SimpleTrafficGenerator::SetState(State state) { + absl::MutexLock lock(&state_mutex_); + state_ = state; +} + +absl::StatusOr SimpleTrafficGenerator::Init( + std::shared_ptr testbed, + const TrafficGenerator::Params& params) { + if (GetState() == kTrafficFlowing) { + return absl::FailedPreconditionError( + "Cannot initialize while traffic is flowing"); + } + + params_ = params; + + // Configure testbed. + ASSIGN_OR_RETURN(auto mirror_testbed_configurator, + MirrorTestbedConfigurator::Create(testbed)); + testbed_configurator_ = std::make_unique( + std::move(mirror_testbed_configurator)); + RETURN_IF_ERROR(testbed_configurator_->ConfigureForForwardingTest({ + .mirror_sut_ports_ids_to_control_switch = + !params_.validation_params.mirror_testbed_port_map_override + .has_value(), + })); + // Install punt entries on control switch. + // TODO: Use testbed configurator to do this, instead. + pdpi::P4RuntimeSession& control_p4rt = + *testbed_configurator_->ControlSwitchApi().p4rt; + RETURN_IF_ERROR(pdpi::ClearEntities(control_p4rt)); + ASSIGN_OR_RETURN(const pdpi::IrP4Info ir_p4info, + pdpi::GetIrP4Info(control_p4rt)); + ASSIGN_OR_RETURN(const pdpi::IrEntities punt_entries, + backend_->GetEntitiesToPuntAllPackets(ir_p4info)); + RETURN_IF_ERROR(pdpi::InstallIrEntities(control_p4rt, punt_entries)); + + // Generate test vectors. + gutil::BazelTestArtifactWriter writer; + ASSIGN_OR_RETURN( + test_vector_by_id_, + GenerateTestVectors(params.validation_params, + testbed_configurator_->SutApi(), *backend_, writer)); + + SetState(kInitialized); + + return PacketSynthesisStats{}; +} + +absl::Status SimpleTrafficGenerator::StartTraffic() { + State state = GetState(); + if (state == kUninitialized) { + return absl::FailedPreconditionError( + "Cannot start traffic before initialization."); + } + if (state == kTrafficFlowing) { + return absl::FailedPreconditionError( + "Traffic injection has already started"); + } + + // Spawn traffic injection thread. + traffic_injection_thread_ = + std::thread(&SimpleTrafficGenerator::InjectTraffic, this); + + // Wait for state to change before returning. + while (GetState() != kTrafficFlowing) { + absl::SleepFor(absl::Seconds(1)); + } + + return absl::OkStatus(); +} + +absl::Status SimpleTrafficGenerator::StopTraffic() { + if (GetState() != kTrafficFlowing) { + return absl::FailedPreconditionError( + "Cannot stop traffic if not already flowing."); + } + + // Change state. + SetState(kInitialized); + + // Wait for traffic injection thread to stop before returning. + traffic_injection_thread_.join(); + + return absl::OkStatus(); +} + +void SimpleTrafficGenerator::InjectTraffic() { + // Change state. + SetState(kTrafficFlowing); + + LOG(INFO) << "Starting to inject traffic"; + int iterations = 0; + while (GetState() == kTrafficFlowing) { + ++iterations; + LOG_EVERY_T(INFO, 10) << "Traffic injection iteration #" << iterations; + + // Inject and collect. + PacketStatistics statistics; + absl::StatusOr test_runs = SendTestPacketsAndCollectOutputs( + *testbed_configurator_->SutApi().p4rt, + *testbed_configurator_->ControlSwitchApi().p4rt, test_vector_by_id_, + { + .max_packets_to_send_per_second = + params_.validation_params.max_packets_to_send_per_second, + .mirror_testbed_port_map = + params_.validation_params.mirror_testbed_port_map_override + .value_or(MirrorTestbedP4rtPortIdMap::CreateIdentityMap()), + }, + statistics); + CHECK_OK(test_runs.status()); // Crash OK. + + // Add results to test_runs_. + absl::MutexLock lock(&test_runs_mutex_); + test_runs_.mutable_test_runs()->Add(test_runs->test_runs().begin(), + test_runs->test_runs().end()); + } + + LOG(INFO) << "Stopped traffic injection"; +} + +absl::StatusOr SimpleTrafficGenerator::GetValidationResult() { + test_runs_mutex_.Lock(); + PacketTestRuns test_runs = test_runs_; + test_runs_mutex_.Unlock(); + + return ValidationResult( + test_runs, params_.validation_params.ignored_fields_for_validation, + params_.validation_params.ignored_metadata_for_validation); +} + +absl::StatusOr +SimpleTrafficGenerator::GetAndClearValidationResult() { + test_runs_mutex_.Lock(); + PacketTestRuns test_runs = test_runs_; + test_runs_.clear_test_runs(); + test_runs_mutex_.Unlock(); + + return ValidationResult( + test_runs, params_.validation_params.ignored_fields_for_validation, + params_.validation_params.ignored_metadata_for_validation); +} + +} // namespace dvaas diff --git a/dvaas/traffic_generator.h b/dvaas/traffic_generator.h new file mode 100644 index 00000000..c0fe8355 --- /dev/null +++ b/dvaas/traffic_generator.h @@ -0,0 +1,207 @@ +// Copyright (c) 2024, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PINS_DVAAS_TRAFFIC_GENERATOR_H_ +#define PINS_DVAAS_TRAFFIC_GENERATOR_H_ + +#include +#include // NOLINT: third_party code. +#include +#include + +#include "absl/base/thread_annotations.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/synchronization/mutex.h" +#include "dvaas/dataplane_validation.h" +#include "dvaas/mirror_testbed_config.h" +#include "dvaas/test_vector.h" +#include "dvaas/test_vector.pb.h" +#include "dvaas/validation_result.h" +#include "thinkit/mirror_testbed.h" +#include "thinkit/mirror_testbed_fixture.h" + +namespace dvaas { + +// Various statistics about test packet synthesis. +struct PacketSynthesisStats { + // TODO: Add unreachable entries to stats. + // List of entries deemed unreachable by the packet synthesizer. + // std::vector unreachable_entries; +}; + +// Interface for generating traffic and validating it. +class TrafficGenerator { + public: + // Traffic generation and validation parameters. + struct Params { + // See dataplane_validation.h for details. + DataplaneValidationParams validation_params; + + // TODO: Implement ignore_punted_packets_for_validation. + // If true, ignores punting behavior during validation. + // bool ignore_punting_for_validation; + // TODO: Provide a knob to say I only want L3 forwarded packets. + }; + + // Initialises the traffic generator (and the testbed) with the given params, + // including synthesising test packets. Does NOT start traffic. + // On success, returns statistic about packet synthesis. + // + // NOTE: The table entries, P4Info, and gNMI configuration used in packets + // synthesis will be read from the SUT itself. + // It is the client's responsibility to ensure the correctness of these + // artifacts. + // + // NOTE: Synthesizing test packets that are used in the traffic is a + // computationaly heavy operation and may take a long time (tens of minutes) + // depending on the coverage goals, the number of entries on SUT, and the + // allocated compute resources. This function *blocks* until packet synthesis + // is finished. + // + // NOTE: Packet synthesis is done only once and during the + // call to `Init`. `StartTraffic` does NOT synthesize new test packets, + // instead it uses test packets synthesized during the call to `Init`. If the + // switch configuration or entries change after the call to `Init`, the + // validation results would be inaccurate. + // + // Preconditions: + // - The switches in the testbed must be configured (i.e. have + // proper gNMI and P4Info). + // - SUT must contain the table desired entries. + // - SUT and its ports must be in a state such that the function can + // connect to SUT to read the gNMI config, P4Info, and table entries. + // - Control switch and its ports must be in a state such that the function + // can modify its table entries through a P4RT session. + // + // Postconditions (on a successful return): + // - SUT's entries will be unchanged. + // - The control switch will have the same entries as + // `GetEntriesToPuntAllPackets`. + // - Any preexisting P4RT connections to SUT and control switch will be + // non-primary. + // - The gNMI configs will be unchanged. + virtual absl::StatusOr Init( + std::shared_ptr testbed, + const Params& params) = 0; + + // Asynchronously starts injecting traffic (and validating the result) using + // test packets that were synthesized during `Init`. + // + // Precondition: Init must already be called before calling start. Traffic + // must NOT be already started. Otherwise an error will be returned. + // Postcondition: Traffic injection is started when the function returns. + virtual absl::Status StartTraffic() = 0; + + // Stops sending traffic. + // Blocks until traffic injection/collection fully stops. + // + // Precondition: Traffic must be already started. + // Postcondition: Traffic is stopped when the function returns. + virtual absl::Status StopTraffic() = 0; + + // Returns various information about the injected traffic including + // result of dataplane validation. + // See validation_result.h for details. + // + // NOTE: The validation is performed against test packets and expected outputs + // synthesized during the call to `Init` (with configuration and table + // entries on SUT at the time of calling `Init`). If the configuration or + // table entries change after that call, the validation result would be + // inaccurate. + // + // NOTE: If called while traffic flowing, the function may block for a while + // to collect in-flight packets and validate results. + virtual absl::StatusOr GetValidationResult() = 0; + // Similar to `GetValidationResult` but (on a successful return) resets the + // old results before returning, in the sense that the future calls to + // Get*ValidationResult will not include the results returned by + // the current call. + virtual absl::StatusOr GetAndClearValidationResult() = 0; + + virtual ~TrafficGenerator() = default; +}; + +// A simple implementation of `TrafficGenerator` interface that can be used as a +// proof of concept. This implementation does NOT provide a consistent traffic +// injection rate guarantee (see `InjectTraffic` function comments for more +// details). +class SimpleTrafficGenerator : public TrafficGenerator { + public: + SimpleTrafficGenerator() = delete; + explicit SimpleTrafficGenerator( + std::unique_ptr backend) + : backend_(std::move(backend)) {} + + absl::StatusOr Init( + std::shared_ptr testbed, + const Params& params) override; + absl::Status StartTraffic() override; + absl::Status StopTraffic() override; + absl::StatusOr GetValidationResult() override; + absl::StatusOr GetAndClearValidationResult() override; + + private: + std::unique_ptr backend_; + std::unique_ptr testbed_configurator_; + // Test vectors created as a result of (latest) call to `Init`. Calls to + // `StartTraffic` use these test vectors. + PacketTestVectorById test_vector_by_id_; + + enum State { + // The object has been created but `Init` has not been called. + kUninitialized, + // `Init` has been called, but no traffic is flowing (either `StartTraffic` + // has not been called or `StopTraffic` has been called after that). + kInitialized, + // Traffic is flowing (`StartTraffic` has been called and `StopTraffic` has + // NOT been called after that). + kTrafficFlowing, + }; + // The state of the SimpleTrafficGenerator object. + State state_ ABSL_GUARDED_BY(state_mutex_) = kUninitialized; + // Mutex to synchronize access to state_; + absl::Mutex state_mutex_; + + // Thread safe getter for state_. + State GetState() ABSL_LOCKS_EXCLUDED(state_mutex_); + // Thread safe setter for state_. + void SetState(State state) ABSL_LOCKS_EXCLUDED(state_mutex_); + + // The thread that is spawned during the call to `StartTraffic` and runs + // `InjectTraffic` function. The thread continues until `StopTraffic` is + // called. + std::thread traffic_injection_thread_; + // Runs in a separate thread, as a loop that injects and collects packets + // until traffic is stopped. + // In each iteration of the loop, injects packets in `test_vector_by_id_` at + // the rate specified by `params_`. At the end of each iteration, WAITS UP TO + // 3 SECONDS to collect any in-flight packets, before moving on to next + // iteration. + void InjectTraffic() ABSL_LOCKS_EXCLUDED(test_runs_mutex_); + + // Result of packet injection and collection (i.e. test vector + switch + // output). Populated by `InjectTraffic`. Used during the call to + // `Get*ValidationStats`. + PacketTestRuns test_runs_ ABSL_GUARDED_BY(test_runs_mutex_); + // Mutex to synchronize access to test_runs_; + absl::Mutex test_runs_mutex_; + + // Parameters received in the (latest) call to `Init`. + TrafficGenerator::Params params_; +}; + +} // namespace dvaas + +#endif // PINS_DVAAS_TRAFFIC_GENERATOR_H_