diff --git a/include/envoy/upstream/retry.h b/include/envoy/upstream/retry.h index 48eb599c8698..1266602c6700 100644 --- a/include/envoy/upstream/retry.h +++ b/include/envoy/upstream/retry.h @@ -116,7 +116,7 @@ class RetryHostPredicateFactory { virtual ~RetryHostPredicateFactory() {} virtual void createHostPredicate(RetryHostPredicateFactoryCallbacks& callbacks, - const ProtobufWkt::Struct& config) PURE; + const Protobuf::Message& config) PURE; /** * @return name name of this factory. diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 6c6df524a6d7..eaf1045aaba5 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -108,6 +108,9 @@ EXTENSIONS = { # TODO(lizan): switch to config target once a transport socket exists "envoy.transport_sockets.alts": "//source/extensions/transport_sockets/alts:tsi_handshaker", "envoy.transport_sockets.capture": "//source/extensions/transport_sockets/capture:config", + + # Retry host predicates + "envoy.retry_host_predicates.other_hosts": "//source/extensions/retry/host/other_hosts:config", } WINDOWS_EXTENSIONS = { diff --git a/source/extensions/retry/host/BUILD b/source/extensions/retry/host/BUILD new file mode 100644 index 000000000000..6156949edef6 --- /dev/null +++ b/source/extensions/retry/host/BUILD @@ -0,0 +1,17 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "well_known_names", + hdrs = ["well_known_names.h"], + deps = [ + "//source/common/singleton:const_singleton", + ], +) diff --git a/source/extensions/retry/host/other_hosts/BUILD b/source/extensions/retry/host/other_hosts/BUILD new file mode 100644 index 000000000000..084c1f188914 --- /dev/null +++ b/source/extensions/retry/host/other_hosts/BUILD @@ -0,0 +1,29 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "other_hosts_predicate_lib", + hdrs = ["other_hosts.h"], + deps = [ + "//include/envoy/upstream:retry_interface", + ], +) + +envoy_cc_library( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":other_hosts_predicate_lib", + "//include/envoy/registry", + "//include/envoy/upstream:retry_interface", + "//source/extensions/retry/host:well_known_names", + ], +) diff --git a/source/extensions/retry/host/other_hosts/config.cc b/source/extensions/retry/host/other_hosts/config.cc new file mode 100644 index 000000000000..f4d6fe6158a4 --- /dev/null +++ b/source/extensions/retry/host/other_hosts/config.cc @@ -0,0 +1,17 @@ +#include "extensions/retry/host/other_hosts/config.h" + +#include "envoy/registry/registry.h" +#include "envoy/upstream/retry.h" + +namespace Envoy { +namespace Extensions { +namespace Retry { +namespace Host { + +static Registry::RegisterFactory + register_; +} +} // namespace Retry +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/retry/host/other_hosts/config.h b/source/extensions/retry/host/other_hosts/config.h new file mode 100644 index 000000000000..2d0ee8a1c144 --- /dev/null +++ b/source/extensions/retry/host/other_hosts/config.h @@ -0,0 +1,26 @@ +#pragma once + +#include "envoy/upstream/retry.h" + +#include "extensions/retry/host/other_hosts/other_hosts.h" +#include "extensions/retry/host/well_known_names.h" + +namespace Envoy { +namespace Extensions { +namespace Retry { +namespace Host { + +class OtherHostsRetryPredicateFactory : public Upstream::RetryHostPredicateFactory { +public: + void createHostPredicate(Upstream::RetryHostPredicateFactoryCallbacks& callbacks, + const Protobuf::Message&) override { + callbacks.addHostPredicate(std::make_shared()); + } + + std::string name() override { return RetryHostPredicateValues::get().PreviousHostsPredicate; } +}; + +} // namespace Host +} // namespace Retry +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/retry/host/other_hosts/other_hosts.h b/source/extensions/retry/host/other_hosts/other_hosts.h new file mode 100644 index 000000000000..cf19192fb1a6 --- /dev/null +++ b/source/extensions/retry/host/other_hosts/other_hosts.h @@ -0,0 +1,19 @@ +#pragma once + +#include "envoy/upstream/retry.h" +#include "envoy/upstream/upstream.h" + +namespace Envoy { +class OtherHostsRetryPredicate : public Upstream::RetryHostPredicate { +public: + bool shouldSelectAnotherHost(const Upstream::Host& candidate_host) override { + return attempted_hosts_.find(candidate_host.address()->asString()) != attempted_hosts_.end(); + } + void onHostAttempted(Upstream::HostDescriptionConstSharedPtr attempted_host) override { + attempted_hosts_.insert(attempted_host->address()->asString()); + } + +private: + std::unordered_set attempted_hosts_; +}; +} // namespace Envoy diff --git a/source/extensions/retry/host/well_known_names.h b/source/extensions/retry/host/well_known_names.h new file mode 100644 index 000000000000..38466850b775 --- /dev/null +++ b/source/extensions/retry/host/well_known_names.h @@ -0,0 +1,24 @@ +#pragma once + +#include "common/singleton/const_singleton.h" + +namespace Envoy { +namespace Extensions { +namespace Retry { +namespace Host { + +/** + * Well-known retry host predicate names. + */ +class RetryHostPredicatesNameValues { +public: + // Previous host predicate. Rejects hosts that have already been tried. + const std::string PreviousHostsPredicate = "envoy.retry_host_predicates.previous_hosts"; +}; + +typedef ConstSingleton RetryHostPredicateValues; + +} // namespace Host +} // namespace Retry +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/retry/host/other_hosts/BUILD b/test/extensions/retry/host/other_hosts/BUILD new file mode 100644 index 000000000000..48b7292f92a9 --- /dev/null +++ b/test/extensions/retry/host/other_hosts/BUILD @@ -0,0 +1,22 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +envoy_package() + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_name = "envoy.retry_host_predicates.other_hosts", + deps = [ + "//source/extensions/retry/host/other_hosts:config", + "//test/mocks/upstream:upstream_mocks", + ], +) diff --git a/test/extensions/retry/host/other_hosts/config_test.cc b/test/extensions/retry/host/other_hosts/config_test.cc new file mode 100644 index 000000000000..e90d16810c3f --- /dev/null +++ b/test/extensions/retry/host/other_hosts/config_test.cc @@ -0,0 +1,65 @@ +#include "envoy/registry/registry.h" +#include "envoy/upstream/retry.h" + +#include "extensions/retry/host/other_hosts/config.h" +#include "extensions/retry/host/well_known_names.h" + +#include "test/mocks/upstream/mocks.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace testing; + +namespace Envoy { +namespace Extensions { +namespace Retry { +namespace Host { + +struct TestHostPredicateFactoryCallback : public Upstream::RetryHostPredicateFactoryCallbacks { + + void addHostPredicate(Upstream::RetryHostPredicateSharedPtr host_predicate) { + host_predicate_ = host_predicate; + } + + Upstream::RetryHostPredicateSharedPtr host_predicate_; +}; + +TEST(OtherHostsRetryPredicateConfigTest, PredicateTest) { + auto factory = Registry::FactoryRegistry::getFactory( + RetryHostPredicateValues::get().PreviousHostsPredicate); + + ASSERT_NE(nullptr, factory); + + TestHostPredicateFactoryCallback callback; + ProtobufWkt::Struct config; + factory->createHostPredicate(callback, config); + + auto predicate = callback.host_predicate_; + + auto host1 = std::make_shared>(); + auto host1_address = std::make_shared("127.0.0.1", 123); + ON_CALL(*host1, address()).WillByDefault(Return(host1_address)); + + auto host2 = std::make_shared>(); + auto host2_address = std::make_shared("127.0.0.1", 456); + ON_CALL(*host2, address()).WillByDefault(Return(host2_address)); + + ASSERT_FALSE(predicate->shouldSelectAnotherHost(*host1)); + ASSERT_FALSE(predicate->shouldSelectAnotherHost(*host2)); + + predicate->onHostAttempted(host1); + + ASSERT_TRUE(predicate->shouldSelectAnotherHost(*host1)); + ASSERT_FALSE(predicate->shouldSelectAnotherHost(*host2)); + + predicate->onHostAttempted(host2); + + ASSERT_TRUE(predicate->shouldSelectAnotherHost(*host1)); + ASSERT_TRUE(predicate->shouldSelectAnotherHost(*host2)); +} + +} // namespace Host +} // namespace Retry +} // namespace Extensions +} // namespace Envoy diff --git a/test/integration/test_host_predicate_config.h b/test/integration/test_host_predicate_config.h index 4035d3f1ee8c..7e8c6f5c21d1 100644 --- a/test/integration/test_host_predicate_config.h +++ b/test/integration/test_host_predicate_config.h @@ -10,7 +10,7 @@ class TestHostPredicateFactory : public Upstream::RetryHostPredicateFactory { std::string name() override { return "envoy.test_host_predicate"; } void createHostPredicate(Upstream::RetryHostPredicateFactoryCallbacks& callbacks, - const ProtobufWkt::Struct&) override { + const Protobuf::Message&) override { callbacks.addHostPredicate(std::make_shared()); } };