From df3af312f76e0ffd39cf75a0817a6fbf0a387d9b Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Thu, 21 Nov 2024 23:52:31 +0530 Subject: [PATCH 01/12] [Thinkit] Adding few more test cases to thinkit_gnmi_interface_util_tests file. (#735) Co-authored-by: kishanps --- tests/thinkit_gnmi_interface_util_tests.cc | 383 ++++++++++++++++++++- 1 file changed, 379 insertions(+), 4 deletions(-) diff --git a/tests/thinkit_gnmi_interface_util_tests.cc b/tests/thinkit_gnmi_interface_util_tests.cc index d25d0677..32e6d582 100644 --- a/tests/thinkit_gnmi_interface_util_tests.cc +++ b/tests/thinkit_gnmi_interface_util_tests.cc @@ -867,7 +867,7 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, TEST_F(GNMIThinkitInterfaceUtilityTest, TestGetBreakoutModeConfigFromStringChannelizedBreakoutModeSuccess) { - const std::string port_index = "1"; + const std::string port_index = "1"; const std::string breakout_mode = "2x200G"; gnmi::SetRequest req, expected_breakout_config; const std::string expected_breakout_config_str = R"pb( @@ -881,11 +881,40 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, } elem { name: "port" } elem { name: "breakout-mode" } - } + } val { json_ietf_val: "{ \"openconfig-platform-port:groups\": { \"group\": [ {\n \"config\": {\n \"breakout-speed\": \"openconfig-if-ethernet:SPEED_200GB\",\n \"index\": 0,\n \"num-breakouts\": 2,\n \"num-physical-channels\": 4\n },\n \"index\": 0\n } ] } }" - } - } + } + } + )pb"; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + expected_breakout_config_str, &expected_breakout_config)); + ASSERT_OK(pins_test::GetBreakoutModeConfigFromString(req, port_index, + breakout_mode)); + EXPECT_THAT(req, EqualsProto(expected_breakout_config)); +} + +TEST_F(GNMIThinkitInterfaceUtilityTest, + TestGetBreakoutModeConfigFromStringMixedBreakoutModeSuccess) { + const std::string port_index = "1"; + const std::string breakout_mode = "1x200G+2x100G"; + gnmi::SetRequest req, expected_breakout_config; + const std::string expected_breakout_config_str = R"pb( + prefix { origin: "openconfig" } + replace { + path { + elem { name: "components" } + elem { + name: "component" + key { key: "name" value: "1/1" } + } + elem { name: "port" } + elem { name: "breakout-mode" } + } + val { + json_ietf_val: "{ \"openconfig-platform-port:groups\": { \"group\": [ {\n \"config\": {\n \"breakout-speed\": \"openconfig-if-ethernet:SPEED_200GB\",\n \"index\": 0,\n \"num-breakouts\": 1,\n \"num-physical-channels\": 4\n },\n \"index\": 0\n },{\n \"config\": {\n \"breakout-speed\": \"openconfig-if-ethernet:SPEED_100GB\",\n \"index\": 1,\n \"num-breakouts\": 2,\n \"num-physical-channels\": 2\n },\n \"index\": 1\n } ] } }" + } + } )pb"; ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( expected_breakout_config_str, &expected_breakout_config)); @@ -894,4 +923,350 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, EXPECT_THAT(req, EqualsProto(expected_breakout_config)); } +TEST_F(GNMIThinkitInterfaceUtilityTest, + TestGetBreakoutModeConfigFromStringIntConversionFailure) { + const std::string port_index = "1"; + const std::string breakout_mode = "Xx400G"; + gnmi::SetRequest req; + EXPECT_THAT(pins_test::GetBreakoutModeConfigFromString(req, port_index, + breakout_mode), + StatusIs(absl::StatusCode::kInternal, + HasSubstr("Failed to convert string (X) to integer"))); +} + +TEST_F(GNMIThinkitInterfaceUtilityTest, + TestGetNonExistingPortsAfterBreakoutForBreakoutAppliedSuccess) { + absl::flat_hash_map + orig_breakout_info; + orig_breakout_info["Ethernet0"] = + pins_test::PortBreakoutInfo{"[0,1,2,3,4,5,6,7]", pins_test::kStateUp}; + absl::flat_hash_map + new_breakout_info; + new_breakout_info["Ethernet0"] = + pins_test::PortBreakoutInfo{"[0,1,2,3]", pins_test::kStateUp}; + new_breakout_info["Ethernet4"] = + pins_test::PortBreakoutInfo{"[4,5,6,7]", pins_test::kStateUp}; + + std::vector expected_non_existing_ports; + EXPECT_THAT(GetNonExistingPortsAfterBreakout(orig_breakout_info, + new_breakout_info, true), + ContainerEq(expected_non_existing_ports)); +} + +TEST_F(GNMIThinkitInterfaceUtilityTest, + TestGetNonExistingPortsAfterBreakoutForBreakoutAppliedAlternateSuccess) { + absl::flat_hash_map + orig_breakout_info; + orig_breakout_info["Ethernet0"] = + pins_test::PortBreakoutInfo{"[0,1,2,3]", pins_test::kStateUp}; + orig_breakout_info["Ethernet4"] = + pins_test::PortBreakoutInfo{"[4,5,6,7]", pins_test::kStateUp}; + absl::flat_hash_map + new_breakout_info; + new_breakout_info["Ethernet0"] = + pins_test::PortBreakoutInfo{"[0,1,2,3,4,5,6,7]", pins_test::kStateUp}; + + std::vector expected_non_existing_ports{"Ethernet4"}; + EXPECT_THAT(GetNonExistingPortsAfterBreakout(orig_breakout_info, + new_breakout_info, true), + ContainerEq(expected_non_existing_ports)); +} + +TEST_F(GNMIThinkitInterfaceUtilityTest, + TestGetNonExistingPortsAfterBreakoutForBreakoutNotAppliedSuccess) { + absl::flat_hash_map + orig_breakout_info; + orig_breakout_info["Ethernet0"] = + pins_test::PortBreakoutInfo{"[0,1,2,3,4,5,6,7]", pins_test::kStateUp}; + absl::flat_hash_map + new_breakout_info; + new_breakout_info["Ethernet0"] = + pins_test::PortBreakoutInfo{"[0,1,2,3]", pins_test::kStateDown}; + new_breakout_info["Ethernet4"] = + pins_test::PortBreakoutInfo{"[4,5,6,7]", pins_test::kStateDown}; + + std::vector expected_non_existing_ports{"Ethernet4"}; + EXPECT_THAT(GetNonExistingPortsAfterBreakout(orig_breakout_info, + new_breakout_info, false), + ContainerEq(expected_non_existing_ports)); +} + +TEST_F( + GNMIThinkitInterfaceUtilityTest, + TestGetNonExistingPortsAfterBreakoutForBreakoutNotAppliedAlternateSuccess) { + absl::flat_hash_map + orig_breakout_info; + orig_breakout_info["Ethernet0"] = + pins_test::PortBreakoutInfo{"[0,1,2,3]", pins_test::kStateUp}; + orig_breakout_info["Ethernet4"] = + pins_test::PortBreakoutInfo{"[4,5,6,7]", pins_test::kStateUp}; + absl::flat_hash_map + new_breakout_info; + new_breakout_info["Ethernet0"] = + pins_test::PortBreakoutInfo{"[0,1,2,3,4,5,6,7]", pins_test::kStateDown}; + + std::vector expected_non_existing_ports{}; + EXPECT_THAT(GetNonExistingPortsAfterBreakout(orig_breakout_info, + new_breakout_info, false), + ContainerEq(expected_non_existing_ports)); +} + +TEST_F(GNMIThinkitInterfaceUtilityTest, + TestValidateBreakoutStateEmptyExpectedInfoFailure) { + auto mock_gnmi_stub_ptr = absl::make_unique(); + absl::flat_hash_map + expected_port_info; + std::vector non_existing_port_list; + EXPECT_THAT( + pins_test::ValidateBreakoutState( + mock_gnmi_stub_ptr.get(), expected_port_info, non_existing_port_list), + StatusIs(absl::StatusCode::kInternal, + HasSubstr("Expected port info map is empty"))); +} + +TEST_F(GNMIThinkitInterfaceUtilityTest, + TestValidateBreakoutStateOperStatusMatchFailure) { + auto mock_gnmi_stub_ptr = absl::make_unique(); + absl::flat_hash_map + expected_port_info; + expected_port_info["Ethernet0"] = + pins_test::PortBreakoutInfo{"[0,1,2,3,4,5,6,7]", pins_test::kStateUp}; + std::vector non_existing_port_list; + gnmi::GetRequest oper_status_req; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(prefix { origin: "openconfig" } + path { + elem { name: "interfaces" } + elem { + name: "interface" + key { key: "name" value: "Ethernet0" } + } + elem { name: "state" } + elem { name: "oper-status" } + } + type: STATE)pb", + &oper_status_req)); + gnmi::GetResponse oper_status_resp; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(notification { + timestamp: 1632102697699213032 + prefix { origin: "openconfig" } + update { + path { + elem { name: "interfaces" } + elem { + name: "interface" + key { key: "name" value: "Ethernet0" } + } + elem { name: "state" } + elem { name: "oper-status" } + } + val { + json_ietf_val: "{\"openconfig-interfaces:oper-status\":\"DOWN\"}" + } + } + })pb", + &oper_status_resp)); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(oper_status_req), _)) + .WillOnce( + DoAll(SetArgPointee<2>(oper_status_resp), Return(grpc::Status::OK))); + EXPECT_THAT( + pins_test::ValidateBreakoutState( + mock_gnmi_stub_ptr.get(), expected_port_info, non_existing_port_list), + StatusIs(absl::StatusCode::kInternal, + HasSubstr(absl::StrCat( + "Port oper-status match failed for port Ethernet0. got: \"", + pins_test::kStateDown, + "\", want:", expected_port_info["Ethernet0"].oper_status)))); +} + +TEST_F(GNMIThinkitInterfaceUtilityTest, + TestValidateBreakoutStatePhysicalChannelsMatchFailure) { + auto mock_gnmi_stub_ptr = absl::make_unique(); + absl::flat_hash_map + expected_port_info; + expected_port_info["Ethernet0"] = + pins_test::PortBreakoutInfo{"[0,1,2,3]", pins_test::kStateUp}; + std::vector non_existing_port_list; + gnmi::GetRequest oper_status_req; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(prefix { origin: "openconfig" } + path { + elem { name: "interfaces" } + elem { + name: "interface" + key { key: "name" value: "Ethernet0" } + } + elem { name: "state" } + elem { name: "oper-status" } + } + type: STATE)pb", + &oper_status_req)); + gnmi::GetResponse oper_status_resp; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(notification { + timestamp: 1632102697699213032 + prefix { origin: "openconfig" } + update { + path { + elem { name: "interfaces" } + elem { + name: "interface" + key { key: "name" value: "Ethernet0" } + } + elem { name: "state" } + elem { name: "oper-status" } + } + val { + json_ietf_val: "{\"openconfig-interfaces:oper-status\":\"UP\"}" + } + } + })pb", + &oper_status_resp)); + gnmi::GetRequest physical_channels_req; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(prefix { origin: "openconfig" } + path { + elem { name: "interfaces" } + elem { + name: "interface" + key { key: "name" value: "Ethernet0" } + } + elem { name: "state" } + elem { name: "physical-channel" } + } + type: STATE)pb", + &physical_channels_req)); + gnmi::GetResponse physical_channels_resp; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(notification { + timestamp: 1632102697805380043 + prefix { origin: "openconfig" } + update { + path { + elem { name: "interfaces" } + elem { + name: "interface" + key { key: "name" value: "Ethernet0" } + } + elem { name: "state" } + elem { name: "physical-channel" } + } + val { + json_ietf_val: "{\"openconfig-platform-transceiver:physical-channel\":[0,1,2,3,4,5,6,7]}" + } + } + })pb", + &physical_channels_resp)); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(oper_status_req), _)) + .WillOnce( + DoAll(SetArgPointee<2>(oper_status_resp), Return(grpc::Status::OK))); + EXPECT_CALL(*mock_gnmi_stub_ptr, + Get(_, EqualsProto(physical_channels_req), _)) + .WillOnce(DoAll(SetArgPointee<2>(physical_channels_resp), + Return(grpc::Status::OK))); + EXPECT_THAT( + pins_test::ValidateBreakoutState( + mock_gnmi_stub_ptr.get(), expected_port_info, non_existing_port_list), + StatusIs(absl::StatusCode::kInternal, + HasSubstr(absl::StrCat( + "Physical channel match failed for port Ethernet0. got: " + "[0,1,2,3,4,5,6,7], want: ", + expected_port_info["Ethernet0"].physical_channels)))); +} + +TEST_F(GNMIThinkitInterfaceUtilityTest, + TestValidateBreakoutStateNonExistingPortListMatchFailure) { + auto mock_gnmi_stub_ptr = absl::make_unique(); + absl::flat_hash_map + expected_port_info; + expected_port_info["Ethernet0"] = + pins_test::PortBreakoutInfo{"[0,1,2,3,4,5,6,7]", pins_test::kStateUp}; + std::vector non_existing_port_list{"Ethernet0"}; + gnmi::GetRequest oper_status_req; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(prefix { origin: "openconfig" } + path { + elem { name: "interfaces" } + elem { + name: "interface" + key { key: "name" value: "Ethernet0" } + } + elem { name: "state" } + elem { name: "oper-status" } + } + type: STATE)pb", + &oper_status_req)); + gnmi::GetResponse oper_status_resp; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(notification { + timestamp: 1632102697699213032 + prefix { origin: "openconfig" } + update { + path { + elem { name: "interfaces" } + elem { + name: "interface" + key { key: "name" value: "Ethernet0" } + } + elem { name: "state" } + elem { name: "oper-status" } + } + val { + json_ietf_val: "{\"openconfig-interfaces:oper-status\":\"UP\"}" + } + } + })pb", + &oper_status_resp)); + gnmi::GetRequest physical_channels_req; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(prefix { origin: "openconfig" } + path { + elem { name: "interfaces" } + elem { + name: "interface" + key { key: "name" value: "Ethernet0" } + } + elem { name: "state" } + elem { name: "physical-channel" } + } + type: STATE)pb", + &physical_channels_req)); + gnmi::GetResponse physical_channels_resp; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(notification { + timestamp: 1632102697805380043 + prefix { origin: "openconfig" } + update { + path { + elem { name: "interfaces" } + elem { + name: "interface" + key { key: "name" value: "Ethernet0" } + } + elem { name: "state" } + elem { name: "physical-channel" } + } + val { + json_ietf_val: "{\"openconfig-platform-transceiver:physical-channel\":[0,1,2,3,4,5,6,7]}" + } + } + })pb", + &physical_channels_resp)); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(oper_status_req), _)) + .Times(2) + .WillRepeatedly( + DoAll(SetArgPointee<2>(oper_status_resp), Return(grpc::Status::OK))); + EXPECT_CALL(*mock_gnmi_stub_ptr, + Get(_, EqualsProto(physical_channels_req), _)) + .WillOnce(DoAll(SetArgPointee<2>(physical_channels_resp), + Return(grpc::Status::OK))); + EXPECT_THAT( + pins_test::ValidateBreakoutState( + mock_gnmi_stub_ptr.get(), expected_port_info, non_existing_port_list), + StatusIs(absl::StatusCode::kInternal, + HasSubstr("Unexpected port (Ethernet0) found after " + "application of breakout mode"))); +} + } // namespace pins_test From 89b4eafffec51debf05fa31351daf36379daadda Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Thu, 21 Nov 2024 23:53:39 +0530 Subject: [PATCH 02/12] [Thinkit] Adding few more test cases to thinkit_gnmi_interface_util_tests file (#736) Co-authored-by: kishanps --- tests/thinkit_gnmi_interface_util_tests.cc | 147 +++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/tests/thinkit_gnmi_interface_util_tests.cc b/tests/thinkit_gnmi_interface_util_tests.cc index 32e6d582..e0e474f2 100644 --- a/tests/thinkit_gnmi_interface_util_tests.cc +++ b/tests/thinkit_gnmi_interface_util_tests.cc @@ -1269,4 +1269,151 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, "application of breakout mode"))); } +TEST_F(GNMIThinkitInterfaceUtilityTest, TestValidateBreakoutStateSuccess) { + auto mock_gnmi_stub_ptr = absl::make_unique(); + absl::flat_hash_map + expected_port_info; + expected_port_info["Ethernet0"] = + pins_test::PortBreakoutInfo{"[0,1,2,3,4,5,6,7]", pins_test::kStateUp}; + std::vector non_existing_port_list{}; + gnmi::GetRequest oper_status_req; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(prefix { origin: "openconfig" } + path { + elem { name: "interfaces" } + elem { + name: "interface" + key { key: "name" value: "Ethernet0" } + } + elem { name: "state" } + elem { name: "oper-status" } + } + type: STATE)pb", + &oper_status_req)); + gnmi::GetResponse oper_status_resp; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(notification { + timestamp: 1632102697699213032 + prefix { origin: "openconfig" } + update { + path { + elem { name: "interfaces" } + elem { + name: "interface" + key { key: "name" value: "Ethernet0" } + } + elem { name: "state" } + elem { name: "oper-status" } + } + val { + json_ietf_val: "{\"openconfig-interfaces:oper-status\":\"UP\"}" + } + } + })pb", + &oper_status_resp)); + gnmi::GetRequest physical_channels_req; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(prefix { origin: "openconfig" } + path { + elem { name: "interfaces" } + elem { + name: "interface" + key { key: "name" value: "Ethernet0" } + } + elem { name: "state" } + elem { name: "physical-channel" } + } + type: STATE)pb", + &physical_channels_req)); + gnmi::GetResponse physical_channels_resp; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(notification { + timestamp: 1632102697805380043 + prefix { origin: "openconfig" } + update { + path { + elem { name: "interfaces" } + elem { + name: "interface" + key { key: "name" value: "Ethernet0" } + } + elem { name: "state" } + elem { name: "physical-channel" } + } + val { + json_ietf_val: "{\"openconfig-platform-transceiver:physical-channel\":[0,1,2,3,4,5,6,7]}" + } + } + })pb", + &physical_channels_resp)); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(oper_status_req), _)) + .WillOnce( + DoAll(SetArgPointee<2>(oper_status_resp), Return(grpc::Status::OK))); + EXPECT_CALL(*mock_gnmi_stub_ptr, + Get(_, EqualsProto(physical_channels_req), _)) + .WillOnce(DoAll(SetArgPointee<2>(physical_channels_resp), + Return(grpc::Status::OK))); + EXPECT_EQ( + pins_test::ValidateBreakoutState( + mock_gnmi_stub_ptr.get(), expected_port_info, non_existing_port_list), + absl::OkStatus()); +} + +TEST_F(GNMIThinkitInterfaceUtilityTest, TestGetPortIndexSuccess) { + const std::string platform_json_contents = + R"pb({ "interfaces": { "Ethernet0": { "index": "1,1,1,1,1,1,1,1" } } } + )pb"; + const std::string port = "Ethernet0"; + const std::string expected_port_index = "1"; + EXPECT_THAT(pins_test::GetPortIndex(platform_json_contents, port), + expected_port_index); +} + +TEST_F(GNMIThinkitInterfaceUtilityTest, + TestGetPortIndexInterfacesNotFoundFailure) { + const std::string platform_json_contents = + R"pb({} + )pb"; + const std::string port = "Ethernet0"; + const std::string expected_port_index = ""; + EXPECT_THAT(pins_test::GetPortIndex(platform_json_contents, port), + StatusIs(absl::StatusCode::kInternal, + HasSubstr("Interfaces not found in platform.json"))); +} + +TEST_F(GNMIThinkitInterfaceUtilityTest, + TestGetPortIndexInterfaceNotFoundFailure) { + const std::string platform_json_contents = + R"pb({ "interfaces": {} } + )pb"; + const std::string port = "Ethernet0"; + const std::string expected_port_index = ""; + EXPECT_THAT(pins_test::GetPortIndex(platform_json_contents, port), + StatusIs(absl::StatusCode::kInternal, + HasSubstr(absl::StrCat( + port, " entry not found in platform.json")))); +} + +TEST_F(GNMIThinkitInterfaceUtilityTest, TestGetPortIndexIndexNotFoundFailure) { + const std::string platform_json_contents = + R"pb({ "interfaces": { "Ethernet0": {} } } + )pb"; + const std::string port = "Ethernet0"; + const std::string expected_port_index = ""; + EXPECT_THAT(pins_test::GetPortIndex(platform_json_contents, port), + StatusIs(absl::StatusCode::kInternal, + HasSubstr(absl::StrCat("Index not found for ", port, + " in platform.json")))); +} + +TEST_F(GNMIThinkitInterfaceUtilityTest, + TestConstructSupportedBreakoutModeSuccess) { + std::string num_breakouts = " 1"; + std::string breakout_speed = "400G "; + const std::string expected_breakout_mode = "1x400G"; + EXPECT_THAT( + pins_test::ConstructSupportedBreakoutMode(num_breakouts, breakout_speed), + expected_breakout_mode); +} + } // namespace pins_test From bf99c8127aa20139eddf6f0bc225ed88308ca3a9 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Thu, 21 Nov 2024 18:24:17 +0000 Subject: [PATCH 03/12] [Dvaas]: Add library that allows DVaaS/Arriba to process user-provided test vectors using a bare-bones, but sane API. (#738) Co-authored-by: kishanps --- dvaas/BUILD.bazel | 18 ++++ dvaas/user_provided_packet_test_vector.cc | 113 ++++++++++++++++++++++ dvaas/user_provided_packet_test_vector.h | 59 +++++++++++ 3 files changed, 190 insertions(+) create mode 100644 dvaas/user_provided_packet_test_vector.cc create mode 100644 dvaas/user_provided_packet_test_vector.h diff --git a/dvaas/BUILD.bazel b/dvaas/BUILD.bazel index c38cc3f0..b1541e7e 100644 --- a/dvaas/BUILD.bazel +++ b/dvaas/BUILD.bazel @@ -256,3 +256,21 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_library( + name = "user_provided_packet_test_vector", + testonly = True, + srcs = ["user_provided_packet_test_vector.cc"], + hdrs = ["user_provided_packet_test_vector.h"], + deps = [ + ":test_vector", + ":test_vector_cc_proto", + "//gutil:proto", + "//gutil:status", + "//p4_pdpi/packetlib", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + ], +) diff --git a/dvaas/user_provided_packet_test_vector.cc b/dvaas/user_provided_packet_test_vector.cc new file mode 100644 index 00000000..a2f11d7f --- /dev/null +++ b/dvaas/user_provided_packet_test_vector.cc @@ -0,0 +1,113 @@ +#include "dvaas/user_provided_packet_test_vector.h" + +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/escaping.h" +#include "absl/types/span.h" +#include "dvaas/test_vector.h" +#include "dvaas/test_vector.pb.h" +#include "gutil/proto.h" +#include "gutil/status.h" +#include "p4_pdpi/packetlib/packetlib.h" + +namespace dvaas { + +namespace { + +// 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)); + 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. +absl::Status LegitimizeTestVector( + PacketTestVector vector, + PacketTestVectorById& legitimized_test_vectors_by_id) { + if (vector.input().type() != SwitchInput::DATAPLANE) { + return gutil::UnimplementedErrorBuilder() + << "only supported input type is DATAPLANE; found " + << SwitchInput::Type_Name(vector.input().type()); + } + + // Legitimize input packet. + Packet& input_packet = *vector.mutable_input()->mutable_packet(); + ASSIGN_OR_RETURN(int tag, ExtractTestPacketTag(input_packet.parsed()), + _.SetPrepend() << "invalid input packet: "); + ASSIGN_OR_RETURN(input_packet, LegitimizePacket(input_packet), + _.SetPrepend() << "invalid input packet: "); + + // Legitimize acceptable outputs. + if (vector.acceptable_outputs().empty()) { + return gutil::InvalidArgumentErrorBuilder() + << "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()) { + return gutil::UnimplementedErrorBuilder() + << "TODO: support vectors expecting `packet_ins` " + "(punting)"; + } + // Legitimize forwarded output packets. + for (int i = 0; i < output.packets().size(); ++i) { + Packet& output_packet = *output.mutable_packets(i); + ASSIGN_OR_RETURN( + int output_tag, ExtractTestPacketTag(output_packet.parsed()), + _.SetPrepend() << "output packet #" << (i + 1) << " invalid: "); + ASSIGN_OR_RETURN(output_packet, LegitimizePacket(output_packet), + _.SetPrepend() + << "output packet #" << (i + 1) << " invalid: "); + if (output_tag != tag) { + return gutil::InvalidArgumentErrorBuilder() + << "mismatch of input packet tag vs output packet tag for " + "output packet #" + << (i + 1) << ": " << tag << " vs " << output_tag; + } + } + } + + // Add internalized vector to result. + const auto& [it, inserted] = + legitimized_test_vectors_by_id.insert({tag, vector}); + if (!inserted) { + return gutil::InvalidArgumentErrorBuilder() + << "user-provided packet test vectors must be tagged with unique " + "IDs in their payload, but found multiple test vectors with ID " + << tag << ". Dumping offending test vectors:\n<" + << gutil::PrintTextProto(it->second) << ">\n<" + << gutil::PrintTextProto(vector) << ">\n"; + } + return absl::OkStatus(); +} + +} // namespace + +absl::StatusOr LegitimizeUserProvidedTestVectors( + absl::Span user_provided_test_vectors) { + PacketTestVectorById legitimized_test_vectors_by_id; + for (const PacketTestVector& vector : user_provided_test_vectors) { + absl::Status status = + LegitimizeTestVector(vector, legitimized_test_vectors_by_id); + if (!status.ok()) { + return gutil::StatusBuilder(status.code()) + << "problem in user-provided packet test vector: " + << status.message() << "\nDumping offending test vector:\n" + << gutil::PrintTextProto(vector); + } + } + return legitimized_test_vectors_by_id; +} + +} // namespace dvaas diff --git a/dvaas/user_provided_packet_test_vector.h b/dvaas/user_provided_packet_test_vector.h new file mode 100644 index 00000000..c368f0f8 --- /dev/null +++ b/dvaas/user_provided_packet_test_vector.h @@ -0,0 +1,59 @@ +// Empowers users to specify custom packet test vectors that can be validated +// by DVaaS or Arriba. + +// 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_DVAAS_USER_PROVIDED_PACKET_TEST_VECTOR_H_ +#define PINS_DVAAS_USER_PROVIDED_PACKET_TEST_VECTOR_H_ + +#include + +#include "absl/status/statusor.h" +#include "absl/types/span.h" +#include "dvaas/test_vector.h" +#include "dvaas/test_vector.pb.h" + +namespace dvaas { + +// Checks user-provided test vectors for well-formedness and prepares them for +// internal use by DVaaS/Arriba: +// * Fills in "omittable fields", see definition below. +// * Checks that each test vector is "well-formed", see definition below. +// * Returns updated, well-formed test vectors organized by ID, or returns an +// actionable, user-facing error if a test vector is not well-formed. +// +// The following `dvaas::Packet` message fields can be omitted by the user: +// * All `hex` fields. +// * All subfields of `packetlib::Packet` messages that are considered "computed +// fields" by packetlib. This includes checksum and length fields. See the +// packetlib library for the exact definition. +// +// To be "well-formed", a test vector must meet the following requirements: +// * Must specify at least 1 acceptable output. +// * Each input and output packet must: +// * Be valid according to `packetlib::ValidatePacket` after computed fields +// have been filled in. +// * Contain a test packet ID/tag according to `ExtractTestPacketTag`. +// This ID must be: +// * Shared among all packets within the test vector. +// * Unique among all test vectors. +// * The input must be of type `DATAPLANE` (other types may be supported in the +// future). +absl::StatusOr LegitimizeUserProvidedTestVectors( + absl::Span user_provided_test_vectors); + +} // namespace dvaas + +#endif // PINS_DVAAS_USER_PROVIDED_PACKET_TEST_VECTOR_H_ From 62dff534bc39869aa75060083244d95992fa13ac Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Thu, 21 Nov 2024 18:24:56 +0000 Subject: [PATCH 04/12] [Dvaas]: Create function to retag packets and update the tag for entire PacketTestVector. (#739) Co-authored-by: kishanps --- dvaas/BUILD.bazel | 12 +- dvaas/test_vector.cc | 64 ++++++++++ dvaas/test_vector.h | 11 +- dvaas/test_vector_test.cc | 259 +++++++++++++++++++++++++++++++++++++- 4 files changed, 334 insertions(+), 12 deletions(-) diff --git a/dvaas/BUILD.bazel b/dvaas/BUILD.bazel index b1541e7e..6fa44fd6 100644 --- a/dvaas/BUILD.bazel +++ b/dvaas/BUILD.bazel @@ -143,16 +143,12 @@ cc_library( "//gutil:proto", "//gutil:status", "//p4_pdpi:ir_cc_proto", + "//p4_pdpi/packetlib", "//p4_pdpi/packetlib:packetlib_cc_proto", - "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/container:btree", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", - "@com_google_absl//absl/types:optional", - "@com_google_absl//absl/types:span", - "@com_google_googletest//:gtest", - "@com_google_protobuf//:protobuf", "@com_googlesource_code_re2//:re2", ], ) @@ -251,8 +247,12 @@ cc_test( srcs = ["test_vector_test.cc"], deps = [ ":test_vector", + ":test_vector_cc_proto", "//gutil:status_matchers", + "//gutil:testing", + "//p4_pdpi/packetlib", "//p4_pdpi/packetlib:packetlib_cc_proto", + "@com_google_absl//absl/status", "@com_google_googletest//:gtest_main", ], ) diff --git a/dvaas/test_vector.cc b/dvaas/test_vector.cc index 3f02e55f..e4a771c1 100644 --- a/dvaas/test_vector.cc +++ b/dvaas/test_vector.cc @@ -17,10 +17,14 @@ #include #include +#include "absl/status/status.h" #include "absl/status/statusor.h" +#include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" +#include "dvaas/test_vector.pb.h" #include "gutil/proto.h" #include "gutil/status.h" +#include "p4_pdpi/packetlib/packetlib.h" #include "p4_pdpi/packetlib/packetlib.pb.h" #include "re2/re2.h" @@ -53,4 +57,64 @@ std::ostream& operator<<(std::ostream& os, const SwitchOutput& output) { return os << output.DebugString(); } +absl::Status UpdateTestTag(packetlib::Packet& packet, int new_tag) { + // Make a new input packet with updated payload. + std::string new_payload = packet.payload(); + if (!RE2::Replace(&new_payload, *kTestPacketIdRegexp, + MakeTestPacketTagFromUniqueId(new_tag))) { + return gutil::InvalidArgumentErrorBuilder() + << "Test packets must contain a tag of the form '" + << kTestPacketIdRegexp->pattern() + << "' in their payload, but the given packet with payload '" + << packet.payload() << "' does not:\n" + << gutil::PrintTextProto(packet); + } + packet.set_payload(new_payload); + bool status; + ASSIGN_OR_RETURN(status, PadPacketToMinimumSize(packet), + _.LogError() << "Failed to pad packet for tag: " << new_tag); + ASSIGN_OR_RETURN(status, UpdateAllComputedFields(packet), + _.LogError() + << "Failed to update payload for tag: " << new_tag); + + return absl::OkStatus(); +} + +// Returns a serialization of the given `packet` as a hexstring. +absl::StatusOr SerializeAsHexString( + const packetlib::Packet& packet) { + ASSIGN_OR_RETURN(std::string serialized_packet, + packetlib::RawSerializePacket(packet), + _ << " where packet = " << packet.DebugString()); + return absl::BytesToHexString(serialized_packet); +} + +absl::Status UpdateTestTag(PacketTestVector& packet_test_vector, int new_tag) { + // Updates the payload of the SwitchInput. + dvaas::Packet& input_packet = + *packet_test_vector.mutable_input()->mutable_packet(); + RETURN_IF_ERROR(UpdateTestTag(*input_packet.mutable_parsed(), new_tag)); + ASSIGN_OR_RETURN(const std::string input_packet_updated_hexstr, + SerializeAsHexString(input_packet.parsed())); + input_packet.set_hex(input_packet_updated_hexstr); + + // Update the payload of the SwitchOutput. + for (SwitchOutput& output_packet : + *packet_test_vector.mutable_acceptable_outputs()) { + for (dvaas::Packet& packet_out : *output_packet.mutable_packets()) { + RETURN_IF_ERROR(UpdateTestTag(*packet_out.mutable_parsed(), new_tag)); + ASSIGN_OR_RETURN(const std::string packet_out_updated_hexstr, + SerializeAsHexString(packet_out.parsed())); + packet_out.set_hex(packet_out_updated_hexstr); + } + for (dvaas::PacketIn& packet_in : *output_packet.mutable_packet_ins()) { + RETURN_IF_ERROR(UpdateTestTag(*packet_in.mutable_parsed(), new_tag)); + ASSIGN_OR_RETURN(const std::string packet_in_updated_hexstr, + SerializeAsHexString(packet_in.parsed())); + packet_in.set_hex(packet_in_updated_hexstr); + } + } + return absl::OkStatus(); +} + } // namespace dvaas diff --git a/dvaas/test_vector.h b/dvaas/test_vector.h index ffbcad09..f0ac3dd3 100644 --- a/dvaas/test_vector.h +++ b/dvaas/test_vector.h @@ -17,16 +17,12 @@ #include #include -#include #include "absl/container/btree_map.h" -#include "absl/container/flat_hash_set.h" +#include "absl/status/status.h" #include "absl/status/statusor.h" -#include "absl/types/optional.h" #include "dvaas/test_vector.pb.h" -#include "google/protobuf/descriptor.h" #include "p4_pdpi/ir.pb.h" -#include "re2/re2.h" namespace dvaas { @@ -47,6 +43,11 @@ absl::StatusOr ExtractTestPacketTag(const packetlib::Packet& packet); // Needed to make gUnit produce human-readable output in open source. std::ostream& operator<<(std::ostream& os, const SwitchOutput& output); +// Updates the test tag (to `new_tag`) and all computed fields of all packets +// (input, acceptable outputs) in the given `packet_test_vectr`. Returns an +// error if the packets are not already tagged. +absl::Status UpdateTestTag(PacketTestVector& packet_test_vector, int new_tag); + using PacketTestVectorById = absl::btree_map; } // namespace dvaas diff --git a/dvaas/test_vector_test.cc b/dvaas/test_vector_test.cc index 28dd28d6..0aa60947 100644 --- a/dvaas/test_vector_test.cc +++ b/dvaas/test_vector_test.cc @@ -1,8 +1,26 @@ +// 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 "dvaas/test_vector.h" +#include "absl/status/status.h" +#include "dvaas/test_vector.pb.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "gutil/status_matchers.h" +#include "gutil/testing.h" +#include "p4_pdpi/packetlib/packetlib.h" #include "p4_pdpi/packetlib/packetlib.pb.h" namespace dvaas { @@ -15,9 +33,248 @@ TEST(MakeTestPacketTag, RoundTripsWithExtractTestPacketTag) { for (int test_packet_id : {0, 1, 2, 42, 1234}) { packetlib::Packet packet; packet.set_payload(MakeTestPacketTagFromUniqueId(test_packet_id)); - EXPECT_THAT(ExtractTestPacketTag(packet), IsOkAndHolds(Eq(test_packet_id))); + ASSERT_THAT(ExtractTestPacketTag(packet), IsOkAndHolds(Eq(test_packet_id))); } } +TEST(UpdateTestPacketTag, YieldsValidPacketTestVectorWithUpdatedTag) { + auto test_vector = gutil::ParseProtoOrDie(R"pb( + input { + type: DATAPLANE + packet { + port: "29" + parsed { + headers { + ethernet_header { + ethernet_destination: "02:1a:0a:d0:62:8b" + ethernet_source: "36:47:08:6f:88:a1" + ethertype: "0x86dd" + } + } + headers { + ipv6_header { + version: "0x6" + dscp: "0x1a" + ecn: "0x0" + flow_label: "0x00000" + payload_length: "0x0025" + next_header: "0x11" + hop_limit: "0x20" + ipv6_source: "2000::" + ipv6_destination: "2800:3f0:c200:800::2000" + } + } + headers { + udp_header { + source_port: "0x0000" + destination_port: "0x03ea" + length: "0x0025" + checksum: "0x3712" + } + } + payload: "test packet #1: Dummy payload" + } + hex: "021a0ad0628b3647086f88a186dd668000000025112020000000000000000000000000000000280003f0c20008000000000000002000000003ea0025371274657374207061636b65742023313a2044756d6d79207061796c6f6164" + } + } + acceptable_outputs { + packets { + port: "12" + parsed { + headers { + ethernet_header { + ethernet_destination: "02:1a:0a:d0:62:8b" + ethernet_source: "36:47:08:6f:88:a1" + ethertype: "0x86dd" + } + } + headers { + ipv6_header { + version: "0x6" + dscp: "0x1a" + ecn: "0x0" + flow_label: "0x00000" + payload_length: "0x0025" + next_header: "0x11" + hop_limit: "0x20" + ipv6_source: "2000::" + ipv6_destination: "2800:3f0:c200:800::2000" + } + } + headers { + udp_header { + source_port: "0x0000" + destination_port: "0x03ea" + length: "0x0025" + checksum: "0x3712" + } + } + payload: "test packet #1: Dummy payload" + } + hex: "021a0ad0628b3647086f88a186dd668000000025112020000000000000000000000000000000280003f0c20008000000000000002000000003ea0025371274657374207061636b65742023313a2044756d6d79207061796c6f6164" + } + packets { + port: "12" + parsed { + headers { + ethernet_header { + ethernet_destination: "02:1a:0a:d0:62:8b" + ethernet_source: "36:47:08:6f:88:a1" + ethertype: "0x86dd" + } + } + headers { + ipv6_header { + version: "0x6" + dscp: "0x1a" + ecn: "0x0" + flow_label: "0x00000" + payload_length: "0x0025" + next_header: "0x11" + hop_limit: "0x20" + ipv6_source: "2000::" + ipv6_destination: "2800:3f0:c200:800::2000" + } + } + headers { + udp_header { + source_port: "0x0000" + destination_port: "0x03ea" + length: "0x0025" + checksum: "0x3712" + } + } + payload: "test packet #1: Dummy payload" + } + hex: "021a0ad0628b3647086f88a186dd668000000025112020000000000000000000000000000000280003f0c20008000000000000002000000003ea0025371274657374207061636b65742023313a2044756d6d79207061796c6f6164" + } + packet_ins { + metadata { + name: "ingress_port" + value { str: "9" } + } + metadata { + name: "target_egress_port" + value { str: "6" } + } + parsed { + headers { + ethernet_header { + ethernet_destination: "02:1a:0a:d0:62:8b" + ethernet_source: "36:47:08:6f:88:a1" + ethertype: "0x86dd" + } + } + headers { + ipv6_header { + version: "0x6" + dscp: "0x1a" + ecn: "0x0" + flow_label: "0x00000" + payload_length: "0x0025" + next_header: "0x11" + hop_limit: "0x20" + ipv6_source: "2000::" + ipv6_destination: "2800:3f0:c200:800::2000" + } + } + headers { + udp_header { + source_port: "0x0000" + destination_port: "0x03ea" + length: "0x0025" + checksum: "0x3712" + } + } + payload: "test packet #1: Dummy payload" + } + hex: "021a0ad0628b3647086f88a186dd668000000025112020000000000000000000000000000000280003f0c20008000000000000002000000003ea0025371274657374207061636b65742023313a2044756d6d79207061796c6f6164" + } + packet_ins { + metadata { + name: "ingress_port" + value { str: "9" } + } + metadata { + name: "target_egress_port" + value { str: "6" } + } + parsed { + headers { + ethernet_header { + ethernet_destination: "02:1a:0a:d0:62:8b" + ethernet_source: "36:47:08:6f:88:a1" + ethertype: "0x86dd" + } + } + headers { + ipv6_header { + version: "0x6" + dscp: "0x1a" + ecn: "0x0" + flow_label: "0x00000" + payload_length: "0x0025" + next_header: "0x11" + hop_limit: "0x20" + ipv6_source: "2000::" + ipv6_destination: "2800:3f0:c200:800::2000" + } + } + headers { + udp_header { + source_port: "0x0000" + destination_port: "0x03ea" + length: "0x0025" + checksum: "0x3712" + } + } + payload: "test packet #1: Dummy payload" + } + hex: "021a0ad0628b3647086f88a186dd668000000025112020000000000000000000000000000000280003f0c20008000000000000002000000003ea0025371274657374207061636b65742023313a2044756d6d79207061796c6f6164" + } + } + )pb"); + PacketTestVector updated_test_vector = test_vector; + int kNewTag = 2000000; + ASSERT_OK(UpdateTestTag(test_vector, kNewTag)); + + // Check if all the tags were updated, including the hex and payload. + ASSERT_OK(packetlib::ValidatePacket(test_vector.input().packet().parsed())); + ASSERT_THAT(ExtractTestPacketTag(test_vector.input().packet().parsed()), + IsOkAndHolds(Eq(kNewTag))); + ASSERT_NE(test_vector.input().packet().hex(), + updated_test_vector.input().packet().hex()); + for (int i = 0; i < test_vector.acceptable_outputs().size(); ++i) { + const SwitchOutput& acceptable_outputs = test_vector.acceptable_outputs(i); + for (int j = 0; j < acceptable_outputs.packets().size(); ++j) { + const Packet& packet = acceptable_outputs.packets(j); + ASSERT_OK(packetlib::ValidatePacket(packet.parsed())); + ASSERT_THAT(ExtractTestPacketTag(packet.parsed()), + IsOkAndHolds(Eq(kNewTag))); + ASSERT_NE(packet.hex(), + updated_test_vector.acceptable_outputs(i).packets(j).hex()); + } + for (int j = 0; j < acceptable_outputs.packet_ins().size(); ++j) { + const PacketIn& packet_in = acceptable_outputs.packet_ins(j); + ASSERT_OK(packetlib::ValidatePacket(packet_in.parsed())); + ASSERT_THAT(ExtractTestPacketTag(packet_in.parsed()), + IsOkAndHolds(Eq(kNewTag))); + ASSERT_NE(packet_in.hex(), + updated_test_vector.acceptable_outputs(i).packet_ins(j).hex()); + } + } +} + +TEST(UpdateTestPacketTag, FailsForPacketWithNoTag) { + auto test_vector = gutil::ParseProtoOrDie(R"pb( + input { + type: DATAPLANE + packet { parsed { payload: "test packet" } } + } + )pb"); + ASSERT_THAT(UpdateTestTag(test_vector, /*new_tag=*/0), + gutil::StatusIs(absl::StatusCode::kInvalidArgument)); +} + } // namespace } // namespace dvaas From 05b4fe9c715054c0accbd8748bc534fa14ca3e8c Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Thu, 21 Nov 2024 18:25:55 +0000 Subject: [PATCH 05/12] [P4-Symbolic] Extend SAI module with packet-out headers and metadata. Add GetHeaderValidityFieldRef. (#740) Co-authored-by: kishanps Co-authored-by: rhalstea Co-authored-by: smolkaj Co-authored-by: kheradmandG --- p4_symbolic/sai/fields.cc | 24 ++++++++++++++++++++++++ p4_symbolic/sai/fields.h | 25 +++++++++++++++++++++++++ p4_symbolic/sai/parser.cc | 17 +++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/p4_symbolic/sai/fields.cc b/p4_symbolic/sai/fields.cc index a35821ce..a947a6d1 100644 --- a/p4_symbolic/sai/fields.cc +++ b/p4_symbolic/sai/fields.cc @@ -15,6 +15,7 @@ #include "p4_symbolic/sai/fields.h" #include +#include #include #include @@ -79,6 +80,10 @@ absl::StatusOr GetUserMetadataFieldName( << absl::StrJoin(mangled_candidates, "\n- "); } +std::string GetHeaderValidityFieldRef(absl::string_view header) { + return absl::StrCat(header, ".$valid$"); +} + namespace { // The p4c compiler mangles field names from the local_metadata struct. @@ -110,6 +115,18 @@ absl::StatusOr GetSaiFields(const SymbolicPerPacketState& state) { return z3::expr(Z3Context()); }; + // TODO: Make unconditional when we no longer need + // backwards-compatability. + auto packet_out = + state.ContainsKey("packet_out_header.$valid$") + ? std::make_optional(SaiPacketOut{ + .valid = get_field("packet_out_header.$valid$"), + .egress_port = get_field("packet_out_header.egress_port"), + .submit_to_ingress = + get_field("packet_out_header.submit_to_ingress"), + .unused_pad = get_field("packet_out_header.unused_pad"), + }) + : std::nullopt; auto erspan_ethernet = SaiEthernet{ .valid = get_field("erspan_ethernet.$valid$"), .dst_addr = get_field("erspan_ethernet.dst_addr"), @@ -256,6 +273,12 @@ absl::StatusOr GetSaiFields(const SymbolicPerPacketState& state) { .mirror_session_id_valid = get_metadata_field("mirror_session_id_valid"), .ingress_port = get_metadata_field("ingress_port"), .route_metadata = get_metadata_field("route_metadata"), + // TODO: Make unconditional when we no longer need + // backwards-compatability. + .bypass_ingress = + GetUserMetadata("bypass_ingress", state).ok() + ? std::make_optional(get_metadata_field("bypass_ingress")) + : std::nullopt, }; auto standard_metadata = V1ModelStandardMetadata{ .ingress_port = get_field("standard_metadata.ingress_port"), @@ -272,6 +295,7 @@ absl::StatusOr GetSaiFields(const SymbolicPerPacketState& state) { return SaiFields{ .headers = SaiHeaders{ + .packet_out = packet_out, .erspan_ethernet = erspan_ethernet, .erspan_ipv4 = erspan_ipv4, .erspan_gre = erspan_gre, diff --git a/p4_symbolic/sai/fields.h b/p4_symbolic/sai/fields.h index b70568f2..f14a1854 100644 --- a/p4_symbolic/sai/fields.h +++ b/p4_symbolic/sai/fields.h @@ -18,11 +18,22 @@ #ifndef PINS_P4_SYMBOLIC_SAI_FIELDS_H_ #define PINS_P4_SYMBOLIC_SAI_FIELDS_H_ +#include + +#include "absl/status/statusor.h" #include "p4_symbolic/symbolic/symbolic.h" #include "z3++.h" namespace p4_symbolic { +// Symbolic version of `packet_out_header_t` in metadata.p4. +struct SaiPacketOut { + z3::expr valid; + z3::expr egress_port; + z3::expr submit_to_ingress; + z3::expr unused_pad; +}; + // Symbolic version of `struct ethernet_t` in headers.p4. struct SaiEthernet { z3::expr valid; @@ -128,6 +139,10 @@ struct SaiGre { // Symbolic version of `struct headers_t` in metadata.p4. struct SaiHeaders { + // TODO: Make non-optional when we no longer need + // backwards-compatability. + std::optional packet_out; + SaiEthernet erspan_ethernet; SaiIpv4 erspan_ipv4; SaiGre erspan_gre; @@ -155,6 +170,9 @@ struct SaiLocalMetadata { z3::expr mirror_session_id_valid; z3::expr ingress_port; z3::expr route_metadata; + // TODO: Make non-optional when we no longer need + // backwards-compatability. + std::optional bypass_ingress; }; // Symbolic version of `struct standard_metadata_t` in v1model.p4 @@ -179,6 +197,13 @@ absl::StatusOr GetSaiFields( absl::StatusOr GetUserMetadataFieldName( const std::string& field, const symbolic::SymbolicPerPacketState& state); +// The p4c compiler adds a special field ("$valid$") to each header +// corresponding to its validity. This function returns a field reference (in +// form of
.) to the p4c generated validity field of the given +// `header`. +// Note: This function does NOT check if the given header exists. +std::string GetHeaderValidityFieldRef(absl::string_view header); + } // namespace p4_symbolic #endif // PINS_P4_SYMBOLIC_SAI_FIELDS_H_ diff --git a/p4_symbolic/sai/parser.cc b/p4_symbolic/sai/parser.cc index ee8e6a57..daa7fc88 100644 --- a/p4_symbolic/sai/parser.cc +++ b/p4_symbolic/sai/parser.cc @@ -30,6 +30,9 @@ absl::StatusOr> EvaluateSaiParser( std::vector constraints; ASSIGN_OR_RETURN(SaiFields fields, GetSaiFields(state)); + // TODO: Make non-optional when we no longer need + // backwards-compatability. + std::optional& packet_out = fields.headers.packet_out; SaiEthernet& erspan_ethernet = fields.headers.erspan_ethernet; SaiIpv4& erspan_ipv4 = fields.headers.erspan_ipv4; SaiGre& erspan_gre = fields.headers.erspan_gre; @@ -59,6 +62,20 @@ absl::StatusOr> EvaluateSaiParser( constraints.push_back(local_metadata.ingress_port == standard_metadata.ingress_port); constraints.push_back(local_metadata.route_metadata == 0); + if (local_metadata.bypass_ingress.has_value()) { + // TODO: Make unconditional when we no longer need + // backwards-compatability. + constraints.push_back(*local_metadata.bypass_ingress == false); + } + + // `parse_packet_out` state. + if (packet_out.has_value()) { + // TODO: Make unconditional when we no longer need + // backwards-compatability. + constraints.push_back( + packet_out->valid == + (standard_metadata.ingress_port == symbolic::kCpuPort)); + } // `parse_ethernet` state. constraints.push_back(ethernet.valid == Z3Context().bool_val(true)); From 2abf318bd751f7e073b62b9ebd8746ae5bd72fc3 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Thu, 21 Nov 2024 18:28:00 +0000 Subject: [PATCH 06/12] [p4-symbolic] Extend SAI module with packet-in headers and metadata. (#741) --------- Co-authored-by: kishanps Co-authored-by: rhalstea Co-authored-by: smolkaj Co-authored-by: kheradmandG --- p4_symbolic/sai/deparser.cc | 16 ++++++++++ p4_symbolic/sai/deparser_test.cc | 50 ++++++++++++++++++++++++++++++++ p4_symbolic/sai/fields.cc | 11 +++++++ p4_symbolic/sai/fields.h | 9 ++++++ p4_symbolic/sai/parser.cc | 12 ++++++-- 5 files changed, 95 insertions(+), 3 deletions(-) diff --git a/p4_symbolic/sai/deparser.cc b/p4_symbolic/sai/deparser.cc index 31dd1461..f8699f8b 100644 --- a/p4_symbolic/sai/deparser.cc +++ b/p4_symbolic/sai/deparser.cc @@ -211,6 +211,17 @@ absl::Status Deparse(const SaiGre& header, const z3::model& model, return absl::OkStatus(); } +absl::Status Deparse(const SaiPacketIn& header, const z3::model& model, + pdpi::BitString& result) { + ASSIGN_OR_RETURN(bool valid, EvalZ3Bool(header.valid, model)); + if (valid) { + RETURN_IF_ERROR(Deparse<9>(header.ingress_port, model, result)); + RETURN_IF_ERROR(Deparse<9>(header.target_egress_port, model, result)); + RETURN_IF_ERROR(Deparse<6>(header.unused_pad, model, result)); + } + return absl::OkStatus(); +} + } // namespace absl::StatusOr SaiDeparser( @@ -222,6 +233,11 @@ absl::StatusOr SaiDeparser( absl::StatusOr SaiDeparser(const SaiFields& packet, const z3::model& model) { pdpi::BitString result; + // TODO: Make unconditional when we no longer need + // backwards-compatibility. + if (packet.headers.packet_in.has_value()) { + RETURN_IF_ERROR(Deparse(packet.headers.packet_in.value(), model, result)); + } RETURN_IF_ERROR(Deparse(packet.headers.erspan_ethernet, model, result)); RETURN_IF_ERROR(Deparse(packet.headers.erspan_ipv4, model, result)); RETURN_IF_ERROR(Deparse(packet.headers.erspan_gre, model, result)); diff --git a/p4_symbolic/sai/deparser_test.cc b/p4_symbolic/sai/deparser_test.cc index 2c82f249..f0270bd5 100644 --- a/p4_symbolic/sai/deparser_test.cc +++ b/p4_symbolic/sai/deparser_test.cc @@ -191,6 +191,56 @@ TEST_P(SaiDeparserTest, IcmpPacketParserIntegrationTest) { ASSERT_THAT(packet.payload(), IsEmpty()); } +TEST_P(SaiDeparserTest, PacketInHeaderIsNeverParsedIntegrationTest) { + // Add parse constraints. + { + ASSERT_OK_AND_ASSIGN(auto parse_constraints, + EvaluateSaiParser(state_->context.ingress_headers)); + for (auto& constraint : parse_constraints) state_->solver->add(constraint); + } + + // 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); + } + + // 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); + } + + // 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)); + + // Check we indeed got a packet_in packet. + packetlib::Packet packet = packetlib::ParsePacket(raw_packet); + 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()); +} + INSTANTIATE_TEST_SUITE_P(Instantiation, SaiDeparserTest, testing::ValuesIn(sai::AllSaiInstantiations())); diff --git a/p4_symbolic/sai/fields.cc b/p4_symbolic/sai/fields.cc index a947a6d1..5a3bf893 100644 --- a/p4_symbolic/sai/fields.cc +++ b/p4_symbolic/sai/fields.cc @@ -117,6 +117,16 @@ absl::StatusOr GetSaiFields(const SymbolicPerPacketState& state) { // TODO: Make unconditional when we no longer need // backwards-compatability. + auto packet_in = + state.ContainsKey("packet_in_header.$valid$") + ? std::make_optional(SaiPacketIn{ + .valid = get_field("packet_in_header.$valid$"), + .ingress_port = get_field("packet_in_header.ingress_port"), + .target_egress_port = + get_field("packet_in_header.target_egress_port"), + .unused_pad = get_field("packet_in_header.unused_pad"), + }) + : std::nullopt; auto packet_out = state.ContainsKey("packet_out_header.$valid$") ? std::make_optional(SaiPacketOut{ @@ -295,6 +305,7 @@ absl::StatusOr GetSaiFields(const SymbolicPerPacketState& state) { return SaiFields{ .headers = SaiHeaders{ + .packet_in = packet_in, .packet_out = packet_out, .erspan_ethernet = erspan_ethernet, .erspan_ipv4 = erspan_ipv4, diff --git a/p4_symbolic/sai/fields.h b/p4_symbolic/sai/fields.h index f14a1854..5ff15961 100644 --- a/p4_symbolic/sai/fields.h +++ b/p4_symbolic/sai/fields.h @@ -26,6 +26,14 @@ namespace p4_symbolic { +// Symbolic version of `packet_in_header_t` in metadata.p4. +struct SaiPacketIn { + z3::expr valid; + z3::expr ingress_port; + z3::expr target_egress_port; + z3::expr unused_pad; +}; + // Symbolic version of `packet_out_header_t` in metadata.p4. struct SaiPacketOut { z3::expr valid; @@ -141,6 +149,7 @@ struct SaiGre { struct SaiHeaders { // TODO: Make non-optional when we no longer need // backwards-compatability. + std::optional packet_in; std::optional packet_out; SaiEthernet erspan_ethernet; diff --git a/p4_symbolic/sai/parser.cc b/p4_symbolic/sai/parser.cc index daa7fc88..041df9c8 100644 --- a/p4_symbolic/sai/parser.cc +++ b/p4_symbolic/sai/parser.cc @@ -31,7 +31,8 @@ absl::StatusOr> EvaluateSaiParser( ASSIGN_OR_RETURN(SaiFields fields, GetSaiFields(state)); // TODO: Make non-optional when we no longer need - // backwards-compatability. + // backwards-compatibility. + std::optional& packet_in = fields.headers.packet_in; std::optional& packet_out = fields.headers.packet_out; SaiEthernet& erspan_ethernet = fields.headers.erspan_ethernet; SaiIpv4& erspan_ipv4 = fields.headers.erspan_ipv4; @@ -64,14 +65,19 @@ absl::StatusOr> EvaluateSaiParser( constraints.push_back(local_metadata.route_metadata == 0); if (local_metadata.bypass_ingress.has_value()) { // TODO: Make unconditional when we no longer need - // backwards-compatability. + // backwards-compatibility. constraints.push_back(*local_metadata.bypass_ingress == false); } + if (packet_in.has_value()) { + // TODO: Make unconditional when we no longer need + // backwards-compatibility. + constraints.push_back(!packet_in->valid); + } // `parse_packet_out` state. if (packet_out.has_value()) { // TODO: Make unconditional when we no longer need - // backwards-compatability. + // backwards-compatibility. constraints.push_back( packet_out->valid == (standard_metadata.ingress_port == symbolic::kCpuPort)); From 03a151c7a0a74484e83eb7640d810b0508edde52 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Thu, 21 Nov 2024 18:28:45 +0000 Subject: [PATCH 07/12] [P4_Symbolic] Update to comply with z3 version 4.12.0.0 (December 12th 2022). (#742) Co-authored-by: kishanps --- p4_symbolic/symbolic/BUILD.bazel | 18 ------ p4_symbolic/symbolic/expected/basic.smt2 | 4 +- .../symbolic/expected/conditional.smt2 | 4 +- .../expected/conditional_sequence.smt2 | 64 +++++++++---------- .../symbolic/expected/string_optional.smt2 | 8 +-- .../symbolic/expected/string_optional.txt | 8 +-- p4_symbolic/symbolic/expected/table.smt2 | 4 +- .../symbolic/expected/table_hit_1.smt2 | 4 +- p4_symbolic/symbolic/expected/table_hit_1.txt | 2 +- p4_symbolic/symbolic/expected/table_hit_2.txt | 4 +- p4_symbolic/symbolic/expected/vrf.smt2 | 4 +- p4_symbolic/symbolic/expected/vrf.txt | 32 +++++----- 12 files changed, 69 insertions(+), 87 deletions(-) diff --git a/p4_symbolic/symbolic/BUILD.bazel b/p4_symbolic/symbolic/BUILD.bazel index 75e349c8..c729f154 100644 --- a/p4_symbolic/symbolic/BUILD.bazel +++ b/p4_symbolic/symbolic/BUILD.bazel @@ -124,24 +124,6 @@ end_to_end_test( table_entries = "//p4_symbolic/testdata:conditional/conditional_sequence_entries.pb.txt", ) -end_to_end_test( - name = "table_hit_1_test", - output_golden_file = "expected/table_hit_1.txt", - p4_program = "//p4_symbolic/testdata:conditional/table_hit_1.p4", - port_count = 8, - smt_golden_file = "expected/table_hit_1.smt2", - table_entries = "//p4_symbolic/testdata:conditional/table_hit_1_entries.pb.txt", -) - -end_to_end_test( - name = "table_hit_2_test", - output_golden_file = "expected/table_hit_2.txt", - p4_program = "//p4_symbolic/testdata:conditional/table_hit_2.p4", - port_count = 8, - smt_golden_file = "expected/table_hit_2.smt2", - table_entries = "//p4_symbolic/testdata:conditional/table_hit_2_entries.pb.txt", -) - cc_test( name = "values_test", srcs = ["values_test.cc"], diff --git a/p4_symbolic/symbolic/expected/basic.smt2 b/p4_symbolic/symbolic/expected/basic.smt2 index 6fc6090c..ea71f237 100644 --- a/p4_symbolic/symbolic/expected/basic.smt2 +++ b/p4_symbolic/symbolic/expected/basic.smt2 @@ -97,8 +97,8 @@ (let (($x63 (and (and $x42 $x61) $x50))) (let ((?x113 (ite $x60 (_ bv1 9) (ite $x63 (_ bv0 9) (ite $x67 (_ bv1 9) (ite $x71 (_ bv1 9) ?x77)))))) (let (($x41 (= ?x113 (_ bv511 9)))) - (let (($x233 (and (not $x41) $x42))) - (and $x233 (= ?x112 0))))))))))))))))))))))) + (let (($x244 (and (not $x41) $x42))) + (and $x244 (= ?x112 0))))))))))))))))))))))) (check-sat) ; diff --git a/p4_symbolic/symbolic/expected/conditional.smt2 b/p4_symbolic/symbolic/expected/conditional.smt2 index f4fd807a..194deea5 100644 --- a/p4_symbolic/symbolic/expected/conditional.smt2 +++ b/p4_symbolic/symbolic/expected/conditional.smt2 @@ -125,7 +125,7 @@ (let ((?x45 (ite (and true (not $x41)) (_ bv511 9) (ite $x34 (_ bv511 9) (ite $x33 (_ bv511 9) standard_metadata.egress_spec))))) (let ((?x50 (ite $x42 (_ bv1 9) ?x45))) (let (($x52 (= ?x50 (_ bv511 9)))) - (let (($x147 (and (not $x52) true))) - (and $x147 (= ?x47 0)))))))))))))) + (let (($x95 (and (not $x52) true))) + (and $x95 (= ?x47 0)))))))))))))) (check-sat) diff --git a/p4_symbolic/symbolic/expected/conditional_sequence.smt2 b/p4_symbolic/symbolic/expected/conditional_sequence.smt2 index a2d50bc8..43922001 100644 --- a/p4_symbolic/symbolic/expected/conditional_sequence.smt2 +++ b/p4_symbolic/symbolic/expected/conditional_sequence.smt2 @@ -13,8 +13,8 @@ (assert (let (($x41 (and true (not (= h1.f1 (concat (_ bv0 7) (_ bv0 1))))))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x131 (not $x76))) - (and (and $x131 $x41) (= (- 1) (- 1))))))) + (let (($x134 (not $x76))) + (and (and $x134 $x41) (= (- 1) (- 1))))))) (check-sat) ; @@ -32,8 +32,8 @@ (assert (let (($x46 (and true (not (= h1.f2 (concat (_ bv0 7) (_ bv0 1))))))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x131 (not $x76))) - (and (and $x131 $x46) (= (- 1) (- 1))))))) + (let (($x134 (not $x76))) + (and (and $x134 $x46) (= (- 1) (- 1))))))) (check-sat) ; @@ -51,8 +51,8 @@ (assert (let (($x50 (and true (not (= h1.f3 (concat (_ bv0 7) (_ bv0 1))))))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x131 (not $x76))) - (and (and $x131 $x50) (= (- 1) (- 1))))))) + (let (($x134 (not $x76))) + (and (and $x134 $x50) (= (- 1) (- 1))))))) (check-sat) ; @@ -70,8 +70,8 @@ (assert (let (($x54 (and true (not (= h1.f4 (concat (_ bv0 7) (_ bv0 1))))))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x131 (not $x76))) - (and (and $x131 $x54) (= (- 1) (- 1))))))) + (let (($x134 (not $x76))) + (and (and $x134 $x54) (= (- 1) (- 1))))))) (check-sat) ; @@ -89,8 +89,8 @@ (assert (let (($x58 (and true (not (= h1.f5 (concat (_ bv0 7) (_ bv0 1))))))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x131 (not $x76))) - (and (and $x131 $x58) (= (- 1) (- 1))))))) + (let (($x134 (not $x76))) + (and (and $x134 $x58) (= (- 1) (- 1))))))) (check-sat) ; @@ -108,8 +108,8 @@ (assert (let (($x62 (and true (not (= h1.f6 (concat (_ bv0 7) (_ bv0 1))))))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x131 (not $x76))) - (and (and $x131 $x62) (= (- 1) (- 1))))))) + (let (($x134 (not $x76))) + (and (and $x134 $x62) (= (- 1) (- 1))))))) (check-sat) ; @@ -127,8 +127,8 @@ (assert (let (($x66 (and true (not (= h1.f7 (concat (_ bv0 7) (_ bv0 1))))))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x131 (not $x76))) - (and (and $x131 $x66) (= (- 1) (- 1))))))) + (let (($x134 (not $x76))) + (and (and $x134 $x66) (= (- 1) (- 1))))))) (check-sat) ; @@ -146,8 +146,8 @@ (assert (let (($x70 (and true (not (= h1.f8 (concat (_ bv0 7) (_ bv0 1))))))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x131 (not $x76))) - (and (and $x131 $x70) (= (- 1) (- 1))))))) + (let (($x134 (not $x76))) + (and (and $x134 $x70) (= (- 1) (- 1))))))) (check-sat) ; @@ -167,8 +167,8 @@ (let (($x38 (= h1.f1 ?x37))) (let (($x40 (and true $x38))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x131 (not $x76))) - (and (and $x131 $x40) (= (- 1) (- 1))))))))) + (let (($x134 (not $x76))) + (and (and $x134 $x40) (= (- 1) (- 1))))))))) (check-sat) ; @@ -188,8 +188,8 @@ (let (($x43 (= h1.f2 ?x37))) (let (($x45 (and true $x43))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x131 (not $x76))) - (and (and $x131 $x45) (= (- 1) (- 1))))))))) + (let (($x134 (not $x76))) + (and (and $x134 $x45) (= (- 1) (- 1))))))))) (check-sat) ; @@ -209,8 +209,8 @@ (let (($x47 (= h1.f3 ?x37))) (let (($x49 (and true $x47))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x131 (not $x76))) - (and (and $x131 $x49) (= (- 1) (- 1))))))))) + (let (($x134 (not $x76))) + (and (and $x134 $x49) (= (- 1) (- 1))))))))) (check-sat) ; @@ -230,8 +230,8 @@ (let (($x51 (= h1.f4 ?x37))) (let (($x53 (and true $x51))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x131 (not $x76))) - (and (and $x131 $x53) (= (- 1) (- 1))))))))) + (let (($x134 (not $x76))) + (and (and $x134 $x53) (= (- 1) (- 1))))))))) (check-sat) ; @@ -251,8 +251,8 @@ (let (($x55 (= h1.f5 ?x37))) (let (($x57 (and true $x55))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x131 (not $x76))) - (and (and $x131 $x57) (= (- 1) (- 1))))))))) + (let (($x134 (not $x76))) + (and (and $x134 $x57) (= (- 1) (- 1))))))))) (check-sat) ; @@ -272,8 +272,8 @@ (let (($x59 (= h1.f6 ?x37))) (let (($x61 (and true $x59))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x131 (not $x76))) - (and (and $x131 $x61) (= (- 1) (- 1))))))))) + (let (($x134 (not $x76))) + (and (and $x134 $x61) (= (- 1) (- 1))))))))) (check-sat) ; @@ -293,8 +293,8 @@ (let (($x63 (= h1.f7 ?x37))) (let (($x65 (and true $x63))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x131 (not $x76))) - (and (and $x131 $x65) (= (- 1) (- 1))))))))) + (let (($x134 (not $x76))) + (and (and $x134 $x65) (= (- 1) (- 1))))))))) (check-sat) ; @@ -314,8 +314,8 @@ (let (($x67 (= h1.f8 ?x37))) (let (($x69 (and true $x67))) (let (($x76 (= standard_metadata.egress_spec (_ bv511 9)))) - (let (($x131 (not $x76))) - (and (and $x131 $x69) (= (- 1) (- 1))))))))) + (let (($x134 (not $x76))) + (and (and $x134 $x69) (= (- 1) (- 1))))))))) (check-sat) ; diff --git a/p4_symbolic/symbolic/expected/string_optional.smt2 b/p4_symbolic/symbolic/expected/string_optional.smt2 index cc1de54a..be5e676a 100644 --- a/p4_symbolic/symbolic/expected/string_optional.smt2 +++ b/p4_symbolic/symbolic/expected/string_optional.smt2 @@ -99,8 +99,8 @@ (let ((?x78 (ite (and (and true (not $x68)) true) (_ bv1 9) standard_metadata.egress_spec))) (let ((?x80 (ite $x69 (_ bv0 9) ?x78))) (let (($x49 (= ?x80 (_ bv511 9)))) - (let (($x132 (and (not $x49) true))) - (and $x132 (= ?x79 0))))))))))))))))))))))) + (let (($x117 (and (not $x49) true))) + (and $x117 (= ?x79 0))))))))))))))))))))))) (check-sat) ; @@ -260,8 +260,8 @@ (let (($x69 (and true $x68))) (let ((?x80 (ite $x69 (_ bv0 9) ?x78))) (let (($x49 (= ?x80 (_ bv511 9)))) - (let (($x132 (and (not $x49) true))) - (and $x132 (= ?x62 0))))))))))))))))))))))))) + (let (($x117 (and (not $x49) true))) + (and $x117 (= ?x62 0))))))))))))))))))))))))) (check-sat) ; diff --git a/p4_symbolic/symbolic/expected/string_optional.txt b/p4_symbolic/symbolic/expected/string_optional.txt index 49dd2cf9..f7b8daec 100644 --- a/p4_symbolic/symbolic/expected/string_optional.txt +++ b/p4_symbolic/symbolic/expected/string_optional.txt @@ -9,9 +9,9 @@ Finding packet for table MyIngress.optional_match and row 0 Finding packet for table MyIngress.optional_match and row 1 Dropped = 0 - standard_metadata.ingress_port = #b000000001 + standard_metadata.ingress_port = #b000000010 standard_metadata.egress_spec = #b000000001 - scalars.metadata.string_field = VALUE-1 + scalars.metadata.string_field = VALUE-2 Finding packet for table MyIngress.set_field_table and row -1 Cannot find solution! @@ -36,7 +36,7 @@ Finding packet for table MyIngress.set_field_table and row 2 Finding packet for table tbl_program92 and row -1 Dropped = 0 - standard_metadata.ingress_port = #b000000010 + standard_metadata.ingress_port = #b000000001 standard_metadata.egress_spec = #b000000001 - scalars.metadata.string_field = VALUE-2 + scalars.metadata.string_field = VALUE-1 diff --git a/p4_symbolic/symbolic/expected/table.smt2 b/p4_symbolic/symbolic/expected/table.smt2 index 0374cc85..71f928db 100644 --- a/p4_symbolic/symbolic/expected/table.smt2 +++ b/p4_symbolic/symbolic/expected/table.smt2 @@ -57,8 +57,8 @@ (let ((?x41 (ite (and (and true (not $x26)) $x29) (_ bv0 9) standard_metadata.egress_spec))) (let ((?x44 (ite $x30 (_ bv1 9) ?x41))) (let (($x35 (= ?x44 (_ bv511 9)))) - (let (($x102 (and (not $x35) true))) - (and $x102 (= ?x43 0)))))))))))))) + (let (($x109 (and (not $x35) true))) + (and $x109 (= ?x43 0)))))))))))))) (check-sat) ; diff --git a/p4_symbolic/symbolic/expected/table_hit_1.smt2 b/p4_symbolic/symbolic/expected/table_hit_1.smt2 index 1d233810..8860c77c 100644 --- a/p4_symbolic/symbolic/expected/table_hit_1.smt2 +++ b/p4_symbolic/symbolic/expected/table_hit_1.smt2 @@ -77,8 +77,8 @@ (let (($x48 (and $x44 (and true (= ethernet.src_addr (_ bv256 48)))))) (let ((?x54 (ite $x48 (_ bv3 9) ?x41))) (let (($x49 (= ?x54 (_ bv511 9)))) - (let (($x207 (and (not $x49) true))) - (and $x207 (= ?x38 0))))))))))))) + (let (($x206 (and (not $x49) true))) + (and $x206 (= ?x38 0))))))))))))) (check-sat) ; diff --git a/p4_symbolic/symbolic/expected/table_hit_1.txt b/p4_symbolic/symbolic/expected/table_hit_1.txt index 5c1521ac..a09a9b0a 100644 --- a/p4_symbolic/symbolic/expected/table_hit_1.txt +++ b/p4_symbolic/symbolic/expected/table_hit_1.txt @@ -3,7 +3,7 @@ Cannot find solution! Finding packet for table MyIngress.t_1 and row 0 Dropped = 0 - standard_metadata.ingress_port = #b000000001 + standard_metadata.ingress_port = #b000000000 standard_metadata.egress_spec = #b000000010 Finding packet for table MyIngress.t_2 and row -1 diff --git a/p4_symbolic/symbolic/expected/table_hit_2.txt b/p4_symbolic/symbolic/expected/table_hit_2.txt index 963d47c4..bf736f78 100644 --- a/p4_symbolic/symbolic/expected/table_hit_2.txt +++ b/p4_symbolic/symbolic/expected/table_hit_2.txt @@ -1,12 +1,12 @@ Finding packet for table MyIngress.t1 and row -1 Dropped = 0 standard_metadata.ingress_port = #b000000000 - standard_metadata.egress_spec = #b000000011 + standard_metadata.egress_spec = #b000000000 Finding packet for table MyIngress.t1 and row 0 Dropped = 0 standard_metadata.ingress_port = #b000000000 - standard_metadata.egress_spec = #b000000001 + standard_metadata.egress_spec = #b000000010 Finding packet for table MyIngress.t2 and row -1 Dropped = 0 diff --git a/p4_symbolic/symbolic/expected/vrf.smt2 b/p4_symbolic/symbolic/expected/vrf.smt2 index 423eab16..2bf13d32 100644 --- a/p4_symbolic/symbolic/expected/vrf.smt2 +++ b/p4_symbolic/symbolic/expected/vrf.smt2 @@ -139,8 +139,8 @@ (let (($x112 (and (and $x85 (not $x91)) $x96))) (let ((?x158 (ite $x109 (_ bv1 9) (ite $x112 (_ bv0 9) ?x142)))) (let (($x52 (= ?x158 (_ bv511 9)))) - (let (($x308 (and (not $x52) $x85))) - (and $x308 (= ?x157 0))))))))))))))))))))))))))))))) + (let (($x307 (and (not $x52) $x85))) + (and $x307 (= ?x157 0))))))))))))))))))))))))))))))) (check-sat) ; diff --git a/p4_symbolic/symbolic/expected/vrf.txt b/p4_symbolic/symbolic/expected/vrf.txt index 00de6e1b..c2573bf9 100644 --- a/p4_symbolic/symbolic/expected/vrf.txt +++ b/p4_symbolic/symbolic/expected/vrf.txt @@ -3,17 +3,17 @@ Cannot find solution! Finding packet for table packet_ingress.ipv4_lpm_table and row 0 Dropped = 0 - standard_metadata.ingress_port = #b000000001 + standard_metadata.ingress_port = #b000000000 standard_metadata.egress_spec = #b000000000 ipv4.srcAddr = #x29210000 - ipv4.dstAddr = #x0a0a0002 + ipv4.dstAddr = #x0a0a0080 ethernet.dstAddr = #x000000000000 scalars.local_metadata_t.vrf = VRF1 scalars.local_metadata_t.vrf_is_valid = #b1 Finding packet for table packet_ingress.ipv4_lpm_table and row 1 Dropped = 0 - standard_metadata.ingress_port = #b000000001 + standard_metadata.ingress_port = #b000000000 standard_metadata.egress_spec = #b000000001 ipv4.srcAddr = #x29210000 ipv4.dstAddr = #x0a0a0000 @@ -23,17 +23,17 @@ Finding packet for table packet_ingress.ipv4_lpm_table and row 1 Finding packet for table packet_ingress.ipv4_lpm_table and row 2 Dropped = 0 - standard_metadata.ingress_port = #b000000001 + standard_metadata.ingress_port = #b000000000 standard_metadata.egress_spec = #b000000001 ipv4.srcAddr = #x29210000 - ipv4.dstAddr = #x0a0e0000 + ipv4.dstAddr = #x0a8a0000 ethernet.dstAddr = #x00000000000a scalars.local_metadata_t.vrf = VRF1 scalars.local_metadata_t.vrf_is_valid = #b1 Finding packet for table packet_ingress.ipv4_lpm_table and row 3 Dropped = 0 - standard_metadata.ingress_port = #b000000001 + standard_metadata.ingress_port = #b000000000 standard_metadata.egress_spec = #b000000001 ipv4.srcAddr = #x20210100 ipv4.dstAddr = #x14140000 @@ -43,17 +43,17 @@ Finding packet for table packet_ingress.ipv4_lpm_table and row 3 Finding packet for table packet_ingress.set_vrf_table and row -1 Dropped = 0 - standard_metadata.ingress_port = #b000000001 + standard_metadata.ingress_port = #b000000000 standard_metadata.egress_spec = #b000000001 - ipv4.srcAddr = #x08000909 - ipv4.dstAddr = #x14140000 + ipv4.srcAddr = #x21000809 + ipv4.dstAddr = #x00000000 ethernet.dstAddr = #x000000000000 scalars.local_metadata_t.vrf = VRF1 scalars.local_metadata_t.vrf_is_valid = #b0 Finding packet for table packet_ingress.set_vrf_table and row 0 Dropped = 0 - standard_metadata.ingress_port = #b000000001 + standard_metadata.ingress_port = #b000000000 standard_metadata.egress_spec = #b000000001 ipv4.srcAddr = #x20210100 ipv4.dstAddr = #x14140000 @@ -63,20 +63,20 @@ Finding packet for table packet_ingress.set_vrf_table and row 0 Finding packet for table packet_ingress.set_vrf_table and row 1 Dropped = 0 - standard_metadata.ingress_port = #b000000001 + standard_metadata.ingress_port = #b000000000 standard_metadata.egress_spec = #b000000001 ipv4.srcAddr = #x21210000 - ipv4.dstAddr = #x0a140000 + ipv4.dstAddr = #x0a000000 ethernet.dstAddr = #x00000000000a scalars.local_metadata_t.vrf = VRF1 scalars.local_metadata_t.vrf_is_valid = #b1 Finding packet for table tbl_vrf133 and row -1 Dropped = 0 - standard_metadata.ingress_port = #b000000001 + standard_metadata.ingress_port = #b000000000 standard_metadata.egress_spec = #b000000001 ipv4.srcAddr = #x21210000 - ipv4.dstAddr = #x0a140000 - ethernet.dstAddr = #x00000000000a + ipv4.dstAddr = #x0a000000 + ethernet.dstAddr = #x000000000000 scalars.local_metadata_t.vrf = VRF1 - scalars.local_metadata_t.vrf_is_valid = #b1 + scalars.local_metadata_t.vrf_is_valid = #b0 From 2e971e0a64b7e28214454ef9a6fc1317f43e2aef Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Thu, 21 Nov 2024 18:29:30 +0000 Subject: [PATCH 08/12] [Dvaas] Refactor various switch output comparison params into a struct. (#743) Co-authored-by: kheradmandG Co-authored-by: kishanps --- dvaas/arriba_test_vector_validation.cc | 3 +- dvaas/arriba_test_vector_validation.h | 20 ++++------ dvaas/test_run_validation.cc | 53 +++++++++++--------------- dvaas/test_run_validation.h | 35 ++++++++++------- dvaas/validation_result.cc | 9 ++--- dvaas/validation_result.h | 7 ++-- 6 files changed, 58 insertions(+), 69 deletions(-) diff --git a/dvaas/arriba_test_vector_validation.cc b/dvaas/arriba_test_vector_validation.cc index 997e89fd..b9e69224 100644 --- a/dvaas/arriba_test_vector_validation.cc +++ b/dvaas/arriba_test_vector_validation.cc @@ -73,8 +73,7 @@ absl::Status ValidateAgaistArribaTestVector( params.max_packets_to_send_per_second)); // Compare the switch output with expected output for each test vector. - return ValidateTestRuns(test_runs, params.ignored_fields_for_validation, - params.ignored_metadata_for_validation, + return ValidateTestRuns(test_runs, params.switch_output_diff_params, /*write_failures=*/[&](absl::string_view failures) { return artifact_writer.AppendToTestArtifact( "test_failures.txt", failures); diff --git a/dvaas/arriba_test_vector_validation.h b/dvaas/arriba_test_vector_validation.h index 21f72383..21c5a30c 100644 --- a/dvaas/arriba_test_vector_validation.h +++ b/dvaas/arriba_test_vector_validation.h @@ -19,6 +19,7 @@ #include "absl/container/flat_hash_set.h" #include "absl/status/statusor.h" +#include "dvaas/test_run_validation.h" #include "dvaas/test_vector.pb.h" #include "p4_pdpi/p4_runtime_session.h" #include "thinkit/mirror_testbed.h" @@ -27,19 +28,12 @@ namespace dvaas { // Parameters for validating a testbed against an ArribaTestVector. struct ArribaTestVectorValidationParams { - // Used to skip packet fields where model and switch are known to have - // different behavior, which we don't want to test. All FieldDescriptors - // should belong to packetlib::Packet. - std::vector - ignored_fields_for_validation = {}; - - // Used to skip packet-in metadata where model and switch are known to have - // different behavior, which we don't want to test. If a packet-in metadata - // field name in the actual or expected packets is equal to one of the entries - // in `ignored_metadata_for_validation`, the field is ignored during - // comparison. - absl::flat_hash_set ignored_metadata_for_validation = { - "target_egress_port", + // Parameters to control the comparison between the actual switch + // output and the expected switch output per each input packet. + SwitchOutputDiffParams switch_output_diff_params = { + // TODO: Remove when it is possible to reliably validate + // target egress port. + .ignored_packet_in_metadata = {"target_egress_port"}, }; // Max number of packets to send per second. If no rate is given, the test diff --git a/dvaas/test_run_validation.cc b/dvaas/test_run_validation.cc index 65d00c1d..32d147b3 100644 --- a/dvaas/test_run_validation.cc +++ b/dvaas/test_run_validation.cc @@ -88,11 +88,10 @@ std::optional GetPacketInMetadataByName( return std::nullopt; } -bool CompareSwitchOutputs( - SwitchOutput actual_output, SwitchOutput expected_output, - const absl::flat_hash_set& ignored_metadata, - const std::vector& ignored_fields, - MatchResultListener* listener) { +bool CompareSwitchOutputs(SwitchOutput actual_output, + SwitchOutput expected_output, + const SwitchOutputDiffParams& params, + MatchResultListener* listener) { if (actual_output.packets_size() != expected_output.packets_size()) { *listener << "has mismatched number of packets (actual: " << actual_output.packets_size() @@ -122,7 +121,7 @@ bool CompareSwitchOutputs( const Packet& actual_packet = actual_output.packets(i); const Packet& expected_packet = expected_output.packets(i); MessageDifferencer differ; - for (auto* field : ignored_fields) differ.IgnoreField(field); + for (auto* field : params.ignored_fields) differ.IgnoreField(field); std::string diff; differ.ReportDifferencesToString(&diff); if (!differ.Compare(expected_packet.parsed(), actual_packet.parsed())) { @@ -137,7 +136,7 @@ bool CompareSwitchOutputs( const PacketIn& expected_packet_in = expected_output.packet_ins(i); MessageDifferencer differ; - for (auto* field : ignored_fields) differ.IgnoreField(field); + for (auto* field : params.ignored_fields) differ.IgnoreField(field); std::string diff; differ.ReportDifferencesToString(&diff); if (!differ.Compare(expected_packet_in.parsed(), @@ -150,7 +149,8 @@ bool CompareSwitchOutputs( // Check that received packet in has desired metadata (except for ignored // metadata). for (const auto& expected_metadata : expected_packet_in.metadata()) { - if (ignored_metadata.contains(expected_metadata.name())) continue; + if (params.ignored_packet_in_metadata.contains(expected_metadata.name())) + continue; std::optional actual_metadata = GetPacketInMetadataByName(actual_packet_in, expected_metadata.name()); @@ -172,7 +172,8 @@ bool CompareSwitchOutputs( // Check that received packet in does not have additional metadata (except // for ignored metadata). for (const auto& actual_metadata : actual_packet_in.metadata()) { - if (ignored_metadata.contains(actual_metadata.name())) continue; + if (params.ignored_packet_in_metadata.contains(actual_metadata.name())) + continue; std::optional expected_metadata = GetPacketInMetadataByName(expected_packet_in, actual_metadata.name()); @@ -194,17 +195,14 @@ bool CompareSwitchOutputs( // is acceptable, or an explanation of why it is not otherwise. absl::optional CompareSwitchOutputs( const PacketTestVector packet_test_vector, - const SwitchOutput& actual_output, - const absl::flat_hash_set& ignored_metadata, - const std::vector& - ignored_fields) { + const SwitchOutput& actual_output, const SwitchOutputDiffParams& params) { testing::StringMatchResultListener listener; for (int i = 0; i < packet_test_vector.acceptable_outputs_size(); ++i) { const SwitchOutput& expected_output = packet_test_vector.acceptable_outputs(i); listener << "- acceptable output #" << (i + 1) << " "; - if (CompareSwitchOutputs(actual_output, expected_output, ignored_metadata, - ignored_fields, &listener)) { + if (CompareSwitchOutputs(actual_output, expected_output, params, + &listener)) { return absl::nullopt; } } @@ -330,15 +328,11 @@ absl::StatusOr ExtractTestPacketTag(const packetlib::Packet& packet) { } PacketTestValidationResult ValidateTestRun( - const PacketTestRun& test_run, - const absl::flat_hash_set& ignored_packet_in_metadata, - const std::vector& - ignored_fields) { + const PacketTestRun& test_run, const SwitchOutputDiffParams& diff_params) { PacketTestValidationResult result; - const absl::optional diff = - CompareSwitchOutputs(test_run.test_vector(), test_run.actual_output(), - ignored_packet_in_metadata, ignored_fields); + const absl::optional diff = CompareSwitchOutputs( + test_run.test_vector(), test_run.actual_output(), diff_params); if (!diff.has_value()) return result; // To make the diff more digestible, we first give an abstract @@ -357,10 +351,10 @@ PacketTestValidationResult ValidateTestRun( std::string expectation = DescribeExpectation( test_run.test_vector().input(), acceptable_output_characterizations); - if (!ignored_fields.empty()) { + if (!diff_params.ignored_fields.empty()) { absl::StrAppend( &expectation, "\n (ignoring the field(s) ", - absl::StrJoin(ignored_fields, ",", + absl::StrJoin(diff_params.ignored_fields, ",", [](std::string* out, const google::protobuf::FieldDescriptor* field) { absl::StrAppend(out, "'", field->name(), "'"); @@ -400,17 +394,14 @@ PacketTestValidationResult ValidateTestRun( return result; } -absl::Status ValidateTestRuns( - const PacketTestRuns& test_runs, - std::vector ignored_fields, - const absl::flat_hash_set& ignored_metadata, - const OutputWriterFunctionType& write_failures) { +absl::Status ValidateTestRuns(const PacketTestRuns& test_runs, + const SwitchOutputDiffParams& diff_params, + const OutputWriterFunctionType& write_failures) { LOG(INFO) << "Validating test runs"; std::vector failures; for (const PacketTestRun& test_run : test_runs.test_runs()) { - PacketTestValidationResult result = - ValidateTestRun(test_run, ignored_metadata, ignored_fields); + PacketTestValidationResult result = ValidateTestRun(test_run, diff_params); if (result.has_failure()) { failures.push_back(result.failure().description()); } diff --git a/dvaas/test_run_validation.h b/dvaas/test_run_validation.h index 27472424..49cb1163 100644 --- a/dvaas/test_run_validation.h +++ b/dvaas/test_run_validation.h @@ -27,24 +27,33 @@ namespace dvaas { -// Validates the given `test_vector` and returns the result. -// Validation compares the actual output packets against the expected output -// packets modulo `ignored_fields` and `ignored_metadata, where: -// * `ignored_fields` must belong to packetlib::Packet, and -// * `ignored_metadata` must be the names of Packet In metadata fields. +// Parameters to control the comparison between the actual switch output and the +// expected switch output per input packet. +struct SwitchOutputDiffParams { + // Used to skip packet fields where model and switch are known to have + // different behavior, which we don't want to test. All FieldDescriptors + // should belong to packetlib::Packet. + std::vector ignored_fields; + + // Used to skip packet-in metadata where model and switch are known to have + // different behavior, which we don't want to test. If a packet-in metadata + // field name in the actual or expected packets is equal to one of the entries + // in `ignored_metadata_for_validation`, the field is ignored during + // comparison. + absl::flat_hash_set ignored_packet_in_metadata; +}; + +// Validates the given `test_vector` using the parameters in `params` and +// returns the result. PacketTestValidationResult ValidateTestRun( const PacketTestRun& test_run, - const absl::flat_hash_set& ignored_packet_in_metadata = {}, - const std::vector& - ignored_fields = {}); + const SwitchOutputDiffParams& diff_params = {}); // Like `ValidateTestRun`, but for a collection of `test_runs`. Also // writes the results to a test artifact using `write_failures`. -absl::Status ValidateTestRuns( - const PacketTestRuns& test_runs, - std::vector ignored_fields, - const absl::flat_hash_set& ignored_metadata, - const OutputWriterFunctionType& write_failures); +absl::Status ValidateTestRuns(const PacketTestRuns& test_runs, + const SwitchOutputDiffParams& diff_params, + const OutputWriterFunctionType& write_failures); } // namespace dvaas diff --git a/dvaas/validation_result.cc b/dvaas/validation_result.cc index 029da0ba..45e24c10 100644 --- a/dvaas/validation_result.cc +++ b/dvaas/validation_result.cc @@ -29,16 +29,13 @@ namespace dvaas { -ValidationResult::ValidationResult( - const PacketTestRuns& test_runs, - std::vector ignored_fields, - const absl::flat_hash_set& ignored_metadata) { +ValidationResult::ValidationResult(const PacketTestRuns& test_runs, + const SwitchOutputDiffParams& diff_params) { test_outcomes_.mutable_outcomes()->Reserve(test_runs.test_runs_size()); for (const auto& test_run : test_runs.test_runs()) { PacketTestOutcome& outcome = *test_outcomes_.add_outcomes(); *outcome.mutable_test_run() = test_run; - *outcome.mutable_test_result() = - ValidateTestRun(test_run, ignored_metadata, ignored_fields); + *outcome.mutable_test_result() = ValidateTestRun(test_run, diff_params); } test_vector_stats_ = ComputeTestVectorStats(test_outcomes_); diff --git a/dvaas/validation_result.h b/dvaas/validation_result.h index fcc02017..c60f0361 100644 --- a/dvaas/validation_result.h +++ b/dvaas/validation_result.h @@ -24,6 +24,7 @@ #include "absl/container/flat_hash_set.h" #include "absl/status/status.h" #include "absl/types/span.h" +#include "dvaas/test_run_validation.h" #include "dvaas/test_vector.pb.h" #include "dvaas/test_vector_stats.h" #include "google/protobuf/descriptor.h" @@ -60,10 +61,8 @@ class [[nodiscard]] ValidationResult { // Constructs a `ValidationResult` from the given `test_vectors`. Ignores // `ignored_fields` and `ignored_metadata` during validation, see // `test_run_validation.h` for details. - ValidationResult( - const PacketTestRuns& test_runs, - std::vector ignored_fields, - const absl::flat_hash_set& ignored_metadata); + ValidationResult(const PacketTestRuns& test_runs, + const SwitchOutputDiffParams& diff_params); private: PacketTestOutcomes test_outcomes_; From 1b4c12adf18568e206481955f7114b40c140384d Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Thu, 21 Nov 2024 18:30:08 +0000 Subject: [PATCH 09/12] [P4 Symbolic] Make certain Z3 APIs more general. (#744) Co-authored-by: kishanps Co-authored-by: jonathan-dilorenzo --- p4_symbolic/packet_synthesizer/util.cc | 2 +- p4_symbolic/symbolic/action.cc | 10 +- p4_symbolic/symbolic/table.cc | 154 ++++++++++++------------- p4_symbolic/symbolic/table.h | 8 ++ p4_symbolic/symbolic/values.cc | 8 +- p4_symbolic/symbolic/values.h | 3 +- p4_symbolic/symbolic/values_test.cc | 37 +++--- p4_symbolic/z3_util.cc | 5 +- p4_symbolic/z3_util.h | 5 +- 9 files changed, 124 insertions(+), 108 deletions(-) diff --git a/p4_symbolic/packet_synthesizer/util.cc b/p4_symbolic/packet_synthesizer/util.cc index 0019ab22..98432909 100644 --- a/p4_symbolic/packet_synthesizer/util.cc +++ b/p4_symbolic/packet_synthesizer/util.cc @@ -185,7 +185,7 @@ absl::StatusOr IrValueToZ3Bitvector(const pdpi::IrValue& value, ASSIGN_OR_RETURN(const std::string bytes, pdpi::IrValueToNormalizedByteString(value, bitwidth)); const std::string hex_string = pdpi::ByteStringToHexString(bytes); - return HexStringToZ3Bitvector(hex_string, bitwidth); + return HexStringToZ3Bitvector(Z3Context(), hex_string, bitwidth); } // The following functions return Z3 constraints corresponding to `field` diff --git a/p4_symbolic/symbolic/action.cc b/p4_symbolic/symbolic/action.cc index 527133db..e5b55b4f 100644 --- a/p4_symbolic/symbolic/action.cc +++ b/p4_symbolic/symbolic/action.cc @@ -168,7 +168,7 @@ absl::StatusOr EvaluateHexStr(const ir::HexstrValue &hexstr) { ASSIGN_OR_RETURN(pdpi::IrValue parsed_value, values::ParseIrValue(hexstr.value())); - return HexStringToZ3Bitvector(hexstr.value()); + return HexStringToZ3Bitvector(Z3Context(), hexstr.value()); } absl::StatusOr EvaluateBool(const ir::BoolValue &bool_value) { @@ -336,10 +336,10 @@ absl::Status EvaluateAction(const ir::Action &action, const std::string ¶meter_type_name = parameter.param().type_name().name(); const int bitwidth = parameter.param().bitwidth(); - ASSIGN_OR_RETURN( - z3::expr parameter_value, - values::FormatP4RTValue(/*field_name=*/"", parameter_type_name, - args.at(i - 1).value(), bitwidth, translator)); + ASSIGN_OR_RETURN(z3::expr parameter_value, + values::FormatP4RTValue( + Z3Context(), /*field_name=*/"", parameter_type_name, + args.at(i - 1).value(), bitwidth, translator)); context.scope.insert({parameter_name, parameter_value}); } diff --git a/p4_symbolic/symbolic/table.cc b/p4_symbolic/symbolic/table.cc index 948ed37c..39d0ed24 100644 --- a/p4_symbolic/symbolic/table.cc +++ b/p4_symbolic/symbolic/table.cc @@ -154,81 +154,6 @@ std::vector> SortEntries( return sorted_entries; } -// Analyze a single match that is part of a table entry. -// Constructs a symbolic expression that semantically corresponds to this -// match. -absl::StatusOr EvaluateSingleMatch( - p4::config::v1::MatchField match_definition, const std::string &field_name, - const z3::expr &field_expression, const pdpi::IrMatch &match, - values::P4RuntimeTranslator *translator) { - if (match_definition.match_case() != p4::config::v1::MatchField::kMatchType) { - // Arch-specific match type. - return absl::InvalidArgumentError( - absl::StrCat("Found match with non-standard type")); - } - - absl::Status mismatch = - gutil::InvalidArgumentErrorBuilder() - << "match on field '" << field_name << "' must be of type " - << p4::config::v1::MatchField::MatchType_Name( - match_definition.match_type()) - << " according to the table definition, but got entry with match: " - << match_definition.ShortDebugString(); - - auto GetZ3Value = - [&](const pdpi::IrValue &value) -> absl::StatusOr { - return values::FormatP4RTValue(field_name, - match_definition.type_name().name(), value, - match_definition.bitwidth(), translator); - }; - - switch (match_definition.match_type()) { - case p4::config::v1::MatchField::EXACT: { - if (match.match_value_case() != pdpi::IrMatch::kExact) return mismatch; - ASSIGN_OR_RETURN(z3::expr value_expression, GetZ3Value(match.exact())); - return operators::Eq(field_expression, value_expression); - } - - case p4::config::v1::MatchField::LPM: { - if (match.match_value_case() != pdpi::IrMatch::kLpm) return mismatch; - - ASSIGN_OR_RETURN(z3::expr value_expression, - GetZ3Value(match.lpm().value())); - return operators::PrefixEq( - field_expression, value_expression, - static_cast(match.lpm().prefix_length())); - } - - case p4::config::v1::MatchField::TERNARY: { - if (match.match_value_case() != pdpi::IrMatch::kTernary) return mismatch; - - ASSIGN_OR_RETURN(z3::expr mask_expression, - GetZ3Value(match.ternary().mask())); - ASSIGN_OR_RETURN(z3::expr value_expression, - GetZ3Value(match.ternary().value())); - ASSIGN_OR_RETURN(z3::expr masked_field, - operators::BitAnd(field_expression, mask_expression)); - return operators::Eq(masked_field, value_expression); - } - - case p4::config::v1::MatchField::OPTIONAL: { - if (match.match_value_case() != pdpi::IrMatch::kOptional) return mismatch; - // According to the P4Runtime spec, "for don't care matches, the P4Runtime - // client must omit the field's entire FieldMatch entry when building the - // match repeated field of the TableEntry message". Therefore, if the - // match value is present for an optional match type, it must be a - // concrete value. - ASSIGN_OR_RETURN(z3::expr value_expression, - GetZ3Value(match.optional().value())); - return operators::Eq(field_expression, value_expression); - } - - default: - return absl::UnimplementedError(absl::StrCat( - "Found unsupported match type ", match_definition.DebugString())); - } -} - // Constructs a symbolic expression that is true if and only if this entry // is matched on. absl::StatusOr EvaluateTableEntryCondition( @@ -279,9 +204,10 @@ absl::StatusOr EvaluateTableEntryCondition( ASSIGN_OR_RETURN( z3::expr match_field_expr, action::EvaluateFieldValue(match_field, state, fake_context)); - ASSIGN_OR_RETURN(z3::expr match_expression, - EvaluateSingleMatch(match_definition, field_name, - match_field_expr, match, translator)); + ASSIGN_OR_RETURN( + z3::expr match_expression, + EvaluateSingleMatch(Z3Context(), match_definition, field_name, + match_field_expr, match, translator)); ASSIGN_OR_RETURN(condition_expression, operators::And(condition_expression, match_expression)); } @@ -350,6 +276,78 @@ absl::Status EvaluateTableEntryAction( } // namespace +absl::StatusOr EvaluateSingleMatch( + z3::context &context, p4::config::v1::MatchField match_definition, + const std::string &field_name, const z3::expr &field_expression, + const pdpi::IrMatch &match, values::P4RuntimeTranslator *translator) { + if (match_definition.match_case() != p4::config::v1::MatchField::kMatchType) { + // Arch-specific match type. + return absl::InvalidArgumentError( + absl::StrCat("Found match with non-standard type")); + } + + absl::Status mismatch = + gutil::InvalidArgumentErrorBuilder() + << "match on field '" << field_name << "' must be of type " + << p4::config::v1::MatchField::MatchType_Name( + match_definition.match_type()) + << " according to the table definition, but got entry with match: " + << match_definition.ShortDebugString(); + + auto GetZ3Value = + [&](const pdpi::IrValue &value) -> absl::StatusOr { + return values::FormatP4RTValue(context, field_name, + match_definition.type_name().name(), value, + match_definition.bitwidth(), translator); + }; + + switch (match_definition.match_type()) { + case p4::config::v1::MatchField::EXACT: { + if (match.match_value_case() != pdpi::IrMatch::kExact) return mismatch; + ASSIGN_OR_RETURN(z3::expr value_expression, GetZ3Value(match.exact())); + return operators::Eq(field_expression, value_expression); + } + + case p4::config::v1::MatchField::LPM: { + if (match.match_value_case() != pdpi::IrMatch::kLpm) return mismatch; + + ASSIGN_OR_RETURN(z3::expr value_expression, + GetZ3Value(match.lpm().value())); + return operators::PrefixEq( + field_expression, value_expression, + static_cast(match.lpm().prefix_length())); + } + + case p4::config::v1::MatchField::TERNARY: { + if (match.match_value_case() != pdpi::IrMatch::kTernary) return mismatch; + + ASSIGN_OR_RETURN(z3::expr mask_expression, + GetZ3Value(match.ternary().mask())); + ASSIGN_OR_RETURN(z3::expr value_expression, + GetZ3Value(match.ternary().value())); + ASSIGN_OR_RETURN(z3::expr masked_field, + operators::BitAnd(field_expression, mask_expression)); + return operators::Eq(masked_field, value_expression); + } + + case p4::config::v1::MatchField::OPTIONAL: { + if (match.match_value_case() != pdpi::IrMatch::kOptional) return mismatch; + // According to the P4Runtime spec, "for don't care matches, the P4Runtime + // client must omit the field's entire FieldMatch entry when building the + // match repeated field of the TableEntry message". Therefore, if the + // match value is present for an optional match type, it must be a + // concrete value. + ASSIGN_OR_RETURN(z3::expr value_expression, + GetZ3Value(match.optional().value())); + return operators::Eq(field_expression, value_expression); + } + + default: + return absl::UnimplementedError(absl::StrCat( + "Found unsupported match type ", match_definition.DebugString())); + } +} + absl::StatusOr EvaluateTable( const Dataplane &data_plane, const ir::Table &table, const std::vector &entries, diff --git a/p4_symbolic/symbolic/table.h b/p4_symbolic/symbolic/table.h index 09def04b..7a6ae699 100644 --- a/p4_symbolic/symbolic/table.h +++ b/p4_symbolic/symbolic/table.h @@ -45,6 +45,14 @@ absl::StatusOr EvaluateTable( SymbolicPerPacketState *state, values::P4RuntimeTranslator *translator, const z3::expr &guard); +// Analyze a single match that is part of a table entry. +// Constructs a symbolic expression that semantically corresponds to this +// match. +absl::StatusOr EvaluateSingleMatch( + z3::context &context, p4::config::v1::MatchField match_definition, + const std::string &field_name, const z3::expr &field_expression, + const pdpi::IrMatch &match, values::P4RuntimeTranslator *translator); + } // namespace table } // namespace symbolic } // namespace p4_symbolic diff --git a/p4_symbolic/symbolic/values.cc b/p4_symbolic/symbolic/values.cc index bfdf6666..b41af2f9 100644 --- a/p4_symbolic/symbolic/values.cc +++ b/p4_symbolic/symbolic/values.cc @@ -75,7 +75,8 @@ absl::StatusOr ParseIrValue(const std::string &value) { } } -absl::StatusOr FormatP4RTValue(const std::string &field_name, +absl::StatusOr FormatP4RTValue(z3::context &context, + const std::string &field_name, const std::string &type_name, const pdpi::IrValue &value, int bitwidth, @@ -110,7 +111,7 @@ absl::StatusOr FormatP4RTValue(const std::string &field_name, type_name, bitwidth)); } - return Z3Context().bv_val(int_value, bitwidth); + return context.bv_val(int_value, bitwidth); } default: { if (translator->fields_p4runtime_type.count(field_name)) { @@ -128,7 +129,8 @@ absl::StatusOr FormatP4RTValue(const std::string &field_name, pdpi::ArbitraryByteStringToIrValue( pdpi::HEX_STRING, bitwidth, byte_string)); // Now convert the hex string internally. - return HexStringToZ3Bitvector(normalized_value.hex_str(), bitwidth); + return HexStringToZ3Bitvector(context, normalized_value.hex_str(), + bitwidth); } } } diff --git a/p4_symbolic/symbolic/values.h b/p4_symbolic/symbolic/values.h index d064fdd9..2d9d0118 100644 --- a/p4_symbolic/symbolic/values.h +++ b/p4_symbolic/symbolic/values.h @@ -113,7 +113,8 @@ absl::StatusOr ParseIrValue(const std::string &value); // translated values (i.e. string IrValues) the bitwidth MUST be 0, in which // case we use the minimum number of bits to encode the resulting translated // value. -absl::StatusOr FormatP4RTValue(const std::string &field_name, +absl::StatusOr FormatP4RTValue(z3::context &context, + const std::string &field_name, const std::string &type_name, const pdpi::IrValue &value, int bitwidth, diff --git a/p4_symbolic/symbolic/values_test.cc b/p4_symbolic/symbolic/values_test.cc index e1ab49ac..b9346ddc 100644 --- a/p4_symbolic/symbolic/values_test.cc +++ b/p4_symbolic/symbolic/values_test.cc @@ -28,9 +28,10 @@ TEST(TranslateValueToP4RT, ReverseTranslatedValuesAreEqualToTheOriginalOnes) { const std::string id = absl::StrCat("id-", i); pdpi::IrValue ir_value; ir_value.set_str(id); - ASSERT_OK_AND_ASSIGN(z3::expr expr, - FormatP4RTValue(kFieldName, kFieldType, ir_value, - /*bitwidth=*/0, &translator)); + ASSERT_OK_AND_ASSIGN( + z3::expr expr, + FormatP4RTValue(Z3Context(), kFieldName, kFieldType, ir_value, + /*bitwidth=*/0, &translator)); z3_value_to_id[expr.to_string()] = id; } @@ -49,9 +50,10 @@ TEST(FormatP4RTValue, WorksFor64BitIPv6) { ASSERT_OK_AND_ASSIGN(auto ir_value, gutil::ParseTextProto( R"pb(ipv6: "0000:ffff:ffff:ffff::")pb")); - ASSERT_OK_AND_ASSIGN(z3::expr z3_expr, - FormatP4RTValue(kFieldName, kFieldType, ir_value, - /*bitwidth=*/64, &trasnlator)); + ASSERT_OK_AND_ASSIGN( + z3::expr z3_expr, + FormatP4RTValue(Z3Context(), kFieldName, kFieldType, ir_value, + /*bitwidth=*/64, &trasnlator)); ASSERT_EQ(Z3ValueStringToInt(z3_expr.to_string()), 0x0000'ffff'ffff'ffffULL); } @@ -59,9 +61,10 @@ TEST(FormatP4RTValue, WorksForIpv4) { P4RuntimeTranslator trasnlator; ASSERT_OK_AND_ASSIGN(auto ir_value, gutil::ParseTextProto( R"pb(ipv4: "127.0.0.1")pb")); - ASSERT_OK_AND_ASSIGN(z3::expr z3_expr, - FormatP4RTValue(kFieldName, kFieldType, ir_value, - /*bitwidth=*/32, &trasnlator)); + ASSERT_OK_AND_ASSIGN( + z3::expr z3_expr, + FormatP4RTValue(Z3Context(), kFieldName, kFieldType, ir_value, + /*bitwidth=*/32, &trasnlator)); ASSERT_EQ(Z3ValueStringToInt(z3_expr.to_string()), 0x7f000001); } @@ -69,9 +72,10 @@ TEST(FormatP4RTValue, WorksForMac) { P4RuntimeTranslator trasnlator; ASSERT_OK_AND_ASSIGN(auto ir_value, gutil::ParseTextProto( R"pb(mac: "01:02:03:04:05:06")pb")); - ASSERT_OK_AND_ASSIGN(z3::expr z3_expr, - FormatP4RTValue(kFieldName, kFieldType, ir_value, - /*bitwidth=*/48, &trasnlator)); + ASSERT_OK_AND_ASSIGN( + z3::expr z3_expr, + FormatP4RTValue(Z3Context(), kFieldName, kFieldType, ir_value, + /*bitwidth=*/48, &trasnlator)); ASSERT_EQ(Z3ValueStringToInt(z3_expr.to_string()), 0x01'02'03'04'05'06ULL); } @@ -79,9 +83,10 @@ TEST(FormatP4RTValue, WorksForHexString) { P4RuntimeTranslator trasnlator; ASSERT_OK_AND_ASSIGN(auto ir_value, gutil::ParseTextProto( R"pb(hex_str: "0xabcd")pb")); - ASSERT_OK_AND_ASSIGN(z3::expr z3_expr, - FormatP4RTValue(kFieldName, kFieldType, ir_value, - /*bitwidth=*/16, &trasnlator)); + ASSERT_OK_AND_ASSIGN( + z3::expr z3_expr, + FormatP4RTValue(Z3Context(), kFieldName, kFieldType, ir_value, + /*bitwidth=*/16, &trasnlator)); ASSERT_EQ(Z3ValueStringToInt(z3_expr.to_string()), 0xabcd); } @@ -89,7 +94,7 @@ TEST(FormatP4RTValue, FailsForStringWithNonZeroBitwidth) { P4RuntimeTranslator trasnlator; ASSERT_OK_AND_ASSIGN(auto ir_value, gutil::ParseTextProto( R"pb(str: "dummy_value")pb")); - ASSERT_THAT(FormatP4RTValue(kFieldName, kFieldType, ir_value, + ASSERT_THAT(FormatP4RTValue(Z3Context(), kFieldName, kFieldType, ir_value, /*bitwidth=*/16, &trasnlator), StatusIs(absl::StatusCode::kInvalidArgument)); } diff --git a/p4_symbolic/z3_util.cc b/p4_symbolic/z3_util.cc index a09186ca..1471a578 100644 --- a/p4_symbolic/z3_util.cc +++ b/p4_symbolic/z3_util.cc @@ -55,7 +55,8 @@ absl::StatusOr EvalZ3Int(const z3::expr& int_expr, return model.eval(int_expr, true).get_numeral_int(); } -absl::StatusOr HexStringToZ3Bitvector(const std::string& hex_string, +absl::StatusOr HexStringToZ3Bitvector(z3::context& context, + const std::string& hex_string, absl::optional bitwidth) { // TODO: Insert check to ensure this won't throw an exception. mpz_class integer = mpz_class(hex_string, /*base=*/0); @@ -63,7 +64,7 @@ absl::StatusOr HexStringToZ3Bitvector(const std::string& hex_string, if (!bitwidth.has_value()) { bitwidth = integer.get_str(/*base=*/2).size(); } - return Z3Context().bv_val(decimal.c_str(), *bitwidth); + return context.bv_val(decimal.c_str(), *bitwidth); } uint64_t Z3ValueStringToInt(const std::string& value) { diff --git a/p4_symbolic/z3_util.h b/p4_symbolic/z3_util.h index f516f81f..7bb52e7a 100644 --- a/p4_symbolic/z3_util.h +++ b/p4_symbolic/z3_util.h @@ -44,9 +44,10 @@ absl::StatusOr> EvalZ3Bitvector(const z3::expr& bv_expr, // -- Constructing Z3 expressions ---------------------------------------------- // Returns Z3 bitvector of the given `hex_string` value. -// If no bitwidth is +// If no bitwidth is given, the size of the bitvector is derived from +// `hex_string`. absl::StatusOr HexStringToZ3Bitvector( - const std::string& hex_string, + z3::context& context, const std::string& hex_string, absl::optional bitwidth = absl::nullopt); // -- Misc. -------------------------------------------------------------------- From 12ae2aac944dfbfa9038018517570051d30acf67 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Thu, 21 Nov 2024 18:31:15 +0000 Subject: [PATCH 10/12] [Dvaas]: Add tests to user-provided test vectors. (#745) Co-authored-by: kheradmandG Co-authored-by: kishanps --- dvaas/BUILD.bazel | 39 ++ .../user_provided_packet_test_vector_test.cc | 452 ++++++++++++++++++ 2 files changed, 491 insertions(+) create mode 100644 dvaas/user_provided_packet_test_vector_test.cc diff --git a/dvaas/BUILD.bazel b/dvaas/BUILD.bazel index 6fa44fd6..1e8cdd58 100644 --- a/dvaas/BUILD.bazel +++ b/dvaas/BUILD.bazel @@ -274,3 +274,42 @@ cc_library( "@com_google_absl//absl/types:span", ], ) + +# go/golden-test-with-coverage +cc_test( + name = "user_provided_packet_test_vector_test", + srcs = ["user_provided_packet_test_vector_test.cc"], + linkstatic = True, + deps = [ + ":test_vector", + ":test_vector_cc_proto", + ":user_provided_packet_test_vector", + "//gutil:collections", + "//gutil:proto", + "//gutil:status_matchers", + "//gutil:testing", + "//p4_pdpi/packetlib:packetlib_cc_proto", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "user_provided_packet_test_vector", + testonly = True, + srcs = ["user_provided_packet_test_vector.cc"], + hdrs = ["user_provided_packet_test_vector.h"], + deps = [ + ":test_vector", + ":test_vector_cc_proto", + "//gutil:proto", + "//gutil:status", + "//p4_pdpi/packetlib", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + ], +) diff --git a/dvaas/user_provided_packet_test_vector_test.cc b/dvaas/user_provided_packet_test_vector_test.cc new file mode 100644 index 00000000..4b45c797 --- /dev/null +++ b/dvaas/user_provided_packet_test_vector_test.cc @@ -0,0 +1,452 @@ +// Empowers users to specify custom packet test vectors that can be validated +// by DVaaS or Arriba. + +// 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 "dvaas/user_provided_packet_test_vector.h" + +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "dvaas/test_vector.h" +#include "dvaas/test_vector.pb.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "gutil/collections.h" +#include "gutil/proto.h" +#include "gutil/status_matchers.h" +#include "gutil/testing.h" +#include "p4_pdpi/packetlib/packetlib.pb.h" + +namespace dvaas { +namespace { + +using ::gutil::IsOk; +using ::gutil::PrintTextProto; +using ::testing::Not; + +struct TestCase { + std::string description; + std::vector vectors; +}; + +void RunTestCase(const TestCase& test_case) { + // Print header. + std::cout << std::string(80, '=') << "\n" + << "InternalizeUserProvidedTestVectors Test: " + << test_case.description << "\n" + << std::string(80, '=') << "\n"; + + // Print input. + std::cout << "-- Input ---------------------------------------------------\n"; + for (int i = 0; i < test_case.vectors.size(); ++i) { + std::cout << "-- Input Packet Test Vector #" << (i + 1) << " --\n" + << PrintTextProto(test_case.vectors[i]); + } + std::cout << "\n"; + + // Print output. + std::cout << "-- Output --------------------------------------------------\n"; + absl::StatusOr output = + LegitimizeUserProvidedTestVectors(test_case.vectors); + if (!output.ok()) { + // Print error without stack trace, for golden testing. + std::cout << "ERROR: " + << output.status().ToString( + absl::StatusToStringMode::kWithNoExtraData) + << "\n\n"; + return; + } + + // Print output vectors in order of input vectors. + for (int i = 0; i < test_case.vectors.size(); ++i) { + const PacketTestVector& original_vector = test_case.vectors[i]; + ASSERT_OK_AND_ASSIGN( + int tag, + ExtractTestPacketTag(original_vector.input().packet().parsed())); + ASSERT_OK_AND_ASSIGN(const PacketTestVector* internalized_vector, + gutil::FindPtrOrStatus(*output, tag)); + ASSERT_OK_AND_ASSIGN( + std::string diff, + gutil::ProtoDiff(original_vector, *internalized_vector)); + std::cout << "-- Internalized Packet Test Vector #" << (i + 1) << " --\n" + << "test packet ID extracted from payload: " << tag << "\n" + << "diff of internalized vector vs input vector:\n" + << diff << "\n"; + } + std::cout << "\n"; +} + +std::vector GetPositiveTestCases() { + std::vector test_cases; + + test_cases.emplace_back() = TestCase{ + .description = "empty list of vectors", + .vectors = {}, + }; + + test_cases.emplace_back() = TestCase{ + .description = "single Ethernet packet expected to be dropped", + .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 {} + )pb"), + }, + }; + + test_cases.emplace_back() = TestCase{ + .description = "single IPv6 packet expected to be forwarded", + .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: "0x86dd" + } + } + headers { + ipv6_header { + version: "0x6" + dscp: "0x1b" + ecn: "0x1" + flow_label: "0x12345" + next_header: "0xfd" + hop_limit: "0xf2" + ipv6_source: "2001::2" + ipv6_destination: "2001::4" + } + } + payload: "test packet #42" + } + } + } + acceptable_outputs { + packets { + port: "8" + parsed { + headers { + ethernet_header { + ethernet_destination: "1:2:3:4:5:6" + ethernet_source: "6:5:4:3:2:1" + ethertype: "0x86dd" + } + } + headers { + ipv6_header { + version: "0x6" + dscp: "0x1b" + ecn: "0x1" + flow_label: "0x12345" + next_header: "0xfd" + hop_limit: "0xf1" + ipv6_source: "2001::2" + ipv6_destination: "2001::4" + } + } + payload: "test packet #42" + } + } + } + )pb"), + }, + }; + + test_cases.emplace_back() = TestCase{ + .description = "several packets with different IDs", + .vectors = + { + gutil::ParseProtoOrDie(R"pb( + input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "42:42:42:42:42:42" + ethernet_source: "42:42:42:42:42:42" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } + } + acceptable_outputs {} + )pb"), + gutil::ParseProtoOrDie(R"pb( + input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "5:5:5:5:5:5" + ethernet_source: "5:5:5:5:5:5" + ethertype: "0x000e" + } + } + payload: "test packet #5" + } + } + } + acceptable_outputs {} + )pb"), + }, + }; + + return test_cases; +} // namespace + +std::vector GetNegativeTestCases() { + std::vector test_cases; + + test_cases.emplace_back() = TestCase{ + .description = "input type != DATAPLANE", + .vectors = + { + gutil::ParseProtoOrDie(R"pb( + input { + type: SUBMIT_TO_INGRESS + 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 {} + )pb"), + }, + }; + + test_cases.emplace_back() = TestCase{ + .description = "missing expectation", + .vectors = + { + gutil::ParseProtoOrDie(R"pb( + input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "42:42:42:42:42:42" + ethernet_source: "42:42:42:42:42:42" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } + } + )pb"), + }, + }; + + test_cases.emplace_back() = TestCase{ + .description = "missing test packet ID", + .vectors = + { + gutil::ParseProtoOrDie(R"pb( + input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "42:42:42:42:42:42" + ethernet_source: "42:42:42:42:42:42" + ethertype: "0x0002" + } + } + payload: "Hi" + } + } + } + )pb"), + }, + }; + + test_cases.emplace_back() = TestCase{ + .description = "inconsistent test packet ID in input vs output", + .vectors = + { + gutil::ParseProtoOrDie(R"pb( + input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "42:42:42:42:42:42" + ethernet_source: "42:42:42:42:42:42" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } + } + acceptable_outputs { + packets { + port: "8" + 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 = "several packets with same test packet ID", + .vectors = + { + gutil::ParseProtoOrDie(R"pb( + input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "42:42:42:42:42:42" + ethernet_source: "42:42:42:42:42:42" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } + } + acceptable_outputs {} + )pb"), + gutil::ParseProtoOrDie(R"pb( + input { + type: DATAPLANE + packet { + port: "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" + } + } + } + acceptable_outputs {} + )pb"), + }, + }; + + test_cases.emplace_back() = TestCase{ + .description = "invalid input packet", + .vectors = + { + gutil::ParseProtoOrDie(R"pb( + input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "5:5:5:5:5:5" + ethernet_source: "5:5:5:5:5:5" + ethertype: "0x86dd" # IPv6. + } + } + # Missing IPv6 header. + payload: "test packet #42" + } + } + } + acceptable_outputs {} + )pb"), + }, + }; + + return test_cases; +} + +TEST(InternalizeUserProvidedTestVectorsTest, PositiveTestCases) { + for (TestCase test_case : GetPositiveTestCases()) { + EXPECT_THAT(LegitimizeUserProvidedTestVectors(test_case.vectors), IsOk()); + RunTestCase(test_case); + } +} + +TEST(InternalizeUserProvidedTestVectorsTest, NegativeTestCases) { + for (TestCase test_case : GetNegativeTestCases()) { + EXPECT_THAT(LegitimizeUserProvidedTestVectors(test_case.vectors), + Not(IsOk())); + RunTestCase(test_case); + } +} + +} // namespace +} // namespace dvaas From 62d598ae48fa4006a82b6b9d30d28743a3dcca6b Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Thu, 21 Nov 2024 18:34:50 +0000 Subject: [PATCH 11/12] [P4_Symbolic] Extend BMv2 proto to support parsers. (#746) Co-authored-by: kishanps Co-authored-by: jonathan-dilorenzo --- p4_symbolic/bmv2/bmv2.proto | 100 ++++++++++++++++--- p4_symbolic/bmv2/expected/basic.pb.txt | 45 +++++++++ p4_symbolic/bmv2/expected/set_invalid.pb.txt | 17 ++++ p4_symbolic/bmv2/expected/table_hit_1.pb.txt | 17 ++++ p4_symbolic/bmv2/expected/table_hit_2.pb.txt | 17 ++++ p4_symbolic/bmv2/test.cc | 4 +- 6 files changed, 183 insertions(+), 17 deletions(-) diff --git a/p4_symbolic/bmv2/bmv2.proto b/p4_symbolic/bmv2/bmv2.proto index 7d0352c0..1062fc94 100644 --- a/p4_symbolic/bmv2/bmv2.proto +++ b/p4_symbolic/bmv2/bmv2.proto @@ -13,7 +13,9 @@ // limitations under the License. // This file contains protobuf definitions for parsing bmv2 json -// files (outputs of p4c). +// files (outputs of p4c). The JSON format reference can be found at +// https://github.com/p4lang/behavioral-model/blob/main/docs/JSON_format.md +// or third_party/p4lang_behavioral_model/docs/JSON_format.md // Some attributes here are intentionally un-constrained (e.g. // struct). This allows parsing of radically different possibilities // or types of constructs that may appear interchangably in that @@ -130,34 +132,104 @@ message Parser { int32 id = 2; // The name of the start state of the parser. string init_state = 3; - // A list of all parser states. - repeated ParserState parse_states = 4; + // A list of all parse states. + repeated ParseState parse_states = 4; } -// Defines a parser state. -message ParserState { - // The user defined name of the state (not qualified). +// Defines a parse state. +// The body of a parse state consists of a series of parser statements (which +// p4c compiles into a list of parser operations), such as assignments, +// variable/constant declarations, conditionals, etc., followed by a transition +// statement. +message ParseState { + // The user-defined name of the state (not qualified). // It does not include the name of the parser. string name = 1; - // Auto generated ID unique among all parser states including those + // Auto-generated ID unique among all parse states including those // belonging to a different parser. int32 id = 2; - // All the possible transitions from this state to different states. + // Ordered list of operations performed in the current parse state. + repeated ParserOperation parser_ops = 3; + // All the possible transitions from this state to other states. repeated ParserTransition transitions = 4; + // Optionally the transition statement contains a "select" expression which + // acts similarly to the switch-case expression in C. The condition of the + // select expression is a list of expressions, which usually contains only one + // element in typical cases. This transition_key represents exactly the + // condition of the select expression as a list of key fields. It will be an + // empty list if the parse state contains only one default transition. + // Reference: https://p4.org/p4-spec/docs/P4-16-v-1.2.3.html#sec-select + repeated ParserTransitionKeyField transition_key = 5; } -// Defines a transition between two parser states. +// Defines a parser operation corresponding to a single statement in a parse +// state. For instance, a parser operation can be a "set" operation +// (corresponding to an assignment statement), an "extract" operation +// (corresponding to an "extract" method call), or other primitives supported by +// the target implementation. Reference: +// https://github.com/p4lang/behavioral-model/blob/main/docs/JSON_format.md#parser-operations. +message ParserOperation { + // Parser operation types. + enum ParserOpType { + unspecified = 0; + // Performs extraction for a fixed-width header. + extract = 1; + // Performs extraction for a variable-width header. + extract_VL = 2; + // Sets a field with the given value. + set = 3; + // Verifies if the given condition holds true. If not, sets + // standard_metadata.parser_error with the specified error code. + verify = 4; + // Shifts the current read position by the given number of bytes. + // Deprecated in favor of advance. + shift = 5; + // Advances the current read position by the given number of bits. + // BMv2 currently requires the number of bits to be a multiple of 8. + advance = 6; + // An invocation of an extern method (must be supported by the target). + primitive = 7; + } + + // The type of parser operation. + ParserOpType op = 1; + // Parameters of the operation. + // The semantics of parameters depend on the type of operation. + // https://github.com/p4lang/behavioral-model/blob/main/docs/JSON_format.md#parser-operations + repeated google.protobuf.Struct parameters = 2; +} + +// One field of a transition key. +message ParserTransitionKeyField { + enum ParserTransKeyFieldType { + unspecified = 0; + // Specifies a normal header field. + field = 1; + // Specifies a header field of the last extracted instance in a stack. + stack_field = 2; + // Specifies a header field of the last extracted instance in a union stack. + union_stack_field = 3; + // Evaluates to a set of bits from the input packet without advancing the + // current read position. + lookahead = 4; + } + + ParserTransKeyFieldType type = 1; + google.protobuf.ListValue value = 2; +} + +// Defines a transition between two parse states. message ParserTransition { - // The value of the condition/match dictating whether - // this transition is applied or not. - // A hexstring or a parser value set depending on the type. + // The value of the condition/match dictating whether this transition is + // applied or not. It can be an empty string (a default transition), a + // hexstring, or a parser value set depending on the type. string value = 1; - // The type of the value above, usually a hex string. + // The type of the transition value, usually a hex string. ParserTransitionType type = 2; // Any mask applied by the transition, if no mask is applied, // protobuf will parse this as "". string mask = 3; - // The name of the state this transition points to. + // The name of the next parse state, or "" if this is the last state. string next_state = 4; } diff --git a/p4_symbolic/bmv2/expected/basic.pb.txt b/p4_symbolic/bmv2/expected/basic.pb.txt index a70f11ba..78bfd67d 100644 --- a/p4_symbolic/bmv2/expected/basic.pb.txt +++ b/p4_symbolic/bmv2/expected/basic.pb.txt @@ -391,6 +391,23 @@ parsers { init_state: "start" parse_states { name: "start" + parser_ops { + op: extract + parameters { + fields { + key: "type" + value { + string_value: "regular" + } + } + fields { + key: "value" + value { + string_value: "ethernet" + } + } + } + } transitions { value: "0x0800" type: hexstr @@ -398,10 +415,38 @@ parsers { } transitions { } + transition_key { + type: field + value { + values { + string_value: "ethernet" + } + values { + string_value: "etherType" + } + } + } } parse_states { name: "parse_ipv4" id: 1 + parser_ops { + op: extract + parameters { + fields { + key: "type" + value { + string_value: "regular" + } + } + fields { + key: "value" + value { + string_value: "ipv4" + } + } + } + } transitions { } } diff --git a/p4_symbolic/bmv2/expected/set_invalid.pb.txt b/p4_symbolic/bmv2/expected/set_invalid.pb.txt index 3c4a7ca8..36110f33 100644 --- a/p4_symbolic/bmv2/expected/set_invalid.pb.txt +++ b/p4_symbolic/bmv2/expected/set_invalid.pb.txt @@ -228,6 +228,23 @@ parsers { init_state: "start" parse_states { name: "start" + parser_ops { + op: extract + parameters { + fields { + key: "type" + value { + string_value: "regular" + } + } + fields { + key: "value" + value { + string_value: "h" + } + } + } + } transitions { } } diff --git a/p4_symbolic/bmv2/expected/table_hit_1.pb.txt b/p4_symbolic/bmv2/expected/table_hit_1.pb.txt index 508e64b5..13e64a2b 100644 --- a/p4_symbolic/bmv2/expected/table_hit_1.pb.txt +++ b/p4_symbolic/bmv2/expected/table_hit_1.pb.txt @@ -250,6 +250,23 @@ parsers { init_state: "start" parse_states { name: "start" + parser_ops { + op: extract + parameters { + fields { + key: "type" + value { + string_value: "regular" + } + } + fields { + key: "value" + value { + string_value: "ethernet" + } + } + } + } transitions { } } diff --git a/p4_symbolic/bmv2/expected/table_hit_2.pb.txt b/p4_symbolic/bmv2/expected/table_hit_2.pb.txt index ba3f44aa..e0b7a17b 100644 --- a/p4_symbolic/bmv2/expected/table_hit_2.pb.txt +++ b/p4_symbolic/bmv2/expected/table_hit_2.pb.txt @@ -261,6 +261,23 @@ parsers { init_state: "start" parse_states { name: "start" + parser_ops { + op: extract + parameters { + fields { + key: "type" + value { + string_value: "regular" + } + } + fields { + key: "value" + value { + string_value: "h1" + } + } + } + } transitions { } } diff --git a/p4_symbolic/bmv2/test.cc b/p4_symbolic/bmv2/test.cc index 2764747a..a2b70d89 100644 --- a/p4_symbolic/bmv2/test.cc +++ b/p4_symbolic/bmv2/test.cc @@ -19,7 +19,6 @@ // objects using protobuf text format and json format to two output files. // The output files paths are provided as command line flags. -#include #include #include @@ -28,7 +27,6 @@ #include "absl/flags/usage.h" #include "absl/status/status.h" #include "absl/strings/str_format.h" -#include "google/protobuf/text_format.h" #include "google/protobuf/util/json_util.h" #include "gutil/io.h" #include "gutil/proto.h" @@ -82,7 +80,7 @@ int main(int argc, char *argv[]) { // Verify link and compile versions are the same. // GOOGLE_PROTOBUF_VERIFY_VERSION; - // Command line arugments and help message. + // Command line arguments and help message. absl::SetProgramUsageMessage( absl::StrFormat("usage: %s %s", argv[0], "--bmv2=path/to/bmv2.json " From b1d2d69425f1a1e4ab5347d73c1716689f23f1a9 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Thu, 21 Nov 2024 18:39:10 +0000 Subject: [PATCH 12/12] [Dvaas]: Adding expected tests output to user-provided test vectors. (#747) Co-authored-by: kheradmandG Co-authored-by: kishanps --- dvaas/BUILD.bazel | 26 +- ..._provided_packet_test_vector_test.expected | 533 ++++++++++++++++++ 2 files changed, 543 insertions(+), 16 deletions(-) create mode 100644 dvaas/user_provided_packet_test_vector_test.expected diff --git a/dvaas/BUILD.bazel b/dvaas/BUILD.bazel index 1e8cdd58..1852460b 100644 --- a/dvaas/BUILD.bazel +++ b/dvaas/BUILD.bazel @@ -296,20 +296,14 @@ cc_test( ], ) -cc_library( - name = "user_provided_packet_test_vector", - testonly = True, - srcs = ["user_provided_packet_test_vector.cc"], - hdrs = ["user_provided_packet_test_vector.h"], - deps = [ - ":test_vector", - ":test_vector_cc_proto", - "//gutil:proto", - "//gutil:status", - "//p4_pdpi/packetlib", - "@com_google_absl//absl/status", - "@com_google_absl//absl/status:statusor", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/types:span", - ], +cmd_diff_test( + name = "user_provided_packet_test_vector_diff_test", + actual_cmd = " | ".join([ + "$(execpath :user_provided_packet_test_vector_test)", + # Strip unnecessary lines for golden testing. + "sed '1,/^\\[ RUN/d'", # Strip everything up to a line beginning with '[ RUN'. + "sed '/^\\[/d'", # Strip every line beginning with '['. + ]), + expected = "user_provided_packet_test_vector_test.expected", + tools = [":user_provided_packet_test_vector_test"], ) diff --git a/dvaas/user_provided_packet_test_vector_test.expected b/dvaas/user_provided_packet_test_vector_test.expected new file mode 100644 index 00000000..47929a7d --- /dev/null +++ b/dvaas/user_provided_packet_test_vector_test.expected @@ -0,0 +1,533 @@ +================================================================================ +InternalizeUserProvidedTestVectors Test: empty list of vectors +================================================================================ +-- Input --------------------------------------------------- + +-- Output -------------------------------------------------- + +================================================================================ +InternalizeUserProvidedTestVectors Test: single Ethernet packet expected to be dropped +================================================================================ +-- 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 { +} + +-- 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" + + +================================================================================ +InternalizeUserProvidedTestVectors Test: single IPv6 packet expected to be forwarded +================================================================================ +-- 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: "0x86dd" + } + } + headers { + ipv6_header { + version: "0x6" + dscp: "0x1b" + ecn: "0x1" + flow_label: "0x12345" + next_header: "0xfd" + hop_limit: "0xf2" + ipv6_source: "2001::2" + ipv6_destination: "2001::4" + } + } + payload: "test packet #42" + } + } +} +acceptable_outputs { + packets { + port: "8" + parsed { + headers { + ethernet_header { + ethernet_destination: "1:2:3:4:5:6" + ethernet_source: "6:5:4:3:2:1" + ethertype: "0x86dd" + } + } + headers { + ipv6_header { + version: "0x6" + dscp: "0x1b" + ecn: "0x1" + flow_label: "0x12345" + next_header: "0xfd" + hop_limit: "0xf1" + ipv6_source: "2001::2" + ipv6_destination: "2001::4" + } + } + 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.parsed.headers[1].ipv6_header.payload_length: "0x000f" +added: input.packet.hex: "ffeeddccbbaa55443322110086dd66d12345000ffdf2200100000000000000000000000000022001000000000000000000000000000474657374207061636b657420233432" +added: acceptable_outputs[0].packets[0].parsed.headers[1].ipv6_header.payload_length: "0x000f" +added: acceptable_outputs[0].packets[0].hex: "01020304050606050403020186dd66d12345000ffdf1200100000000000000000000000000022001000000000000000000000000000474657374207061636b657420233432" + + +================================================================================ +InternalizeUserProvidedTestVectors Test: several packets with different IDs +================================================================================ +-- Input --------------------------------------------------- +-- Input Packet Test Vector #1 -- +input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "42:42:42:42:42:42" + ethernet_source: "42:42:42:42:42:42" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } +} +acceptable_outputs { +} +-- Input Packet Test Vector #2 -- +input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "5:5:5:5:5:5" + ethernet_source: "5:5:5:5:5:5" + ethertype: "0x000e" + } + } + payload: "test packet #5" + } + } +} +acceptable_outputs { +} + +-- Output -------------------------------------------------- +-- Internalized Packet Test Vector #1 -- +test packet ID extracted from payload: 42 +diff of internalized vector vs input vector: +added: input.packet.hex: "424242424242424242424242000f74657374207061636b657420233432" + +-- Internalized Packet Test Vector #2 -- +test packet ID extracted from payload: 5 +diff of internalized vector vs input vector: +added: input.packet.hex: "050505050505050505050505000e74657374207061636b6574202335" + + +================================================================================ +InternalizeUserProvidedTestVectors Test: input type != DATAPLANE +================================================================================ +-- Input --------------------------------------------------- +-- Input Packet Test Vector #1 -- +input { + type: SUBMIT_TO_INGRESS + 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 { +} + +-- Output -------------------------------------------------- +ERROR: UNIMPLEMENTED: problem in user-provided packet test vector: only supported input type is DATAPLANE; found SUBMIT_TO_INGRESS +Dumping offending test vector: +input { + type: SUBMIT_TO_INGRESS + 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 { +} + + +================================================================================ +InternalizeUserProvidedTestVectors Test: missing expectation +================================================================================ +-- Input --------------------------------------------------- +-- Input Packet Test Vector #1 -- +input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "42:42:42:42:42:42" + ethernet_source: "42:42:42:42:42:42" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } +} + +-- Output -------------------------------------------------- +ERROR: INVALID_ARGUMENT: problem in user-provided packet test vector: must specify at least 1 acceptable output, but 0 were found +Dumping offending test vector: +input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "42:42:42:42:42:42" + ethernet_source: "42:42:42:42:42:42" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } +} + + +================================================================================ +InternalizeUserProvidedTestVectors Test: missing test packet ID +================================================================================ +-- Input --------------------------------------------------- +-- Input Packet Test Vector #1 -- +input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "42:42:42:42:42:42" + ethernet_source: "42:42:42:42:42:42" + ethertype: "0x0002" + } + } + payload: "Hi" + } + } +} + +-- Output -------------------------------------------------- +ERROR: INVALID_ARGUMENT: problem in user-provided packet test vector: invalid input packet: test packets must contain a tag of the form 'test packet #([0-9]+)' in their payload, but the given packet with payload 'Hi' does not: +headers { + ethernet_header { + ethernet_destination: "42:42:42:42:42:42" + ethernet_source: "42:42:42:42:42:42" + ethertype: "0x0002" + } +} +payload: "Hi" + +Dumping offending test vector: +input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "42:42:42:42:42:42" + ethernet_source: "42:42:42:42:42:42" + ethertype: "0x0002" + } + } + payload: "Hi" + } + } +} + + +================================================================================ +InternalizeUserProvidedTestVectors Test: inconsistent test packet ID in input vs output +================================================================================ +-- Input --------------------------------------------------- +-- Input Packet Test Vector #1 -- +input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "42:42:42:42:42:42" + ethernet_source: "42:42:42:42:42:42" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } +} +acceptable_outputs { + packets { + port: "8" + 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 tag vs output packet tag for output packet #1: 42 vs 24 +Dumping offending test vector: +input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "42:42:42:42:42:42" + ethernet_source: "42:42:42:42:42:42" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } +} +acceptable_outputs { + packets { + port: "8" + 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: several packets with same test packet ID +================================================================================ +-- Input --------------------------------------------------- +-- Input Packet Test Vector #1 -- +input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "42:42:42:42:42:42" + ethernet_source: "42:42:42:42:42:42" + ethertype: "0x000f" + } + } + payload: "test packet #42" + } + } +} +acceptable_outputs { +} +-- Input Packet Test Vector #2 -- +input { + type: DATAPLANE + packet { + port: "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" + } + } +} +acceptable_outputs { +} + +-- Output -------------------------------------------------- +ERROR: INVALID_ARGUMENT: problem in user-provided packet test vector: user-provided packet test vectors must be tagged with unique IDs in their payload, but found multiple test vectors with ID 42. Dumping offending test vectors: + + + +Dumping offending test vector: +input { + type: DATAPLANE + packet { + port: "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" + } + } +} +acceptable_outputs { +} + + +================================================================================ +InternalizeUserProvidedTestVectors Test: invalid input packet +================================================================================ +-- Input --------------------------------------------------- +-- Input Packet Test Vector #1 -- +input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "5:5:5:5:5:5" + ethernet_source: "5:5:5:5:5:5" + ethertype: "0x86dd" + } + } + payload: "test packet #42" + } + } +} +acceptable_outputs { +} + +-- Output -------------------------------------------------- +ERROR: INVALID_ARGUMENT: problem in user-provided packet test vector: invalid input packet: Packet invalid for the following reasons: +- in EthernetHeader headers[0]: expected at least 46 bytes of Ethernet payload, but got only 15 +- headers[1]: header missing - expected Ipv6Header +Dumping offending test vector: +input { + type: DATAPLANE + packet { + port: "1" + parsed { + headers { + ethernet_header { + ethernet_destination: "5:5:5:5:5:5" + ethernet_source: "5:5:5:5:5:5" + ethertype: "0x86dd" + } + } + payload: "test packet #42" + } + } +} +acceptable_outputs { +}