Concept docs cover 7 methods; tests cover 7; examples cover Zero. Examples is the bottleneck — every Completed method needs all three. Closing the largest group would move 54% of the debt — the biggest single shift available.
// [#not-implemented-hide:] Not configuration. Workaround c++ protobuf issue with importing
// services: https://github.com/google/protobuf/issues/4221
message AdsDummy {
option (udpa.annotations.versioning).previous_message_type =
"envoy.service.discovery.v2.AdsDummy";
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// See https://github.com/envoyproxy/envoy-api#apis for a description of the role of
// ADS and how it is intended to be used by a management server. ADS requests
// have the same structure as their singleton xDS counterparts, but can
// multiplex many resource types on a single stream. The type_url in the
// DiscoveryRequest/DiscoveryResponse provides sufficient information to recover
// the multiplexed singleton APIs at the Envoy instance and management server.
service AggregatedDiscoveryService {
// This is a gRPC-only API.
rpc StreamAggregatedResources(stream DiscoveryRequest) returns (stream DiscoveryResponse) {
}
rpc DeltaAggregatedResources(stream DeltaDiscoveryRequest)
returns (stream DeltaDiscoveryResponse) {
}
}
See :repo:`discovery.proto <api/envoy/service/discovery/v3/discovery.proto>` for the service definition. This is used by Envoy as a client when
TEST_P(GrpcMuxImplTest, MaybeCreateLoadStatsReporter) {
TestScopedRuntime scoped_runtime;
scoped_runtime.mergeValues({{"envoy.reloadable_features.enable_lrs_server_self_ads", "true"}});
bool factory_called = false;
auto lrs_factory = [&]() -> std::unique_ptr<Upstream::LoadStatsReporter> {
factory_called = true;
return std::make_unique<Upstream::MockLoadStatsReporter>();
};
// Re-setup with the factory
GrpcMuxContext grpc_mux_context{
/*async_client_=*/std::unique_ptr<Grpc::MockAsyncClient>(async_client_),
/*failover_async_client_=*/nullptr,
/*dispatcher_=*/dispatcher_,
/*service_method_=*/
*Protobuf::DescriptorPool::generated_pool()->FindMethodByName(
"envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"),
/*local_info_=*/local_info_,
/*rate_limit_settings_=*/rate_limit_settings_,
/*scope_=*/*stats_.rootScope(),
/*config_validators_=*/std::move(config_validators_),
/*xds_resources_delegate_=*/XdsResourcesDelegateOptRef(),
/*xds_config_tracker_=*/XdsConfigTrackerOptRef(),
/*backoff_strategy_=*/
std::make_unique<JitteredExponentialBackOffStrategy>(
SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, random_),
/*target_xds_authority_=*/"",
/*eds_resources_cache_=*/std::unique_ptr<MockEdsResourcesCache>(eds_resources_cache_),
/*skip_subsequent_node_=*/true,
TEST_P(GrpcMuxImplTest, MaybeCreateLoadStatsReporterRuntimeDisabled) {
TestScopedRuntime scoped_runtime;
scoped_runtime.mergeValues({{"envoy.reloadable_features.enable_lrs_server_self_ads", "false"}});
auto lrs_factory = [&]() -> std::unique_ptr<Upstream::LoadStatsReporter> {
return nullptr; // Should not be called
};
GrpcMuxContext grpc_mux_context{
/*async_client_=*/std::unique_ptr<Grpc::MockAsyncClient>(async_client_),
/*failover_async_client_=*/nullptr,
/*dispatcher_=*/dispatcher_,
/*service_method_=*/
*Protobuf::DescriptorPool::generated_pool()->FindMethodByName(
"envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"),
/*local_info_=*/local_info_,
/*rate_limit_settings_=*/rate_limit_settings_,
/*scope_=*/*stats_.rootScope(),
/*config_validators_=*/std::move(config_validators_),
/*xds_resources_delegate_=*/XdsResourcesDelegateOptRef(),
/*xds_config_tracker_=*/XdsConfigTrackerOptRef(),
/*backoff_strategy_=*/
std::make_unique<JitteredExponentialBackOffStrategy>(
SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, random_),
/*target_xds_authority_=*/"",
/*eds_resources_cache_=*/std::unique_ptr<MockEdsResourcesCache>(eds_resources_cache_),
/*skip_subsequent_node_=*/true,
/*load_stats_reporter_factory_=*/lrs_factory};
grpc_mux_ = std::make_unique<GrpcMuxImpl>(grpc_mux_context);
TEST_P(GrpcMuxImplTest, BadLocalInfoEmptyClusterName) {
EXPECT_CALL(local_info_, clusterName()).WillOnce(ReturnRef(EMPTY_STRING));
GrpcMuxContext grpc_mux_context{
/*async_client_=*/std::unique_ptr<Grpc::MockAsyncClient>(async_client_),
/*failover_async_client_=*/nullptr,
/*dispatcher_=*/dispatcher_,
/*service_method_=*/
*Protobuf::DescriptorPool::generated_pool()->FindMethodByName(
"envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"),
/*local_info_=*/local_info_,
/*rate_limit_settings_=*/rate_limit_settings_,
/*scope_=*/*stats_.rootScope(),
/*config_validators_=*/std::make_unique<NiceMock<MockCustomConfigValidators>>(),
/*xds_resources_delegate_=*/XdsResourcesDelegateOptRef(),
/*xds_config_tracker_=*/XdsConfigTrackerOptRef(),
/*backoff_strategy_=*/
std::make_unique<JitteredExponentialBackOffStrategy>(
SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, random_),
/*target_xds_authority_=*/"",
/*eds_resources_cache_=*/nullptr,
/*skip_subsequent_node_=*/true};
EXPECT_THROW_WITH_MESSAGE(
(XdsMux::GrpcMuxSotw(grpc_mux_context)), EnvoyException,
"ads: node 'id' and 'cluster' are required. Set it either in 'node' config or via "
"--service-node and --service-cluster options.");
}
N/A · no in-tree implementor
N/A
N/A
rpc DeltaAggregatedResources(stream DeltaDiscoveryRequest)
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// This is a gRPC-only API.
rpc StreamAggregatedResources(stream DiscoveryRequest) returns (stream DiscoveryResponse) {
}
See :repo:`discovery.proto <api/envoy/service/discovery/v3/discovery.proto>` for the service definition. This is used by Envoy as a client when
TEST_P(GrpcMuxImplTest, MaybeCreateLoadStatsReporter) {
TestScopedRuntime scoped_runtime;
scoped_runtime.mergeValues({{"envoy.reloadable_features.enable_lrs_server_self_ads", "true"}});
bool factory_called = false;
auto lrs_factory = [&]() -> std::unique_ptr<Upstream::LoadStatsReporter> {
factory_called = true;
return std::make_unique<Upstream::MockLoadStatsReporter>();
};
// Re-setup with the factory
GrpcMuxContext grpc_mux_context{
/*async_client_=*/std::unique_ptr<Grpc::MockAsyncClient>(async_client_),
/*failover_async_client_=*/nullptr,
/*dispatcher_=*/dispatcher_,
/*service_method_=*/
*Protobuf::DescriptorPool::generated_pool()->FindMethodByName(
"envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"),
/*local_info_=*/local_info_,
/*rate_limit_settings_=*/rate_limit_settings_,
/*scope_=*/*stats_.rootScope(),
/*config_validators_=*/std::move(config_validators_),
/*xds_resources_delegate_=*/XdsResourcesDelegateOptRef(),
/*xds_config_tracker_=*/XdsConfigTrackerOptRef(),
/*backoff_strategy_=*/
std::make_unique<JitteredExponentialBackOffStrategy>(
SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, random_),
/*target_xds_authority_=*/"",
/*eds_resources_cache_=*/std::unique_ptr<MockEdsResourcesCache>(eds_resources_cache_),
/*skip_subsequent_node_=*/true,
TEST_P(GrpcMuxImplTest, MaybeCreateLoadStatsReporterRuntimeDisabled) {
TestScopedRuntime scoped_runtime;
scoped_runtime.mergeValues({{"envoy.reloadable_features.enable_lrs_server_self_ads", "false"}});
auto lrs_factory = [&]() -> std::unique_ptr<Upstream::LoadStatsReporter> {
return nullptr; // Should not be called
};
GrpcMuxContext grpc_mux_context{
/*async_client_=*/std::unique_ptr<Grpc::MockAsyncClient>(async_client_),
/*failover_async_client_=*/nullptr,
/*dispatcher_=*/dispatcher_,
/*service_method_=*/
*Protobuf::DescriptorPool::generated_pool()->FindMethodByName(
"envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"),
/*local_info_=*/local_info_,
/*rate_limit_settings_=*/rate_limit_settings_,
/*scope_=*/*stats_.rootScope(),
/*config_validators_=*/std::move(config_validators_),
/*xds_resources_delegate_=*/XdsResourcesDelegateOptRef(),
/*xds_config_tracker_=*/XdsConfigTrackerOptRef(),
/*backoff_strategy_=*/
std::make_unique<JitteredExponentialBackOffStrategy>(
SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, random_),
/*target_xds_authority_=*/"",
/*eds_resources_cache_=*/std::unique_ptr<MockEdsResourcesCache>(eds_resources_cache_),
/*skip_subsequent_node_=*/true,
/*load_stats_reporter_factory_=*/lrs_factory};
grpc_mux_ = std::make_unique<GrpcMuxImpl>(grpc_mux_context);
TEST_P(GrpcMuxImplTest, BadLocalInfoEmptyClusterName) {
EXPECT_CALL(local_info_, clusterName()).WillOnce(ReturnRef(EMPTY_STRING));
GrpcMuxContext grpc_mux_context{
/*async_client_=*/std::unique_ptr<Grpc::MockAsyncClient>(async_client_),
/*failover_async_client_=*/nullptr,
/*dispatcher_=*/dispatcher_,
/*service_method_=*/
*Protobuf::DescriptorPool::generated_pool()->FindMethodByName(
"envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"),
/*local_info_=*/local_info_,
/*rate_limit_settings_=*/rate_limit_settings_,
/*scope_=*/*stats_.rootScope(),
/*config_validators_=*/std::make_unique<NiceMock<MockCustomConfigValidators>>(),
/*xds_resources_delegate_=*/XdsResourcesDelegateOptRef(),
/*xds_config_tracker_=*/XdsConfigTrackerOptRef(),
/*backoff_strategy_=*/
std::make_unique<JitteredExponentialBackOffStrategy>(
SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, random_),
/*target_xds_authority_=*/"",
/*eds_resources_cache_=*/nullptr,
/*skip_subsequent_node_=*/true};
EXPECT_THROW_WITH_MESSAGE(
(XdsMux::GrpcMuxSotw(grpc_mux_context)), EnvoyException,
"ads: node 'id' and 'cluster' are required. Set it either in 'node' config or via "
"--service-node and --service-cluster options.");
}
N/A · no in-tree implementor
N/A
N/A
// all requests/acknowledgments/updates are logically walled off by ``type_url``:
// a Cluster ACK exists in a completely separate world from a prior Route NACK.
// In particular, ``initial_resource_versions`` being sent at the "start" of every
// gRPC stream actually entails a message for each ``type_url``, each with its own
// ``initial_resource_versions``.
// [#next-free-field: 10]
message DeltaDiscoveryRequest {
option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.DeltaDiscoveryRequest";
// The node making the request.
config.core.v3.Node node = 1;
// Type of the resource that is being requested, e.g.
// ``type.googleapis.com/envoy.api.v2.ClusterLoadAssignment``. This does not need to be set if
// resources are only referenced via ``xds_resource_subscribe`` and
// ``xds_resources_unsubscribe``.
string type_url = 2;
non-empty :ref:`resource_names_subscribe <envoy_v3_api_field_service.discovery.v3.DeltaDiscoveryRequest.resource_names_subscribe>` field), the server should treat that identically to how it would treat the client having explicitly subscribed to ``*``. However, once the client does explicitly subscribe to a resource name (whether it be ``*`` or any other name), then this legacy semantic is no longer available; at that point, clearing the list of subscribed resources is interpretted as an unsubscription (see :ref:`Unsubscribing From Resources<xds_protocol_unsubscribing>`) rather than as a subscr…
- Client sends a request with :ref:`resource_names_unsubscribe <envoy_v3_api_field_service.discovery.v3.DeltaDiscoveryRequest.resource_names_unsubscribe>` set to ``*``. Server interprets this as unsubscribing to ``*`` and continuing the existing subscription to ``A``. - Client sends a request with :ref:`resource_names_unsubscribe <envoy_v3_api_field_service.discovery.v3.DeltaDiscoveryRequest.resource_names_unsubscribe>` set to ``A``. Server interprets this as unsubscribing to ``A`` (i.e., the client has now unsubscribed to all resources). Although the set of subscribed resources is now empty, …
A :ref:`resource_names_subscribe <envoy_v3_api_field_service.discovery.v3.DeltaDiscoveryRequest.resource_names_subscribe>` field may contain resource names that the server believes the client is already subscribed to, and furthermore has the most recent versions of. However, the server *must* still provide those resources in the response; due to implementation details hidden from the server, the client may have "forgotten" those resources despite apparently remaining subscribed.
TEST_P(SubscriptionImplTest, InitialRequestResponse) {
startSubscription({"cluster0", "cluster1"});
EXPECT_TRUE(statsAre(1, 0, 0, 0, 0, 0, 0, ""));
deliverConfigUpdate({"cluster0", "cluster1"}, "v25-ubuntu18-beta", true);
EXPECT_TRUE(
statsAre(2, 1, 0, 0, 0, TEST_TIME_MILLIS, 18202868392629624077U, "v25-ubuntu18-beta"));
}
TEST_P(SubscriptionImplTest, ResponseStream) {
startSubscription({"cluster0", "cluster1"});
EXPECT_TRUE(statsAre(1, 0, 0, 0, 0, 0, 0, ""));
deliverConfigUpdate({"cluster0", "cluster1"}, "1.2.3.4", true);
EXPECT_TRUE(statsAre(2, 1, 0, 0, 0, TEST_TIME_MILLIS, 14026795738668939420U, "1.2.3.4"));
deliverConfigUpdate({"cluster0", "cluster1"}, "5_6_7", true);
EXPECT_TRUE(statsAre(3, 2, 0, 0, 0, TEST_TIME_MILLIS, 7612520132475921171U, "5_6_7"));
}
TEST_P(SubscriptionImplTest, RejectConfig) {
startSubscription({"cluster0", "cluster1"});
EXPECT_TRUE(statsAre(1, 0, 0, 0, 0, 0, 0, ""));
deliverConfigUpdate({"cluster0", "cluster1"}, "0", false);
EXPECT_TRUE(statsAre(2, 0, 1, 0, 0, 0, 0, ""));
}
N/A · no in-tree implementor
N/A
N/A
// [#next-free-field: 10]
message DeltaDiscoveryResponse {
option (udpa.annotations.versioning).previous_message_type =
"envoy.api.v2.DeltaDiscoveryResponse";
// The version of the response data (used for debugging).
string system_version_info = 1;
// The response resources. These are typed resources, whose types must match
// the ``type_url`` field.
repeated Resource resources = 2;
// field id 3 IS available!
resource of a :ref:`DeltaDiscoveryResponse <envoy_v3_api_msg_service.discovery.v3.DeltaDiscoveryResponse>`. The resource name will be returned in the name field in the resource of a :ref:`DeltaDiscoveryResponse <envoy_v3_api_msg_service.discovery.v3.DeltaDiscoveryResponse>`.
deleted via the :ref:`removed_resources <envoy_v3_api_field_service.discovery.v3.DeltaDiscoveryResponse.removed_resources>` field of the response. This tells the client to remove the resource from its local cache.
:ref:`removed_resources <envoy_v3_api_field_service.discovery.v3.DeltaDiscoveryResponse.removed_resources>` field.
TEST_P(SubscriptionImplTest, InitialRequestResponse) {
startSubscription({"cluster0", "cluster1"});
EXPECT_TRUE(statsAre(1, 0, 0, 0, 0, 0, 0, ""));
deliverConfigUpdate({"cluster0", "cluster1"}, "v25-ubuntu18-beta", true);
EXPECT_TRUE(
statsAre(2, 1, 0, 0, 0, TEST_TIME_MILLIS, 18202868392629624077U, "v25-ubuntu18-beta"));
}
TEST_P(SubscriptionImplTest, ResponseStream) {
startSubscription({"cluster0", "cluster1"});
EXPECT_TRUE(statsAre(1, 0, 0, 0, 0, 0, 0, ""));
deliverConfigUpdate({"cluster0", "cluster1"}, "1.2.3.4", true);
EXPECT_TRUE(statsAre(2, 1, 0, 0, 0, TEST_TIME_MILLIS, 14026795738668939420U, "1.2.3.4"));
deliverConfigUpdate({"cluster0", "cluster1"}, "5_6_7", true);
EXPECT_TRUE(statsAre(3, 2, 0, 0, 0, TEST_TIME_MILLIS, 7612520132475921171U, "5_6_7"));
}
TEST_P(SubscriptionImplTest, RejectConfig) {
startSubscription({"cluster0", "cluster1"});
EXPECT_TRUE(statsAre(1, 0, 0, 0, 0, 0, 0, ""));
deliverConfigUpdate({"cluster0", "cluster1"}, "0", false);
EXPECT_TRUE(statsAre(2, 0, 1, 0, 0, 0, 0, ""));
}
N/A · no in-tree implementor
N/A
N/A
// A DiscoveryRequest requests a set of versioned resources of the same type for
// a given Envoy node on some API.
// [#next-free-field: 8]
message DiscoveryRequest {
option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.DiscoveryRequest";
// The ``version_info`` provided in the request messages will be the ``version_info``
// received with the most recent successfully processed response or empty on
// the first request. It is expected that no new request is sent after a
// response is received until the Envoy instance is ready to ACK/NACK the new
// configuration. ACK/NACK takes place by returning the new API config version
// as applied or the previous API config version respectively. Each ``type_url``
// (see below) has an independent version associated with it.
string version_info = 1;
:ref:`error_detail <envoy_v3_api_field_service.discovery.v3.DiscoveryRequest.error_detail>` field in the request sent by the client. Some older servers may instead detect a NACK by looking at both the version and the nonce in the request: if the version in the request is not equal to the one sent by the server with that nonce, then the client has rejected the most recent version. However, this approach does not work for APIs other than LDS and CDS for clients that may dynamically change the set of resources that they are subscribing to, unless the server has somehow arranged to increment the r…
resource type have had an empty :ref:`resource_names <envoy_v3_api_field_service.discovery.v3.DiscoveryRequest.resource_names>` field, or in incremental, having never sent a request on the stream for that resource type with a non-empty :ref:`resource_names_subscribe <envoy_v3_api_field_service.discovery.v3.DeltaDiscoveryRequest.resource_names_subscribe>` field), the server should treat that identically to how it would treat the client having explicitly subscribed to ``*``. However, once the client does explicitly subscribe to a resource name (whether it be ``*`` or any other name), then this l…
subscribed to in the :ref:`resource_names <envoy_v3_api_field_service.discovery.v3.DiscoveryRequest.resource_names>` field, so unsubscribing to a set of resources is done by sending a new request containing all resource names that are still being subscribed to but not containing the resource names being unsubscribed to. For example, if the client had previously been subscribed to resources ``A`` and ``B`` but wishes to unsubscribe from ``B``, it must send a new request containing only resource ``A``.
TEST_P(GrpcSubscriptionImplTest, StreamCreationFailure) {
InSequence s;
EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(nullptr));
// onConfigUpdateFailed() should not be called for gRPC stream connection failure
EXPECT_CALL(callbacks_,
onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure, _))
.Times(0);
EXPECT_CALL(random_, random());
EXPECT_CALL(*timer_, enableTimer(_, _));
subscription_->start({"cluster0", "cluster1"});
EXPECT_TRUE(statsAre(2, 0, 0, 1, 0, 0, 0, ""));
// Ensure this doesn't cause an issue by sending a request, since we don't
// have a gRPC stream.
subscription_->updateResourceInterest({"cluster2"});
// Retry and succeed.
EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_));
expectSendMessage({"cluster2"}, "", true);
timer_->invokeCallback();
EXPECT_TRUE(statsAre(3, 0, 0, 1, 0, 0, 0, ""));
verifyControlPlaneStats(1);
}
TEST_P(GrpcSubscriptionImplTest, RemoteStreamClose) {
startSubscription({"cluster0", "cluster1"});
EXPECT_TRUE(statsAre(1, 0, 0, 0, 0, 0, 0, ""));
// onConfigUpdateFailed() should not be called for gRPC stream connection failure
EXPECT_CALL(callbacks_,
onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure, _))
.Times(0);
EXPECT_CALL(*timer_, enableTimer(_, _));
EXPECT_CALL(random_, random());
onRemoteClose();
EXPECT_TRUE(statsAre(2, 0, 0, 1, 0, 0, 0, ""));
verifyControlPlaneStats(0);
// Retry and succeed.
EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_));
expectSendMessage({"cluster0", "cluster1"}, "", true);
timer_->invokeCallback();
EXPECT_TRUE(statsAre(2, 0, 0, 1, 0, 0, 0, ""));
}
TEST_P(GrpcSubscriptionImplTest, RepeatedNonce) {
InSequence s;
startSubscription({"cluster0", "cluster1"});
EXPECT_TRUE(statsAre(1, 0, 0, 0, 0, 0, 0, ""));
// First with the initial, empty version update to "0".
updateResourceInterest({"cluster2"});
EXPECT_TRUE(statsAre(2, 0, 0, 0, 0, 0, 0, ""));
deliverConfigUpdate({"cluster0", "cluster2"}, "0", false);
EXPECT_TRUE(statsAre(3, 0, 1, 0, 0, 0, 0, ""));
deliverConfigUpdate({"cluster0", "cluster2"}, "0", true);
EXPECT_TRUE(statsAre(4, 1, 1, 0, 0, TEST_TIME_MILLIS, 7148434200721666028, "0"));
// Now with version "0" update to "1".
updateResourceInterest({"cluster3"});
EXPECT_TRUE(statsAre(5, 1, 1, 0, 0, TEST_TIME_MILLIS, 7148434200721666028, "0"));
deliverConfigUpdate({"cluster3"}, "42", false);
EXPECT_TRUE(statsAre(6, 1, 2, 0, 0, TEST_TIME_MILLIS, 7148434200721666028, "0"));
deliverConfigUpdate({"cluster3"}, "42", true);
EXPECT_TRUE(statsAre(7, 2, 2, 0, 0, TEST_TIME_MILLIS, 7919287270473417401, "42"));
}
N/A · no in-tree implementor
N/A
N/A
// [#next-free-field: 8]
message DiscoveryResponse {
option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.DiscoveryResponse";
// The version of the response data.
string version_info = 1;
// The response resources. These resources are typed and depend on the API being called.
repeated google.protobuf.Any resources = 2;
// [#not-implemented-hide:]
// Canary is used to support two Envoy command line flags:
//
:ref:`DiscoveryResponse <envoy_v3_api_msg_service.discovery.v3.DiscoveryResponse>`. A management server does not need to send an update until it determines a new version is available. Earlier requests at a version then also become stale. It may process multiple :ref:`DiscoveryRequests <envoy_v3_api_msg_service.discovery.v3.DiscoveryRequest>` at a version until a new version is ready.
``X`` :ref:`version_info <envoy_v3_api_field_service.discovery.v3.DiscoveryResponse.version_info>`. To avoid this, the management server provides a ``nonce`` that Envoy uses to indicate the specific :ref:`DiscoveryResponse <envoy_v3_api_msg_service.discovery.v3.DiscoveryResponse>` each :ref:`DiscoveryRequest <envoy_v3_api_msg_service.discovery.v3.DiscoveryRequest>` corresponds to:
:ref:`DiscoveryResponse <envoy_v3_api_msg_service.discovery.v3.DiscoveryResponse>` proto in the file on update. Binary protobufs, JSON, YAML and proto text are supported formats for the :ref:`DiscoveryResponse <envoy_v3_api_msg_service.discovery.v3.DiscoveryResponse>`.
TEST_P(GrpcSubscriptionImplTest, StreamCreationFailure) {
InSequence s;
EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(nullptr));
// onConfigUpdateFailed() should not be called for gRPC stream connection failure
EXPECT_CALL(callbacks_,
onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure, _))
.Times(0);
EXPECT_CALL(random_, random());
EXPECT_CALL(*timer_, enableTimer(_, _));
subscription_->start({"cluster0", "cluster1"});
EXPECT_TRUE(statsAre(2, 0, 0, 1, 0, 0, 0, ""));
// Ensure this doesn't cause an issue by sending a request, since we don't
// have a gRPC stream.
subscription_->updateResourceInterest({"cluster2"});
// Retry and succeed.
EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_));
expectSendMessage({"cluster2"}, "", true);
timer_->invokeCallback();
EXPECT_TRUE(statsAre(3, 0, 0, 1, 0, 0, 0, ""));
verifyControlPlaneStats(1);
}
TEST_P(GrpcSubscriptionImplTest, RemoteStreamClose) {
startSubscription({"cluster0", "cluster1"});
EXPECT_TRUE(statsAre(1, 0, 0, 0, 0, 0, 0, ""));
// onConfigUpdateFailed() should not be called for gRPC stream connection failure
EXPECT_CALL(callbacks_,
onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure, _))
.Times(0);
EXPECT_CALL(*timer_, enableTimer(_, _));
EXPECT_CALL(random_, random());
onRemoteClose();
EXPECT_TRUE(statsAre(2, 0, 0, 1, 0, 0, 0, ""));
verifyControlPlaneStats(0);
// Retry and succeed.
EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_));
expectSendMessage({"cluster0", "cluster1"}, "", true);
timer_->invokeCallback();
EXPECT_TRUE(statsAre(2, 0, 0, 1, 0, 0, 0, ""));
}
TEST_P(GrpcSubscriptionImplTest, RepeatedNonce) {
InSequence s;
startSubscription({"cluster0", "cluster1"});
EXPECT_TRUE(statsAre(1, 0, 0, 0, 0, 0, 0, ""));
// First with the initial, empty version update to "0".
updateResourceInterest({"cluster2"});
EXPECT_TRUE(statsAre(2, 0, 0, 0, 0, 0, 0, ""));
deliverConfigUpdate({"cluster0", "cluster2"}, "0", false);
EXPECT_TRUE(statsAre(3, 0, 1, 0, 0, 0, 0, ""));
deliverConfigUpdate({"cluster0", "cluster2"}, "0", true);
EXPECT_TRUE(statsAre(4, 1, 1, 0, 0, TEST_TIME_MILLIS, 7148434200721666028, "0"));
// Now with version "0" update to "1".
updateResourceInterest({"cluster3"});
EXPECT_TRUE(statsAre(5, 1, 1, 0, 0, TEST_TIME_MILLIS, 7148434200721666028, "0"));
deliverConfigUpdate({"cluster3"}, "42", false);
EXPECT_TRUE(statsAre(6, 1, 2, 0, 0, TEST_TIME_MILLIS, 7148434200721666028, "0"));
deliverConfigUpdate({"cluster3"}, "42", true);
EXPECT_TRUE(statsAre(7, 2, 2, 0, 0, TEST_TIME_MILLIS, 7919287270473417401, "42"));
}
N/A · no in-tree implementor
N/A
N/A
// A set of dynamic parameter constraints associated with a variant of an individual xDS resource.
// These constraints determine whether the resource matches a subscription based on the set of
// dynamic parameters in the subscription, as specified in the
// :ref:`ResourceLocator.dynamic_parameters <envoy_v3_api_field_service.discovery.v3.ResourceLocator.dynamic_parameters>`
// field. This allows xDS implementations (clients, servers, and caching proxies) to determine
// which variant of a resource is appropriate for a given client.
message DynamicParameterConstraints {
// A single constraint for a given key.
message SingleConstraint {
message Exists {
}
// The key to match against.
string key = 1;
oneof constraint_type {
option (validate.required) = true;
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// [#next-free-field: 10]
message Resource {
option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.Resource";
// Cache control properties for the resource.
// [#not-implemented-hide:]
message CacheControl {
// If true, xDS proxies may not cache this resource.
//
// .. note::
// This does not apply to clients other than xDS proxies, which must cache resources
// for their own use, regardless of the value of this field.
//
:ref:`Resource <envoy_v3_api_msg_service.discovery.v3.Resource>`. This allows setting the same TTL field that is used for Delta xDS with SotW, without changing the SotW API. Heartbeats are supported for SotW as well: any resource within the response that look like a heartbeat resource will only be used to update the TTL.
be specified on each :ref:`Resource <envoy_v3_api_msg_service.discovery.v3.Resource>`. Each resource will have its own TTL expiry time, at which point the resource will be expired. Each xDS type may have different ways of handling such an expiry.
:ref:`Resource <envoy_v3_api_msg_service.discovery.v3.Resource>` with the :ref:`resource <envoy_v3_api_msg_service.discovery.v3.Resource>` unset and version matching the most recently sent version can be used to update the TTL. These resources will not be treated as resource updates, but only as TTL updates.
TEST(DecodedResourceImplTest, All) {
MockOpaqueResourceDecoder resource_decoder;
Protobuf::Any some_opaque_resource;
some_opaque_resource.set_type_url("some_type_url");
{
EXPECT_CALL(resource_decoder, decodeResource(ProtoEq(some_opaque_resource)))
.WillOnce(InvokeWithoutArgs(
[]() -> ProtobufTypes::MessagePtr { return std::make_unique<Protobuf::Empty>(); }));
EXPECT_CALL(resource_decoder, resourceName(ProtoEq(Protobuf::Empty())))
.WillOnce(Return("some_name"));
auto decoded_resource =
*DecodedResourceImpl::fromResource(resource_decoder, some_opaque_resource, "foo");
EXPECT_EQ("some_name", decoded_resource->name());
EXPECT_TRUE(decoded_resource->aliases().empty());
EXPECT_EQ("foo", decoded_resource->version());
EXPECT_THAT(decoded_resource->resource(), ProtoEq(Protobuf::Empty()));
EXPECT_TRUE(decoded_resource->hasResource());
}
{
envoy::service::discovery::v3::Resource resource_wrapper;
resource_wrapper.set_name("real_name");
resource_wrapper.add_aliases("bar");
resource_wrapper.add_aliases("baz");
resource_wrapper.mutable_resource()->MergeFrom(some_opaque_resource);
resource_wrapper.set_version("foo");
EXPECT_CALL(resource_decoder, decodeResource(ProtoEq(some_opaque_resource)))
.WillOnce(InvokeWithoutArgs(
[]() -> ProtobufTypes::MessagePtr { return std::make_unique<Protobuf::Empty>(); }));
TEST_F(CdsApiImplTest, DeltaConfigUpdate) {
{
InSequence s;
setup();
}
EXPECT_CALL(initialized_, ready());
{
Protobuf::RepeatedPtrField<envoy::service::discovery::v3::Resource> resources;
{
envoy::config::cluster::v3::Cluster cluster;
cluster.set_name("cluster_1");
expectAdd("cluster_1", "v1");
auto* resource = resources.Add();
resource->mutable_resource()->PackFrom(cluster);
resource->set_name("cluster_1");
resource->set_version("v1");
}
{
envoy::config::cluster::v3::Cluster cluster;
cluster.set_name("cluster_2");
expectAdd("cluster_2", "v1");
auto* resource = resources.Add();
resource->mutable_resource()->PackFrom(cluster);
resource->set_name("cluster_2");
resource->set_version("v1");
}
const auto decoded_resources =
TestUtility::decodeResources<envoy::config::cluster::v3::Cluster>(resources);
EXPECT_TRUE(cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, {}, "v1").ok());
TEST_F(EdsTest, DeltaOnConfigUpdateSuccess) {
envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment;
cluster_load_assignment.set_cluster_name("fare");
initialize();
Protobuf::RepeatedPtrField<envoy::service::discovery::v3::Resource> resources;
auto* resource = resources.Add();
resource->mutable_resource()->PackFrom(cluster_load_assignment);
resource->set_version("v1");
const auto decoded_resources =
TestUtility::decodeResources<envoy::config::endpoint::v3::ClusterLoadAssignment>(
resources, "cluster_name");
EXPECT_TRUE(eds_callbacks_->onConfigUpdate(decoded_resources.refvec_, {}, "v1").ok());
EXPECT_TRUE(initialized_);
EXPECT_EQ(1UL,
stats_.findCounterByString("cluster.name.update_no_rebuild").value().get().value());
}
N/A · no in-tree implementor
N/A
N/A
// [#not-implemented-hide:]
// An error associated with a specific resource name, returned to the
// client by the server.
message ResourceError {
// The name of the resource.
ResourceName resource_name = 1;
// The error reported for the resource.
google.rpc.Status error_detail = 2;
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Specifies a resource to be subscribed to.
message ResourceLocator {
// The resource name to subscribe to.
string name = 1;
// A set of dynamic parameters used to match against the dynamic parameter
// constraints on the resource. This allows clients to select between
// multiple variants of the same resource.
map<string, string> dynamic_parameters = 2;
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Specifies a concrete resource name.
message ResourceName {
// The name of the resource.
string name = 1;
// Dynamic parameter constraints associated with this resource. To be used by client-side caches
// (including xDS proxies) when matching subscribed resource locators.
DynamicParameterConstraints dynamic_parameter_constraints = 2;
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
workflows rendered-reference block to surface this layer.