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

grid: Plumb the AlternateProtocolCache down to the grid from the UpstreamClusterManager #16385

Merged
merged 23 commits into from
May 18, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
6 changes: 5 additions & 1 deletion api/envoy/config/core/v3/protocol.proto
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ message UpstreamHttpProtocolOptions {
bool auto_san_validation = 2;
}

// Configures the alternate protocols cache which tracks alternate protocols that can be used to
// make an HTTP connection to an origin server. See https://tools.ietf.org/html/rfc7838 for
// HTTP Alternate Services and https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-04
// for the "HTTPS" DNS resource record.
message AlternateProtocolsCacheOptions {
// The name of the cache. Multiple named caches allow independent alternate protocols cache
// configurations to operate within a single Envoy process using different configurations. All
Expand All @@ -84,7 +88,7 @@ message AlternateProtocolsCacheOptions {
// .. note:
//
// The implementation is approximate and enforced independently on each worker thread, thus
// it is possible for the maximum hosts in the cache to go slightly above the configured
// it is possible for the maximum entries in the cache to go slightly above the configured
// value depending on timing. This is similar to how other circuit breakers work.
google.protobuf.UInt32Value max_entries = 2 [(validate.rules).uint32 = {gt: 0}];
}
Expand Down
6 changes: 5 additions & 1 deletion api/envoy/config/core/v4alpha/protocol.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion generated_api_shadow/envoy/config/core/v3/protocol.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 12 additions & 19 deletions source/common/http/alternate_protocols_cache_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,15 @@
namespace Envoy {
namespace Http {

AlternateProtocolsCacheImpl::AlternateProtocolsCacheImpl(ThreadLocal::Instance& tls,
TimeSource& time_source)
: time_source_(time_source), slot_(tls) {
slot_.set([](Event::Dispatcher& /*dispatcher*/) { return std::make_shared<State>(); });
}
AlternateProtocolsCacheImpl::AlternateProtocolsCacheImpl(TimeSource& time_source)
: time_source_(time_source) {}

AlternateProtocolsCacheImpl::~AlternateProtocolsCacheImpl() = default;

void AlternateProtocolsCacheImpl::setAlternatives(const Origin& origin,
const std::vector<AlternateProtocol>& protocols,
const MonotonicTime& expiration) {
// TODO(RyanTheOptimist): Propagate state changes across threads.
State& state = *(slot_.get());
Entry& entry = state.protocols_[origin];
Entry& entry = protocols_[origin];
if (entry.protocols_ != protocols) {
alyssawilk marked this conversation as resolved.
Show resolved Hide resolved
entry.protocols_ = protocols;
}
Expand All @@ -27,26 +22,24 @@ void AlternateProtocolsCacheImpl::setAlternatives(const Origin& origin,

OptRef<const std::vector<AlternateProtocolsCache::AlternateProtocol>>
AlternateProtocolsCacheImpl::findAlternatives(const Origin& origin) {
State& state = *(slot_.get());
auto entry_it = state.protocols_.find(origin);
if (entry_it == state.protocols_.end()) {
return makeOptRefFromPtr<const std::vector<AlternateProtocol>>(nullptr);
auto entry_it = protocols_.find(origin);
if (entry_it == protocols_.end()) {
return makeOptRefFromPtr<const std::vector<AlternateProtocolsCache::AlternateProtocol>>(
nullptr);
}

const Entry& entry = entry_it->second;
if (time_source_.monotonicTime() > entry.expiration_) {
// TODO(RyanTheOptimist): expire entries based on a timer.
// Expire the entry.
RyanTheOptimist marked this conversation as resolved.
Show resolved Hide resolved
state.protocols_.erase(entry_it);
return makeOptRefFromPtr<const std::vector<AlternateProtocol>>(nullptr);
// TODO(RyanTheOptimist): expire entries based on a timer.
protocols_.erase(entry_it);
return makeOptRefFromPtr<const std::vector<AlternateProtocolsCache::AlternateProtocol>>(
nullptr);
}
return makeOptRef(entry.protocols_);
}

size_t AlternateProtocolsCacheImpl::size() const {
State& state = *(slot_.get());
return state.protocols_.size();
}
size_t AlternateProtocolsCacheImpl::size() const { return protocols_.size(); }

} // namespace Http
} // namespace Envoy
17 changes: 7 additions & 10 deletions source/common/http/alternate_protocols_cache_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@
#include "envoy/common/optref.h"
#include "envoy/common/time.h"
#include "envoy/http/alternate_protocols_cache.h"
#include "envoy/thread_local/thread_local.h"

#include "absl/strings/string_view.h"

namespace Envoy {
namespace Http {

// An implementation of AlternateProtocolsCache.
// See: source/docs/http3_upstream.md
class AlternateProtocolsCacheImpl : public AlternateProtocolsCache {
alyssawilk marked this conversation as resolved.
Show resolved Hide resolved
public:
AlternateProtocolsCacheImpl(ThreadLocal::Instance& tls, TimeSource& time_source);
explicit AlternateProtocolsCacheImpl(TimeSource& time_source);
~AlternateProtocolsCacheImpl() override;

// AlternateProtocolsCache
Expand All @@ -32,16 +33,12 @@ class AlternateProtocolsCacheImpl : public AlternateProtocolsCache {
MonotonicTime expiration_;
};

struct State : ThreadLocal::ThreadLocalObject {
// Map from hostname to list of alternate protocols.
// TODO(RyanTheOptimist): Add a limit to the size of this map and evict based on usage.
std::map<Origin, Entry> protocols_;
};

// Time source used to check expiration of entries.
TimeSource& time_source_;
// Thread local state for the cache
ThreadLocal::TypedSlot<State> slot_;

// Map from hostname to list of alternate protocols.
// TODO(RyanTheOptimist): Add a limit to the size of this map and evict based on usage.
std::map<Origin, Entry> protocols_;
};

} // namespace Http
Expand Down
16 changes: 11 additions & 5 deletions source/common/http/alternate_protocols_cache_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,29 @@ namespace Http {

SINGLETON_MANAGER_REGISTRATION(alternate_protocols_cache_manager);

AlternateProtocolsCacheManagerImpl::AlternateProtocolsCacheManagerImpl(TimeSource& time_source,
ThreadLocal::Instance& tls)
: time_source_(time_source), slot_(tls) {
slot_.set([](Event::Dispatcher& /*dispatcher*/) { return std::make_shared<State>(); });
}

AlternateProtocolsCacheSharedPtr AlternateProtocolsCacheManagerImpl::getCache(
const envoy::config::core::v3::AlternateProtocolsCacheOptions& options) {
const auto& existing_cache = caches_.find(options.name());
if (existing_cache != caches_.end()) {
const auto& existing_cache = (*slot_).caches_.find(options.name());
if (existing_cache != (*slot_).caches_.end()) {
if (!Protobuf::util::MessageDifferencer::Equivalent(options, existing_cache->second.options_)) {
throw EnvoyException(fmt::format(
"options specified alternate protocols cache '{}' with different settings"
" old '{}' new '{}'",
" first '{}' second '{}'",
options.name(), existing_cache->second.options_.DebugString(), options.DebugString()));
}

return existing_cache->second.cache_;
}

AlternateProtocolsCacheSharedPtr new_cache =
std::make_shared<AlternateProtocolsCacheImpl>(tls_, time_source_);
caches_.emplace(options.name(), CacheWithOptions{options, new_cache});
std::make_shared<AlternateProtocolsCacheImpl>(time_source_);
(*slot_).caches_.emplace(options.name(), CacheWithOptions{options, new_cache});
return new_cache;
}

Expand Down
14 changes: 9 additions & 5 deletions source/common/http/alternate_protocols_cache_manager_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ namespace Http {
class AlternateProtocolsCacheManagerImpl : public AlternateProtocolsCacheManager,
public Singleton::Instance {
public:
AlternateProtocolsCacheManagerImpl(TimeSource& time_source, ThreadLocal::Instance& tls)
: time_source_(time_source), tls_(tls) {}
AlternateProtocolsCacheManagerImpl(TimeSource& time_source, ThreadLocal::Instance& tls);

// AlternateProtocolsCacheManager
AlternateProtocolsCacheSharedPtr
Expand All @@ -32,11 +31,16 @@ class AlternateProtocolsCacheManagerImpl : public AlternateProtocolsCacheManager
AlternateProtocolsCacheSharedPtr cache_;
};

// Per-thread state.
struct State : public ThreadLocal::ThreadLocalObject {
// Map from config name to cache for that config.
absl::flat_hash_map<std::string, CacheWithOptions> caches_;
};

TimeSource& time_source_;
ThreadLocal::Instance& tls_;

// Map from config name to cache for that config.
absl::flat_hash_map<std::string, CacheWithOptions> caches_;
// Thread local state for the cache.
ThreadLocal::TypedSlot<State> slot_;
};

class AlternateProtocolsCacheManagerFactoryImpl : public AlternateProtocolsCacheManagerFactory {
RyanTheOptimist marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
1 change: 0 additions & 1 deletion source/common/upstream/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ envoy_cc_library(
":subset_lb_lib",
"//include/envoy/api:api_interface",
"//include/envoy/event:dispatcher_interface",
"@envoy_api//envoy/extensions/clusters/dynamic_forward_proxy/v3:pkg_cc_proto",
"//include/envoy/http:codes_interface",
"//include/envoy/local_info:local_info_interface",
"//include/envoy/network:dns_interface",
Expand Down
14 changes: 12 additions & 2 deletions source/docs/http3_upstream.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
### Overview

Support for Upstream HTTP/3 connections is slightly more complex than for HTTP/1 or HTTP/2
alyssawilk marked this conversation as resolved.
Show resolved Hide resolved
and this document attempts to describe it.
and requires specific configurations to enable it. HTTP/3 is only attempted to servers which
Copy link
Contributor

Choose a reason for hiding this comment

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

If HTTP/3 is configured in auto_config, HTTP/3 connections will only be attempted

Since it is force-attempted for explicit_http_config and "do what downstream did"

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 point. Done.

advertise HTTP/3 support either via [HTTP Alternative Services](https://tools.ietf.org/html/rfc7838)
or the [HTTPS DNS resource record](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-04).
If no such advertisement exists, then HTTP/2 or HTTP/1 will be used instead. Further,
HTTP/3 runs over QUIC (which uses UDP) and not over TCP (which HTTP/1 and HTTP/2 use).
It is not uncommon for network devices to block UDP traffic, and hence block HTTP/3. This
means that Upstream HTTP/3 connection attempts might be blocked by the network and should fall
Copy link
Contributor

Choose a reason for hiding this comment

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

upstream here and below

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!

back to using HTTP/2. On such networks, the Upstream connection code needs to
track that HTTP/3 connects attempts are not succeeding and avoid making connections
which are doomed to fail. On networks where HTTP/3 is working correctly, however, the
Upstream connection code should avoid attempting HTTP/2 unnecessarily.
Copy link
Contributor

Choose a reason for hiding this comment

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

Upstream -> upstream :-P

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right! (facepalm)


### Components

Expand Down Expand Up @@ -29,7 +39,7 @@ sent to the mixed pool, and whichever succeeds first will be passed back up to t

### Required configuration

Specific Envoy configuration is required in order to enable the previous. components
Specific Envoy configuration is required in order to enable the previously described components.

#### Auto Cluster Pool

Expand Down
2 changes: 0 additions & 2 deletions test/common/http/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,6 @@ envoy_cc_test(
"//test/mocks/network:connection_mocks",
"//test/mocks/router:router_mocks",
"//test/mocks/runtime:runtime_mocks",
"//test/mocks/thread_local:thread_local_mocks",
"//test/mocks/stats:stats_mocks",
"//source/common/quic:quic_factory_lib",
"//source/common/quic:quic_transport_socket_factory_lib",
Expand All @@ -458,7 +457,6 @@ envoy_cc_test(
":common_lib",
"//source/common/http:alternate_protocols_cache",
"//test/mocks:common_lib",
"//test/mocks/thread_local:thread_local_mocks",
"//test/test_common:simulated_time_system_lib",
],
)
Expand Down
5 changes: 1 addition & 4 deletions test/common/http/alternate_protocols_cache_impl_test.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include "common/http/alternate_protocols_cache_impl.h"

#include "test/mocks/thread_local/mocks.h"
#include "test/test_common/simulated_time_system.h"

#include "gtest/gtest.h"
Expand All @@ -11,9 +10,7 @@ namespace Http {
namespace {
class AlternateProtocolsCacheImplTest : public testing::Test, public Event::TestUsingSimulatedTime {
public:
AlternateProtocolsCacheImplTest() : protocols_(tls_, simTime()) {}

testing::NiceMock<ThreadLocal::MockInstance> tls_;
AlternateProtocolsCacheImplTest() : protocols_(simTime()) {}

AlternateProtocolsCacheImpl protocols_;
const std::string hostname1_ = "hostname1";
Expand Down
11 changes: 3 additions & 8 deletions test/common/http/alternate_protocols_cache_manager_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,9 @@ TEST_F(AlternateProtocolsCacheManagerTest, GetCacheForDifferentOptions) {
TEST_F(AlternateProtocolsCacheManagerTest, GetCacheForConflictingOptions) {
AlternateProtocolsCacheSharedPtr cache1 = manager_->getCache(options1_);
options2_.set_name(options1_.name());
try {
AlternateProtocolsCacheSharedPtr cache2 = manager_->getCache(options2_);
FAIL();
} catch (const EnvoyException& e) {
EXPECT_THAT(e.what(),
testing::HasSubstr(
"options specified alternate protocols cache 'name1' with different settings"));
}
EXPECT_THROW_WITH_REGEX(
manager_->getCache(options2_), EnvoyException,
"options specified alternate protocols cache 'name1' with different settings.*");
}

} // namespace
Expand Down
4 changes: 1 addition & 3 deletions test/common/http/conn_pool_grid_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
#include "test/mocks/http/stream_encoder.h"
#include "test/mocks/network/connection.h"
#include "test/mocks/ssl/mocks.h"
#include "test/mocks/thread_local/mocks.h"
#include "test/mocks/upstream/cluster_info.h"
#include "test/test_common/simulated_time_system.h"
#include "test/test_common/utility.h"
Expand Down Expand Up @@ -117,7 +116,7 @@ class ConnectivityGridTestBase : public Event::TestUsingSimulatedTime, public te
if (!use_alternate_protocols) {
return nullptr;
}
return std::make_shared<AlternateProtocolsCacheImpl>(tls_, simTime());
return std::make_shared<AlternateProtocolsCacheImpl>(simTime());
}

void addHttp3AlternateProtocol() {
Expand All @@ -135,7 +134,6 @@ class ConnectivityGridTestBase : public Event::TestUsingSimulatedTime, public te
NiceMock<Event::MockDispatcher> dispatcher_;
std::shared_ptr<Upstream::MockClusterInfo> cluster_{new NiceMock<Upstream::MockClusterInfo>()};
NiceMock<Random::MockRandomGenerator> random_;
NiceMock<ThreadLocal::MockInstance> tls_;
AlternateProtocolsCacheSharedPtr alternate_protocols_;
ConnectivityGridForTest grid_;
Upstream::HostDescriptionConstSharedPtr host_;
Expand Down