From b4a0a73ada9df73888e259afe5e8b3495e0b5848 Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Wed, 22 Jan 2025 11:59:15 +0530 Subject: [PATCH] [Thinkit] Transition Ouroboros to take a DataplaneValidationParams as a parameter. (#955) Co-authored-by: kishanps --- tests/forwarding/BUILD.bazel | 58 +++++++++++++++++ tests/forwarding/ouroboros_test.cc | 90 +++++++++++++------------ tests/forwarding/ouroboros_test.h | 101 +++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 46 deletions(-) create mode 100644 tests/forwarding/ouroboros_test.h diff --git a/tests/forwarding/BUILD.bazel b/tests/forwarding/BUILD.bazel index 82e15ba60..9233f7c9d 100644 --- a/tests/forwarding/BUILD.bazel +++ b/tests/forwarding/BUILD.bazel @@ -155,6 +155,64 @@ cc_library( ], ) +cc_library( + name = "ouroboros_test", + testonly = True, + srcs = ["ouroboros_test.cc"], + hdrs = ["ouroboros_test.h"], + linkstatic = True, + deps = [ + ":util", + "//dvaas:dataplane_validation", + "//dvaas:packet_injection", + "//dvaas:switch_api", + "//dvaas:test_vector_cc_proto", + "//gutil:proto", + "//gutil:status", + "//gutil:status_matchers", + "//lib/gnmi:gnmi_helper", + "//lib/gnmi:openconfig_cc_proto", + "//lib/p4rt:p4rt_port", + "//p4_fuzzer:annotation_util", + "//p4_fuzzer:fuzzer_cc_proto", + "//p4_fuzzer:fuzzer_config", + "//p4_fuzzer:mutation_and_fuzz_util", + "//p4_fuzzer:switch_state", + "//p4_pdpi:ir", + "//p4_pdpi:ir_cc_proto", + "//p4_pdpi:p4_runtime_session", + "//p4_pdpi:p4_runtime_session_extras", + "//p4_pdpi:pd", + "//p4_pdpi/packetlib", + "//p4_pdpi/packetlib:packetlib_cc_proto", + "//p4_pdpi/string_encodings:decimal_string", + "//p4_symbolic/packet_synthesizer:packet_synthesizer_cc_proto", + "//sai_p4/instantiations/google:sai_pd_cc_proto", + "//tests/lib:switch_test_setup_helpers", + "//thinkit:mirror_testbed", + "//thinkit:mirror_testbed_fixture", + "//thinkit:test_environment", + "@com_github_gnmi//proto/gnmi:gnmi_cc_grpc_proto", + "@com_github_google_glog//:glog", + "@com_github_p4lang_p4runtime//:p4info_cc_proto", + "@com_github_p4lang_p4runtime//:p4runtime_cc_proto", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/random", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:span", + "@com_google_googletest//:gtest", + "@com_google_protobuf//:protobuf", + ], + alwayslink = True, +) + cc_library( name = "match_action_coverage_test", testonly = True, diff --git a/tests/forwarding/ouroboros_test.cc b/tests/forwarding/ouroboros_test.cc index 24a7cb004..624b9baf6 100644 --- a/tests/forwarding/ouroboros_test.cc +++ b/tests/forwarding/ouroboros_test.cc @@ -37,10 +37,11 @@ #include "absl/time/time.h" #include "absl/types/span.h" #include "dvaas/dataplane_validation.h" +#include "dvaas/packet_injection.h" +#include "dvaas/switch_api.h" +#include "dvaas/test_vector.pb.h" #include "glog/logging.h" -#include "gmock/gmock.h" #include "google/protobuf/descriptor.h" -#include "gtest/gtest.h" #include "gutil/proto.h" #include "gutil/status.h" #include "gutil/status_matchers.h" @@ -64,17 +65,17 @@ #include "p4_pdpi/string_encodings/decimal_string.h" #include "proto/gnmi/gnmi.grpc.pb.h" #include "sai_p4/instantiations/google/sai_pd.pb.h" -#include "tests/forwarding/test_vector.h" -#include "tests/forwarding/test_vector.pb.h" #include "tests/forwarding/util.h" #include "tests/lib/switch_test_setup_helpers.h" #include "thinkit/mirror_testbed.h" #include "thinkit/test_environment.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" namespace pins_test { namespace { -using ::dvaas::Switch; +using ::dvaas::SwitchApi; using ::p4_fuzzer::FuzzerConfig; // -- Auxiliary functions ------------------------------------------------------ @@ -86,8 +87,8 @@ std::string CreateHeader(absl::string_view title) { // Reads table entries on `sut` and outputs them into an artifact given by // `artifact_name`. -absl::Status OutputTableEntriesToArtifact(Switch& sut, - thinkit::TestEnvironment& environment, +absl::Status OutputTableEntriesToArtifact(SwitchApi &sut, + thinkit::TestEnvironment &environment, absl::string_view artifact_name, int iteration) { RETURN_IF_ERROR(environment.AppendToTestArtifact( @@ -102,19 +103,17 @@ absl::Status OutputTableEntriesToArtifact(Switch& sut, // Augments the given FuzzerConfig to fit the `sut` and Ouroboros Test by // replacing the IrP4Info and available ports with those read from the switch // and setting mutation probability to 0. -absl::Status AugmentFuzzerConfig(Switch& sut, FuzzerConfig& fuzzer_config) { +absl::Status AugmentFuzzerConfig(SwitchApi &sut, FuzzerConfig &fuzzer_config) { ASSIGN_OR_RETURN(p4::v1::GetForwardingPipelineConfigResponse response, pdpi::GetForwardingPipelineConfig(sut.p4rt.get())); - ASSIGN_OR_RETURN(pdpi::IrP4Info ir_info, - pdpi::CreateIrP4Info(response.config().p4info())); - fuzzer_config.info = ir_info; - ASSIGN_OR_RETURN(fuzzer_config.ports, + RETURN_IF_ERROR(fuzzer_config.SetP4Info(response.config().p4info())); + ASSIGN_OR_RETURN(auto port_ids, pins_test::GetMatchingP4rtPortIds( *sut.gnmi, pins_test::IsEnabledEthernetInterface)); - fuzzer_config.mutate_update_probability = 0.0; - // Our validator, BMv2, does not support empty action profile groups. - fuzzer_config.no_empty_action_profile_groups = true; + fuzzer_config.SetPorts(port_ids); + fuzzer_config.SetMutateUpdateProbability(0.0); + fuzzer_config.SetNoEmptyActionProfileGroups(true); return absl::OkStatus(); } @@ -122,12 +121,13 @@ absl::Status AugmentFuzzerConfig(Switch& sut, FuzzerConfig& fuzzer_config) { // `gnmi_config` and `p4info` (if given). Mirrors the SUTs interfaces on the // control switch and waits for them to be Up. // Returns a configured (SUT, Control Switch) pair. -absl::StatusOr> ConfigureMirrorTestbed( - thinkit::MirrorTestbed& testbed, std::optional gnmi_config, - std::optional p4info) { +absl::StatusOr> +ConfigureMirrorTestbed(thinkit::MirrorTestbed &testbed, + std::optional gnmi_config, + std::optional p4info) { // Configure both switches and set up gNMI and P4Runtime sessions to them. - Switch sut; - Switch control_switch; + SwitchApi sut; + SwitchApi control_switch; ASSIGN_OR_RETURN(sut.gnmi, testbed.Sut().CreateGnmiStub()); ASSIGN_OR_RETURN(control_switch.gnmi, testbed.ControlSwitch().CreateGnmiStub()); @@ -155,11 +155,11 @@ absl::StatusOr> ConfigureMirrorTestbed( // Generates updates to switch state using the P4-Fuzzer and sends them to the // switch. -absl::Status FuzzSwitchState(absl::BitGen& gen, Switch& sut, - thinkit::TestEnvironment& environment, - int iteration, const FuzzerConfig& fuzzer_config, +absl::Status FuzzSwitchState(absl::BitGen &gen, SwitchApi &sut, + thinkit::TestEnvironment &environment, + int iteration, const FuzzerConfig &fuzzer_config, int min_num_updates, - p4_fuzzer::SwitchState& state) { + p4_fuzzer::SwitchState &state) { int num_updates = 0; int num_fuzzing_cycles = 0; while (num_updates < min_num_updates) { @@ -222,7 +222,7 @@ TEST_P( pins_test::GetInterfacesAsProto(*control_gnmi_stub, gnmi::GetRequest::CONFIG)); - Switch sut, control_switch; + SwitchApi sut, control_switch; ASSERT_OK_AND_ASSIGN(std::tie(sut, control_switch), ConfigureMirrorTestbed(testbed, GetParam().gnmi_config, GetParam().config.p4info())); @@ -238,7 +238,7 @@ TEST_P( FuzzerConfig fuzzer_config = GetParam().fuzzer_config; ASSERT_OK(AugmentFuzzerConfig(sut, fuzzer_config)); - p4_fuzzer::SwitchState fuzzer_switch_state(fuzzer_config.info); + p4_fuzzer::SwitchState fuzzer_switch_state(fuzzer_config.GetIrP4Info()); absl::BitGen gen; @@ -264,26 +264,24 @@ TEST_P( sut, environment, /*artifact_name=*/"ouroboros_table_entries.txt", iteration)); - ASSERT_OK_AND_ASSIGN( - dvaas::ValidationResult validation_result_unused, - GetParam().validator->ValidateDataplane( - sut, control_switch, /*params=*/ - dvaas::DataplaneValidationParams{ - .ignored_fields_for_validation = - GetParam().ignored_fields_for_validation, - .ignored_metadata_for_validation = - GetParam().ignored_packetin_metadata_for_validation, - .artifact_prefix = "ouroboros", - .get_artifact_header = - [=]() { - return CreateHeader( - absl::StrCat("Iteration ", iteration)); - }, - .max_packets_to_send_per_second = - GetParam().max_packets_to_send_per_second, - })); - // Mark that the validation result is currently unused. - (void)validation_result_unused; + // Configure `get_artifact_header` for current iteration. + dvaas::DataplaneValidationParams dvaas_params = GetParam().dvaas_params; + dvaas_params.get_artifact_header = [=]() { + std::string iteration_header = + CreateHeader(absl::StrCat("Iteration ", iteration)); + if (dvaas_params.get_artifact_header.has_value()) { + return absl::StrCat(iteration_header, + (*dvaas_params.get_artifact_header)()); + } else { + return iteration_header; + } + }; + + ASSERT_OK_AND_ASSIGN(dvaas::ValidationResult validation_result, + GetParam().validator->ValidateDataplane( + sut, control_switch, dvaas_params)); + validation_result.LogStatistics(); + ASSERT_OK(validation_result.HasSuccessRateOfAtLeast(1.0)); last_iteration_time = absl::Now() - iteration_start_time; } diff --git a/tests/forwarding/ouroboros_test.h b/tests/forwarding/ouroboros_test.h new file mode 100644 index 000000000..d017d9e61 --- /dev/null +++ b/tests/forwarding/ouroboros_test.h @@ -0,0 +1,101 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PINS_TESTS_FORWARDING_OUROBOROS_TEST_H_ +#define PINS_TESTS_FORWARDING_OUROBOROS_TEST_H_ + +#include +#include +#include +#include +#include +#include + +#include "absl/container/btree_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "absl/types/span.h" +#include "dvaas/dataplane_validation.h" +#include "dvaas/test_vector.pb.h" +#include "lib/p4rt/p4rt_port.h" +#include "p4/config/v1/p4info.pb.h" +#include "p4/v1/p4runtime.pb.h" +#include "p4_fuzzer/fuzzer_config.h" +#include "p4_pdpi/ir.pb.h" +#include "p4_pdpi/packetlib/packetlib.pb.h" +#include "p4_symbolic/packet_synthesizer/packet_synthesizer.pb.h" +#include "thinkit/mirror_testbed_fixture.h" +#include "gtest/gtest.h" + +namespace pins_test { + +struct OuroborosTestParams { + // -- Basic Settings --------------------------------------------------------- + std::shared_ptr mirror_testbed; + p4::v1::ForwardingPipelineConfig config; + + // A set of entries that will be installed on the SUT before initiating the + // main loop of the test. + pdpi::IrTableEntries initial_sut_table_entries; + + // Target time for the test to run. This should not include testbed setup and + // teardown. + absl::Duration target_test_time = absl::Minutes(55); + // Maximum number of iterations of fuzzing and dataplane testing. The actual + // number of executed iterations will depend on the time taken in each + // iteration and the value of `target_test_time`. + int max_iterations = std::numeric_limits::max(); + + // -- Switch State Generation Settings --------------------------------------- + // We use P4-Fuzzer to generate switch state updates. + p4_fuzzer::FuzzerConfig fuzzer_config; + // The minimum number of switch state updates to generate per Ouroboros loop. + int min_num_updates_per_loop = 200; + + // -- Result Validation Settings --------------------------------------------- + // Validates the dataplane behavior. Uses a shared_ptr because test parameters + // must be copyable and DataplaneValidator is not. + std::shared_ptr validator; + // Specifies user-facing parameters of DVaaS. See + // dvaas::DataplaneValidationParams documentation for more details. + // NOTE: `get_artifact_header` gets overwritten to print additional + // information per iteration. + dvaas::DataplaneValidationParams dvaas_params; + + // -- Obscure, Usually Unneeded Settings ------------------------------------- + // The test assumes that the switch is pre-configured if no `gnmi_config` is + // given (default), or otherwise pushes the given config before starting. + std::optional gnmi_config; +}; + +// The Ouroboros test is designed to test all dataplane behavior on the switch, +// in the limit. +// The test has four components which it runs in a loop: +// - It generates and sends updates to the switch. +// - It generates test packets based on the entries on the switch. +// - It sends these packets to the switch collecting the switch's outputs. +// - It validates that the switch's outputs are correct w.r.t. the P4 program +// and the entries installed on the switch. +// +// See go/ouroboros for more details. +class OuroborosTest : public testing::TestWithParam { +protected: + void SetUp() override { GetParam().mirror_testbed->SetUp(); } + void TearDown() override { GetParam().mirror_testbed->TearDown(); } +}; + +} // namespace pins_test + +#endif // PINS_TESTS_FORWARDING_OUROBOROS_TEST_H_