Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Thinkit] Transition Ouroboros to take a DataplaneValidationParams as a parameter. #955

Merged
merged 3 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions tests/forwarding/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
90 changes: 44 additions & 46 deletions tests/forwarding/ouroboros_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 ------------------------------------------------------
Expand All @@ -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(
Expand All @@ -102,32 +103,31 @@ 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();
}

// Creates connections to the SUT and Control switch and configures them with a
// `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<std::pair<Switch, Switch>> ConfigureMirrorTestbed(
thinkit::MirrorTestbed& testbed, std::optional<std::string> gnmi_config,
std::optional<p4::config::v1::P4Info> p4info) {
absl::StatusOr<std::pair<SwitchApi, SwitchApi>>
ConfigureMirrorTestbed(thinkit::MirrorTestbed &testbed,
std::optional<std::string> gnmi_config,
std::optional<p4::config::v1::P4Info> 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());
Expand Down Expand Up @@ -155,11 +155,11 @@ absl::StatusOr<std::pair<Switch, Switch>> 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) {
Expand Down Expand Up @@ -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()));
Expand All @@ -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;

Expand All @@ -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;
}
Expand Down
101 changes: 101 additions & 0 deletions tests/forwarding/ouroboros_test.h
Original file line number Diff line number Diff line change
@@ -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 <functional>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <vector>

#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<thinkit::MirrorTestbedInterface> 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<int>::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<dvaas::DataplaneValidator> 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<std::string> 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<OuroborosTestParams> {
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_
Loading