-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Fixes #7617 - fixes handling creation of VHDS subscription after Init… #8650
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,8 +23,12 @@ | |
#include "envoy/thread_local/thread_local.h" | ||
|
||
#include "common/common/callback_impl.h" | ||
#include "common/common/cleanup.h" | ||
#include "common/common/logger.h" | ||
#include "common/config/resources.h" | ||
#include "common/init/manager_impl.h" | ||
#include "common/init/target_impl.h" | ||
#include "common/init/watcher_impl.h" | ||
#include "common/protobuf/utility.h" | ||
#include "common/router/route_config_update_receiver_impl.h" | ||
#include "common/router/vhds.h" | ||
|
@@ -111,6 +115,9 @@ class RdsRouteConfigSubscription : Envoy::Config::SubscriptionCallbacks, | |
return route_config_providers_; | ||
} | ||
RouteConfigUpdatePtr& routeConfigUpdate() { return config_update_info_; } | ||
void maybeCreateInitManager(const std::string& version_info, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's not leak this method into the public API of this class. Is it not possible to test its behavior in the context of onConfigUpdate? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is a concrete implementation class, not a public interface. I very strongly prefer testability over encapsulation in such a case.
I'm trying to test this in isolation; the setup is already difficult (and fragile) enough without dragging in another method. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps make it private and make the test class a friend so that it can access it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would result in a dependency to test code in production code, would it not? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the friend class statement constitutes a forward reference and needn't ever be declared, but I could be wrong. |
||
std::unique_ptr<Init::ManagerImpl>& init_manager, | ||
std::unique_ptr<Cleanup>& resume_rds); | ||
|
||
private: | ||
// Config::SubscriptionCallbacks | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,6 +72,16 @@ const char Config[] = R"EOF( | |
cluster_name: xds_cluster | ||
)EOF"; | ||
|
||
const char RdsWithoutVhdsConfig[] = R"EOF( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We prefer these configs to are created using the protobuf objects directly rather than parsing configs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the first time I hear about this preference. This test suit been around for a while now, with this exact setup (and it's not the only test suite to use yaml for setup). FWIW, configurations used in these tests have enough nested fields/protobufs that building them programmatically will be laborious and harder to read. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The ConfigHelper object underlying the integration test should be helpful here. I'm ok if you leave this as-is for now with a todo to circle back and refactor this test to use ConfigHelper. I think it's a matter of picking one of the default configs from test/utility/config.cc, adding your RDS/VHDS clusters with a ConfigModifier, and adding virtual hosts either with addVirtualHost or createVirtualHost. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I disagree re: adding vhds to one of default configs (shared test configuration couples independent tests and can make them brittle), but I see value in adding helper methods to generate VHDS config. Will do, but in a separate PR if that's ok. |
||
name: my_route | ||
virtual_hosts: | ||
- name: vhost_rds1 | ||
domains: ["vhost.rds.first"] | ||
routes: | ||
- match: { prefix: "/rdsone" } | ||
route: { cluster: my_service } | ||
)EOF"; | ||
|
||
const char RdsConfig[] = R"EOF( | ||
name: my_route | ||
vhds: | ||
|
@@ -100,6 +110,113 @@ name: my_route | |
cluster_name: xds_cluster | ||
)EOF"; | ||
|
||
const char VhostTemplate[] = R"EOF( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above. |
||
name: {} | ||
domains: [{}] | ||
routes: | ||
- match: {{ prefix: "/" }} | ||
route: {{ cluster: "my_service" }} | ||
)EOF"; | ||
|
||
class VhdsInitializationTest : public HttpIntegrationTest, | ||
public Grpc::GrpcClientIntegrationParamTest { | ||
public: | ||
VhdsInitializationTest() | ||
: HttpIntegrationTest(Http::CodecClient::Type::HTTP2, ipVersion(), realTime(), Config) { | ||
use_lds_ = false; | ||
} | ||
|
||
void TearDown() override { | ||
cleanUpXdsConnection(); | ||
test_server_.reset(); | ||
fake_upstreams_.clear(); | ||
} | ||
|
||
// Overridden to insert this stuff into the initialize() at the very beginning of | ||
// HttpIntegrationTest::testRouterRequestAndResponseWithBody(). | ||
void initialize() override { | ||
// Controls how many fake_upstreams_.emplace_back(new FakeUpstream) will happen in | ||
// BaseIntegrationTest::createUpstreams() (which is part of initialize()). | ||
// Make sure this number matches the size of the 'clusters' repeated field in the bootstrap | ||
// config that you use! | ||
setUpstreamCount(2); // the CDS cluster | ||
setUpstreamProtocol(FakeHttpConnection::Type::HTTP2); // CDS uses gRPC uses HTTP2. | ||
|
||
// BaseIntegrationTest::initialize() does many things: | ||
// 1) It appends to fake_upstreams_ as many as you asked for via setUpstreamCount(). | ||
// 2) It updates your bootstrap config with the ports your fake upstreams are actually listening | ||
// on (since you're supposed to leave them as 0). | ||
// 3) It creates and starts an IntegrationTestServer - the thing that wraps the almost-actual | ||
// Envoy used in the tests. | ||
// 4) Bringing up the server usually entails waiting to ensure that any listeners specified in | ||
// the bootstrap config have come up, and registering them in a port map (see lookupPort()). | ||
// However, this test needs to defer all of that to later. | ||
defer_listener_finalization_ = true; | ||
HttpIntegrationTest::initialize(); | ||
|
||
// Now that the upstream has been created, process Envoy's request to discover it. | ||
// (First, we have to let Envoy establish its connection to the RDS server.) | ||
AssertionResult result = // xds_connection_ is filled with the new FakeHttpConnection. | ||
fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, xds_connection_); | ||
RELEASE_ASSERT(result, result.message()); | ||
result = xds_connection_->waitForNewStream(*dispatcher_, xds_stream_); | ||
RELEASE_ASSERT(result, result.message()); | ||
xds_stream_->startGrpcStream(); | ||
fake_upstreams_[0]->set_allow_unexpected_disconnects(true); | ||
|
||
EXPECT_TRUE(compareSotwDiscoveryRequest(Config::TypeUrl::get().RouteConfiguration, "", | ||
{"my_route"}, true)); | ||
sendSotwDiscoveryResponse<envoy::api::v2::RouteConfiguration>( | ||
Config::TypeUrl::get().RouteConfiguration, | ||
{TestUtility::parseYaml<envoy::api::v2::RouteConfiguration>(RdsWithoutVhdsConfig)}, "1"); | ||
|
||
// Wait for our statically specified listener to become ready, and register its port in the | ||
// test framework's downstream listener port map. | ||
test_server_->waitUntilListenersReady(); | ||
registerTestServerPorts({"http"}); | ||
} | ||
|
||
FakeStreamPtr vhds_stream_; | ||
}; | ||
|
||
INSTANTIATE_TEST_SUITE_P(IpVersionsClientType, VhdsInitializationTest, | ||
GRPC_CLIENT_INTEGRATION_PARAMS); | ||
|
||
// tests a scenario when: | ||
// - RouteConfiguration without VHDS is received | ||
// - RouteConfiguration update with VHDS configuration in it is received | ||
// - Upstream makes a request to a VirtualHost in the VHDS update | ||
TEST_P(VhdsInitializationTest, InitializeVhdsAfterRdsHasBeenInitialized) { | ||
// Calls our initialize(), which includes establishing a listener, route, and cluster. | ||
testRouterHeaderOnlyRequestAndResponse(nullptr, 1, "/rdsone", "vhost.rds.first"); | ||
cleanupUpstreamAndDownstream(); | ||
codec_client_->waitForDisconnect(); | ||
|
||
// Update RouteConfig, this time include VHDS config | ||
sendSotwDiscoveryResponse<envoy::api::v2::RouteConfiguration>( | ||
Config::TypeUrl::get().RouteConfiguration, | ||
{TestUtility::parseYaml<envoy::api::v2::RouteConfiguration>(RdsConfigWithVhosts)}, "2"); | ||
|
||
auto result = xds_connection_->waitForNewStream(*dispatcher_, vhds_stream_, true); | ||
RELEASE_ASSERT(result, result.message()); | ||
vhds_stream_->startGrpcStream(); | ||
|
||
EXPECT_TRUE( | ||
compareDeltaDiscoveryRequest(Config::TypeUrl::get().VirtualHost, {}, {}, vhds_stream_)); | ||
sendDeltaDiscoveryResponse<envoy::api::v2::route::VirtualHost>( | ||
Config::TypeUrl::get().VirtualHost, | ||
{TestUtility::parseYaml<envoy::api::v2::route::VirtualHost>( | ||
fmt::format(VhostTemplate, "vhost_0", "vhost.first"))}, | ||
{}, "1", vhds_stream_); | ||
EXPECT_TRUE( | ||
compareDeltaDiscoveryRequest(Config::TypeUrl::get().VirtualHost, {}, {}, vhds_stream_)); | ||
|
||
// Confirm vhost.first that was configured via VHDS is reachable | ||
testRouterHeaderOnlyRequestAndResponse(nullptr, 1, "/", "vhost.first"); | ||
cleanupUpstreamAndDownstream(); | ||
codec_client_->waitForDisconnect(); | ||
} | ||
|
||
class VhdsIntegrationTest : public HttpIntegrationTest, | ||
public Grpc::GrpcClientIntegrationParamTest { | ||
public: | ||
|
@@ -115,14 +232,7 @@ class VhdsIntegrationTest : public HttpIntegrationTest, | |
} | ||
|
||
std::string virtualHostYaml(const std::string& name, const std::string& domain) { | ||
return fmt::format(R"EOF( | ||
name: {} | ||
domains: [{}] | ||
routes: | ||
- match: {{ prefix: "/" }} | ||
route: {{ cluster: "my_service" }} | ||
)EOF", | ||
name, domain); | ||
return fmt::format(VhostTemplate, name, domain); | ||
} | ||
|
||
envoy::api::v2::route::VirtualHost buildVirtualHost() { | ||
|
@@ -206,8 +316,6 @@ class VhdsIntegrationTest : public HttpIntegrationTest, | |
bool use_rds_with_vhosts{false}; | ||
}; | ||
|
||
INSTANTIATE_TEST_SUITE_P(IpVersionsClientType, VhdsIntegrationTest, GRPC_CLIENT_INTEGRATION_PARAMS); | ||
|
||
// tests a scenario when: | ||
// - a spontaneous VHDS DiscoveryResponse adds two virtual hosts | ||
// - the next spontaneous VHDS DiscoveryResponse removes newly added virtual hosts | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: return unique_ptrInit::Manager. Maybe optionalInit::ManagerImpl
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm populating both init_manager and init_vhds method parameters, I think returning just one of them as the result of the method call would be confusing...