diff --git a/RAW_RELEASE_NOTES.md b/RAW_RELEASE_NOTES.md index c58558bef7fee..acc61c73fb96c 100644 --- a/RAW_RELEASE_NOTES.md +++ b/RAW_RELEASE_NOTES.md @@ -63,6 +63,7 @@ final version. * Added the ability to pass a URL encoded Pem encoded peer certificate in the x-forwarded-client-cert header. * Added support for abstract unix domain sockets on linux. The abstract namespace can be used by prepending '@' to a socket path. +* Added support for dynamically loading a tracer. * Added `GEORADIUS_RO` and `GEORADIUSBYMEMBER_RO` to the Redis command splitter whitelist. * Added support for trusting additional hops in the X-Forwarded-For request header. * Added setting host header value for http health check request. diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 05b7da29ca07c..7a2f16a057e07 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -39,8 +39,8 @@ REPOSITORY_LOCATIONS = dict( remote = "https://github.com/grpc/grpc.git", ), io_opentracing_cpp = dict( - commit = "e57161e2a4bd1f9d3a8d3edf23185f033bb45f17", - remote = "https://github.com/opentracing/opentracing-cpp", # v1.2.0 + commit = "f3c1f42601d13504c68e2bc81c60604f0de055dd", + remote = "https://github.com/opentracing/opentracing-cpp", ), com_lightstep_tracer_cpp = dict( commit = "6a198acd328f976984699f7272bbec7c8b220f65", diff --git a/source/common/config/well_known_names.h b/source/common/config/well_known_names.h index 43757ad4666b5..99dd2ee1c2d27 100644 --- a/source/common/config/well_known_names.h +++ b/source/common/config/well_known_names.h @@ -158,6 +158,8 @@ class HttpTracerNameValues { const std::string LIGHTSTEP = "envoy.lightstep"; // Zipkin tracer const std::string ZIPKIN = "envoy.zipkin"; + // Dynamic tracer + const std::string DYNAMIC_OT = "envoy.dynamic.ot"; }; typedef ConstSingleton HttpTracerNames; diff --git a/source/common/tracing/BUILD b/source/common/tracing/BUILD index 823a42552c017..8d7c4431322e8 100644 --- a/source/common/tracing/BUILD +++ b/source/common/tracing/BUILD @@ -53,6 +53,20 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "dynamic_opentracing_driver_lib", + srcs = [ + "dynamic_opentracing_driver_impl.cc", + ], + hdrs = [ + "dynamic_opentracing_driver_impl.h", + ], + deps = [ + ":http_tracer_lib", + ":opentracing_driver_lib", + ], +) + envoy_cc_library( name = "lightstep_tracer_lib", srcs = [ diff --git a/source/common/tracing/dynamic_opentracing_driver_impl.cc b/source/common/tracing/dynamic_opentracing_driver_impl.cc new file mode 100644 index 0000000000000..132f8628cc6a9 --- /dev/null +++ b/source/common/tracing/dynamic_opentracing_driver_impl.cc @@ -0,0 +1,38 @@ +#include "common/tracing/dynamic_opentracing_driver_impl.h" + +#include "common/common/assert.h" + +namespace Envoy { +namespace Tracing { + +DynamicOpenTracingDriver::DynamicOpenTracingDriver(Stats::Store& stats, const std::string& library, + const std::string& tracer_config) + : OpenTracingDriver{stats} { + std::string error_message; + opentracing::expected library_handle_maybe = + opentracing::DynamicallyLoadTracingLibrary(library.c_str(), error_message); + if (!library_handle_maybe) { + throw EnvoyException{formatErrorMessage(library_handle_maybe.error(), error_message)}; + } + library_handle_ = std::move(*library_handle_maybe); + + opentracing::expected> tracer_maybe = + library_handle_.tracer_factory().MakeTracer(tracer_config.c_str(), error_message); + if (!tracer_maybe) { + throw EnvoyException{formatErrorMessage(tracer_maybe.error(), error_message)}; + } + tracer_ = std::move(*tracer_maybe); + RELEASE_ASSERT(tracer_ != nullptr); +} + +std::string DynamicOpenTracingDriver::formatErrorMessage(std::error_code error_code, + const std::string& error_message) { + if (error_message.empty()) { + return fmt::format("{}", error_code.message()); + } else { + return fmt::format("{}: {}", error_code.message(), error_message); + } +} + +} // namespace Tracing +} // namespace Envoy diff --git a/source/common/tracing/dynamic_opentracing_driver_impl.h b/source/common/tracing/dynamic_opentracing_driver_impl.h new file mode 100644 index 0000000000000..b6f51e5a1b143 --- /dev/null +++ b/source/common/tracing/dynamic_opentracing_driver_impl.h @@ -0,0 +1,41 @@ +#pragma once + +#include "envoy/runtime/runtime.h" +#include "envoy/thread_local/thread_local.h" +#include "envoy/tracing/http_tracer.h" +#include "envoy/upstream/cluster_manager.h" + +#include "common/tracing/opentracing_driver_impl.h" + +#include "opentracing/dynamic_load.h" + +namespace Envoy { +namespace Tracing { + +/** + * This driver provides support for dynamically loading tracing libraries into Envoy that provide an + * implementation of the OpenTracing API (see https://github.com/opentracing/opentracing-cpp). + * TODO(rnburn): Add an example showing how to use a tracer library with this driver. + */ +class DynamicOpenTracingDriver : public OpenTracingDriver { +public: + DynamicOpenTracingDriver(Stats::Store& stats, const std::string& library, + const std::string& tracer_config); + + static std::string formatErrorMessage(std::error_code error_code, + const std::string& error_message); + + // Tracer::OpenTracingDriver + opentracing::Tracer& tracer() override { return *tracer_; } + + PropagationMode propagationMode() const override { + return OpenTracingDriver::PropagationMode::TracerNative; + } + +private: + opentracing::DynamicTracingLibraryHandle library_handle_; + std::shared_ptr tracer_; +}; + +} // namespace Tracing +} // namespace Envoy diff --git a/source/common/tracing/lightstep_tracer_impl.cc b/source/common/tracing/lightstep_tracer_impl.cc index 49f719e4d6439..6d3b4069dec0b 100644 --- a/source/common/tracing/lightstep_tracer_impl.cc +++ b/source/common/tracing/lightstep_tracer_impl.cc @@ -15,25 +15,21 @@ namespace Envoy { namespace Tracing { -namespace { -class LightStepLogger : Logger::Loggable { -public: - void operator()(lightstep::LogLevel level, opentracing::string_view message) const { - const fmt::StringRef fmt_message{message.data(), message.size()}; - switch (level) { - case lightstep::LogLevel::debug: - ENVOY_LOG(debug, "{}", fmt_message); - break; - case lightstep::LogLevel::info: - ENVOY_LOG(info, "{}", fmt_message); - break; - default: - ENVOY_LOG(warn, "{}", fmt_message); - break; - } +void LightStepLogger::operator()(lightstep::LogLevel level, + opentracing::string_view message) const { + const fmt::StringRef fmt_message{message.data(), message.size()}; + switch (level) { + case lightstep::LogLevel::debug: + ENVOY_LOG(debug, "{}", fmt_message); + break; + case lightstep::LogLevel::info: + ENVOY_LOG(info, "{}", fmt_message); + break; + default: + ENVOY_LOG(warn, "{}", fmt_message); + break; } -}; -} // namespace +} LightStepDriver::LightStepTransporter::LightStepTransporter(LightStepDriver& driver) : driver_(driver) {} @@ -68,8 +64,6 @@ void LightStepDriver::LightStepTransporter::onSuccess(Http::MessagePtr&& respons active_request_ = nullptr; Grpc::Common::validateResponse(*response); - Grpc::Common::chargeStat(*driver_.cluster(), lightstep::CollectorServiceFullName(), - lightstep::CollectorMethodName(), true); // http://www.grpc.io/docs/guides/wire.html // First 5 bytes contain the message header. response->body()->drain(5); @@ -77,11 +71,17 @@ void LightStepDriver::LightStepTransporter::onSuccess(Http::MessagePtr&& respons if (!active_response_->ParseFromZeroCopyStream(&stream)) { throw EnvoyException("Failed to parse LightStep collector response"); } + Grpc::Common::chargeStat(*driver_.cluster(), lightstep::CollectorServiceFullName(), + lightstep::CollectorMethodName(), true); active_callback_->OnSuccess(); } catch (const Grpc::Exception& ex) { Grpc::Common::chargeStat(*driver_.cluster(), lightstep::CollectorServiceFullName(), lightstep::CollectorMethodName(), false); active_callback_->OnFailure(std::make_error_code(std::errc::network_down)); + } catch (const EnvoyException& ex) { + Grpc::Common::chargeStat(*driver_.cluster(), lightstep::CollectorServiceFullName(), + lightstep::CollectorMethodName(), false); + active_callback_->OnFailure(std::make_error_code(std::errc::bad_message)); } } @@ -112,7 +112,7 @@ LightStepDriver::TlsLightStepTracer::TlsLightStepTracer( enableTimer(); } -const opentracing::Tracer& LightStepDriver::TlsLightStepTracer::tracer() const { return *tracer_; } +opentracing::Tracer& LightStepDriver::TlsLightStepTracer::tracer() { return *tracer_; } void LightStepDriver::TlsLightStepTracer::enableTimer() { const uint64_t flush_interval = @@ -160,7 +160,7 @@ LightStepDriver::LightStepDriver(const Json::Object& config, }); } -const opentracing::Tracer& LightStepDriver::tracer() const { +opentracing::Tracer& LightStepDriver::tracer() { return tls_->getTyped().tracer(); } diff --git a/source/common/tracing/lightstep_tracer_impl.h b/source/common/tracing/lightstep_tracer_impl.h index 46dddbeddcdce..ec4c5cf07d2ce 100644 --- a/source/common/tracing/lightstep_tracer_impl.h +++ b/source/common/tracing/lightstep_tracer_impl.h @@ -31,6 +31,14 @@ struct LightstepTracerStats { LIGHTSTEP_TRACER_STATS(GENERATE_COUNTER_STRUCT) }; +/** + * LightStepLogger is used to translate logs generated from LightStep's tracer to Envoy logs. + */ +class LightStepLogger : Logger::Loggable { +public: + void operator()(lightstep::LogLevel level, opentracing::string_view message) const; +}; + /** * LightStep (http://lightstep.com/) provides tracing capabilities, aggregation, visualization of * application trace data. @@ -50,7 +58,7 @@ class LightStepDriver : public OpenTracingDriver { LightstepTracerStats& tracerStats() { return tracer_stats_; } // Tracer::OpenTracingDriver - const opentracing::Tracer& tracer() const override; + opentracing::Tracer& tracer() override; PropagationMode propagationMode() const override { return propagation_mode_; } private: @@ -90,7 +98,7 @@ class LightStepDriver : public OpenTracingDriver { TlsLightStepTracer(const std::shared_ptr& tracer, LightStepDriver& driver, Event::Dispatcher& dispatcher); - const opentracing::Tracer& tracer() const; + opentracing::Tracer& tracer(); private: void enableTimer(); diff --git a/source/common/tracing/opentracing_driver_impl.h b/source/common/tracing/opentracing_driver_impl.h index f843eb868b7cb..e0f262a09231d 100644 --- a/source/common/tracing/opentracing_driver_impl.h +++ b/source/common/tracing/opentracing_driver_impl.h @@ -51,7 +51,7 @@ class OpenTracingDriver : public Driver, protected Logger::Loggable + +#include "envoy/registry/registry.h" + +#include "common/common/utility.h" +#include "common/config/well_known_names.h" +#include "common/tracing/dynamic_opentracing_driver_impl.h" +#include "common/tracing/http_tracer_impl.h" + +namespace Envoy { +namespace Server { +namespace Configuration { + +Tracing::HttpTracerPtr DynamicOpenTracingHttpTracerFactory::createHttpTracer( + const Json::Object& json_config, Server::Instance& server, + Upstream::ClusterManager& /*cluster_manager*/) { + const std::string library = json_config.getString("library"); + const std::string config = json_config.getObject("config")->asJsonString(); + Tracing::DriverPtr dynamic_driver{ + std::make_unique(server.stats(), library, config)}; + return std::make_unique(std::move(dynamic_driver), server.localInfo()); +} + +std::string DynamicOpenTracingHttpTracerFactory::name() { + return Config::HttpTracerNames::get().DYNAMIC_OT; +} + +/** + * Static registration for the dynamic opentracing http tracer. @see RegisterFactory. + */ +static Registry::RegisterFactory register_; + +} // namespace Configuration +} // namespace Server +} // namespace Envoy diff --git a/source/server/config/http/dynamic_opentracing_http_tracer.h b/source/server/config/http/dynamic_opentracing_http_tracer.h new file mode 100644 index 0000000000000..e3db4cb1bd07d --- /dev/null +++ b/source/server/config/http/dynamic_opentracing_http_tracer.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "envoy/server/instance.h" + +#include "server/configuration_impl.h" + +namespace Envoy { +namespace Server { +namespace Configuration { + +/** + * Config registration for the dynamic opentracing tracer. @see HttpTracerFactory. + */ +class DynamicOpenTracingHttpTracerFactory : public HttpTracerFactory { +public: + // HttpTracerFactory + Tracing::HttpTracerPtr createHttpTracer(const Json::Object& json_config, Server::Instance& server, + Upstream::ClusterManager& cluster_manager) override; + + std::string name() override; + + bool requiresClusterName() const override { return false; } +}; + +} // namespace Configuration +} // namespace Server +} // namespace Envoy diff --git a/source/server/configuration_impl.cc b/source/server/configuration_impl.cc index b671bec6c0b7f..38c530a388378 100644 --- a/source/server/configuration_impl.cc +++ b/source/server/configuration_impl.cc @@ -96,11 +96,6 @@ void MainImpl::initializeTracers(const envoy::config::trace::v2::Tracing& config return; } - if (server.localInfo().clusterName().empty()) { - throw EnvoyException("cluster name must be defined if tracing is enabled. See " - "--service-cluster option."); - } - // Initialize tracing driver. std::string type = configuration.http().name(); ENVOY_LOG(info, " loading tracing driver: {}", type); @@ -111,6 +106,11 @@ void MainImpl::initializeTracers(const envoy::config::trace::v2::Tracing& config // Now see if there is a factory that will accept the config. auto& factory = Config::Utility::getAndCheckFactory(type); + if (factory.requiresClusterName() && server.localInfo().clusterName().empty()) { + throw EnvoyException(fmt::format("cluster name must be defined for the tracing driver {}. See " + "--service-cluster option.", + type)); + } http_tracer_ = factory.createHttpTracer(*driver_config, server, *cluster_manager_); } diff --git a/source/server/configuration_impl.h b/source/server/configuration_impl.h index 3adf670e80469..0cdcb664415c3 100644 --- a/source/server/configuration_impl.h +++ b/source/server/configuration_impl.h @@ -54,6 +54,11 @@ class HttpTracerFactory { * factory. */ virtual std::string name() PURE; + + /** + * Returns true if the tracing driver requires cluster name to be defined. + */ + virtual bool requiresClusterName() const { return true; } }; /** diff --git a/test/common/tracing/BUILD b/test/common/tracing/BUILD index ec7031afa005b..81661d78e6262 100644 --- a/test/common/tracing/BUILD +++ b/test/common/tracing/BUILD @@ -32,6 +32,20 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "opentracing_driver_impl_test", + srcs = [ + "opentracing_driver_impl_test.cc", + ], + deps = [ + "//source/common/tracing:dynamic_opentracing_driver_lib", + "//test/mocks/http:http_mocks", + "//test/mocks/stats:stats_mocks", + "//test/mocks/tracing:tracing_mocks", + "@io_opentracing_cpp//mocktracer", + ], +) + envoy_cc_test( name = "lightstep_tracer_impl_test", srcs = [ @@ -55,3 +69,21 @@ envoy_cc_test( "//test/test_common:utility_lib", ], ) + +envoy_cc_test( + name = "dynamic_opentracing_driver_impl_test", + srcs = [ + "dynamic_opentracing_driver_impl_test.cc", + ], + data = [ + "@io_opentracing_cpp//mocktracer:libmocktracer_plugin.so", + ], + deps = [ + "//source/common/http:header_map_lib", + "//source/common/tracing:dynamic_opentracing_driver_lib", + "//test/mocks/http:http_mocks", + "//test/mocks/stats:stats_mocks", + "//test/mocks/tracing:tracing_mocks", + "//test/test_common:environment_lib", + ], +) diff --git a/test/common/tracing/dynamic_opentracing_driver_impl_test.cc b/test/common/tracing/dynamic_opentracing_driver_impl_test.cc new file mode 100644 index 0000000000000..8a2a193311266 --- /dev/null +++ b/test/common/tracing/dynamic_opentracing_driver_impl_test.cc @@ -0,0 +1,84 @@ +#include "common/http/header_map_impl.h" +#include "common/tracing/dynamic_opentracing_driver_impl.h" + +#include "test/mocks/http/mocks.h" +#include "test/mocks/stats/mocks.h" +#include "test/mocks/tracing/mocks.h" +#include "test/test_common/environment.h" + +#include "fmt/printf.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::Test; + +namespace Envoy { +namespace Tracing { + +class DynamicOpenTracingDriverTest : public Test { +public: + void setup(const std::string& library, const std::string& tracer_config) { + driver_.reset(new DynamicOpenTracingDriver{stats_, library, tracer_config}); + } + + void setupValidDriver() { setup(library_path_, tracer_config_); } + + const std::string library_path_ = + TestEnvironment::runfilesDirectory() + + "/external/io_opentracing_cpp/mocktracer/libmocktracer_plugin.so"; + const std::string spans_file_ = TestEnvironment::temporaryDirectory() + "/spans.json"; + const std::string tracer_config_ = fmt::sprintf(R"EOF( + { + "output_file": "%s" + } + )EOF", + spans_file_); + std::unique_ptr driver_; + Stats::IsolatedStoreImpl stats_; + + const std::string operation_name_{"test"}; + Http::TestHeaderMapImpl request_headers_{ + {":path", "/"}, {":method", "GET"}, {"x-request-id", "foo"}}; + SystemTime start_time_; + NiceMock config_; +}; + +TEST_F(DynamicOpenTracingDriverTest, formatErrorMessage) { + const std::error_code error_code = std::make_error_code(std::errc::permission_denied); + EXPECT_EQ(error_code.message(), DynamicOpenTracingDriver::formatErrorMessage(error_code, "")); + EXPECT_EQ(error_code.message() + ": abc", + DynamicOpenTracingDriver::formatErrorMessage(error_code, "abc")); +} + +TEST_F(DynamicOpenTracingDriverTest, InitializeDriver) { + { + std::string invalid_library = "abc123"; + std::string invalid_config = R"EOF( + {"fake" : "fake"} + )EOF"; + + EXPECT_THROW(setup(invalid_library, invalid_config), EnvoyException); + } + + { + std::string empty_config = "{}"; + + EXPECT_THROW(setup(library_path_, empty_config), EnvoyException); + } +} + +TEST_F(DynamicOpenTracingDriverTest, FlushSpans) { + setupValidDriver(); + + SpanPtr first_span = driver_->startSpan(config_, request_headers_, operation_name_, start_time_); + first_span->finishSpan(); + driver_->tracer().Close(); + + const Json::ObjectSharedPtr spans_json = + TestEnvironment::jsonLoadFromString(TestEnvironment::readFileToStringForTest(spans_file_)); + EXPECT_NE(spans_json, nullptr); + EXPECT_EQ(spans_json->asObjectArray().size(), 1); +} + +} // namespace Tracing +} // namespace Envoy diff --git a/test/common/tracing/lightstep_tracer_impl_test.cc b/test/common/tracing/lightstep_tracer_impl_test.cc index e878cdbf6a832..67c36149d19a3 100644 --- a/test/common/tracing/lightstep_tracer_impl_test.cc +++ b/test/common/tracing/lightstep_tracer_impl_test.cc @@ -92,6 +92,15 @@ class LightStepDriverTest : public Test { NiceMock config_; }; +TEST_F(LightStepDriverTest, LightStepLogger) { + LightStepLogger logger; + + // Verify calls to logger don't crash. + logger(lightstep::LogLevel::debug, "abc"); + logger(lightstep::LogLevel::info, "abc"); + logger(lightstep::LogLevel::error, "abc"); +} + TEST_F(LightStepDriverTest, InitializeDriver) { { std::string invalid_config = R"EOF( @@ -236,12 +245,54 @@ TEST_F(LightStepDriverTest, FlushOneFailure) { first_span->finishSpan(); + callback->onFailure(Http::AsyncClient::FailureReason::Reset); + + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("grpc.lightstep.collector.CollectorService.Report.failure") + .value()); + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("grpc.lightstep.collector.CollectorService.Report.total") + .value()); + EXPECT_EQ(1U, stats_.counter("tracing.lightstep.spans_sent").value()); +} + +TEST_F(LightStepDriverTest, FlushOneInvalidResponse) { + setupValidDriver(); + + Http::MockAsyncClientRequest request(&cm_.async_client_); + Http::AsyncClient::Callbacks* callback; + const Optional timeout(std::chrono::seconds(5)); + + EXPECT_CALL(cm_.async_client_, send_(_, _, timeout)) + .WillOnce( + Invoke([&](Http::MessagePtr& message, Http::AsyncClient::Callbacks& callbacks, + const Optional&) -> Http::AsyncClient::Request* { + callback = &callbacks; + + EXPECT_STREQ("/lightstep.collector.CollectorService/Report", + message->headers().Path()->value().c_str()); + EXPECT_STREQ("fake_cluster", message->headers().Host()->value().c_str()); + EXPECT_STREQ("application/grpc", message->headers().ContentType()->value().c_str()); + + return &request; + })); + + EXPECT_CALL(runtime_.snapshot_, getInteger("tracing.lightstep.min_flush_spans", 5)) + .WillOnce(Return(1)); + EXPECT_CALL(runtime_.snapshot_, getInteger("tracing.lightstep.request_timeout", 5000U)) + .WillOnce(Return(5000U)); + + SpanPtr first_span = driver_->startSpan(config_, request_headers_, operation_name_, start_time_); + + first_span->finishSpan(); + Http::MessagePtr msg(new Http::ResponseMessageImpl( Http::HeaderMapPtr{new Http::TestHeaderMapImpl{{":status", "200"}}})); msg->trailers(Http::HeaderMapPtr{new Http::TestHeaderMapImpl{{"grpc-status", "0"}}}); + msg->body() = std::make_unique("invalidresponse"); - callback->onFailure(Http::AsyncClient::FailureReason::Reset); + callback->onSuccess(std::move(msg)); EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ .counter("grpc.lightstep.collector.CollectorService.Report.failure") diff --git a/test/common/tracing/opentracing_driver_impl_test.cc b/test/common/tracing/opentracing_driver_impl_test.cc new file mode 100644 index 0000000000000..925b5c0a92d81 --- /dev/null +++ b/test/common/tracing/opentracing_driver_impl_test.cc @@ -0,0 +1,115 @@ +#include "common/tracing/opentracing_driver_impl.h" + +#include "test/mocks/http/mocks.h" +#include "test/mocks/stats/mocks.h" +#include "test/mocks/tracing/mocks.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "opentracing/mocktracer/in_memory_recorder.h" +#include "opentracing/mocktracer/tracer.h" + +using testing::Test; + +namespace Envoy { +namespace Tracing { + +class TestDriver : public OpenTracingDriver { +public: + TestDriver(OpenTracingDriver::PropagationMode propagation_mode, + const opentracing::mocktracer::PropagationOptions& propagation_options, + Stats::Store& stats) + : OpenTracingDriver{stats}, propagation_mode_{propagation_mode} { + opentracing::mocktracer::MockTracerOptions options; + auto recorder = new opentracing::mocktracer::InMemoryRecorder{}; + recorder_ = recorder; + options.recorder.reset(recorder); + options.propagation_options = propagation_options; + tracer_.reset(new opentracing::mocktracer::MockTracer{std::move(options)}); + } + + const opentracing::mocktracer::InMemoryRecorder& recorder() const { return *recorder_; } + + // OpenTracingDriver + opentracing::Tracer& tracer() override { return *tracer_; } + + PropagationMode propagationMode() const override { return propagation_mode_; } + +private: + const OpenTracingDriver::PropagationMode propagation_mode_; + const opentracing::mocktracer::InMemoryRecorder* recorder_; + std::shared_ptr tracer_; +}; + +class OpenTracingDriverTest : public Test { +public: + void + setupValidDriver(OpenTracingDriver::PropagationMode propagation_mode = + OpenTracingDriver::PropagationMode::SingleHeader, + const opentracing::mocktracer::PropagationOptions& propagation_options = {}) { + driver_.reset(new TestDriver{propagation_mode, propagation_options, stats_}); + } + + const std::string operation_name_{"test"}; + Http::TestHeaderMapImpl request_headers_{ + {":path", "/"}, {":method", "GET"}, {"x-request-id", "foo"}}; + const Http::TestHeaderMapImpl response_headers_{{":status", "500"}}; + SystemTime start_time_; + + std::unique_ptr driver_; + Stats::IsolatedStoreImpl stats_; + + NiceMock config_; +}; + +TEST_F(OpenTracingDriverTest, FlushSpanWithTag) { + setupValidDriver(); + + SpanPtr first_span = driver_->startSpan(config_, request_headers_, operation_name_, start_time_); + first_span->setTag("abc", "123"); + first_span->finishSpan(); + + const std::unordered_map expected_tags = { + {"abc", std::string{"123"}}}; + + EXPECT_EQ(1, driver_->recorder().spans().size()); + EXPECT_EQ(expected_tags, driver_->recorder().top().tags); +} + +TEST_F(OpenTracingDriverTest, InjectFailure) { + for (OpenTracingDriver::PropagationMode propagation_mode : + {OpenTracingDriver::PropagationMode::SingleHeader, + OpenTracingDriver::PropagationMode::TracerNative}) { + opentracing::mocktracer::PropagationOptions propagation_options; + propagation_options.inject_error_code = std::make_error_code(std::errc::bad_message); + setupValidDriver(propagation_mode, propagation_options); + + SpanPtr span = driver_->startSpan(config_, request_headers_, operation_name_, start_time_); + + const auto span_context_injection_error_count = + stats_.counter("tracing.opentracing.span_context_injection_error").value(); + EXPECT_EQ(nullptr, request_headers_.OtSpanContext()); + span->injectContext(request_headers_); + + EXPECT_EQ(span_context_injection_error_count + 1, + stats_.counter("tracing.opentracing.span_context_injection_error").value()); + } +} + +TEST_F(OpenTracingDriverTest, ExtractWithUnindexedHeader) { + opentracing::mocktracer::PropagationOptions propagation_options; + propagation_options.propagation_key = "unindexed-header"; + setupValidDriver(OpenTracingDriver::PropagationMode::TracerNative, propagation_options); + + SpanPtr first_span = driver_->startSpan(config_, request_headers_, operation_name_, start_time_); + first_span->injectContext(request_headers_); + + SpanPtr second_span = driver_->startSpan(config_, request_headers_, operation_name_, start_time_); + second_span->finishSpan(); + first_span->finishSpan(); + + auto spans = driver_->recorder().spans(); + EXPECT_EQ(spans.at(1).span_context.span_id, spans.at(0).references.at(0).span_id); +} +} // namespace Tracing +} // namespace Envoy diff --git a/test/server/BUILD b/test/server/BUILD index 5d570330215af..1b38aa6b8569d 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -20,16 +20,21 @@ envoy_cc_test( envoy_cc_test( name = "configuration_impl_test", srcs = ["configuration_impl_test.cc"], + data = [ + "@io_opentracing_cpp//mocktracer:libmocktracer_plugin.so", + ], deps = [ "//source/common/config:well_known_names", "//source/common/event:dispatcher_lib", "//source/common/upstream:cluster_manager_lib", "//source/server:configuration_lib", + "//source/server/config/http:dynamic_opentracing_lib", "//source/server/config/network:raw_buffer_socket_lib", "//source/server/config/stats:statsd_lib", "//test/mocks:common_lib", "//test/mocks/network:network_mocks", "//test/mocks/server:server_mocks", + "//test/test_common:environment_lib", "//test/test_common:utility_lib", ], ) diff --git a/test/server/config/http/BUILD b/test/server/config/http/BUILD index cd966f24da48f..40feea2c80f4f 100644 --- a/test/server/config/http/BUILD +++ b/test/server/config/http/BUILD @@ -44,3 +44,17 @@ envoy_cc_test( "//test/test_common:utility_lib", ], ) + +envoy_cc_test( + name = "dynamic_opentracing_config_test", + srcs = ["dynamic_opentracing_config_test.cc"], + data = [ + "@io_opentracing_cpp//mocktracer:libmocktracer_plugin.so", + ], + deps = [ + "//source/server/config/http:dynamic_opentracing_lib", + "//test/mocks/server:server_mocks", + "//test/test_common:environment_lib", + "//test/test_common:utility_lib", + ], +) diff --git a/test/server/config/http/dynamic_opentracing_config_test.cc b/test/server/config/http/dynamic_opentracing_config_test.cc new file mode 100644 index 0000000000000..062426b25073e --- /dev/null +++ b/test/server/config/http/dynamic_opentracing_config_test.cc @@ -0,0 +1,46 @@ +#include + +#include "server/config/http/dynamic_opentracing_http_tracer.h" + +#include "test/mocks/server/mocks.h" +#include "test/test_common/environment.h" + +#include "fmt/printf.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { + +using testing::NiceMock; +using testing::Return; +using testing::_; + +namespace Server { +namespace Configuration { + +TEST(HttpTracerConfigTest, DynamicOpentracingHttpTracer) { + NiceMock cm; + EXPECT_CALL(cm, get("fake_cluster")).WillRepeatedly(Return(&cm.thread_local_cluster_)); + ON_CALL(*cm.thread_local_cluster_.cluster_.info_, features()) + .WillByDefault(Return(Upstream::ClusterInfo::Features::HTTP2)); + + const std::string valid_config = fmt::sprintf(R"EOF( + { + "library": "%s/external/io_opentracing_cpp/mocktracer/libmocktracer_plugin.so", + "config": { + "output_file" : "fake_file" + } + } + )EOF", + TestEnvironment::runfilesDirectory()); + const Json::ObjectSharedPtr valid_json = Json::Factory::loadFromString(valid_config); + NiceMock server; + DynamicOpenTracingHttpTracerFactory factory; + + const Tracing::HttpTracerPtr tracer = factory.createHttpTracer(*valid_json, server, cm); + EXPECT_NE(nullptr, tracer); +} + +} // namespace Configuration +} // namespace Server +} // namespace Envoy diff --git a/test/server/configuration_impl_test.cc b/test/server/configuration_impl_test.cc index f03ef6c98e866..78be848aaa43f 100644 --- a/test/server/configuration_impl_test.cc +++ b/test/server/configuration_impl_test.cc @@ -10,8 +10,10 @@ #include "test/mocks/common.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/mocks.h" +#include "test/test_common/environment.h" #include "test/test_common/utility.h" +#include "fmt/printf.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -158,6 +160,32 @@ TEST_F(ConfigurationImplTest, ServiceClusterNotSetWhenLSTracing) { EXPECT_THROW(config.initialize(bootstrap, server_, cluster_manager_factory_), EnvoyException); } +TEST_F(ConfigurationImplTest, ServiceClusterNotSetWhenOtDynamicTracing) { + std::string yaml = fmt::sprintf(R"EOF( + admin: + access_log_path: /dev/null + address: + socket_address: { address: 1.2.3.4, port_value: 5678 } + + tracing: + http: + name: envoy.dynamic.ot + config: + library: %s/external/io_opentracing_cpp/mocktracer/libmocktracer_plugin.so + config: { + "output_file" : "fake_file" + } + )EOF", + TestEnvironment::runfilesDirectory()); + + envoy::config::bootstrap::v2::Bootstrap bootstrap; + MessageUtil::loadFromYaml(yaml, bootstrap); + + server_.local_info_.node_.set_cluster(""); + MainImpl config; + config.initialize(bootstrap, server_, cluster_manager_factory_); +} + TEST_F(ConfigurationImplTest, NullTracerSetWhenTracingConfigurationAbsent) { std::string json = R"EOF( {