Skip to content
Merged
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
5 changes: 3 additions & 2 deletions api/envoy/config/core/v3/extension.proto
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ message TypedExtensionConfig {
string name = 1 [(validate.rules).string = {min_len: 1}];

// The typed config for the extension. The type URL will be used to identify
// the extension. In the case that the type URL is *udpa.type.v1.TypedStruct*,
// the inner type URL of *TypedStruct* will be utilized. See the
// the extension. In the case that the type URL is *xds.type.v3.TypedStruct*
// (or, for historical reasons, *udpa.type.v1.TypedStruct*), the inner type
// URL of *TypedStruct* will be utilized. See the
// :ref:`extension configuration overview
// <config_overview_extension_configuration>` for further details.
google.protobuf.Any typed_config = 2 [(validate.rules).any = {required: true}];
Expand Down
2 changes: 1 addition & 1 deletion docs/root/api/client_features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Currently Defined Client Features

- **envoy.config.require-any-fields-contain-struct**: This feature indicates that xDS client
requires that the configuration entries of type *google.protobuf.Any* contain messages of type
*udpa.type.v1.TypedStruct* only.
*xds.type.v3.TypedStruct* (or, for historical reasons, *udpa.type.v1.TypedStruct*) only.
- **envoy.lb.does_not_support_overprovisioning**: This feature indicates that the client does not
support overprovisioning for priority failover and locality weighting as configured by the
:ref:`overprovisioning_factor <envoy_v3_api_field_config.endpoint.v3.clusterloadassignment.policy.overprovisioning_factor>`
Expand Down
6 changes: 3 additions & 3 deletions docs/root/configuration/overview/extension.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ filter configuration snippet is permitted:
dynamic_stats: true

In case the control plane lacks the schema definitions for an extension,
``udpa.type.v1.TypedStruct`` should be used as a generic container. The type URL
``xds.type.v3.TypedStruct`` should be used as a generic container. The type URL
inside it is then used by a client to convert the contents to a typed
configuration resource. For example, the above example could be written as
follows:
Expand All @@ -44,7 +44,7 @@ follows:

name: front-http-proxy
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
"@type": type.googleapis.com/xds.type.v3.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
value:
stat_prefix: ingress_http
Expand All @@ -62,7 +62,7 @@ follows:
http_filters:
- name: front-router
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
"@type": type.googleapis.com/xds.type.v3.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.router.v3Router

.. _config_overview_extension_discovery:
Expand Down
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Removed Config or Runtime

New Features
------------
* api: added support for *xds.type.v3.TypedStruct* in addition to the now-deprecated *udpa.type.v1.TypedStruct* proto message, which is a wrapper proto used to encode typed JSON data in a *google.protobuf.Any* field.
* ext_authz: added :ref:`query_parameters_to_set <envoy_v3_api_field_service.auth.v3.OkHttpResponse.query_parameters_to_set>` and :ref:`query_parameters_to_remove <envoy_v3_api_field_service.auth.v3.OkHttpResponse.query_parameters_to_remove>` for adding and removing query string parameters when using a gRPC authorization server.
* http: added support for :ref:`retriable health check status codes <envoy_v3_api_field_config.core.v3.HealthCheck.HttpHealthCheck.retriable_statuses>`.
* thrift_proxy: add upstream response zone metrics in the form ``cluster.cluster_name.zone.local_zone.upstream_zone.thrift.upstream_resp_success``.
Expand Down
1 change: 1 addition & 0 deletions source/common/config/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ envoy_cc_library(
"//source/common/stats:stats_matcher_lib",
"//source/common/stats:tag_producer_lib",
"@com_github_cncf_udpa//udpa/type/v1:pkg_cc_proto",
"@com_github_cncf_udpa//xds/type/v3:pkg_cc_proto",
"@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto",
"@envoy_api//envoy/config/cluster/v3:pkg_cc_proto",
"@envoy_api//envoy/config/core/v3:pkg_cc_proto",
Expand Down
13 changes: 13 additions & 0 deletions source/common/config/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ void Utility::translateOpaqueConfig(const ProtobufWkt::Any& typed_config,
static const std::string struct_type =
ProtobufWkt::Struct::default_instance().GetDescriptor()->full_name();
static const std::string typed_struct_type =
xds::type::v3::TypedStruct::default_instance().GetDescriptor()->full_name();
static const std::string legacy_typed_struct_type =
udpa::type::v1::TypedStruct::default_instance().GetDescriptor()->full_name();

if (!typed_config.value().empty()) {
Expand All @@ -262,6 +264,17 @@ void Utility::translateOpaqueConfig(const ProtobufWkt::Any& typed_config,
absl::string_view type = TypeUtil::typeUrlToDescriptorFullName(typed_config.type_url());

if (type == typed_struct_type) {
xds::type::v3::TypedStruct typed_struct;
MessageUtil::unpackTo(typed_config, typed_struct);
// if out_proto is expecting Struct, return directly
if (out_proto.GetDescriptor()->full_name() == struct_type) {
out_proto.CopyFrom(typed_struct.value());
} else {
// The typed struct might match out_proto, or some earlier version, let
// MessageUtil::jsonConvert sort this out.
MessageUtil::jsonConvert(typed_struct.value(), validation_visitor, out_proto);
}
} else if (type == legacy_typed_struct_type) {
udpa::type::v1::TypedStruct typed_struct;
MessageUtil::unpackTo(typed_config, typed_struct);
// if out_proto is expecting Struct, return directly
Expand Down
8 changes: 8 additions & 0 deletions source/common/config/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "source/common/singleton/const_singleton.h"

#include "udpa/type/v1/typed_struct.pb.h"
#include "xds/type/v3/typed_struct.pb.h"

namespace Envoy {
namespace Config {
Expand Down Expand Up @@ -329,11 +330,18 @@ class Utility {
*/
static std::string getFactoryType(const ProtobufWkt::Any& typed_config) {
static const std::string& typed_struct_type =
xds::type::v3::TypedStruct::default_instance().GetDescriptor()->full_name();
static const std::string& legacy_typed_struct_type =
udpa::type::v1::TypedStruct::default_instance().GetDescriptor()->full_name();
// Unpack methods will only use the fully qualified type name after the last '/'.
// https://github.com/protocolbuffers/protobuf/blob/3.6.x/src/google/protobuf/any.proto#L87
auto type = std::string(TypeUtil::typeUrlToDescriptorFullName(typed_config.type_url()));
if (type == typed_struct_type) {
xds::type::v3::TypedStruct typed_struct;
MessageUtil::unpackTo(typed_config, typed_struct);
// Not handling nested structs or typed structs in typed structs
return std::string(TypeUtil::typeUrlToDescriptorFullName(typed_struct.type_url()));
} else if (type == legacy_typed_struct_type) {
udpa::type::v1::TypedStruct typed_struct;
MessageUtil::unpackTo(typed_config, typed_struct);
// Not handling nested structs or typed structs in typed structs
Expand Down
8 changes: 5 additions & 3 deletions source/common/protobuf/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -644,9 +644,10 @@ bool redactAny(Protobuf::Message* message, bool ancestor_is_sensitive) {
}

// To redact a `TypedStruct`, we have to reify it based on its `type_url` to redact it.
bool redactTypedStruct(Protobuf::Message* message, bool ancestor_is_sensitive) {
bool redactTypedStruct(Protobuf::Message* message, const char* typed_struct_type,
bool ancestor_is_sensitive) {
return redactOpaque(
message, ancestor_is_sensitive, "udpa.type.v1.TypedStruct",
message, ancestor_is_sensitive, typed_struct_type,
[message](Protobuf::Message* typed_message, const Protobuf::Reflection* reflection,
const Protobuf::FieldDescriptor* field_descriptor) {
// To unpack a `TypedStruct`, convert the struct from JSON.
Expand All @@ -664,7 +665,8 @@ bool redactTypedStruct(Protobuf::Message* message, bool ancestor_is_sensitive) {
// Recursive helper method for MessageUtil::redact() below.
void redact(Protobuf::Message* message, bool ancestor_is_sensitive) {
if (redactAny(message, ancestor_is_sensitive) ||
redactTypedStruct(message, ancestor_is_sensitive)) {
redactTypedStruct(message, "xds.type.v3.TypedStruct", ancestor_is_sensitive) ||
redactTypedStruct(message, "udpa.type.v1.TypedStruct", ancestor_is_sensitive)) {
return;
}

Expand Down
1 change: 1 addition & 0 deletions test/common/config/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ envoy_cc_test(
"//test/test_common:logging_lib",
"//test/test_common:utility_lib",
"@com_github_cncf_udpa//udpa/type/v1:pkg_cc_proto",
"@com_github_cncf_udpa//xds/type/v3:pkg_cc_proto",
"@envoy_api//envoy/api/v2:pkg_cc_proto",
"@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto",
"@envoy_api//envoy/config/cluster/v3:pkg_cc_proto",
Expand Down
88 changes: 48 additions & 40 deletions test/common/config/utility_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "udpa/type/v1/typed_struct.pb.h"
#include "xds/type/v3/typed_struct.pb.h"

using testing::Ref;
using testing::Return;
Expand Down Expand Up @@ -328,37 +329,44 @@ TEST(UtilityTest, TranslateAnyToFactoryConfig) {
EXPECT_THAT(*config, ProtoEq(source_duration));
}

void packTypedStructIntoAny(ProtobufWkt::Any& typed_config, const Protobuf::Message& inner) {
udpa::type::v1::TypedStruct typed_struct;
(*typed_struct.mutable_type_url()) =
absl::StrCat("type.googleapis.com/", inner.GetDescriptor()->full_name());
MessageUtil::jsonConvert(inner, *typed_struct.mutable_value());
typed_config.PackFrom(typed_struct);
}
template <typename T> class UtilityTypedStructTest : public ::testing::Test {
public:
static void packTypedStructIntoAny(ProtobufWkt::Any& typed_config,
const Protobuf::Message& inner) {
T typed_struct;
(*typed_struct.mutable_type_url()) =
absl::StrCat("type.googleapis.com/", inner.GetDescriptor()->full_name());
MessageUtil::jsonConvert(inner, *typed_struct.mutable_value());
typed_config.PackFrom(typed_struct);
}
};

using TypedStructTypes = ::testing::Types<xds::type::v3::TypedStruct, udpa::type::v1::TypedStruct>;
TYPED_TEST_SUITE(UtilityTypedStructTest, TypedStructTypes);

// Verify that udpa.type.v1.TypedStruct can be translated into google.protobuf.Struct
TEST(UtilityTest, TypedStructToStruct) {
// Verify that TypedStruct can be translated into google.protobuf.Struct
TYPED_TEST(UtilityTypedStructTest, TypedStructToStruct) {
ProtobufWkt::Any typed_config;
ProtobufWkt::Struct untyped_struct;
(*untyped_struct.mutable_fields())["foo"].set_string_value("bar");
packTypedStructIntoAny(typed_config, untyped_struct);
this->packTypedStructIntoAny(typed_config, untyped_struct);

ProtobufWkt::Struct out;
Utility::translateOpaqueConfig(typed_config, ProtobufMessage::getStrictValidationVisitor(), out);

EXPECT_THAT(out, ProtoEq(untyped_struct));
}

// Verify that udpa.type.v1.TypedStruct can be translated into an arbitrary message of correct type
// Verify that TypedStruct can be translated into an arbitrary message of correct type
// (v2 API, no upgrading).
TEST(UtilityTest, TypedStructToClusterV2) {
TYPED_TEST(UtilityTypedStructTest, TypedStructToClusterV2) {
ProtobufWkt::Any typed_config;
API_NO_BOOST(envoy::api::v2::Cluster) cluster;
const std::string cluster_config_yaml = R"EOF(
drain_connections_on_host_removal: true
)EOF";
TestUtility::loadFromYaml(cluster_config_yaml, cluster);
packTypedStructIntoAny(typed_config, cluster);
this->packTypedStructIntoAny(typed_config, cluster);

{
API_NO_BOOST(envoy::api::v2::Cluster) out;
Expand All @@ -373,16 +381,16 @@ TEST(UtilityTest, TypedStructToClusterV2) {
}
}

// Verify that udpa.type.v1.TypedStruct can be translated into an arbitrary message of correct type
// Verify that TypedStruct can be translated into an arbitrary message of correct type
// (v3 API, upgrading).
TEST(UtilityTest, TypedStructToClusterV3) {
TYPED_TEST(UtilityTypedStructTest, TypedStructToClusterV3) {
ProtobufWkt::Any typed_config;
API_NO_BOOST(envoy::config::cluster::v3::Cluster) cluster;
const std::string cluster_config_yaml = R"EOF(
ignore_health_on_host_removal: true
)EOF";
TestUtility::loadFromYaml(cluster_config_yaml, cluster);
packTypedStructIntoAny(typed_config, cluster);
this->packTypedStructIntoAny(typed_config, cluster);

{
API_NO_BOOST(envoy::config::cluster::v3::Cluster) out;
Expand All @@ -397,6 +405,30 @@ TEST(UtilityTest, TypedStructToClusterV3) {
}
}

// Verify that translation from TypedStruct into message of incorrect type fails
TYPED_TEST(UtilityTypedStructTest, TypedStructToInvalidType) {
ProtobufWkt::Any typed_config;
envoy::config::bootstrap::v3::Bootstrap bootstrap;
const std::string bootstrap_config_yaml = R"EOF(
admin:
access_log:
- name: envoy.access_loggers.file
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
path: /dev/null
address:
pipe:
path: "/"
)EOF";
TestUtility::loadFromYaml(bootstrap_config_yaml, bootstrap);
this->packTypedStructIntoAny(typed_config, bootstrap);

ProtobufWkt::Any out;
EXPECT_THROW_WITH_REGEX(Utility::translateOpaqueConfig(
typed_config, ProtobufMessage::getStrictValidationVisitor(), out),
EnvoyException, "Unable to parse JSON as proto");
}

// Verify that Any can be translated into an arbitrary message of correct type
// (v2 API, no upgrading).
TEST(UtilityTest, AnyToClusterV2) {
Expand Down Expand Up @@ -429,30 +461,6 @@ TEST(UtilityTest, AnyToClusterV3) {
EXPECT_THAT(out, ProtoEq(cluster));
}

// Verify that translation from udpa.type.v1.TypedStruct into message of incorrect type fails
TEST(UtilityTest, TypedStructToInvalidType) {
ProtobufWkt::Any typed_config;
envoy::config::bootstrap::v3::Bootstrap bootstrap;
const std::string bootstrap_config_yaml = R"EOF(
admin:
access_log:
- name: envoy.access_loggers.file
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
path: /dev/null
address:
pipe:
path: "/"
)EOF";
TestUtility::loadFromYaml(bootstrap_config_yaml, bootstrap);
packTypedStructIntoAny(typed_config, bootstrap);

ProtobufWkt::Any out;
EXPECT_THROW_WITH_REGEX(Utility::translateOpaqueConfig(
typed_config, ProtobufMessage::getStrictValidationVisitor(), out),
EnvoyException, "Unable to parse JSON as proto");
}

// Verify that ProtobufWkt::Empty can load into a typed factory with an empty config proto
TEST(UtilityTest, EmptyToEmptyConfig) {
ProtobufWkt::Any typed_config;
Expand Down
30 changes: 18 additions & 12 deletions test/common/protobuf/utility_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "absl/container/node_hash_set.h"
#include "gtest/gtest.h"
#include "udpa/type/v1/typed_struct.pb.h"
#include "xds/type/v3/typed_struct.pb.h"

using namespace std::chrono_literals;

Expand Down Expand Up @@ -999,50 +1000,55 @@ TEST_F(ProtobufUtilityTest, RedactTypedStruct) {
EXPECT_TRUE(TestUtility::protoEqual(expected, actual));
}

template <typename T> class TypedStructUtilityTest : public ProtobufUtilityTest {};

using TypedStructTypes = ::testing::Types<xds::type::v3::TypedStruct, udpa::type::v1::TypedStruct>;
TYPED_TEST_SUITE(TypedStructUtilityTest, TypedStructTypes);

// Empty `TypedStruct` can be trivially redacted.
TEST_F(ProtobufUtilityTest, RedactEmptyTypedStruct) {
udpa::type::v1::TypedStruct actual;
TYPED_TEST(TypedStructUtilityTest, RedactEmptyTypedStruct) {
TypeParam actual;
TestUtility::loadFromYaml(R"EOF(
type_url: type.googleapis.com/envoy.test.Sensitive
)EOF",
actual);

udpa::type::v1::TypedStruct expected = actual;
TypeParam expected = actual;
MessageUtil::redact(actual);
EXPECT_TRUE(TestUtility::protoEqual(expected, actual));
}

TEST_F(ProtobufUtilityTest, RedactTypedStructWithNoTypeUrl) {
udpa::type::v1::TypedStruct actual;
TYPED_TEST(TypedStructUtilityTest, RedactTypedStructWithNoTypeUrl) {
TypeParam actual;
TestUtility::loadFromYaml(R"EOF(
value:
sensitive_string: This field is sensitive, but we have no way of knowing.
)EOF",
actual);

udpa::type::v1::TypedStruct expected = actual;
TypeParam expected = actual;
MessageUtil::redact(actual);
EXPECT_TRUE(TestUtility::protoEqual(expected, actual));
}

// Messages packed into `TypedStruct` with unknown type URLs are skipped.
TEST_F(ProtobufUtilityTest, RedactTypedStructWithUnknownTypeUrl) {
udpa::type::v1::TypedStruct actual;
TYPED_TEST(TypedStructUtilityTest, RedactTypedStructWithUnknownTypeUrl) {
TypeParam actual;
TestUtility::loadFromYaml(R"EOF(
type_url: type.googleapis.com/envoy.unknown.Message
value:
sensitive_string: This field is sensitive, but we have no way of knowing.
)EOF",
actual);

udpa::type::v1::TypedStruct expected = actual;
TypeParam expected = actual;
MessageUtil::redact(actual);
EXPECT_TRUE(TestUtility::protoEqual(expected, actual));
}

TEST_F(ProtobufUtilityTest, RedactEmptyTypeUrlTypedStruct) {
udpa::type::v1::TypedStruct actual;
udpa::type::v1::TypedStruct expected = actual;
TYPED_TEST(TypedStructUtilityTest, RedactEmptyTypeUrlTypedStruct) {
TypeParam actual;
TypeParam expected = actual;
MessageUtil::redact(actual);
EXPECT_TRUE(TestUtility::protoEqual(expected, actual));
}
Expand Down
2 changes: 1 addition & 1 deletion test/common/watchdog/abort_action_config_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ TEST(AbortActionFactoryTest, CanCreateAction) {
"config": {
"name": "envoy.watchdog.abort_action",
"typed_config": {
"@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
"@type": "type.googleapis.com/xds.type.v3.TypedStruct",
"type_url": "type.googleapis.com/envoy.watchdog.abort_action.v3.AbortActionConfig",
"value": {
"wait_duration": "2s",
Expand Down
2 changes: 1 addition & 1 deletion test/config/integration/server_xds.lds.typed_struct.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ resources:
- filters:
- name: http
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
"@type": type.googleapis.com/xds.type.v3.TypedStruct
type_url: "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
value:
codec_type: HTTP2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ resources:
- filters:
- name: http
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
"@type": type.googleapis.com/xds.type.v3.TypedStruct
type_url: "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
value:
codec_type: HTTP2
Expand Down
Loading