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

rds: validate config in depth before update config dump #7956

Merged
merged 3 commits into from
Aug 21, 2019
Merged
Show file tree
Hide file tree
Changes from 2 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: 5 additions & 0 deletions include/envoy/router/rds.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ class RouteConfigProvider {
* Callback used to notify RouteConfigProvider about configuration changes.
*/
virtual void onConfigUpdate() PURE;

/**
* Validate if the route configuration can be applied to the context of the route config provider.
*/
virtual void validateConfig(const envoy::api::v2::RouteConfiguration& config) const PURE;
};

using RouteConfigProviderPtr = std::unique_ptr<RouteConfigProvider>;
Expand Down
1 change: 1 addition & 0 deletions include/envoy/router/route_config_update_receiver.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class RouteConfigUpdateReceiver {
*/
virtual bool onRdsUpdate(const envoy::api::v2::RouteConfiguration& rc,
const std::string& version_info) PURE;

/**
* Called on updates via VHDS.
* @param added_resources supplies Resources (each containing a VirtualHost) that have been
Expand Down
11 changes: 11 additions & 0 deletions source/common/router/rds_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ void RdsRouteConfigSubscription::onConfigUpdate(
throw EnvoyException(fmt::format("Unexpected RDS configuration (expecting {}): {}",
route_config_name_, route_config.name()));
}
for (auto* provider : route_config_providers_) {
// This seems inefficient, though it is necessary to validate config in each context,
// especially when it comes with per_filter_config,
provider->validateConfig(route_config);
Copy link
Member

Choose a reason for hiding this comment

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

@stevenzzzz FYI, keep in mind we need to retain this behavior in the world of SRDS.

Copy link
Contributor

Choose a reason for hiding this comment

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

SG. This is another reason RDS should move to the config-provider-framework. Where RDSSubscription holds a single RDSProvider and this sort of validation(ConfigImpl instantiation) happens before update propagation.

Copy link
Member Author

Choose a reason for hiding this comment

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

Where RDSSubscription holds a single RDSProvider

@stevenzzzz This is not the case because RDS is being used in multiple context and each context needs its own provider, that's what we address here. We may want to limit that flexibility but it will be hard to do so without breaking API compatibility.

}

if (config_update_info_->onRdsUpdate(route_config, version_info)) {
stats_.config_reload_.inc();
Expand Down Expand Up @@ -198,6 +203,12 @@ void RdsRouteConfigProviderImpl::onConfigUpdate() {
[this, new_config]() -> void { tls_->getTyped<ThreadLocalConfig>().config_ = new_config; });
}

void RdsRouteConfigProviderImpl::validateConfig(
const envoy::api::v2::RouteConfiguration& config) const {
// TODO(lizan): consider cache the config here until onConfigUpdate.
ConfigImpl validation_config(config, factory_context_, false);
}

RouteConfigProviderManagerImpl::RouteConfigProviderManagerImpl(Server::Admin& admin) {
config_tracker_entry_ =
admin.getConfigTracker().add("routes", [this] { return dumpRouteConfigs(); });
Expand Down
4 changes: 3 additions & 1 deletion source/common/router/rds_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class StaticRouteConfigProviderImpl : public RouteConfigProvider {
}
SystemTime lastUpdated() const override { return last_updated_; }
void onConfigUpdate() override {}
void validateConfig(const envoy::api::v2::RouteConfiguration&) const override {}

private:
ConfigConstSharedPtr config_;
Expand Down Expand Up @@ -159,14 +160,15 @@ class RdsRouteConfigProviderImpl : public RouteConfigProvider,
~RdsRouteConfigProviderImpl() override;

RdsRouteConfigSubscription& subscription() { return *subscription_; }
void onConfigUpdate() override;

// Router::RouteConfigProvider
Router::ConfigConstSharedPtr config() override;
absl::optional<ConfigInfo> configInfo() const override {
return config_update_info_->configInfo();
}
SystemTime lastUpdated() const override { return config_update_info_->lastUpdated(); }
void onConfigUpdate() override;
void validateConfig(const envoy::api::v2::RouteConfiguration& config) const override;

private:
struct ThreadLocalConfig : public ThreadLocal::ThreadLocalObject {
Expand Down
1 change: 1 addition & 0 deletions source/server/http/admin.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ class AdminImpl : public Admin,
absl::optional<ConfigInfo> configInfo() const override { return {}; }
SystemTime lastUpdated() const override { return time_source_.systemTime(); }
void onConfigUpdate() override {}
void validateConfig(const envoy::api::v2::RouteConfiguration&) const override {}

Router::ConfigConstSharedPtr config_;
TimeSource& time_source_;
Expand Down
1 change: 1 addition & 0 deletions test/common/http/conn_manager_impl_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ struct RouteConfigProvider : public Router::RouteConfigProvider {
absl::optional<ConfigInfo> configInfo() const override { return {}; }
SystemTime lastUpdated() const override { return time_source_.systemTime(); }
void onConfigUpdate() override {}
void validateConfig(const envoy::api::v2::RouteConfiguration&) const override {}

TimeSource& time_source_;
std::shared_ptr<Router::MockConfig> route_config_{new NiceMock<Router::MockConfig>()};
Expand Down
87 changes: 87 additions & 0 deletions test/common/router/rds_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,93 @@ TEST_F(RouteConfigProviderManagerImplTest, onConfigUpdateWrongSize) {
EnvoyException, "Unexpected RDS resource length: 2");
}

// Regression test for https://github.com/envoyproxy/envoy/issues/7939
TEST_F(RouteConfigProviderManagerImplTest, ConfigDumpAfterConfigRejected) {
auto message_ptr = factory_context_.admin_.config_tracker_.config_tracker_callbacks_["routes"]();
const auto& route_config_dump =
MessageUtil::downcastAndValidate<const envoy::admin::v2alpha::RoutesConfigDump&>(
*message_ptr);

// No routes at all, no last_updated timestamp
envoy::admin::v2alpha::RoutesConfigDump expected_route_config_dump;
TestUtility::loadFromYaml(R"EOF(
static_route_configs:
dynamic_route_configs:
)EOF",
expected_route_config_dump);
EXPECT_EQ(expected_route_config_dump.DebugString(), route_config_dump.DebugString());

timeSystem().setSystemTime(std::chrono::milliseconds(1234567891234));

// dynamic.
setup();
EXPECT_CALL(*factory_context_.cluster_manager_.subscription_factory_.subscription_, start(_));
factory_context_.init_manager_.initialize(init_watcher_);

const std::string response1_json = R"EOF(
{
"version_info": "1",
"resources": [
{
"@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration",
Copy link
Member

Choose a reason for hiding this comment

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

Can you use YAML for examples? it's more concise and the canonical text proto representation in Envoy.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

"name": "foo_route_config",
"virtual_hosts": [
{
"name": "integration",
"domains": [
"*"
],
"routes": [
{
"match": {
"prefix": "/foo"
},
"route": {
"cluster_header": ":authority"
}
}
]
},
{
"name": "duplicate",
"domains": [
"*"
],
"routes": [
{
"match": {
"prefix": "/foo"
},
"route": {
"cluster_header": ":authority"
}
}
]
}
]
}
]
}
)EOF";
auto response1 = TestUtility::parseYaml<envoy::api::v2::DiscoveryResponse>(response1_json);

EXPECT_CALL(init_watcher_, ready());

EXPECT_THROW(rds_callbacks_->onConfigUpdate(response1.resources(), response1.version_info()),
Copy link
Member

Choose a reason for hiding this comment

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

Can you explain why we should expect a throw here? I.e. what's throw-worthy in the config?

Copy link
Member Author

Choose a reason for hiding this comment

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

done

EnvoyException);

message_ptr = factory_context_.admin_.config_tracker_.config_tracker_callbacks_["routes"]();
const auto& route_config_dump3 =
MessageUtil::downcastAndValidate<const envoy::admin::v2alpha::RoutesConfigDump&>(
*message_ptr);
TestUtility::loadFromYaml(R"EOF(
static_route_configs:
dynamic_route_configs:
)EOF",
expected_route_config_dump);
EXPECT_EQ(expected_route_config_dump.DebugString(), route_config_dump3.DebugString());
}

} // namespace
} // namespace Router
} // namespace Envoy