diff --git a/CODEOWNERS b/CODEOWNERS index 62327d48ac913..7c795a1a87202 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -32,6 +32,8 @@ extensions/filters/common/original_src @snowp @klarose /*/extensions/filters/network/sni_cluster @rshriram @lizan # tracers.datadog extension /*/extensions/tracers/datadog @cgilmour @palazzem +# tracers.xray extension +/*/extensions/tracers/xray @marcomagdy @lavignes # mysql_proxy extension /*/extensions/filters/network/mysql_proxy @rshriram @venilnoronha @mattklein123 # quic extension diff --git a/api/envoy/config/trace/v2/trace.proto b/api/envoy/config/trace/v2/trace.proto index 1fad6beb2ad35..01b29c1738381 100644 --- a/api/envoy/config/trace/v2/trace.proto +++ b/api/envoy/config/trace/v2/trace.proto @@ -6,6 +6,7 @@ option java_outer_classname = "TraceProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.trace.v2"; +import "envoy/api/v2/core/base.proto"; import "envoy/api/v2/core/grpc_service.proto"; import "google/protobuf/any.proto"; @@ -34,6 +35,7 @@ message Tracing { // - *envoy.dynamic.ot* // - *envoy.tracers.datadog* // - *envoy.tracers.opencensus* + // - *envoy.tracers.xray* string name = 1 [(validate.rules).string = {min_bytes: 1}]; // Trace driver specific configuration which depends on the driver being instantiated. @@ -44,6 +46,8 @@ message Tracing { // - :ref:`DynamicOtConfig ` // - :ref:`DatadogConfig ` // - :ref:`OpenCensusConfig ` + // [#comment: TODO(marco) when XRay is implemented, uncomment the following; - :ref:`XRayConfig + // `] oneof config_type { google.protobuf.Struct config = 2; @@ -197,6 +201,20 @@ message OpenCensusConfig { repeated TraceContext outgoing_trace_context = 9; } +// [#not-implemented-hide:] +// Configuration for AWS X-Ray tracer. +message XRayConfig { + // The endpoint of the X-Ray Daemon where the spans will be sent. Since by default daemon + // listens to localhost:2000, so the default value is 127.0.0.1:2000. + string daemon_endpoint = 1 [(validate.rules).string = {min_bytes: 1}]; + + // The custom name to name a X-Ray segment. By default will use cluster name. + string segment_name = 2; + + // The location of custom sampling rule json file. + api.v2.core.DataSource sampling_rule_manifest = 3; +} + // Configuration structure. message TraceServiceConfig { // The upstream gRPC cluster that hosts the metrics service. diff --git a/api/envoy/config/trace/v3alpha/trace.proto b/api/envoy/config/trace/v3alpha/trace.proto index 238a6ad6bdb8a..37de634399689 100644 --- a/api/envoy/config/trace/v3alpha/trace.proto +++ b/api/envoy/config/trace/v3alpha/trace.proto @@ -6,6 +6,7 @@ option java_outer_classname = "TraceProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.trace.v3alpha"; +import "envoy/api/v3alpha/core/base.proto"; import "envoy/api/v3alpha/core/grpc_service.proto"; import "google/protobuf/any.proto"; @@ -34,6 +35,7 @@ message Tracing { // - *envoy.dynamic.ot* // - *envoy.tracers.datadog* // - *envoy.tracers.opencensus* + // - *envoy.tracers.xray* string name = 1 [(validate.rules).string = {min_bytes: 1}]; // Trace driver specific configuration which depends on the driver being instantiated. @@ -44,6 +46,8 @@ message Tracing { // - :ref:`DynamicOtConfig ` // - :ref:`DatadogConfig ` // - :ref:`OpenCensusConfig ` + // [#comment: TODO(marco) when XRay is implemented, uncomment the following; - :ref:`XRayConfig + // `] oneof config_type { google.protobuf.Struct config = 2; @@ -197,6 +201,20 @@ message OpenCensusConfig { repeated TraceContext outgoing_trace_context = 9; } +// [#not-implemented-hide:] +// Configuration for AWS X-Ray tracer. +message XRayConfig { + // The endpoint of the X-Ray Daemon where the spans will be sent. Since by default daemon + // listens to localhost:2000, so the default value is 127.0.0.1:2000. + string daemon_endpoint = 1 [(validate.rules).string = {min_bytes: 1}]; + + // The custom name to name a X-Ray segment. By default will use cluster name. + string segment_name = 2; + + // The location of custom sampling rule json file. + api.v3alpha.core.DataSource sampling_rule_manifest = 3; +} + // Configuration structure. message TraceServiceConfig { // The upstream gRPC cluster that hosts the metrics service. diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index fd78ee5b78116..7240b0468ee85 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -124,6 +124,7 @@ EXTENSIONS = { "envoy.tracers.datadog": "//source/extensions/tracers/datadog:config", "envoy.tracers.zipkin": "//source/extensions/tracers/zipkin:config", "envoy.tracers.opencensus": "//source/extensions/tracers/opencensus:config", + "envoy.tracers.xray": "//source/extensions/tracers/xray:config", # # Transport sockets diff --git a/source/extensions/tracers/well_known_names.h b/source/extensions/tracers/well_known_names.h index a756df8de089f..8b48fd0aeb546 100644 --- a/source/extensions/tracers/well_known_names.h +++ b/source/extensions/tracers/well_known_names.h @@ -23,6 +23,8 @@ class TracerNameValues { const std::string Datadog = "envoy.tracers.datadog"; // OpenCensus tracer const std::string OpenCensus = "envoy.tracers.opencensus"; + // AWS XRay tracer + const std::string XRay = "envoy.tracers.xray"; }; using TracerNames = ConstSingleton; diff --git a/source/extensions/tracers/xray/BUILD b/source/extensions/tracers/xray/BUILD new file mode 100644 index 0000000000000..4306e557f25c2 --- /dev/null +++ b/source/extensions/tracers/xray/BUILD @@ -0,0 +1,47 @@ +licenses(["notice"]) # Apache 2 + +# Trace driver for AWS X-Ray. + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "xray_lib", + srcs = [ + "util.cc", + "xray_tracer_impl.cc", + ], + hdrs = [ + "sampling_strategy.h", + "tracer.h", + "util.h", + "xray_configuration.h", + "xray_tracer_impl.h", + ], + deps = [ + "//include/envoy/common:time_interface", + "//include/envoy/server:instance_interface", + "//include/envoy/tracing:http_tracer_interface", + "//source/common/common:macros", + "//source/common/http:header_map_lib", + "//source/common/json:json_loader_lib", + "//source/common/tracing:http_tracer_lib", + ], +) + +envoy_cc_library( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":xray_lib", + "//source/common/config:datasource_lib", + "//source/extensions/tracers:well_known_names", + "//source/extensions/tracers/common:factory_base_lib", + ], +) diff --git a/source/extensions/tracers/xray/config.cc b/source/extensions/tracers/xray/config.cc new file mode 100644 index 0000000000000..e19705bd9b8ae --- /dev/null +++ b/source/extensions/tracers/xray/config.cc @@ -0,0 +1,47 @@ +#include "extensions/tracers/xray/config.h" + +#include + +#include "envoy/registry/registry.h" + +#include "common/common/utility.h" +#include "common/config/datasource.h" +#include "common/tracing/http_tracer_impl.h" + +#include "extensions/tracers/well_known_names.h" +#include "extensions/tracers/xray/xray_tracer_impl.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace XRay { + +XRayTracerFactory::XRayTracerFactory() : FactoryBase(TracerNames::get().XRay) {} + +Tracing::HttpTracerPtr +XRayTracerFactory::createHttpTracerTyped(const envoy::config::trace::v2::XRayConfig& proto_config, + Server::Instance& server) { + std::string sampling_rules_json; + try { + sampling_rules_json = + Config::DataSource::read(proto_config.sampling_rule_manifest(), true, server.api()); + } catch (EnvoyException& e) { + ENVOY_LOG(error, "Failed to read sampling rules manifest because of {}.", e.what()); + } + + XRayConfiguration xconfig(proto_config.daemon_endpoint(), proto_config.segment_name(), + sampling_rules_json); + auto xray_driver = std::make_unique(xconfig, server); + + return std::make_unique(std::move(xray_driver), server.localInfo()); +} + +/** + * Static registration for the XRay tracer. @see RegisterFactory. + */ +REGISTER_FACTORY(XRayTracerFactory, Server::Configuration::TracerFactory); + +} // namespace XRay +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/xray/config.h b/source/extensions/tracers/xray/config.h new file mode 100644 index 0000000000000..a02b590d9cf45 --- /dev/null +++ b/source/extensions/tracers/xray/config.h @@ -0,0 +1,31 @@ +#pragma once + +#include "envoy/config/trace/v2/trace.pb.validate.h" + +#include "common/common/logger.h" + +#include "extensions/tracers/common/factory_base.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace XRay { + +/** + * Config registration for the XRay tracer. @see TracerFactory. + */ +class XRayTracerFactory : public Common::FactoryBase, + Logger::Loggable { +public: + XRayTracerFactory(); + +private: + Tracing::HttpTracerPtr + createHttpTracerTyped(const envoy::config::trace::v2::XRayConfig& proto_config, + Server::Instance& server) override; +}; + +} // namespace XRay +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/xray/sampling_strategy.h b/source/extensions/tracers/xray/sampling_strategy.h new file mode 100644 index 0000000000000..5fed69492e632 --- /dev/null +++ b/source/extensions/tracers/xray/sampling_strategy.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + +#include "envoy/common/pure.h" + +#include "common/common/macros.h" + +#include "absl/strings/string_view.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace XRay { + +struct SamplingRequest { + /** + * Creates a new SamplingRequest + * + * @param host_name The host name the request. + * @param http_method The http method of the request e.g. GET, POST, etc. + * @param http_url The path part of the URL of the request. + * @param service The name of the service (user specified) + * @param service_type The type of the service (user specified) + */ + SamplingRequest(absl::string_view host_name, absl::string_view http_method, + absl::string_view http_url) + : host_(host_name), http_method_(http_method), http_url_(http_url) {} + + const std::string host_; + const std::string http_method_; + const std::string http_url_; +}; + +/** + * Strategy provides an interface for implementing trace sampling strategies. + */ +class SamplingStrategy { +public: + explicit SamplingStrategy(uint64_t rng_seed) : rng_(rng_seed) {} + virtual ~SamplingStrategy() = default; + + /** + * sampleRequest determines if the given request should be traced or not. + */ + virtual bool sampleRequest(const SamplingRequest& sampling_request) { + UNREFERENCED_PARAMETER(sampling_request); // unused for now + return rng_() % 100 == 42; + } + +private: + std::mt19937 rng_; +}; + +using SamplingStrategyPtr = std::unique_ptr; + +} // namespace XRay +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/xray/tracer.h b/source/extensions/tracers/xray/tracer.h new file mode 100644 index 0000000000000..b201b858a5583 --- /dev/null +++ b/source/extensions/tracers/xray/tracer.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include "envoy/common/time.h" +#include "envoy/tracing/http_tracer.h" + +#include "extensions/tracers/xray/sampling_strategy.h" + +#include "absl/strings/string_view.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace XRay { + +class Tracer { +public: + Tracer(absl::string_view segment_name, TimeSource& time_source) + : segment_name_(segment_name), time_source_(time_source) { + UNREFERENCED_PARAMETER(time_source_); + } + + /** + * Starts a tracing span for XRay + */ + Tracing::SpanPtr startSpan() { return nullptr; } + +private: + const std::string segment_name_; + TimeSource& time_source_; +}; + +} // namespace XRay +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/xray/util.cc b/source/extensions/tracers/xray/util.cc new file mode 100644 index 0000000000000..65b052d483006 --- /dev/null +++ b/source/extensions/tracers/xray/util.cc @@ -0,0 +1,66 @@ +#include "extensions/tracers/xray/util.h" + +#include "absl/strings/ascii.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace XRay { + +// AWS X-Ray has two sources of sampling rules (wildcard patterns): +// +// 1- The manifest file (static config) +// 2- The X-Ray Service - periodically polled for new rules. (dynamic config) +// +// X-Ray will inspect every single request and try to match it against the set of rules it has to +// decide whether or not a given request should be sampled. That means this wildcard matching +// routine is on the hot path. I've spent a great deal of time to make this function optimal and not +// allocate. +// Using regex matching here has many downsides and it would require us to: +// 1- escape/lint the user input pattern to avoid messing up its meaning (think '.' character is a +// valid regex and URL character) 2- compile the regex and store it with every corresponding part of +// the rule +// +// Those two steps would add significant overhead to the tracer. Meanwhile, the following function +// has a comprehensive test suite and fuzz tests. +bool wildcardMatch(absl::string_view pattern, absl::string_view input) { + if (pattern.empty()) { + return input.empty(); + } + + // Check the special case of a single * pattern, as it's common. + constexpr char glob = '*'; + if (pattern.size() == 1 && pattern[0] == glob) { + return true; + } + + size_t i = 0, p = 0, i_star = input.size(), p_star = 0; + while (i < input.size()) { + if (p < pattern.size() && absl::ascii_tolower(input[i]) == absl::ascii_tolower(pattern[p])) { + ++i; + ++p; + } else if (p < pattern.size() && '?' == pattern[p]) { + ++i; + ++p; + } else if (p < pattern.size() && pattern[p] == glob) { + i_star = i; + p_star = p++; + } else if (i_star != input.size()) { + i = ++i_star; + p = p_star + 1; + } else { + return false; + } + } + + while (p < pattern.size() && pattern[p] == glob) { + ++p; + } + + return p == pattern.size() && i == input.size(); +} + +} // namespace XRay +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/xray/util.h b/source/extensions/tracers/xray/util.h new file mode 100644 index 0000000000000..be2559bdd502c --- /dev/null +++ b/source/extensions/tracers/xray/util.h @@ -0,0 +1,27 @@ +#pragma once +#include + +#include "absl/strings/string_view.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace XRay { + +/** + * Performs a case-insensitive wild-card match against the input string. + * This method works with pseudo-regex chars; specifically ? and * + * + * An asterisk (*) represents any combination of characters. + * A question mark (?) represents any single character. + * + * @param pattern The regex-like pattern to compare with. + * @param text The string to compare against the pattern. + * @return whether the text matches the pattern. + */ +bool wildcardMatch(absl::string_view pattern, absl::string_view input); + +} // namespace XRay +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/xray/xray_configuration.h b/source/extensions/tracers/xray/xray_configuration.h new file mode 100644 index 0000000000000..c35c3f111fe07 --- /dev/null +++ b/source/extensions/tracers/xray/xray_configuration.h @@ -0,0 +1,25 @@ +#pragma once +#include + +#include "absl/strings/string_view.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace XRay { + +struct XRayConfiguration { + XRayConfiguration(absl::string_view daemon_endpoint, absl::string_view segment_name, + absl::string_view sampling_rules) + : daemon_endpoint_(daemon_endpoint), segment_name_(segment_name), + sampling_rules_(sampling_rules) {} + + const std::string daemon_endpoint_; + const std::string segment_name_; + const std::string sampling_rules_; +}; + +} // namespace XRay +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/xray/xray_tracer_impl.cc b/source/extensions/tracers/xray/xray_tracer_impl.cc new file mode 100644 index 0000000000000..841eb1d0428dc --- /dev/null +++ b/source/extensions/tracers/xray/xray_tracer_impl.cc @@ -0,0 +1,45 @@ +#include "extensions/tracers/xray/xray_tracer_impl.h" + +#include "common/common/macros.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace XRay { + +static const char DefaultDaemonEndpoint[] = "127.0.0.1:2000"; +Driver::Driver(const XRayConfiguration& config, Server::Instance& server) : xray_config_(config) { + + const std::string daemon_endpoint = + config.daemon_endpoint_.empty() ? DefaultDaemonEndpoint : config.daemon_endpoint_; + + ENVOY_LOG(debug, "send X-Ray generated segments to daemon address on {}", daemon_endpoint); + std::string span_name = + config.segment_name_.empty() ? server.localInfo().clusterName() : config.segment_name_; + + sampling_strategy_ = std::make_unique(server.random().random()); + tracer_.emplace(span_name, server.timeSource()); +} + +Tracing::SpanPtr Driver::startSpan(const Tracing::Config& config, Http::HeaderMap& request_headers, + const std::string& operation_name, SystemTime start_time, + const Tracing::Decision tracing_decision) { + + UNREFERENCED_PARAMETER(config); + UNREFERENCED_PARAMETER(operation_name); + UNREFERENCED_PARAMETER(start_time); + UNREFERENCED_PARAMETER(tracing_decision); + const SamplingRequest request{request_headers.Host()->value().getStringView(), + request_headers.Method()->value().getStringView(), + request_headers.Path()->value().getStringView()}; + + if (!sampling_strategy_->sampleRequest(request)) { + return nullptr; + } + + return tracer_->startSpan(); +} +} // namespace XRay +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/xray/xray_tracer_impl.h b/source/extensions/tracers/xray/xray_tracer_impl.h new file mode 100644 index 0000000000000..c1c4dd84d785a --- /dev/null +++ b/source/extensions/tracers/xray/xray_tracer_impl.h @@ -0,0 +1,33 @@ +#pragma once + +#include "envoy/server/instance.h" +#include "envoy/tracing/http_tracer.h" + +#include "common/tracing/http_tracer_impl.h" + +#include "extensions/tracers/xray/tracer.h" +#include "extensions/tracers/xray/xray_configuration.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace XRay { + +class Driver : public Tracing::Driver, Logger::Loggable { +public: + Driver(const XRay::XRayConfiguration& config, Server::Instance& server); + + Tracing::SpanPtr startSpan(const Tracing::Config& config, Http::HeaderMap& request_headers, + const std::string& operation_name, SystemTime start_time, + const Tracing::Decision tracing_decision) override; + +private: + XRayConfiguration xray_config_; + SamplingStrategyPtr sampling_strategy_; + absl::optional tracer_; // optional<> for delayed construction +}; + +} // namespace XRay +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/xray/BUILD b/test/extensions/tracers/xray/BUILD new file mode 100644 index 0000000000000..b4ae1dbd65d61 --- /dev/null +++ b/test/extensions/tracers/xray/BUILD @@ -0,0 +1,46 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_fuzz_test", + "envoy_cc_test_library", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +envoy_package() + +envoy_extension_cc_test( + name = "xray_test", + srcs = [ + "util_test.cc", + ], + extension_name = "envoy.tracers.xray", + deps = [ + "//source/extensions/tracers/xray:xray_lib", + ], +) + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_name = "envoy.tracers.xray", + deps = [ + "//source/extensions/tracers/xray:config", + "//test/mocks/server:server_mocks", + "//test/test_common:environment_lib", + "//test/test_common:utility_lib", + ], +) + +envoy_cc_fuzz_test( + name = "xray_fuzz_test", + srcs = ["fuzz_test.cc"], + corpus = "wildcard_matcher_corpus", + deps = [ + "//source/extensions/tracers/xray:xray_lib", + ], +) diff --git a/test/extensions/tracers/xray/config_test.cc b/test/extensions/tracers/xray/config_test.cc new file mode 100644 index 0000000000000..91dcd5903a0d0 --- /dev/null +++ b/test/extensions/tracers/xray/config_test.cc @@ -0,0 +1,78 @@ +#include "envoy/registry/registry.h" + +#include "extensions/tracers/xray/config.h" + +#include "test/mocks/server/mocks.h" +#include "test/test_common/environment.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using ::testing::Throw; + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace XRay { +namespace { + +TEST(XRayTracerConfigTest, XRayHttpTracerWithTypedConfig) { + NiceMock server; + + const std::string yaml_string = R"EOF( + http: + name: envoy.tracers.xray + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v2.XRayConfig + daemon_endpoint: 127.0.0.1 + segment_name: AwsAppMesh + sampling_rule_manifest: + filename: "rules.json")EOF"; + + envoy::config::trace::v2::Tracing configuration; + TestUtility::loadFromYaml(yaml_string, configuration); + + XRayTracerFactory factory; + auto message = Config::Utility::translateToFactoryConfig( + configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); + Tracing::HttpTracerPtr xray_tracer = factory.createHttpTracer(*message, server); + ASSERT_NE(nullptr, xray_tracer); +} + +TEST(XRayTracerConfigTest, XRayHttpTracerWithInvalidFileName) { + NiceMock server; + NiceMock api; + NiceMock file_system; + + // fake invalid file + EXPECT_CALL(file_system, fileReadToEnd("rules.json")) + .WillRepeatedly(Throw(EnvoyException("failed to open file."))); + EXPECT_CALL(api, fileSystem()).WillRepeatedly(ReturnRef(file_system)); + EXPECT_CALL(server, api()).WillRepeatedly(ReturnRef(api)); + + const std::string yaml_string = R"EOF( + http: + name: envoy.tracers.xray + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v2.XRayConfig + daemon_endpoint: 127.0.0.1 + segment_name: AwsAppMesh + sampling_rule_manifest: + filename: "rules.json")EOF"; + + envoy::config::trace::v2::Tracing configuration; + TestUtility::loadFromYaml(yaml_string, configuration); + + XRayTracerFactory factory; + auto message = Config::Utility::translateToFactoryConfig( + configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); + + Tracing::HttpTracerPtr xray_tracer = factory.createHttpTracer(*message, server); + ASSERT_NE(nullptr, xray_tracer); +} + +} // namespace +} // namespace XRay +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/xray/fuzz_test.cc b/test/extensions/tracers/xray/fuzz_test.cc new file mode 100644 index 0000000000000..cbec8697ea8bb --- /dev/null +++ b/test/extensions/tracers/xray/fuzz_test.cc @@ -0,0 +1,29 @@ +#include "extensions/tracers/xray/util.h" + +#include "test/fuzz/fuzz_runner.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace XRay { + +// TODO(@marcomagdy): @htuch suggests to compare results with re2 (after replacing * with .* and ? +// with '.' and doing proper regex escaping) +DEFINE_FUZZER(const uint8_t* buf, size_t len) { + absl::string_view pattern, input; + if (len > 1) { + pattern = absl::string_view(reinterpret_cast(buf), len / 2); + input = absl::string_view(reinterpret_cast(buf + len / 2), len - len / 2); + wildcardMatch(pattern, input); + } else { // buf is a single byte, use it for both pattern and input + absl::string_view sv(reinterpret_cast(buf), len); + wildcardMatch(sv, "hello"); + wildcardMatch("*", sv); + wildcardMatch("?", sv); + } +} + +} // namespace XRay +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/xray/util_test.cc b/test/extensions/tracers/xray/util_test.cc new file mode 100644 index 0000000000000..9a5622dd4653c --- /dev/null +++ b/test/extensions/tracers/xray/util_test.cc @@ -0,0 +1,73 @@ +#include "extensions/tracers/xray/util.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace XRay { + +TEST(XRayWildcardTest, MatchingEmpty) { + ASSERT_TRUE(wildcardMatch("", "")); + ASSERT_FALSE(wildcardMatch("", "42")); + ASSERT_TRUE(wildcardMatch("*", "")); + ASSERT_FALSE(wildcardMatch("?", "")); +} + +TEST(XRayWildcardTest, MatchIdentityCaseInsensitive) { + ASSERT_TRUE(wildcardMatch("foo", "foo")); + ASSERT_TRUE(wildcardMatch("foo", "FOO")); + ASSERT_TRUE(wildcardMatch("foo", "Foo")); + ASSERT_TRUE(wildcardMatch("6543210", "6543210")); +} + +TEST(XRayWildcardTest, MatchIdentityExtra) { + ASSERT_FALSE(wildcardMatch("foo", "foob")); + ASSERT_FALSE(wildcardMatch("foo", "xfoo")); + ASSERT_FALSE(wildcardMatch("foo", "bar")); +} + +TEST(XRayWildcardTest, SingleWildcard) { + ASSERT_FALSE(wildcardMatch("f?o", "boo")); + ASSERT_TRUE(wildcardMatch("fo?", "foo")); +} + +TEST(XRayWildcardTest, MultipleWildcards) { + ASSERT_FALSE(wildcardMatch("f??", "boo")); + ASSERT_TRUE(wildcardMatch("he??o", "Hello")); + ASSERT_TRUE(wildcardMatch("?o?", "foo")); +} + +TEST(XRayWildcardTest, GlobMatch) { + ASSERT_TRUE(wildcardMatch("f?o*ba*", "foobazbar")); + ASSERT_TRUE(wildcardMatch("*oo", "foo")); + ASSERT_TRUE(wildcardMatch("*o?", "foo")); + ASSERT_TRUE(wildcardMatch("mis*spell", "mistily spell")); + ASSERT_TRUE(wildcardMatch("mis*spell", "misspell")); +} + +TEST(XRayWildcardTest, GlobMismatch) { + ASSERT_FALSE(wildcardMatch("foo*", "fo0")); + ASSERT_FALSE(wildcardMatch("fo*obar", "foobaz")); + ASSERT_FALSE(wildcardMatch("mis*spellx", "mispellx")); + ASSERT_FALSE(wildcardMatch("f?*", "boo")); +} + +TEST(XRayWildcardTest, OnlyGlob) { + ASSERT_TRUE(wildcardMatch("*", "foo")); + ASSERT_TRUE(wildcardMatch("*", "anything")); + ASSERT_TRUE(wildcardMatch("*", "12354")); + ASSERT_TRUE(wildcardMatch("*", "UPPERCASE")); + ASSERT_TRUE(wildcardMatch("*", "miXEDcaSe")); + ASSERT_TRUE(wildcardMatch("*******", "Envoy")); +} + +TEST(XRayWildcardTest, LengthAtLeastTwo) { + EXPECT_FALSE(wildcardMatch("??*", "a")); + EXPECT_TRUE(wildcardMatch("??*", "aa")); + EXPECT_TRUE(wildcardMatch("??*", "aaa")); +} +} // namespace XRay +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/xray/wildcard_matcher_corpus/example b/test/extensions/tracers/xray/wildcard_matcher_corpus/example new file mode 100644 index 0000000000000..1de83e62599de --- /dev/null +++ b/test/extensions/tracers/xray/wildcard_matcher_corpus/example @@ -0,0 +1 @@ +/match/on/th?s/*/route* diff --git a/test/extensions/tracers/zipkin/config_test.cc b/test/extensions/tracers/zipkin/config_test.cc index 14841b034e17b..7505966552912 100644 --- a/test/extensions/tracers/zipkin/config_test.cc +++ b/test/extensions/tracers/zipkin/config_test.cc @@ -17,6 +17,7 @@ namespace { TEST(ZipkinTracerConfigTest, ZipkinHttpTracer) { NiceMock server; + EXPECT_CALL(server.cluster_manager_, get(Eq("fake_cluster"))) .WillRepeatedly(Return(&server.cluster_manager_.thread_local_cluster_)); @@ -41,6 +42,7 @@ TEST(ZipkinTracerConfigTest, ZipkinHttpTracer) { TEST(ZipkinTracerConfigTest, ZipkinHttpTracerWithTypedConfig) { NiceMock server; + EXPECT_CALL(server.cluster_manager_, get(Eq("fake_cluster"))) .WillRepeatedly(Return(&server.cluster_manager_.thread_local_cluster_));