Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion api/envoy/config/route/v3/route_components.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2006,7 +2006,7 @@ message Decorator {
google.protobuf.BoolValue propagate = 2;
}

// [#next-free-field: 7]
// [#next-free-field: 8]
message Tracing {
option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.Tracing";

Expand Down Expand Up @@ -2070,6 +2070,13 @@ message Tracing {
// * :ref:`HCM tracing upstream operation
// <envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.Tracing.upstream_operation>`
string upstream_operation = 6;

// Formatter command parsers used for this route's tracing custom tags and operation names.
// HCM tracing formatter configuration is scoped separately. Built-in command parsers such as
// ``%CEL%`` are always available; this field is only needed to add extension parsers or to
// override a built-in parser's configuration.
// [#extension-category: envoy.formatter]
repeated core.v3.TypedExtensionConfig formatters = 7;
}

// A virtual cluster is a way of specifying a regex matching rule against
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ message HttpConnectionManager {
JSON = 1;
}

// [#next-free-field: 14]
// [#next-free-field: 15]
message Tracing {
option (udpa.annotations.versioning).previous_message_type =
"envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager.Tracing";
Expand Down Expand Up @@ -268,6 +268,13 @@ message HttpConnectionManager {
//
// Default: false (context propagation is enabled)
bool no_context_propagation = 13;

// Formatter command parsers used for HCM tracing custom tags and operation names.
// Route tracing formatter configuration is scoped separately. Built-in command parsers such
// as ``%CEL%`` are always available; this field is only needed to add extension parsers or to
// override a built-in parser's configuration.
// [#extension-category: envoy.formatter]
repeated config.core.v3.TypedExtensionConfig formatters = 14;
}

message InternalAddressConfig {
Expand Down
5 changes: 4 additions & 1 deletion api/envoy/extensions/formatter/cel/v3/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@ load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package")
licenses(["notice"]) # Apache 2

api_proto_package(
deps = ["@xds//udpa/annotations:pkg"],
deps = [
"//envoy/config/core/v3:pkg",
"@xds//udpa/annotations:pkg",
],
)
9 changes: 7 additions & 2 deletions api/envoy/extensions/formatter/cel/v3/cel.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ syntax = "proto3";

package envoy.extensions.formatter.cel.v3;

import "envoy/config/core/v3/cel.proto";

import "udpa/annotations/status.proto";

option java_package = "io.envoyproxy.envoy.extensions.formatter.cel.v3";
Expand Down Expand Up @@ -50,7 +52,10 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// Configuration for the CEL formatter.
//
// .. warning::
// This extension is treated as built-in extension and will be enabled by default now.
// It is unnecessary to configure this extension.
// This extension is treated as a built-in extension and is enabled by default.
// It is unnecessary to configure this extension unless overriding the CEL expression runtime
// via ``cel_config``.
message Cel {
// Configuration for the CEL expression runtime used by this formatter.
config.core.v3.CelExpressionConfig cel_config = 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Exposed :ref:`cel_config <envoy_v3_api_field_extensions.formatter.cel.v3.Cel.cel_config>` on the
``envoy.formatter.cel`` formatter configuration, allowing CEL runtime options such as string
functions to be enabled for configured CEL formatters.
1 change: 1 addition & 0 deletions source/common/router/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ envoy_cc_library(
"//source/extensions/early_data:default_early_data_policy_lib",
"//source/extensions/path/match/uri_template:config",
"//source/extensions/path/rewrite/uri_template:config",
"//source/server:generic_factory_context_lib",
"@envoy_api//envoy/config/common/matcher/v3:pkg_cc_proto",
"@envoy_api//envoy/config/core/v3:pkg_cc_proto",
"@envoy_api//envoy/config/route/v3:pkg_cc_proto",
Expand Down
33 changes: 25 additions & 8 deletions source/common/router/config_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
#include "source/extensions/matching/network/common/inputs.h"
#include "source/extensions/path/match/uri_template/uri_template_match.h"
#include "source/extensions/path/rewrite/uri_template/uri_template_rewrite.h"
#include "source/server/generic_factory_context.h"

#include "absl/container/flat_hash_set.h"
#include "absl/container/inlined_vector.h"
Expand Down Expand Up @@ -411,7 +412,8 @@ const std::string& DecoratorImpl::getOperation() const { return operation_; }

bool DecoratorImpl::propagate() const { return propagate_; }

RouteTracingImpl::RouteTracingImpl(const envoy::config::route::v3::Tracing& tracing) {
RouteTracingImpl::RouteTracingImpl(const envoy::config::route::v3::Tracing& tracing,
const Formatter::CommandParserPtrVector& command_parsers) {
if (!tracing.has_client_sampling()) {
client_sampling_.set_numerator(100);
client_sampling_.set_denominator(envoy::type::v3::FractionalPercent::HUNDRED);
Expand All @@ -431,15 +433,17 @@ RouteTracingImpl::RouteTracingImpl(const envoy::config::route::v3::Tracing& trac
overall_sampling_ = tracing.overall_sampling();
}
for (const auto& tag : tracing.custom_tags()) {
custom_tags_.emplace(tag.tag(), Tracing::CustomTagUtility::createCustomTag(tag));
custom_tags_.emplace(tag.tag(),
Tracing::CustomTagUtility::createCustomTag(tag, command_parsers));
}
if (!tracing.operation().empty()) {
auto operation = Formatter::FormatterImpl::create(tracing.operation(), true);
auto operation = Formatter::FormatterImpl::create(tracing.operation(), true, command_parsers);
THROW_IF_NOT_OK_REF(operation.status());
operation_ = std::move(operation.value());
}
if (!tracing.upstream_operation().empty()) {
auto operation = Formatter::FormatterImpl::create(tracing.upstream_operation(), true);
auto operation =
Formatter::FormatterImpl::create(tracing.upstream_operation(), true, command_parsers);
THROW_IF_NOT_OK_REF(operation.status());
upstream_operation_ = std::move(operation.value());
}
Expand Down Expand Up @@ -541,8 +545,8 @@ RouteEntryImplBase::RouteEntryImplBase(const CommonVirtualHostSharedPtr& vhost,
return vec;
}()),
opaque_config_(parseOpaqueConfig(route)), decorator_(parseDecorator(route)),
route_tracing_(parseRouteTracing(route)), route_name_(route.name()),
time_source_(factory_context.mainThreadDispatcher().timeSource()),
route_tracing_(parseRouteTracing(route, factory_context, validator)),
route_name_(route.name()), time_source_(factory_context.mainThreadDispatcher().timeSource()),
request_body_buffer_limit_(getRequestBodyBufferLimit(vhost, route)),
direct_response_code_(ConfigUtility::parseDirectResponseCode(route)),
cluster_not_found_response_code_(ConfigUtility::parseClusterNotFoundResponseCode(
Expand Down Expand Up @@ -1308,10 +1312,23 @@ DecoratorConstPtr RouteEntryImplBase::parseDecorator(const envoy::config::route:
}

RouteTracingConstPtr
RouteEntryImplBase::parseRouteTracing(const envoy::config::route::v3::Route& route) {
RouteEntryImplBase::parseRouteTracing(const envoy::config::route::v3::Route& route,
Server::Configuration::ServerFactoryContext& factory_context,
ProtobufMessage::ValidationVisitor& validator) {
RouteTracingConstPtr ret;
if (route.has_tracing()) {
ret = RouteTracingConstPtr(new RouteTracingImpl(route.tracing()));
// The `formatters` field only configures extension command parsers, including CEL option
// overrides such as `enable_string_functions`. Built-in parsers like `%CEL%` work when it is
// empty, so avoid resolving formatter config unless the field is present.
Formatter::CommandParserPtrVector command_parsers;
if (!route.tracing().formatters().empty()) {
Server::GenericFactoryContextImpl generic_context(factory_context, validator);
command_parsers =
THROW_OR_RETURN_VALUE(Formatter::SubstitutionFormatStringUtils::parseFormatters(
route.tracing().formatters(), generic_context),
Formatter::CommandParserPtrVector);
}
ret = RouteTracingConstPtr(new RouteTracingImpl(route.tracing(), command_parsers));
}
return ret;
}
Expand Down
8 changes: 6 additions & 2 deletions source/common/router/config_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,8 @@ class DecoratorImpl : public Decorator {
*/
class RouteTracingImpl : public RouteTracing {
public:
explicit RouteTracingImpl(const envoy::config::route::v3::Tracing& tracing);
explicit RouteTracingImpl(const envoy::config::route::v3::Tracing& tracing,
const Formatter::CommandParserPtrVector& command_parsers);

// RouteTracing
const envoy::type::v3::FractionalPercent& getClientSampling() const override;
Expand Down Expand Up @@ -919,7 +920,10 @@ class RouteEntryImplBase : public RouteEntryAndRoute,

static DecoratorConstPtr parseDecorator(const envoy::config::route::v3::Route& route);

static RouteTracingConstPtr parseRouteTracing(const envoy::config::route::v3::Route& route);
static RouteTracingConstPtr
parseRouteTracing(const envoy::config::route::v3::Route& route,
Server::Configuration::ServerFactoryContext& factory_context,
ProtobufMessage::ValidationVisitor& validator);

bool evaluateRuntimeMatch(const uint64_t random_value) const;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ envoy_cc_extension(
"//source/common/config:utility_lib",
"//source/common/config:xds_resource_lib",
"//source/common/filter:config_discovery_lib",
"//source/common/formatter:substitution_format_string_lib",
"//source/common/http:conn_manager_lib",
"//source/common/http:default_server_string_lib",
"//source/common/http:filter_chain_helper_lib",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "source/common/common/fmt.h"
#include "source/common/config/utility.h"
#include "source/common/config/xds_resource.h"
#include "source/common/formatter/substitution_format_string.h"
#include "source/common/http/conn_manager_config.h"
#include "source/common/http/conn_manager_utility.h"
#include "source/common/http/default_server_string.h"
Expand Down Expand Up @@ -641,8 +642,15 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig(

if (config.has_tracing()) {
tracer_ = tracer_manager.getOrCreateTracer(getPerFilterTracerConfig(config));
tracing_config_ = std::make_unique<Http::TracingConnectionManagerConfig>(context.direction(),
config.tracing());
Formatter::CommandParserPtrVector command_parsers;
if (!config.tracing().formatters().empty()) {
auto command_parsers_or_error = Formatter::SubstitutionFormatStringUtils::parseFormatters(
config.tracing().formatters(), context_);
SET_AND_RETURN_IF_NOT_OK(command_parsers_or_error.status(), creation_status);
command_parsers = std::move(command_parsers_or_error.value());
}
tracing_config_ = std::make_unique<Http::TracingConnectionManagerConfig>(
context.direction(), config.tracing(), command_parsers);
}

for (const auto& access_log : config.access_log()) {
Expand Down
2 changes: 2 additions & 0 deletions source/extensions/formatter/cel/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ envoy_cc_extension(
],
deps = [
"//envoy/registry",
"//source/common/protobuf:utility_lib",
"//source/extensions/filters/common/expr:evaluator_lib",
"//source/extensions/formatter/cel:cel_lib",
"@envoy_api//envoy/extensions/formatter/cel/v3:pkg_cc_proto",
] + select(
Expand Down
21 changes: 18 additions & 3 deletions source/extensions/formatter/cel/cel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ Protobuf::Value CELFormatter::formatValue(const Envoy::Formatter::Context& conte
}
}

CELFormatterCommandParser::CELFormatterCommandParser(
const ::Envoy::LocalInfo::LocalInfo& local_info,
Expr::BuilderInstanceSharedConstPtr expr_builder)
: configured_state_(ConfiguredState{local_info, std::move(expr_builder)}) {
ASSERT(configured_state_->expr_builder != nullptr);
}

absl::StatusOr<Envoy::Formatter::FormatterProviderPtr>
CELFormatterCommandParser::parse(absl::string_view command, absl::string_view subcommand,
std::optional<size_t> max_length) const {
Expand All @@ -85,10 +92,18 @@ CELFormatterCommandParser::parse(absl::string_view command, absl::string_view su
if (!parse_status.ok()) {
throw EnvoyException("Not able to parse expression: " + parse_status.status().ToString());
}
Server::Configuration::ServerFactoryContext& context =
Server::Configuration::ServerFactoryContextInstance::get();

// Lazily resolve the active server context at CEL-command parse time: built-in command parsers
// are process-global, so they cannot capture server-owned CEL state.
if (!configured_state_.has_value()) {
Server::Configuration::ServerFactoryContext& context =
Server::Configuration::ServerFactoryContextInstance::get();
return std::make_unique<CELFormatter>(context.localInfo(), Expr::getBuilder(context),
parse_status.value().expr(), max_length,
command == "TYPED_CEL");
}
return std::make_unique<CELFormatter>(
context.localInfo(), Extensions::Filters::Common::Expr::getBuilder(context),
configured_state_->local_info.get(), configured_state_->expr_builder,
parse_status.value().expr(), max_length, command == "TYPED_CEL");
}

Expand Down
16 changes: 16 additions & 0 deletions source/extensions/formatter/cel/cel.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <functional>
#include <optional>
#include <string>

#include "envoy/config/typed_config.h"
Expand Down Expand Up @@ -33,9 +35,23 @@ class CELFormatter : public ::Envoy::Formatter::FormatterProvider {
class CELFormatterCommandParser : public ::Envoy::Formatter::CommandParser {
public:
CELFormatterCommandParser() = default;
CELFormatterCommandParser(
const ::Envoy::LocalInfo::LocalInfo& local_info,
Extensions::Filters::Common::Expr::BuilderInstanceSharedConstPtr expr_builder);
absl::StatusOr<Envoy::Formatter::FormatterProviderPtr>
parse(absl::string_view command, absl::string_view subcommand,
std::optional<size_t> max_length) const override;

private:
struct ConfiguredState {
std::reference_wrapper<const ::Envoy::LocalInfo::LocalInfo> local_info;
Extensions::Filters::Common::Expr::BuilderInstanceSharedConstPtr expr_builder;
};

// Present only for parsers created from explicit `envoy.formatter.cel` config. Keeping the
// server-owned values together avoids a half-configured parser; absence means built-in parser
// mode, where `parse()` resolves the active server context for each CEL command.
std::optional<ConfiguredState> configured_state_;
};

} // namespace Formatter
Expand Down
21 changes: 14 additions & 7 deletions source/extensions/formatter/cel/config.cc
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
#include "source/extensions/formatter/cel/config.h"

#include "envoy/extensions/formatter/cel/v3/cel.pb.h"
#include "envoy/extensions/formatter/cel/v3/cel.pb.validate.h"

#include "source/common/protobuf/utility.h"
#include "source/extensions/formatter/cel/cel.h"

namespace Envoy {
namespace Extensions {
namespace Formatter {

::Envoy::Formatter::CommandParserPtr
CELFormatterFactory::createCommandParserFromProto(const Protobuf::Message&,
Server::Configuration::GenericFactoryContext&) {
::Envoy::Formatter::CommandParserPtr CELFormatterFactory::createCommandParserFromProto(
const Protobuf::Message& proto_config, Server::Configuration::GenericFactoryContext& context) {
#if defined(USE_CEL_PARSER)
ENVOY_LOG_TO_LOGGER(Logger::Registry::getLog(Logger::Id::config), warn,
"'CEL' formatter is treated as a built-in formatter and does not "
"require configuration.");
return std::make_unique<CELFormatterCommandParser>();
const auto& config =
MessageUtil::downcastAndValidate<const envoy::extensions::formatter::cel::v3::Cel&>(
proto_config, context.messageValidationVisitor());
const auto config_ref = config.has_cel_config()
? Envoy::makeOptRef(config.cel_config())
: Envoy::OptRef<const envoy::config::core::v3::CelExpressionConfig>{};
return std::make_unique<CELFormatterCommandParser>(
context.serverFactoryContext().localInfo(),
Filters::Common::Expr::getBuilder(context.serverFactoryContext(), config_ref));
#else
UNREFERENCED_PARAMETER(proto_config);
UNREFERENCED_PARAMETER(context);
throw EnvoyException("CEL is not available for use in this environment.");
#endif
Expand Down
26 changes: 26 additions & 0 deletions test/common/router/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,32 @@ envoy_cc_test(
deps = [":config_impl_test_lib"],
)

# TODO: fold these into config_impl_test if/when CEL builds on Windows. They are a separate
# target only because config_impl_test runs on Windows, where cel:config isn't built. See #28588.
envoy_cc_test(
name = "route_tracing_formatter_test",
srcs = ["route_tracing_formatter_test.cc"],
copts = select({
"//bazel:windows_x86_64": [],
"//conditions:default": [
"-DUSE_CEL_PARSER",
],
}),
rbe_pool = "6gig",
tags = ["skip_on_windows"],
deps = [
"//source/common/formatter:formatter_extension_lib",
"//source/common/http:header_map_lib",
"//source/common/router:config_lib",
"//source/extensions/formatter/cel:config",
"//test/mocks/server:server_factory_context_mocks",
"//test/mocks/stream_info:stream_info_mocks",
"//test/mocks/tracing:tracing_mocks",
"//test/test_common:utility_lib",
"@envoy_api//envoy/config/route/v3:pkg_cc_proto",
],
)

envoy_cc_test_library(
name = "config_impl_test_lib",
srcs = ["config_impl_test.cc"],
Expand Down
Loading