Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support api config source without cluster names #2999

Merged

Conversation

ambuc
Copy link
Contributor

@ambuc ambuc commented Apr 4, 2018

To support the deprecation of cluster_names in the api_config_source (#2860), I

  • added a more verbose error in utility.cc for GRPC api_config_sources with any named clusters
  • hunted down places in tests where api_config_source.cluster_names()[0] was implicitly used (assuming that API would be GRPC and would have cluster names).
  • renamed factoryForApiConfigSource to factoryForGrpcApiConfigSource, as it already implicitly returns a Grpc::AsyncClientFactoryPtr, and gave a more verbose error for when the user passes a config with cluster_names set.
  • separated out checkApiConfigSourceSubscriptionBackingCluster into checkApiConfigSourceNames, which does the gRPC services v. cluster names validation, and checkApiConfigSourceSubscriptionBackingCluster, which actually validates the cluster name against the clusters map.
  • more completely covered the tests for the branching cases for non-gRPC API configs which happened to have grpc services defined.

Risk Level: Low
Testing: bazel test test/... all passed.

Fixed #2680
Fixed #2902

Signed-off-by: James Buckland jbuckland@google.com

@htuch htuch self-assigned this Apr 4, 2018
Signed-off-by: James Buckland <jbuckland@google.com>
@ambuc ambuc force-pushed the support-api_config_source-without-cluster_names branch from 8f7fc1b to 6cafac9 Compare April 4, 2018 19:53
@@ -5,6 +5,7 @@
#include "common/config/resources.h"
#include "common/protobuf/protobuf.h"

#include "test/integration/fake_upstream.h"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I'll remove this. I was at one point attempting to mock an upstream target_uri.

@@ -190,18 +188,26 @@ INSTANTIATE_TEST_CASE_P(SubscriptionFactoryTestApiConfigSource,
envoy::api::v2::core::ApiConfigSource::REST,
envoy::api::v2::core::ApiConfigSource::GRPC));

// TODO(jbuckland) add test case for ApiConfigSource::GRPC
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I'll remove this. I had at one point removed envoy::api::v2::core::ApiConfigSource::GRPC from the values block above.

"envoy::api::v2::core::ConfigSource must have a singleton cluster name specified");
}
if (api_config_source.api_type() == envoy::api::v2::core::ApiConfigSource::GRPC) {
if (api_config_source.cluster_names().size() != 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also throw an exception if grpc_services().size() != 1.

"envoy::api::v2::core::ConfigSource must have a statically "
"defined non-EDS cluster: '{}' does not exist, was added via api, or is an EDS cluster",
cluster_name));
const auto& cluster_name = api_config_source.cluster_names()[0];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you meant here. You should have a condition here; if it's api_type() == GRPC && api_config_soures.grpc_service()[0].has_envoy_grpc() then you can verify api_Config.grpc_Services()[0].envou_grpc().cluster_name().

"non-EDS cluster: 'eds_cluster' "
"does not exist, was added via api, or is an EDS cluster");
if (api_config_source->api_type() == envoy::api::v2::core::ApiConfigSource::GRPC) {
EXPECT_THROW_WITH_MESSAGE(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for subscription_factory_test, we should go in and add additional tests to cover all the branches possible now in checkApiConfigSourceSubscriptionBackingCluster

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d149e7f.

Signed-off-by: James Buckland <jbuckland@google.com>
Signed-off-by: James Buckland <jbuckland@google.com>
…urceSubscriptionBackingCluster

Signed-off-by: James Buckland <jbuckland@google.com>
…ps://github.com/ambuc/envoy into support-api_config_source-without-cluster_names

Signed-off-by: James Buckland <jbuckland@google.com>
Signed-off-by: James Buckland <jbuckland@google.com>
…g_source-without-cluster_names

Signed-off-by: James Buckland <jbuckland@google.com>
…ster_names

Signed-off-by: James Buckland <jbuckland@google.com>
Signed-off-by: James Buckland <jbuckland@google.com>
// TODO(htuch): Add support for multiple clusters, #1170.
throw EnvoyException(
"envoy::api::v2::core::ConfigSource must have a singleton cluster name specified");
bool is_grpc = (api_config_source.api_type() == envoy::api::v2::core::ApiConfigSource::GRPC);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: const bool

}
if (api_config_source.grpc_services().size() != 1) {
throw EnvoyException(
"envoy::api::v2::core::ConfigSource::GRPC must have a singleton grpc service specified");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: either "singleton grpc_services" or "single gRPC service".

} else {
if (api_config_source.grpc_services().size() != 0) {
throw EnvoyException(
"envoy::api::v2::core::ConfigSource must not have a grpc service specified");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

}

const auto& cluster_name = api_config_source.cluster_names()[0];
const std::string& cluster_name =
is_grpc ? api_config_source.grpc_services()[0].envoy_grpc().cluster_name()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There needs to be another condition here on has_envoy_grpc(). Some gRPC services will be configured with google_grpc and won't have a cluster name.

}
grpc_service.MergeFrom(api_config_source.grpc_services(0));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can skip this line now and pass grpc_service(0) directly into the below call to the factory method.


if (api_config_source.grpc_services().empty()) {
throw EnvoyException(
fmt::format("Missing gRPC services in envoy::api::v2::core::ApiConfigSource: {}",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems we have the same log messages as in checkApiConfigSourceSubscriptionBackingCluster. Can we refactor? Alternatively, can we expect checkApiConfigSourceSubscriptionBackingCluster to always be called before we get here and just put in some ASSERTs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. in source/common/upstream/cluster_manager_impl.cc, we run factoryFor... to reset load_stats_reporter_, but we don't have a list of clusters to validate against. I could split up checkApiConfig... into two bits, the first of which checks cluster_names() vs. grpc_services() (no list of clusters necessary), and the second of which does the actual validation. Does that sound like the right solution?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

"bar");
cluster_map.emplace("bar", cluster);
envoy::api::v2::core::GrpcService expected_grpc_service_bar;
expected_grpc_service_bar.mutable_envoy_grpc()->set_cluster_name("bar");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, this is a stub from when I was using EXPECT_CALL(cm_.async_client_manager_, factoryForGrpcService(ProtoEq(expected_grpc_service)...)) in all the test cases. I'll get rid of it here...

"foo");
cluster_map.emplace("foo", cluster);
envoy::api::v2::core::GrpcService expected_grpc_service_foo;
expected_grpc_service_foo.mutable_envoy_grpc()->set_cluster_name("foo");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... and here.

Upstream::ClusterManager::ClusterInfoMap cluster_map;
Upstream::MockCluster cluster;
cluster_map.emplace("eds_cluster", cluster);
cluster_map.emplace("foo", cluster);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this lends clarity, seems a gratuitous rename.

Copy link
Contributor Author

@ambuc ambuc Apr 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't actually an eds cluster by default -- because it's a mock object, it defaults to the first enum in envoy::api::v2::Cluster::, which is STATIC. https://github.com/envoyproxy/data-plane-api/blob/master/envoy/api/v2/cds.proto#L67

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd make it static_cluster then for clarity.

if (api_config_source->api_type() == envoy::api::v2::core::ApiConfigSource::GRPC) {
EXPECT_THROW_WITH_MESSAGE(
subscriptionFromConfigSource(config)->start({"foo"}, callbacks_), EnvoyException,
"envoy::api::v2::core::ConfigSource::GRPC must not have a cluster name specified");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to setup this test to actually get the below behavior, by using grpc_services.envoy_grpc.cluster_name to specify cluster name in the gRPC case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll refactor all the SubscriptionFactoryTestApiConfigSource/* tests to better fit this.

Signed-off-by: James Buckland <jbuckland@google.com>
Signed-off-by: James Buckland <jbuckland@google.com>
Signed-off-by: James Buckland <jbuckland@google.com>
Signed-off-by: James Buckland <jbuckland@google.com>
…n-statically defined error instead

Signed-off-by: James Buckland <jbuckland@google.com>
Signed-off-by: James Buckland <jbuckland@google.com>
…iptionBackingCluster into checkApiConfigSourceNames and use it to validate inside factoryForGrpcApiConfigSource

And various assorted testing changes to make this possible

Signed-off-by: James Buckland <jbuckland@google.com>
Copy link
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM modulo nits and the missing coverage case. This is looking like a really nice cleanup.

const bool is_grpc =
(api_config_source.api_type() == envoy::api::v2::core::ApiConfigSource::GRPC);

// we ought to validate the cluster name if and only if there is a cluster name.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super tiny nit: Prefer having comment sentences begin with capital letters (here and below).

@@ -121,6 +121,13 @@ class Utility {
*/
static void checkFilesystemSubscriptionBackingPath(const std::string& path);

/**
* Check the grpc_services and cluster_names for API config sanity. Throws on error.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use Doxygeen @throw syntax here, grep around for examples.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 421a420.

EXPECT_CALL(cm_, clusters()).WillOnce(Return(cluster_map));
EXPECT_THROW_WITH_MESSAGE(
subscriptionFromConfigSource(config), EnvoyException,
"envoy::api::v2::core::ConfigSource::GRPC must have a single gRPC service specified");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to change anything now that we have these tests, but I would probably approach testing by doing full branch coverage in a dedicated unit test for Utility::checkApiConfigSourceNames() and then just do specific point tests in SubscriptionFactoryTest for where behavior is more complex. I think what you have is fine though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good catch. The coverage report actually shows a hole in utility.cc for a REST API with defined gRPC services. I won't refactor this entirely but I am going to add coverage for that case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in bb137e7.

…rpc services

Signed-off-by: James Buckland <jbuckland@google.com>
Signed-off-by: James Buckland <jbuckland@google.com>
@htuch
Copy link
Member

htuch commented Apr 6, 2018

One more pro tip. You can add "Fixed #2680" and "Fixes #2902" to the commit message at the top to close out the issues automatically when this merges.

Copy link
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rad, this is a really great cleanup.

@htuch htuch merged commit 01e75f9 into envoyproxy:master Apr 6, 2018
@ambuc ambuc deleted the support-api_config_source-without-cluster_names branch April 6, 2018 19:56
htuch added a commit to htuch/envoy that referenced this pull request Apr 6, 2018
)"

This reverts commit 01e75f9.

Discuession on #envoy-users revealed that this is going to break operationally for folks who are
trying to roll forward binaries with the same config, there's no config that can work both
before/after.

Signed-off-by: Harvey Tuch <htuch@google.com>
mattklein123 pushed a commit that referenced this pull request Apr 6, 2018
This reverts commit 01e75f9.

Discuession on #envoy-users revealed that this is going to break operationally for folks who are
trying to roll forward binaries with the same config, there's no config that can work both
before/after.

Signed-off-by: Harvey Tuch <htuch@google.com>
ambuc added a commit to ambuc/envoy that referenced this pull request Apr 9, 2018
…yproxy#2999)" (envoyproxy#3018)"

This reverts commit fdcbe10.

Signed-off-by: James Buckland <jbuckland@google.com>
htuch pushed a commit that referenced this pull request Apr 11, 2018
This reverts #3018, which reverted #2999. Therefore this PR is similar to #2999, except that
77b292c allows REST/gRPC config mismatch errors to warn instead of throwing an error. Hopefully this solves the issue for users who were unable to roll forward their binaries while keeping the same config.

Changelist for #2999:

To support the deprecation of cluster_names in the api_config_source (#2860), I

added a more verbose error in utility.cc for GRPC api_config_sources with any named clusters
hunted down places in tests where api_config_source.cluster_names()[0] was implicitly used (assuming that API would be GRPC and would have cluster names).
renamed factoryForApiConfigSource to factoryForGrpcApiConfigSource, as it already implicitly returns a Grpc::AsyncClientFactoryPtr, and gave a more verbose error for when the user passes a config with cluster_names set.
separated out checkApiConfigSourceSubscriptionBackingCluster into checkApiConfigSourceNames, which does the gRPC services v. cluster names validation, and - checkApiConfigSourceSubscriptionBackingCluster, which actually validates the cluster name against the clusters map.
more completely covered the tests for the branching cases for non-gRPC API configs which happened to have grpc services defined.
Risk Level: Low

Testing: bazel test test/... all passed.

Fixed #2680
Fixed #2902

Signed-off-by: James Buckland <jbuckland@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants