Skip to content

Commit

Permalink
[Thinkit] Transition Ouroboros to take a DataplaneValidationParams as…
Browse files Browse the repository at this point in the history
… a parameter.
  • Loading branch information
divyagayathri-hcl committed Jan 17, 2025
1 parent e8835ba commit a936656
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 46 deletions.
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_

0 comments on commit a936656

Please sign in to comment.