diff --git a/include/nighthawk/adaptive_load/BUILD b/include/nighthawk/adaptive_load/BUILD index 32ed22151..1e0728795 100644 --- a/include/nighthawk/adaptive_load/BUILD +++ b/include/nighthawk/adaptive_load/BUILD @@ -96,6 +96,19 @@ envoy_basic_cc_library( ], ) +envoy_basic_cc_library( + name = "session_spec_proto_helper", + hdrs = [ + "session_spec_proto_helper.h", + ], + include_prefix = "nighthawk/adaptive_load", + deps = [ + "//api/adaptive_load:adaptive_load_proto_cc_proto", + "@com_google_absl//absl/status", + "@envoy//include/envoy/common:base_includes", + ], +) + envoy_basic_cc_library( name = "step_controller", hdrs = [ diff --git a/include/nighthawk/adaptive_load/session_spec_proto_helper.h b/include/nighthawk/adaptive_load/session_spec_proto_helper.h new file mode 100644 index 000000000..06e77ded7 --- /dev/null +++ b/include/nighthawk/adaptive_load/session_spec_proto_helper.h @@ -0,0 +1,43 @@ +#pragma once + +#include "envoy/common/pure.h" + +#include "api/adaptive_load/adaptive_load.pb.h" + +#include "absl/status/status.h" + +namespace Nighthawk { + +/** + * Utilities for setting default values and validating user settings in the main + * AdaptiveLoadSessionSpec proto. + */ +class AdaptiveLoadSessionSpecProtoHelper { +public: + virtual ~AdaptiveLoadSessionSpecProtoHelper() = default; + + /** + * Returns a copy of the input spec with default values inserted. Avoids overriding pre-set values + * in the original spec. + * + * @param spec Valid adaptive load session spec. + * + * @return Adaptive load session spec with default values inserted. + */ + virtual nighthawk::adaptive_load::AdaptiveLoadSessionSpec + SetSessionSpecDefaults(nighthawk::adaptive_load::AdaptiveLoadSessionSpec spec) PURE; + + /** + * Checks whether a session spec is valid: No forbidden fields in Nighthawk traffic spec; no bad + * plugin references or bad plugin configurations (step controller, metric, scoring function); no + * nonexistent metric names. Reports all errors in one pass. + * + * @param spec A potentially invalid adaptive load session spec. + * + * @return Status OK if no problems were found, or InvalidArgument with all errors. + */ + virtual absl::Status + CheckSessionSpec(const nighthawk::adaptive_load::AdaptiveLoadSessionSpec& spec) PURE; +}; + +} // namespace Nighthawk \ No newline at end of file diff --git a/source/adaptive_load/BUILD b/source/adaptive_load/BUILD index c6e0fcf7e..3bc034c93 100644 --- a/source/adaptive_load/BUILD +++ b/source/adaptive_load/BUILD @@ -119,6 +119,28 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "session_spec_proto_helper_impl", + srcs = [ + "session_spec_proto_helper_impl.cc", + ], + hdrs = [ + "session_spec_proto_helper_impl.h", + ], + repository = "@envoy", + visibility = ["//visibility:public"], + deps = [ + ":metrics_plugin_impl", + ":plugin_loader", + "//include/nighthawk/adaptive_load:metrics_plugin", + "//include/nighthawk/adaptive_load:scoring_function", + "//include/nighthawk/adaptive_load:session_spec_proto_helper", + "//include/nighthawk/adaptive_load:step_controller", + "@envoy//source/common/config:utility_lib_with_external_headers", + "@envoy//source/common/protobuf:protobuf_with_external_headers", + ], +) + envoy_cc_library( name = "step_controller_impl", srcs = [ diff --git a/source/adaptive_load/session_spec_proto_helper_impl.cc b/source/adaptive_load/session_spec_proto_helper_impl.cc new file mode 100644 index 000000000..3c45d3d86 --- /dev/null +++ b/source/adaptive_load/session_spec_proto_helper_impl.cc @@ -0,0 +1,119 @@ +#include "adaptive_load/session_spec_proto_helper_impl.h" + +#include "nighthawk/adaptive_load/metrics_plugin.h" +#include "nighthawk/adaptive_load/step_controller.h" + +#include "api/adaptive_load/adaptive_load.pb.h" +#include "api/adaptive_load/metric_spec.pb.h" + +#include "absl/container/flat_hash_map.h" +#include "absl/strings/str_join.h" +#include "absl/strings/string_view.h" +#include "adaptive_load/metrics_plugin_impl.h" +#include "adaptive_load/plugin_loader.h" + +namespace Nighthawk { + +nighthawk::adaptive_load::AdaptiveLoadSessionSpec +AdaptiveLoadSessionSpecProtoHelperImpl::SetSessionSpecDefaults( + nighthawk::adaptive_load::AdaptiveLoadSessionSpec spec) { + if (!spec.nighthawk_traffic_template().has_open_loop()) { + spec.mutable_nighthawk_traffic_template()->mutable_open_loop()->set_value(true); + } + if (!spec.has_measuring_period()) { + spec.mutable_measuring_period()->set_seconds(10); + } + if (!spec.has_convergence_deadline()) { + spec.mutable_convergence_deadline()->set_seconds(300); + } + if (!spec.has_testing_stage_duration()) { + spec.mutable_testing_stage_duration()->set_seconds(30); + } + for (nighthawk::adaptive_load::MetricSpecWithThreshold& threshold : + *spec.mutable_metric_thresholds()) { + if (threshold.metric_spec().metrics_plugin_name().empty()) { + threshold.mutable_metric_spec()->set_metrics_plugin_name("nighthawk.builtin"); + } + if (!threshold.threshold_spec().has_weight()) { + threshold.mutable_threshold_spec()->mutable_weight()->set_value(1.0); + } + } + for (nighthawk::adaptive_load::MetricSpec& metric_spec : + *spec.mutable_informational_metric_specs()) { + if (metric_spec.metrics_plugin_name().empty()) { + metric_spec.set_metrics_plugin_name("nighthawk.builtin"); + } + } + return spec; +} + +absl::Status AdaptiveLoadSessionSpecProtoHelperImpl::CheckSessionSpec( + const nighthawk::adaptive_load::AdaptiveLoadSessionSpec& spec) { + std::vector errors; + if (spec.nighthawk_traffic_template().has_duration()) { + errors.emplace_back( + "nighthawk_traffic_template should not have |duration| set. Set |measuring_period| " + "and |testing_stage_duration| in the AdaptiveLoadSessionSpec proto instead."); + } + absl::flat_hash_map plugin_from_name; + std::vector plugin_names = {"nighthawk.builtin"}; + plugin_from_name["nighthawk.builtin"] = + std::make_unique(nighthawk::client::Output()); + for (const envoy::config::core::v3::TypedExtensionConfig& config : + spec.metrics_plugin_configs()) { + plugin_names.push_back(config.name()); + absl::StatusOr metrics_plugin_or = LoadMetricsPlugin(config); + if (!metrics_plugin_or.ok()) { + errors.emplace_back( + absl::StrCat("Failed to load MetricsPlugin: ", metrics_plugin_or.status().message())); + continue; + } + plugin_from_name[config.name()] = std::move(metrics_plugin_or.value()); + } + absl::StatusOr step_controller_or = + LoadStepControllerPlugin(spec.step_controller_config(), spec.nighthawk_traffic_template()); + if (!step_controller_or.ok()) { + errors.emplace_back(absl::StrCat("Failed to load StepController plugin: ", + step_controller_or.status().message())); + } + std::vector all_metric_specs; + for (const nighthawk::adaptive_load::MetricSpecWithThreshold& metric_threshold : + spec.metric_thresholds()) { + all_metric_specs.push_back(metric_threshold.metric_spec()); + absl::StatusOr scoring_function_or = + LoadScoringFunctionPlugin(metric_threshold.threshold_spec().scoring_function()); + if (!scoring_function_or.ok()) { + errors.emplace_back(absl::StrCat("Failed to load ScoringFunction plugin: ", + scoring_function_or.status().message())); + } + } + for (const nighthawk::adaptive_load::MetricSpec& metric_spec : + spec.informational_metric_specs()) { + all_metric_specs.push_back(metric_spec); + } + for (const nighthawk::adaptive_load::MetricSpec& metric_spec : all_metric_specs) { + if (plugin_from_name.contains(metric_spec.metrics_plugin_name())) { + std::vector supported_metrics = + plugin_from_name[metric_spec.metrics_plugin_name()]->GetAllSupportedMetricNames(); + if (std::find(supported_metrics.begin(), supported_metrics.end(), + metric_spec.metric_name()) == supported_metrics.end()) { + errors.emplace_back( + absl::StrCat("Metric named '", metric_spec.metric_name(), + "' not implemented by plugin '", metric_spec.metrics_plugin_name(), + "'. Metrics implemented: ", absl::StrJoin(supported_metrics, ", "), ".")); + } + } else { + errors.emplace_back(absl::StrCat( + "MetricSpec referred to nonexistent metrics_plugin_name '", + metric_spec.metrics_plugin_name(), + "'. You must declare the plugin in metrics_plugin_configs or use plugin ", + "'nighthawk.builtin'. Available plugins: ", absl::StrJoin(plugin_names, ", "), ".")); + } + } + if (errors.size() > 0) { + return absl::InvalidArgumentError(absl::StrJoin(errors, "\n")); + } + return absl::OkStatus(); +} + +} // namespace Nighthawk diff --git a/source/adaptive_load/session_spec_proto_helper_impl.h b/source/adaptive_load/session_spec_proto_helper_impl.h new file mode 100644 index 000000000..d47e3a680 --- /dev/null +++ b/source/adaptive_load/session_spec_proto_helper_impl.h @@ -0,0 +1,14 @@ +#include "nighthawk/adaptive_load/session_spec_proto_helper.h" + +namespace Nighthawk { + +class AdaptiveLoadSessionSpecProtoHelperImpl : public AdaptiveLoadSessionSpecProtoHelper { +public: + nighthawk::adaptive_load::AdaptiveLoadSessionSpec + SetSessionSpecDefaults(nighthawk::adaptive_load::AdaptiveLoadSessionSpec spec) override; + + absl::Status + CheckSessionSpec(const nighthawk::adaptive_load::AdaptiveLoadSessionSpec& spec) override; +}; + +} // namespace Nighthawk diff --git a/test/adaptive_load/BUILD b/test/adaptive_load/BUILD index a8639f34e..78518f6cb 100644 --- a/test/adaptive_load/BUILD +++ b/test/adaptive_load/BUILD @@ -63,6 +63,17 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "session_spec_proto_helper_test", + srcs = ["session_spec_proto_helper_test.cc"], + repository = "@envoy", + deps = [ + "//source/adaptive_load:session_spec_proto_helper_impl", + "//test/adaptive_load/fake_plugins/fake_metrics_plugin", + "//test/adaptive_load/fake_plugins/fake_step_controller", + ], +) + envoy_cc_test( name = "scoring_function_test", srcs = ["scoring_function_test.cc"], diff --git a/test/adaptive_load/fake_plugins/fake_metrics_plugin/fake_metrics_plugin.cc b/test/adaptive_load/fake_plugins/fake_metrics_plugin/fake_metrics_plugin.cc index 41909cf4e..1f1c6e694 100644 --- a/test/adaptive_load/fake_plugins/fake_metrics_plugin/fake_metrics_plugin.cc +++ b/test/adaptive_load/fake_plugins/fake_metrics_plugin/fake_metrics_plugin.cc @@ -81,4 +81,12 @@ FakeMetricsPluginConfigFactory::ValidateConfig(const Envoy::Protobuf::Message& m REGISTER_FACTORY(FakeMetricsPluginConfigFactory, MetricsPluginConfigFactory); +envoy::config::core::v3::TypedExtensionConfig MakeFakeMetricsPluginTypedExtensionConfig( + const nighthawk::adaptive_load::FakeMetricsPluginConfig& config) { + envoy::config::core::v3::TypedExtensionConfig outer_config; + outer_config.set_name("nighthawk.fake_metrics_plugin"); + outer_config.mutable_typed_config()->PackFrom(config); + return outer_config; +} + } // namespace Nighthawk diff --git a/test/adaptive_load/fake_plugins/fake_metrics_plugin/fake_metrics_plugin.h b/test/adaptive_load/fake_plugins/fake_metrics_plugin/fake_metrics_plugin.h index 1bb6b9fd2..96c2e4446 100644 --- a/test/adaptive_load/fake_plugins/fake_metrics_plugin/fake_metrics_plugin.h +++ b/test/adaptive_load/fake_plugins/fake_metrics_plugin/fake_metrics_plugin.h @@ -50,4 +50,16 @@ class FakeMetricsPluginConfigFactory : public MetricsPluginConfigFactory { // This factory is activated through LoadMetricsPlugin in plugin_util.h. DECLARE_FACTORY(FakeMetricsPluginConfigFactory); +/** + * Creates a TypedExtensionConfig that activates a FakeMetricsPlugin by name with the given config + * proto. + * + * @param config The plugin-specific config proto to be packed into the typed_config Any. + * + * @return TypedExtensionConfig A proto that activates a FakeMetricsPlugin by name with a bundled + * config proto. + */ +envoy::config::core::v3::TypedExtensionConfig MakeFakeMetricsPluginTypedExtensionConfig( + const nighthawk::adaptive_load::FakeMetricsPluginConfig& config); + } // namespace Nighthawk diff --git a/test/adaptive_load/fake_plugins/fake_metrics_plugin/fake_metrics_plugin_test.cc b/test/adaptive_load/fake_plugins/fake_metrics_plugin/fake_metrics_plugin_test.cc index 547f89ba9..bfbc39ad8 100644 --- a/test/adaptive_load/fake_plugins/fake_metrics_plugin/fake_metrics_plugin_test.cc +++ b/test/adaptive_load/fake_plugins/fake_metrics_plugin/fake_metrics_plugin_test.cc @@ -6,6 +6,7 @@ #include "api/client/options.pb.h" #include "test/adaptive_load/fake_plugins/fake_metrics_plugin/fake_metrics_plugin.h" +#include "test/adaptive_load/fake_plugins/fake_metrics_plugin/fake_metrics_plugin.pb.h" #include "adaptive_load/plugin_loader.h" #include "gmock/gmock.h" @@ -114,5 +115,23 @@ TEST(FakeMetricsPlugin, GetAllSupportedMetricNamesReturnsCorrectValues) { ::testing::UnorderedElementsAre("metric1", "metric2")); } +TEST(MakeFakeMetricsPluginTypedExtensionConfig, SetsCorrectPluginName) { + envoy::config::core::v3::TypedExtensionConfig activator = + MakeFakeMetricsPluginTypedExtensionConfig( + nighthawk::adaptive_load::FakeMetricsPluginConfig()); + EXPECT_EQ(activator.name(), "nighthawk.fake_metrics_plugin"); +} + +TEST(MakeFakeMetricsPluginTypedExtensionConfig, PacksGivenConfigProto) { + nighthawk::adaptive_load::FakeMetricsPluginConfig expected_config; + expected_config.mutable_fake_metrics()->Add()->set_name("a"); + envoy::config::core::v3::TypedExtensionConfig activator = + MakeFakeMetricsPluginTypedExtensionConfig(expected_config); + nighthawk::adaptive_load::FakeMetricsPluginConfig actual_config; + Envoy::MessageUtil::unpackTo(activator.typed_config(), actual_config); + EXPECT_EQ(expected_config.DebugString(), actual_config.DebugString()); + EXPECT_TRUE(MessageDifferencer::Equivalent(expected_config, actual_config)); +} + } // namespace } // namespace Nighthawk diff --git a/test/adaptive_load/session_spec_proto_helper_test.cc b/test/adaptive_load/session_spec_proto_helper_test.cc new file mode 100644 index 000000000..565475994 --- /dev/null +++ b/test/adaptive_load/session_spec_proto_helper_test.cc @@ -0,0 +1,265 @@ +#include "envoy/registry/registry.h" + +#include "external/envoy/source/common/config/utility.h" + +#include "api/adaptive_load/adaptive_load.pb.h" +#include "api/adaptive_load/metric_spec.pb.h" +#include "api/client/options.pb.h" + +#include "test/adaptive_load/fake_plugins/fake_metrics_plugin/fake_metrics_plugin.h" + +#include "adaptive_load/plugin_loader.h" +#include "adaptive_load/session_spec_proto_helper_impl.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Nighthawk { + +namespace { + +using ::nighthawk::adaptive_load::AdaptiveLoadSessionSpec; +using ::testing::HasSubstr; + +TEST(SetSessionSpecDefaults, SetsDefaultValueIfOpenLoopUnset) { + AdaptiveLoadSessionSpec original_spec; + AdaptiveLoadSessionSpecProtoHelperImpl helper; + AdaptiveLoadSessionSpec spec = helper.SetSessionSpecDefaults(original_spec); + EXPECT_TRUE(spec.nighthawk_traffic_template().open_loop().value()); +} + +TEST(SetSessionSpecDefaults, PreservesExplicitOpenLoopSetting) { + AdaptiveLoadSessionSpec original_spec; + original_spec.mutable_nighthawk_traffic_template()->mutable_open_loop()->set_value(false); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + AdaptiveLoadSessionSpec spec = helper.SetSessionSpecDefaults(original_spec); + EXPECT_FALSE(spec.nighthawk_traffic_template().open_loop().value()); +} + +TEST(SetSessionSpecDefaults, SetsDefaultMeasuringPeriodIfUnset) { + AdaptiveLoadSessionSpec original_spec; + AdaptiveLoadSessionSpecProtoHelperImpl helper; + AdaptiveLoadSessionSpec spec = helper.SetSessionSpecDefaults(original_spec); + EXPECT_EQ(spec.measuring_period().seconds(), 10); +} + +TEST(SetSessionSpecDefaults, PreservesExplicitMeasuringPeriod) { + const int kExpectedMeasuringPeriodSeconds = 123; + AdaptiveLoadSessionSpec original_spec; + original_spec.mutable_measuring_period()->set_seconds(kExpectedMeasuringPeriodSeconds); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + AdaptiveLoadSessionSpec spec = helper.SetSessionSpecDefaults(original_spec); + EXPECT_EQ(spec.measuring_period().seconds(), kExpectedMeasuringPeriodSeconds); +} + +TEST(SetSessionSpecDefaults, SetsDefaultConvergenceDeadlineIfUnset) { + AdaptiveLoadSessionSpec original_spec; + AdaptiveLoadSessionSpecProtoHelperImpl helper; + AdaptiveLoadSessionSpec spec = helper.SetSessionSpecDefaults(original_spec); + EXPECT_EQ(spec.convergence_deadline().seconds(), 300); +} + +TEST(SetSessionSpecDefaults, PreservesExplicitConvergenceDeadline) { + const int kExpectedConvergenceDeadlineSeconds = 123; + AdaptiveLoadSessionSpec original_spec; + original_spec.mutable_convergence_deadline()->set_seconds(kExpectedConvergenceDeadlineSeconds); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + AdaptiveLoadSessionSpec spec = helper.SetSessionSpecDefaults(original_spec); + EXPECT_EQ(spec.convergence_deadline().seconds(), kExpectedConvergenceDeadlineSeconds); +} + +TEST(SetSessionSpecDefaults, SetsDefaultTestingStageDurationIfUnset) { + AdaptiveLoadSessionSpec original_spec; + AdaptiveLoadSessionSpecProtoHelperImpl helper; + AdaptiveLoadSessionSpec spec = helper.SetSessionSpecDefaults(original_spec); + EXPECT_EQ(spec.testing_stage_duration().seconds(), 30); +} + +TEST(SetSessionSpecDefaults, PreservesExplicitTestingStageDuration) { + const int kExpectedTestingStageDurationSeconds = 123; + AdaptiveLoadSessionSpec original_spec; + original_spec.mutable_testing_stage_duration()->set_seconds(kExpectedTestingStageDurationSeconds); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + AdaptiveLoadSessionSpec spec = helper.SetSessionSpecDefaults(original_spec); + EXPECT_EQ(spec.testing_stage_duration().seconds(), kExpectedTestingStageDurationSeconds); +} + +TEST(SetSessionSpecDefaults, SetsDefaultScoredMetricPluginNameIfUnset) { + AdaptiveLoadSessionSpec original_spec; + (void)original_spec.mutable_metric_thresholds()->Add(); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + AdaptiveLoadSessionSpec spec = helper.SetSessionSpecDefaults(original_spec); + ASSERT_GT(spec.metric_thresholds_size(), 0); + EXPECT_EQ(spec.metric_thresholds(0).metric_spec().metrics_plugin_name(), "nighthawk.builtin"); +} + +TEST(SetSessionSpecDefaults, PreservesExplicitScoredMetricPluginName) { + const std::string kExpectedMetricsPluginName = "a"; + AdaptiveLoadSessionSpec original_spec; + nighthawk::adaptive_load::MetricSpecWithThreshold* spec_threshold = + original_spec.mutable_metric_thresholds()->Add(); + spec_threshold->mutable_metric_spec()->set_metrics_plugin_name(kExpectedMetricsPluginName); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + AdaptiveLoadSessionSpec spec = helper.SetSessionSpecDefaults(original_spec); + ASSERT_GT(spec.metric_thresholds_size(), 0); + EXPECT_EQ(spec.metric_thresholds(0).metric_spec().metrics_plugin_name(), + kExpectedMetricsPluginName); +} + +TEST(SetSessionSpecDefaults, SetsDefaultScoredMetricWeightIfUnset) { + AdaptiveLoadSessionSpec original_spec; + (void)original_spec.mutable_metric_thresholds()->Add(); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + AdaptiveLoadSessionSpec spec = helper.SetSessionSpecDefaults(original_spec); + ASSERT_GT(spec.metric_thresholds_size(), 0); + EXPECT_EQ(spec.metric_thresholds(0).threshold_spec().weight().value(), 1.0); +} + +TEST(SetSessionSpecDefaults, PreservesExplicitScoredMetricWeight) { + const double kExpectedWeight = 123.0; + AdaptiveLoadSessionSpec original_spec; + nighthawk::adaptive_load::MetricSpecWithThreshold* spec_threshold = + original_spec.mutable_metric_thresholds()->Add(); + spec_threshold->mutable_threshold_spec()->mutable_weight()->set_value(kExpectedWeight); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + AdaptiveLoadSessionSpec spec = helper.SetSessionSpecDefaults(original_spec); + ASSERT_GT(spec.metric_thresholds_size(), 0); + EXPECT_EQ(spec.metric_thresholds(0).threshold_spec().weight().value(), kExpectedWeight); +} + +TEST(SetSessionSpecDefaults, SetsDefaultInformationalMetricPluginNameIfUnset) { + AdaptiveLoadSessionSpec original_spec; + (void)original_spec.mutable_informational_metric_specs()->Add(); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + AdaptiveLoadSessionSpec spec = helper.SetSessionSpecDefaults(original_spec); + ASSERT_GT(spec.informational_metric_specs_size(), 0); + EXPECT_EQ(spec.informational_metric_specs(0).metrics_plugin_name(), "nighthawk.builtin"); +} + +TEST(SetSessionSpecDefaults, PreservesExplicitInformationalMetricPluginName) { + const std::string kExpectedMetricsPluginName = "a"; + AdaptiveLoadSessionSpec original_spec; + nighthawk::adaptive_load::MetricSpec* metric_spec = + original_spec.mutable_informational_metric_specs()->Add(); + metric_spec->set_metrics_plugin_name(kExpectedMetricsPluginName); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + AdaptiveLoadSessionSpec spec = helper.SetSessionSpecDefaults(original_spec); + ASSERT_GT(spec.informational_metric_specs_size(), 0); + EXPECT_EQ(spec.informational_metric_specs(0).metrics_plugin_name(), kExpectedMetricsPluginName); +} + +TEST(CheckSessionSpec, RejectsDurationIfSet) { + AdaptiveLoadSessionSpec spec; + spec.mutable_nighthawk_traffic_template()->mutable_duration()->set_seconds(1); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + absl::Status status = helper.CheckSessionSpec(spec); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_THAT(status.message(), HasSubstr("should not have |duration| set")); +} + +TEST(CheckSessionSpec, RejectsInvalidMetricsPlugin) { + AdaptiveLoadSessionSpec spec; + envoy::config::core::v3::TypedExtensionConfig metrics_plugin_config; + metrics_plugin_config.set_name("bogus"); + *spec.mutable_metrics_plugin_configs()->Add() = metrics_plugin_config; + AdaptiveLoadSessionSpecProtoHelperImpl helper; + absl::Status status = helper.CheckSessionSpec(spec); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_THAT(status.message(), HasSubstr("Failed to load MetricsPlugin")); +} + +TEST(CheckSessionSpec, RejectsInvalidStepControllerPlugin) { + AdaptiveLoadSessionSpec spec; + spec.mutable_step_controller_config()->set_name("bogus"); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + absl::Status status = helper.CheckSessionSpec(spec); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_THAT(status.message(), HasSubstr("Failed to load StepController plugin")); +} + +TEST(CheckSessionSpec, RejectsInvalidScoringFunctionPlugin) { + AdaptiveLoadSessionSpec spec; + nighthawk::adaptive_load::MetricSpecWithThreshold* spec_threshold = + spec.mutable_metric_thresholds()->Add(); + spec_threshold->mutable_threshold_spec()->mutable_scoring_function()->set_name("bogus"); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + absl::Status status = helper.CheckSessionSpec(spec); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_THAT(status.message(), HasSubstr("Failed to load ScoringFunction plugin")); +} + +TEST(CheckSessionSpec, RejectsScoredMetricWithUndeclaredMetricsPluginName) { + AdaptiveLoadSessionSpec spec; + nighthawk::adaptive_load::MetricSpecWithThreshold* spec_threshold = + spec.mutable_metric_thresholds()->Add(); + spec_threshold->mutable_metric_spec()->set_metrics_plugin_name("bogus"); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + absl::Status status = helper.CheckSessionSpec(spec); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_THAT(status.message(), HasSubstr("nonexistent metrics_plugin_name")); +} + +TEST(CheckSessionSpec, RejectsInformationalMetricWithUndeclaredMetricsPluginName) { + AdaptiveLoadSessionSpec spec; + nighthawk::adaptive_load::MetricSpec* metric_spec = + spec.mutable_informational_metric_specs()->Add(); + metric_spec->set_metrics_plugin_name("bogus"); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + absl::Status status = helper.CheckSessionSpec(spec); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_THAT(status.message(), HasSubstr("nonexistent metrics_plugin_name")); +} + +TEST(CheckSessionSpec, RejectsScoredMetricWithNonexistentDefaultMetricsPluginMetric) { + AdaptiveLoadSessionSpec spec; + nighthawk::adaptive_load::MetricSpecWithThreshold* spec_threshold = + spec.mutable_metric_thresholds()->Add(); + spec_threshold->mutable_metric_spec()->set_metric_name("bogus"); + spec_threshold->mutable_metric_spec()->set_metrics_plugin_name("nighthawk.builtin"); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + absl::Status status = helper.CheckSessionSpec(spec); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_THAT(status.message(), HasSubstr("not implemented by plugin")); +} + +TEST(CheckSessionSpec, RejectsInformationalMetricWithNonexistentDefaultMetricsPluginMetric) { + AdaptiveLoadSessionSpec spec; + nighthawk::adaptive_load::MetricSpec* metric_spec = + spec.mutable_informational_metric_specs()->Add(); + metric_spec->set_metric_name("bogus"); + metric_spec->set_metrics_plugin_name("nighthawk.builtin"); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + absl::Status status = helper.CheckSessionSpec(spec); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_THAT(status.message(), HasSubstr("not implemented by plugin")); +} + +TEST(CheckSessionSpec, RejectsScoredMetricWithNonexistentCustomMetricsPluginMetric) { + AdaptiveLoadSessionSpec spec; + *spec.mutable_metrics_plugin_configs()->Add() = MakeFakeMetricsPluginTypedExtensionConfig( + nighthawk::adaptive_load::FakeMetricsPluginConfig()); + nighthawk::adaptive_load::MetricSpecWithThreshold* spec_threshold = + spec.mutable_metric_thresholds()->Add(); + spec_threshold->mutable_metric_spec()->set_metric_name("bogus"); + spec_threshold->mutable_metric_spec()->set_metrics_plugin_name("nighthawk.fake_metrics_plugin"); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + absl::Status status = helper.CheckSessionSpec(spec); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_THAT(status.message(), HasSubstr("not implemented by plugin")); +} + +TEST(CheckSessionSpec, RejectsInformationalMetricWithNonexistentCustomMetricsPluginMetric) { + AdaptiveLoadSessionSpec spec; + *spec.mutable_metrics_plugin_configs()->Add() = MakeFakeMetricsPluginTypedExtensionConfig( + nighthawk::adaptive_load::FakeMetricsPluginConfig()); + nighthawk::adaptive_load::MetricSpec* metric_spec = + spec.mutable_informational_metric_specs()->Add(); + metric_spec->set_metric_name("bogus"); + metric_spec->set_metrics_plugin_name("nighthawk.fake_metrics_plugin"); + AdaptiveLoadSessionSpecProtoHelperImpl helper; + absl::Status status = helper.CheckSessionSpec(spec); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_THAT(status.message(), HasSubstr("not implemented by plugin")); +} + +} // namespace +} // namespace Nighthawk