diff --git a/api/envoy/extensions/tracers/opentelemetry/samplers/v3/BUILD b/api/envoy/extensions/tracers/opentelemetry/samplers/v3/BUILD index 29ebf0741406e..09a37ad16b837 100644 --- a/api/envoy/extensions/tracers/opentelemetry/samplers/v3/BUILD +++ b/api/envoy/extensions/tracers/opentelemetry/samplers/v3/BUILD @@ -5,5 +5,8 @@ load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") licenses(["notice"]) # Apache 2 api_proto_package( - deps = ["@com_github_cncf_xds//udpa/annotations:pkg"], + deps = [ + "//envoy/config/core/v3:pkg", + "@com_github_cncf_xds//udpa/annotations:pkg", + ], ) diff --git a/api/envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.proto b/api/envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.proto index e0c37a7e51a46..cf93ab04ed8fd 100644 --- a/api/envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.proto +++ b/api/envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package envoy.extensions.tracers.opentelemetry.samplers.v3; +import "envoy/config/core/v3/http_uri.proto"; + import "udpa/annotations/status.proto"; option java_package = "io.envoyproxy.envoy.extensions.tracers.opentelemetry.samplers.v3"; @@ -17,4 +19,8 @@ message DynatraceSamplerConfig { string tenant_id = 1; string cluster_id = 2; + + config.core.v3.HttpUri http_uri = 3; + + string token = 4; } diff --git a/source/extensions/tracers/opentelemetry/samplers/BUILD b/source/extensions/tracers/opentelemetry/samplers/BUILD index 32a1005b11e80..729330bb3de1a 100644 --- a/source/extensions/tracers/opentelemetry/samplers/BUILD +++ b/source/extensions/tracers/opentelemetry/samplers/BUILD @@ -20,6 +20,7 @@ envoy_cc_library( "//envoy/server:tracer_config_interface", "//source/common/common:logger_lib", "//source/common/config:utility_lib", + "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", "@opentelemetry_proto//:trace_cc_proto", ], ) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD b/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD index 2d772e01cb9fe..69e62de7bd2c3 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD @@ -26,17 +26,21 @@ envoy_cc_library( srcs = [ "dynatrace_sampler.cc", "dynatrace_tracestate.cc", + "sampler_config_fetcher.cc", "tracestate.cc", ], hdrs = [ "dynatrace_sampler.h", "dynatrace_tracestate.h", + "sampler_config.h", + "sampler_config_fetcher.h", "tracestate.h", ], deps = [ "//source/common/config:datasource_lib", "//source/extensions/tracers/opentelemetry:opentelemetry_tracer_lib", "//source/extensions/tracers/opentelemetry/samplers:sampler_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/tracers/opentelemetry/samplers/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index a76a76810ecba..ca96afa61ec7d 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -30,9 +30,10 @@ FW4Tag DynatraceSampler::getFW4Tag(const Tracestate& tracestate) { DynatraceSampler::DynatraceSampler( const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig& config, - Server::Configuration::TracerFactoryContext& /*context*/) + Server::Configuration::TracerFactoryContext& context) : tenant_id_(config.tenant_id()), cluster_id_(config.cluster_id()), - dt_tracestate_entry_(tenant_id_, cluster_id_), counter_(0) {} + dt_tracestate_entry_(tenant_id_, cluster_id_), + sampler_config_fetcher_(context, config.http_uri(), config.token()), counter_(0) {} SamplingResult DynatraceSampler::shouldSample(const absl::optional parent_context, const std::string& /*trace_id*/, diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h index 33ae48fc9c55b..7c5e7323581a6 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -6,6 +6,7 @@ #include "source/common/common/logger.h" #include "source/common/config/datasource.h" #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.h" #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/tracestate.h" #include "source/extensions/tracers/opentelemetry/samplers/sampler.h" @@ -35,6 +36,7 @@ class DynatraceSampler : public Sampler, Logger::Loggable { std::string tenant_id_; std::string cluster_id_; DtTracestateEntry dt_tracestate_entry_; + SamplerConfigFetcher sampler_config_fetcher_; std::atomic counter_; // request counter for dummy sampling FW4Tag getFW4Tag(const Tracestate& tracestate); }; diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h new file mode 100644 index 0000000000000..4b0ec5a9a1a31 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include +#include + +#include "source/common/json/json_loader.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +class SamplerConfig { +public: + static constexpr uint64_t ROOT_SPANS_PER_MINUTE_DEFAULT = 1000; + + void parse(const std::string& json) { + root_spans_per_minute_.store(ROOT_SPANS_PER_MINUTE_DEFAULT); // reset to default + auto result = Envoy::Json::Factory::loadFromStringNoThrow(json); + if (result.ok()) { + auto obj = result.value(); + if (obj->hasObject("rootSpansPerMinute")) { + auto value = obj->getInteger("rootSpansPerMinute", ROOT_SPANS_PER_MINUTE_DEFAULT); + root_spans_per_minute_.store(value); + } + (void)obj; + } + } + + uint64_t getRootSpansPerMinute() const { return root_spans_per_minute_.load(); } + +private: + std::atomic root_spans_per_minute_ = ROOT_SPANS_PER_MINUTE_DEFAULT; +}; + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc new file mode 100644 index 0000000000000..7c73c058ee9f2 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc @@ -0,0 +1,77 @@ +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.h" + +#include "source/common/common/enum_to_int.h" +#include "source/common/http/utility.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +static constexpr std::chrono::seconds INITIAL_TIMER_DURATION{10}; +static constexpr std::chrono::minutes TIMER_INTERVAL{5}; + +SamplerConfigFetcher::SamplerConfigFetcher(Server::Configuration::TracerFactoryContext& context, + const envoy::config::core::v3::HttpUri& http_uri, + const std::string& token) + : cluster_manager_(context.serverFactoryContext().clusterManager()), http_uri_(http_uri), + parsed_authorization_header_to_add_( + {Http::LowerCaseString("authorization"), absl::StrCat("Api-Token ", token)}), + sampler_config_() { + + timer_ = context.serverFactoryContext().mainThreadDispatcher().createTimer([this]() -> void { + const auto thread_local_cluster = cluster_manager_.getThreadLocalCluster(http_uri_.cluster()); + if (thread_local_cluster == nullptr) { + ENVOY_LOG(error, "SamplerConfigFetcher failed: [cluster = {}] is not configured", + http_uri_.cluster()); + } else { + Http::RequestMessagePtr message = Http::Utility::prepareHeaders(http_uri_); + // TODO: set path once it is finalized in TI-8742 + // message->headers().setPath("path/to/sampler/service"); + message->headers().setReferenceMethod(Http::Headers::get().MethodValues.Get); + message->headers().setReference(parsed_authorization_header_to_add_.first, + parsed_authorization_header_to_add_.second); + active_request_ = thread_local_cluster->httpAsyncClient().send( + std::move(message), *this, + Http::AsyncClient::RequestOptions().setTimeout(std::chrono::milliseconds(6000))); + } + }); + + timer_->enableTimer(std::chrono::seconds(INITIAL_TIMER_DURATION)); +} + +SamplerConfigFetcher::~SamplerConfigFetcher() { + if (active_request_) { + active_request_->cancel(); + } +} + +void SamplerConfigFetcher::onSuccess(const Http::AsyncClient::Request& /*request*/, + Http::ResponseMessagePtr&& http_response) { + onRequestDone(); + const auto response_code = Http::Utility::getResponseStatus(http_response->headers()); + if (response_code != enumToInt(Http::Code::OK)) { + ENVOY_LOG(error, "SamplerConfigFetcher received a non-success status code: {}", response_code); + } else { + ENVOY_LOG(info, "SamplerConfigFetcher received success status code: {}", response_code); + sampler_config_.parse(http_response->bodyAsString()); + } +} + +void SamplerConfigFetcher::onFailure(const Http::AsyncClient::Request& /*request*/, + Http::AsyncClient::FailureReason reason) { + onRequestDone(); + ENVOY_LOG(info, "The OTLP export request failed. Reason {}", enumToInt(reason)); +} + +void SamplerConfigFetcher::onRequestDone() { + // TODO: should we re-enable timer after send() to avoid having the request duration added to the + // timer? If so, we would need a list containing the active requests (not a single pointer) + active_request_ = nullptr; + timer_->enableTimer(std::chrono::seconds(TIMER_INTERVAL)); +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.h new file mode 100644 index 0000000000000..3425f72f20b84 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +#include "envoy/config/core/v3/http_service.pb.h" +#include "envoy/config/core/v3/http_uri.pb.h" +#include "envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.pb.h" +#include "envoy/http/async_client.h" +#include "envoy/http/message.h" +#include "envoy/server/tracer_config.h" + +#include "source/common/http/async_client_impl.h" +#include "source/common/http/async_client_utility.h" +#include "source/common/http/headers.h" +#include "source/common/http/message_impl.h" +#include "source/common/http/utility.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +class SamplerConfigFetcher : public Logger::Loggable, + public Http::AsyncClient::Callbacks { +public: + SamplerConfigFetcher(Server::Configuration::TracerFactoryContext& context, + const envoy::config::core::v3::HttpUri& http_uri, const std::string& token); + + void onSuccess(const Http::AsyncClient::Request& request, + Http::ResponseMessagePtr&& response) override; + + void onFailure(const Http::AsyncClient::Request& request, + Http::AsyncClient::FailureReason reason) override; + void onBeforeFinalizeUpstreamSpan(Envoy::Tracing::Span& /*span*/, + const Http::ResponseHeaderMap* /*response_headers*/) override{}; + + const SamplerConfig& getSamplerConfig() const { return sampler_config_; } + + ~SamplerConfigFetcher() override; + +private: + Event::TimerPtr timer_; + Upstream::ClusterManager& cluster_manager_; + envoy::config::core::v3::HttpUri http_uri_; + std::pair parsed_authorization_header_to_add_; + Http::AsyncClient::Request* active_request_{}; + SamplerConfig sampler_config_; + + void onRequestDone(); +}; + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/sampler.h b/source/extensions/tracers/opentelemetry/samplers/sampler.h index 33794645344ec..2eadb9734a3ff 100644 --- a/source/extensions/tracers/opentelemetry/samplers/sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/sampler.h @@ -6,6 +6,7 @@ #include #include "envoy/common/optref.h" +#include "envoy/config/trace/v3/opentelemetry.pb.h" #include "envoy/config/typed_config.h" #include "envoy/server/tracer_config.h" #include "envoy/tracing/trace_context.h" diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD b/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD index e1d099cfc05d1..6677842baef60 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD @@ -29,6 +29,8 @@ envoy_extension_cc_test( srcs = [ "dynatrace_sampler_test.cc", "dynatrace_tracestate_test.cc", + "sampler_config_fetcher_test.cc", + "sampler_config_test.cc", "tracestate_test.cc", ], extension_names = ["envoy.tracers.opentelemetry.samplers.dynatrace"], @@ -36,6 +38,7 @@ envoy_extension_cc_test( "//source/extensions/tracers/opentelemetry/samplers/dynatrace:dynatrace_sampler_lib", "//test/mocks/server:tracer_factory_context_mocks", "//test/test_common:utility_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/tracers/opentelemetry/samplers/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/config_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/config_test.cc index f4aa709296034..acd486727d759 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/config_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/config_test.cc @@ -20,12 +20,13 @@ TEST(DynatraceSamplerFactoryTest, Test) { EXPECT_NE(factory->createEmptyConfigProto(), nullptr); envoy::config::core::v3::TypedExtensionConfig typed_config; - const std::string yaml = R"EOF( + const std::string sampler_yaml = R"EOF( name: envoy.tracers.opentelemetry.samplers.dynatrace typed_config: "@type": type.googleapis.com/envoy.extensions.tracers.opentelemetry.samplers.v3.DynatraceSamplerConfig )EOF"; - TestUtility::loadFromYaml(yaml, typed_config); + TestUtility::loadFromYaml(sampler_yaml, typed_config); + NiceMock context; EXPECT_NE(factory->createSampler(typed_config.typed_config(), context), nullptr); EXPECT_STREQ(factory->name().c_str(), "envoy.tracers.opentelemetry.samplers.dynatrace"); diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher_test.cc new file mode 100644 index 0000000000000..709f42624da28 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher_test.cc @@ -0,0 +1,129 @@ +#include +#include +#include + +#include "envoy/config/core/v3/http_uri.pb.h" + +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.h" + +#include "test/mocks/common.h" +#include "test/mocks/http/mocks.h" +#include "test/mocks/server/tracer_factory_context.h" +#include "test/mocks/tracing/mocks.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +class SamplerConfigFetcherTest : public testing::Test { +public: + SamplerConfigFetcherTest() + : request_(&tracerFactoryContext_.server_factory_context_.cluster_manager_ + .thread_local_cluster_.async_client_) { + const std::string yaml_string = R"EOF( + cluster: "cluster_name" + uri: "https://testhost.com/otlp/v1/traces" + timeout: 0.250s + )EOF"; + TestUtility::loadFromYaml(yaml_string, http_uri_); + + ON_CALL(tracerFactoryContext_.server_factory_context_.cluster_manager_, + getThreadLocalCluster(_)) + .WillByDefault(Return( + &tracerFactoryContext_.server_factory_context_.cluster_manager_.thread_local_cluster_)); + timer_ = + new NiceMock(&tracerFactoryContext_.server_factory_context_.dispatcher_); + ON_CALL(tracerFactoryContext_.server_factory_context_.dispatcher_, createTimer_(_)) + .WillByDefault(Invoke([this](Event::TimerCb) { return timer_; })); + } + +protected: + NiceMock tracerFactoryContext_; + envoy::config::core::v3::HttpUri http_uri_; + NiceMock* timer_; + Http::MockAsyncClientRequest request_; +}; + +MATCHER_P(MessageMatcher, unusedArg, "") { + // prefix 'Api-Token' should be added to 'tokenval' set via SamplerConfigFetcher constructor + const Http::LowerCaseString auth{"authorization"}; + return (arg->headers().get(auth)[0]->value().getStringView() == "Api-Token tokenval") && + (arg->headers().get(Http::Headers::get().Path)[0]->value().getStringView() == + "/otlp/v1/traces") && + (arg->headers().get(Http::Headers::get().Host)[0]->value().getStringView() == + "testhost.com") && + (arg->headers().get(Http::Headers::get().Method)[0]->value().getStringView() == "GET"); +} + +// Test a request is sent if timer fires +TEST_F(SamplerConfigFetcherTest, TestRequestIsSent) { + EXPECT_CALL(tracerFactoryContext_.server_factory_context_.cluster_manager_.thread_local_cluster_ + .async_client_, + send_(MessageMatcher("unused-but-machtes-requires-an-arg"), _, _)); + SamplerConfigFetcher configFetcher(tracerFactoryContext_, http_uri_, "tokenval"); + timer_->invokeCallback(); +} + +// Test receiving a response with code 200 and valid json +TEST_F(SamplerConfigFetcherTest, TestResponseOk) { + SamplerConfigFetcher configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); + timer_->invokeCallback(); + + Http::ResponseMessagePtr message(new Http::ResponseMessageImpl( + Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); + message->body().add("{\n \"rootSpansPerMinute\" : 4356 \n }"); + configFetcher.onSuccess(request_, std::move(message)); + EXPECT_EQ(configFetcher.getSamplerConfig().getRootSpansPerMinute(), 4356); + EXPECT_TRUE(timer_->enabled()); +} + +// Test receiving a response with code 200 and unexpected json +TEST_F(SamplerConfigFetcherTest, TestResponseOkInvalidJson) { + SamplerConfigFetcher configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); + timer_->invokeCallback(); + + Http::ResponseMessagePtr message(new Http::ResponseMessageImpl( + Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); + message->body().add("{\n "); + configFetcher.onSuccess(request_, std::move(message)); + EXPECT_EQ(configFetcher.getSamplerConfig().getRootSpansPerMinute(), + SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + EXPECT_TRUE(timer_->enabled()); +} + +// Test receiving a response with code != 200 +TEST_F(SamplerConfigFetcherTest, TestResponseErrorCode) { + SamplerConfigFetcher configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); + timer_->invokeCallback(); + + Http::ResponseMessagePtr message(new Http::ResponseMessageImpl( + Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "401"}}})); + message->body().add("{\n \"rootSpansPerMinute\" : 4356 \n }"); + configFetcher.onSuccess(request_, std::move(message)); + EXPECT_EQ(configFetcher.getSamplerConfig().getRootSpansPerMinute(), + SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + EXPECT_TRUE(timer_->enabled()); +} + +// Test sending failed +TEST_F(SamplerConfigFetcherTest, TestOnFailure) { + SamplerConfigFetcher configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); + timer_->invokeCallback(); + configFetcher.onFailure(request_, Http::AsyncClient::FailureReason::Reset); + EXPECT_EQ(configFetcher.getSamplerConfig().getRootSpansPerMinute(), + SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + EXPECT_TRUE(timer_->enabled()); +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_test.cc new file mode 100644 index 0000000000000..704396fde979c --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_test.cc @@ -0,0 +1,41 @@ +#include +#include + +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +// Test sampler config json parsing +TEST(SamplerConfigTest, test) { + SamplerConfig config; + config.parse("{\n \"rootSpansPerMinute\" : 2000 \n }"); + EXPECT_EQ(config.getRootSpansPerMinute(), 2000u); + config.parse("{\n \"rootSpansPerMinute\" : 10000 \n }"); + EXPECT_EQ(config.getRootSpansPerMinute(), 10000u); + + // unexpected json, default value should be used + config.parse("{}"); + EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + + config.parse(""); + EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + + config.parse("\\"); + EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + + config.parse(" { "); + EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + + config.parse(" } "); + EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy