From 756827b95f4b2c1909a78bec11e2f19d444b54a9 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Fri, 6 Dec 2024 03:01:52 +0000 Subject: [PATCH] [Dvaas]: Adding Traffic generator code to dvaas. (#810) Co-authored-by: kishanps --- dvaas/traffic_generator.cc | 197 +++++++++++++++++++++++++++++++++++ dvaas/traffic_generator.h | 207 +++++++++++++++++++++++++++++++++++++ 2 files changed, 404 insertions(+) create mode 100644 dvaas/traffic_generator.cc create mode 100644 dvaas/traffic_generator.h diff --git a/dvaas/traffic_generator.cc b/dvaas/traffic_generator.cc new file mode 100644 index 000000000..229427ab4 --- /dev/null +++ b/dvaas/traffic_generator.cc @@ -0,0 +1,197 @@ +// Copyright (c) 2024, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dvaas/traffic_generator.h" + +#include +#include +#include // NOLINT: third_party code. +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "dvaas/dataplane_validation.h" +#include "dvaas/mirror_testbed_config.h" +#include "dvaas/packet_injection.h" +#include "dvaas/port_id_map.h" +#include "dvaas/switch_api.h" +#include "dvaas/test_vector.pb.h" +#include "dvaas/validation_result.h" +#include "glog/logging.h" +#include "gutil/status.h" +#include "gutil/test_artifact_writer.h" +#include "p4_pdpi/ir.pb.h" +#include "p4_pdpi/p4_runtime_session.h" +#include "p4_pdpi/p4_runtime_session_extras.h" +#include "p4_symbolic/packet_synthesizer/packet_synthesizer.pb.h" +#include "thinkit/mirror_testbed_fixture.h" + +// Crash if `status` is not okay. Only use in tests. +#define CHECK_OK(val) CHECK_EQ(::absl::OkStatus(), (val)) // Crash OK. + +namespace dvaas { + +SimpleTrafficGenerator::State SimpleTrafficGenerator::GetState() { + absl::MutexLock lock(&state_mutex_); + return state_; +} + +void SimpleTrafficGenerator::SetState(State state) { + absl::MutexLock lock(&state_mutex_); + state_ = state; +} + +absl::StatusOr SimpleTrafficGenerator::Init( + std::shared_ptr testbed, + const TrafficGenerator::Params& params) { + if (GetState() == kTrafficFlowing) { + return absl::FailedPreconditionError( + "Cannot initialize while traffic is flowing"); + } + + params_ = params; + + // Configure testbed. + ASSIGN_OR_RETURN(auto mirror_testbed_configurator, + MirrorTestbedConfigurator::Create(testbed)); + testbed_configurator_ = std::make_unique( + std::move(mirror_testbed_configurator)); + RETURN_IF_ERROR(testbed_configurator_->ConfigureForForwardingTest({ + .mirror_sut_ports_ids_to_control_switch = + !params_.validation_params.mirror_testbed_port_map_override + .has_value(), + })); + // Install punt entries on control switch. + // TODO: Use testbed configurator to do this, instead. + pdpi::P4RuntimeSession& control_p4rt = + *testbed_configurator_->ControlSwitchApi().p4rt; + RETURN_IF_ERROR(pdpi::ClearEntities(control_p4rt)); + ASSIGN_OR_RETURN(const pdpi::IrP4Info ir_p4info, + pdpi::GetIrP4Info(control_p4rt)); + ASSIGN_OR_RETURN(const pdpi::IrEntities punt_entries, + backend_->GetEntitiesToPuntAllPackets(ir_p4info)); + RETURN_IF_ERROR(pdpi::InstallIrEntities(control_p4rt, punt_entries)); + + // Generate test vectors. + gutil::BazelTestArtifactWriter writer; + ASSIGN_OR_RETURN( + test_vector_by_id_, + GenerateTestVectors(params.validation_params, + testbed_configurator_->SutApi(), *backend_, writer)); + + SetState(kInitialized); + + return PacketSynthesisStats{}; +} + +absl::Status SimpleTrafficGenerator::StartTraffic() { + State state = GetState(); + if (state == kUninitialized) { + return absl::FailedPreconditionError( + "Cannot start traffic before initialization."); + } + if (state == kTrafficFlowing) { + return absl::FailedPreconditionError( + "Traffic injection has already started"); + } + + // Spawn traffic injection thread. + traffic_injection_thread_ = + std::thread(&SimpleTrafficGenerator::InjectTraffic, this); + + // Wait for state to change before returning. + while (GetState() != kTrafficFlowing) { + absl::SleepFor(absl::Seconds(1)); + } + + return absl::OkStatus(); +} + +absl::Status SimpleTrafficGenerator::StopTraffic() { + if (GetState() != kTrafficFlowing) { + return absl::FailedPreconditionError( + "Cannot stop traffic if not already flowing."); + } + + // Change state. + SetState(kInitialized); + + // Wait for traffic injection thread to stop before returning. + traffic_injection_thread_.join(); + + return absl::OkStatus(); +} + +void SimpleTrafficGenerator::InjectTraffic() { + // Change state. + SetState(kTrafficFlowing); + + LOG(INFO) << "Starting to inject traffic"; + int iterations = 0; + while (GetState() == kTrafficFlowing) { + ++iterations; + LOG_EVERY_T(INFO, 10) << "Traffic injection iteration #" << iterations; + + // Inject and collect. + PacketStatistics statistics; + absl::StatusOr test_runs = SendTestPacketsAndCollectOutputs( + *testbed_configurator_->SutApi().p4rt, + *testbed_configurator_->ControlSwitchApi().p4rt, test_vector_by_id_, + { + .max_packets_to_send_per_second = + params_.validation_params.max_packets_to_send_per_second, + .mirror_testbed_port_map = + params_.validation_params.mirror_testbed_port_map_override + .value_or(MirrorTestbedP4rtPortIdMap::CreateIdentityMap()), + }, + statistics); + CHECK_OK(test_runs.status()); // Crash OK. + + // Add results to test_runs_. + absl::MutexLock lock(&test_runs_mutex_); + test_runs_.mutable_test_runs()->Add(test_runs->test_runs().begin(), + test_runs->test_runs().end()); + } + + LOG(INFO) << "Stopped traffic injection"; +} + +absl::StatusOr SimpleTrafficGenerator::GetValidationResult() { + test_runs_mutex_.Lock(); + PacketTestRuns test_runs = test_runs_; + test_runs_mutex_.Unlock(); + + return ValidationResult( + test_runs, params_.validation_params.ignored_fields_for_validation, + params_.validation_params.ignored_metadata_for_validation); +} + +absl::StatusOr +SimpleTrafficGenerator::GetAndClearValidationResult() { + test_runs_mutex_.Lock(); + PacketTestRuns test_runs = test_runs_; + test_runs_.clear_test_runs(); + test_runs_mutex_.Unlock(); + + return ValidationResult( + test_runs, params_.validation_params.ignored_fields_for_validation, + params_.validation_params.ignored_metadata_for_validation); +} + +} // namespace dvaas diff --git a/dvaas/traffic_generator.h b/dvaas/traffic_generator.h new file mode 100644 index 000000000..c0fe8355d --- /dev/null +++ b/dvaas/traffic_generator.h @@ -0,0 +1,207 @@ +// Copyright (c) 2024, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PINS_DVAAS_TRAFFIC_GENERATOR_H_ +#define PINS_DVAAS_TRAFFIC_GENERATOR_H_ + +#include +#include // NOLINT: third_party code. +#include +#include + +#include "absl/base/thread_annotations.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/synchronization/mutex.h" +#include "dvaas/dataplane_validation.h" +#include "dvaas/mirror_testbed_config.h" +#include "dvaas/test_vector.h" +#include "dvaas/test_vector.pb.h" +#include "dvaas/validation_result.h" +#include "thinkit/mirror_testbed.h" +#include "thinkit/mirror_testbed_fixture.h" + +namespace dvaas { + +// Various statistics about test packet synthesis. +struct PacketSynthesisStats { + // TODO: Add unreachable entries to stats. + // List of entries deemed unreachable by the packet synthesizer. + // std::vector unreachable_entries; +}; + +// Interface for generating traffic and validating it. +class TrafficGenerator { + public: + // Traffic generation and validation parameters. + struct Params { + // See dataplane_validation.h for details. + DataplaneValidationParams validation_params; + + // TODO: Implement ignore_punted_packets_for_validation. + // If true, ignores punting behavior during validation. + // bool ignore_punting_for_validation; + // TODO: Provide a knob to say I only want L3 forwarded packets. + }; + + // Initialises the traffic generator (and the testbed) with the given params, + // including synthesising test packets. Does NOT start traffic. + // On success, returns statistic about packet synthesis. + // + // NOTE: The table entries, P4Info, and gNMI configuration used in packets + // synthesis will be read from the SUT itself. + // It is the client's responsibility to ensure the correctness of these + // artifacts. + // + // NOTE: Synthesizing test packets that are used in the traffic is a + // computationaly heavy operation and may take a long time (tens of minutes) + // depending on the coverage goals, the number of entries on SUT, and the + // allocated compute resources. This function *blocks* until packet synthesis + // is finished. + // + // NOTE: Packet synthesis is done only once and during the + // call to `Init`. `StartTraffic` does NOT synthesize new test packets, + // instead it uses test packets synthesized during the call to `Init`. If the + // switch configuration or entries change after the call to `Init`, the + // validation results would be inaccurate. + // + // Preconditions: + // - The switches in the testbed must be configured (i.e. have + // proper gNMI and P4Info). + // - SUT must contain the table desired entries. + // - SUT and its ports must be in a state such that the function can + // connect to SUT to read the gNMI config, P4Info, and table entries. + // - Control switch and its ports must be in a state such that the function + // can modify its table entries through a P4RT session. + // + // Postconditions (on a successful return): + // - SUT's entries will be unchanged. + // - The control switch will have the same entries as + // `GetEntriesToPuntAllPackets`. + // - Any preexisting P4RT connections to SUT and control switch will be + // non-primary. + // - The gNMI configs will be unchanged. + virtual absl::StatusOr Init( + std::shared_ptr testbed, + const Params& params) = 0; + + // Asynchronously starts injecting traffic (and validating the result) using + // test packets that were synthesized during `Init`. + // + // Precondition: Init must already be called before calling start. Traffic + // must NOT be already started. Otherwise an error will be returned. + // Postcondition: Traffic injection is started when the function returns. + virtual absl::Status StartTraffic() = 0; + + // Stops sending traffic. + // Blocks until traffic injection/collection fully stops. + // + // Precondition: Traffic must be already started. + // Postcondition: Traffic is stopped when the function returns. + virtual absl::Status StopTraffic() = 0; + + // Returns various information about the injected traffic including + // result of dataplane validation. + // See validation_result.h for details. + // + // NOTE: The validation is performed against test packets and expected outputs + // synthesized during the call to `Init` (with configuration and table + // entries on SUT at the time of calling `Init`). If the configuration or + // table entries change after that call, the validation result would be + // inaccurate. + // + // NOTE: If called while traffic flowing, the function may block for a while + // to collect in-flight packets and validate results. + virtual absl::StatusOr GetValidationResult() = 0; + // Similar to `GetValidationResult` but (on a successful return) resets the + // old results before returning, in the sense that the future calls to + // Get*ValidationResult will not include the results returned by + // the current call. + virtual absl::StatusOr GetAndClearValidationResult() = 0; + + virtual ~TrafficGenerator() = default; +}; + +// A simple implementation of `TrafficGenerator` interface that can be used as a +// proof of concept. This implementation does NOT provide a consistent traffic +// injection rate guarantee (see `InjectTraffic` function comments for more +// details). +class SimpleTrafficGenerator : public TrafficGenerator { + public: + SimpleTrafficGenerator() = delete; + explicit SimpleTrafficGenerator( + std::unique_ptr backend) + : backend_(std::move(backend)) {} + + absl::StatusOr Init( + std::shared_ptr testbed, + const Params& params) override; + absl::Status StartTraffic() override; + absl::Status StopTraffic() override; + absl::StatusOr GetValidationResult() override; + absl::StatusOr GetAndClearValidationResult() override; + + private: + std::unique_ptr backend_; + std::unique_ptr testbed_configurator_; + // Test vectors created as a result of (latest) call to `Init`. Calls to + // `StartTraffic` use these test vectors. + PacketTestVectorById test_vector_by_id_; + + enum State { + // The object has been created but `Init` has not been called. + kUninitialized, + // `Init` has been called, but no traffic is flowing (either `StartTraffic` + // has not been called or `StopTraffic` has been called after that). + kInitialized, + // Traffic is flowing (`StartTraffic` has been called and `StopTraffic` has + // NOT been called after that). + kTrafficFlowing, + }; + // The state of the SimpleTrafficGenerator object. + State state_ ABSL_GUARDED_BY(state_mutex_) = kUninitialized; + // Mutex to synchronize access to state_; + absl::Mutex state_mutex_; + + // Thread safe getter for state_. + State GetState() ABSL_LOCKS_EXCLUDED(state_mutex_); + // Thread safe setter for state_. + void SetState(State state) ABSL_LOCKS_EXCLUDED(state_mutex_); + + // The thread that is spawned during the call to `StartTraffic` and runs + // `InjectTraffic` function. The thread continues until `StopTraffic` is + // called. + std::thread traffic_injection_thread_; + // Runs in a separate thread, as a loop that injects and collects packets + // until traffic is stopped. + // In each iteration of the loop, injects packets in `test_vector_by_id_` at + // the rate specified by `params_`. At the end of each iteration, WAITS UP TO + // 3 SECONDS to collect any in-flight packets, before moving on to next + // iteration. + void InjectTraffic() ABSL_LOCKS_EXCLUDED(test_runs_mutex_); + + // Result of packet injection and collection (i.e. test vector + switch + // output). Populated by `InjectTraffic`. Used during the call to + // `Get*ValidationStats`. + PacketTestRuns test_runs_ ABSL_GUARDED_BY(test_runs_mutex_); + // Mutex to synchronize access to test_runs_; + absl::Mutex test_runs_mutex_; + + // Parameters received in the (latest) call to `Init`. + TrafficGenerator::Params params_; +}; + +} // namespace dvaas + +#endif // PINS_DVAAS_TRAFFIC_GENERATOR_H_