From 293d8522292f04d7e25861e48b1899721b53281a Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Mon, 14 Oct 2019 06:13:11 -0700 Subject: [PATCH 01/27] Refactor the redirection logic into the RedisConnPool. Add CompositeArray to allow the RedisConnPool to maintain a shared_ptr to the request and to avoid copying the request. Added performance test Signed-off-by: Henry Yang --- .../extensions/clusters/redis/redis_cluster.h | 6 +- .../clusters/redis/redis_cluster_lb.cc | 15 +- .../filters/network/common/redis/BUILD | 6 +- .../filters/network/common/redis/client.h | 14 +- .../network/common/redis/client_impl.cc | 13 +- .../network/common/redis/client_impl.h | 6 +- .../filters/network/common/redis/codec.h | 78 +- .../network/common/redis/codec_impl.cc | 142 ++++ .../filters/network/common/redis/codec_impl.h | 55 +- .../filters/network/common/redis/utility.cc | 77 +- .../filters/network/common/redis/utility.h | 37 + .../filters/network/redis_proxy/BUILD | 4 + .../redis_proxy/command_splitter_impl.cc | 287 ++----- .../redis_proxy/command_splitter_impl.h | 36 +- .../filters/network/redis_proxy/conn_pool.h | 29 +- .../network/redis_proxy/conn_pool_impl.cc | 174 ++++- .../network/redis_proxy/conn_pool_impl.h | 61 +- .../extensions/health_checkers/redis/redis.cc | 2 +- .../extensions/health_checkers/redis/redis.h | 6 +- .../clusters/redis/redis_cluster_test.cc | 13 +- .../network/common/redis/client_impl_test.cc | 60 +- .../network/common/redis/codec_impl_test.cc | 78 ++ .../filters/network/common/redis/mocks.cc | 12 +- .../filters/network/common/redis/mocks.h | 36 +- .../filters/network/redis_proxy/BUILD | 18 + .../redis_proxy/command_split_speed_test.cc | 125 ++++ .../redis_proxy/command_splitter_impl_test.cc | 705 +----------------- .../redis_proxy/conn_pool_impl_test.cc | 408 ++++++++-- .../filters/network/redis_proxy/mocks.cc | 3 + .../filters/network/redis_proxy/mocks.h | 24 +- .../health_checkers/redis/redis_test.cc | 24 +- 31 files changed, 1412 insertions(+), 1142 deletions(-) create mode 100644 test/extensions/filters/network/redis_proxy/command_split_speed_test.cc diff --git a/source/extensions/clusters/redis/redis_cluster.h b/source/extensions/clusters/redis/redis_cluster.h index ca90a9e9ab14..a90ea64c2d58 100644 --- a/source/extensions/clusters/redis/redis_cluster.h +++ b/source/extensions/clusters/redis/redis_cluster.h @@ -192,7 +192,7 @@ class RedisCluster : public Upstream::BaseDynamicClusterImpl { struct RedisDiscoverySession : public Extensions::NetworkFilters::Common::Redis::Client::Config, - public Extensions::NetworkFilters::Common::Redis::Client::PoolCallbacks { + public Extensions::NetworkFilters::Common::Redis::Client::ClientCallbacks { RedisDiscoverySession(RedisCluster& parent, NetworkFilters::Common::Redis::Client::ClientFactory& client_factory); @@ -223,11 +223,11 @@ class RedisCluster : public Upstream::BaseDynamicClusterImpl { return Extensions::NetworkFilters::Common::Redis::Client::ReadPolicy::Master; } - // Extensions::NetworkFilters::Common::Redis::Client::PoolCallbacks + // Extensions::NetworkFilters::Common::Redis::Client::ClientCallbacks void onResponse(NetworkFilters::Common::Redis::RespValuePtr&& value) override; void onFailure() override; // Note: Below callback isn't used in topology updates - bool onRedirection(const NetworkFilters::Common::Redis::RespValue&) override { return true; } + bool onRedirection(NetworkFilters::Common::Redis::RespValuePtr&&) override { return true; } void onUnexpectedResponse(const NetworkFilters::Common::Redis::RespValuePtr&); Network::Address::InstanceConstSharedPtr diff --git a/source/extensions/clusters/redis/redis_cluster_lb.cc b/source/extensions/clusters/redis/redis_cluster_lb.cc index b4f3b1d06a2c..25b52cacc5cc 100644 --- a/source/extensions/clusters/redis/redis_cluster_lb.cc +++ b/source/extensions/clusters/redis/redis_cluster_lb.cc @@ -163,15 +163,20 @@ Upstream::HostConstSharedPtr RedisClusterLoadBalancerFactory::RedisClusterLoadBa namespace { bool isReadRequest(const NetworkFilters::Common::Redis::RespValue& request) { - if (request.type() != NetworkFilters::Common::Redis::RespType::Array) { + const NetworkFilters::Common::Redis::RespValue* command = nullptr; + if (request.type() == NetworkFilters::Common::Redis::RespType::Array) { + command = &(request.asArray()[0]); + } else if (request.type() == NetworkFilters::Common::Redis::RespType::CompositeArray) { + command = request.asCompositeArray().command(); + } + if (!command) { return false; } - auto first = request.asArray()[0]; - if (first.type() != NetworkFilters::Common::Redis::RespType::SimpleString && - first.type() != NetworkFilters::Common::Redis::RespType::BulkString) { + if (command->type() != NetworkFilters::Common::Redis::RespType::SimpleString && + command->type() != NetworkFilters::Common::Redis::RespType::BulkString) { return false; } - return NetworkFilters::Common::Redis::SupportedCommands::isReadCommand(first.asString()); + return NetworkFilters::Common::Redis::SupportedCommands::isReadCommand(command->asString()); } } // namespace diff --git a/source/extensions/filters/network/common/redis/BUILD b/source/extensions/filters/network/common/redis/BUILD index ae2702107c4a..2bd232d50516 100644 --- a/source/extensions/filters/network/common/redis/BUILD +++ b/source/extensions/filters/network/common/redis/BUILD @@ -11,7 +11,10 @@ envoy_package() envoy_cc_library( name = "codec_interface", hdrs = ["codec.h"], - deps = ["//include/envoy/buffer:buffer_interface"], + deps = [ + "//source/common/common:assert_lib", + "//include/envoy/buffer:buffer_interface", + ], ) envoy_cc_library( @@ -79,6 +82,7 @@ envoy_cc_library( hdrs = ["utility.h"], deps = [ ":codec_lib", + "//source/common/common:utility_lib", ], ) diff --git a/source/extensions/filters/network/common/redis/client.h b/source/extensions/filters/network/common/redis/client.h index fc76c61ac44e..e100b18e0491 100644 --- a/source/extensions/filters/network/common/redis/client.h +++ b/source/extensions/filters/network/common/redis/client.h @@ -30,9 +30,9 @@ class PoolRequest { /** * Outbound request callbacks. */ -class PoolCallbacks { +class ClientCallbacks { public: - virtual ~PoolCallbacks() = default; + virtual ~ClientCallbacks() = default; /** * Called when a pipelined response is received. @@ -50,19 +50,19 @@ class PoolCallbacks { * @param value supplies the MOVED error response * @return bool true if the request is successfully redirected, false otherwise */ - virtual bool onRedirection(const Common::Redis::RespValue& value) PURE; + virtual bool onRedirection(RespValuePtr&& value) PURE; }; /** * DoNothingPoolCallbacks is used for internally generated commands whose response is * transparently filtered, and redirection never occurs (e.g., "asking", "auth", etc.). */ -class DoNothingPoolCallbacks : public PoolCallbacks { +class DoNothingPoolCallbacks : public ClientCallbacks { public: - // PoolCallbacks + // ClientCallbacks void onResponse(Common::Redis::RespValuePtr&&) override {} void onFailure() override {} - bool onRedirection(const Common::Redis::RespValue&) override { return false; } + bool onRedirection(Common::Redis::RespValuePtr&&) override { return false; } }; /** @@ -95,7 +95,7 @@ class Client : public Event::DeferredDeletable { * @return PoolRequest* a handle to the active request or nullptr if the request could not be made * for some reason. */ - virtual PoolRequest* makeRequest(const RespValue& request, PoolCallbacks& callbacks) PURE; + virtual PoolRequest* makeRequest(const RespValue& request, ClientCallbacks& callbacks) PURE; /** * Initialize the connection. Issue the auth command and readonly command as needed. diff --git a/source/extensions/filters/network/common/redis/client_impl.cc b/source/extensions/filters/network/common/redis/client_impl.cc index 631221a39496..ae59768b9333 100644 --- a/source/extensions/filters/network/common/redis/client_impl.cc +++ b/source/extensions/filters/network/common/redis/client_impl.cc @@ -7,6 +7,8 @@ namespace Common { namespace Redis { namespace Client { namespace { +// null_pool_callbacks is used for requests that must be filtered and not redirected such as +// "asking". Common::Redis::Client::DoNothingPoolCallbacks null_pool_callbacks; } // namespace @@ -97,7 +99,7 @@ void ClientImpl::flushBufferAndResetTimer() { connection_->write(encoder_buffer_, false); } -PoolRequest* ClientImpl::makeRequest(const RespValue& request, PoolCallbacks& callbacks) { +PoolRequest* ClientImpl::makeRequest(const RespValue& request, ClientCallbacks& callbacks) { ASSERT(connection_->state() == Network::Connection::State::Open); const bool empty_buffer = encoder_buffer_.length() == 0; @@ -212,7 +214,7 @@ void ClientImpl::onRespValue(RespValuePtr&& value) { } request.aggregate_request_timer_->complete(); - PoolCallbacks& callbacks = request.callbacks_; + ClientCallbacks& callbacks = request.callbacks_; // We need to ensure the request is popped before calling the callback, since the callback might // result in closing the connection. @@ -224,15 +226,14 @@ void ClientImpl::onRespValue(RespValuePtr&& value) { bool redirected = false; if (err.size() == 3) { if (err[0] == RedirectionResponse::get().MOVED || err[0] == RedirectionResponse::get().ASK) { - redirected = callbacks.onRedirection(*value); + redirected = callbacks.onRedirection(std::move(value)); if (redirected) { host_->cluster().stats().upstream_internal_redirect_succeeded_total_.inc(); } else { host_->cluster().stats().upstream_internal_redirect_failed_total_.inc(); } } - } - if (!redirected) { + } else { callbacks.onResponse(std::move(value)); } } else { @@ -251,7 +252,7 @@ void ClientImpl::onRespValue(RespValuePtr&& value) { putOutlierEvent(Upstream::Outlier::Result::EXT_ORIGIN_REQUEST_SUCCESS); } -ClientImpl::PendingRequest::PendingRequest(ClientImpl& parent, PoolCallbacks& callbacks, +ClientImpl::PendingRequest::PendingRequest(ClientImpl& parent, ClientCallbacks& callbacks, Stats::StatName command) : parent_(parent), callbacks_(callbacks), command_{command}, aggregate_request_timer_(parent_.redis_command_stats_->createAggregateTimer( diff --git a/source/extensions/filters/network/common/redis/client_impl.h b/source/extensions/filters/network/common/redis/client_impl.h index be0d1f8119be..d7466c09e570 100644 --- a/source/extensions/filters/network/common/redis/client_impl.h +++ b/source/extensions/filters/network/common/redis/client_impl.h @@ -83,7 +83,7 @@ class ClientImpl : public Client, public DecoderCallbacks, public Network::Conne connection_->addConnectionCallbacks(callbacks); } void close() override; - PoolRequest* makeRequest(const RespValue& request, PoolCallbacks& callbacks) override; + PoolRequest* makeRequest(const RespValue& request, ClientCallbacks& callbacks) override; bool active() override { return !pending_requests_.empty(); } void flushBufferAndResetTimer(); void initialize(const std::string& auth_password) override; @@ -104,14 +104,14 @@ class ClientImpl : public Client, public DecoderCallbacks, public Network::Conne }; struct PendingRequest : public PoolRequest { - PendingRequest(ClientImpl& parent, PoolCallbacks& callbacks, Stats::StatName stat_name); + PendingRequest(ClientImpl& parent, ClientCallbacks& callbacks, Stats::StatName stat_name); ~PendingRequest() override; // PoolRequest void cancel() override; ClientImpl& parent_; - PoolCallbacks& callbacks_; + ClientCallbacks& callbacks_; Stats::StatName command_; bool canceled_{}; Stats::CompletableTimespanPtr aggregate_request_timer_; diff --git a/source/extensions/filters/network/common/redis/codec.h b/source/extensions/filters/network/common/redis/codec.h index 47b4b6da2002..7f7b1ba29002 100644 --- a/source/extensions/filters/network/common/redis/codec.h +++ b/source/extensions/filters/network/common/redis/codec.h @@ -7,6 +7,9 @@ #include "envoy/buffer/buffer.h" #include "envoy/common/exception.h" +#include "common/common/assert.h" +#include "common/common/logger.h" + namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -16,7 +19,7 @@ namespace Redis { /** * All RESP types as defined here: https://redis.io/topics/protocol */ -enum class RespType { Null, SimpleString, BulkString, Integer, Error, Array }; +enum class RespType { Null, SimpleString, BulkString, Integer, Error, Array, CompositeArray }; /** * A variant implementation of a RESP value optimized for performance. A C++11 union is used for @@ -25,10 +28,17 @@ enum class RespType { Null, SimpleString, BulkString, Integer, Error, Array }; class RespValue { public: RespValue() : type_(RespType::Null) {} - ~RespValue() { cleanup(); } + + RespValue(std::shared_ptr base_array, const RespValue& command, const uint64_t start, + const uint64_t end) : type_(RespType::CompositeArray) { + new (&composite_array_) CompositeArray(std::move(base_array), command, start, end); + } + virtual ~RespValue() { cleanup(); } RespValue(const RespValue& other); // copy constructor + RespValue(RespValue&& other) noexcept; // move constructor RespValue& operator=(const RespValue& other); // copy assignment + RespValue& operator=(RespValue&& other); // move assignment bool operator==(const RespValue& other) const; // test for equality, unit tests bool operator!=(const RespValue& other) const { return !(*this == other); } @@ -37,6 +47,60 @@ class RespValue { */ std::string toString() const; + class CompositeArray { + public: + CompositeArray() = default; + CompositeArray(std::shared_ptr base_array, const RespValue& command, const uint64_t start, + const uint64_t end) : base_array_(std::move(base_array)), command_(&command), start_(start), end_(end) { + ASSERT(command.type() == RespType::BulkString || command.type() == RespType::SimpleString); + ASSERT(base_array_ != nullptr); + ASSERT(base_array_->type() == RespType::Array); + ASSERT(start <= end); + ASSERT(start >= 0); + ASSERT(end < base_array_->asArray().size()); + } + + const RespValue* command() const { return command_; } + const std::shared_ptr baseArray() const { return base_array_; } + + bool operator==(const CompositeArray& other) const; + + uint32_t size() const; + + /** + * Forward const iterator for CompositeArray. + * @note this implementation currently supports the minimum functionality needed to support + * the `for (const RespValue& value : array)` idiom. + */ + struct CompositeArrayConstIterator { + CompositeArrayConstIterator(const RespValue* command, const std::vector& array, + uint64_t index, bool first) + : command_(command), array_(array), index_(index), first_(first) {} + const RespValue& operator*(); + CompositeArrayConstIterator& operator++(); + bool operator!=(const CompositeArrayConstIterator& rhs) const; + static const CompositeArrayConstIterator& empty(); + + const RespValue* command_; + const std::vector& array_; + uint64_t index_; + bool first_; + }; + + CompositeArrayConstIterator begin() const noexcept { + return (command_ && base_array_) ? CompositeArrayConstIterator{command_, base_array_->asArray(), start_, true} : CompositeArrayConstIterator::empty(); + } + + CompositeArrayConstIterator end() const noexcept { + return (command_ && base_array_) ? CompositeArrayConstIterator{command_, base_array_->asArray(), end_+1, false} : CompositeArrayConstIterator::empty(); + } + private: + std::shared_ptr base_array_; + const RespValue* command_; + uint64_t start_; + uint64_t end_; + }; + /** * The following are getters and setters for the internal value. A RespValue starts as null, * and must change type via type() before the following methods can be used. @@ -47,6 +111,8 @@ class RespValue { const std::string& asString() const; int64_t& asInteger(); int64_t asInteger() const; + CompositeArray& asCompositeArray(); + const CompositeArray& asCompositeArray() const; /** * Get/set the type of the RespValue. A RespValue can only be a single type at a time. Each time @@ -55,19 +121,23 @@ class RespValue { RespType type() const { return type_; } void type(RespType type); +protected: + RespType type_{}; + private: union { std::vector array_; std::string string_; int64_t integer_; + CompositeArray composite_array_; }; void cleanup(); - - RespType type_{}; }; using RespValuePtr = std::unique_ptr; +using RespValueSharedPtr = std::shared_ptr; +using RespValueConstSharedPtr = std::shared_ptr; /** * Callbacks that the decoder fires. diff --git a/source/extensions/filters/network/common/redis/codec_impl.cc b/source/extensions/filters/network/common/redis/codec_impl.cc index 4c80b43ff512..643a38f601ed 100644 --- a/source/extensions/filters/network/common/redis/codec_impl.cc +++ b/source/extensions/filters/network/common/redis/codec_impl.cc @@ -28,6 +28,17 @@ std::string RespValue::toString() const { } return ret + "]"; } + case RespType::CompositeArray: { + std::string ret = "["; + uint64_t i = 0; + for (const RespValue& value : asCompositeArray()) { + ret += value.toString(); + if (++i != asCompositeArray().size()) { + ret += ", "; + } + } + return ret + "]"; + } case RespType::SimpleString: case RespType::BulkString: case RespType::Error: @@ -73,6 +84,16 @@ int64_t RespValue::asInteger() const { return integer_; } +RespValue::CompositeArray& RespValue::asCompositeArray() { + ASSERT(type_ == RespType::CompositeArray); + return composite_array_; +} + +const RespValue::CompositeArray& RespValue::asCompositeArray() const { + ASSERT(type_ == RespType::CompositeArray); + return composite_array_; +} + void RespValue::cleanup() { // Need to manually delete because of the union. switch (type_) { @@ -80,6 +101,10 @@ void RespValue::cleanup() { array_.~vector(); break; } + case RespType::CompositeArray: { + composite_array_.~CompositeArray(); + break; + } case RespType::SimpleString: case RespType::BulkString: case RespType::Error: { @@ -103,6 +128,10 @@ void RespValue::type(RespType type) { new (&array_) std::vector(); break; } + case RespType::CompositeArray: { + new (&composite_array_) CompositeArray(); + break; + } case RespType::SimpleString: case RespType::BulkString: case RespType::Error: { @@ -123,6 +152,10 @@ RespValue::RespValue(const RespValue& other) : type_(RespType::Null) { this->asArray() = other.asArray(); break; } + case RespType::CompositeArray: { + this->asCompositeArray() = other.asCompositeArray(); + break; + } case RespType::SimpleString: case RespType::BulkString: case RespType::Error: { @@ -138,6 +171,30 @@ RespValue::RespValue(const RespValue& other) : type_(RespType::Null) { } } +RespValue::RespValue(RespValue&& other) noexcept : type_(other.type_) { + switch (type_) { + case RespType::Array: { + new (&array_) std::vector(std::move(other.array_)); + break; + } + case RespType::CompositeArray: { + new (&composite_array_) CompositeArray(std::move(other.composite_array_)); + } + case RespType::SimpleString: + case RespType::BulkString: + case RespType::Error: { + string_ = std::move(other.string_); + break; + } + case RespType::Integer: { + integer_ = std::move(other.integer_); + break; + } + case RespType::Null: + break; + } +} + RespValue& RespValue::operator=(const RespValue& other) { if (&other == this) { return *this; @@ -148,6 +205,10 @@ RespValue& RespValue::operator=(const RespValue& other) { this->asArray() = other.asArray(); break; } + case RespType::CompositeArray: { + this->asCompositeArray() = other.asCompositeArray(); + break; + } case RespType::SimpleString: case RespType::BulkString: case RespType::Error: { @@ -164,6 +225,37 @@ RespValue& RespValue::operator=(const RespValue& other) { return *this; } +RespValue& RespValue::operator=(RespValue&& other) { + if (&other == this) { + return *this; + } + + type(other.type()); + switch (type_) { + case RespType::Array: { + array_ = std::move(other.array_); + break; + } + case RespType::CompositeArray: { + composite_array_ = std::move(other.composite_array_); + break; + } + case RespType::SimpleString: + case RespType::BulkString: + case RespType::Error: { + string_ = std::move(other.string_); + break; + } + case RespType::Integer: { + integer_ = std::move(other.integer_); + break; + } + case RespType::Null: + break; + } + return *this; +} + bool RespValue::operator==(const RespValue& other) const { bool result = false; if (type_ != other.type()) { @@ -175,6 +267,10 @@ bool RespValue::operator==(const RespValue& other) const { result = (this->asArray() == other.asArray()); break; } + case RespType::CompositeArray: { + result = (this->asCompositeArray() == other.asCompositeArray()); + break; + } case RespType::SimpleString: case RespType::BulkString: case RespType::Error: { @@ -193,6 +289,35 @@ bool RespValue::operator==(const RespValue& other) const { return result; } +uint32_t RespValue::CompositeArray::size() const { + return end_ - start_ + 2; +} + +bool RespValue::CompositeArray::operator==(const RespValue::CompositeArray& other) const { + return base_array_ == other.base_array_ && command_ == other.command_ && start_ == other.start_ && end_ == other.end_; +} + +const RespValue& RespValue::CompositeArray::CompositeArrayConstIterator::operator*() { return first_ ? *command_ : array_[index_]; } + +RespValue::CompositeArray::CompositeArrayConstIterator& RespValue::CompositeArray::CompositeArrayConstIterator::operator++() { + if (first_) { + first_ = false; + } else { + ++index_; + } + return *this; +} + +bool RespValue::CompositeArray::CompositeArrayConstIterator::operator!=(const CompositeArrayConstIterator& rhs) const { + return command_ != (rhs.command_) || &array_ != &(rhs.array_) || index_ != rhs.index_ || first_ != rhs.first_; +} + +const RespValue::CompositeArray::CompositeArrayConstIterator& RespValue::CompositeArray::CompositeArrayConstIterator::empty() { + static const RespValue::CompositeArray::CompositeArrayConstIterator* instance = new RespValue::CompositeArray::CompositeArrayConstIterator( + nullptr, {}, 0, false); + return *instance; +} + void DecoderImpl::decode(Buffer::Instance& data) { uint64_t num_slices = data.getRawSlices(nullptr, 0); STACK_ARRAY(slices, Buffer::RawSlice, num_slices); @@ -422,6 +547,10 @@ void EncoderImpl::encode(const RespValue& value, Buffer::Instance& out) { encodeArray(value.asArray(), out); break; } + case RespType::CompositeArray: { + encodeCompositeArray(value.asCompositeArray(), out); + break; + } case RespType::SimpleString: { encodeSimpleString(value.asString(), out); break; @@ -458,6 +587,19 @@ void EncoderImpl::encodeArray(const std::vector& array, Buffer::Insta } } +void EncoderImpl::encodeCompositeArray(const RespValue::CompositeArray& composite_array, Buffer::Instance& out) { + char buffer[32]; + char* current = buffer; + *current++ = '*'; + current += StringUtil::itoa(current, 31, composite_array.size()); + *current++ = '\r'; + *current++ = '\n'; + out.add(buffer, current - buffer); + for (const RespValue& value : composite_array) { + encode(value, out); + } +} + void EncoderImpl::encodeBulkString(const std::string& string, Buffer::Instance& out) { char buffer[32]; char* current = buffer; diff --git a/source/extensions/filters/network/common/redis/codec_impl.h b/source/extensions/filters/network/common/redis/codec_impl.h index 678e537883f3..867be955e963 100644 --- a/source/extensions/filters/network/common/redis/codec_impl.h +++ b/source/extensions/filters/network/common/redis/codec_impl.h @@ -9,12 +9,64 @@ #include "extensions/filters/network/common/redis/codec.h" +#include "absl/types/optional.h" + namespace Envoy { namespace Extensions { namespace NetworkFilters { namespace Common { namespace Redis { +class FragementRespValue : public RespValue { +public: + FragementRespValue(RespValueSharedPtr base, const RespValue& command, const uint64_t start, + const uint64_t end) + : base_(std::move(base)), command_(command), start_(start), end_(end) { + type(RespType::Array); + } + + ~FragementRespValue() override {} + + struct FragementRespValueConstIterator { + FragementRespValueConstIterator(const RespValue& command, const std::vector& array, + uint64_t index) + : command_(command), array_(array), index_(index), first_(true) {} + const RespValue& operator*() { return first_ ? command_ : array_[index_]; } + FragementRespValueConstIterator operator++() { + if (first_) { + first_ = false; + } else { + index_++; + } + return *this; + } + bool operator!=(const FragementRespValueConstIterator& rhs) const { + return &command_ != &(rhs.command_) || &array_ != &rhs.array_ || index_ != rhs.index_; + } + + const RespValue& command_; + const std::vector& array_; + uint64_t index_; + bool first_; + }; + + FragementRespValueConstIterator begin() const noexcept { + return {command_, base_->asArray(), start_}; + } + + FragementRespValueConstIterator end() const noexcept { + return {command_, base_->asArray(), end_}; + } + + uint64_t size() const { return end_ - start_ + 1; } + +private: + const RespValueSharedPtr base_; + const RespValue& command_; + const uint64_t start_; + const uint64_t end_; +}; + /** * Decoder implementation of https://redis.io/topics/protocol * @@ -85,7 +137,8 @@ class EncoderImpl : public Encoder { void encode(const RespValue& value, Buffer::Instance& out) override; private: - void encodeArray(const std::vector& array, Buffer::Instance& out); + void encodeArray(const std::vector& array, Buffer::Instance& out);; + void encodeCompositeArray(const RespValue::CompositeArray& array, Buffer::Instance& out); void encodeBulkString(const std::string& string, Buffer::Instance& out); void encodeError(const std::string& string, Buffer::Instance& out); void encodeInteger(int64_t integer, Buffer::Instance& out); diff --git a/source/extensions/filters/network/common/redis/utility.cc b/source/extensions/filters/network/common/redis/utility.cc index c13b9c4fb4e8..ce31c69c8249 100644 --- a/source/extensions/filters/network/common/redis/utility.cc +++ b/source/extensions/filters/network/common/redis/utility.cc @@ -6,18 +6,49 @@ namespace NetworkFilters { namespace Common { namespace Redis { namespace Utility { - Redis::RespValue makeAuthCommand(const std::string& password) { - Redis::RespValue auth_command, value; + std::vector values(2); + values[0].type(NetworkFilters::Common::Redis::RespType::BulkString); + values[0].asString() = "auth"; + values[1].type(NetworkFilters::Common::Redis::RespType::BulkString); + values[1].asString() = password; + + Redis::RespValue auth_command; auth_command.type(Redis::RespType::Array); - value.type(Redis::RespType::BulkString); - value.asString() = "auth"; - auth_command.asArray().push_back(value); - value.asString() = password; - auth_command.asArray().push_back(value); + auth_command.asArray().swap(values); return auth_command; } +bool redirectionArgsInvalid(const Common::Redis::RespValue* original_request, + const Common::Redis::RespValue& error_response, + std::vector& error_substrings, + bool& ask_redirection) { + if ((original_request == nullptr) || (error_response.type() != Common::Redis::RespType::Error)) { + return true; + } + error_substrings = StringUtil::splitToken(error_response.asString(), " ", false); + if (error_substrings.size() != 3) { + return true; + } + if (error_substrings[0] == "ASK") { + ask_redirection = true; + } else if (error_substrings[0] == "MOVED") { + ask_redirection = false; + } else { + // The first substring must be MOVED or ASK. + return true; + } + // Other validation done later to avoid duplicate processing. + return false; +} + +RespValuePtr makeError(const std::string& error) { + Common::Redis::RespValuePtr response(new RespValue()); + response->type(Common::Redis::RespType::Error); + response->asString() = error; + return response; +} + ReadOnlyRequest::ReadOnlyRequest() { std::vector values(1); values[0].type(NetworkFilters::Common::Redis::RespType::BulkString); @@ -31,6 +62,38 @@ const ReadOnlyRequest& ReadOnlyRequest::instance() { return *instance; } +AskingRequest::AskingRequest() { + std::vector values(1); + values[0].type(NetworkFilters::Common::Redis::RespType::BulkString); + values[0].asString() = "asking"; + type(NetworkFilters::Common::Redis::RespType::Array); + asArray().swap(values); +} + +const AskingRequest& AskingRequest::instance() { + static const AskingRequest* instance = new AskingRequest{}; + return *instance; +} + +GetRequest::GetRequest() { + type(RespType::BulkString); + asString() = "get"; +} + +const GetRequest& GetRequest::instance() { + static const GetRequest* instance = new GetRequest{}; + return *instance; +} + +SetRequest::SetRequest() { + type(RespType::BulkString); + asString() = "set"; +} + +const SetRequest& SetRequest::instance() { + static const SetRequest* instance = new SetRequest{}; + return *instance; +} } // namespace Utility } // namespace Redis } // namespace Common diff --git a/source/extensions/filters/network/common/redis/utility.h b/source/extensions/filters/network/common/redis/utility.h index fc8369acef45..05ab70ad8799 100644 --- a/source/extensions/filters/network/common/redis/utility.h +++ b/source/extensions/filters/network/common/redis/utility.h @@ -2,6 +2,8 @@ #include +#include "common/common/utility.h" + #include "extensions/filters/network/common/redis/codec.h" namespace Envoy { @@ -13,12 +15,47 @@ namespace Utility { Redis::RespValue makeAuthCommand(const std::string& password); +/** + * Validate the received moved/ask redirection error and the original redis request. + * @param[in] original_request supplies the incoming request associated with the command splitter + * request. + * @param[in] error_response supplies the moved/ask redirection response from the upstream Redis + * server. + * @param[out] error_substrings the non-whitespace substrings of error_response. + * @param[out] ask_redirection true if error_response is an ASK redirection error, false otherwise. + * @return bool true if the original_request or error_response are not valid, false otherwise. + */ +bool redirectionArgsInvalid(const Common::Redis::RespValue* original_request, + const Common::Redis::RespValue& error_response, + std::vector& error_substrings, + bool& ask_redirection); + +RespValuePtr makeError(const std::string& error); + class ReadOnlyRequest : public Redis::RespValue { public: ReadOnlyRequest(); static const ReadOnlyRequest& instance(); }; +class AskingRequest : public Redis::RespValue { +public: + AskingRequest(); + static const AskingRequest& instance(); +}; + +class GetRequest : public Redis::RespValue { +public: + GetRequest(); + static const GetRequest& instance(); +}; + +class SetRequest : public Redis::RespValue { +public: + SetRequest(); + static const SetRequest& instance(); +}; + } // namespace Utility } // namespace Redis } // namespace Common diff --git a/source/extensions/filters/network/redis_proxy/BUILD b/source/extensions/filters/network/redis_proxy/BUILD index 7178d82679db..07baa04d281a 100644 --- a/source/extensions/filters/network/redis_proxy/BUILD +++ b/source/extensions/filters/network/redis_proxy/BUILD @@ -56,6 +56,7 @@ envoy_cc_library( hdrs = ["command_splitter_impl.h"], deps = [ ":command_splitter_interface", + ":conn_pool_lib", ":router_interface", "//include/envoy/stats:stats_macros", "//include/envoy/stats:timespan", @@ -65,6 +66,7 @@ envoy_cc_library( "//source/common/common:utility_lib", "//source/extensions/filters/network/common/redis:client_lib", "//source/extensions/filters/network/common/redis:supported_commands_lib", + "//source/extensions/filters/network/common/redis:utility_lib", ], ) @@ -138,7 +140,9 @@ envoy_cc_library( "//include/envoy/thread_local:thread_local_interface", "//include/envoy/upstream:cluster_manager_interface", "//source/common/common:to_lower_table_lib", + "//source/extensions/filters/network/common/redis:codec_lib", "//source/extensions/filters/network/common/redis:supported_commands_lib", + "//source/extensions/filters/network/common/redis:utility_lib", "//source/extensions/filters/network/redis_proxy:conn_pool_lib", "@envoy_api//envoy/config/filter/network/redis_proxy/v2:redis_proxy_cc", ], diff --git a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc index c148825a7909..ac668d409e16 100644 --- a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc +++ b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc @@ -7,68 +7,11 @@ namespace Extensions { namespace NetworkFilters { namespace RedisProxy { namespace CommandSplitter { - -Common::Redis::RespValuePtr Utility::makeError(const std::string& error) { - Common::Redis::RespValuePtr response(new Common::Redis::RespValue()); - response->type(Common::Redis::RespType::Error); - response->asString() = error; - return response; -} - namespace { // null_pool_callbacks is used for requests that must be filtered and not redirected such as // "asking". -Common::Redis::Client::DoNothingPoolCallbacks null_pool_callbacks; - -// Create an asking command request. -const Common::Redis::RespValue& askingRequest() { - static Common::Redis::RespValue request; - static bool initialized = false; - - if (!initialized) { - Common::Redis::RespValue asking_cmd; - asking_cmd.type(Common::Redis::RespType::BulkString); - asking_cmd.asString() = "asking"; - request.type(Common::Redis::RespType::Array); - request.asArray().push_back(asking_cmd); - initialized = true; - } - return request; -} - -/** - * Validate the received moved/ask redirection error and the original redis request. - * @param[in] original_request supplies the incoming request associated with the command splitter - * request. - * @param[in] error_response supplies the moved/ask redirection response from the upstream Redis - * server. - * @param[out] error_substrings the non-whitespace substrings of error_response. - * @param[out] ask_redirection true if error_response is an ASK redirection error, false otherwise. - * @return bool true if the original_request or error_response are not valid, false otherwise. - */ -bool redirectionArgsInvalid(const Common::Redis::RespValue* original_request, - const Common::Redis::RespValue& error_response, - std::vector& error_substrings, - bool& ask_redirection) { - if ((original_request == nullptr) || (error_response.type() != Common::Redis::RespType::Error)) { - return true; - } - error_substrings = StringUtil::splitToken(error_response.asString(), " ", false); - if (error_substrings.size() != 3) { - return true; - } - if (error_substrings[0] == "ASK") { - ask_redirection = true; - } else if (error_substrings[0] == "MOVED") { - ask_redirection = false; - } else { - // The first substring must be MOVED or ASK. - return true; - } - // Other validation done later to avoid duplicate processing. - return false; -} +ConnPool::DoNothingPoolCallbacks null_pool_callbacks; /** * Make request and maybe mirror the request based on the mirror policies of the route. @@ -82,8 +25,8 @@ bool redirectionArgsInvalid(const Common::Redis::RespValue* original_request, */ Common::Redis::Client::PoolRequest* makeRequest(const RouteSharedPtr& route, const std::string& command, const std::string& key, - const Common::Redis::RespValue& incoming_request, - Common::Redis::Client::PoolCallbacks& callbacks) { + Common::Redis::RespValueSharedPtr incoming_request, + ConnPool::PoolCallbacks& callbacks) { auto handler = route->upstream()->makeRequest(key, incoming_request, callbacks); if (handler) { for (auto& mirror_policy : route->mirrorPolicies()) { @@ -98,7 +41,7 @@ Common::Redis::Client::PoolRequest* makeRequest(const RouteSharedPtr& route, void SplitRequestBase::onWrongNumberOfArguments(SplitCallbacks& callbacks, const Common::Redis::RespValue& request) { - callbacks.onResponse(Utility::makeError( + callbacks.onResponse(Common::Redis::Utility::makeError( fmt::format("wrong number of arguments for '{}' command", request.asArray()[0].asString()))); } @@ -122,31 +65,7 @@ void SingleServerRequest::onResponse(Common::Redis::RespValuePtr&& response) { void SingleServerRequest::onFailure() { handle_ = nullptr; updateStats(false); - callbacks_.onResponse(Utility::makeError(Response::get().UpstreamFailure)); -} - -bool SingleServerRequest::onRedirection(const Common::Redis::RespValue& value) { - std::vector err; - bool ask_redirection = false; - if (redirectionArgsInvalid(incoming_request_.get(), value, err, ask_redirection) || !conn_pool_) { - return false; - } - - // MOVED and ASK redirection errors have the following substrings: MOVED or ASK (err[0]), hash key - // slot (err[1]), and IP address and TCP port separated by a colon (err[2]). - const std::string host_address = std::string(err[2]); - - // Prepend request with an asking command if redirected via an ASK error. The returned handle is - // not important since there is no point in being able to cancel the request. The use of - // null_pool_callbacks ensures the transparent filtering of the Redis server's response to the - // "asking" command; this is fine since the server either responds with an OK or an error message - // if cluster support is not enabled (in which case we should not get an ASK redirection error). - if (ask_redirection && - !conn_pool_->makeRequestToHost(host_address, askingRequest(), null_pool_callbacks)) { - return false; - } - handle_ = conn_pool_->makeRequestToHost(host_address, *incoming_request_, *this); - return (handle_ != nullptr); + callbacks_.onResponse(Common::Redis::Utility::makeError(Response::get().UpstreamFailure)); } void SingleServerRequest::cancel() { @@ -163,18 +82,16 @@ SplitRequestPtr SimpleRequest::create(Router& router, const auto route = router.upstreamPool(incoming_request->asArray()[1].asString()); if (route) { - request_ptr->conn_pool_ = route->upstream(); - request_ptr->handle_ = - makeRequest(route, incoming_request->asArray()[0].asString(), - incoming_request->asArray()[1].asString(), *incoming_request, *request_ptr); + request_ptr->handle_ = makeRequest(route, incoming_request->asArray()[0].asString(), + incoming_request->asArray()[1].asString(), + std::move(incoming_request), *request_ptr); } if (!request_ptr->handle_) { - callbacks.onResponse(Utility::makeError(Response::get().NoUpstreamHost)); + callbacks.onResponse(Common::Redis::Utility::makeError(Response::get().NoUpstreamHost)); return nullptr; } - request_ptr->incoming_request_ = std::move(incoming_request); return request_ptr; } @@ -194,19 +111,17 @@ SplitRequestPtr EvalRequest::create(Router& router, Common::Redis::RespValuePtr& const auto route = router.upstreamPool(incoming_request->asArray()[3].asString()); if (route) { - request_ptr->conn_pool_ = route->upstream(); - request_ptr->handle_ = - makeRequest(route, incoming_request->asArray()[0].asString(), - incoming_request->asArray()[3].asString(), *incoming_request, *request_ptr); + request_ptr->handle_ = makeRequest(route, incoming_request->asArray()[0].asString(), + incoming_request->asArray()[3].asString(), + std::move(incoming_request), *request_ptr); } if (!request_ptr->handle_) { command_stats.error_.inc(); - callbacks.onResponse(Utility::makeError(Response::get().NoUpstreamHost)); + callbacks.onResponse(Common::Redis::Utility::makeError(Response::get().NoUpstreamHost)); return nullptr; } - request_ptr->incoming_request_ = std::move(incoming_request); return request_ptr; } @@ -228,7 +143,7 @@ void FragmentedRequest::cancel() { } void FragmentedRequest::onChildFailure(uint32_t index) { - onChildResponse(Utility::makeError(Response::get().UpstreamFailure), index); + onChildResponse(Common::Redis::Utility::makeError(Response::get().UpstreamFailure), index); } SplitRequestPtr MGETRequest::create(Router& router, Common::Redis::RespValuePtr&& incoming_request, @@ -245,69 +160,31 @@ SplitRequestPtr MGETRequest::create(Router& router, Common::Redis::RespValuePtr& std::vector responses(request_ptr->num_pending_responses_); request_ptr->pending_response_->asArray().swap(responses); - std::vector values(2); - values[0].type(Common::Redis::RespType::BulkString); - values[0].asString() = "get"; - values[1].type(Common::Redis::RespType::BulkString); - Common::Redis::RespValue single_mget; - single_mget.type(Common::Redis::RespType::Array); - single_mget.asArray().swap(values); - - for (uint64_t i = 1; i < incoming_request->asArray().size(); i++) { + Common::Redis::RespValueSharedPtr base_request = std::move(incoming_request); + for (uint64_t i = 1; i < base_request->asArray().size(); i++) { request_ptr->pending_requests_.emplace_back(*request_ptr, i - 1); PendingRequest& pending_request = request_ptr->pending_requests_.back(); - single_mget.asArray()[1].asString() = incoming_request->asArray()[i].asString(); - ENVOY_LOG(debug, "redis: parallel get: '{}'", single_mget.toString()); - const auto route = router.upstreamPool(incoming_request->asArray()[i].asString()); + const auto route = router.upstreamPool(base_request->asArray()[i].asString()); if (route) { - pending_request.conn_pool_ = route->upstream(); - pending_request.handle_ = makeRequest(route, "get", incoming_request->asArray()[i].asString(), - single_mget, pending_request); + auto single_get = std::make_shared( + base_request, Common::Redis::Utility::GetRequest::instance(), i, i); + pending_request.handle_ = makeRequest(route, "get", base_request->asArray()[i].asString(), + single_get, pending_request); } if (!pending_request.handle_) { - pending_request.onResponse(Utility::makeError(Response::get().NoUpstreamHost)); + pending_request.onResponse(Common::Redis::Utility::makeError(Response::get().NoUpstreamHost)); } } if (request_ptr->num_pending_responses_ > 0) { - request_ptr->incoming_request_ = std::move(incoming_request); return request_ptr; } return nullptr; } -bool FragmentedRequest::onChildRedirection(const Common::Redis::RespValue& value, uint32_t index, - const ConnPool::InstanceSharedPtr& conn_pool) { - std::vector err; - bool ask_redirection = false; - if (redirectionArgsInvalid(incoming_request_.get(), value, err, ask_redirection) || !conn_pool) { - return false; - } - - // MOVED and ASK redirection errors have the following substrings: MOVED or ASK (err[0]), hash key - // slot (err[1]), and IP address and TCP port separated by a colon (err[2]). - std::string host_address = std::string(err[2]); - Common::Redis::RespValue request; - recreate(request, index); - - // Prepend request with an asking command if redirected via an ASK error. The returned handle is - // not important since there is no point in being able to cancel the request. The use of - // null_pool_callbacks ensures the transparent filtering of the Redis server's response to the - // "asking" command; this is fine since the server either responds with an OK or an error message - // if cluster support is not enabled (in which case we should not get an ASK redirection error). - if (ask_redirection && - !conn_pool->makeRequestToHost(host_address, askingRequest(), null_pool_callbacks)) { - return false; - } - - this->pending_requests_[index].handle_ = - conn_pool->makeRequestToHost(host_address, request, this->pending_requests_[index]); - return (this->pending_requests_[index].handle_ != nullptr); -} - void MGETRequest::onChildResponse(Common::Redis::RespValuePtr&& value, uint32_t index) { pending_requests_[index].handle_ = nullptr; @@ -315,7 +192,8 @@ void MGETRequest::onChildResponse(Common::Redis::RespValuePtr&& value, uint32_t switch (value->type()) { case Common::Redis::RespType::Array: case Common::Redis::RespType::Integer: - case Common::Redis::RespType::SimpleString: { + case Common::Redis::RespType::SimpleString: + case Common::Redis::RespType::CompositeArray: { pending_response_->asArray()[index].type(Common::Redis::RespType::Error); pending_response_->asArray()[index].asString() = Response::get().UpstreamProtocolError; error_count_++; @@ -341,21 +219,6 @@ void MGETRequest::onChildResponse(Common::Redis::RespValuePtr&& value, uint32_t } } -void MGETRequest::recreate(Common::Redis::RespValue& request, uint32_t index) { - static const uint32_t GET_COMMAND_SUBSTRINGS = 2; - uint32_t num_values = GET_COMMAND_SUBSTRINGS; - std::vector values(num_values); - - for (uint32_t i = 0; i < num_values; i++) { - values[i].type(Common::Redis::RespType::BulkString); - } - values[--num_values].asString() = incoming_request_->asArray()[index + 1].asString(); - values[--num_values].asString() = "get"; - - request.type(Common::Redis::RespType::Array); - request.asArray().swap(values); -} - SplitRequestPtr MSETRequest::create(Router& router, Common::Redis::RespValuePtr&& incoming_request, SplitCallbacks& callbacks, CommandStats& command_stats, TimeSource& time_source, bool latency_in_micros) { @@ -373,38 +236,27 @@ SplitRequestPtr MSETRequest::create(Router& router, Common::Redis::RespValuePtr& request_ptr->pending_response_ = std::make_unique(); request_ptr->pending_response_->type(Common::Redis::RespType::SimpleString); - std::vector values(3); - values[0].type(Common::Redis::RespType::BulkString); - values[0].asString() = "set"; - values[1].type(Common::Redis::RespType::BulkString); - values[2].type(Common::Redis::RespType::BulkString); - Common::Redis::RespValue single_mset; - single_mset.type(Common::Redis::RespType::Array); - single_mset.asArray().swap(values); - + Common::Redis::RespValueSharedPtr base_request = std::move(incoming_request); uint64_t fragment_index = 0; - for (uint64_t i = 1; i < incoming_request->asArray().size(); i += 2) { + for (uint64_t i = 1; i < base_request->asArray().size(); i += 2) { request_ptr->pending_requests_.emplace_back(*request_ptr, fragment_index++); PendingRequest& pending_request = request_ptr->pending_requests_.back(); - single_mset.asArray()[1].asString() = incoming_request->asArray()[i].asString(); - single_mset.asArray()[2].asString() = incoming_request->asArray()[i + 1].asString(); - - ENVOY_LOG(debug, "redis: parallel set: '{}'", single_mset.toString()); - const auto route = router.upstreamPool(incoming_request->asArray()[i].asString()); + // ENVOY_LOG(debug, "redis: parallel set: '{}'", single_set->toString()); + const auto route = router.upstreamPool(base_request->asArray()[i].asString()); if (route) { - pending_request.conn_pool_ = route->upstream(); - pending_request.handle_ = makeRequest(route, "set", incoming_request->asArray()[i].asString(), - single_mset, pending_request); + auto single_set = std::make_shared( + base_request, Common::Redis::Utility::SetRequest::instance(), i, i + 1); + pending_request.handle_ = makeRequest(route, "set", base_request->asArray()[i].asString(), + std::move(single_set), pending_request); } if (!pending_request.handle_) { - pending_request.onResponse(Utility::makeError(Response::get().NoUpstreamHost)); + pending_request.onResponse(Common::Redis::Utility::makeError(Response::get().NoUpstreamHost)); } } if (request_ptr->num_pending_responses_ > 0) { - request_ptr->incoming_request_ = std::move(incoming_request); return request_ptr; } @@ -434,28 +286,12 @@ void MSETRequest::onChildResponse(Common::Redis::RespValuePtr&& value, uint32_t pending_response_->asString() = Response::get().OK; callbacks_.onResponse(std::move(pending_response_)); } else { - callbacks_.onResponse( - Utility::makeError(fmt::format("finished with {} error(s)", error_count_))); + callbacks_.onResponse(Common::Redis::Utility::makeError( + fmt::format("finished with {} error(s)", error_count_))); } } } -void MSETRequest::recreate(Common::Redis::RespValue& request, uint32_t index) { - static const uint32_t SET_COMMAND_SUBSTRINGS = 3; - uint32_t num_values = SET_COMMAND_SUBSTRINGS; - std::vector values(num_values); - - for (uint32_t i = 0; i < num_values; i++) { - values[i].type(Common::Redis::RespType::BulkString); - } - values[--num_values].asString() = incoming_request_->asArray()[(index * 2) + 2].asString(); - values[--num_values].asString() = incoming_request_->asArray()[(index * 2) + 1].asString(); - values[--num_values].asString() = "set"; - - request.type(Common::Redis::RespType::Array); - request.asArray().swap(values); -} - SplitRequestPtr SplitKeysSumResultRequest::create(Router& router, Common::Redis::RespValuePtr&& incoming_request, SplitCallbacks& callbacks, @@ -470,36 +306,28 @@ SplitRequestPtr SplitKeysSumResultRequest::create(Router& router, request_ptr->pending_response_ = std::make_unique(); request_ptr->pending_response_->type(Common::Redis::RespType::Integer); - std::vector values(2); - values[0].type(Common::Redis::RespType::BulkString); - values[0].asString() = incoming_request->asArray()[0].asString(); - values[1].type(Common::Redis::RespType::BulkString); - Common::Redis::RespValue single_fragment; - single_fragment.type(Common::Redis::RespType::Array); - single_fragment.asArray().swap(values); - - for (uint64_t i = 1; i < incoming_request->asArray().size(); i++) { + Common::Redis::RespValueSharedPtr base_request = std::move(incoming_request); + for (uint64_t i = 1; i < base_request->asArray().size(); i++) { request_ptr->pending_requests_.emplace_back(*request_ptr, i - 1); PendingRequest& pending_request = request_ptr->pending_requests_.back(); - single_fragment.asArray()[1].asString() = incoming_request->asArray()[i].asString(); - ENVOY_LOG(debug, "redis: parallel {}: '{}'", incoming_request->asArray()[0].asString(), - single_fragment.toString()); - const auto route = router.upstreamPool(incoming_request->asArray()[i].asString()); + auto single_fragment = std::make_shared( + base_request, base_request->asArray()[0], i, i); + ENVOY_LOG(debug, "redis: parallel {}: '{}'", base_request->asArray()[0].asString(), + single_fragment->toString()); + const auto route = router.upstreamPool(base_request->asArray()[i].asString()); if (route) { - pending_request.conn_pool_ = route->upstream(); pending_request.handle_ = - makeRequest(route, single_fragment.asArray()[0].asString(), - incoming_request->asArray()[i].asString(), single_fragment, pending_request); + makeRequest(route, base_request->asArray()[0].asString(), + base_request->asArray()[i].asString(), single_fragment, pending_request); } if (!pending_request.handle_) { - pending_request.onResponse(Utility::makeError(Response::get().NoUpstreamHost)); + pending_request.onResponse(Common::Redis::Utility::makeError(Response::get().NoUpstreamHost)); } } if (request_ptr->num_pending_responses_ > 0) { - request_ptr->incoming_request_ = std::move(incoming_request); return request_ptr; } @@ -528,27 +356,12 @@ void SplitKeysSumResultRequest::onChildResponse(Common::Redis::RespValuePtr&& va pending_response_->asInteger() = total_; callbacks_.onResponse(std::move(pending_response_)); } else { - callbacks_.onResponse( - Utility::makeError(fmt::format("finished with {} error(s)", error_count_))); + callbacks_.onResponse(Common::Redis::Utility::makeError( + fmt::format("finished with {} error(s)", error_count_))); } } } -void SplitKeysSumResultRequest::recreate(Common::Redis::RespValue& request, uint32_t index) { - static const uint32_t BASE_COMMAND_SUBSTRINGS = 2; - uint32_t num_values = BASE_COMMAND_SUBSTRINGS; - std::vector values(num_values); - - for (uint32_t i = 0; i < num_values; i++) { - values[i].type(Common::Redis::RespType::BulkString); - } - values[--num_values].asString() = incoming_request_->asArray()[index + 1].asString(); - values[--num_values].asString() = incoming_request_->asArray()[0].asString(); - - request.type(Common::Redis::RespType::Array); - request.asArray().swap(values); -} - InstanceImpl::InstanceImpl(RouterPtr&& router, Stats::Scope& scope, const std::string& stat_prefix, TimeSource& time_source, bool latency_in_micros) : router_(std::move(router)), simple_command_handler_(*router_), @@ -600,7 +413,7 @@ SplitRequestPtr InstanceImpl::makeRequest(Common::Redis::RespValuePtr&& request, } if (!callbacks.connectionAllowed()) { - callbacks.onResponse(Utility::makeError(Response::get().AuthRequiredError)); + callbacks.onResponse(Common::Redis::Utility::makeError(Response::get().AuthRequiredError)); return nullptr; } @@ -622,7 +435,7 @@ SplitRequestPtr InstanceImpl::makeRequest(Common::Redis::RespValuePtr&& request, auto handler = handler_lookup_table_.find(to_lower_string.c_str()); if (handler == nullptr) { stats_.unsupported_command_.inc(); - callbacks.onResponse(Utility::makeError( + callbacks.onResponse(Common::Redis::Utility::makeError( fmt::format("unsupported command '{}'", request->asArray()[0].asString()))); return nullptr; } @@ -635,7 +448,7 @@ SplitRequestPtr InstanceImpl::makeRequest(Common::Redis::RespValuePtr&& request, void InstanceImpl::onInvalidRequest(SplitCallbacks& callbacks) { stats_.invalid_request_.inc(); - callbacks.onResponse(Utility::makeError(Response::get().InvalidRequest)); + callbacks.onResponse(Common::Redis::Utility::makeError(Response::get().InvalidRequest)); } void InstanceImpl::addHandler(Stats::Scope& scope, const std::string& stat_prefix, diff --git a/source/extensions/filters/network/redis_proxy/command_splitter_impl.h b/source/extensions/filters/network/redis_proxy/command_splitter_impl.h index f4ee4c6f79d4..2ee25004ef1e 100644 --- a/source/extensions/filters/network/redis_proxy/command_splitter_impl.h +++ b/source/extensions/filters/network/redis_proxy/command_splitter_impl.h @@ -15,8 +15,9 @@ #include "common/singleton/const_singleton.h" #include "extensions/filters/network/common/redis/client_impl.h" +#include "extensions/filters/network/common/redis/utility.h" #include "extensions/filters/network/redis_proxy/command_splitter.h" -#include "extensions/filters/network/redis_proxy/conn_pool.h" +#include "extensions/filters/network/redis_proxy/conn_pool_impl.h" #include "extensions/filters/network/redis_proxy/router.h" namespace Envoy { @@ -36,11 +37,6 @@ struct ResponseValues { using Response = ConstSingleton; -class Utility { -public: - static Common::Redis::RespValuePtr makeError(const std::string& error); -}; - /** * All command level stats. @see stats_macros.h */ @@ -98,14 +94,13 @@ class SplitRequestBase : public SplitRequest { /** * SingleServerRequest is a base class for commands that hash to a single backend. */ -class SingleServerRequest : public SplitRequestBase, public Common::Redis::Client::PoolCallbacks { +class SingleServerRequest : public SplitRequestBase, public ConnPool::PoolCallbacks { public: ~SingleServerRequest() override; - // Common::Redis::Client::PoolCallbacks + // ConnPool::PoolCallbacks void onResponse(Common::Redis::RespValuePtr&& response) override; void onFailure() override; - bool onRedirection(const Common::Redis::RespValue& value) override; // RedisProxy::CommandSplitter::SplitRequest void cancel() override; @@ -168,34 +163,29 @@ class FragmentedRequest : public SplitRequestBase { bool latency_in_micros) : SplitRequestBase(command_stats, time_source, latency_in_micros), callbacks_(callbacks) {} - struct PendingRequest : public Common::Redis::Client::PoolCallbacks { + struct PendingRequest : public ConnPool::PoolCallbacks { PendingRequest(FragmentedRequest& parent, uint32_t index) : parent_(parent), index_(index) {} - // Common::Redis::Client::PoolCallbacks + // ConnPool::PoolCallbacks void onResponse(Common::Redis::RespValuePtr&& value) override { parent_.onChildResponse(std::move(value), index_); } void onFailure() override { parent_.onChildFailure(index_); } - bool onRedirection(const Common::Redis::RespValue& value) override { - return parent_.onChildRedirection(value, index_, conn_pool_); - } - FragmentedRequest& parent_; const uint32_t index_; Common::Redis::Client::PoolRequest* handle_{}; - ConnPool::InstanceSharedPtr conn_pool_; }; virtual void onChildResponse(Common::Redis::RespValuePtr&& value, uint32_t index) PURE; void onChildFailure(uint32_t index); - bool onChildRedirection(const Common::Redis::RespValue& value, uint32_t index, - const ConnPool::InstanceSharedPtr& conn_pool); - virtual void recreate(Common::Redis::RespValue& request, uint32_t index) PURE; + // bool onChildRedirection(const Common::Redis::RespValue& value, uint32_t index, + // const ConnPool::InstanceSharedPtr& conn_pool); + // virtual void recreate(Common::Redis::RespValue& request, uint32_t index) PURE; SplitCallbacks& callbacks_; - Common::Redis::RespValuePtr incoming_request_; + // Common::Redis::RespValuePtr incoming_request_; Common::Redis::RespValuePtr pending_response_; std::vector pending_requests_; uint32_t num_pending_responses_; @@ -219,7 +209,7 @@ class MGETRequest : public FragmentedRequest, Logger::Loggable; diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc index 7304ef902325..70fed658cc54 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc @@ -15,6 +15,14 @@ namespace Extensions { namespace NetworkFilters { namespace RedisProxy { namespace ConnPool { +namespace { +// null_pool_callbacks is used for requests that must be filtered and not redirected such as +// "asking". +DoNothingPoolCallbacks null_pool_callbacks; +// null_pool_callbacks is used for requests that must be filtered and not redirected such as +// "asking". +Common::Redis::Client::DoNothingPoolCallbacks null_client_callbacks; +} // namespace InstanceImpl::InstanceImpl( const std::string& cluster_name, Upstream::ClusterManager& cm, @@ -33,15 +41,15 @@ InstanceImpl::InstanceImpl( } Common::Redis::Client::PoolRequest* -InstanceImpl::makeRequest(const std::string& key, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks& callbacks) { +InstanceImpl::makeRequest(const std::string& key, Common::Redis::RespValueSharedPtr request, + PoolCallbacks& callbacks) { return tls_->getTyped().makeRequest(key, request, callbacks); } Common::Redis::Client::PoolRequest* InstanceImpl::makeRequestToHost(const std::string& host_address, - const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks& callbacks) { + Common::Redis::RespValueSharedPtr request, + PoolCallbacks& callbacks) { return tls_->getTyped().makeRequestToHost(host_address, request, callbacks); } @@ -62,6 +70,9 @@ InstanceImpl::ThreadLocalPool::~ThreadLocalPool() { if (host_set_member_update_cb_handle_ != nullptr) { host_set_member_update_cb_handle_->remove(); } + while (!pending_requests_.empty()) { + pending_requests_.pop_front(); + } while (!client_map_.empty()) { client_map_.begin()->second->redis_client_->close(); } @@ -199,9 +210,38 @@ InstanceImpl::ThreadLocalPool::threadLocalActiveClient(Upstream::HostConstShared } Common::Redis::Client::PoolRequest* -InstanceImpl::ThreadLocalPool::makeRequest(const std::string& key, - const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks& callbacks) { +InstanceImpl::ThreadLocalPool::makeRequestInternal(const Upstream::HostConstSharedPtr& host, + Common::Redis::RespValueSharedPtr request, + PoolCallbacks& callbacks) { + pending_requests_.emplace_back(*this, request, callbacks); + PendingRequest& pending_request = pending_requests_.back(); + ThreadLocalActiveClientPtr& client = this->threadLocalActiveClient(host); + pending_request.request_handler_ = + client->redis_client_->makeRequest(*(pending_request.incoming_request_), pending_request); + if (pending_request.request_handler_) { + return &pending_request; + } else { + onRequestCompleted(); + return nullptr; + } +} + +Common::Redis::Client::PoolRequest* +InstanceImpl::ThreadLocalPool::makeRequestInternal(const Upstream::HostConstSharedPtr& host, + PendingRequest& pending_request) { + ThreadLocalActiveClientPtr& client = this->threadLocalActiveClient(host); + pending_request.request_handler_ = + client->redis_client_->makeRequest(*(pending_request.incoming_request_), pending_request); + if (pending_request.request_handler_) { + return &pending_request; + } else { + onRequestCompleted(); + return nullptr; + } +} + +Common::Redis::Client::PoolRequest* InstanceImpl::ThreadLocalPool::makeRequest( + const std::string& key, Common::Redis::RespValueSharedPtr request, PoolCallbacks& callbacks) { if (cluster_ == nullptr) { ASSERT(client_map_.empty()); ASSERT(host_set_member_update_cb_handle_ == nullptr); @@ -210,21 +250,16 @@ InstanceImpl::ThreadLocalPool::makeRequest(const std::string& key, const bool use_crc16 = is_redis_cluster_; Clusters::Redis::RedisLoadBalancerContextImpl lb_context( - key, parent_.config_.enableHashtagging(), use_crc16, request, parent_.config_.readPolicy()); + key, parent_.config_.enableHashtagging(), use_crc16, *request, parent_.config_.readPolicy()); Upstream::HostConstSharedPtr host = cluster_->loadBalancer().chooseHost(&lb_context); if (!host) { return nullptr; } - - ThreadLocalActiveClientPtr& client = threadLocalActiveClient(host); - - return client->redis_client_->makeRequest(request, callbacks); + return makeRequestInternal(host, request, callbacks); } -Common::Redis::Client::PoolRequest* -InstanceImpl::ThreadLocalPool::makeRequestToHost(const std::string& host_address, - const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks& callbacks) { +Upstream::HostConstSharedPtr +InstanceImpl::ThreadLocalPool::getHost(const std::string& host_address) { if (cluster_ == nullptr) { ASSERT(client_map_.empty()); ASSERT(host_set_member_update_cb_handle_ == nullptr); @@ -288,11 +323,52 @@ InstanceImpl::ThreadLocalPool::makeRequestToHost(const std::string& host_address it = host_address_map_.find(host_address_map_key); } - ThreadLocalActiveClientPtr& client = threadLocalActiveClient(it->second); + return it->second; +} +Common::Redis::Client::PoolRequest* +InstanceImpl::ThreadLocalPool::makeRequestToHost(const std::string& host_address, + Common::Redis::RespValueSharedPtr request, + PoolCallbacks& callbacks) { + const Upstream::HostConstSharedPtr host = getHost(host_address); + if (!host) { + return nullptr; + } + return makeRequestInternal(host, request, callbacks); +} + +Common::Redis::Client::PoolRequest* +InstanceImpl::ThreadLocalPool::makeRequestToHostInternal(const std::string& host_address, + PendingRequest& pending_request) { + const Upstream::HostConstSharedPtr host = getHost(host_address); + if (!host) { + return nullptr; + } + ThreadLocalActiveClientPtr& client = this->threadLocalActiveClient(host); + return client->redis_client_->makeRequest(*(pending_request.incoming_request_), pending_request); +} + +Common::Redis::Client::PoolRequest* InstanceImpl::ThreadLocalPool::makeRequestToHostInternal( + const std::string& host_address, const Common::Redis::RespValue& request, + Common::Redis::Client::ClientCallbacks& callbacks) { + const Upstream::HostConstSharedPtr host = getHost(host_address); + if (!host) { + return nullptr; + } + ThreadLocalActiveClientPtr& client = this->threadLocalActiveClient(host); return client->redis_client_->makeRequest(request, callbacks); } +void InstanceImpl::ThreadLocalPool::onRequestCompleted() { + ASSERT(!pending_requests_.empty()); + + // The response we got might not be in order, so flush out what we can. (A new response may + // unlock several out of order responses). + while (!pending_requests_.empty() && !pending_requests_.front().request_handler_) { + pending_requests_.pop_front(); + } +} + void InstanceImpl::ThreadLocalActiveClient::onEvent(Network::ConnectionEvent event) { if (event == Network::ConnectionEvent::RemoteClose || event == Network::ConnectionEvent::LocalClose) { @@ -316,6 +392,70 @@ void InstanceImpl::ThreadLocalActiveClient::onEvent(Network::ConnectionEvent eve } } +InstanceImpl::PendingRequest::PendingRequest(InstanceImpl::ThreadLocalPool& parent, + Common::Redis::RespValueSharedPtr incoming_request, + PoolCallbacks& pool_callbacks) + : parent_(parent), incoming_request_(incoming_request), pool_callbacks_(pool_callbacks) {} + +InstanceImpl::PendingRequest::~PendingRequest() { + if (request_handler_) { + request_handler_->cancel(); + request_handler_ = nullptr; + // If we have to cancel the request on the client, then we'll treat this as failure for pool + // callback + pool_callbacks_.onFailure(); + } +} + +void InstanceImpl::PendingRequest::onResponse(Common::Redis::RespValuePtr&& response) { + request_handler_ = nullptr; + pool_callbacks_.onResponse(std::move(response)); + parent_.onRequestCompleted(); +} + +void InstanceImpl::PendingRequest::onFailure() { + request_handler_ = nullptr; + pool_callbacks_.onFailure(); + parent_.onRequestCompleted(); +} + +bool InstanceImpl::PendingRequest::onRedirection(Common::Redis::RespValuePtr&& value) { + std::vector err; + bool ask_redirection = false; + if (Common::Redis::Utility::redirectionArgsInvalid(incoming_request_.get(), *value, err, + ask_redirection)) { + onResponse(std::move(value)); + return false; + } + + // MOVED and ASK redirection errors have the following substrings: MOVED or ASK (err[0]), hash key + // slot (err[1]), and IP address and TCP port separated by a colon (err[2]). + const std::string host_address = std::string(err[2]); + + // Prepend request with an asking command if redirected via an ASK error. The returned handle is + // not important since there is no point in being able to cancel the request. The use of + // null_pool_callbacks ensures the transparent filtering of the Redis server's response to the + // "asking" command; this is fine since the server either responds with an OK or an error message + // if cluster support is not enabled (in which case we should not get an ASK redirection error). + if (ask_redirection && + !parent_.makeRequestToHostInternal( + host_address, Common::Redis::Utility::AskingRequest::instance(), null_client_callbacks)) { + onResponse(std::move(value)); + return false; + } + request_handler_ = parent_.makeRequestToHostInternal(host_address, *this); + if (!request_handler_) { + onResponse(std::move(value)); + } + return (request_handler_ != nullptr); +} + +void InstanceImpl::PendingRequest::cancel() { + request_handler_->cancel(); + request_handler_ = nullptr; + parent_.onRequestCompleted(); +} + } // namespace ConnPool } // namespace RedisProxy } // namespace NetworkFilters diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h index 7731943b873f..8b931f185904 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h @@ -45,6 +45,12 @@ struct RedisClusterStats { REDIS_CLUSTER_STATS(GENERATE_COUNTER_STRUCT) }; +class DoNothingPoolCallbacks : public PoolCallbacks { +public: + void onResponse(Common::Redis::RespValuePtr&&) override{}; + void onFailure() override{}; +}; + class InstanceImpl : public Instance { public: InstanceImpl( @@ -54,12 +60,12 @@ class InstanceImpl : public Instance { Api::Api& api, Stats::ScopePtr&& stats_scope, const Common::Redis::RedisCommandStatsSharedPtr& redis_command_stats); // RedisProxy::ConnPool::Instance - Common::Redis::Client::PoolRequest* - makeRequest(const std::string& key, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks& callbacks) override; - Common::Redis::Client::PoolRequest* - makeRequestToHost(const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks& callbacks) override; + Common::Redis::Client::PoolRequest* makeRequest(const std::string& key, + Common::Redis::RespValueSharedPtr request, + PoolCallbacks& callbacks) override; + Common::Redis::Client::PoolRequest* makeRequestToHost(const std::string& host_address, + Common::Redis::RespValueSharedPtr request, + PoolCallbacks& callbacks) override; // Allow the unit test to have access to private members. friend class RedisConnPoolImplTest; @@ -82,17 +88,49 @@ class InstanceImpl : public Instance { using ThreadLocalActiveClientPtr = std::unique_ptr; + struct PendingRequest : public Common::Redis::Client::ClientCallbacks, + public Common::Redis::Client::PoolRequest { + PendingRequest(ThreadLocalPool& parent, Common::Redis::RespValueSharedPtr incoming_request, + PoolCallbacks& pool_callbacks); + ~PendingRequest(); + + // Common::Redis::Client::ClientCallbacks + void onResponse(Common::Redis::RespValuePtr&& response) override; + void onFailure() override; + bool onRedirection(Common::Redis::RespValuePtr&& value) override; + + // PoolRequest + void cancel() override; + + ThreadLocalPool& parent_; + Common::Redis::RespValueSharedPtr incoming_request_; + Common::Redis::Client::PoolRequest* request_handler_; + PoolCallbacks& pool_callbacks_; + }; + struct ThreadLocalPool : public ThreadLocal::ThreadLocalObject, public Upstream::ClusterUpdateCallbacks { ThreadLocalPool(InstanceImpl& parent, Event::Dispatcher& dispatcher, std::string cluster_name); ~ThreadLocalPool() override; ThreadLocalActiveClientPtr& threadLocalActiveClient(Upstream::HostConstSharedPtr host); + Common::Redis::Client::PoolRequest* makeRequest(const std::string& key, + Common::Redis::RespValueSharedPtr request, + PoolCallbacks& callbacks); + Common::Redis::Client::PoolRequest* makeRequestToHost(const std::string& host_address, + Common::Redis::RespValueSharedPtr request, + PoolCallbacks& callbacks); + Common::Redis::Client::PoolRequest* makeRequestToHostInternal(const std::string& host_address, + PendingRequest& pending_request); Common::Redis::Client::PoolRequest* - makeRequest(const std::string& key, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks& callbacks); + makeRequestInternal(const Upstream::HostConstSharedPtr& host, + Common::Redis::RespValueSharedPtr request, PoolCallbacks& callbacks); Common::Redis::Client::PoolRequest* - makeRequestToHost(const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks& callbacks); + makeRequestInternal(const Upstream::HostConstSharedPtr& host, PendingRequest& pending_request); + Common::Redis::Client::PoolRequest* + makeRequestToHostInternal(const std::string& host_address, + const Common::Redis::RespValue& request, + Common::Redis::Client::ClientCallbacks& callbacks); + Upstream::HostConstSharedPtr getHost(const std::string& host_address); void onClusterAddOrUpdateNonVirtual(Upstream::ThreadLocalCluster& cluster); void onHostsAdded(const std::vector& hosts_added); void onHostsRemoved(const std::vector& hosts_removed); @@ -104,6 +142,8 @@ class InstanceImpl : public Instance { } void onClusterRemoval(const std::string& cluster_name) override; + void onRequestCompleted(); + InstanceImpl& parent_; Event::Dispatcher& dispatcher_; const std::string cluster_name_; @@ -115,6 +155,7 @@ class InstanceImpl : public Instance { std::string auth_password_; std::list created_via_redirect_hosts_; std::list clients_to_drain_; + std::list pending_requests_; /* This timer is used to poll the active clients in clients_to_drain_ to determine whether they * have been drained (have no active requests) or not. It is only enabled after a client has diff --git a/source/extensions/health_checkers/redis/redis.cc b/source/extensions/health_checkers/redis/redis.cc index 22a7bd9be07f..4c953be6391a 100644 --- a/source/extensions/health_checkers/redis/redis.cc +++ b/source/extensions/health_checkers/redis/redis.cc @@ -113,7 +113,7 @@ void RedisHealthChecker::RedisActiveHealthCheckSession::onFailure() { } bool RedisHealthChecker::RedisActiveHealthCheckSession::onRedirection( - const NetworkFilters::Common::Redis::RespValue&) { + NetworkFilters::Common::Redis::RespValuePtr&&) { // Treat any redirection error response from a Redis server as success. current_request_ = nullptr; handleSuccess(); diff --git a/source/extensions/health_checkers/redis/redis.h b/source/extensions/health_checkers/redis/redis.h index 5268e6e8e404..8b11d3df6fb8 100644 --- a/source/extensions/health_checkers/redis/redis.h +++ b/source/extensions/health_checkers/redis/redis.h @@ -50,7 +50,7 @@ class RedisHealthChecker : public Upstream::HealthCheckerImplBase { struct RedisActiveHealthCheckSession : public ActiveHealthCheckSession, public Extensions::NetworkFilters::Common::Redis::Client::Config, - public Extensions::NetworkFilters::Common::Redis::Client::PoolCallbacks, + public Extensions::NetworkFilters::Common::Redis::Client::ClientCallbacks, public Network::ConnectionCallbacks { RedisActiveHealthCheckSession(RedisHealthChecker& parent, const Upstream::HostSharedPtr& host); ~RedisActiveHealthCheckSession() override; @@ -85,10 +85,10 @@ class RedisHealthChecker : public Upstream::HealthCheckerImplBase { uint32_t maxUpstreamUnknownConnections() const override { return 0; } bool enableCommandStats() const override { return false; } - // Extensions::NetworkFilters::Common::Redis::Client::PoolCallbacks + // Extensions::NetworkFilters::Common::Redis::Client::ClientCallbacks void onResponse(NetworkFilters::Common::Redis::RespValuePtr&& value) override; void onFailure() override; - bool onRedirection(const NetworkFilters::Common::Redis::RespValue& value) override; + bool onRedirection(NetworkFilters::Common::Redis::RespValuePtr&& value) override; // Network::ConnectionCallbacks void onEvent(Network::ConnectionEvent event) override; diff --git a/test/extensions/clusters/redis/redis_cluster_test.cc b/test/extensions/clusters/redis/redis_cluster_test.cc index b0179a1d887e..16830cfead4e 100644 --- a/test/extensions/clusters/redis/redis_cluster_test.cc +++ b/test/extensions/clusters/redis/redis_cluster_test.cc @@ -162,7 +162,7 @@ class RedisClusterTest : public testing::Test, EXPECT_CALL(*client_, addConnectionCallbacks(_)); EXPECT_CALL(*client_, close()); } - EXPECT_CALL(*client_, makeRequest(Ref(RedisCluster::ClusterSlotsRequest::instance_), _)) + EXPECT_CALL(*client_, makeRequest_(Ref(RedisCluster::ClusterSlotsRequest::instance_), _)) .WillOnce(Return(&pool_request_)); } @@ -506,10 +506,11 @@ class RedisClusterTest : public testing::Test, EXPECT_EQ(discovery_session.bufferFlushTimeoutInMs(), std::chrono::milliseconds(0)); EXPECT_EQ(discovery_session.maxUpstreamUnknownConnections(), 0); - NetworkFilters::Common::Redis::RespValue dummy_value; - dummy_value.type(NetworkFilters::Common::Redis::RespType::Error); - dummy_value.asString() = "dummy text"; - EXPECT_TRUE(discovery_session.onRedirection(dummy_value)); + NetworkFilters::Common::Redis::RespValuePtr dummy_value{ + new NetworkFilters::Common::Redis::RespValue()}; + dummy_value->type(NetworkFilters::Common::Redis::RespType::Error); + dummy_value->asString() = "dummy text"; + EXPECT_TRUE(discovery_session.onRedirection(std::move(dummy_value))); RedisCluster::RedisDiscoveryClient discovery_client(discovery_session); EXPECT_NO_THROW(discovery_client.onAboveWriteBufferHighWatermark()); @@ -550,7 +551,7 @@ class RedisClusterTest : public testing::Test, Event::MockTimer* interval_timer_{}; Extensions::NetworkFilters::Common::Redis::Client::MockClient* client_{}; Extensions::NetworkFilters::Common::Redis::Client::MockPoolRequest pool_request_; - Extensions::NetworkFilters::Common::Redis::Client::PoolCallbacks* pool_callbacks_{}; + Extensions::NetworkFilters::Common::Redis::Client::ClientCallbacks* pool_callbacks_{}; std::shared_ptr cluster_; std::shared_ptr> cluster_callback_; Network::MockActiveDnsQuery active_dns_query_; diff --git a/test/extensions/filters/network/common/redis/client_impl_test.cc b/test/extensions/filters/network/common/redis/client_impl_test.cc index b8cd7c5bb39c..dd9e10c3d776 100644 --- a/test/extensions/filters/network/common/redis/client_impl_test.cc +++ b/test/extensions/filters/network/common/redis/client_impl_test.cc @@ -149,7 +149,7 @@ TEST_F(RedisClientImplTest, BatchWithZeroBufferAndTimeout) { // Make the dummy request Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); @@ -196,7 +196,7 @@ TEST_F(RedisClientImplTest, BatchWithTimerFiring) { // Make the dummy request Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enableTimer(_, _)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); @@ -238,7 +238,7 @@ TEST_F(RedisClientImplTest, BatchWithTimerCancelledByBufferFlush) { // Make the dummy request (doesn't fill buffer, starts timer) Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enableTimer(_, _)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); @@ -246,7 +246,7 @@ TEST_F(RedisClientImplTest, BatchWithTimerCancelledByBufferFlush) { // Make a second dummy request (fills buffer, cancels timer) Common::Redis::RespValue request2; - MockPoolCallbacks callbacks2; + MockClientCallbacks callbacks2; EXPECT_CALL(*encoder_, encode(Ref(request2), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(true)); ; @@ -287,7 +287,7 @@ TEST_F(RedisClientImplTest, Basic) { client_->initialize(auth_password_); Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); @@ -296,7 +296,7 @@ TEST_F(RedisClientImplTest, Basic) { onConnected(); Common::Redis::RespValue request2; - MockPoolCallbacks callbacks2; + MockClientCallbacks callbacks2; EXPECT_CALL(*encoder_, encode(Ref(request2), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle2 = client_->makeRequest(request2, callbacks2); @@ -378,7 +378,7 @@ TEST_F(RedisClientImplTest, Cancel) { setup(); Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); @@ -387,7 +387,7 @@ TEST_F(RedisClientImplTest, Cancel) { onConnected(); Common::Redis::RespValue request2; - MockPoolCallbacks callbacks2; + MockClientCallbacks callbacks2; EXPECT_CALL(*encoder_, encode(Ref(request2), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle2 = client_->makeRequest(request2, callbacks2); @@ -431,7 +431,7 @@ TEST_F(RedisClientImplTest, FailAll) { client_->addConnectionCallbacks(connection_callbacks); Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); @@ -459,7 +459,7 @@ TEST_F(RedisClientImplTest, FailAllWithCancel) { client_->addConnectionCallbacks(connection_callbacks); Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); @@ -484,7 +484,7 @@ TEST_F(RedisClientImplTest, ProtocolError) { setup(); Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); @@ -513,7 +513,7 @@ TEST_F(RedisClientImplTest, ConnectFail) { setup(); Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); @@ -549,7 +549,7 @@ TEST_F(RedisClientImplTest, OutlierDisabled) { setup(std::make_unique()); Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); @@ -570,7 +570,7 @@ TEST_F(RedisClientImplTest, ConnectTimeout) { setup(); Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); @@ -593,7 +593,7 @@ TEST_F(RedisClientImplTest, OpTimeout) { setup(); Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); @@ -638,7 +638,7 @@ TEST_F(RedisClientImplTest, AskRedirection) { setup(); Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); @@ -647,7 +647,7 @@ TEST_F(RedisClientImplTest, AskRedirection) { onConnected(); Common::Redis::RespValue request2; - MockPoolCallbacks callbacks2; + MockClientCallbacks callbacks2; EXPECT_CALL(*encoder_, encode(Ref(request2), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle2 = client_->makeRequest(request2, callbacks2); @@ -666,8 +666,7 @@ TEST_F(RedisClientImplTest, AskRedirection) { // The exact values of the hash slot and IP info are not important. response1->asString() = "ASK 1111 10.1.2.3:4321"; // Simulate redirection failure. - EXPECT_CALL(callbacks1, onRedirection(Ref(*response1))).WillOnce(Return(false)); - EXPECT_CALL(callbacks1, onResponse_(Ref(response1))); + EXPECT_CALL(callbacks1, onRedirection_(Ref(response1))).WillOnce(Return(false)); EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); EXPECT_CALL(host_->outlier_detector_, putResult(Upstream::Outlier::Result::EXT_ORIGIN_REQUEST_SUCCESS, _)); @@ -679,7 +678,7 @@ TEST_F(RedisClientImplTest, AskRedirection) { response2->type(Common::Redis::RespType::Error); // The exact values of the hash slot and IP info are not important. response2->asString() = "ASK 2222 10.1.2.4:4321"; - EXPECT_CALL(callbacks2, onRedirection(Ref(*response2))).WillOnce(Return(true)); + EXPECT_CALL(callbacks2, onRedirection_(Ref(response2))).WillOnce(Return(true)); EXPECT_CALL(*connect_or_op_timer_, disableTimer()); EXPECT_CALL(host_->outlier_detector_, putResult(Upstream::Outlier::Result::EXT_ORIGIN_REQUEST_SUCCESS, _)); @@ -700,7 +699,7 @@ TEST_F(RedisClientImplTest, MovedRedirection) { setup(); Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); @@ -709,7 +708,7 @@ TEST_F(RedisClientImplTest, MovedRedirection) { onConnected(); Common::Redis::RespValue request2; - MockPoolCallbacks callbacks2; + MockClientCallbacks callbacks2; EXPECT_CALL(*encoder_, encode(Ref(request2), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle2 = client_->makeRequest(request2, callbacks2); @@ -728,8 +727,7 @@ TEST_F(RedisClientImplTest, MovedRedirection) { // The exact values of the hash slot and IP info are not important. response1->asString() = "MOVED 1111 10.1.2.3:4321"; // Simulate redirection failure. - EXPECT_CALL(callbacks1, onRedirection(Ref(*response1))).WillOnce(Return(false)); - EXPECT_CALL(callbacks1, onResponse_(Ref(response1))); + EXPECT_CALL(callbacks1, onRedirection_(Ref(response1))).WillOnce(Return(false)); EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); EXPECT_CALL(host_->outlier_detector_, putResult(Upstream::Outlier::Result::EXT_ORIGIN_REQUEST_SUCCESS, _)); @@ -741,7 +739,7 @@ TEST_F(RedisClientImplTest, MovedRedirection) { response2->type(Common::Redis::RespType::Error); // The exact values of the hash slot and IP info are not important. response2->asString() = "MOVED 2222 10.1.2.4:4321"; - EXPECT_CALL(callbacks2, onRedirection(Ref(*response2))).WillOnce(Return(true)); + EXPECT_CALL(callbacks2, onRedirection_(Ref(response2))).WillOnce(Return(true)); EXPECT_CALL(*connect_or_op_timer_, disableTimer()); EXPECT_CALL(host_->outlier_detector_, putResult(Upstream::Outlier::Result::EXT_ORIGIN_REQUEST_SUCCESS, _)); @@ -762,7 +760,7 @@ TEST_F(RedisClientImplTest, AskRedirectionNotEnabled) { setup(std::make_unique(createConnPoolSettings(20, true, false))); Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); @@ -771,7 +769,7 @@ TEST_F(RedisClientImplTest, AskRedirectionNotEnabled) { onConnected(); Common::Redis::RespValue request2; - MockPoolCallbacks callbacks2; + MockClientCallbacks callbacks2; EXPECT_CALL(*encoder_, encode(Ref(request2), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle2 = client_->makeRequest(request2, callbacks2); @@ -825,7 +823,7 @@ TEST_F(RedisClientImplTest, MovedRedirectionNotEnabled) { setup(std::make_unique(createConnPoolSettings(20, true, false))); Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); @@ -834,7 +832,7 @@ TEST_F(RedisClientImplTest, MovedRedirectionNotEnabled) { onConnected(); Common::Redis::RespValue request2; - MockPoolCallbacks callbacks2; + MockClientCallbacks callbacks2; EXPECT_CALL(*encoder_, encode(Ref(request2), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle2 = client_->makeRequest(request2, callbacks2); @@ -889,7 +887,7 @@ TEST_F(RedisClientImplTest, RemoveFailedHealthCheck) { setup(); Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); @@ -923,7 +921,7 @@ TEST_F(RedisClientImplTest, RemoveFailedHost) { client_->addConnectionCallbacks(connection_callbacks); Common::Redis::RespValue request1; - MockPoolCallbacks callbacks1; + MockClientCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); diff --git a/test/extensions/filters/network/common/redis/codec_impl_test.cc b/test/extensions/filters/network/common/redis/codec_impl_test.cc index b69b05f2bcca..a79eeed8fb17 100644 --- a/test/extensions/filters/network/common/redis/codec_impl_test.cc +++ b/test/extensions/filters/network/common/redis/codec_impl_test.cc @@ -11,6 +11,7 @@ #include "gtest/gtest.h" +using testing::ContainerEq; using testing::InSequence; namespace Envoy { @@ -36,6 +37,25 @@ class RedisRespValueTest : public testing::Test { value.type(RespType::Array); value.asArray().insert(value.asArray().end(), items.begin(), items.end()); } + + void verifyMoves(RespValue& value) { + RespValue copy = value; + RespValue move(std::move(copy)); + EXPECT_TRUE(value == move); + + RespValue move_assign = std::move(move); + EXPECT_TRUE(value == move_assign); + } + + void validateIterator(RespValue& value, const std::vector& strings) { + EXPECT_EQ(RespType::CompositeArray, value.type()); + EXPECT_EQ(value.asCompositeArray().size(), strings.size()); + std::vector values; + for (const RespValue& part : value.asCompositeArray()) { + values.emplace_back(part.asString()); + } + EXPECT_THAT(values, ContainerEq(strings)); + } }; TEST_F(RedisRespValueTest, EqualityTestingAndCopyingTest) { @@ -117,6 +137,64 @@ TEST_F(RedisRespValueTest, EqualityTestingAndCopyingTest) { EXPECT_EQ(value8.type(), RespType::Null); } +TEST_F(RedisRespValueTest, MoveOperationsTest) { + InSequence s; + + RespValue array_value, bulkstring_value, simplestring_value, error_value, integer_value, + null_value; + makeBulkStringArray(array_value, {"get", "foo", "bar", "now"}); + bulkstring_value.type(RespType::BulkString); + bulkstring_value.asString() = "foo"; + simplestring_value.type(RespType::SimpleString); + simplestring_value.asString() = "bar"; + error_value.type(RespType::Error); + error_value.asString() = "error"; + integer_value.type(RespType::Integer); + integer_value.asInteger() = 123; + + verifyMoves(array_value); + verifyMoves(bulkstring_value); + verifyMoves(simplestring_value); + verifyMoves(error_value); + verifyMoves(integer_value); + verifyMoves(null_value); +} + +TEST_F(RedisRespValueTest, SwapTest) { + InSequence s; + + RespValue value1, value2, value3; + + makeBulkStringArray(value1, {"get", "foo", "bar", "now"}); + makeBulkStringArray(value2, {"get", "foo", "bar", "now"}); + makeBulkStringArray(value3, {"get", "foo", "bar", "later"}); + + std::swap(value2, value3); + EXPECT_TRUE(value1 == value3); + + std::swap(value3, value3); + EXPECT_TRUE(value1 == value3); +} + +TEST_F(RedisRespValueTest, IteratorTest) { + InSequence s; + + RespValueSharedPtr base = std::make_shared(); + makeBulkStringArray(*base, {"get", "foo", "bar", "now"}); + + RespValue command; + command.type(RespType::SimpleString); + command.asString() = "get"; + + RespValue value1{base, command, 1, 1}; + RespValue value2{base, command, 2, 2}; + RespValue value3{base, command, 3, 3}; + + validateIterator(value1, {"get", "foo"}); + validateIterator(value2, {"get", "bar"}); + validateIterator(value3, {"get", "now"}); +} + class RedisEncoderDecoderImplTest : public testing::Test, public DecoderCallbacks { public: RedisEncoderDecoderImplTest() : decoder_(*this) {} diff --git a/test/extensions/filters/network/common/redis/mocks.cc b/test/extensions/filters/network/common/redis/mocks.cc index 29d0a35725ea..9e8de5d29fba 100644 --- a/test/extensions/filters/network/common/redis/mocks.cc +++ b/test/extensions/filters/network/common/redis/mocks.cc @@ -42,7 +42,15 @@ MockClient::MockClient() { })); ON_CALL(*this, close()).WillByDefault(Invoke([this]() -> void { raiseEvent(Network::ConnectionEvent::LocalClose); + // for (ClientCallbacks * callbacks : client_callbacks_) { + // callbacks->onFailure(); + // } })); + // ON_CALL(*this, makeRequest(_, _)).WillByDefault(Invoke([this](const Common::Redis::RespValue&, + // ClientCallbacks& callbacks) -> PoolRequest* { + // client_callbacks_.push_back(&callbacks); + // return &pool_request_; + // })); } MockClient::~MockClient() = default; @@ -50,8 +58,8 @@ MockClient::~MockClient() = default; MockPoolRequest::MockPoolRequest() = default; MockPoolRequest::~MockPoolRequest() = default; -MockPoolCallbacks::MockPoolCallbacks() = default; -MockPoolCallbacks::~MockPoolCallbacks() = default; +MockClientCallbacks::MockClientCallbacks() = default; +MockClientCallbacks::~MockClientCallbacks() = default; } // namespace Client diff --git a/test/extensions/filters/network/common/redis/mocks.h b/test/extensions/filters/network/common/redis/mocks.h index a44e41ef63e9..e21260c5b85e 100644 --- a/test/extensions/filters/network/common/redis/mocks.h +++ b/test/extensions/filters/network/common/redis/mocks.h @@ -45,6 +45,14 @@ class MockDecoder : public Common::Redis::Decoder { namespace Client { +class MockPoolRequest : public PoolRequest { +public: + MockPoolRequest(); + ~MockPoolRequest() override; + + MOCK_METHOD0(cancel, void()); +}; + class MockClient : public Client { public: MockClient(); @@ -68,34 +76,34 @@ class MockClient : public Client { } } + PoolRequest* makeRequest(const Common::Redis::RespValue& request, + ClientCallbacks& callbacks) override { + client_callbacks_.push_back(&callbacks); + return makeRequest_(request, callbacks); + } + MOCK_METHOD1(addConnectionCallbacks, void(Network::ConnectionCallbacks& callbacks)); MOCK_METHOD0(active, bool()); MOCK_METHOD0(close, void()); - MOCK_METHOD2(makeRequest, - PoolRequest*(const Common::Redis::RespValue& request, PoolCallbacks& callbacks)); + MOCK_METHOD2(makeRequest_, + PoolRequest*(const Common::Redis::RespValue& request, ClientCallbacks& callbacks)); MOCK_METHOD1(initialize, void(const std::string& password)); std::list callbacks_; + std::list client_callbacks_; }; -class MockPoolRequest : public PoolRequest { -public: - MockPoolRequest(); - ~MockPoolRequest() override; - - MOCK_METHOD0(cancel, void()); -}; - -class MockPoolCallbacks : public PoolCallbacks { +class MockClientCallbacks : public ClientCallbacks { public: - MockPoolCallbacks(); - ~MockPoolCallbacks() override; + MockClientCallbacks(); + ~MockClientCallbacks() override; void onResponse(Common::Redis::RespValuePtr&& value) override { onResponse_(value); } + bool onRedirection(Common::Redis::RespValuePtr&& value) override { return onRedirection_(value); } MOCK_METHOD1(onResponse_, void(Common::Redis::RespValuePtr& value)); MOCK_METHOD0(onFailure, void()); - MOCK_METHOD1(onRedirection, bool(const Common::Redis::RespValue& value)); + MOCK_METHOD1(onRedirection_, bool(Common::Redis::RespValuePtr& value)); }; } // namespace Client diff --git a/test/extensions/filters/network/redis_proxy/BUILD b/test/extensions/filters/network/redis_proxy/BUILD index 1f9a52b6a029..fa40227ec1d9 100644 --- a/test/extensions/filters/network/redis_proxy/BUILD +++ b/test/extensions/filters/network/redis_proxy/BUILD @@ -134,3 +134,21 @@ envoy_extension_cc_test( "//test/integration:integration_lib", ], ) + +envoy_extension_cc_test_binary( + name = "command_split_speed_test", + srcs = ["command_split_speed_test.cc"], + extension_name = "envoy.filters.network.redis_proxy", + external_deps = [ + "benchmark", + ], + deps = [ + ":redis_mocks", + "//source/common/stats:isolated_store_lib", + "//source/common/stats:stats_lib", + "//source/extensions/filters/network/redis_proxy:command_splitter_lib", + "//source/extensions/filters/network/redis_proxy:router_lib", + "//test/test_common:printers_lib", + "//test/test_common:simulated_time_system_lib", + ], +) diff --git a/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc b/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc new file mode 100644 index 000000000000..8486928940cd --- /dev/null +++ b/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc @@ -0,0 +1,125 @@ +// Note: this should be run with --compilation_mode=opt, and would benefit from a +// quiescent system with disabled cstate power management. + +#include +#include +#include + +#include "common/common/fmt.h" +#include "common/stats/isolated_store_impl.h" + +#include "extensions/filters/network/common/redis/client_impl.h" +#include "extensions/filters/network/common/redis/supported_commands.h" +#include "extensions/filters/network/redis_proxy/command_splitter_impl.h" +#include "extensions/filters/network/redis_proxy/router_impl.h" + +#include "test/test_common/simulated_time_system.h" + +#include "benchmark/benchmark.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace RedisProxy { + +class CommandSplitSpeedTest { +public: + Common::Redis::RespValuePtr makeBulkStringArray(uint64_t batch_size, uint64_t key_size, + uint64_t value_size) { + Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; + std::vector values(batch_size * 2 + 1); + values[0].type(Common::Redis::RespType::BulkString); + values[0].asString() = "mset"; + for (uint64_t i = 1; i < batch_size * 2 + 1; i += 2) { + values[i].type(Common::Redis::RespType::BulkString); + values[i].asString() = std::string(key_size, 'k'); + values[i + 1].type(Common::Redis::RespType::BulkString); + values[i + 1].asString() = std::string(value_size, 'v'); + } + + request->type(Common::Redis::RespType::Array); + request->asArray().swap(values); + + return request; + } + + Common::Redis::RespValueSharedPtr + makeSharedBulkStringArray(uint64_t batch_size, uint64_t key_size, uint64_t value_size) { + Common::Redis::RespValueSharedPtr request{new Common::Redis::RespValue()}; + std::vector values(batch_size * 2 + 1); + values[0].type(Common::Redis::RespType::BulkString); + values[0].asString() = "mset"; + for (uint64_t i = 1; i < batch_size * 2 + 1; i += 2) { + values[i].type(Common::Redis::RespType::BulkString); + values[i].asString() = std::string(key_size, 'k'); + values[i + 1].type(Common::Redis::RespType::BulkString); + values[i + 1].asString() = std::string(value_size, 'v'); + } + + request->type(Common::Redis::RespType::Array); + request->asArray().swap(values); + + return request; + } + + void move(Common::Redis::RespValueSharedPtr request) { + for (uint64_t i = 1; i < request->asArray().size(); i += 2) { +// auto single_set = std::make_shared(); +// single_set->type(Common::Redis::RespType::CompositeArray); +// single_set->asCompositeArray().initialize(request, Common::Redis::Utility::SetRequest::instance(), i, i + 2); + + auto single_set = std::make_shared(request, Common::Redis::Utility::SetRequest::instance(), i, i + 2); + } + } + + void copy(Common::Redis::RespValueSharedPtr request) { + std::vector values(3); + values[0].type(Common::Redis::RespType::BulkString); + values[0].asString() = "set"; + values[1].type(Common::Redis::RespType::BulkString); + values[2].type(Common::Redis::RespType::BulkString); + Common::Redis::RespValue single_mset; + single_mset.type(Common::Redis::RespType::Array); + single_mset.asArray().swap(values); + + for (uint64_t i = 1; i < request->asArray().size(); i += 2) { + single_mset.asArray()[1].asString() = request->asArray()[i].asString(); + single_mset.asArray()[2].asString() = request->asArray()[i + 1].asString(); + } + } + +}; +} // namespace RedisProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy + +static void BM_Split_Move(benchmark::State& state) { + Envoy::Extensions::NetworkFilters::RedisProxy::CommandSplitSpeedTest context; + Envoy::Extensions::NetworkFilters::Common::Redis::RespValueSharedPtr request = + context.makeSharedBulkStringArray(state.range(0), 36, state.range(1)); + for (auto _ : state) { + context.move(request); + } +} +BENCHMARK(BM_Split_Move)->Ranges({{1, 100}, {512, 8 << 14}}); + +static void BM_Split_Copy(benchmark::State& state) { + Envoy::Extensions::NetworkFilters::RedisProxy::CommandSplitSpeedTest context; + Envoy::Extensions::NetworkFilters::Common::Redis::RespValueSharedPtr request = + context.makeSharedBulkStringArray(state.range(0), 36, state.range(1)); + for (auto _ : state) { + context.copy(request); + } +} +BENCHMARK(BM_Split_Copy)->Ranges({{1, 100}, {512, 8 << 14}}); + +// Boilerplate main(), which discovers benchmarks in the same file and runs them. +int main(int argc, char** argv) { + benchmark::Initialize(&argc, argv); + + if (benchmark::ReportUnrecognizedArguments(argc, argv)) { + return 1; + } + benchmark::RunSpecifiedBenchmarks(); +} diff --git a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc index dcbe36bd4207..21c5a0849b0a 100644 --- a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc @@ -18,10 +18,12 @@ using testing::_; using testing::ByRef; using testing::DoAll; +using testing::ElementsAreArray; using testing::Eq; using testing::InSequence; using testing::Invoke; using testing::NiceMock; +using testing::Pointee; using testing::Property; using testing::Ref; using testing::Return; @@ -158,7 +160,7 @@ class RedisSingleServerRequestTest : public RedisCommandSplitterImplTest, public: void makeRequest(const std::string& hash_key, Common::Redis::RespValuePtr&& request) { EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); - EXPECT_CALL(*conn_pool_, makeRequest(hash_key, Ref(*request), _)) + EXPECT_CALL(*conn_pool_, makeRequest(hash_key, Pointee(Ref(*request)), _)) .WillOnce(DoAll(WithArg<2>(SaveArgAddress(&pool_callbacks_)), Return(&pool_request_))); handle_ = splitter_.makeRequest(std::move(request), callbacks_); } @@ -178,7 +180,7 @@ class RedisSingleServerRequestTest : public RedisCommandSplitterImplTest, pool_callbacks_->onResponse(std::move(response1)); } - Common::Redis::Client::PoolCallbacks* pool_callbacks_; + ConnPool::PoolCallbacks* pool_callbacks_; Common::Redis::Client::MockPoolRequest pool_request_; }; @@ -271,7 +273,8 @@ TEST_P(RedisSingleServerRequestTest, NoUpstream) { EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; makeBulkStringArray(*request, {GetParam(), "hello"}); - EXPECT_CALL(*conn_pool_, makeRequest("hello", Ref(*request), _)).WillOnce(Return(nullptr)); + EXPECT_CALL(*conn_pool_, makeRequest("hello", Pointee(Ref(*request)), _)) + .WillOnce(Return(nullptr)); Common::Redis::RespValue response; response.type(Common::Redis::RespType::Error); @@ -378,7 +381,7 @@ TEST_F(RedisSingleServerRequestTest, EvalNoUpstream) { EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; makeBulkStringArray(*request, {"eval", "return {ARGV[1]}", "1", "key", "arg"}); - EXPECT_CALL(*conn_pool_, makeRequest("key", Ref(*request), _)).WillOnce(Return(nullptr)); + EXPECT_CALL(*conn_pool_, makeRequest("key", Pointee(Ref(*request)), _)).WillOnce(Return(nullptr)); Common::Redis::RespValue response; response.type(Common::Redis::RespType::Error); @@ -391,203 +394,17 @@ TEST_F(RedisSingleServerRequestTest, EvalNoUpstream) { EXPECT_EQ(1UL, store_.counter("redis.foo.command.eval.error").value()); }; -TEST_F(RedisSingleServerRequestTest, MovedRedirectionSuccess) { - InSequence s; - - Common::Redis::Client::MockPoolRequest pool_request2; - Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; - makeBulkStringArray(*request, {"get", "foo"}); - makeRequest("foo", std::move(request)); - EXPECT_NE(nullptr, handle_); - - Common::Redis::RespValue moved_response; - moved_response.type(Common::Redis::RespType::Error); - moved_response.asString() = "MOVED 1111 10.1.2.3:4000"; - std::string host_address; - Common::Redis::RespValue request_copy; - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, Ref(*pool_callbacks_))) - .WillOnce( - DoAll(SaveArg<0>(&host_address), SaveArg<1>(&request_copy), Return(&pool_request2))); - EXPECT_TRUE(pool_callbacks_->onRedirection(moved_response)); - EXPECT_EQ(host_address, "10.1.2.3:4000"); - EXPECT_EQ(request_copy.type(), Common::Redis::RespType::Array); - EXPECT_EQ(request_copy.asArray().size(), 2); - EXPECT_EQ(request_copy.asArray()[0].type(), Common::Redis::RespType::BulkString); - EXPECT_EQ(request_copy.asArray()[0].asString(), "get"); - EXPECT_EQ(request_copy.asArray()[1].type(), Common::Redis::RespType::BulkString); - EXPECT_EQ(request_copy.asArray()[1].asString(), "foo"); - - respond(); -}; - -TEST_F(RedisSingleServerRequestTest, MovedRedirectionFailure) { - InSequence s; - - Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; - makeBulkStringArray(*request, {"get", "foo"}); - makeRequest("foo", std::move(request)); - EXPECT_NE(nullptr, handle_); - - // Test a truncated MOVED error response that cannot be parsed properly. - Common::Redis::RespValue moved_response; - moved_response.type(Common::Redis::RespType::Error); - moved_response.asString() = "MOVED 1111"; - EXPECT_FALSE(pool_callbacks_->onRedirection(moved_response)); - moved_response.type(Common::Redis::RespType::Integer); - moved_response.asInteger() = 1; - EXPECT_FALSE(pool_callbacks_->onRedirection(moved_response)); - - // Test an upstream error preventing the request from being sent. - moved_response.type(Common::Redis::RespType::Error); - moved_response.asString() = "MOVED 1111 10.1.2.3:4000"; - std::string host_address; - Common::Redis::RespValue request_copy; - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, _)).WillOnce(Return(nullptr)); - EXPECT_FALSE(pool_callbacks_->onRedirection(moved_response)); - - respond(); -}; - -TEST_F(RedisSingleServerRequestTest, RedirectionFailure) { - InSequence s; - - Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; - makeBulkStringArray(*request, {"get", "foo"}); - makeRequest("foo", std::move(request)); - EXPECT_NE(nullptr, handle_); - - // Test an error that looks like it might be a MOVED or ASK redirection error except for the first - // non-whitespace substring. - Common::Redis::RespValue moved_response; - moved_response.type(Common::Redis::RespType::Error); - moved_response.asString() = "NOTMOVEDORASK 1111 1.1.1.1:1"; - EXPECT_FALSE(pool_callbacks_->onRedirection(moved_response)); - moved_response.type(Common::Redis::RespType::Integer); - moved_response.asInteger() = 1; - EXPECT_FALSE(pool_callbacks_->onRedirection(moved_response)); - - respond(); -}; - -TEST_F(RedisSingleServerRequestTest, AskRedirectionSuccess) { - InSequence s; - - Common::Redis::Client::MockPoolRequest pool_request2, pool_request3; - Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; - makeBulkStringArray(*request, {"get", "foo"}); - makeRequest("foo", std::move(request)); - EXPECT_NE(nullptr, handle_); - - Common::Redis::RespValue ask_response; - ask_response.type(Common::Redis::RespType::Error); - ask_response.asString() = "ASK 1111 10.1.2.3:4000"; - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, _)) - .WillOnce( - Invoke([&](const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks&) -> Common::Redis::Client::PoolRequest* { - // Verify that the request has been properly prepended with an "asking" command. - std::vector commands = {"asking"}; - EXPECT_EQ(host_address, "10.1.2.3:4000"); - EXPECT_TRUE(request.type() == Common::Redis::RespType::Array); - EXPECT_EQ(request.asArray().size(), commands.size()); - for (unsigned int i = 0; i < commands.size(); i++) { - EXPECT_TRUE(request.asArray()[i].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[i].asString(), commands[i]); - } - return &pool_request2; - })); - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, Ref(*pool_callbacks_))) - .WillOnce( - Invoke([&](const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks&) -> Common::Redis::Client::PoolRequest* { - std::vector commands = {"get", "foo"}; - EXPECT_EQ(host_address, "10.1.2.3:4000"); - EXPECT_TRUE(request.type() == Common::Redis::RespType::Array); - EXPECT_EQ(request.asArray().size(), commands.size()); - for (unsigned int i = 0; i < commands.size(); i++) { - EXPECT_TRUE(request.asArray()[i].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[i].asString(), commands[i]); - } - return &pool_request3; - })); - EXPECT_TRUE(pool_callbacks_->onRedirection(ask_response)); - respond(); -}; - -TEST_F(RedisSingleServerRequestTest, AskRedirectionFailure) { - InSequence s; - - Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; - makeBulkStringArray(*request, {"get", "foo"}); - makeRequest("foo", std::move(request)); - EXPECT_NE(nullptr, handle_); - - Common::Redis::RespValue ask_response; - - // Test a truncated ASK error response that cannot be parsed properly. - ask_response.type(Common::Redis::RespType::Error); - ask_response.asString() = "ASK 1111"; - EXPECT_FALSE(pool_callbacks_->onRedirection(ask_response)); - ask_response.type(Common::Redis::RespType::Integer); - ask_response.asInteger() = 1; - EXPECT_FALSE(pool_callbacks_->onRedirection(ask_response)); - - // Test an upstream error from trying to send an "asking" command upstream. - ask_response.type(Common::Redis::RespType::Error); - ask_response.asString() = "ASK 1111 10.1.2.3:4000"; - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, _)) - .WillOnce( - Invoke([&](const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks&) -> Common::Redis::Client::PoolRequest* { - // Verify that the request has been properly prepended with an "asking" command. - std::vector commands = {"asking"}; - EXPECT_EQ(host_address, "10.1.2.3:4000"); - EXPECT_TRUE(request.type() == Common::Redis::RespType::Array); - EXPECT_EQ(request.asArray().size(), commands.size()); - for (unsigned int i = 0; i < commands.size(); i++) { - EXPECT_TRUE(request.asArray()[i].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[i].asString(), commands[i]); - } - return nullptr; - })); - EXPECT_FALSE(pool_callbacks_->onRedirection(ask_response)); - - // Test an upstream error from trying to send the original request after the "asking" command is - // sent successfully. - Common::Redis::Client::MockPoolRequest pool_request; - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, _)) - .WillOnce( - Invoke([&](const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks&) -> Common::Redis::Client::PoolRequest* { - // Verify that the request has been properly prepended with an "asking" command. - std::vector commands = {"asking"}; - EXPECT_EQ(host_address, "10.1.2.3:4000"); - EXPECT_TRUE(request.type() == Common::Redis::RespType::Array); - EXPECT_EQ(request.asArray().size(), commands.size()); - for (unsigned int i = 0; i < commands.size(); i++) { - EXPECT_TRUE(request.asArray()[i].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[i].asString(), commands[i]); - } - return &pool_request; - })); - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, Ref(*pool_callbacks_))) - .WillOnce( - Invoke([&](const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks&) -> Common::Redis::Client::PoolRequest* { - std::vector commands = {"get", "foo"}; - EXPECT_EQ(host_address, "10.1.2.3:4000"); - EXPECT_TRUE(request.type() == Common::Redis::RespType::Array); - EXPECT_EQ(request.asArray().size(), commands.size()); - for (unsigned int i = 0; i < commands.size(); i++) { - EXPECT_TRUE(request.asArray()[i].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[i].asString(), commands[i]); - } - return nullptr; - })); - EXPECT_FALSE(pool_callbacks_->onRedirection(ask_response)); - - respond(); -}; +MATCHER_P(CompositeArrayEq, rhs, "") { + const Common::Redis::RespValueSharedPtr& obj = arg; + EXPECT_TRUE(obj->type() == Common::Redis::RespType::CompositeArray); + EXPECT_EQ(obj->asCompositeArray().size(), rhs.size()); + std::vector array(obj->asCompositeArray().size()); + for (auto const& entry : obj->asCompositeArray()) { + array.emplace_back(entry.asString()); + } + EXPECT_EQ(array, rhs); + return true; +} class RedisMGETCommandHandlerTest : public RedisCommandSplitterImplTest { public: @@ -600,7 +417,7 @@ class RedisMGETCommandHandlerTest : public RedisCommandSplitterImplTest { Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; makeBulkStringArray(*request, request_strings); - std::vector tmp_expected_requests(num_gets); + std::vector> tmp_expected_requests(num_gets); expected_requests_.swap(tmp_expected_requests); pool_callbacks_.resize(num_gets); std::vector tmp_pool_requests(num_gets); @@ -609,21 +426,22 @@ class RedisMGETCommandHandlerTest : public RedisCommandSplitterImplTest { EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); for (uint32_t i = 0; i < num_gets; i++) { - makeBulkStringArray(expected_requests_[i], {"get", std::to_string(i)}); + expected_requests_.push_back({"get", std::to_string(i)}); Common::Redis::Client::PoolRequest* request_to_use = nullptr; if (std::find(null_handle_indexes.begin(), null_handle_indexes.end(), i) == null_handle_indexes.end()) { request_to_use = &pool_requests_[i]; } - EXPECT_CALL(*conn_pool_, makeRequest(std::to_string(i), Eq(ByRef(expected_requests_[i])), _)) + EXPECT_CALL(*conn_pool_, + makeRequest(std::to_string(i), CompositeArrayEq(expected_requests_[i]), _)) .WillOnce(DoAll(WithArg<2>(SaveArgAddress(&pool_callbacks_[i])), Return(request_to_use))); } handle_ = splitter_.makeRequest(std::move(request), callbacks_); } - std::vector expected_requests_; - std::vector pool_callbacks_; + std::vector> expected_requests_; + std::vector pool_callbacks_; std::vector pool_requests_; }; @@ -792,197 +610,6 @@ TEST_F(RedisMGETCommandHandlerTest, Cancel) { handle_->cancel(); }; -TEST_F(RedisMGETCommandHandlerTest, NormalWithMovedRedirection) { - InSequence s; - - setup(2, {}); - EXPECT_NE(nullptr, handle_); - - // Test with a non-error response. - Common::Redis::RespValue bad_moved_response; - bad_moved_response.type(Common::Redis::RespType::Integer); - bad_moved_response.asInteger() = 1; - EXPECT_FALSE(pool_callbacks_[0]->onRedirection(bad_moved_response)); - - // Test with a valid MOVED response. - Common::Redis::RespValue moved_response; - moved_response.type(Common::Redis::RespType::Error); - moved_response.asString() = "MOVED 1234 192.168.0.1:5000"; // Exact values are not important. - - // Test with simulated upstream failures. This exercises code in - // FragmentedRequest::onChildRedirection() common to MGET, MSET, and SplitKeysSumResult commands. - for (unsigned int i = 0; i < 2; i++) { - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, Ref(*pool_callbacks_[i]))) - .WillOnce(Invoke( - [&](const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks&) -> Common::Redis::Client::PoolRequest* { - EXPECT_EQ(host_address, "192.168.0.1:5000"); - EXPECT_TRUE(request.type() == Common::Redis::RespType::Array); - EXPECT_EQ(request.asArray().size(), 2); - EXPECT_TRUE(request.asArray()[0].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[0].asString(), "get"); - EXPECT_TRUE(request.asArray()[1].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[1].asString(), std::to_string(i)); - EXPECT_NE(&pool_requests_[i], nullptr); - return nullptr; - })); - EXPECT_FALSE(pool_callbacks_[i]->onRedirection(moved_response)); - } - - // Test "successful" redirection. - for (unsigned int i = 0; i < 2; i++) { - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, Ref(*pool_callbacks_[i]))) - .WillOnce(Invoke( - [&](const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks&) -> Common::Redis::Client::PoolRequest* { - EXPECT_EQ(host_address, "192.168.0.1:5000"); - EXPECT_TRUE(request.type() == Common::Redis::RespType::Array); - EXPECT_EQ(request.asArray().size(), 2); - EXPECT_TRUE(request.asArray()[0].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[0].asString(), "get"); - EXPECT_TRUE(request.asArray()[1].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[1].asString(), std::to_string(i)); - EXPECT_NE(&pool_requests_[i], nullptr); - return &pool_requests_[i]; - })); - EXPECT_TRUE(pool_callbacks_[i]->onRedirection(moved_response)); - } - - Common::Redis::RespValue expected_response; - expected_response.type(Common::Redis::RespType::Array); - std::vector elements(2); - elements[0].type(Common::Redis::RespType::BulkString); - elements[0].asString() = "response"; - elements[1].type(Common::Redis::RespType::BulkString); - elements[1].asString() = "5"; - expected_response.asArray().swap(elements); - - Common::Redis::RespValuePtr response2(new Common::Redis::RespValue()); - response2->type(Common::Redis::RespType::BulkString); - response2->asString() = "5"; - pool_callbacks_[1]->onResponse(std::move(response2)); - - Common::Redis::RespValuePtr response1(new Common::Redis::RespValue()); - response1->type(Common::Redis::RespType::BulkString); - response1->asString() = "response"; - time_system_.setMonotonicTime(std::chrono::milliseconds(10)); - EXPECT_CALL(store_, deliverHistogramToSinks( - Property(&Stats::Metric::name, "redis.foo.command.mget.latency"), 10)); - EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&expected_response))); - pool_callbacks_[0]->onResponse(std::move(response1)); - - EXPECT_EQ(1UL, store_.counter("redis.foo.command.mget.total").value()); - EXPECT_EQ(1UL, store_.counter("redis.foo.command.mget.success").value()); -}; - -TEST_F(RedisMGETCommandHandlerTest, NormalWithAskRedirection) { - InSequence s; - - setup(2, {}); - EXPECT_NE(nullptr, handle_); - - // Test with an non-error response. - Common::Redis::RespValue bad_ask_response; - bad_ask_response.type(Common::Redis::RespType::Integer); - bad_ask_response.asInteger() = 1; - EXPECT_FALSE(pool_callbacks_[0]->onRedirection(bad_ask_response)); - - // Test with a valid ASK response. - Common::Redis::RespValue ask_response; - ask_response.type(Common::Redis::RespType::Error); - ask_response.asString() = "ASK 1234 192.168.0.1:5000"; // Exact values are not important. - Common::Redis::Client::MockPoolRequest dummy_poolrequest; - - // Test redirection with simulated upstream failures. This exercises code in - // FragmentedRequest::onChildRedirection() common to MGET, MSET, and SplitKeysSumResult commands. - for (unsigned int i = 0; i < 2; i++) { - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, _)) - .WillOnce(Invoke( - [&](const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks&) -> Common::Redis::Client::PoolRequest* { - EXPECT_EQ(host_address, "192.168.0.1:5000"); - EXPECT_TRUE(request.type() == Common::Redis::RespType::Array); - EXPECT_EQ(request.asArray().size(), 1); - EXPECT_TRUE(request.asArray()[0].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[0].asString(), "asking"); - return (i == 0 ? nullptr : &dummy_poolrequest); - })); - if (i == 1) { - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, Ref(*pool_callbacks_[i]))) - .WillOnce(Invoke( - [&](const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks&) -> Common::Redis::Client::PoolRequest* { - EXPECT_EQ(host_address, "192.168.0.1:5000"); - EXPECT_TRUE(request.type() == Common::Redis::RespType::Array); - EXPECT_EQ(request.asArray().size(), 2); - EXPECT_TRUE(request.asArray()[0].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[0].asString(), "get"); - EXPECT_TRUE(request.asArray()[1].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[1].asString(), std::to_string(i)); - EXPECT_NE(&pool_requests_[i], nullptr); - return (i == 1 ? nullptr : &pool_requests_[i]); - })); - } - EXPECT_FALSE(pool_callbacks_[i]->onRedirection(ask_response)); - } - - // Test "successful" redirection. - for (unsigned int i = 0; i < 2; i++) { - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, _)) - .WillOnce(Invoke( - [&](const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks&) -> Common::Redis::Client::PoolRequest* { - EXPECT_EQ(host_address, "192.168.0.1:5000"); - EXPECT_TRUE(request.type() == Common::Redis::RespType::Array); - EXPECT_EQ(request.asArray().size(), 1); - EXPECT_TRUE(request.asArray()[0].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[0].asString(), "asking"); - return &dummy_poolrequest; - })); - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, Ref(*pool_callbacks_[i]))) - .WillOnce(Invoke( - [&](const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks&) -> Common::Redis::Client::PoolRequest* { - EXPECT_EQ(host_address, "192.168.0.1:5000"); - EXPECT_TRUE(request.type() == Common::Redis::RespType::Array); - EXPECT_EQ(request.asArray().size(), 2); - EXPECT_TRUE(request.asArray()[0].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[0].asString(), "get"); - EXPECT_TRUE(request.asArray()[1].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[1].asString(), std::to_string(i)); - EXPECT_NE(&pool_requests_[i], nullptr); - return &pool_requests_[i]; - })); - EXPECT_TRUE(pool_callbacks_[i]->onRedirection(ask_response)); - } - - Common::Redis::RespValue expected_response; - expected_response.type(Common::Redis::RespType::Array); - std::vector elements(2); - elements[0].type(Common::Redis::RespType::BulkString); - elements[0].asString() = "response"; - elements[1].type(Common::Redis::RespType::BulkString); - elements[1].asString() = "5"; - expected_response.asArray().swap(elements); - - Common::Redis::RespValuePtr response2(new Common::Redis::RespValue()); - response2->type(Common::Redis::RespType::BulkString); - response2->asString() = "5"; - pool_callbacks_[1]->onResponse(std::move(response2)); - - Common::Redis::RespValuePtr response1(new Common::Redis::RespValue()); - response1->type(Common::Redis::RespType::BulkString); - response1->asString() = "response"; - time_system_.setMonotonicTime(std::chrono::milliseconds(10)); - EXPECT_CALL(store_, deliverHistogramToSinks( - Property(&Stats::Metric::name, "redis.foo.command.mget.latency"), 10)); - EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&expected_response))); - pool_callbacks_[0]->onResponse(std::move(response1)); - - EXPECT_EQ(1UL, store_.counter("redis.foo.command.mget.total").value()); - EXPECT_EQ(1UL, store_.counter("redis.foo.command.mget.success").value()); -}; - class RedisMSETCommandHandlerTest : public RedisCommandSplitterImplTest { public: void setup(uint32_t num_sets, const std::list& null_handle_indexes) { @@ -997,8 +624,7 @@ class RedisMSETCommandHandlerTest : public RedisCommandSplitterImplTest { Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; makeBulkStringArray(*request, request_strings); - std::vector tmp_expected_requests(num_sets); - expected_requests_.swap(tmp_expected_requests); + expected_requests_.resize(num_sets); pool_callbacks_.resize(num_sets); std::vector tmp_pool_requests(num_sets); pool_requests_.swap(tmp_pool_requests); @@ -1006,21 +632,22 @@ class RedisMSETCommandHandlerTest : public RedisCommandSplitterImplTest { EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); for (uint32_t i = 0; i < num_sets; i++) { - makeBulkStringArray(expected_requests_[i], {"set", std::to_string(i), std::to_string(i)}); + expected_requests_.push_back({"set", std::to_string(i), std::to_string(i)}); Common::Redis::Client::PoolRequest* request_to_use = nullptr; if (std::find(null_handle_indexes.begin(), null_handle_indexes.end(), i) == null_handle_indexes.end()) { request_to_use = &pool_requests_[i]; } - EXPECT_CALL(*conn_pool_, makeRequest(std::to_string(i), Eq(ByRef(expected_requests_[i])), _)) + EXPECT_CALL(*conn_pool_, + makeRequest(std::to_string(i), CompositeArrayEq(expected_requests_[i]), _)) .WillOnce(DoAll(WithArg<2>(SaveArgAddress(&pool_callbacks_[i])), Return(request_to_use))); } handle_ = splitter_.makeRequest(std::move(request), callbacks_); } - std::vector expected_requests_; - std::vector pool_callbacks_; + std::vector> expected_requests_; + std::vector pool_callbacks_; std::vector pool_requests_; }; @@ -1112,136 +739,6 @@ TEST_F(RedisMSETCommandHandlerTest, WrongNumberOfArgs) { EXPECT_EQ(1UL, store_.counter("redis.foo.command.mset.error").value()); }; -TEST_F(RedisMSETCommandHandlerTest, NormalWithMovedRedirection) { - InSequence s; - - setup(2, {}); - EXPECT_NE(nullptr, handle_); - - // Test with a non-error response. - Common::Redis::RespValue bad_moved_response; - bad_moved_response.type(Common::Redis::RespType::Integer); - bad_moved_response.asInteger() = 1; - EXPECT_FALSE(pool_callbacks_[0]->onRedirection(bad_moved_response)); - - // Test with a valid MOVED response. - Common::Redis::RespValue moved_response; - moved_response.type(Common::Redis::RespType::Error); - moved_response.asString() = "MOVED 1234 192.168.0.1:5000"; // Exact values are not important. - for (unsigned int i = 0; i < 2; i++) { - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, Ref(*pool_callbacks_[i]))) - .WillOnce(Invoke( - [&](const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks&) -> Common::Redis::Client::PoolRequest* { - EXPECT_EQ(host_address, "192.168.0.1:5000"); - EXPECT_TRUE(request.type() == Common::Redis::RespType::Array); - EXPECT_EQ(request.asArray().size(), 3); - EXPECT_TRUE(request.asArray()[0].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[0].asString(), "set"); - EXPECT_TRUE(request.asArray()[1].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[1].asString(), std::to_string(i)); - EXPECT_TRUE(request.asArray()[2].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[2].asString(), std::to_string(i)); - EXPECT_NE(&pool_requests_[i], nullptr); - return &pool_requests_[i]; - })); - EXPECT_TRUE(pool_callbacks_[i]->onRedirection(moved_response)); - } - - Common::Redis::RespValue expected_response; - expected_response.type(Common::Redis::RespType::SimpleString); - expected_response.asString() = Response::get().OK; - - Common::Redis::RespValuePtr response2(new Common::Redis::RespValue()); - response2->type(Common::Redis::RespType::SimpleString); - response2->asString() = Response::get().OK; - pool_callbacks_[1]->onResponse(std::move(response2)); - - Common::Redis::RespValuePtr response1(new Common::Redis::RespValue()); - response1->type(Common::Redis::RespType::SimpleString); - response1->asString() = Response::get().OK; - - time_system_.setMonotonicTime(std::chrono::milliseconds(10)); - EXPECT_CALL(store_, deliverHistogramToSinks( - Property(&Stats::Metric::name, "redis.foo.command.mset.latency"), 10)); - EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&expected_response))); - pool_callbacks_[0]->onResponse(std::move(response1)); - - EXPECT_EQ(1UL, store_.counter("redis.foo.command.mset.total").value()); - EXPECT_EQ(1UL, store_.counter("redis.foo.command.mset.success").value()); -}; - -TEST_F(RedisMSETCommandHandlerTest, NormalWithAskRedirection) { - InSequence s; - - setup(2, {}); - EXPECT_NE(nullptr, handle_); - - // Test with a non-error response. - Common::Redis::RespValue bad_ask_response; - bad_ask_response.type(Common::Redis::RespType::Integer); - bad_ask_response.asInteger() = 1; - EXPECT_FALSE(pool_callbacks_[0]->onRedirection(bad_ask_response)); - - // Test with a valid ASK response. - Common::Redis::RespValue ask_response; - ask_response.type(Common::Redis::RespType::Error); - ask_response.asString() = "ASK 1234 192.168.0.1:5000"; // Exact values are not important. - Common::Redis::Client::MockPoolRequest dummy_poolrequest; - for (unsigned int i = 0; i < 2; i++) { - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, _)) - .WillOnce(Invoke( - [&](const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks&) -> Common::Redis::Client::PoolRequest* { - EXPECT_EQ(host_address, "192.168.0.1:5000"); - EXPECT_TRUE(request.type() == Common::Redis::RespType::Array); - EXPECT_EQ(request.asArray().size(), 1); - EXPECT_TRUE(request.asArray()[0].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[0].asString(), "asking"); - return &dummy_poolrequest; - })); - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, Ref(*pool_callbacks_[i]))) - .WillOnce(Invoke( - [&](const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks&) -> Common::Redis::Client::PoolRequest* { - EXPECT_EQ(host_address, "192.168.0.1:5000"); - EXPECT_TRUE(request.type() == Common::Redis::RespType::Array); - EXPECT_EQ(request.asArray().size(), 3); - EXPECT_TRUE(request.asArray()[0].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[0].asString(), "set"); - EXPECT_TRUE(request.asArray()[1].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[1].asString(), std::to_string(i)); - EXPECT_TRUE(request.asArray()[2].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[2].asString(), std::to_string(i)); - EXPECT_NE(&pool_requests_[i], nullptr); - return &pool_requests_[i]; - })); - EXPECT_TRUE(pool_callbacks_[i]->onRedirection(ask_response)); - } - - Common::Redis::RespValue expected_response; - expected_response.type(Common::Redis::RespType::SimpleString); - expected_response.asString() = Response::get().OK; - - Common::Redis::RespValuePtr response2(new Common::Redis::RespValue()); - response2->type(Common::Redis::RespType::SimpleString); - response2->asString() = Response::get().OK; - pool_callbacks_[1]->onResponse(std::move(response2)); - - Common::Redis::RespValuePtr response1(new Common::Redis::RespValue()); - response1->type(Common::Redis::RespType::SimpleString); - response1->asString() = Response::get().OK; - - time_system_.setMonotonicTime(std::chrono::milliseconds(10)); - EXPECT_CALL(store_, deliverHistogramToSinks( - Property(&Stats::Metric::name, "redis.foo.command.mset.latency"), 10)); - EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&expected_response))); - pool_callbacks_[0]->onResponse(std::move(response1)); - - EXPECT_EQ(1UL, store_.counter("redis.foo.command.mset.total").value()); - EXPECT_EQ(1UL, store_.counter("redis.foo.command.mset.success").value()); -}; - class RedisSplitKeysSumResultHandlerTest : public RedisCommandSplitterImplTest, public testing::WithParamInterface { public: @@ -1254,8 +751,7 @@ class RedisSplitKeysSumResultHandlerTest : public RedisCommandSplitterImplTest, Common::Redis::RespValuePtr request(new Common::Redis::RespValue()); makeBulkStringArray(*request, request_strings); - std::vector tmp_expected_requests(num_commands); - expected_requests_.swap(tmp_expected_requests); + expected_requests_.resize(num_commands); pool_callbacks_.resize(num_commands); std::vector tmp_pool_requests(num_commands); pool_requests_.swap(tmp_pool_requests); @@ -1263,21 +759,22 @@ class RedisSplitKeysSumResultHandlerTest : public RedisCommandSplitterImplTest, EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); for (uint32_t i = 0; i < num_commands; i++) { - makeBulkStringArray(expected_requests_[i], {GetParam(), std::to_string(i)}); + expected_requests_.push_back({GetParam(), std::to_string(i)}); Common::Redis::Client::PoolRequest* request_to_use = nullptr; if (std::find(null_handle_indexes.begin(), null_handle_indexes.end(), i) == null_handle_indexes.end()) { request_to_use = &pool_requests_[i]; } - EXPECT_CALL(*conn_pool_, makeRequest(std::to_string(i), Eq(ByRef(expected_requests_[i])), _)) + EXPECT_CALL(*conn_pool_, + makeRequest(std::to_string(i), CompositeArrayEq(expected_requests_[i]), _)) .WillOnce(DoAll(WithArg<2>(SaveArgAddress(&pool_callbacks_[i])), Return(request_to_use))); } handle_ = splitter_.makeRequest(std::move(request), callbacks_); } - std::vector expected_requests_; - std::vector pool_callbacks_; + std::vector> expected_requests_; + std::vector pool_callbacks_; std::vector pool_requests_; }; @@ -1350,134 +847,6 @@ TEST_P(RedisSplitKeysSumResultHandlerTest, NoUpstreamHostForAll) { EXPECT_EQ(1UL, store_.counter("redis.foo.command." + GetParam() + ".error").value()); }; -TEST_P(RedisSplitKeysSumResultHandlerTest, NormalWithMovedRedirection) { - InSequence s; - - setup(2, {}); - EXPECT_NE(nullptr, handle_); - - // Test with a non-error response. - Common::Redis::RespValue bad_moved_response; - bad_moved_response.type(Common::Redis::RespType::Integer); - bad_moved_response.asInteger() = 1; - EXPECT_FALSE(pool_callbacks_[0]->onRedirection(bad_moved_response)); - - // Test with a valid MOVED response. - Common::Redis::RespValue moved_response; - moved_response.type(Common::Redis::RespType::Error); - moved_response.asString() = "MOVED 1234 192.168.0.1:5000"; // Exact values are not important. - for (unsigned int i = 0; i < 2; i++) { - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, Ref(*pool_callbacks_[i]))) - .WillOnce(Invoke( - [&](const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks&) -> Common::Redis::Client::PoolRequest* { - EXPECT_EQ(host_address, "192.168.0.1:5000"); - EXPECT_TRUE(request.type() == Common::Redis::RespType::Array); - EXPECT_EQ(request.asArray().size(), 2); - EXPECT_TRUE(request.asArray()[0].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[0].asString(), GetParam()); - EXPECT_TRUE(request.asArray()[1].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[1].asString(), std::to_string(i)); - EXPECT_NE(&pool_requests_[i], nullptr); - return &pool_requests_[i]; - })); - EXPECT_TRUE(pool_callbacks_[i]->onRedirection(moved_response)); - } - - Common::Redis::RespValue expected_response; - expected_response.type(Common::Redis::RespType::Integer); - expected_response.asInteger() = 2; - - Common::Redis::RespValuePtr response2(new Common::Redis::RespValue()); - response2->type(Common::Redis::RespType::Integer); - response2->asInteger() = 1; - pool_callbacks_[1]->onResponse(std::move(response2)); - - Common::Redis::RespValuePtr response1(new Common::Redis::RespValue()); - response1->type(Common::Redis::RespType::Integer); - response1->asInteger() = 1; - time_system_.setMonotonicTime(std::chrono::milliseconds(10)); - EXPECT_CALL( - store_, - deliverHistogramToSinks( - Property(&Stats::Metric::name, "redis.foo.command." + GetParam() + ".latency"), 10)); - EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&expected_response))); - pool_callbacks_[0]->onResponse(std::move(response1)); - - EXPECT_EQ(1UL, store_.counter("redis.foo.command." + GetParam() + ".total").value()); - EXPECT_EQ(1UL, store_.counter("redis.foo.command." + GetParam() + ".success").value()); -}; - -TEST_P(RedisSplitKeysSumResultHandlerTest, NormalWithAskRedirection) { - InSequence s; - - setup(2, {}); - EXPECT_NE(nullptr, handle_); - - // Test with a non-error response. - Common::Redis::RespValue bad_ask_response; - bad_ask_response.type(Common::Redis::RespType::Integer); - bad_ask_response.asInteger() = 1; - EXPECT_FALSE(pool_callbacks_[0]->onRedirection(bad_ask_response)); - - // Test with a valid ASK response. - Common::Redis::RespValue ask_response; - ask_response.type(Common::Redis::RespType::Error); - ask_response.asString() = "ASK 1234 192.168.0.1:5000"; // Exact values are not important. - Common::Redis::Client::MockPoolRequest dummy_poolrequest; - for (unsigned int i = 0; i < 2; i++) { - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, _)) - .WillOnce(Invoke( - [&](const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks&) -> Common::Redis::Client::PoolRequest* { - EXPECT_EQ(host_address, "192.168.0.1:5000"); - EXPECT_TRUE(request.type() == Common::Redis::RespType::Array); - EXPECT_EQ(request.asArray().size(), 1); - EXPECT_TRUE(request.asArray()[0].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[0].asString(), "asking"); - return &dummy_poolrequest; - })); - EXPECT_CALL(*conn_pool_, makeRequestToHost(_, _, Ref(*pool_callbacks_[i]))) - .WillOnce(Invoke( - [&](const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks&) -> Common::Redis::Client::PoolRequest* { - EXPECT_EQ(host_address, "192.168.0.1:5000"); - EXPECT_TRUE(request.type() == Common::Redis::RespType::Array); - EXPECT_EQ(request.asArray().size(), 2); - EXPECT_TRUE(request.asArray()[0].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[0].asString(), GetParam()); - EXPECT_TRUE(request.asArray()[1].type() == Common::Redis::RespType::BulkString); - EXPECT_EQ(request.asArray()[1].asString(), std::to_string(i)); - EXPECT_NE(&pool_requests_[i], nullptr); - return &pool_requests_[i]; - })); - EXPECT_TRUE(pool_callbacks_[i]->onRedirection(ask_response)); - } - - Common::Redis::RespValue expected_response; - expected_response.type(Common::Redis::RespType::Integer); - expected_response.asInteger() = 2; - - Common::Redis::RespValuePtr response2(new Common::Redis::RespValue()); - response2->type(Common::Redis::RespType::Integer); - response2->asInteger() = 1; - pool_callbacks_[1]->onResponse(std::move(response2)); - - Common::Redis::RespValuePtr response1(new Common::Redis::RespValue()); - response1->type(Common::Redis::RespType::Integer); - response1->asInteger() = 1; - time_system_.setMonotonicTime(std::chrono::milliseconds(10)); - EXPECT_CALL( - store_, - deliverHistogramToSinks( - Property(&Stats::Metric::name, "redis.foo.command." + GetParam() + ".latency"), 10)); - EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&expected_response))); - pool_callbacks_[0]->onResponse(std::move(response1)); - - EXPECT_EQ(1UL, store_.counter("redis.foo.command." + GetParam() + ".total").value()); - EXPECT_EQ(1UL, store_.counter("redis.foo.command." + GetParam() + ".success").value()); -}; - INSTANTIATE_TEST_SUITE_P( RedisSplitKeysSumResultHandlerTest, RedisSplitKeysSumResultHandlerTest, testing::ValuesIn(Common::Redis::SupportedCommands::hashMultipleSumResultCommands())); @@ -1486,7 +855,7 @@ class RedisSingleServerRequestWithLatencyMicrosTest : public RedisSingleServerRe public: void makeRequest(const std::string& hash_key, Common::Redis::RespValuePtr&& request) { EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); - EXPECT_CALL(*conn_pool_, makeRequest(hash_key, Ref(*request), _)) + EXPECT_CALL(*conn_pool_, makeRequest(hash_key, Pointee(Ref(*request)), _)) .WillOnce(DoAll(WithArg<2>(SaveArgAddress(&pool_callbacks_)), Return(&pool_request_))); handle_ = splitter_.makeRequest(std::move(request), callbacks_); } diff --git a/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc b/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc index 262e8d9bcd81..226d40b71d18 100644 --- a/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc @@ -9,6 +9,7 @@ #include "test/extensions/clusters/redis/mocks.h" #include "test/extensions/filters/network/common/redis/mocks.h" #include "test/extensions/filters/network/common/redis/test_utils.h" +#include "test/extensions/filters/network/redis_proxy/mocks.h" #include "test/mocks/api/mocks.h" #include "test/mocks/thread_local/mocks.h" @@ -93,16 +94,54 @@ class RedisConnPoolImplTest : public testing::Test, public Common::Redis::Client client_ = new NiceMock(); EXPECT_CALL(*this, create_(_)).WillOnce(Return(client_)); } - Common::Redis::RespValue value; - Common::Redis::Client::MockPoolCallbacks callbacks; + Common::Redis::RespValueSharedPtr value = std::make_shared(); + MockPoolCallbacks callbacks; + std::list client_callbacks; Common::Redis::Client::MockPoolRequest active_request; EXPECT_CALL(*cm_.thread_local_cluster_.lb_.host_, address()) .WillRepeatedly(Return(test_address_)); - EXPECT_CALL(*client_, makeRequest(Ref(value), Ref(callbacks))) - .WillOnce(Return(&active_request)); + EXPECT_CALL(*client_, makeRequest_(Ref(*value), _)) + .WillOnce(Invoke( + [&](const Common::Redis::RespValue&, Common::Redis::Client::ClientCallbacks& callbacks) + -> Common::Redis::Client::PoolRequest* { + client_callbacks.push_back(&callbacks); + return &active_request; + })); Common::Redis::Client::PoolRequest* request = conn_pool_->makeRequest(hash_key, value, callbacks); - EXPECT_EQ(&active_request, request); + EXPECT_NE(nullptr, request); + EXPECT_NE(nullptr, client_callbacks.back()); + + EXPECT_CALL(active_request, cancel()); + request->cancel(); + // Common::Redis::RespValuePtr resp_value = std::make_unique(); + // resp_value->type(Common::Redis::RespType::BulkString); + // resp_value->asString() = "bar"; + // EXPECT_CALL(callbacks, onResponse_(_)); + // client_callbacks.back((->onResponse(std::move(resp_value)); + } + + void makeRequest(Common::Redis::Client::MockClient* client, + Common::Redis::RespValueSharedPtr& value, MockPoolCallbacks& callbacks, + Common::Redis::Client::MockPoolRequest& active_request, + bool create_client = true) { + EXPECT_CALL(cm_.thread_local_cluster_.lb_, chooseHost(_)) + .WillOnce( + Invoke([&](Upstream::LoadBalancerContext* context) -> Upstream::HostConstSharedPtr { + EXPECT_EQ(context->computeHashKey().value(), MurmurHash::murmurHash2_64("hash_key")); + EXPECT_EQ(context->metadataMatchCriteria(), nullptr); + EXPECT_EQ(context->downstreamConnection(), nullptr); + return this->cm_.thread_local_cluster_.lb_.host_; + })); + if (create_client) { + EXPECT_CALL(*this, create_(_)).WillOnce(Return(client)); + } + EXPECT_CALL(*cm_.thread_local_cluster_.lb_.host_, address()) + .WillRepeatedly(Return(this->test_address_)); + EXPECT_CALL(*client, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request)); + Common::Redis::Client::PoolRequest* request = + this->conn_pool_->makeRequest("hash_key", value, callbacks); + EXPECT_NE(nullptr, request); } std::unordered_map& @@ -170,9 +209,9 @@ class RedisConnPoolImplTest : public testing::Test, public Common::Redis::Client read_policy_ = read_policy; setup(); - Common::Redis::RespValue value; + Common::Redis::RespValueSharedPtr value = std::make_shared(); Common::Redis::Client::MockPoolRequest auth_request, active_request, readonly_request; - Common::Redis::Client::MockPoolCallbacks callbacks; + MockPoolCallbacks callbacks; Common::Redis::Client::MockClient* client = new NiceMock(); EXPECT_CALL(cm_.thread_local_cluster_.lb_, chooseHost(_)) @@ -189,21 +228,30 @@ class RedisConnPoolImplTest : public testing::Test, public Common::Redis::Client EXPECT_CALL(*this, create_(_)).WillOnce(Return(client)); EXPECT_CALL(*cm_.thread_local_cluster_.lb_.host_, address()) .WillRepeatedly(Return(test_address_)); - EXPECT_CALL(*client, makeRequest(Ref(value), Ref(callbacks))).WillOnce(Return(&active_request)); + EXPECT_CALL(*client, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request)); Common::Redis::Client::PoolRequest* request = conn_pool_->makeRequest("hash_key", value, callbacks); - EXPECT_EQ(&active_request, request); + EXPECT_NE(nullptr, request); + EXPECT_CALL(active_request, cancel()); + EXPECT_CALL(callbacks, onFailure_()); EXPECT_CALL(*client, close()); tls_.shutdownThread(); } + void respond(MockPoolCallbacks& callbacks, Common::Redis::Client::MockClient* client) { + EXPECT_CALL(callbacks, onResponse_(_)); + client->client_callbacks_.back()->onResponse(std::make_unique()); + EXPECT_EQ(0, + conn_pool_->tls_->getTyped().pending_requests_.size()); + } + MOCK_METHOD1(create_, Common::Redis::Client::Client*(Upstream::HostConstSharedPtr host)); const std::string cluster_name_{"fake_cluster"}; NiceMock cm_; NiceMock tls_; - InstanceSharedPtr conn_pool_; + std::shared_ptr conn_pool_; Upstream::ClusterUpdateCallbacks* update_callbacks_{}; Common::Redis::Client::MockClient* client_{}; Network::Address::InstanceConstSharedPtr test_address_; @@ -221,9 +269,9 @@ TEST_F(RedisConnPoolImplTest, Basic) { setup(); - Common::Redis::RespValue value; + Common::Redis::RespValueSharedPtr value = std::make_shared(); Common::Redis::Client::MockPoolRequest active_request; - Common::Redis::Client::MockPoolCallbacks callbacks; + MockPoolCallbacks callbacks; Common::Redis::Client::MockClient* client = new NiceMock(); EXPECT_CALL(cm_.thread_local_cluster_.lb_, chooseHost(_)) @@ -236,11 +284,13 @@ TEST_F(RedisConnPoolImplTest, Basic) { EXPECT_CALL(*this, create_(_)).WillOnce(Return(client)); EXPECT_CALL(*cm_.thread_local_cluster_.lb_.host_, address()) .WillRepeatedly(Return(test_address_)); - EXPECT_CALL(*client, makeRequest(Ref(value), Ref(callbacks))).WillOnce(Return(&active_request)); + EXPECT_CALL(*client, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request)); Common::Redis::Client::PoolRequest* request = conn_pool_->makeRequest("hash_key", value, callbacks); - EXPECT_EQ(&active_request, request); + EXPECT_NE(nullptr, request); + EXPECT_CALL(active_request, cancel()); + EXPECT_CALL(callbacks, onFailure_()); EXPECT_CALL(*client, close()); tls_.shutdownThread(); }; @@ -265,8 +315,8 @@ TEST_F(RedisConnPoolImplTest, Hashtagging) { setup(); - Common::Redis::RespValue value; - Common::Redis::Client::MockPoolCallbacks callbacks; + Common::Redis::RespValueSharedPtr value = std::make_shared(); + MockPoolCallbacks callbacks; auto expectHashKey = [](const std::string& s) { return [s](Upstream::LoadBalancerContext* context) -> Upstream::HostConstSharedPtr { @@ -296,8 +346,8 @@ TEST_F(RedisConnPoolImplTest, HashtaggingNotEnabled) { setup(true, false); // Test with hashtagging not enabled. - Common::Redis::RespValue value; - Common::Redis::Client::MockPoolCallbacks callbacks; + Common::Redis::RespValueSharedPtr value = std::make_shared(); + MockPoolCallbacks callbacks; auto expectHashKey = [](const std::string& s) { return [s](Upstream::LoadBalancerContext* context) -> Upstream::HostConstSharedPtr { @@ -332,8 +382,8 @@ TEST_F(RedisConnPoolImplTest, NoClusterAtConstruction) { setup(false); - Common::Redis::RespValue value; - Common::Redis::Client::MockPoolCallbacks callbacks; + Common::Redis::RespValueSharedPtr value = std::make_shared(); + MockPoolCallbacks callbacks; Common::Redis::Client::PoolRequest* request = conn_pool_->makeRequest("hash_key", value, callbacks); EXPECT_EQ(nullptr, request); @@ -379,12 +429,10 @@ TEST_F(RedisConnPoolImplTest, NoClusterAtConstruction) { // This test removes a single host from the ConnPool after learning about 2 hosts from the // associated load balancer. TEST_F(RedisConnPoolImplTest, HostRemove) { - InSequence s; - setup(); - Common::Redis::Client::MockPoolCallbacks callbacks; - Common::Redis::RespValue value; + MockPoolCallbacks callbacks; + Common::Redis::RespValueSharedPtr value = std::make_shared(); std::shared_ptr host1(new Upstream::MockHost()); std::shared_ptr host2(new Upstream::MockHost()); Common::Redis::Client::MockClient* client1 = new NiceMock(); @@ -395,25 +443,28 @@ TEST_F(RedisConnPoolImplTest, HostRemove) { Common::Redis::Client::MockPoolRequest active_request1; EXPECT_CALL(*host1, address()).WillRepeatedly(Return(test_address_)); - EXPECT_CALL(*client1, makeRequest(Ref(value), Ref(callbacks))).WillOnce(Return(&active_request1)); + EXPECT_CALL(*client1, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request1)); Common::Redis::Client::PoolRequest* request1 = conn_pool_->makeRequest("hash_key", value, callbacks); - EXPECT_EQ(&active_request1, request1); + EXPECT_NE(nullptr, request1); EXPECT_CALL(cm_.thread_local_cluster_.lb_, chooseHost(_)).WillOnce(Return(host2)); EXPECT_CALL(*this, create_(Eq(host2))).WillOnce(Return(client2)); Common::Redis::Client::MockPoolRequest active_request2; EXPECT_CALL(*host2, address()).WillRepeatedly(Return(test_address_)); - EXPECT_CALL(*client2, makeRequest(Ref(value), Ref(callbacks))).WillOnce(Return(&active_request2)); + EXPECT_CALL(*client2, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request2)); Common::Redis::Client::PoolRequest* request2 = conn_pool_->makeRequest("bar", value, callbacks); - EXPECT_EQ(&active_request2, request2); + EXPECT_NE(nullptr, request2); EXPECT_CALL(*client2, close()); EXPECT_CALL(*host2, address()).WillRepeatedly(Return(test_address_)); cm_.thread_local_cluster_.cluster_.prioritySet().getMockHostSet(0)->runCallbacks({}, {host2}); + EXPECT_CALL(active_request1, cancel()); + EXPECT_CALL(active_request2, cancel()); EXPECT_CALL(*client1, close()); + EXPECT_CALL(callbacks, onFailure_()).Times(2); tls_.shutdownThread(); ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(host1.get())); @@ -452,8 +503,8 @@ TEST_F(RedisConnPoolImplTest, NoHost) { setup(); - Common::Redis::RespValue value; - Common::Redis::Client::MockPoolCallbacks callbacks; + Common::Redis::RespValueSharedPtr value = std::make_shared(); + MockPoolCallbacks callbacks; EXPECT_CALL(cm_.thread_local_cluster_.lb_, chooseHost(_)).WillOnce(Return(nullptr)); Common::Redis::Client::PoolRequest* request = conn_pool_->makeRequest("hash_key", value, callbacks); @@ -467,16 +518,16 @@ TEST_F(RedisConnPoolImplTest, RemoteClose) { setup(); - Common::Redis::RespValue value; + Common::Redis::RespValueSharedPtr value = std::make_shared(); Common::Redis::Client::MockPoolRequest active_request; - Common::Redis::Client::MockPoolCallbacks callbacks; + MockPoolCallbacks callbacks; Common::Redis::Client::MockClient* client = new NiceMock(); EXPECT_CALL(cm_.thread_local_cluster_.lb_, chooseHost(_)); EXPECT_CALL(*this, create_(_)).WillOnce(Return(client)); EXPECT_CALL(*cm_.thread_local_cluster_.lb_.host_, address()) .WillRepeatedly(Return(test_address_)); - EXPECT_CALL(*client, makeRequest(Ref(value), Ref(callbacks))).WillOnce(Return(&active_request)); + EXPECT_CALL(*client, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request)); conn_pool_->makeRequest("hash_key", value, callbacks); EXPECT_CALL(tls_.dispatcher_, deferredDelete_(_)); @@ -484,6 +535,8 @@ TEST_F(RedisConnPoolImplTest, RemoteClose) { client->runLowWatermarkCallbacks(); client->raiseEvent(Network::ConnectionEvent::RemoteClose); + EXPECT_CALL(active_request, cancel()); + EXPECT_CALL(callbacks, onFailure_()); tls_.shutdownThread(); } @@ -492,11 +545,11 @@ TEST_F(RedisConnPoolImplTest, MakeRequestToHost) { setup(false); - Common::Redis::RespValue value; + Common::Redis::RespValueSharedPtr value = std::make_shared(); Common::Redis::Client::MockPoolRequest active_request1; Common::Redis::Client::MockPoolRequest active_request2; - Common::Redis::Client::MockPoolCallbacks callbacks1; - Common::Redis::Client::MockPoolCallbacks callbacks2; + MockPoolCallbacks callbacks1; + MockPoolCallbacks callbacks2; Common::Redis::Client::MockClient* client1 = new NiceMock(); Common::Redis::Client::MockClient* client2 = new NiceMock(); Upstream::HostConstSharedPtr host1; @@ -508,24 +561,28 @@ TEST_F(RedisConnPoolImplTest, MakeRequestToHost) { update_callbacks_->onClusterAddOrUpdate(cm_.thread_local_cluster_); EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host1), Return(client1))); - EXPECT_CALL(*client1, makeRequest(Ref(value), Ref(callbacks1))) - .WillOnce(Return(&active_request1)); + EXPECT_CALL(*client1, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request1)); Common::Redis::Client::PoolRequest* request1 = conn_pool_->makeRequestToHost("10.0.0.1:3000", value, callbacks1); - EXPECT_EQ(&active_request1, request1); + EXPECT_NE(nullptr, request1); EXPECT_EQ(host1->address()->asString(), "10.0.0.1:3000"); + EXPECT_CALL(callbacks1, onResponse_(_)); + client1->client_callbacks_.back()->onResponse(std::make_unique()); + // IPv6 address returned from Redis server will not have square brackets // around it, while Envoy represents Address::Ipv6Instance addresses with square brackets around // the address. EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host2), Return(client2))); - EXPECT_CALL(*client2, makeRequest(Ref(value), Ref(callbacks2))) - .WillOnce(Return(&active_request2)); + EXPECT_CALL(*client2, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request2)); Common::Redis::Client::PoolRequest* request2 = conn_pool_->makeRequestToHost("2001:470:813B:0:0:0:0:1:3333", value, callbacks2); - EXPECT_EQ(&active_request2, request2); + EXPECT_NE(nullptr, request2); EXPECT_EQ(host2->address()->asString(), "[2001:470:813b::1]:3333"); + EXPECT_CALL(callbacks2, onResponse_(_)); + client2->client_callbacks_.back()->onResponse(std::make_unique()); + // Test with a badly specified host address (no colon, no address, no port). EXPECT_EQ(conn_pool_->makeRequestToHost("bad", value, callbacks1), nullptr); // Test with a badly specified IPv4 address. @@ -553,8 +610,8 @@ TEST_F(RedisConnPoolImplTest, MakeRequestToHostWithZeroMaxUnknownUpstreamConnect // Create a ConnPool with a max_upstream_unknown_connections setting of 0. setup(true, true, 0); - Common::Redis::RespValue value; - Common::Redis::Client::MockPoolCallbacks callbacks1; + Common::Redis::RespValueSharedPtr value = std::make_shared(); + MockPoolCallbacks callbacks1; // The max_unknown_upstream_connections is set to 0. Request should fail. EXPECT_EQ(nullptr, conn_pool_->makeRequestToHost("10.0.0.1:3000", value, callbacks1)); @@ -569,33 +626,31 @@ TEST_F(RedisConnPoolImplTest, MakeRequestToHostWithZeroMaxUnknownUpstreamConnect TEST_F(RedisConnPoolImplTest, HostsAddedAndRemovedWithDraining) { setup(); - Common::Redis::RespValue value; + Common::Redis::RespValueSharedPtr value = std::make_shared(); Common::Redis::Client::MockPoolRequest auth_request1, active_request1; Common::Redis::Client::MockPoolRequest auth_request2, active_request2; - Common::Redis::Client::MockPoolCallbacks callbacks1; - Common::Redis::Client::MockPoolCallbacks callbacks2; + MockPoolCallbacks callbacks1; + MockPoolCallbacks callbacks2; Common::Redis::Client::MockClient* client1 = new NiceMock(); Common::Redis::Client::MockClient* client2 = new NiceMock(); Upstream::HostConstSharedPtr host1; Upstream::HostConstSharedPtr host2; EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host1), Return(client1))); - EXPECT_CALL(*client1, makeRequest(Ref(value), Ref(callbacks1))) - .WillOnce(Return(&active_request1)); + EXPECT_CALL(*client1, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request1)); Common::Redis::Client::PoolRequest* request1 = conn_pool_->makeRequestToHost("10.0.0.1:3000", value, callbacks1); - EXPECT_EQ(&active_request1, request1); + EXPECT_NE(nullptr, request1); EXPECT_EQ(host1->address()->asString(), "10.0.0.1:3000"); // IPv6 address returned from Redis server will not have square brackets // around it, while Envoy represents Address::Ipv6Instance addresses with square brackets around // the address. EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host2), Return(client2))); - EXPECT_CALL(*client2, makeRequest(Ref(value), Ref(callbacks2))) - .WillOnce(Return(&active_request2)); + EXPECT_CALL(*client2, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request2)); Common::Redis::Client::PoolRequest* request2 = conn_pool_->makeRequestToHost("2001:470:813B:0:0:0:0:1:3333", value, callbacks2); - EXPECT_EQ(&active_request2, request2); + EXPECT_NE(nullptr, request2); EXPECT_EQ(host2->address()->asString(), "[2001:470:813b::1]:3333"); std::unordered_map& host_address_map = @@ -658,6 +713,11 @@ TEST_F(RedisConnPoolImplTest, HostsAddedAndRemovedWithDraining) { EXPECT_EQ(drainTimer()->enabled(), false); EXPECT_EQ(upstreamCxDrained().value(), 1); + EXPECT_CALL(active_request1, cancel()); + EXPECT_CALL(active_request2, cancel()); + EXPECT_CALL(callbacks1, onFailure_()); + EXPECT_CALL(callbacks2, onFailure_()); + tls_.shutdownThread(); } @@ -668,35 +728,39 @@ TEST_F(RedisConnPoolImplTest, HostsAddedAndRemovedWithDraining) { TEST_F(RedisConnPoolImplTest, HostsAddedAndEndWithNoDraining) { setup(); - Common::Redis::RespValue value; + Common::Redis::RespValueSharedPtr value = std::make_shared(); Common::Redis::Client::MockPoolRequest auth_request1, active_request1; Common::Redis::Client::MockPoolRequest auth_request2, active_request2; - Common::Redis::Client::MockPoolCallbacks callbacks1; - Common::Redis::Client::MockPoolCallbacks callbacks2; + MockPoolCallbacks callbacks1; + MockPoolCallbacks callbacks2; Common::Redis::Client::MockClient* client1 = new NiceMock(); Common::Redis::Client::MockClient* client2 = new NiceMock(); Upstream::HostConstSharedPtr host1; Upstream::HostConstSharedPtr host2; EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host1), Return(client1))); - EXPECT_CALL(*client1, makeRequest(Ref(value), Ref(callbacks1))) - .WillOnce(Return(&active_request1)); + EXPECT_CALL(*client1, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request1)); Common::Redis::Client::PoolRequest* request1 = conn_pool_->makeRequestToHost("10.0.0.1:3000", value, callbacks1); - EXPECT_EQ(&active_request1, request1); + EXPECT_NE(nullptr, request1); EXPECT_EQ(host1->address()->asString(), "10.0.0.1:3000"); + EXPECT_CALL(callbacks1, onFailure_()); + client1->client_callbacks_.back()->onFailure(); + // IPv6 address returned from Redis server will not have square brackets // around it, while Envoy represents Address::Ipv6Instance addresses with square brackets around // the address. EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host2), Return(client2))); - EXPECT_CALL(*client2, makeRequest(Ref(value), Ref(callbacks2))) - .WillOnce(Return(&active_request2)); + EXPECT_CALL(*client2, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request2)); Common::Redis::Client::PoolRequest* request2 = conn_pool_->makeRequestToHost("2001:470:813B:0:0:0:0:1:3333", value, callbacks2); - EXPECT_EQ(&active_request2, request2); + EXPECT_NE(nullptr, request2); EXPECT_EQ(host2->address()->asString(), "[2001:470:813b::1]:3333"); + EXPECT_CALL(callbacks2, onFailure_()); + client2->client_callbacks_.back()->onFailure(); + std::unordered_map& host_address_map = hostAddressMap(); EXPECT_EQ(host_address_map.size(), 2); // host1 and host2 have been created. @@ -746,35 +810,43 @@ TEST_F(RedisConnPoolImplTest, HostsAddedAndEndWithNoDraining) { TEST_F(RedisConnPoolImplTest, HostsAddedAndEndWithClusterRemoval) { setup(); - Common::Redis::RespValue value; + Common::Redis::RespValueSharedPtr value = std::make_shared(); Common::Redis::Client::MockPoolRequest auth_request1, active_request1; Common::Redis::Client::MockPoolRequest auth_request2, active_request2; - Common::Redis::Client::MockPoolCallbacks callbacks1; - Common::Redis::Client::MockPoolCallbacks callbacks2; + MockPoolCallbacks callbacks1; + MockPoolCallbacks callbacks2; Common::Redis::Client::MockClient* client1 = new NiceMock(); Common::Redis::Client::MockClient* client2 = new NiceMock(); Upstream::HostConstSharedPtr host1; Upstream::HostConstSharedPtr host2; EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host1), Return(client1))); - EXPECT_CALL(*client1, makeRequest(Ref(value), Ref(callbacks1))) - .WillOnce(Return(&active_request1)); + EXPECT_CALL(*client1, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request1)); Common::Redis::Client::PoolRequest* request1 = conn_pool_->makeRequestToHost("10.0.0.1:3000", value, callbacks1); - EXPECT_EQ(&active_request1, request1); + Common::Redis::Client::ClientCallbacks* client_callbacks1 = client1->client_callbacks_.back(); + EXPECT_NE(nullptr, request1); + EXPECT_NE(nullptr, client_callbacks1); EXPECT_EQ(host1->address()->asString(), "10.0.0.1:3000"); + EXPECT_CALL(callbacks1, onFailure_()); + client_callbacks1->onFailure(); + // IPv6 address returned from Redis server will not have square brackets // around it, while Envoy represents Address::Ipv6Instance addresses with square brackets around // the address. EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host2), Return(client2))); - EXPECT_CALL(*client2, makeRequest(Ref(value), Ref(callbacks2))) - .WillOnce(Return(&active_request2)); + EXPECT_CALL(*client2, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request2)); Common::Redis::Client::PoolRequest* request2 = conn_pool_->makeRequestToHost("2001:470:813B:0:0:0:0:1:3333", value, callbacks2); - EXPECT_EQ(&active_request2, request2); + Common::Redis::Client::ClientCallbacks* client_callbacks2 = client2->client_callbacks_.back(); + EXPECT_NE(nullptr, request2); + EXPECT_NE(nullptr, client_callbacks2); EXPECT_EQ(host2->address()->asString(), "[2001:470:813b::1]:3333"); + EXPECT_CALL(callbacks2, onFailure_()); + client_callbacks2->onFailure(); + std::unordered_map& host_address_map = hostAddressMap(); EXPECT_EQ(host_address_map.size(), 2); // host1 and host2 have been created. @@ -862,6 +934,200 @@ TEST_F(RedisConnPoolImplTest, MakeRequestToRedisClusterHashtag) { tls_.shutdownThread(); }; +TEST_F(RedisConnPoolImplTest, MovedRedirectionSuccess) { + InSequence s; + + setup(); + + Common::Redis::RespValueSharedPtr request_value = std::make_shared(); + Common::Redis::Client::MockPoolRequest active_request; + MockPoolCallbacks callbacks; + Common::Redis::Client::MockClient* client = new NiceMock(); + makeRequest(client, request_value, callbacks, active_request); + + Common::Redis::Client::MockPoolRequest active_request2; + Common::Redis::Client::MockClient* client2 = new NiceMock(); + Upstream::HostConstSharedPtr host1; + + Common::Redis::RespValuePtr moved_response{new Common::Redis::RespValue()}; + moved_response->type(Common::Redis::RespType::Error); + moved_response->asString() = "MOVED 1111 10.1.2.3:4000"; + + EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host1), Return(client2))); + EXPECT_CALL(*client2, makeRequest_(Ref(*request_value), _)).WillOnce(Return(&active_request2)); + EXPECT_TRUE(client->client_callbacks_.back()->onRedirection(std::move(moved_response))); + EXPECT_EQ(host1->address()->asString(), "10.1.2.3:4000"); + + respond(callbacks, client2); + + EXPECT_CALL(*client, close()); + tls_.shutdownThread(); +} + +TEST_F(RedisConnPoolImplTest, MovedRedirectionFailure) { + InSequence s; + + setup(); + + // Test a truncated MOVED error response that cannot be parsed properly. + Common::Redis::RespValueSharedPtr request_value = std::make_shared(); + Common::Redis::Client::MockPoolRequest active_request; + MockPoolCallbacks callbacks; + Common::Redis::Client::MockClient* client = new NiceMock(); + makeRequest(client, request_value, callbacks, active_request); + Common::Redis::RespValuePtr moved_response{new Common::Redis::RespValue()}; + moved_response->type(Common::Redis::RespType::Error); + moved_response->asString() = "MOVED 1111"; + EXPECT_CALL(callbacks, onResponse_(Ref(moved_response))); + EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(moved_response))); + + Common::Redis::RespValueSharedPtr request2 = std::make_shared(); + Common::Redis::Client::MockPoolRequest active_request2; + makeRequest(client, request2, callbacks, active_request2, false); + Common::Redis::RespValuePtr moved_response2{new Common::Redis::RespValue()}; + moved_response2->type(Common::Redis::RespType::Integer); + moved_response2->asInteger() = 1; + + EXPECT_CALL(callbacks, onResponse_(Ref(moved_response2))); + EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(moved_response2))); + + // Test an upstream error preventing the request from being sent. + Common::Redis::RespValueSharedPtr request3 = std::make_shared(); + Common::Redis::Client::MockPoolRequest active_request3; + Common::Redis::Client::MockClient* client2 = new NiceMock(); + Upstream::HostConstSharedPtr host1; + makeRequest(client, request3, callbacks, active_request3, false); + Common::Redis::RespValuePtr moved_response3{new Common::Redis::RespValue()}; + moved_response3->type(Common::Redis::RespType::Error); + moved_response3->asString() = "MOVED 1111 10.1.2.3:4000"; + EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host1), Return(client2))); + EXPECT_CALL(*client2, makeRequest_(Ref(*request3), _)).WillOnce(Return(nullptr)); + EXPECT_CALL(callbacks, onResponse_(Ref(moved_response3))); + EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(moved_response3))); + EXPECT_EQ(host1->address()->asString(), "10.1.2.3:4000"); + + EXPECT_CALL(*client, close()); + tls_.shutdownThread(); +} + +TEST_F(RedisConnPoolImplTest, RedirectionFailure) { + InSequence s; + + setup(); + + Common::Redis::RespValueSharedPtr request = std::make_shared(); + Common::Redis::Client::MockPoolRequest active_request; + MockPoolCallbacks callbacks; + Common::Redis::Client::MockClient* client = new NiceMock(); + + makeRequest(client, request, callbacks, active_request); + + // Test an error that looks like it might be a MOVED or ASK redirection error except for the first + // non-whitespace substring. + Common::Redis::RespValuePtr moved_response{new Common::Redis::RespValue()}; + moved_response->type(Common::Redis::RespType::Error); + moved_response->asString() = "NOTMOVEDORASK 1111 1.1.1.1:1"; + EXPECT_CALL(callbacks, onResponse_(Ref(moved_response))); + EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(moved_response))); + + EXPECT_CALL(*client, close()); + tls_.shutdownThread(); +} + +TEST_F(RedisConnPoolImplTest, AskRedirectionSuccess) { + InSequence s; + + setup(); + + Common::Redis::RespValueSharedPtr request_value = std::make_shared(); + Common::Redis::Client::MockPoolRequest active_request; + MockPoolCallbacks callbacks; + Common::Redis::Client::MockClient* client = new NiceMock(); + makeRequest(client, request_value, callbacks, active_request); + + Common::Redis::Client::MockPoolRequest ask_request, active_request2; + Common::Redis::Client::MockClient* client2 = new NiceMock(); + Upstream::HostConstSharedPtr host1; + + Common::Redis::RespValuePtr ask_response{new Common::Redis::RespValue()}; + ask_response->type(Common::Redis::RespType::Error); + ask_response->asString() = "ASK 1111 10.1.2.3:4000"; + EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host1), Return(client2))); + // Verify that the request has been properly prepended with an "asking" command. + EXPECT_CALL(*client2, makeRequest_(Ref(Common::Redis::Utility::AskingRequest::instance()), _)) + .WillOnce(Return(&ask_request)); + EXPECT_CALL(*client2, makeRequest_(Ref(*request_value), _)).WillOnce(Return(&active_request2)); + EXPECT_TRUE(client->client_callbacks_.back()->onRedirection(std::move(ask_response))); + EXPECT_EQ(host1->address()->asString(), "10.1.2.3:4000"); + + respond(callbacks, client2); + + EXPECT_CALL(*client, close()); + tls_.shutdownThread(); +} + +TEST_F(RedisConnPoolImplTest, AskRedirectionFailure) { + InSequence s; + + setup(); + + Common::Redis::RespValueSharedPtr request_value = std::make_shared(); + Common::Redis::Client::MockPoolRequest active_request; + MockPoolCallbacks callbacks; + Common::Redis::Client::MockClient* client = new NiceMock(); + makeRequest(client, request_value, callbacks, active_request); + + // Test a truncated ASK error response that cannot be parsed properly. + Common::Redis::Client::MockPoolRequest ask_request; + Common::Redis::RespValuePtr ask_response{new Common::Redis::RespValue()}; + ask_response->type(Common::Redis::RespType::Error); + ask_response->asString() = "ASK 1111"; + EXPECT_CALL(callbacks, onResponse_(Ref(ask_response))); + EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(ask_response))); + + Common::Redis::Client::MockPoolRequest active_request2; + Common::Redis::RespValueSharedPtr request2 = std::make_shared(); + makeRequest(client, request2, callbacks, active_request2, false); + Common::Redis::RespValuePtr ask_response2{new Common::Redis::RespValue()}; + ask_response2->type(Common::Redis::RespType::Integer); + ask_response2->asInteger() = 1; + EXPECT_CALL(callbacks, onResponse_(Ref(ask_response2))); + EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(ask_response2))); + + // Test an upstream error from trying to send an "asking" command upstream. + Common::Redis::Client::MockPoolRequest active_request3; + Common::Redis::RespValueSharedPtr request3 = std::make_shared(); + Common::Redis::Client::MockClient* client2 = new NiceMock(); + Upstream::HostConstSharedPtr host1; + makeRequest(client, request3, callbacks, active_request3, false); + Common::Redis::RespValuePtr ask_response3{new Common::Redis::RespValue()}; + ask_response3->type(Common::Redis::RespType::Error); + ask_response3->asString() = "ASK 1111 10.1.2.3:4000"; + EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host1), Return(client2))); + EXPECT_CALL(*client2, makeRequest_(Ref(Common::Redis::Utility::AskingRequest::instance()), _)) + .WillOnce(Return(nullptr)); + EXPECT_CALL(callbacks, onResponse_(Ref(ask_response3))); + EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(ask_response3))); + EXPECT_EQ(host1->address()->asString(), "10.1.2.3:4000"); + + // Test an upstream error from trying to send the original request after the "asking" command is + // sent successfully. + Common::Redis::Client::MockPoolRequest active_request4, active_request5; + Common::Redis::RespValueSharedPtr request4 = std::make_shared(); + makeRequest(client, request4, callbacks, active_request4, false); + Common::Redis::RespValuePtr ask_response4{new Common::Redis::RespValue()}; + ask_response4->type(Common::Redis::RespType::Error); + ask_response4->asString() = "ASK 1111 10.1.2.3:4000"; + EXPECT_CALL(*client2, makeRequest_(Ref(Common::Redis::Utility::AskingRequest::instance()), _)) + .WillOnce(Return(&active_request5)); + EXPECT_CALL(*client2, makeRequest_(Ref(*request4), _)).WillOnce(Return(nullptr)); + EXPECT_CALL(callbacks, onResponse_(Ref(ask_response4))); + EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(ask_response4))); + + EXPECT_CALL(*client, close()); + tls_.shutdownThread(); +} + } // namespace ConnPool } // namespace RedisProxy } // namespace NetworkFilters diff --git a/test/extensions/filters/network/redis_proxy/mocks.cc b/test/extensions/filters/network/redis_proxy/mocks.cc index 9c1457258377..1668e25aa3ca 100644 --- a/test/extensions/filters/network/redis_proxy/mocks.cc +++ b/test/extensions/filters/network/redis_proxy/mocks.cc @@ -19,6 +19,9 @@ MockRoute::~MockRoute() = default; namespace ConnPool { +MockPoolCallbacks::MockPoolCallbacks() = default; +MockPoolCallbacks::~MockPoolCallbacks() = default; + MockInstance::MockInstance() = default; MockInstance::~MockInstance() = default; diff --git a/test/extensions/filters/network/redis_proxy/mocks.h b/test/extensions/filters/network/redis_proxy/mocks.h index 7a169df1f767..b0c39eb97666 100644 --- a/test/extensions/filters/network/redis_proxy/mocks.h +++ b/test/extensions/filters/network/redis_proxy/mocks.h @@ -42,19 +42,31 @@ class MockRoute : public Route { namespace ConnPool { +class MockPoolCallbacks : public PoolCallbacks { +public: + MockPoolCallbacks(); + ~MockPoolCallbacks() override; + + void onResponse(Common::Redis::RespValuePtr&& value) override { onResponse_(value); } + void onFailure() override { onFailure_(); } + + MOCK_METHOD1(onResponse_, void(Common::Redis::RespValuePtr& value)); + MOCK_METHOD0(onFailure_, void()); +}; + class MockInstance : public Instance { public: MockInstance(); ~MockInstance() override; MOCK_METHOD3(makeRequest, - Common::Redis::Client::PoolRequest*( - const std::string& hash_key, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks& callbacks)); + Common::Redis::Client::PoolRequest*(const std::string& hash_key, + Common::Redis::RespValueSharedPtr request, + PoolCallbacks& callbacks)); MOCK_METHOD3(makeRequestToHost, - Common::Redis::Client::PoolRequest*( - const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::PoolCallbacks& callbacks)); + Common::Redis::Client::PoolRequest*(const std::string& host_address, + Common::Redis::RespValueSharedPtr request, + PoolCallbacks& callbacks)); }; } // namespace ConnPool diff --git a/test/extensions/health_checkers/redis/redis_test.cc b/test/extensions/health_checkers/redis/redis_test.cc index c55616220361..878b4c8ec4be 100644 --- a/test/extensions/health_checkers/redis/redis_test.cc +++ b/test/extensions/health_checkers/redis/redis_test.cc @@ -146,13 +146,13 @@ class RedisHealthCheckerTest } void expectExistsRequestCreate() { - EXPECT_CALL(*client_, makeRequest(Ref(RedisHealthChecker::existsHealthCheckRequest("")), _)) + EXPECT_CALL(*client_, makeRequest_(Ref(RedisHealthChecker::existsHealthCheckRequest("")), _)) .WillOnce(DoAll(WithArg<1>(SaveArgAddress(&pool_callbacks_)), Return(&pool_request_))); EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); } void expectPingRequestCreate() { - EXPECT_CALL(*client_, makeRequest(Ref(RedisHealthChecker::pingHealthCheckRequest()), _)) + EXPECT_CALL(*client_, makeRequest_(Ref(RedisHealthChecker::pingHealthCheckRequest()), _)) .WillOnce(DoAll(WithArg<1>(SaveArgAddress(&pool_callbacks_)), Return(&pool_request_))); EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); } @@ -183,7 +183,7 @@ class RedisHealthCheckerTest Event::MockTimer* interval_timer_{}; Extensions::NetworkFilters::Common::Redis::Client::MockClient* client_{}; Extensions::NetworkFilters::Common::Redis::Client::MockPoolRequest pool_request_; - Extensions::NetworkFilters::Common::Redis::Client::PoolCallbacks* pool_callbacks_{}; + Extensions::NetworkFilters::Common::Redis::Client::ClientCallbacks* pool_callbacks_{}; std::shared_ptr health_checker_; Api::ApiPtr api_; }; @@ -437,10 +437,11 @@ TEST_F(RedisHealthCheckerTest, ExistsRedirected) { // Success with moved redirection EXPECT_CALL(*timeout_timer_, disableTimer()); EXPECT_CALL(*interval_timer_, enableTimer(_, _)); - NetworkFilters::Common::Redis::RespValue moved_response; - moved_response.type(NetworkFilters::Common::Redis::RespType::Error); - moved_response.asString() = "MOVED 1111 127.0.0.1:81"; // exact values not important - pool_callbacks_->onRedirection(moved_response); + NetworkFilters::Common::Redis::RespValuePtr moved_response{ + new NetworkFilters::Common::Redis::RespValue()}; + moved_response->type(NetworkFilters::Common::Redis::RespType::Error); + moved_response->asString() = "MOVED 1111 127.0.0.1:81"; // exact values not important + pool_callbacks_->onRedirection(std::move(moved_response)); expectExistsRequestCreate(); interval_timer_->invokeCallback(); @@ -448,10 +449,11 @@ TEST_F(RedisHealthCheckerTest, ExistsRedirected) { // Success with ask redirection EXPECT_CALL(*timeout_timer_, disableTimer()); EXPECT_CALL(*interval_timer_, enableTimer(_, _)); - NetworkFilters::Common::Redis::RespValue ask_response; - ask_response.type(NetworkFilters::Common::Redis::RespType::Error); - ask_response.asString() = "ASK 1111 127.0.0.1:81"; // exact values not important - pool_callbacks_->onRedirection(ask_response); + NetworkFilters::Common::Redis::RespValuePtr ask_response{ + new NetworkFilters::Common::Redis::RespValue()}; + ask_response->type(NetworkFilters::Common::Redis::RespType::Error); + ask_response->asString() = "ASK 1111 127.0.0.1:81"; // exact values not important + pool_callbacks_->onRedirection(std::move(ask_response)); EXPECT_CALL(*client_, close()); From 36ebc52cf95ae22df518dd1e8133791467529e2b Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Mon, 14 Oct 2019 06:19:21 -0700 Subject: [PATCH 02/27] Fix format Signed-off-by: Henry Yang --- .../filters/network/common/redis/BUILD | 2 +- .../filters/network/common/redis/codec.h | 17 +++++++---- .../network/common/redis/codec_impl.cc | 30 +++++++++++-------- .../filters/network/common/redis/codec_impl.h | 3 +- .../redis_proxy/command_splitter_impl.cc | 4 +-- .../redis_proxy/command_split_speed_test.cc | 11 +++---- 6 files changed, 41 insertions(+), 26 deletions(-) diff --git a/source/extensions/filters/network/common/redis/BUILD b/source/extensions/filters/network/common/redis/BUILD index 2bd232d50516..24a410d6d8e3 100644 --- a/source/extensions/filters/network/common/redis/BUILD +++ b/source/extensions/filters/network/common/redis/BUILD @@ -12,8 +12,8 @@ envoy_cc_library( name = "codec_interface", hdrs = ["codec.h"], deps = [ - "//source/common/common:assert_lib", "//include/envoy/buffer:buffer_interface", + "//source/common/common:assert_lib", ], ) diff --git a/source/extensions/filters/network/common/redis/codec.h b/source/extensions/filters/network/common/redis/codec.h index 7f7b1ba29002..124f6de3dd5e 100644 --- a/source/extensions/filters/network/common/redis/codec.h +++ b/source/extensions/filters/network/common/redis/codec.h @@ -30,7 +30,8 @@ class RespValue { RespValue() : type_(RespType::Null) {} RespValue(std::shared_ptr base_array, const RespValue& command, const uint64_t start, - const uint64_t end) : type_(RespType::CompositeArray) { + const uint64_t end) + : type_(RespType::CompositeArray) { new (&composite_array_) CompositeArray(std::move(base_array), command, start, end); } virtual ~RespValue() { cleanup(); } @@ -50,8 +51,9 @@ class RespValue { class CompositeArray { public: CompositeArray() = default; - CompositeArray(std::shared_ptr base_array, const RespValue& command, const uint64_t start, - const uint64_t end) : base_array_(std::move(base_array)), command_(&command), start_(start), end_(end) { + CompositeArray(std::shared_ptr base_array, const RespValue& command, + const uint64_t start, const uint64_t end) + : base_array_(std::move(base_array)), command_(&command), start_(start), end_(end) { ASSERT(command.type() == RespType::BulkString || command.type() == RespType::SimpleString); ASSERT(base_array_ != nullptr); ASSERT(base_array_->type() == RespType::Array); @@ -88,12 +90,17 @@ class RespValue { }; CompositeArrayConstIterator begin() const noexcept { - return (command_ && base_array_) ? CompositeArrayConstIterator{command_, base_array_->asArray(), start_, true} : CompositeArrayConstIterator::empty(); + return (command_ && base_array_) + ? CompositeArrayConstIterator{command_, base_array_->asArray(), start_, true} + : CompositeArrayConstIterator::empty(); } CompositeArrayConstIterator end() const noexcept { - return (command_ && base_array_) ? CompositeArrayConstIterator{command_, base_array_->asArray(), end_+1, false} : CompositeArrayConstIterator::empty(); + return (command_ && base_array_) + ? CompositeArrayConstIterator{command_, base_array_->asArray(), end_ + 1, false} + : CompositeArrayConstIterator::empty(); } + private: std::shared_ptr base_array_; const RespValue* command_; diff --git a/source/extensions/filters/network/common/redis/codec_impl.cc b/source/extensions/filters/network/common/redis/codec_impl.cc index 643a38f601ed..2becb340abdf 100644 --- a/source/extensions/filters/network/common/redis/codec_impl.cc +++ b/source/extensions/filters/network/common/redis/codec_impl.cc @@ -289,17 +289,19 @@ bool RespValue::operator==(const RespValue& other) const { return result; } -uint32_t RespValue::CompositeArray::size() const { - return end_ - start_ + 2; -} +uint32_t RespValue::CompositeArray::size() const { return end_ - start_ + 2; } bool RespValue::CompositeArray::operator==(const RespValue::CompositeArray& other) const { - return base_array_ == other.base_array_ && command_ == other.command_ && start_ == other.start_ && end_ == other.end_; + return base_array_ == other.base_array_ && command_ == other.command_ && start_ == other.start_ && + end_ == other.end_; } -const RespValue& RespValue::CompositeArray::CompositeArrayConstIterator::operator*() { return first_ ? *command_ : array_[index_]; } +const RespValue& RespValue::CompositeArray::CompositeArrayConstIterator::operator*() { + return first_ ? *command_ : array_[index_]; +} -RespValue::CompositeArray::CompositeArrayConstIterator& RespValue::CompositeArray::CompositeArrayConstIterator::operator++() { +RespValue::CompositeArray::CompositeArrayConstIterator& +RespValue::CompositeArray::CompositeArrayConstIterator::operator++() { if (first_) { first_ = false; } else { @@ -308,13 +310,16 @@ RespValue::CompositeArray::CompositeArrayConstIterator& RespValue::CompositeArra return *this; } -bool RespValue::CompositeArray::CompositeArrayConstIterator::operator!=(const CompositeArrayConstIterator& rhs) const { - return command_ != (rhs.command_) || &array_ != &(rhs.array_) || index_ != rhs.index_ || first_ != rhs.first_; +bool RespValue::CompositeArray::CompositeArrayConstIterator:: +operator!=(const CompositeArrayConstIterator& rhs) const { + return command_ != (rhs.command_) || &array_ != &(rhs.array_) || index_ != rhs.index_ || + first_ != rhs.first_; } -const RespValue::CompositeArray::CompositeArrayConstIterator& RespValue::CompositeArray::CompositeArrayConstIterator::empty() { - static const RespValue::CompositeArray::CompositeArrayConstIterator* instance = new RespValue::CompositeArray::CompositeArrayConstIterator( - nullptr, {}, 0, false); +const RespValue::CompositeArray::CompositeArrayConstIterator& +RespValue::CompositeArray::CompositeArrayConstIterator::empty() { + static const RespValue::CompositeArray::CompositeArrayConstIterator* instance = + new RespValue::CompositeArray::CompositeArrayConstIterator(nullptr, {}, 0, false); return *instance; } @@ -587,7 +592,8 @@ void EncoderImpl::encodeArray(const std::vector& array, Buffer::Insta } } -void EncoderImpl::encodeCompositeArray(const RespValue::CompositeArray& composite_array, Buffer::Instance& out) { +void EncoderImpl::encodeCompositeArray(const RespValue::CompositeArray& composite_array, + Buffer::Instance& out) { char buffer[32]; char* current = buffer; *current++ = '*'; diff --git a/source/extensions/filters/network/common/redis/codec_impl.h b/source/extensions/filters/network/common/redis/codec_impl.h index 867be955e963..ed0ef671f81a 100644 --- a/source/extensions/filters/network/common/redis/codec_impl.h +++ b/source/extensions/filters/network/common/redis/codec_impl.h @@ -137,7 +137,8 @@ class EncoderImpl : public Encoder { void encode(const RespValue& value, Buffer::Instance& out) override; private: - void encodeArray(const std::vector& array, Buffer::Instance& out);; + void encodeArray(const std::vector& array, Buffer::Instance& out); + ; void encodeCompositeArray(const RespValue::CompositeArray& array, Buffer::Instance& out); void encodeBulkString(const std::string& string, Buffer::Instance& out); void encodeError(const std::string& string, Buffer::Instance& out); diff --git a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc index ac668d409e16..38a09e5ce185 100644 --- a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc +++ b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc @@ -311,8 +311,8 @@ SplitRequestPtr SplitKeysSumResultRequest::create(Router& router, request_ptr->pending_requests_.emplace_back(*request_ptr, i - 1); PendingRequest& pending_request = request_ptr->pending_requests_.back(); - auto single_fragment = std::make_shared( - base_request, base_request->asArray()[0], i, i); + auto single_fragment = + std::make_shared(base_request, base_request->asArray()[0], i, i); ENVOY_LOG(debug, "redis: parallel {}: '{}'", base_request->asArray()[0].asString(), single_fragment->toString()); const auto route = router.upstreamPool(base_request->asArray()[i].asString()); diff --git a/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc b/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc index 8486928940cd..c6e3fd756f11 100644 --- a/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc @@ -64,11 +64,13 @@ class CommandSplitSpeedTest { void move(Common::Redis::RespValueSharedPtr request) { for (uint64_t i = 1; i < request->asArray().size(); i += 2) { -// auto single_set = std::make_shared(); -// single_set->type(Common::Redis::RespType::CompositeArray); -// single_set->asCompositeArray().initialize(request, Common::Redis::Utility::SetRequest::instance(), i, i + 2); + // auto single_set = std::make_shared(); + // single_set->type(Common::Redis::RespType::CompositeArray); + // single_set->asCompositeArray().initialize(request, + // Common::Redis::Utility::SetRequest::instance(), i, i + 2); - auto single_set = std::make_shared(request, Common::Redis::Utility::SetRequest::instance(), i, i + 2); + auto single_set = std::make_shared( + request, Common::Redis::Utility::SetRequest::instance(), i, i + 2); } } @@ -87,7 +89,6 @@ class CommandSplitSpeedTest { single_mset.asArray()[2].asString() = request->asArray()[i + 1].asString(); } } - }; } // namespace RedisProxy } // namespace NetworkFilters From 882e42be8f03a838ad4f608e59356ecfdd6e5e09 Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Mon, 14 Oct 2019 14:21:33 -0700 Subject: [PATCH 03/27] Fix tests Signed-off-by: Henry Yang --- .../filters/network/common/redis/codec.h | 10 ++-- .../network/common/redis/codec_impl.cc | 6 +-- .../filters/network/common/redis/codec_impl.h | 50 ------------------- .../redis_proxy/command_splitter_impl.cc | 14 +++--- .../redis_proxy/command_splitter_impl_test.cc | 15 ++---- 5 files changed, 21 insertions(+), 74 deletions(-) diff --git a/source/extensions/filters/network/common/redis/codec.h b/source/extensions/filters/network/common/redis/codec.h index 124f6de3dd5e..d18d0c61045d 100644 --- a/source/extensions/filters/network/common/redis/codec.h +++ b/source/extensions/filters/network/common/redis/codec.h @@ -36,11 +36,11 @@ class RespValue { } virtual ~RespValue() { cleanup(); } - RespValue(const RespValue& other); // copy constructor - RespValue(RespValue&& other) noexcept; // move constructor - RespValue& operator=(const RespValue& other); // copy assignment - RespValue& operator=(RespValue&& other); // move assignment - bool operator==(const RespValue& other) const; // test for equality, unit tests + RespValue(const RespValue& other); // copy constructor + RespValue(RespValue&& other) noexcept; // move constructor + RespValue& operator=(const RespValue& other); // copy assignment + RespValue& operator=(RespValue&& other) noexcept; // move assignment + bool operator==(const RespValue& other) const; // test for equality, unit tests bool operator!=(const RespValue& other) const { return !(*this == other); } /** diff --git a/source/extensions/filters/network/common/redis/codec_impl.cc b/source/extensions/filters/network/common/redis/codec_impl.cc index 2becb340abdf..4ad095113cde 100644 --- a/source/extensions/filters/network/common/redis/codec_impl.cc +++ b/source/extensions/filters/network/common/redis/codec_impl.cc @@ -187,7 +187,7 @@ RespValue::RespValue(RespValue&& other) noexcept : type_(other.type_) { break; } case RespType::Integer: { - integer_ = std::move(other.integer_); + integer_ = other.integer_; break; } case RespType::Null: @@ -225,7 +225,7 @@ RespValue& RespValue::operator=(const RespValue& other) { return *this; } -RespValue& RespValue::operator=(RespValue&& other) { +RespValue& RespValue::operator=(RespValue&& other) noexcept { if (&other == this) { return *this; } @@ -247,7 +247,7 @@ RespValue& RespValue::operator=(RespValue&& other) { break; } case RespType::Integer: { - integer_ = std::move(other.integer_); + integer_ = other.integer_; break; } case RespType::Null: diff --git a/source/extensions/filters/network/common/redis/codec_impl.h b/source/extensions/filters/network/common/redis/codec_impl.h index ed0ef671f81a..257ec361172d 100644 --- a/source/extensions/filters/network/common/redis/codec_impl.h +++ b/source/extensions/filters/network/common/redis/codec_impl.h @@ -17,56 +17,6 @@ namespace NetworkFilters { namespace Common { namespace Redis { -class FragementRespValue : public RespValue { -public: - FragementRespValue(RespValueSharedPtr base, const RespValue& command, const uint64_t start, - const uint64_t end) - : base_(std::move(base)), command_(command), start_(start), end_(end) { - type(RespType::Array); - } - - ~FragementRespValue() override {} - - struct FragementRespValueConstIterator { - FragementRespValueConstIterator(const RespValue& command, const std::vector& array, - uint64_t index) - : command_(command), array_(array), index_(index), first_(true) {} - const RespValue& operator*() { return first_ ? command_ : array_[index_]; } - FragementRespValueConstIterator operator++() { - if (first_) { - first_ = false; - } else { - index_++; - } - return *this; - } - bool operator!=(const FragementRespValueConstIterator& rhs) const { - return &command_ != &(rhs.command_) || &array_ != &rhs.array_ || index_ != rhs.index_; - } - - const RespValue& command_; - const std::vector& array_; - uint64_t index_; - bool first_; - }; - - FragementRespValueConstIterator begin() const noexcept { - return {command_, base_->asArray(), start_}; - } - - FragementRespValueConstIterator end() const noexcept { - return {command_, base_->asArray(), end_}; - } - - uint64_t size() const { return end_ - start_ + 1; } - -private: - const RespValueSharedPtr base_; - const RespValue& command_; - const uint64_t start_; - const uint64_t end_; -}; - /** * Decoder implementation of https://redis.io/topics/protocol * diff --git a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc index 38a09e5ce185..c895db7a5af9 100644 --- a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc +++ b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc @@ -82,9 +82,10 @@ SplitRequestPtr SimpleRequest::create(Router& router, const auto route = router.upstreamPool(incoming_request->asArray()[1].asString()); if (route) { - request_ptr->handle_ = makeRequest(route, incoming_request->asArray()[0].asString(), - incoming_request->asArray()[1].asString(), - std::move(incoming_request), *request_ptr); + Common::Redis::RespValueSharedPtr base_request = std::move(incoming_request); + request_ptr->handle_ = + makeRequest(route, base_request->asArray()[0].asString(), + base_request->asArray()[1].asString(), base_request, *request_ptr); } if (!request_ptr->handle_) { @@ -111,9 +112,10 @@ SplitRequestPtr EvalRequest::create(Router& router, Common::Redis::RespValuePtr& const auto route = router.upstreamPool(incoming_request->asArray()[3].asString()); if (route) { - request_ptr->handle_ = makeRequest(route, incoming_request->asArray()[0].asString(), - incoming_request->asArray()[3].asString(), - std::move(incoming_request), *request_ptr); + Common::Redis::RespValueSharedPtr base_request = std::move(incoming_request); + request_ptr->handle_ = + makeRequest(route, base_request->asArray()[0].asString(), + base_request->asArray()[3].asString(), base_request, *request_ptr); } if (!request_ptr->handle_) { diff --git a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc index 21c5a0849b0a..3d6b2f8ecc30 100644 --- a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc @@ -16,12 +16,8 @@ #include "test/test_common/simulated_time_system.h" using testing::_; -using testing::ByRef; using testing::DoAll; -using testing::ElementsAreArray; -using testing::Eq; using testing::InSequence; -using testing::Invoke; using testing::NiceMock; using testing::Pointee; using testing::Property; @@ -394,11 +390,11 @@ TEST_F(RedisSingleServerRequestTest, EvalNoUpstream) { EXPECT_EQ(1UL, store_.counter("redis.foo.command.eval.error").value()); }; -MATCHER_P(CompositeArrayEq, rhs, "") { +MATCHER_P(CompositeArrayEq, rhs, "CompositeArray should be equal") { const Common::Redis::RespValueSharedPtr& obj = arg; EXPECT_TRUE(obj->type() == Common::Redis::RespType::CompositeArray); EXPECT_EQ(obj->asCompositeArray().size(), rhs.size()); - std::vector array(obj->asCompositeArray().size()); + std::vector array; for (auto const& entry : obj->asCompositeArray()) { array.emplace_back(entry.asString()); } @@ -417,8 +413,7 @@ class RedisMGETCommandHandlerTest : public RedisCommandSplitterImplTest { Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; makeBulkStringArray(*request, request_strings); - std::vector> tmp_expected_requests(num_gets); - expected_requests_.swap(tmp_expected_requests); + expected_requests_.reserve(num_gets); pool_callbacks_.resize(num_gets); std::vector tmp_pool_requests(num_gets); pool_requests_.swap(tmp_pool_requests); @@ -624,7 +619,7 @@ class RedisMSETCommandHandlerTest : public RedisCommandSplitterImplTest { Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; makeBulkStringArray(*request, request_strings); - expected_requests_.resize(num_sets); + expected_requests_.reserve(num_sets); pool_callbacks_.resize(num_sets); std::vector tmp_pool_requests(num_sets); pool_requests_.swap(tmp_pool_requests); @@ -751,7 +746,7 @@ class RedisSplitKeysSumResultHandlerTest : public RedisCommandSplitterImplTest, Common::Redis::RespValuePtr request(new Common::Redis::RespValue()); makeBulkStringArray(*request, request_strings); - expected_requests_.resize(num_commands); + expected_requests_.reserve(num_commands); pool_callbacks_.resize(num_commands); std::vector tmp_pool_requests(num_commands); pool_requests_.swap(tmp_pool_requests); From d151ae2a8c3672b726f3966e079af05168aadbb4 Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Mon, 14 Oct 2019 18:05:50 -0700 Subject: [PATCH 04/27] Fix move constructor Signed-off-by: Henry Yang --- source/extensions/filters/network/common/redis/codec_impl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/extensions/filters/network/common/redis/codec_impl.cc b/source/extensions/filters/network/common/redis/codec_impl.cc index 4ad095113cde..69ab5a2dd4e7 100644 --- a/source/extensions/filters/network/common/redis/codec_impl.cc +++ b/source/extensions/filters/network/common/redis/codec_impl.cc @@ -183,7 +183,7 @@ RespValue::RespValue(RespValue&& other) noexcept : type_(other.type_) { case RespType::SimpleString: case RespType::BulkString: case RespType::Error: { - string_ = std::move(other.string_); + new (&string_) std::string(std::move(other.string_)); break; } case RespType::Integer: { From 75367898cb10af94a683701e45df6bdf9007d7b3 Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Tue, 15 Oct 2019 15:53:02 -0700 Subject: [PATCH 05/27] Fix clang errors Signed-off-by: Henry Yang --- .../network/redis_proxy/conn_pool_impl.cc | 17 ----------------- .../network/redis_proxy/conn_pool_impl.h | 4 +--- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc index 0d655ef919fd..4fa8a677c46e 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc @@ -18,9 +18,6 @@ namespace ConnPool { namespace { // null_pool_callbacks is used for requests that must be filtered and not redirected such as // "asking". -DoNothingPoolCallbacks null_pool_callbacks; -// null_pool_callbacks is used for requests that must be filtered and not redirected such as -// "asking". Common::Redis::Client::DoNothingPoolCallbacks null_client_callbacks; } // namespace @@ -226,20 +223,6 @@ InstanceImpl::ThreadLocalPool::makeRequestInternal(const Upstream::HostConstShar } } -Common::Redis::Client::PoolRequest* -InstanceImpl::ThreadLocalPool::makeRequestInternal(const Upstream::HostConstSharedPtr& host, - PendingRequest& pending_request) { - ThreadLocalActiveClientPtr& client = this->threadLocalActiveClient(host); - pending_request.request_handler_ = - client->redis_client_->makeRequest(*(pending_request.incoming_request_), pending_request); - if (pending_request.request_handler_) { - return &pending_request; - } else { - onRequestCompleted(); - return nullptr; - } -} - Common::Redis::Client::PoolRequest* InstanceImpl::ThreadLocalPool::makeRequest( const std::string& key, Common::Redis::RespValueSharedPtr request, PoolCallbacks& callbacks) { if (cluster_ == nullptr) { diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h index 8b931f185904..216255a71c5e 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h @@ -92,7 +92,7 @@ class InstanceImpl : public Instance { public Common::Redis::Client::PoolRequest { PendingRequest(ThreadLocalPool& parent, Common::Redis::RespValueSharedPtr incoming_request, PoolCallbacks& pool_callbacks); - ~PendingRequest(); + ~PendingRequest() override; // Common::Redis::Client::ClientCallbacks void onResponse(Common::Redis::RespValuePtr&& response) override; @@ -125,8 +125,6 @@ class InstanceImpl : public Instance { makeRequestInternal(const Upstream::HostConstSharedPtr& host, Common::Redis::RespValueSharedPtr request, PoolCallbacks& callbacks); Common::Redis::Client::PoolRequest* - makeRequestInternal(const Upstream::HostConstSharedPtr& host, PendingRequest& pending_request); - Common::Redis::Client::PoolRequest* makeRequestToHostInternal(const std::string& host_address, const Common::Redis::RespValue& request, Common::Redis::Client::ClientCallbacks& callbacks); From a9cfd910150778040267a049cd12b5c3785e0393 Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Wed, 16 Oct 2019 20:54:28 -0700 Subject: [PATCH 06/27] Fix clang errors Signed-off-by: Henry Yang --- source/extensions/filters/network/common/redis/codec.h | 2 +- source/extensions/filters/network/common/redis/codec_impl.cc | 2 +- source/extensions/filters/network/common/redis/utility.cc | 1 + source/extensions/filters/network/common/redis/utility.h | 2 -- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/source/extensions/filters/network/common/redis/codec.h b/source/extensions/filters/network/common/redis/codec.h index d18d0c61045d..42f4f295f7f0 100644 --- a/source/extensions/filters/network/common/redis/codec.h +++ b/source/extensions/filters/network/common/redis/codec.h @@ -67,7 +67,7 @@ class RespValue { bool operator==(const CompositeArray& other) const; - uint32_t size() const; + uint64_t size() const; /** * Forward const iterator for CompositeArray. diff --git a/source/extensions/filters/network/common/redis/codec_impl.cc b/source/extensions/filters/network/common/redis/codec_impl.cc index 69ab5a2dd4e7..77904ae36007 100644 --- a/source/extensions/filters/network/common/redis/codec_impl.cc +++ b/source/extensions/filters/network/common/redis/codec_impl.cc @@ -289,7 +289,7 @@ bool RespValue::operator==(const RespValue& other) const { return result; } -uint32_t RespValue::CompositeArray::size() const { return end_ - start_ + 2; } +uint64_t RespValue::CompositeArray::size() const { return end_ - start_ + 2; } bool RespValue::CompositeArray::operator==(const RespValue::CompositeArray& other) const { return base_array_ == other.base_array_ && command_ == other.command_ && start_ == other.start_ && diff --git a/source/extensions/filters/network/common/redis/utility.cc b/source/extensions/filters/network/common/redis/utility.cc index ce31c69c8249..8c037282f2e1 100644 --- a/source/extensions/filters/network/common/redis/utility.cc +++ b/source/extensions/filters/network/common/redis/utility.cc @@ -1,4 +1,5 @@ #include "extensions/filters/network/common/redis/utility.h" +#include "common/common/utility.h" namespace Envoy { namespace Extensions { diff --git a/source/extensions/filters/network/common/redis/utility.h b/source/extensions/filters/network/common/redis/utility.h index 05ab70ad8799..b5ab184673f3 100644 --- a/source/extensions/filters/network/common/redis/utility.h +++ b/source/extensions/filters/network/common/redis/utility.h @@ -2,8 +2,6 @@ #include -#include "common/common/utility.h" - #include "extensions/filters/network/common/redis/codec.h" namespace Envoy { From 5793b6b89d4851df4f946c1069f8228d5553ec7c Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Wed, 16 Oct 2019 20:59:11 -0700 Subject: [PATCH 07/27] Format code Signed-off-by: Henry Yang --- source/extensions/filters/network/common/redis/utility.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/source/extensions/filters/network/common/redis/utility.cc b/source/extensions/filters/network/common/redis/utility.cc index 8c037282f2e1..189e40ed1449 100644 --- a/source/extensions/filters/network/common/redis/utility.cc +++ b/source/extensions/filters/network/common/redis/utility.cc @@ -1,4 +1,5 @@ #include "extensions/filters/network/common/redis/utility.h" + #include "common/common/utility.h" namespace Envoy { From fac3ffb21035bb0197b860e9516b3370fbc7ff2c Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Thu, 17 Oct 2019 05:14:28 -0700 Subject: [PATCH 08/27] Use absl::variant instead of shared_ptr to improve performance. Signed-off-by: Henry Yang --- .../network/common/redis/codec_impl.cc | 4 +- .../redis_proxy/command_splitter_impl.cc | 77 +++++++--- .../filters/network/redis_proxy/conn_pool.h | 25 ++- .../network/redis_proxy/conn_pool_impl.cc | 105 +++++-------- .../network/redis_proxy/conn_pool_impl.h | 42 ++--- .../network/common/redis/codec_impl_test.cc | 38 ++++- .../redis_proxy/command_split_speed_test.cc | 33 +++- .../redis_proxy/command_splitter_impl_test.cc | 31 ++-- .../redis_proxy/conn_pool_impl_test.cc | 145 ++++++++++-------- .../filters/network/redis_proxy/mocks.h | 18 +-- 10 files changed, 296 insertions(+), 222 deletions(-) diff --git a/source/extensions/filters/network/common/redis/codec_impl.cc b/source/extensions/filters/network/common/redis/codec_impl.cc index 77904ae36007..763680e8d0cb 100644 --- a/source/extensions/filters/network/common/redis/codec_impl.cc +++ b/source/extensions/filters/network/common/redis/codec_impl.cc @@ -289,7 +289,9 @@ bool RespValue::operator==(const RespValue& other) const { return result; } -uint64_t RespValue::CompositeArray::size() const { return end_ - start_ + 2; } +uint64_t RespValue::CompositeArray::size() const { + return (command_ && base_array_) ? end_ - start_ + 2 : 0; +} bool RespValue::CompositeArray::operator==(const RespValue::CompositeArray& other) const { return base_array_ == other.base_array_ && command_ == other.command_ && start_ == other.start_ && diff --git a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc index c895db7a5af9..41e6c7530526 100644 --- a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc +++ b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc @@ -23,15 +23,44 @@ ConnPool::DoNothingPoolCallbacks null_pool_callbacks; * @return PoolRequest* a handle to the active request or nullptr if the request could not be made * for some reason. */ -Common::Redis::Client::PoolRequest* makeRequest(const RouteSharedPtr& route, - const std::string& command, const std::string& key, - Common::Redis::RespValueSharedPtr incoming_request, - ConnPool::PoolCallbacks& callbacks) { - auto handler = route->upstream()->makeRequest(key, incoming_request, callbacks); +Common::Redis::Client::PoolRequest* +makeSingleServerRequest(const RouteSharedPtr& route, const std::string& command, + const std::string& key, Common::Redis::RespValueSharedPtr incoming_request, + ConnPool::PoolCallbacks& callbacks) { + auto handler = + route->upstream()->makeRequest(key, ConnPool::RespVariant(incoming_request), callbacks); if (handler) { for (auto& mirror_policy : route->mirrorPolicies()) { if (mirror_policy->shouldMirror(command)) { - mirror_policy->upstream()->makeRequest(key, incoming_request, null_pool_callbacks); + mirror_policy->upstream()->makeRequest(key, ConnPool::RespVariant(incoming_request), + null_pool_callbacks); + } + } + } + return handler; +} + +/** + * Make request and maybe mirror the request based on the mirror policies of the route. + * @param route supplies the route matched with the request. + * @param command supplies the command of the request. + * @param key supplies the key of the request. + * @param incoming_request supplies the request. + * @param callbacks supplies the request completion callbacks. + * @return PoolRequest* a handle to the active request or nullptr if the request could not be made + * for some reason. + */ +Common::Redis::Client::PoolRequest* +makeFragmentedRequest(const RouteSharedPtr& route, const std::string& command, + const std::string& key, const Common::Redis::RespValue& incoming_request, + ConnPool::PoolCallbacks& callbacks) { + auto handler = + route->upstream()->makeRequest(key, ConnPool::RespVariant(incoming_request), callbacks); + if (handler) { + for (auto& mirror_policy : route->mirrorPolicies()) { + if (mirror_policy->shouldMirror(command)) { + mirror_policy->upstream()->makeRequest(key, ConnPool::RespVariant(incoming_request), + null_pool_callbacks); } } } @@ -84,8 +113,8 @@ SplitRequestPtr SimpleRequest::create(Router& router, if (route) { Common::Redis::RespValueSharedPtr base_request = std::move(incoming_request); request_ptr->handle_ = - makeRequest(route, base_request->asArray()[0].asString(), - base_request->asArray()[1].asString(), base_request, *request_ptr); + makeSingleServerRequest(route, base_request->asArray()[0].asString(), + base_request->asArray()[1].asString(), base_request, *request_ptr); } if (!request_ptr->handle_) { @@ -114,8 +143,8 @@ SplitRequestPtr EvalRequest::create(Router& router, Common::Redis::RespValuePtr& if (route) { Common::Redis::RespValueSharedPtr base_request = std::move(incoming_request); request_ptr->handle_ = - makeRequest(route, base_request->asArray()[0].asString(), - base_request->asArray()[3].asString(), base_request, *request_ptr); + makeSingleServerRequest(route, base_request->asArray()[0].asString(), + base_request->asArray()[3].asString(), base_request, *request_ptr); } if (!request_ptr->handle_) { @@ -169,10 +198,10 @@ SplitRequestPtr MGETRequest::create(Router& router, Common::Redis::RespValuePtr& const auto route = router.upstreamPool(base_request->asArray()[i].asString()); if (route) { - auto single_get = std::make_shared( - base_request, Common::Redis::Utility::GetRequest::instance(), i, i); - pending_request.handle_ = makeRequest(route, "get", base_request->asArray()[i].asString(), - single_get, pending_request); + Common::Redis::RespValue single_get(base_request, + Common::Redis::Utility::GetRequest::instance(), i, i); + pending_request.handle_ = makeFragmentedRequest( + route, "get", base_request->asArray()[i].asString(), single_get, pending_request); } if (!pending_request.handle_) { @@ -247,10 +276,11 @@ SplitRequestPtr MSETRequest::create(Router& router, Common::Redis::RespValuePtr& // ENVOY_LOG(debug, "redis: parallel set: '{}'", single_set->toString()); const auto route = router.upstreamPool(base_request->asArray()[i].asString()); if (route) { - auto single_set = std::make_shared( - base_request, Common::Redis::Utility::SetRequest::instance(), i, i + 1); - pending_request.handle_ = makeRequest(route, "set", base_request->asArray()[i].asString(), - std::move(single_set), pending_request); + Common::Redis::RespValue single_set(base_request, + Common::Redis::Utility::SetRequest::instance(), i, i + 1); + pending_request.handle_ = + makeFragmentedRequest(route, "set", base_request->asArray()[i].asString(), + std::move(single_set), pending_request); } if (!pending_request.handle_) { @@ -313,15 +343,14 @@ SplitRequestPtr SplitKeysSumResultRequest::create(Router& router, request_ptr->pending_requests_.emplace_back(*request_ptr, i - 1); PendingRequest& pending_request = request_ptr->pending_requests_.back(); - auto single_fragment = - std::make_shared(base_request, base_request->asArray()[0], i, i); + Common::Redis::RespValue single_fragment(base_request, base_request->asArray()[0], i, i); ENVOY_LOG(debug, "redis: parallel {}: '{}'", base_request->asArray()[0].asString(), - single_fragment->toString()); + single_fragment.toString()); const auto route = router.upstreamPool(base_request->asArray()[i].asString()); if (route) { - pending_request.handle_ = - makeRequest(route, base_request->asArray()[0].asString(), - base_request->asArray()[i].asString(), single_fragment, pending_request); + pending_request.handle_ = makeFragmentedRequest(route, base_request->asArray()[0].asString(), + base_request->asArray()[i].asString(), + single_fragment, pending_request); } if (!pending_request.handle_) { diff --git a/source/extensions/filters/network/redis_proxy/conn_pool.h b/source/extensions/filters/network/redis_proxy/conn_pool.h index 149f6006df6a..ff502bc041a7 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool.h +++ b/source/extensions/filters/network/redis_proxy/conn_pool.h @@ -9,6 +9,8 @@ #include "extensions/filters/network/common/redis/client.h" #include "extensions/filters/network/common/redis/codec.h" +#include "absl/types/variant.h" + namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -34,6 +36,13 @@ class PoolCallbacks { virtual void onFailure() PURE; }; +/** + * A variant that either holds a shared pointer to a single server request or a composite array + * resp value. This is for performance reason to avoid creating RespValueSharedPtr for each + * composite arrays. + */ +using RespVariant = absl::variant; + /** * A redis connection pool. Wraps M connections to N upstream hosts, consistent hashing, * pipelining, failure handling, etc. @@ -51,22 +60,8 @@ class Instance { * for some reason. */ virtual Common::Redis::Client::PoolRequest* makeRequest(const std::string& hash_key, - Common::Redis::RespValueSharedPtr request, + const RespVariant&& request, PoolCallbacks& callbacks) PURE; - - /** - * Makes a redis request based on IP address and TCP port of the upstream host (e.g., moved/ask - * cluster redirection). - * @param host_address supplies the IP address and TCP port of the upstream host to receive the - * request. - * @param request supplies the Redis request to make. - * @param callbacks supplies the request completion callbacks. - * @return PoolRequest* a handle to the active request or nullptr if the request could not be made - * for some reason. - */ - virtual Common::Redis::Client::PoolRequest* - makeRequestToHost(const std::string& host_address, Common::Redis::RespValueSharedPtr request, - PoolCallbacks& callbacks) PURE; }; using InstanceSharedPtr = std::shared_ptr; diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc index 4fa8a677c46e..3870a4d84202 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc @@ -19,6 +19,14 @@ namespace { // null_pool_callbacks is used for requests that must be filtered and not redirected such as // "asking". Common::Redis::Client::DoNothingPoolCallbacks null_client_callbacks; + +const Common::Redis::RespValue& getRequest(const RespVariant& request) { + if (request.index() == 0) { + return absl::get(request); + } else { + return *(absl::get(request)); + } +} } // namespace InstanceImpl::InstanceImpl( @@ -37,16 +45,16 @@ InstanceImpl::InstanceImpl( }); } -Common::Redis::Client::PoolRequest* -InstanceImpl::makeRequest(const std::string& key, Common::Redis::RespValueSharedPtr request, - PoolCallbacks& callbacks) { - return tls_->getTyped().makeRequest(key, request, callbacks); +Common::Redis::Client::PoolRequest* InstanceImpl::makeRequest(const std::string& key, + const RespVariant&& request, + PoolCallbacks& callbacks) { + return tls_->getTyped().makeRequest(key, std::move(request), callbacks); } Common::Redis::Client::PoolRequest* InstanceImpl::makeRequestToHost(const std::string& host_address, - Common::Redis::RespValueSharedPtr request, - PoolCallbacks& callbacks) { + const Common::Redis::RespValue& request, + Common::Redis::Client::ClientCallbacks& callbacks) { return tls_->getTyped().makeRequestToHost(host_address, request, callbacks); } @@ -207,24 +215,8 @@ InstanceImpl::ThreadLocalPool::threadLocalActiveClient(Upstream::HostConstShared } Common::Redis::Client::PoolRequest* -InstanceImpl::ThreadLocalPool::makeRequestInternal(const Upstream::HostConstSharedPtr& host, - Common::Redis::RespValueSharedPtr request, - PoolCallbacks& callbacks) { - pending_requests_.emplace_back(*this, request, callbacks); - PendingRequest& pending_request = pending_requests_.back(); - ThreadLocalActiveClientPtr& client = this->threadLocalActiveClient(host); - pending_request.request_handler_ = - client->redis_client_->makeRequest(*(pending_request.incoming_request_), pending_request); - if (pending_request.request_handler_) { - return &pending_request; - } else { - onRequestCompleted(); - return nullptr; - } -} - -Common::Redis::Client::PoolRequest* InstanceImpl::ThreadLocalPool::makeRequest( - const std::string& key, Common::Redis::RespValueSharedPtr request, PoolCallbacks& callbacks) { +InstanceImpl::ThreadLocalPool::makeRequest(const std::string& key, const RespVariant&& request, + PoolCallbacks& callbacks) { if (cluster_ == nullptr) { ASSERT(client_map_.empty()); ASSERT(host_set_member_update_cb_handle_ == nullptr); @@ -232,17 +224,28 @@ Common::Redis::Client::PoolRequest* InstanceImpl::ThreadLocalPool::makeRequest( } Clusters::Redis::RedisLoadBalancerContextImpl lb_context(key, parent_.config_.enableHashtagging(), - is_redis_cluster_, *request, + is_redis_cluster_, getRequest(request), parent_.config_.readPolicy()); Upstream::HostConstSharedPtr host = cluster_->loadBalancer().chooseHost(&lb_context); if (!host) { return nullptr; } - return makeRequestInternal(host, request, callbacks); + pending_requests_.emplace_back(*this, std::move(request), callbacks); + PendingRequest& pending_request = pending_requests_.back(); + ThreadLocalActiveClientPtr& client = this->threadLocalActiveClient(host); + pending_request.request_handler_ = client->redis_client_->makeRequest( + getRequest(pending_request.incoming_request_), pending_request); + if (pending_request.request_handler_) { + return &pending_request; + } else { + onRequestCompleted(); + return nullptr; + } } -Upstream::HostConstSharedPtr -InstanceImpl::ThreadLocalPool::getHost(const std::string& host_address) { +Common::Redis::Client::PoolRequest* InstanceImpl::ThreadLocalPool::makeRequestToHost( + const std::string& host_address, const Common::Redis::RespValue& request, + Common::Redis::Client::ClientCallbacks& callbacks) { if (cluster_ == nullptr) { ASSERT(client_map_.empty()); ASSERT(host_set_member_update_cb_handle_ == nullptr); @@ -306,39 +309,8 @@ InstanceImpl::ThreadLocalPool::getHost(const std::string& host_address) { it = host_address_map_.find(host_address_map_key); } - return it->second; -} - -Common::Redis::Client::PoolRequest* -InstanceImpl::ThreadLocalPool::makeRequestToHost(const std::string& host_address, - Common::Redis::RespValueSharedPtr request, - PoolCallbacks& callbacks) { - const Upstream::HostConstSharedPtr host = getHost(host_address); - if (!host) { - return nullptr; - } - return makeRequestInternal(host, request, callbacks); -} - -Common::Redis::Client::PoolRequest* -InstanceImpl::ThreadLocalPool::makeRequestToHostInternal(const std::string& host_address, - PendingRequest& pending_request) { - const Upstream::HostConstSharedPtr host = getHost(host_address); - if (!host) { - return nullptr; - } - ThreadLocalActiveClientPtr& client = this->threadLocalActiveClient(host); - return client->redis_client_->makeRequest(*(pending_request.incoming_request_), pending_request); -} + ThreadLocalActiveClientPtr& client = threadLocalActiveClient(it->second); -Common::Redis::Client::PoolRequest* InstanceImpl::ThreadLocalPool::makeRequestToHostInternal( - const std::string& host_address, const Common::Redis::RespValue& request, - Common::Redis::Client::ClientCallbacks& callbacks) { - const Upstream::HostConstSharedPtr host = getHost(host_address); - if (!host) { - return nullptr; - } - ThreadLocalActiveClientPtr& client = this->threadLocalActiveClient(host); return client->redis_client_->makeRequest(request, callbacks); } @@ -376,9 +348,10 @@ void InstanceImpl::ThreadLocalActiveClient::onEvent(Network::ConnectionEvent eve } InstanceImpl::PendingRequest::PendingRequest(InstanceImpl::ThreadLocalPool& parent, - Common::Redis::RespValueSharedPtr incoming_request, + const RespVariant&& incoming_request, PoolCallbacks& pool_callbacks) - : parent_(parent), incoming_request_(incoming_request), pool_callbacks_(pool_callbacks) {} + : parent_(parent), incoming_request_(std::move(incoming_request)), + pool_callbacks_(pool_callbacks) {} InstanceImpl::PendingRequest::~PendingRequest() { if (request_handler_) { @@ -405,7 +378,7 @@ void InstanceImpl::PendingRequest::onFailure() { bool InstanceImpl::PendingRequest::onRedirection(Common::Redis::RespValuePtr&& value) { std::vector err; bool ask_redirection = false; - if (Common::Redis::Utility::redirectionArgsInvalid(incoming_request_.get(), *value, err, + if (Common::Redis::Utility::redirectionArgsInvalid(&getRequest(incoming_request_), *value, err, ask_redirection)) { onResponse(std::move(value)); return false; @@ -421,12 +394,12 @@ bool InstanceImpl::PendingRequest::onRedirection(Common::Redis::RespValuePtr&& v // "asking" command; this is fine since the server either responds with an OK or an error message // if cluster support is not enabled (in which case we should not get an ASK redirection error). if (ask_redirection && - !parent_.makeRequestToHostInternal( - host_address, Common::Redis::Utility::AskingRequest::instance(), null_client_callbacks)) { + !parent_.makeRequestToHost(host_address, Common::Redis::Utility::AskingRequest::instance(), + null_client_callbacks)) { onResponse(std::move(value)); return false; } - request_handler_ = parent_.makeRequestToHostInternal(host_address, *this); + request_handler_ = parent_.makeRequestToHost(host_address, getRequest(incoming_request_), *this); if (!request_handler_) { onResponse(std::move(value)); } diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h index 216255a71c5e..a7abdd788993 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h @@ -61,11 +61,23 @@ class InstanceImpl : public Instance { const Common::Redis::RedisCommandStatsSharedPtr& redis_command_stats); // RedisProxy::ConnPool::Instance Common::Redis::Client::PoolRequest* makeRequest(const std::string& key, - Common::Redis::RespValueSharedPtr request, + const RespVariant&& request, PoolCallbacks& callbacks) override; - Common::Redis::Client::PoolRequest* makeRequestToHost(const std::string& host_address, - Common::Redis::RespValueSharedPtr request, - PoolCallbacks& callbacks) override; + /** + * Makes a redis request based on IP address and TCP port of the upstream host (e.g., + moved/ask cluster redirection). This is now only kept mostly for testing. + * @param host_address supplies the IP address and TCP port of the upstream host to receive + the + * request. + * @param request supplies the Redis request to make. + * @param callbacks supplies the request completion callbacks. + * @return PoolRequest* a handle to the active request or nullptr if the request could not be + made + * for some reason. + */ + Common::Redis::Client::PoolRequest* + makeRequestToHost(const std::string& host_address, const Common::Redis::RespValue& request, + Common::Redis::Client::ClientCallbacks& callbacks); // Allow the unit test to have access to private members. friend class RedisConnPoolImplTest; @@ -90,7 +102,7 @@ class InstanceImpl : public Instance { struct PendingRequest : public Common::Redis::Client::ClientCallbacks, public Common::Redis::Client::PoolRequest { - PendingRequest(ThreadLocalPool& parent, Common::Redis::RespValueSharedPtr incoming_request, + PendingRequest(ThreadLocalPool& parent, const RespVariant&& incoming_request, PoolCallbacks& pool_callbacks); ~PendingRequest() override; @@ -103,7 +115,7 @@ class InstanceImpl : public Instance { void cancel() override; ThreadLocalPool& parent_; - Common::Redis::RespValueSharedPtr incoming_request_; + const RespVariant incoming_request_; Common::Redis::Client::PoolRequest* request_handler_; PoolCallbacks& pool_callbacks_; }; @@ -113,22 +125,12 @@ class InstanceImpl : public Instance { ThreadLocalPool(InstanceImpl& parent, Event::Dispatcher& dispatcher, std::string cluster_name); ~ThreadLocalPool() override; ThreadLocalActiveClientPtr& threadLocalActiveClient(Upstream::HostConstSharedPtr host); - Common::Redis::Client::PoolRequest* makeRequest(const std::string& key, - Common::Redis::RespValueSharedPtr request, - PoolCallbacks& callbacks); - Common::Redis::Client::PoolRequest* makeRequestToHost(const std::string& host_address, - Common::Redis::RespValueSharedPtr request, - PoolCallbacks& callbacks); - Common::Redis::Client::PoolRequest* makeRequestToHostInternal(const std::string& host_address, - PendingRequest& pending_request); Common::Redis::Client::PoolRequest* - makeRequestInternal(const Upstream::HostConstSharedPtr& host, - Common::Redis::RespValueSharedPtr request, PoolCallbacks& callbacks); + makeRequest(const std::string& key, const RespVariant&& request, PoolCallbacks& callbacks); Common::Redis::Client::PoolRequest* - makeRequestToHostInternal(const std::string& host_address, - const Common::Redis::RespValue& request, - Common::Redis::Client::ClientCallbacks& callbacks); - Upstream::HostConstSharedPtr getHost(const std::string& host_address); + makeRequestToHost(const std::string& host_address, const Common::Redis::RespValue& request, + Common::Redis::Client::ClientCallbacks& callbacks); + void onClusterAddOrUpdateNonVirtual(Upstream::ThreadLocalCluster& cluster); void onHostsAdded(const std::vector& hosts_added); void onHostsRemoved(const std::vector& hosts_removed); diff --git a/test/extensions/filters/network/common/redis/codec_impl_test.cc b/test/extensions/filters/network/common/redis/codec_impl_test.cc index a79eeed8fb17..4a2ba1839a59 100644 --- a/test/extensions/filters/network/common/redis/codec_impl_test.cc +++ b/test/extensions/filters/network/common/redis/codec_impl_test.cc @@ -176,7 +176,7 @@ TEST_F(RedisRespValueTest, SwapTest) { EXPECT_TRUE(value1 == value3); } -TEST_F(RedisRespValueTest, IteratorTest) { +TEST_F(RedisRespValueTest, CompositeArrayTest) { InSequence s; RespValueSharedPtr base = std::make_shared(); @@ -193,6 +193,13 @@ TEST_F(RedisRespValueTest, IteratorTest) { validateIterator(value1, {"get", "foo"}); validateIterator(value2, {"get", "bar"}); validateIterator(value3, {"get", "now"}); + + EXPECT_EQ(value1.asCompositeArray().command(), &command); + EXPECT_EQ(value1.asCompositeArray().baseArray(), base); + + RespValue empty; + empty.type(RespType::CompositeArray); + validateIterator(empty, {}); } class RedisEncoderDecoderImplTest : public testing::Test, public DecoderCallbacks { @@ -319,6 +326,35 @@ TEST_F(RedisEncoderDecoderImplTest, Array) { EXPECT_EQ(0UL, buffer_.length()); } +TEST_F(RedisEncoderDecoderImplTest, CompositeArray) { + std::vector values(2); + values[0].type(RespType::BulkString); + values[0].asString() = "bar"; + values[1].type(RespType::BulkString); + values[1].asString() = "foo"; + + auto base = std::make_shared(); + base->type(RespType::Array); + base->asArray().swap(values); + + RespValue command; + command.type(RespType::SimpleString); + command.asString() = "get"; + + RespValue value1{base, command, 0, 0}; + RespValue value2{base, command, 1, 1}; + + EXPECT_EQ("[\"get\", \"bar\"]", value1.toString()); + encoder_.encode(value1, buffer_); + EXPECT_EQ("*2\r\n+get\r\n$3\r\nbar\r\n", buffer_.toString()); + + EXPECT_EQ("[\"get\", \"foo\"]", value2.toString()); + encoder_.encode(value2, buffer_); + EXPECT_EQ("*2\r\n+get\r\n$3\r\nbar\r\n*2\r\n+get\r\n$3\r\nfoo\r\n", buffer_.toString()); + + // There is no decoder for composite array +} + TEST_F(RedisEncoderDecoderImplTest, NestedArray) { std::vector nested_values(3); nested_values[0].type(RespType::BulkString); diff --git a/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc b/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc index c6e3fd756f11..9079faa3516f 100644 --- a/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc @@ -15,6 +15,7 @@ #include "test/test_common/simulated_time_system.h" +#include "absl/types/variant.h" #include "benchmark/benchmark.h" namespace Envoy { @@ -61,19 +62,23 @@ class CommandSplitSpeedTest { return request; } + using ValueOrPointer = absl::variant; void move(Common::Redis::RespValueSharedPtr request) { for (uint64_t i = 1; i < request->asArray().size(); i += 2) { - // auto single_set = std::make_shared(); - // single_set->type(Common::Redis::RespType::CompositeArray); - // single_set->asCompositeArray().initialize(request, - // Common::Redis::Utility::SetRequest::instance(), i, i + 2); - auto single_set = std::make_shared( request, Common::Redis::Utility::SetRequest::instance(), i, i + 2); } } + void moveLocalVariant(Common::Redis::RespValueSharedPtr request) { + for (uint64_t i = 1; i < request->asArray().size(); i += 2) { + Common::Redis::RespValue single_set(request, Common::Redis::Utility::SetRequest::instance(), + i, i + 1); + ValueOrPointer variant(single_set); + } + } + void copy(Common::Redis::RespValueSharedPtr request) { std::vector values(3); values[0].type(Common::Redis::RespType::BulkString); @@ -102,8 +107,20 @@ static void BM_Split_Move(benchmark::State& state) { for (auto _ : state) { context.move(request); } +// state.counters["use_count"] = request.use_count(); } -BENCHMARK(BM_Split_Move)->Ranges({{1, 100}, {512, 8 << 14}}); +BENCHMARK(BM_Split_Move)->Ranges({{1, 100}, {64, 8 << 14}}); + +static void BM_Split_Move_Local_Variant(benchmark::State& state) { + Envoy::Extensions::NetworkFilters::RedisProxy::CommandSplitSpeedTest context; + Envoy::Extensions::NetworkFilters::Common::Redis::RespValueSharedPtr request = + context.makeSharedBulkStringArray(state.range(0), 36, state.range(1)); + for (auto _ : state) { + context.moveLocalVariant(request); + } +// state.counters["use_count"] = request.use_count(); +} +BENCHMARK(BM_Split_Move_Local_Variant)->Ranges({{1, 100}, {64, 8 << 14}}); static void BM_Split_Copy(benchmark::State& state) { Envoy::Extensions::NetworkFilters::RedisProxy::CommandSplitSpeedTest context; @@ -112,8 +129,10 @@ static void BM_Split_Copy(benchmark::State& state) { for (auto _ : state) { context.copy(request); } + +// state.counters["use_count"] = request.use_count(); } -BENCHMARK(BM_Split_Copy)->Ranges({{1, 100}, {512, 8 << 14}}); +BENCHMARK(BM_Split_Copy)->Ranges({{1, 100}, {64, 8 << 14}}); // Boilerplate main(), which discovers benchmarks in the same file and runs them. int main(int argc, char** argv) { diff --git a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc index 3d6b2f8ecc30..e05c2f93614f 100644 --- a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc @@ -151,12 +151,19 @@ TEST_F(RedisCommandSplitterImplTest, UnsupportedCommand) { EXPECT_EQ(1UL, store_.counter("redis.foo.splitter.unsupported_command").value()); } +MATCHER_P(RespVariantEq, rhs, "RespVariant should be equal") { + const ConnPool::RespVariant& obj = arg; + EXPECT_EQ(obj.index(), 1); + EXPECT_EQ(*(absl::get(obj)), rhs); + return true; +} + class RedisSingleServerRequestTest : public RedisCommandSplitterImplTest, public testing::WithParamInterface { public: void makeRequest(const std::string& hash_key, Common::Redis::RespValuePtr&& request) { EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); - EXPECT_CALL(*conn_pool_, makeRequest(hash_key, Pointee(Ref(*request)), _)) + EXPECT_CALL(*conn_pool_, makeRequest_(hash_key, RespVariantEq(*request), _)) .WillOnce(DoAll(WithArg<2>(SaveArgAddress(&pool_callbacks_)), Return(&pool_request_))); handle_ = splitter_.makeRequest(std::move(request), callbacks_); } @@ -269,7 +276,7 @@ TEST_P(RedisSingleServerRequestTest, NoUpstream) { EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; makeBulkStringArray(*request, {GetParam(), "hello"}); - EXPECT_CALL(*conn_pool_, makeRequest("hello", Pointee(Ref(*request)), _)) + EXPECT_CALL(*conn_pool_, makeRequest_("hello", RespVariantEq(*request), _)) .WillOnce(Return(nullptr)); Common::Redis::RespValue response; @@ -377,7 +384,8 @@ TEST_F(RedisSingleServerRequestTest, EvalNoUpstream) { EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; makeBulkStringArray(*request, {"eval", "return {ARGV[1]}", "1", "key", "arg"}); - EXPECT_CALL(*conn_pool_, makeRequest("key", Pointee(Ref(*request)), _)).WillOnce(Return(nullptr)); + EXPECT_CALL(*conn_pool_, makeRequest_("key", RespVariantEq(*request), _)) + .WillOnce(Return(nullptr)); Common::Redis::RespValue response; response.type(Common::Redis::RespType::Error); @@ -391,11 +399,12 @@ TEST_F(RedisSingleServerRequestTest, EvalNoUpstream) { }; MATCHER_P(CompositeArrayEq, rhs, "CompositeArray should be equal") { - const Common::Redis::RespValueSharedPtr& obj = arg; - EXPECT_TRUE(obj->type() == Common::Redis::RespType::CompositeArray); - EXPECT_EQ(obj->asCompositeArray().size(), rhs.size()); + const ConnPool::RespVariant& obj = arg; + const Common::Redis::RespValue& lhs = absl::get(obj); + EXPECT_TRUE(lhs.type() == Common::Redis::RespType::CompositeArray); + EXPECT_EQ(lhs.asCompositeArray().size(), rhs.size()); std::vector array; - for (auto const& entry : obj->asCompositeArray()) { + for (auto const& entry : lhs.asCompositeArray()) { array.emplace_back(entry.asString()); } EXPECT_EQ(array, rhs); @@ -428,7 +437,7 @@ class RedisMGETCommandHandlerTest : public RedisCommandSplitterImplTest { request_to_use = &pool_requests_[i]; } EXPECT_CALL(*conn_pool_, - makeRequest(std::to_string(i), CompositeArrayEq(expected_requests_[i]), _)) + makeRequest_(std::to_string(i), CompositeArrayEq(expected_requests_[i]), _)) .WillOnce(DoAll(WithArg<2>(SaveArgAddress(&pool_callbacks_[i])), Return(request_to_use))); } @@ -634,7 +643,7 @@ class RedisMSETCommandHandlerTest : public RedisCommandSplitterImplTest { request_to_use = &pool_requests_[i]; } EXPECT_CALL(*conn_pool_, - makeRequest(std::to_string(i), CompositeArrayEq(expected_requests_[i]), _)) + makeRequest_(std::to_string(i), CompositeArrayEq(expected_requests_[i]), _)) .WillOnce(DoAll(WithArg<2>(SaveArgAddress(&pool_callbacks_[i])), Return(request_to_use))); } @@ -761,7 +770,7 @@ class RedisSplitKeysSumResultHandlerTest : public RedisCommandSplitterImplTest, request_to_use = &pool_requests_[i]; } EXPECT_CALL(*conn_pool_, - makeRequest(std::to_string(i), CompositeArrayEq(expected_requests_[i]), _)) + makeRequest_(std::to_string(i), CompositeArrayEq(expected_requests_[i]), _)) .WillOnce(DoAll(WithArg<2>(SaveArgAddress(&pool_callbacks_[i])), Return(request_to_use))); } @@ -850,7 +859,7 @@ class RedisSingleServerRequestWithLatencyMicrosTest : public RedisSingleServerRe public: void makeRequest(const std::string& hash_key, Common::Redis::RespValuePtr&& request) { EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); - EXPECT_CALL(*conn_pool_, makeRequest(hash_key, Pointee(Ref(*request)), _)) + EXPECT_CALL(*conn_pool_, makeRequest_(hash_key, RespVariantEq(*request), _)) .WillOnce(DoAll(WithArg<2>(SaveArgAddress(&pool_callbacks_)), Return(&pool_request_))); handle_ = splitter_.makeRequest(std::move(request), callbacks_); } diff --git a/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc b/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc index 226d40b71d18..9186c8a10e40 100644 --- a/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc @@ -246,6 +246,19 @@ class RedisConnPoolImplTest : public testing::Test, public Common::Redis::Client conn_pool_->tls_->getTyped().pending_requests_.size()); } + void verifyInvalidMoveResponse(Common::Redis::Client::MockClient* client, + const std::string& move_response, bool create_client) { + Common::Redis::RespValueSharedPtr request_value = std::make_shared(); + Common::Redis::Client::MockPoolRequest active_request; + MockPoolCallbacks callbacks; + makeRequest(client, request_value, callbacks, active_request, create_client); + Common::Redis::RespValuePtr moved_response{new Common::Redis::RespValue()}; + moved_response->type(Common::Redis::RespType::Error); + moved_response->asString() = move_response; + EXPECT_CALL(callbacks, onResponse_(Ref(moved_response))); + EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(moved_response))); + } + MOCK_METHOD1(create_, Common::Redis::Client::Client*(Upstream::HostConstSharedPtr host)); const std::string cluster_name_{"fake_cluster"}; @@ -545,11 +558,11 @@ TEST_F(RedisConnPoolImplTest, MakeRequestToHost) { setup(false); - Common::Redis::RespValueSharedPtr value = std::make_shared(); + Common::Redis::RespValue value; Common::Redis::Client::MockPoolRequest active_request1; Common::Redis::Client::MockPoolRequest active_request2; - MockPoolCallbacks callbacks1; - MockPoolCallbacks callbacks2; + Common::Redis::Client::MockClientCallbacks callbacks1; + Common::Redis::Client::MockClientCallbacks callbacks2; Common::Redis::Client::MockClient* client1 = new NiceMock(); Common::Redis::Client::MockClient* client2 = new NiceMock(); Upstream::HostConstSharedPtr host1; @@ -561,28 +574,24 @@ TEST_F(RedisConnPoolImplTest, MakeRequestToHost) { update_callbacks_->onClusterAddOrUpdate(cm_.thread_local_cluster_); EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host1), Return(client1))); - EXPECT_CALL(*client1, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request1)); + EXPECT_CALL(*client1, makeRequest_(Ref(value), Ref(callbacks1))) + .WillOnce(Return(&active_request1)); Common::Redis::Client::PoolRequest* request1 = conn_pool_->makeRequestToHost("10.0.0.1:3000", value, callbacks1); - EXPECT_NE(nullptr, request1); + EXPECT_EQ(&active_request1, request1); EXPECT_EQ(host1->address()->asString(), "10.0.0.1:3000"); - EXPECT_CALL(callbacks1, onResponse_(_)); - client1->client_callbacks_.back()->onResponse(std::make_unique()); - // IPv6 address returned from Redis server will not have square brackets // around it, while Envoy represents Address::Ipv6Instance addresses with square brackets around // the address. EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host2), Return(client2))); - EXPECT_CALL(*client2, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request2)); + EXPECT_CALL(*client2, makeRequest_(Ref(value), Ref(callbacks2))) + .WillOnce(Return(&active_request2)); Common::Redis::Client::PoolRequest* request2 = conn_pool_->makeRequestToHost("2001:470:813B:0:0:0:0:1:3333", value, callbacks2); - EXPECT_NE(nullptr, request2); + EXPECT_EQ(&active_request2, request2); EXPECT_EQ(host2->address()->asString(), "[2001:470:813b::1]:3333"); - EXPECT_CALL(callbacks2, onResponse_(_)); - client2->client_callbacks_.back()->onResponse(std::make_unique()); - // Test with a badly specified host address (no colon, no address, no port). EXPECT_EQ(conn_pool_->makeRequestToHost("bad", value, callbacks1), nullptr); // Test with a badly specified IPv4 address. @@ -610,8 +619,8 @@ TEST_F(RedisConnPoolImplTest, MakeRequestToHostWithZeroMaxUnknownUpstreamConnect // Create a ConnPool with a max_upstream_unknown_connections setting of 0. setup(true, true, 0); - Common::Redis::RespValueSharedPtr value = std::make_shared(); - MockPoolCallbacks callbacks1; + Common::Redis::RespValue value; + Common::Redis::Client::MockClientCallbacks callbacks1; // The max_unknown_upstream_connections is set to 0. Request should fail. EXPECT_EQ(nullptr, conn_pool_->makeRequestToHost("10.0.0.1:3000", value, callbacks1)); @@ -626,31 +635,33 @@ TEST_F(RedisConnPoolImplTest, MakeRequestToHostWithZeroMaxUnknownUpstreamConnect TEST_F(RedisConnPoolImplTest, HostsAddedAndRemovedWithDraining) { setup(); - Common::Redis::RespValueSharedPtr value = std::make_shared(); + Common::Redis::RespValue value; Common::Redis::Client::MockPoolRequest auth_request1, active_request1; Common::Redis::Client::MockPoolRequest auth_request2, active_request2; - MockPoolCallbacks callbacks1; - MockPoolCallbacks callbacks2; + Common::Redis::Client::MockClientCallbacks callbacks1; + Common::Redis::Client::MockClientCallbacks callbacks2; Common::Redis::Client::MockClient* client1 = new NiceMock(); Common::Redis::Client::MockClient* client2 = new NiceMock(); Upstream::HostConstSharedPtr host1; Upstream::HostConstSharedPtr host2; EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host1), Return(client1))); - EXPECT_CALL(*client1, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request1)); + EXPECT_CALL(*client1, makeRequest_(Ref(value), Ref(callbacks1))) + .WillOnce(Return(&active_request1)); Common::Redis::Client::PoolRequest* request1 = conn_pool_->makeRequestToHost("10.0.0.1:3000", value, callbacks1); - EXPECT_NE(nullptr, request1); + EXPECT_EQ(&active_request1, request1); EXPECT_EQ(host1->address()->asString(), "10.0.0.1:3000"); // IPv6 address returned from Redis server will not have square brackets // around it, while Envoy represents Address::Ipv6Instance addresses with square brackets around // the address. EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host2), Return(client2))); - EXPECT_CALL(*client2, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request2)); + EXPECT_CALL(*client2, makeRequest_(Ref(value), Ref(callbacks2))) + .WillOnce(Return(&active_request2)); Common::Redis::Client::PoolRequest* request2 = conn_pool_->makeRequestToHost("2001:470:813B:0:0:0:0:1:3333", value, callbacks2); - EXPECT_NE(nullptr, request2); + EXPECT_EQ(&active_request2, request2); EXPECT_EQ(host2->address()->asString(), "[2001:470:813b::1]:3333"); std::unordered_map& host_address_map = @@ -713,11 +724,6 @@ TEST_F(RedisConnPoolImplTest, HostsAddedAndRemovedWithDraining) { EXPECT_EQ(drainTimer()->enabled(), false); EXPECT_EQ(upstreamCxDrained().value(), 1); - EXPECT_CALL(active_request1, cancel()); - EXPECT_CALL(active_request2, cancel()); - EXPECT_CALL(callbacks1, onFailure_()); - EXPECT_CALL(callbacks2, onFailure_()); - tls_.shutdownThread(); } @@ -728,39 +734,35 @@ TEST_F(RedisConnPoolImplTest, HostsAddedAndRemovedWithDraining) { TEST_F(RedisConnPoolImplTest, HostsAddedAndEndWithNoDraining) { setup(); - Common::Redis::RespValueSharedPtr value = std::make_shared(); + Common::Redis::RespValue value; Common::Redis::Client::MockPoolRequest auth_request1, active_request1; Common::Redis::Client::MockPoolRequest auth_request2, active_request2; - MockPoolCallbacks callbacks1; - MockPoolCallbacks callbacks2; + Common::Redis::Client::MockClientCallbacks callbacks1; + Common::Redis::Client::MockClientCallbacks callbacks2; Common::Redis::Client::MockClient* client1 = new NiceMock(); Common::Redis::Client::MockClient* client2 = new NiceMock(); Upstream::HostConstSharedPtr host1; Upstream::HostConstSharedPtr host2; EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host1), Return(client1))); - EXPECT_CALL(*client1, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request1)); + EXPECT_CALL(*client1, makeRequest_(Ref(value), Ref(callbacks1))) + .WillOnce(Return(&active_request1)); Common::Redis::Client::PoolRequest* request1 = conn_pool_->makeRequestToHost("10.0.0.1:3000", value, callbacks1); - EXPECT_NE(nullptr, request1); + EXPECT_EQ(&active_request1, request1); EXPECT_EQ(host1->address()->asString(), "10.0.0.1:3000"); - EXPECT_CALL(callbacks1, onFailure_()); - client1->client_callbacks_.back()->onFailure(); - // IPv6 address returned from Redis server will not have square brackets // around it, while Envoy represents Address::Ipv6Instance addresses with square brackets around // the address. EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host2), Return(client2))); - EXPECT_CALL(*client2, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request2)); + EXPECT_CALL(*client2, makeRequest_(Ref(value), Ref(callbacks2))) + .WillOnce(Return(&active_request2)); Common::Redis::Client::PoolRequest* request2 = conn_pool_->makeRequestToHost("2001:470:813B:0:0:0:0:1:3333", value, callbacks2); - EXPECT_NE(nullptr, request2); + EXPECT_EQ(&active_request2, request2); EXPECT_EQ(host2->address()->asString(), "[2001:470:813b::1]:3333"); - EXPECT_CALL(callbacks2, onFailure_()); - client2->client_callbacks_.back()->onFailure(); - std::unordered_map& host_address_map = hostAddressMap(); EXPECT_EQ(host_address_map.size(), 2); // host1 and host2 have been created. @@ -810,43 +812,35 @@ TEST_F(RedisConnPoolImplTest, HostsAddedAndEndWithNoDraining) { TEST_F(RedisConnPoolImplTest, HostsAddedAndEndWithClusterRemoval) { setup(); - Common::Redis::RespValueSharedPtr value = std::make_shared(); + Common::Redis::RespValue value; Common::Redis::Client::MockPoolRequest auth_request1, active_request1; Common::Redis::Client::MockPoolRequest auth_request2, active_request2; - MockPoolCallbacks callbacks1; - MockPoolCallbacks callbacks2; + Common::Redis::Client::MockClientCallbacks callbacks1; + Common::Redis::Client::MockClientCallbacks callbacks2; Common::Redis::Client::MockClient* client1 = new NiceMock(); Common::Redis::Client::MockClient* client2 = new NiceMock(); Upstream::HostConstSharedPtr host1; Upstream::HostConstSharedPtr host2; EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host1), Return(client1))); - EXPECT_CALL(*client1, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request1)); + EXPECT_CALL(*client1, makeRequest_(Ref(value), Ref(callbacks1))) + .WillOnce(Return(&active_request1)); Common::Redis::Client::PoolRequest* request1 = conn_pool_->makeRequestToHost("10.0.0.1:3000", value, callbacks1); - Common::Redis::Client::ClientCallbacks* client_callbacks1 = client1->client_callbacks_.back(); - EXPECT_NE(nullptr, request1); - EXPECT_NE(nullptr, client_callbacks1); + EXPECT_EQ(&active_request1, request1); EXPECT_EQ(host1->address()->asString(), "10.0.0.1:3000"); - EXPECT_CALL(callbacks1, onFailure_()); - client_callbacks1->onFailure(); - // IPv6 address returned from Redis server will not have square brackets // around it, while Envoy represents Address::Ipv6Instance addresses with square brackets around // the address. EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host2), Return(client2))); - EXPECT_CALL(*client2, makeRequest_(Ref(*value), _)).WillOnce(Return(&active_request2)); + EXPECT_CALL(*client2, makeRequest_(Ref(value), Ref(callbacks2))) + .WillOnce(Return(&active_request2)); Common::Redis::Client::PoolRequest* request2 = conn_pool_->makeRequestToHost("2001:470:813B:0:0:0:0:1:3333", value, callbacks2); - Common::Redis::Client::ClientCallbacks* client_callbacks2 = client2->client_callbacks_.back(); - EXPECT_NE(nullptr, request2); - EXPECT_NE(nullptr, client_callbacks2); + EXPECT_EQ(&active_request2, request2); EXPECT_EQ(host2->address()->asString(), "[2001:470:813b::1]:3333"); - EXPECT_CALL(callbacks2, onFailure_()); - client_callbacks2->onFailure(); - std::unordered_map& host_address_map = hostAddressMap(); EXPECT_EQ(host_address_map.size(), 2); // host1 and host2 have been created. @@ -969,25 +963,40 @@ TEST_F(RedisConnPoolImplTest, MovedRedirectionFailure) { setup(); - // Test a truncated MOVED error response that cannot be parsed properly. - Common::Redis::RespValueSharedPtr request_value = std::make_shared(); - Common::Redis::Client::MockPoolRequest active_request; - MockPoolCallbacks callbacks; Common::Redis::Client::MockClient* client = new NiceMock(); - makeRequest(client, request_value, callbacks, active_request); - Common::Redis::RespValuePtr moved_response{new Common::Redis::RespValue()}; - moved_response->type(Common::Redis::RespType::Error); - moved_response->asString() = "MOVED 1111"; - EXPECT_CALL(callbacks, onResponse_(Ref(moved_response))); - EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(moved_response))); + // Test a truncated MOVED error response that cannot be parsed properly. + verifyInvalidMoveResponse(client, "MOVED 1111", true); + + // Test with a badly specified host address (no colon, no address, no port). + verifyInvalidMoveResponse(client, "MOVED 1111 bad", false); + + // Test with a badly specified IPv4 address. + verifyInvalidMoveResponse(client, "MOVED 1111 10.0.bad:3000", false); + + // Test with a badly specified TCP port. + verifyInvalidMoveResponse(client, "MOVED 1111 10.0.bad:3000", false); + + // Test with a TCP port outside of the acceptable range for a 32-bit integer. + verifyInvalidMoveResponse(client, "MOVED 1111 10.0.0.1:4294967297", false); // 2^32 + 1 + + // Test with a TCP port outside of the acceptable range for a TCP port (0 .. 65535). + verifyInvalidMoveResponse(client, "MOVED 1111 10.0.0.1:65536", false); + + // Test with a badly specified IPv6-like address. + verifyInvalidMoveResponse(client, "MOVED 1111 bad:ipv6:3000", false); + + // Test with a valid IPv6 address and a badly specified TCP port (out of range). + verifyInvalidMoveResponse(client, "MOVED 1111 2001:470:813b:::70000", false); + + // Test a wrong response + MockPoolCallbacks callbacks; Common::Redis::RespValueSharedPtr request2 = std::make_shared(); Common::Redis::Client::MockPoolRequest active_request2; makeRequest(client, request2, callbacks, active_request2, false); Common::Redis::RespValuePtr moved_response2{new Common::Redis::RespValue()}; moved_response2->type(Common::Redis::RespType::Integer); moved_response2->asInteger() = 1; - EXPECT_CALL(callbacks, onResponse_(Ref(moved_response2))); EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(moved_response2))); diff --git a/test/extensions/filters/network/redis_proxy/mocks.h b/test/extensions/filters/network/redis_proxy/mocks.h index b0c39eb97666..5da4d46c08c2 100644 --- a/test/extensions/filters/network/redis_proxy/mocks.h +++ b/test/extensions/filters/network/redis_proxy/mocks.h @@ -59,16 +59,16 @@ class MockInstance : public Instance { MockInstance(); ~MockInstance() override; - MOCK_METHOD3(makeRequest, - Common::Redis::Client::PoolRequest*(const std::string& hash_key, - Common::Redis::RespValueSharedPtr request, - PoolCallbacks& callbacks)); - MOCK_METHOD3(makeRequestToHost, - Common::Redis::Client::PoolRequest*(const std::string& host_address, - Common::Redis::RespValueSharedPtr request, - PoolCallbacks& callbacks)); -}; + Common::Redis::Client::PoolRequest* makeRequest(const std::string& hash_key, + const RespVariant&& request, + PoolCallbacks& callbacks) override { + return makeRequest_(hash_key, request, callbacks); + } + MOCK_METHOD3(makeRequest_, Common::Redis::Client::PoolRequest*(const std::string& hash_key, + const RespVariant& request, + PoolCallbacks& callbacks)); +}; } // namespace ConnPool namespace CommandSplitter { From b940d047a64518384daff2cff42651483c102d3e Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Thu, 17 Oct 2019 05:17:43 -0700 Subject: [PATCH 09/27] Fix format Signed-off-by: Henry Yang --- .../extensions/filters/network/redis_proxy/conn_pool_impl.h | 2 +- .../filters/network/redis_proxy/command_split_speed_test.cc | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h index a7abdd788993..1d6795701fef 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h @@ -65,7 +65,7 @@ class InstanceImpl : public Instance { PoolCallbacks& callbacks) override; /** * Makes a redis request based on IP address and TCP port of the upstream host (e.g., - moved/ask cluster redirection). This is now only kept mostly for testing. + moved/ask cluster redirection). This is now only kept mostly for testing. * @param host_address supplies the IP address and TCP port of the upstream host to receive the * request. diff --git a/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc b/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc index 9079faa3516f..75fe6db0d231 100644 --- a/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc @@ -107,7 +107,7 @@ static void BM_Split_Move(benchmark::State& state) { for (auto _ : state) { context.move(request); } -// state.counters["use_count"] = request.use_count(); + // state.counters["use_count"] = request.use_count(); } BENCHMARK(BM_Split_Move)->Ranges({{1, 100}, {64, 8 << 14}}); @@ -118,7 +118,7 @@ static void BM_Split_Move_Local_Variant(benchmark::State& state) { for (auto _ : state) { context.moveLocalVariant(request); } -// state.counters["use_count"] = request.use_count(); + // state.counters["use_count"] = request.use_count(); } BENCHMARK(BM_Split_Move_Local_Variant)->Ranges({{1, 100}, {64, 8 << 14}}); @@ -130,7 +130,7 @@ static void BM_Split_Copy(benchmark::State& state) { context.copy(request); } -// state.counters["use_count"] = request.use_count(); + // state.counters["use_count"] = request.use_count(); } BENCHMARK(BM_Split_Copy)->Ranges({{1, 100}, {64, 8 << 14}}); From e63f38e7cc08d41d548e6c0441bac54373a3cf25 Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Thu, 17 Oct 2019 16:32:54 -0700 Subject: [PATCH 10/27] Fix clang Signed-off-by: Henry Yang --- docs/root/intro/version_history.rst | 2 ++ .../filters/network/redis_proxy/command_splitter_impl.cc | 5 ++--- .../extensions/filters/network/redis_proxy/conn_pool.h | 5 ++--- .../filters/network/redis_proxy/conn_pool_impl.cc | 9 ++++----- .../filters/network/redis_proxy/conn_pool_impl.h | 9 ++++----- .../network/redis_proxy/command_splitter_impl_test.cc | 2 -- test/extensions/filters/network/redis_proxy/mocks.h | 8 ++++---- 7 files changed, 18 insertions(+), 22 deletions(-) diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index a7351555b52b..c7c1b0e00603 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -54,6 +54,8 @@ Version history * redis: added :ref:`read_policy ` to allow reading from redis replicas for Redis Cluster deployments. * redis: fix a bug where the redis health checker ignored the upstream auth password. * redis: enable_hashtaging is always enabled when the upstream uses open source Redis cluster protocol. +* redis: correctly follow MOVE/ASK redirection for mirrored clusters. +* redis: performance improvement for larger split commands by avoiding string copies. * regex: introduce new :ref:`RegexMatcher ` type that provides a safe regex implementation for untrusted user input. This type is now used in all configuration that processes user provided input. See :ref:`deprecated configuration details diff --git a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc index 41e6c7530526..edea5516e2bd 100644 --- a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc +++ b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc @@ -278,9 +278,8 @@ SplitRequestPtr MSETRequest::create(Router& router, Common::Redis::RespValuePtr& if (route) { Common::Redis::RespValue single_set(base_request, Common::Redis::Utility::SetRequest::instance(), i, i + 1); - pending_request.handle_ = - makeFragmentedRequest(route, "set", base_request->asArray()[i].asString(), - std::move(single_set), pending_request); + pending_request.handle_ = makeFragmentedRequest( + route, "set", base_request->asArray()[i].asString(), single_set, pending_request); } if (!pending_request.handle_) { diff --git a/source/extensions/filters/network/redis_proxy/conn_pool.h b/source/extensions/filters/network/redis_proxy/conn_pool.h index ff502bc041a7..c4c46d535edd 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool.h +++ b/source/extensions/filters/network/redis_proxy/conn_pool.h @@ -59,9 +59,8 @@ class Instance { * @return PoolRequest* a handle to the active request or nullptr if the request could not be made * for some reason. */ - virtual Common::Redis::Client::PoolRequest* makeRequest(const std::string& hash_key, - const RespVariant&& request, - PoolCallbacks& callbacks) PURE; + virtual Common::Redis::Client::PoolRequest* + makeRequest(const std::string& hash_key, RespVariant&& request, PoolCallbacks& callbacks) PURE; }; using InstanceSharedPtr = std::shared_ptr; diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc index 3870a4d84202..d9e25fcaa518 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc @@ -45,9 +45,8 @@ InstanceImpl::InstanceImpl( }); } -Common::Redis::Client::PoolRequest* InstanceImpl::makeRequest(const std::string& key, - const RespVariant&& request, - PoolCallbacks& callbacks) { +Common::Redis::Client::PoolRequest* +InstanceImpl::makeRequest(const std::string& key, RespVariant&& request, PoolCallbacks& callbacks) { return tls_->getTyped().makeRequest(key, std::move(request), callbacks); } @@ -215,7 +214,7 @@ InstanceImpl::ThreadLocalPool::threadLocalActiveClient(Upstream::HostConstShared } Common::Redis::Client::PoolRequest* -InstanceImpl::ThreadLocalPool::makeRequest(const std::string& key, const RespVariant&& request, +InstanceImpl::ThreadLocalPool::makeRequest(const std::string& key, RespVariant&& request, PoolCallbacks& callbacks) { if (cluster_ == nullptr) { ASSERT(client_map_.empty()); @@ -348,7 +347,7 @@ void InstanceImpl::ThreadLocalActiveClient::onEvent(Network::ConnectionEvent eve } InstanceImpl::PendingRequest::PendingRequest(InstanceImpl::ThreadLocalPool& parent, - const RespVariant&& incoming_request, + RespVariant&& incoming_request, PoolCallbacks& pool_callbacks) : parent_(parent), incoming_request_(std::move(incoming_request)), pool_callbacks_(pool_callbacks) {} diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h index 1d6795701fef..13bc9fe4bb64 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h @@ -60,8 +60,7 @@ class InstanceImpl : public Instance { Api::Api& api, Stats::ScopePtr&& stats_scope, const Common::Redis::RedisCommandStatsSharedPtr& redis_command_stats); // RedisProxy::ConnPool::Instance - Common::Redis::Client::PoolRequest* makeRequest(const std::string& key, - const RespVariant&& request, + Common::Redis::Client::PoolRequest* makeRequest(const std::string& key, RespVariant&& request, PoolCallbacks& callbacks) override; /** * Makes a redis request based on IP address and TCP port of the upstream host (e.g., @@ -102,7 +101,7 @@ class InstanceImpl : public Instance { struct PendingRequest : public Common::Redis::Client::ClientCallbacks, public Common::Redis::Client::PoolRequest { - PendingRequest(ThreadLocalPool& parent, const RespVariant&& incoming_request, + PendingRequest(ThreadLocalPool& parent, RespVariant&& incoming_request, PoolCallbacks& pool_callbacks); ~PendingRequest() override; @@ -125,8 +124,8 @@ class InstanceImpl : public Instance { ThreadLocalPool(InstanceImpl& parent, Event::Dispatcher& dispatcher, std::string cluster_name); ~ThreadLocalPool() override; ThreadLocalActiveClientPtr& threadLocalActiveClient(Upstream::HostConstSharedPtr host); - Common::Redis::Client::PoolRequest* - makeRequest(const std::string& key, const RespVariant&& request, PoolCallbacks& callbacks); + Common::Redis::Client::PoolRequest* makeRequest(const std::string& key, RespVariant&& request, + PoolCallbacks& callbacks); Common::Redis::Client::PoolRequest* makeRequestToHost(const std::string& host_address, const Common::Redis::RespValue& request, Common::Redis::Client::ClientCallbacks& callbacks); diff --git a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc index e05c2f93614f..7b4c6025a29d 100644 --- a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc @@ -19,9 +19,7 @@ using testing::_; using testing::DoAll; using testing::InSequence; using testing::NiceMock; -using testing::Pointee; using testing::Property; -using testing::Ref; using testing::Return; using testing::SaveArg; using testing::WithArg; diff --git a/test/extensions/filters/network/redis_proxy/mocks.h b/test/extensions/filters/network/redis_proxy/mocks.h index 5da4d46c08c2..a5098ebb4921 100644 --- a/test/extensions/filters/network/redis_proxy/mocks.h +++ b/test/extensions/filters/network/redis_proxy/mocks.h @@ -60,14 +60,14 @@ class MockInstance : public Instance { ~MockInstance() override; Common::Redis::Client::PoolRequest* makeRequest(const std::string& hash_key, - const RespVariant&& request, + RespVariant&& request, PoolCallbacks& callbacks) override { return makeRequest_(hash_key, request, callbacks); } - MOCK_METHOD3(makeRequest_, Common::Redis::Client::PoolRequest*(const std::string& hash_key, - const RespVariant& request, - PoolCallbacks& callbacks)); + MOCK_METHOD3(makeRequest_, + Common::Redis::Client::PoolRequest*(const std::string& hash_key, + RespVariant& request, PoolCallbacks& callbacks)); }; } // namespace ConnPool From dd78fb0ecdb0b707eb528df092c2580e42431dae Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Thu, 17 Oct 2019 18:15:39 -0700 Subject: [PATCH 11/27] Fix clang Signed-off-by: Henry Yang --- .../filters/network/redis_proxy/command_split_speed_test.cc | 6 +++--- .../network/redis_proxy/command_splitter_impl_test.cc | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc b/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc index 75fe6db0d231..faf375b2e937 100644 --- a/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc @@ -64,14 +64,14 @@ class CommandSplitSpeedTest { } using ValueOrPointer = absl::variant; - void move(Common::Redis::RespValueSharedPtr request) { + void move(const Common::Redis::RespValueSharedPtr& request) { for (uint64_t i = 1; i < request->asArray().size(); i += 2) { auto single_set = std::make_shared( request, Common::Redis::Utility::SetRequest::instance(), i, i + 2); } } - void moveLocalVariant(Common::Redis::RespValueSharedPtr request) { + void moveLocalVariant(Common::Redis::RespValueSharedPtr& request) { for (uint64_t i = 1; i < request->asArray().size(); i += 2) { Common::Redis::RespValue single_set(request, Common::Redis::Utility::SetRequest::instance(), i, i + 1); @@ -79,7 +79,7 @@ class CommandSplitSpeedTest { } } - void copy(Common::Redis::RespValueSharedPtr request) { + void copy(Common::Redis::RespValueSharedPtr& request) { std::vector values(3); values[0].type(Common::Redis::RespType::BulkString); values[0].asString() = "set"; diff --git a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc index 7b4c6025a29d..7a27c253cc6d 100644 --- a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc @@ -21,7 +21,6 @@ using testing::InSequence; using testing::NiceMock; using testing::Property; using testing::Return; -using testing::SaveArg; using testing::WithArg; namespace Envoy { @@ -398,7 +397,7 @@ TEST_F(RedisSingleServerRequestTest, EvalNoUpstream) { MATCHER_P(CompositeArrayEq, rhs, "CompositeArray should be equal") { const ConnPool::RespVariant& obj = arg; - const Common::Redis::RespValue& lhs = absl::get(obj); + const auto& lhs = absl::get(obj); EXPECT_TRUE(lhs.type() == Common::Redis::RespType::CompositeArray); EXPECT_EQ(lhs.asCompositeArray().size(), rhs.size()); std::vector array; From a1b074152d06343c8b80ee299ed7439237965489 Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Thu, 17 Oct 2019 23:47:03 -0700 Subject: [PATCH 12/27] Kick CI Signed-off-by: Henry Yang From cf4c3f67e9ba5771b6a20dfa78378c6768a2c6c3 Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Sat, 19 Oct 2019 01:49:40 -0700 Subject: [PATCH 13/27] fix merge errors Signed-off-by: Henry Yang --- source/extensions/filters/network/redis_proxy/conn_pool_impl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc index 88d2b64f7e5f..14e8883a23fa 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc @@ -404,7 +404,7 @@ bool InstanceImpl::PendingRequest::onRedirection(Common::Redis::RespValuePtr&& v if (!request_handler_) { onResponse(std::move(value)); } else { - parent_.onRedirection(); + parent_.parent_.onRedirection(); } return (request_handler_ != nullptr); } From deab4b6d72a1b14d72321da3442174b8759a7e34 Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Sat, 19 Oct 2019 17:12:24 -0700 Subject: [PATCH 14/27] Fix format Signed-off-by: Henry Yang --- .../filters/network/common/redis/codec_impl.cc | 4 ++-- .../filters/network/common/redis/codec_impl_test.cc | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/source/extensions/filters/network/common/redis/codec_impl.cc b/source/extensions/filters/network/common/redis/codec_impl.cc index 763680e8d0cb..a6675a3cbd05 100644 --- a/source/extensions/filters/network/common/redis/codec_impl.cc +++ b/source/extensions/filters/network/common/redis/codec_impl.cc @@ -312,8 +312,8 @@ RespValue::CompositeArray::CompositeArrayConstIterator::operator++() { return *this; } -bool RespValue::CompositeArray::CompositeArrayConstIterator:: -operator!=(const CompositeArrayConstIterator& rhs) const { +bool RespValue::CompositeArray::CompositeArrayConstIterator::operator!=( + const CompositeArrayConstIterator& rhs) const { return command_ != (rhs.command_) || &array_ != &(rhs.array_) || index_ != rhs.index_ || first_ != rhs.first_; } diff --git a/test/extensions/filters/network/common/redis/codec_impl_test.cc b/test/extensions/filters/network/common/redis/codec_impl_test.cc index 4a2ba1839a59..e1fc67513a3d 100644 --- a/test/extensions/filters/network/common/redis/codec_impl_test.cc +++ b/test/extensions/filters/network/common/redis/codec_impl_test.cc @@ -141,7 +141,7 @@ TEST_F(RedisRespValueTest, MoveOperationsTest) { InSequence s; RespValue array_value, bulkstring_value, simplestring_value, error_value, integer_value, - null_value; + null_value, composite_array_empty; makeBulkStringArray(array_value, {"get", "foo", "bar", "now"}); bulkstring_value.type(RespType::BulkString); bulkstring_value.asString() = "foo"; @@ -151,6 +151,7 @@ TEST_F(RedisRespValueTest, MoveOperationsTest) { error_value.asString() = "error"; integer_value.type(RespType::Integer); integer_value.asInteger() = 123; + composite_array_empty.type(RespType::CompositeArray); verifyMoves(array_value); verifyMoves(bulkstring_value); @@ -158,6 +159,7 @@ TEST_F(RedisRespValueTest, MoveOperationsTest) { verifyMoves(error_value); verifyMoves(integer_value); verifyMoves(null_value); + verifyMoves(composite_array_empty); } TEST_F(RedisRespValueTest, SwapTest) { @@ -197,6 +199,12 @@ TEST_F(RedisRespValueTest, CompositeArrayTest) { EXPECT_EQ(value1.asCompositeArray().command(), &command); EXPECT_EQ(value1.asCompositeArray().baseArray(), base); + RespValue value4{base, command, 1, 1}; + EXPECT_TRUE(value1 == value1); + EXPECT_FALSE(value1 == value2); + EXPECT_FALSE(value1 == value3); + EXPECT_TRUE(value1 == value4); + RespValue empty; empty.type(RespType::CompositeArray); validateIterator(empty, {}); From 7a80d457364ba1f9b9f61b90dd86bc435339daf6 Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Wed, 23 Oct 2019 16:49:34 -0700 Subject: [PATCH 15/27] Fix tests Signed-off-by: Henry Yang --- source/extensions/filters/network/common/redis/codec.h | 1 - source/extensions/filters/network/common/redis/codec_impl.cc | 1 + .../extensions/filters/network/redis_proxy/conn_pool_impl.cc | 3 ++- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/source/extensions/filters/network/common/redis/codec.h b/source/extensions/filters/network/common/redis/codec.h index 42f4f295f7f0..3115bf94d3bd 100644 --- a/source/extensions/filters/network/common/redis/codec.h +++ b/source/extensions/filters/network/common/redis/codec.h @@ -58,7 +58,6 @@ class RespValue { ASSERT(base_array_ != nullptr); ASSERT(base_array_->type() == RespType::Array); ASSERT(start <= end); - ASSERT(start >= 0); ASSERT(end < base_array_->asArray().size()); } diff --git a/source/extensions/filters/network/common/redis/codec_impl.cc b/source/extensions/filters/network/common/redis/codec_impl.cc index a6675a3cbd05..2286ffc947d5 100644 --- a/source/extensions/filters/network/common/redis/codec_impl.cc +++ b/source/extensions/filters/network/common/redis/codec_impl.cc @@ -179,6 +179,7 @@ RespValue::RespValue(RespValue&& other) noexcept : type_(other.type_) { } case RespType::CompositeArray: { new (&composite_array_) CompositeArray(std::move(other.composite_array_)); + break; } case RespType::SimpleString: case RespType::BulkString: diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc index 14e8883a23fa..11074c1dee18 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc @@ -403,10 +403,11 @@ bool InstanceImpl::PendingRequest::onRedirection(Common::Redis::RespValuePtr&& v request_handler_ = parent_.makeRequestToHost(host_address, getRequest(incoming_request_), *this); if (!request_handler_) { onResponse(std::move(value)); + return false; } else { parent_.parent_.onRedirection(); + return true; } - return (request_handler_ != nullptr); } void InstanceImpl::PendingRequest::cancel() { From aa890243feabe6315aaacdcf77c4ca017c770fed Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Tue, 5 Nov 2019 23:34:28 -0800 Subject: [PATCH 16/27] Fix merge errors Signed-off-by: Henry Yang --- docs/root/intro/version_history.rst | 4 +--- .../redis_proxy/command_splitter_impl.cc | 18 +++++++++--------- .../redis_proxy/command_splitter_impl.h | 7 ------- .../network/redis_proxy/conn_pool_impl.h | 8 +++----- 4 files changed, 13 insertions(+), 24 deletions(-) diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index f7825ac8bd25..ee217d53c580 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -5,6 +5,7 @@ Version history ================ * access log: added FILTER_STATE :ref:`access log formatters ` and gRPC access logger. * api: remove all support for v1 +* redis: correctly follow MOVE/ASK redirection for mirrored clusters. * redis: performance improvement for larger split commands by avoiding string copies. * tcp_proxy: added :ref:`hash_policy` * tls: remove TLS 1.0 and 1.1 from client defaults @@ -77,9 +78,6 @@ Version history * redis: added :ref:`read_policy ` to allow reading from redis replicas for Redis Cluster deployments. * redis: fixed a bug where the redis health checker ignored the upstream auth password. * redis: enable_hashtaging is always enabled when the upstream uses open source Redis cluster protocol. -* redis: correctly follow MOVE/ASK redirection for mirrored clusters. -* redis: performance improvement for larger split commands by avoiding string copies. -* regex: introduce new :ref:`RegexMatcher ` type that * regex: introduced new :ref:`RegexMatcher ` type that provides a safe regex implementation for untrusted user input. This type is now used in all configuration that processes user provided input. See :ref:`deprecated configuration details diff --git a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc index 1d9ea00e2c62..9938dbd11304 100644 --- a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc +++ b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc @@ -197,10 +197,10 @@ SplitRequestPtr MGETRequest::create(Router& router, Common::Redis::RespValuePtr& const auto route = router.upstreamPool(base_request->asArray()[i].asString()); if (route) { // Create composite array for a single get. - Common::Redis::RespValue single_get(base_request, - Common::Redis::Utility::GetRequest::instance(), i, i); + const Common::Redis::RespValue single_mget( + base_request, Common::Redis::Utility::GetRequest::instance(), i, i); pending_request.handle_ = makeFragmentedRequest( - route, "get", base_request->asArray()[i].asString(), single_get, pending_request); + route, "get", base_request->asArray()[i].asString(), single_mget, pending_request); } if (!pending_request.handle_) { @@ -266,16 +266,16 @@ SplitRequestPtr MSETRequest::create(Router& router, Common::Redis::RespValuePtr& request_ptr->pending_response_->type(Common::Redis::RespType::SimpleString); Common::Redis::RespValueSharedPtr base_request = std::move(incoming_request); - uint64_t fragment_index = 0; - for (uint64_t i = 1; i < base_request->asArray().size(); i += 2) { + unsigned fragment_index = 0; + for (unsigned i = 1; i < base_request->asArray().size(); i += 2) { request_ptr->pending_requests_.emplace_back(*request_ptr, fragment_index++); PendingRequest& pending_request = request_ptr->pending_requests_.back(); const auto route = router.upstreamPool(base_request->asArray()[i].asString()); if (route) { // Create composite array for a single set command. - Common::Redis::RespValue single_set(base_request, - Common::Redis::Utility::SetRequest::instance(), i, i + 1); + const Common::Redis::RespValue single_set( + base_request, Common::Redis::Utility::SetRequest::instance(), i, i + 1); ENVOY_LOG(debug, "redis: parallel set: '{}'", single_set.toString()); pending_request.handle_ = makeFragmentedRequest( route, "set", base_request->asArray()[i].asString(), single_set, pending_request); @@ -337,12 +337,12 @@ SplitRequestPtr SplitKeysSumResultRequest::create(Router& router, request_ptr->pending_response_->type(Common::Redis::RespType::Integer); Common::Redis::RespValueSharedPtr base_request = std::move(incoming_request); - for (uint64_t i = 1; i < base_request->asArray().size(); i++) { + for (unsigned i = 1; i < base_request->asArray().size(); i++) { request_ptr->pending_requests_.emplace_back(*request_ptr, i - 1); PendingRequest& pending_request = request_ptr->pending_requests_.back(); // Create the composite array for a single fragment. - Common::Redis::RespValue single_fragment(base_request, base_request->asArray()[0], i, i); + const Common::Redis::RespValue single_fragment(base_request, base_request->asArray()[0], i, i); ENVOY_LOG(debug, "redis: parallel {}: '{}'", base_request->asArray()[0].asString(), single_fragment.toString()); const auto route = router.upstreamPool(base_request->asArray()[i].asString()); diff --git a/source/extensions/filters/network/redis_proxy/command_splitter_impl.h b/source/extensions/filters/network/redis_proxy/command_splitter_impl.h index ce2643f2e339..301edc9d0687 100644 --- a/source/extensions/filters/network/redis_proxy/command_splitter_impl.h +++ b/source/extensions/filters/network/redis_proxy/command_splitter_impl.h @@ -172,13 +172,9 @@ class FragmentedRequest : public SplitRequestBase { virtual void onChildResponse(Common::Redis::RespValuePtr&& value, uint32_t index) PURE; void onChildFailure(uint32_t index); - // bool onChildRedirection(const Common::Redis::RespValue& value, uint32_t index, - // const ConnPool::InstanceSharedPtr& conn_pool); - // virtual void recreate(Common::Redis::RespValue& request, uint32_t index) PURE; SplitCallbacks& callbacks_; - // Common::Redis::RespValuePtr incoming_request_; Common::Redis::RespValuePtr pending_response_; std::vector pending_requests_; uint32_t num_pending_responses_; @@ -201,7 +197,6 @@ class MGETRequest : public FragmentedRequest, Logger::Loggable Date: Mon, 11 Nov 2019 18:16:58 -0800 Subject: [PATCH 17/27] Add speed test and make respvalue const Signed-off-by: Henry Yang --- .../redis_proxy/command_splitter_impl.cc | 2 +- .../filters/network/redis_proxy/conn_pool.h | 2 +- .../network/redis_proxy/conn_pool_impl.cc | 4 +- .../redis_proxy/command_split_speed_test.cc | 39 +++++++++++++++++++ .../redis_proxy/command_splitter_impl_test.cc | 4 +- 5 files changed, 45 insertions(+), 6 deletions(-) diff --git a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc index 9938dbd11304..aa5a1afa25b5 100644 --- a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc +++ b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc @@ -25,7 +25,7 @@ ConnPool::DoNothingPoolCallbacks null_pool_callbacks; */ Common::Redis::Client::PoolRequest* makeSingleServerRequest(const RouteSharedPtr& route, const std::string& command, - const std::string& key, Common::Redis::RespValueSharedPtr incoming_request, + const std::string& key, Common::Redis::RespValueConstSharedPtr incoming_request, ConnPool::PoolCallbacks& callbacks) { auto handler = route->upstream()->makeRequest(key, ConnPool::RespVariant(incoming_request), callbacks); diff --git a/source/extensions/filters/network/redis_proxy/conn_pool.h b/source/extensions/filters/network/redis_proxy/conn_pool.h index b39675df5ee3..e6c52cd73e68 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool.h +++ b/source/extensions/filters/network/redis_proxy/conn_pool.h @@ -41,7 +41,7 @@ class PoolCallbacks { * resp value. This is for performance reason to avoid creating RespValueSharedPtr for each * composite arrays. */ -using RespVariant = absl::variant; +using RespVariant = absl::variant; /** * A redis connection pool. Wraps M connections to N upstream hosts, consistent hashing, diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc index ff283c71249c..065a175bfdca 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc @@ -22,9 +22,9 @@ Common::Redis::Client::DoNothingPoolCallbacks null_client_callbacks; const Common::Redis::RespValue& getRequest(const RespVariant& request) { if (request.index() == 0) { - return absl::get(request); + return absl::get(request); } else { - return *(absl::get(request)); + return *(absl::get(request)); } } } // namespace diff --git a/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc b/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc index 93927de22c41..324d352bf72b 100644 --- a/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc @@ -43,6 +43,22 @@ class CommandSplitSpeedTest { return request; } + using ValueOrPointer = absl::variant; + + void createShared(Common::Redis::RespValueSharedPtr request) { + for (uint64_t i = 1; i < request->asArray().size(); i += 2) { + auto single_set = std::make_shared( + request, Common::Redis::Utility::SetRequest::instance(), i, i + 2); + } + } + + void createVariant(Common::Redis::RespValueSharedPtr request) { + for (uint64_t i = 1; i < request->asArray().size(); i += 2) { + Common::Redis::RespValue single_set(request, Common::Redis::Utility::SetRequest::instance(), + i, i + 1); + ValueOrPointer variant(single_set); + } + } void createLocalCompositeArray(Common::Redis::RespValueSharedPtr& request) { for (uint64_t i = 1; i < request->asArray().size(); i += 2) { @@ -92,6 +108,29 @@ static void BM_Split_Copy(benchmark::State& state) { } BENCHMARK(BM_Split_Copy)->Ranges({{1, 100}, {64, 8 << 14}}); +static void BM_Split_CreateShared(benchmark::State& state) { + Envoy::Extensions::NetworkFilters::RedisProxy::CommandSplitSpeedTest context; + Envoy::Extensions::NetworkFilters::Common::Redis::RespValueSharedPtr request = + context.makeSharedBulkStringArray(state.range(0), 36, state.range(1)); + for (auto _ : state) { + context.createShared(request); + } + state.counters["use_count"] = request.use_count(); +} +BENCHMARK(BM_Split_CreateShared)->Ranges({{1, 100}, {64, 8 << 14}}); + +static void BM_Split_CreateVariant(benchmark::State& state) { + Envoy::Extensions::NetworkFilters::RedisProxy::CommandSplitSpeedTest context; + Envoy::Extensions::NetworkFilters::Common::Redis::RespValueSharedPtr request = + context.makeSharedBulkStringArray(state.range(0), 36, state.range(1)); + for (auto _ : state) { + context.createVariant(request); + } + state.counters["use_count"] = request.use_count(); +} +BENCHMARK(BM_Split_CreateVariant)->Ranges({{1, 100}, {64, 8 << 14}}); + + // Boilerplate main(), which discovers benchmarks in the same file and runs them. int main(int argc, char** argv) { benchmark::Initialize(&argc, argv); diff --git a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc index bd789e7a2a7a..d48df8a03647 100644 --- a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc @@ -151,7 +151,7 @@ TEST_F(RedisCommandSplitterImplTest, UnsupportedCommand) { MATCHER_P(RespVariantEq, rhs, "RespVariant should be equal") { const ConnPool::RespVariant& obj = arg; EXPECT_EQ(obj.index(), 1); - EXPECT_EQ(*(absl::get(obj)), rhs); + EXPECT_EQ(*(absl::get(obj)), rhs); return true; } @@ -397,7 +397,7 @@ TEST_F(RedisSingleServerRequestTest, EvalNoUpstream) { MATCHER_P(CompositeArrayEq, rhs, "CompositeArray should be equal") { const ConnPool::RespVariant& obj = arg; - const auto& lhs = absl::get(obj); + const auto& lhs = absl::get(obj); EXPECT_TRUE(lhs.type() == Common::Redis::RespType::CompositeArray); EXPECT_EQ(lhs.asCompositeArray().size(), rhs.size()); std::vector array; From 36d7190fe36169d03f6a23fc77e4d5d05e517210 Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Mon, 11 Nov 2019 18:23:04 -0800 Subject: [PATCH 18/27] fix format Signed-off-by: Henry Yang --- .../filters/network/redis_proxy/command_splitter_impl.cc | 7 +++---- source/extensions/filters/network/redis_proxy/conn_pool.h | 3 ++- .../network/redis_proxy/command_split_speed_test.cc | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc index aa5a1afa25b5..69c5753ec74a 100644 --- a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc +++ b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc @@ -23,10 +23,9 @@ ConnPool::DoNothingPoolCallbacks null_pool_callbacks; * @return PoolRequest* a handle to the active request or nullptr if the request could not be made * for some reason. */ -Common::Redis::Client::PoolRequest* -makeSingleServerRequest(const RouteSharedPtr& route, const std::string& command, - const std::string& key, Common::Redis::RespValueConstSharedPtr incoming_request, - ConnPool::PoolCallbacks& callbacks) { +Common::Redis::Client::PoolRequest* makeSingleServerRequest( + const RouteSharedPtr& route, const std::string& command, const std::string& key, + Common::Redis::RespValueConstSharedPtr incoming_request, ConnPool::PoolCallbacks& callbacks) { auto handler = route->upstream()->makeRequest(key, ConnPool::RespVariant(incoming_request), callbacks); if (handler) { diff --git a/source/extensions/filters/network/redis_proxy/conn_pool.h b/source/extensions/filters/network/redis_proxy/conn_pool.h index e6c52cd73e68..0fa1e68bec96 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool.h +++ b/source/extensions/filters/network/redis_proxy/conn_pool.h @@ -41,7 +41,8 @@ class PoolCallbacks { * resp value. This is for performance reason to avoid creating RespValueSharedPtr for each * composite arrays. */ -using RespVariant = absl::variant; +using RespVariant = + absl::variant; /** * A redis connection pool. Wraps M connections to N upstream hosts, consistent hashing, diff --git a/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc b/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc index 324d352bf72b..1ee376e32c30 100644 --- a/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc @@ -43,7 +43,8 @@ class CommandSplitSpeedTest { return request; } - using ValueOrPointer = absl::variant; + using ValueOrPointer = + absl::variant; void createShared(Common::Redis::RespValueSharedPtr request) { for (uint64_t i = 1; i < request->asArray().size(); i += 2) { @@ -130,7 +131,6 @@ static void BM_Split_CreateVariant(benchmark::State& state) { } BENCHMARK(BM_Split_CreateVariant)->Ranges({{1, 100}, {64, 8 << 14}}); - // Boilerplate main(), which discovers benchmarks in the same file and runs them. int main(int argc, char** argv) { benchmark::Initialize(&argc, argv); From ac9ba4e352961105e21e2b09089273989e7a715f Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Tue, 12 Nov 2019 18:30:31 -0800 Subject: [PATCH 19/27] update command splitter test Signed-off-by: Henry Yang --- .../redis_proxy/command_splitter_impl_test.cc | 363 ++++++++++++------ .../filters/network/redis_proxy/mocks.cc | 11 +- .../filters/network/redis_proxy/mocks.h | 16 +- 3 files changed, 264 insertions(+), 126 deletions(-) diff --git a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc index d48df8a03647..dc0ae9fd9d63 100644 --- a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc @@ -29,17 +29,6 @@ namespace NetworkFilters { namespace RedisProxy { namespace CommandSplitter { -class PassthruRouter : public Router { -public: - PassthruRouter(ConnPool::InstanceSharedPtr conn_pool) - : route_(std::make_shared>(conn_pool)) {} - - RouteSharedPtr upstreamPool(std::string&) override { return route_; } - -private: - RouteSharedPtr route_; -}; - class RedisCommandSplitterImplTest : public testing::Test { public: void makeBulkStringArray(Common::Redis::RespValue& value, @@ -54,11 +43,20 @@ class RedisCommandSplitterImplTest : public testing::Test { value.asArray().swap(values); } + void setupMirrorPolicy() { + auto mirror_policy = std::make_shared>(mirror_conn_pool_shared_ptr_); + route_->policies_.push_back(mirror_policy); + } + ConnPool::MockInstance* conn_pool_{new ConnPool::MockInstance()}; + ConnPool::MockInstance* mirror_conn_pool_{new ConnPool::MockInstance()}; + ConnPool::InstanceSharedPtr mirror_conn_pool_shared_ptr_{mirror_conn_pool_}; + std::shared_ptr> route_{ + new NiceMock(ConnPool::InstanceSharedPtr{conn_pool_})}; NiceMock store_; Event::SimulatedTimeSystem time_system_; - InstanceImpl splitter_{std::make_unique(ConnPool::InstanceSharedPtr{conn_pool_}), - store_, "redis.foo.", time_system_, false}; + InstanceImpl splitter_{std::make_unique>(route_), store_, "redis.foo.", + time_system_, false}; MockSplitCallbacks callbacks_; SplitRequestPtr handle_; }; @@ -158,10 +156,16 @@ MATCHER_P(RespVariantEq, rhs, "RespVariant should be equal") { class RedisSingleServerRequestTest : public RedisCommandSplitterImplTest, public testing::WithParamInterface { public: - void makeRequest(const std::string& hash_key, Common::Redis::RespValuePtr&& request) { + void makeRequest(const std::string& hash_key, Common::Redis::RespValuePtr&& request, + bool mirrored = false) { EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); EXPECT_CALL(*conn_pool_, makeRequest_(hash_key, RespVariantEq(*request), _)) .WillOnce(DoAll(WithArg<2>(SaveArgAddress(&pool_callbacks_)), Return(&pool_request_))); + if (mirrored) { + EXPECT_CALL(*mirror_conn_pool_, makeRequest_(hash_key, RespVariantEq(*request), _)) + .WillOnce(DoAll(WithArg<2>(SaveArgAddress(&mirror_pool_callbacks_)), + Return(&mirror_pool_request_))); + } handle_ = splitter_.makeRequest(std::move(request), callbacks_); } @@ -173,15 +177,23 @@ class RedisSingleServerRequestTest : public RedisCommandSplitterImplTest, pool_callbacks_->onFailure(); } - void respond() { + void respond(bool mirrored = false) { Common::Redis::RespValuePtr response1(new Common::Redis::RespValue()); Common::Redis::RespValue* response1_ptr = response1.get(); - EXPECT_CALL(callbacks_, onResponse_(PointeesEq(response1_ptr))); - pool_callbacks_->onResponse(std::move(response1)); + if (mirrored) { + // expect no-opt for mirrored requests + mirror_pool_callbacks_->onResponse(std::move(response1)); + } else { + EXPECT_CALL(callbacks_, onResponse_(PointeesEq(response1_ptr))); + pool_callbacks_->onResponse(std::move(response1)); + } } ConnPool::PoolCallbacks* pool_callbacks_; Common::Redis::Client::MockPoolRequest pool_request_; + + ConnPool::PoolCallbacks* mirror_pool_callbacks_; + Common::Redis::Client::MockPoolRequest mirror_pool_request_; }; TEST_P(RedisSingleServerRequestTest, Success) { @@ -208,6 +220,61 @@ TEST_P(RedisSingleServerRequestTest, Success) { store_.counter(fmt::format("redis.foo.command.{}.success", lower_command)).value()); }; +TEST_P(RedisSingleServerRequestTest, Mirrored) { + InSequence s; + + setupMirrorPolicy(); + + ToLowerTable table; + std::string lower_command(GetParam()); + table.toLowerCase(lower_command); + + Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; + makeBulkStringArray(*request, {GetParam(), "hello"}); + makeRequest("hello", std::move(request), true); + EXPECT_NE(nullptr, handle_); + + time_system_.setMonotonicTime(std::chrono::milliseconds(10)); + EXPECT_CALL(store_, deliverHistogramToSinks( + Property(&Stats::Metric::name, + fmt::format("redis.foo.command.{}.latency", lower_command)), + 10)); + respond(); + respond(true); + + EXPECT_EQ(1UL, store_.counter(fmt::format("redis.foo.command.{}.total", lower_command)).value()); + EXPECT_EQ(1UL, + store_.counter(fmt::format("redis.foo.command.{}.success", lower_command)).value()); +}; + +TEST_P(RedisSingleServerRequestTest, MirroredFailed) { + InSequence s; + + setupMirrorPolicy(); + + ToLowerTable table; + std::string lower_command(GetParam()); + table.toLowerCase(lower_command); + + Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; + makeBulkStringArray(*request, {GetParam(), "hello"}); + makeRequest("hello", std::move(request), true); + EXPECT_NE(nullptr, handle_); + + time_system_.setMonotonicTime(std::chrono::milliseconds(10)); + EXPECT_CALL(store_, deliverHistogramToSinks( + Property(&Stats::Metric::name, + fmt::format("redis.foo.command.{}.latency", lower_command)), + 10)); + // Mirrored request failure should not result in main path failure + mirror_pool_callbacks_->onFailure(); + respond(); + + EXPECT_EQ(1UL, store_.counter(fmt::format("redis.foo.command.{}.total", lower_command)).value()); + EXPECT_EQ(1UL, + store_.counter(fmt::format("redis.foo.command.{}.success", lower_command)).value()); +}; + TEST_P(RedisSingleServerRequestTest, SuccessMultipleArgs) { InSequence s; @@ -408,34 +475,44 @@ MATCHER_P(CompositeArrayEq, rhs, "CompositeArray should be equal") { return true; } -class RedisMGETCommandHandlerTest : public RedisCommandSplitterImplTest { +class FragmentedRequestCommandHandlerTest : public RedisCommandSplitterImplTest { public: - void setup(uint32_t num_gets, const std::list& null_handle_indexes) { - std::vector request_strings = {"mget"}; - for (uint32_t i = 0; i < num_gets; i++) { - request_strings.push_back(std::to_string(i)); - } + void makeRequest(std::vector& request_strings, + const std::list& null_handle_indexes, bool mirrored) { + uint32_t num_gets = expected_requests_.size(); Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; makeBulkStringArray(*request, request_strings); - expected_requests_.reserve(num_gets); pool_callbacks_.resize(num_gets); + mirror_pool_callbacks_.resize(num_gets); std::vector tmp_pool_requests(num_gets); pool_requests_.swap(tmp_pool_requests); + std::vector tmp_mirrored_pool_requests(num_gets); + mirror_pool_requests_.swap(tmp_mirrored_pool_requests); EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); for (uint32_t i = 0; i < num_gets; i++) { - expected_requests_.push_back({"get", std::to_string(i)}); Common::Redis::Client::PoolRequest* request_to_use = nullptr; if (std::find(null_handle_indexes.begin(), null_handle_indexes.end(), i) == null_handle_indexes.end()) { request_to_use = &pool_requests_[i]; } + Common::Redis::Client::PoolRequest* mirror_request_to_use = nullptr; + if (std::find(null_handle_indexes.begin(), null_handle_indexes.end(), i) == + null_handle_indexes.end()) { + mirror_request_to_use = &mirror_request_to_use[i]; + } EXPECT_CALL(*conn_pool_, makeRequest_(std::to_string(i), CompositeArrayEq(expected_requests_[i]), _)) .WillOnce(DoAll(WithArg<2>(SaveArgAddress(&pool_callbacks_[i])), Return(request_to_use))); + if (mirrored) { + EXPECT_CALL(*mirror_conn_pool_, + makeRequest_(std::to_string(i), CompositeArrayEq(expected_requests_[i]), _)) + .WillOnce(DoAll(WithArg<2>(SaveArgAddress(&mirror_pool_callbacks_[i])), + Return(mirror_request_to_use))); + } } handle_ = splitter_.makeRequest(std::move(request), callbacks_); @@ -444,6 +521,29 @@ class RedisMGETCommandHandlerTest : public RedisCommandSplitterImplTest { std::vector> expected_requests_; std::vector pool_callbacks_; std::vector pool_requests_; + std::vector mirror_pool_callbacks_; + std::vector mirror_pool_requests_; +}; + +class RedisMGETCommandHandlerTest : public FragmentedRequestCommandHandlerTest { +public: + void setup(uint32_t num_gets, const std::list& null_handle_indexes, + bool mirrored = false) { + expected_requests_.reserve(num_gets); + std::vector request_strings = {"mget"}; + for (uint32_t i = 0; i < num_gets; i++) { + request_strings.push_back(std::to_string(i)); + expected_requests_.push_back({"get", std::to_string(i)}); + } + makeRequest(request_strings, null_handle_indexes, mirrored); + } + + Common::Redis::RespValuePtr response(const std::string& result) { + Common::Redis::RespValuePtr response = std::make_unique(); + response->type(Common::Redis::RespType::BulkString); + response->asString() = result; + return response; + } }; TEST_F(RedisMGETCommandHandlerTest, Normal) { @@ -461,19 +561,43 @@ TEST_F(RedisMGETCommandHandlerTest, Normal) { elements[1].asString() = "5"; expected_response.asArray().swap(elements); - Common::Redis::RespValuePtr response2(new Common::Redis::RespValue()); - response2->type(Common::Redis::RespType::BulkString); - response2->asString() = "5"; - pool_callbacks_[1]->onResponse(std::move(response2)); + pool_callbacks_[1]->onResponse(response("5")); - Common::Redis::RespValuePtr response1(new Common::Redis::RespValue()); - response1->type(Common::Redis::RespType::BulkString); - response1->asString() = "response"; time_system_.setMonotonicTime(std::chrono::milliseconds(10)); EXPECT_CALL(store_, deliverHistogramToSinks( Property(&Stats::Metric::name, "redis.foo.command.mget.latency"), 10)); EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&expected_response))); - pool_callbacks_[0]->onResponse(std::move(response1)); + pool_callbacks_[0]->onResponse(response("response")); + + EXPECT_EQ(1UL, store_.counter("redis.foo.command.mget.total").value()); + EXPECT_EQ(1UL, store_.counter("redis.foo.command.mget.success").value()); +}; + +TEST_F(RedisMGETCommandHandlerTest, Mirrored) { + InSequence s; + + setupMirrorPolicy(); + setup(2, {}, true); + EXPECT_NE(nullptr, handle_); + + Common::Redis::RespValue expected_response; + expected_response.type(Common::Redis::RespType::Array); + std::vector elements(2); + elements[0].type(Common::Redis::RespType::BulkString); + elements[0].asString() = "response"; + elements[1].type(Common::Redis::RespType::BulkString); + elements[1].asString() = "5"; + expected_response.asArray().swap(elements); + + pool_callbacks_[1]->onResponse(response("5")); + mirror_pool_callbacks_[1]->onResponse(response("5")); + + time_system_.setMonotonicTime(std::chrono::milliseconds(10)); + EXPECT_CALL(store_, deliverHistogramToSinks( + Property(&Stats::Metric::name, "redis.foo.command.mget.latency"), 10)); + EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&expected_response))); + pool_callbacks_[0]->onResponse(response("response")); + mirror_pool_callbacks_[0]->onResponse(response("response")); EXPECT_EQ(1UL, store_.counter("redis.foo.command.mget.total").value()); EXPECT_EQ(1UL, store_.counter("redis.foo.command.mget.success").value()); @@ -495,11 +619,8 @@ TEST_F(RedisMGETCommandHandlerTest, NormalWithNull) { Common::Redis::RespValuePtr response2(new Common::Redis::RespValue()); pool_callbacks_[1]->onResponse(std::move(response2)); - Common::Redis::RespValuePtr response1(new Common::Redis::RespValue()); - response1->type(Common::Redis::RespType::BulkString); - response1->asString() = "response"; EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&expected_response))); - pool_callbacks_[0]->onResponse(std::move(response1)); + pool_callbacks_[0]->onResponse(response("response")); }; TEST_F(RedisMGETCommandHandlerTest, NoUpstreamHostForAll) { @@ -559,14 +680,11 @@ TEST_F(RedisMGETCommandHandlerTest, Failure) { pool_callbacks_[1]->onFailure(); - Common::Redis::RespValuePtr response1(new Common::Redis::RespValue()); - response1->type(Common::Redis::RespType::BulkString); - response1->asString() = "response"; time_system_.setMonotonicTime(std::chrono::milliseconds(5)); EXPECT_CALL(store_, deliverHistogramToSinks( Property(&Stats::Metric::name, "redis.foo.command.mget.latency"), 5)); EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&expected_response))); - pool_callbacks_[0]->onResponse(std::move(response1)); + pool_callbacks_[0]->onResponse(response("response")); EXPECT_EQ(1UL, store_.counter("redis.foo.command.mget.total").value()); EXPECT_EQ(1UL, store_.counter("redis.foo.command.mget.error").value()); }; @@ -611,45 +729,30 @@ TEST_F(RedisMGETCommandHandlerTest, Cancel) { handle_->cancel(); }; -class RedisMSETCommandHandlerTest : public RedisCommandSplitterImplTest { +class RedisMSETCommandHandlerTest : public FragmentedRequestCommandHandlerTest { public: - void setup(uint32_t num_sets, const std::list& null_handle_indexes) { + void setup(uint32_t num_sets, const std::list& null_handle_indexes, + bool mirrored = false) { + + expected_requests_.reserve(num_sets); std::vector request_strings = {"mset"}; for (uint32_t i = 0; i < num_sets; i++) { // key request_strings.push_back(std::to_string(i)); // value request_strings.push_back(std::to_string(i)); - } - Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; - makeBulkStringArray(*request, request_strings); - - expected_requests_.reserve(num_sets); - pool_callbacks_.resize(num_sets); - std::vector tmp_pool_requests(num_sets); - pool_requests_.swap(tmp_pool_requests); - - EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); - - for (uint32_t i = 0; i < num_sets; i++) { expected_requests_.push_back({"set", std::to_string(i), std::to_string(i)}); - Common::Redis::Client::PoolRequest* request_to_use = nullptr; - if (std::find(null_handle_indexes.begin(), null_handle_indexes.end(), i) == - null_handle_indexes.end()) { - request_to_use = &pool_requests_[i]; - } - EXPECT_CALL(*conn_pool_, - makeRequest_(std::to_string(i), CompositeArrayEq(expected_requests_[i]), _)) - .WillOnce(DoAll(WithArg<2>(SaveArgAddress(&pool_callbacks_[i])), Return(request_to_use))); } - - handle_ = splitter_.makeRequest(std::move(request), callbacks_); + makeRequest(request_strings, null_handle_indexes, mirrored); } - std::vector> expected_requests_; - std::vector pool_callbacks_; - std::vector pool_requests_; + Common::Redis::RespValuePtr okResponse() { + Common::Redis::RespValuePtr response = std::make_unique(); + response->type(Common::Redis::RespType::SimpleString); + response->asString() = Response::get().OK; + return response; + } }; TEST_F(RedisMSETCommandHandlerTest, Normal) { @@ -662,20 +765,38 @@ TEST_F(RedisMSETCommandHandlerTest, Normal) { expected_response.type(Common::Redis::RespType::SimpleString); expected_response.asString() = Response::get().OK; - Common::Redis::RespValuePtr response2(new Common::Redis::RespValue()); - response2->type(Common::Redis::RespType::SimpleString); - response2->asString() = Response::get().OK; - pool_callbacks_[1]->onResponse(std::move(response2)); + pool_callbacks_[1]->onResponse(okResponse()); - Common::Redis::RespValuePtr response1(new Common::Redis::RespValue()); - response1->type(Common::Redis::RespType::SimpleString); - response1->asString() = Response::get().OK; + time_system_.setMonotonicTime(std::chrono::milliseconds(10)); + EXPECT_CALL(store_, deliverHistogramToSinks( + Property(&Stats::Metric::name, "redis.foo.command.mset.latency"), 10)); + EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&expected_response))); + pool_callbacks_[0]->onResponse(okResponse()); + + EXPECT_EQ(1UL, store_.counter("redis.foo.command.mset.total").value()); + EXPECT_EQ(1UL, store_.counter("redis.foo.command.mset.success").value()); +}; + +TEST_F(RedisMSETCommandHandlerTest, Mirrored) { + InSequence s; + + setupMirrorPolicy(); + setup(2, {}, true); + EXPECT_NE(nullptr, handle_); + + Common::Redis::RespValue expected_response; + expected_response.type(Common::Redis::RespType::SimpleString); + expected_response.asString() = Response::get().OK; + + pool_callbacks_[1]->onResponse(okResponse()); + mirror_pool_callbacks_[1]->onResponse(okResponse()); time_system_.setMonotonicTime(std::chrono::milliseconds(10)); EXPECT_CALL(store_, deliverHistogramToSinks( Property(&Stats::Metric::name, "redis.foo.command.mset.latency"), 10)); EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&expected_response))); - pool_callbacks_[0]->onResponse(std::move(response1)); + pool_callbacks_[0]->onResponse(okResponse()); + mirror_pool_callbacks_[0]->onResponse(okResponse()); EXPECT_EQ(1UL, store_.counter("redis.foo.command.mset.total").value()); EXPECT_EQ(1UL, store_.counter("redis.foo.command.mset.success").value()); @@ -705,11 +826,8 @@ TEST_F(RedisMSETCommandHandlerTest, NoUpstreamHostForOne) { expected_response.type(Common::Redis::RespType::Error); expected_response.asString() = "finished with 1 error(s)"; - Common::Redis::RespValuePtr response2(new Common::Redis::RespValue()); - response2->type(Common::Redis::RespType::SimpleString); - response2->asString() = Response::get().OK; EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&expected_response))); - pool_callbacks_[1]->onResponse(std::move(response2)); + pool_callbacks_[1]->onResponse(okResponse()); EXPECT_EQ(1UL, store_.counter("redis.foo.command.mset.total").value()); EXPECT_EQ(1UL, store_.counter("redis.foo.command.mset.error").value()); }; @@ -740,70 +858,75 @@ TEST_F(RedisMSETCommandHandlerTest, WrongNumberOfArgs) { EXPECT_EQ(1UL, store_.counter("redis.foo.command.mset.error").value()); }; -class RedisSplitKeysSumResultHandlerTest : public RedisCommandSplitterImplTest, +class RedisSplitKeysSumResultHandlerTest : public FragmentedRequestCommandHandlerTest, public testing::WithParamInterface { public: - void setup(uint32_t num_commands, const std::list& null_handle_indexes) { + void setup(uint32_t num_commands, const std::list& null_handle_indexes, + bool mirrored = false) { + + expected_requests_.reserve(num_commands); std::vector request_strings = {GetParam()}; for (uint32_t i = 0; i < num_commands; i++) { request_strings.push_back(std::to_string(i)); + expected_requests_.push_back({GetParam(), std::to_string(i)}); } + makeRequest(request_strings, null_handle_indexes, mirrored); + } - Common::Redis::RespValuePtr request(new Common::Redis::RespValue()); - makeBulkStringArray(*request, request_strings); + Common::Redis::RespValuePtr response(int64_t value) { + Common::Redis::RespValuePtr response = std::make_unique(); + response->type(Common::Redis::RespType::Integer); + response->asInteger() = value; + return response; + } +}; - expected_requests_.reserve(num_commands); - pool_callbacks_.resize(num_commands); - std::vector tmp_pool_requests(num_commands); - pool_requests_.swap(tmp_pool_requests); +TEST_P(RedisSplitKeysSumResultHandlerTest, Normal) { + InSequence s; - EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); + setup(2, {}); + EXPECT_NE(nullptr, handle_); - for (uint32_t i = 0; i < num_commands; i++) { - expected_requests_.push_back({GetParam(), std::to_string(i)}); - Common::Redis::Client::PoolRequest* request_to_use = nullptr; - if (std::find(null_handle_indexes.begin(), null_handle_indexes.end(), i) == - null_handle_indexes.end()) { - request_to_use = &pool_requests_[i]; - } - EXPECT_CALL(*conn_pool_, - makeRequest_(std::to_string(i), CompositeArrayEq(expected_requests_[i]), _)) - .WillOnce(DoAll(WithArg<2>(SaveArgAddress(&pool_callbacks_[i])), Return(request_to_use))); - } + Common::Redis::RespValue expected_response; + expected_response.type(Common::Redis::RespType::Integer); + expected_response.asInteger() = 2; - handle_ = splitter_.makeRequest(std::move(request), callbacks_); - } + pool_callbacks_[1]->onResponse(response(1)); - std::vector> expected_requests_; - std::vector pool_callbacks_; - std::vector pool_requests_; + time_system_.setMonotonicTime(std::chrono::milliseconds(10)); + EXPECT_CALL( + store_, + deliverHistogramToSinks( + Property(&Stats::Metric::name, "redis.foo.command." + GetParam() + ".latency"), 10)); + EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&expected_response))); + pool_callbacks_[0]->onResponse(response(1)); + + EXPECT_EQ(1UL, store_.counter("redis.foo.command." + GetParam() + ".total").value()); + EXPECT_EQ(1UL, store_.counter("redis.foo.command." + GetParam() + ".success").value()); }; -TEST_P(RedisSplitKeysSumResultHandlerTest, Normal) { +TEST_P(RedisSplitKeysSumResultHandlerTest, Mirrored) { InSequence s; - setup(2, {}); + setupMirrorPolicy(); + setup(2, {}, true); EXPECT_NE(nullptr, handle_); Common::Redis::RespValue expected_response; expected_response.type(Common::Redis::RespType::Integer); expected_response.asInteger() = 2; - Common::Redis::RespValuePtr response2(new Common::Redis::RespValue()); - response2->type(Common::Redis::RespType::Integer); - response2->asInteger() = 1; - pool_callbacks_[1]->onResponse(std::move(response2)); + pool_callbacks_[1]->onResponse(response(1)); + mirror_pool_callbacks_[1]->onResponse(response(1)); - Common::Redis::RespValuePtr response1(new Common::Redis::RespValue()); - response1->type(Common::Redis::RespType::Integer); - response1->asInteger() = 1; time_system_.setMonotonicTime(std::chrono::milliseconds(10)); EXPECT_CALL( store_, deliverHistogramToSinks( Property(&Stats::Metric::name, "redis.foo.command." + GetParam() + ".latency"), 10)); EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&expected_response))); - pool_callbacks_[0]->onResponse(std::move(response1)); + pool_callbacks_[0]->onResponse(response(1)); + mirror_pool_callbacks_[0]->onResponse(response(1)); EXPECT_EQ(1UL, store_.counter("redis.foo.command." + GetParam() + ".total").value()); EXPECT_EQ(1UL, store_.counter("redis.foo.command." + GetParam() + ".success").value()); @@ -819,16 +942,10 @@ TEST_P(RedisSplitKeysSumResultHandlerTest, NormalOneZero) { expected_response.type(Common::Redis::RespType::Integer); expected_response.asInteger() = 1; - Common::Redis::RespValuePtr response2(new Common::Redis::RespValue()); - response2->type(Common::Redis::RespType::Integer); - response2->asInteger() = 0; - pool_callbacks_[1]->onResponse(std::move(response2)); + pool_callbacks_[1]->onResponse(response(0)); - Common::Redis::RespValuePtr response1(new Common::Redis::RespValue()); - response1->type(Common::Redis::RespType::Integer); - response1->asInteger() = 1; EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&expected_response))); - pool_callbacks_[0]->onResponse(std::move(response1)); + pool_callbacks_[0]->onResponse(response(1)); EXPECT_EQ(1UL, store_.counter("redis.foo.command." + GetParam() + ".total").value()); EXPECT_EQ(1UL, store_.counter("redis.foo.command." + GetParam() + ".success").value()); diff --git a/test/extensions/filters/network/redis_proxy/mocks.cc b/test/extensions/filters/network/redis_proxy/mocks.cc index 1668e25aa3ca..d51809ba27f6 100644 --- a/test/extensions/filters/network/redis_proxy/mocks.cc +++ b/test/extensions/filters/network/redis_proxy/mocks.cc @@ -1,5 +1,6 @@ #include "mocks.h" +using testing::_; using testing::Return; using testing::ReturnRef; @@ -8,7 +9,9 @@ namespace Extensions { namespace NetworkFilters { namespace RedisProxy { -MockRouter::MockRouter() = default; +MockRouter::MockRouter(RouteSharedPtr route) : route_(std::move(route)) { + ON_CALL(*this, upstreamPool(_)).WillByDefault(Return(route_)); +} MockRouter::~MockRouter() = default; MockRoute::MockRoute(ConnPool::InstanceSharedPtr conn_pool) : conn_pool_(std::move(conn_pool)) { @@ -17,6 +20,12 @@ MockRoute::MockRoute(ConnPool::InstanceSharedPtr conn_pool) : conn_pool_(std::mo } MockRoute::~MockRoute() = default; +MockMirrorPolicy::MockMirrorPolicy(ConnPool::InstanceSharedPtr conn_pool) + : conn_pool_(std::move(conn_pool)) { + ON_CALL(*this, upstream()).WillByDefault(Return(conn_pool_)); + ON_CALL(*this, shouldMirror(_)).WillByDefault(Return(true)); +} + namespace ConnPool { MockPoolCallbacks::MockPoolCallbacks() = default; diff --git a/test/extensions/filters/network/redis_proxy/mocks.h b/test/extensions/filters/network/redis_proxy/mocks.h index 9e6597824c80..fb96d0dd646e 100644 --- a/test/extensions/filters/network/redis_proxy/mocks.h +++ b/test/extensions/filters/network/redis_proxy/mocks.h @@ -22,10 +22,11 @@ namespace RedisProxy { class MockRouter : public Router { public: - MockRouter(); + MockRouter(RouteSharedPtr route); ~MockRouter() override; MOCK_METHOD1(upstreamPool, RouteSharedPtr(std::string& key)); + RouteSharedPtr route_; }; class MockRoute : public Route { @@ -36,7 +37,18 @@ class MockRoute : public Route { MOCK_CONST_METHOD0(upstream, ConnPool::InstanceSharedPtr()); MOCK_CONST_METHOD0(mirrorPolicies, const MirrorPolicies&()); ConnPool::InstanceSharedPtr conn_pool_; - const MirrorPolicies policies_; + MirrorPolicies policies_; +}; + +class MockMirrorPolicy : public MirrorPolicy { +public: + MockMirrorPolicy(ConnPool::InstanceSharedPtr); + ~MockMirrorPolicy() = default; + + MOCK_CONST_METHOD0(upstream, ConnPool::InstanceSharedPtr()); + MOCK_CONST_METHOD1(shouldMirror, bool(const std::string&)); + + ConnPool::InstanceSharedPtr conn_pool_; }; namespace ConnPool { From 47fd4d9be872b1491751aaa45f1d096ecb328f0a Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Wed, 13 Nov 2019 06:28:52 -0800 Subject: [PATCH 20/27] Fix failing test Signed-off-by: Henry Yang --- .../redis_proxy/command_splitter_impl_test.cc | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc index dc0ae9fd9d63..b5bcc1c6fccb 100644 --- a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc @@ -31,6 +31,8 @@ namespace CommandSplitter { class RedisCommandSplitterImplTest : public testing::Test { public: + RedisCommandSplitterImplTest() : RedisCommandSplitterImplTest(false) {} + RedisCommandSplitterImplTest(bool latency_in_macro) : latency_in_micros_(latency_in_macro) {} void makeBulkStringArray(Common::Redis::RespValue& value, const std::vector& strings) { std::vector values(strings.size()); @@ -48,6 +50,7 @@ class RedisCommandSplitterImplTest : public testing::Test { route_->policies_.push_back(mirror_policy); } + const bool latency_in_micros_; ConnPool::MockInstance* conn_pool_{new ConnPool::MockInstance()}; ConnPool::MockInstance* mirror_conn_pool_{new ConnPool::MockInstance()}; ConnPool::InstanceSharedPtr mirror_conn_pool_shared_ptr_{mirror_conn_pool_}; @@ -56,7 +59,7 @@ class RedisCommandSplitterImplTest : public testing::Test { NiceMock store_; Event::SimulatedTimeSystem time_system_; InstanceImpl splitter_{std::make_unique>(route_), store_, "redis.foo.", - time_system_, false}; + time_system_, latency_in_micros_}; MockSplitCallbacks callbacks_; SplitRequestPtr handle_; }; @@ -156,6 +159,9 @@ MATCHER_P(RespVariantEq, rhs, "RespVariant should be equal") { class RedisSingleServerRequestTest : public RedisCommandSplitterImplTest, public testing::WithParamInterface { public: + RedisSingleServerRequestTest() : RedisSingleServerRequestTest(false) {} + RedisSingleServerRequestTest(bool latency_in_micros) + : RedisCommandSplitterImplTest(latency_in_micros) {} void makeRequest(const std::string& hash_key, Common::Redis::RespValuePtr&& request, bool mirrored = false) { EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); @@ -971,17 +977,7 @@ INSTANTIATE_TEST_SUITE_P( class RedisSingleServerRequestWithLatencyMicrosTest : public RedisSingleServerRequestTest { public: - void makeRequest(const std::string& hash_key, Common::Redis::RespValuePtr&& request) { - EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); - EXPECT_CALL(*conn_pool_, makeRequest_(hash_key, RespVariantEq(*request), _)) - .WillOnce(DoAll(WithArg<2>(SaveArgAddress(&pool_callbacks_)), Return(&pool_request_))); - handle_ = splitter_.makeRequest(std::move(request), callbacks_); - } - - ConnPool::MockInstance* conn_pool_{new ConnPool::MockInstance()}; - NiceMock store_; - InstanceImpl splitter_{std::make_unique(ConnPool::InstanceSharedPtr{conn_pool_}), - store_, "redis.foo.", time_system_, true}; + RedisSingleServerRequestWithLatencyMicrosTest() : RedisSingleServerRequestTest(true) {} }; TEST_P(RedisSingleServerRequestWithLatencyMicrosTest, Success) { From b6f57cc5dae2c30591683a6851fa7d5f401475df Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Thu, 14 Nov 2019 11:19:33 -0800 Subject: [PATCH 21/27] Add failure test for ConnPoolImpl Signed-off-by: Henry Yang --- .../redis_proxy/conn_pool_impl_test.cc | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc b/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc index 42e45708359f..d50cccc4964d 100644 --- a/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc @@ -312,6 +312,66 @@ TEST_F(RedisConnPoolImplTest, Basic) { tls_.shutdownThread(); }; +TEST_F(RedisConnPoolImplTest, BasicRespVariant) { + InSequence s; + + setup(); + + Common::Redis::RespValue value; + Common::Redis::Client::MockPoolRequest active_request; + MockPoolCallbacks callbacks; + Common::Redis::Client::MockClient* client = new NiceMock(); + + EXPECT_CALL(cm_.thread_local_cluster_.lb_, chooseHost(_)) + .WillOnce(Invoke([&](Upstream::LoadBalancerContext* context) -> Upstream::HostConstSharedPtr { + EXPECT_EQ(context->computeHashKey().value(), MurmurHash::murmurHash2_64("hash_key")); + EXPECT_EQ(context->metadataMatchCriteria(), nullptr); + EXPECT_EQ(context->downstreamConnection(), nullptr); + return cm_.thread_local_cluster_.lb_.host_; + })); + EXPECT_CALL(*this, create_(_)).WillOnce(Return(client)); + EXPECT_CALL(*cm_.thread_local_cluster_.lb_.host_, address()) + .WillRepeatedly(Return(test_address_)); + EXPECT_CALL(*client, makeRequest_(Eq(value), _)).WillOnce(Return(&active_request)); + Common::Redis::Client::PoolRequest* request = + conn_pool_->makeRequest("hash_key", ConnPool::RespVariant(value), callbacks); + EXPECT_NE(nullptr, request); + + EXPECT_CALL(active_request, cancel()); + EXPECT_CALL(callbacks, onFailure_()); + EXPECT_CALL(*client, close()); + tls_.shutdownThread(); +}; + +TEST_F(RedisConnPoolImplTest, ClientRequestFailed) { + InSequence s; + + setup(); + + Common::Redis::RespValue value; + MockPoolCallbacks callbacks; + Common::Redis::Client::MockClient* client = new NiceMock(); + + EXPECT_CALL(cm_.thread_local_cluster_.lb_, chooseHost(_)) + .WillOnce(Invoke([&](Upstream::LoadBalancerContext* context) -> Upstream::HostConstSharedPtr { + EXPECT_EQ(context->computeHashKey().value(), MurmurHash::murmurHash2_64("hash_key")); + EXPECT_EQ(context->metadataMatchCriteria(), nullptr); + EXPECT_EQ(context->downstreamConnection(), nullptr); + return cm_.thread_local_cluster_.lb_.host_; + })); + EXPECT_CALL(*this, create_(_)).WillOnce(Return(client)); + EXPECT_CALL(*cm_.thread_local_cluster_.lb_.host_, address()) + .WillRepeatedly(Return(test_address_)); + EXPECT_CALL(*client, makeRequest_(Eq(value), _)).WillOnce(Return(nullptr)); + Common::Redis::Client::PoolRequest* request = + conn_pool_->makeRequest("hash_key", ConnPool::RespVariant(value), callbacks); + + // the request should be null and the callback is not called + EXPECT_EQ(nullptr, request); + EXPECT_CALL(*client, close()); + tls_.shutdownThread(); +}; + TEST_F(RedisConnPoolImplTest, BasicWithReadPolicy) { testReadPolicy(envoy::config::filter::network::redis_proxy::v2:: RedisProxy_ConnPoolSettings_ReadPolicy_PREFER_MASTER, From 98ab0bcf1694ef003b754aabd322e27ad0506542 Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Thu, 14 Nov 2019 11:30:51 -0800 Subject: [PATCH 22/27] sort version_history file Signed-off-by: Henry Yang --- docs/root/intro/version_history.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index a0e112c07b01..d0e596bf7107 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -5,7 +5,6 @@ Version history ================ * access log: added FILTER_STATE :ref:`access log formatters ` and gRPC access logger. * api: remove all support for v1 -* redis: correctly follow MOVE/ASK redirection for mirrored clusters. * buffer: remove old implementation * build: official released binary is now built against libc++. * ext_authz: added :ref:`configurable ability` to send the :ref:`certificate` to the `ext_authz` service. @@ -14,6 +13,7 @@ Version history * lb_subset_config: new fallback policy for selectors: :ref:`KEYS_SUBSET` * logger: added :ref:`--log-format-escaped ` command line option to escape newline characters in application logs. * redis: performance improvement for larger split commands by avoiding string copies. +* redis: correctly follow MOVE/ASK redirection for mirrored clusters. * router: added support for REQ(header-name) :ref:`header formatter `. * server: fixed a bug in config validation for configs with runtime layers * tcp_proxy: added :ref:`ClusterWeight.metadata_match` From 93311e50143ca7c69e5a5353a4f4825e6abbde16 Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Wed, 27 Nov 2019 00:40:42 -0800 Subject: [PATCH 23/27] Change the onDirection method signature. Signed-off-by: Henry Yang --- .../extensions/clusters/redis/redis_cluster.h | 5 +- .../filters/network/common/redis/client.h | 9 +- .../network/common/redis/client_impl.cc | 25 +++-- .../filters/network/common/redis/utility.cc | 23 ----- .../filters/network/common/redis/utility.h | 15 --- .../network/redis_proxy/conn_pool_impl.cc | 16 +-- .../network/redis_proxy/conn_pool_impl.h | 3 +- .../extensions/health_checkers/redis/redis.cc | 2 +- .../extensions/health_checkers/redis/redis.h | 3 +- .../clusters/redis/redis_cluster_test.cc | 2 +- .../network/common/redis/client_impl_test.cc | 77 ++++++++++++++- .../filters/network/common/redis/mocks.h | 8 +- .../redis_proxy/conn_pool_impl_test.cc | 97 +++++-------------- .../health_checkers/redis/redis_test.cc | 4 +- 14 files changed, 141 insertions(+), 148 deletions(-) diff --git a/source/extensions/clusters/redis/redis_cluster.h b/source/extensions/clusters/redis/redis_cluster.h index e577ed8c2953..51077c3b652d 100644 --- a/source/extensions/clusters/redis/redis_cluster.h +++ b/source/extensions/clusters/redis/redis_cluster.h @@ -229,7 +229,10 @@ class RedisCluster : public Upstream::BaseDynamicClusterImpl { void onResponse(NetworkFilters::Common::Redis::RespValuePtr&& value) override; void onFailure() override; // Note: Below callback isn't used in topology updates - bool onRedirection(NetworkFilters::Common::Redis::RespValuePtr&&) override { return true; } + bool onRedirection(NetworkFilters::Common::Redis::RespValuePtr&&, const std::string&, + bool) override { + return true; + } void onUnexpectedResponse(const NetworkFilters::Common::Redis::RespValuePtr&); Network::Address::InstanceConstSharedPtr diff --git a/source/extensions/filters/network/common/redis/client.h b/source/extensions/filters/network/common/redis/client.h index e100b18e0491..b420438ac55f 100644 --- a/source/extensions/filters/network/common/redis/client.h +++ b/source/extensions/filters/network/common/redis/client.h @@ -48,9 +48,12 @@ class ClientCallbacks { /** * Called when a MOVED or ASK redirection error is received, and the request must be retried. * @param value supplies the MOVED error response + * @param host_address supplies the redirection host address and port + * @param ask_redirection indicates if this is a ASK redirection * @return bool true if the request is successfully redirected, false otherwise */ - virtual bool onRedirection(RespValuePtr&& value) PURE; + virtual bool onRedirection(RespValuePtr&& value, const std::string& host_address, + bool ask_redirection) PURE; }; /** @@ -62,7 +65,9 @@ class DoNothingPoolCallbacks : public ClientCallbacks { // ClientCallbacks void onResponse(Common::Redis::RespValuePtr&&) override {} void onFailure() override {} - bool onRedirection(Common::Redis::RespValuePtr&&) override { return false; } + bool onRedirection(Common::Redis::RespValuePtr&&, const std::string&, bool) override { + return false; + } }; /** diff --git a/source/extensions/filters/network/common/redis/client_impl.cc b/source/extensions/filters/network/common/redis/client_impl.cc index d0d3411771a5..64674cee95f9 100644 --- a/source/extensions/filters/network/common/redis/client_impl.cc +++ b/source/extensions/filters/network/common/redis/client_impl.cc @@ -224,17 +224,26 @@ void ClientImpl::onRespValue(RespValuePtr&& value) { } else if (config_.enableRedirection() && (value->type() == Common::Redis::RespType::Error)) { std::vector err = StringUtil::splitToken(value->asString(), " ", false); bool redirected = false; + bool redirect_succeeded = false; if (err.size() == 3) { - if (err[0] == RedirectionResponse::get().MOVED || err[0] == RedirectionResponse::get().ASK) { - redirected = callbacks.onRedirection(std::move(value)); - if (redirected) { - host_->cluster().stats().upstream_internal_redirect_succeeded_total_.inc(); - } else { - host_->cluster().stats().upstream_internal_redirect_failed_total_.inc(); - } + // MOVED and ASK redirection errors have the following substrings: MOVED or ASK (err[0]), hash + // key slot (err[1]), and IP address and TCP port separated by a colon (err[2]) + if (err[0] == RedirectionResponse::get().MOVED) { + redirected = true; + redirect_succeeded = callbacks.onRedirection(std::move(value), std::string(err[2]), false); + } else if (err[0] == RedirectionResponse::get().ASK) { + redirected = true; + redirect_succeeded = callbacks.onRedirection(std::move(value), std::string(err[2]), true); } - } else { + } + if (!redirected) { callbacks.onResponse(std::move(value)); + } else { + if (redirect_succeeded) { + host_->cluster().stats().upstream_internal_redirect_succeeded_total_.inc(); + } else { + host_->cluster().stats().upstream_internal_redirect_failed_total_.inc(); + } } } else { callbacks.onResponse(std::move(value)); diff --git a/source/extensions/filters/network/common/redis/utility.cc b/source/extensions/filters/network/common/redis/utility.cc index 2778b97381f1..c652addb3e12 100644 --- a/source/extensions/filters/network/common/redis/utility.cc +++ b/source/extensions/filters/network/common/redis/utility.cc @@ -19,29 +19,6 @@ AuthRequest::AuthRequest(const std::string& password) { asArray().swap(values); } -bool redirectionArgsInvalid(const Common::Redis::RespValue* original_request, - const Common::Redis::RespValue& error_response, - std::vector& error_substrings, - bool& ask_redirection) { - if ((original_request == nullptr) || (error_response.type() != Common::Redis::RespType::Error)) { - return true; - } - error_substrings = StringUtil::splitToken(error_response.asString(), " ", false); - if (error_substrings.size() != 3) { - return true; - } - if (error_substrings[0] == "ASK") { - ask_redirection = true; - } else if (error_substrings[0] == "MOVED") { - ask_redirection = false; - } else { - // The first substring must be MOVED or ASK. - return true; - } - // Other validation done later to avoid duplicate processing. - return false; -} - RespValuePtr makeError(const std::string& error) { Common::Redis::RespValuePtr response(new RespValue()); response->type(Common::Redis::RespType::Error); diff --git a/source/extensions/filters/network/common/redis/utility.h b/source/extensions/filters/network/common/redis/utility.h index a6f958155043..b2e77b8e94ab 100644 --- a/source/extensions/filters/network/common/redis/utility.h +++ b/source/extensions/filters/network/common/redis/utility.h @@ -16,21 +16,6 @@ class AuthRequest : public Redis::RespValue { AuthRequest(const std::string& password); }; -/** - * Validate the received moved/ask redirection error and the original redis request. - * @param[in] original_request supplies the incoming request associated with the command splitter - * request. - * @param[in] error_response supplies the moved/ask redirection response from the upstream Redis - * server. - * @param[out] error_substrings the non-whitespace substrings of error_response. - * @param[out] ask_redirection true if error_response is an ASK redirection error, false otherwise. - * @return bool true if the original_request or error_response are not valid, false otherwise. - */ -bool redirectionArgsInvalid(const Common::Redis::RespValue* original_request, - const Common::Redis::RespValue& error_response, - std::vector& error_substrings, - bool& ask_redirection); - RespValuePtr makeError(const std::string& error); class ReadOnlyRequest : public Redis::RespValue { diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc index 065a175bfdca..96b1e5ed1907 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc @@ -376,19 +376,9 @@ void InstanceImpl::PendingRequest::onFailure() { parent_.onRequestCompleted(); } -bool InstanceImpl::PendingRequest::onRedirection(Common::Redis::RespValuePtr&& value) { - std::vector err; - bool ask_redirection = false; - if (Common::Redis::Utility::redirectionArgsInvalid(&getRequest(incoming_request_), *value, err, - ask_redirection)) { - onResponse(std::move(value)); - return false; - } - - // MOVED and ASK redirection errors have the following substrings: MOVED or ASK (err[0]), hash key - // slot (err[1]), and IP address and TCP port separated by a colon (err[2]). - const std::string host_address = std::string(err[2]); - +bool InstanceImpl::PendingRequest::onRedirection(Common::Redis::RespValuePtr&& value, + const std::string& host_address, + bool ask_redirection) { // Prepend request with an asking command if redirected via an ASK error. The returned handle is // not important since there is no point in being able to cancel the request. The use of // null_pool_callbacks ensures the transparent filtering of the Redis server's response to the diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h index 907533f40e57..bf6f997a01ee 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h @@ -110,7 +110,8 @@ class InstanceImpl : public Instance { // Common::Redis::Client::ClientCallbacks void onResponse(Common::Redis::RespValuePtr&& response) override; void onFailure() override; - bool onRedirection(Common::Redis::RespValuePtr&& value) override; + bool onRedirection(Common::Redis::RespValuePtr&& value, const std::string& host_address, + bool ask_redirection) override; // PoolRequest void cancel() override; diff --git a/source/extensions/health_checkers/redis/redis.cc b/source/extensions/health_checkers/redis/redis.cc index 4c953be6391a..0ba52b6dbd4f 100644 --- a/source/extensions/health_checkers/redis/redis.cc +++ b/source/extensions/health_checkers/redis/redis.cc @@ -113,7 +113,7 @@ void RedisHealthChecker::RedisActiveHealthCheckSession::onFailure() { } bool RedisHealthChecker::RedisActiveHealthCheckSession::onRedirection( - NetworkFilters::Common::Redis::RespValuePtr&&) { + NetworkFilters::Common::Redis::RespValuePtr&&, const std::string&, bool) { // Treat any redirection error response from a Redis server as success. current_request_ = nullptr; handleSuccess(); diff --git a/source/extensions/health_checkers/redis/redis.h b/source/extensions/health_checkers/redis/redis.h index 8b11d3df6fb8..25f2b562134a 100644 --- a/source/extensions/health_checkers/redis/redis.h +++ b/source/extensions/health_checkers/redis/redis.h @@ -88,7 +88,8 @@ class RedisHealthChecker : public Upstream::HealthCheckerImplBase { // Extensions::NetworkFilters::Common::Redis::Client::ClientCallbacks void onResponse(NetworkFilters::Common::Redis::RespValuePtr&& value) override; void onFailure() override; - bool onRedirection(NetworkFilters::Common::Redis::RespValuePtr&& value) override; + bool onRedirection(NetworkFilters::Common::Redis::RespValuePtr&&, const std::string&, + bool) override; // Network::ConnectionCallbacks void onEvent(Network::ConnectionEvent event) override; diff --git a/test/extensions/clusters/redis/redis_cluster_test.cc b/test/extensions/clusters/redis/redis_cluster_test.cc index 16830cfead4e..8096fa9f26e5 100644 --- a/test/extensions/clusters/redis/redis_cluster_test.cc +++ b/test/extensions/clusters/redis/redis_cluster_test.cc @@ -510,7 +510,7 @@ class RedisClusterTest : public testing::Test, new NetworkFilters::Common::Redis::RespValue()}; dummy_value->type(NetworkFilters::Common::Redis::RespType::Error); dummy_value->asString() = "dummy text"; - EXPECT_TRUE(discovery_session.onRedirection(std::move(dummy_value))); + EXPECT_TRUE(discovery_session.onRedirection(std::move(dummy_value), "dummy ip", false)); RedisCluster::RedisDiscoveryClient discovery_client(discovery_session); EXPECT_NO_THROW(discovery_client.onAboveWriteBufferHighWatermark()); diff --git a/test/extensions/filters/network/common/redis/client_impl_test.cc b/test/extensions/filters/network/common/redis/client_impl_test.cc index 7e563accd6cf..bf2a9a218fb2 100644 --- a/test/extensions/filters/network/common/redis/client_impl_test.cc +++ b/test/extensions/filters/network/common/redis/client_impl_test.cc @@ -666,7 +666,8 @@ TEST_F(RedisClientImplTest, AskRedirection) { // The exact values of the hash slot and IP info are not important. response1->asString() = "ASK 1111 10.1.2.3:4321"; // Simulate redirection failure. - EXPECT_CALL(callbacks1, onRedirection_(Ref(response1))).WillOnce(Return(false)); + EXPECT_CALL(callbacks1, onRedirection_(Ref(response1), "10.1.2.3:4321", true)) + .WillOnce(Return(false)); EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); EXPECT_CALL(host_->outlier_detector_, putResult(Upstream::Outlier::Result::ExtOriginRequestSuccess, _)); @@ -678,7 +679,8 @@ TEST_F(RedisClientImplTest, AskRedirection) { response2->type(Common::Redis::RespType::Error); // The exact values of the hash slot and IP info are not important. response2->asString() = "ASK 2222 10.1.2.4:4321"; - EXPECT_CALL(callbacks2, onRedirection_(Ref(response2))).WillOnce(Return(true)); + EXPECT_CALL(callbacks2, onRedirection_(Ref(response2), "10.1.2.4:4321", true)) + .WillOnce(Return(true)); EXPECT_CALL(*connect_or_op_timer_, disableTimer()); EXPECT_CALL(host_->outlier_detector_, putResult(Upstream::Outlier::Result::ExtOriginRequestSuccess, _)); @@ -727,7 +729,8 @@ TEST_F(RedisClientImplTest, MovedRedirection) { // The exact values of the hash slot and IP info are not important. response1->asString() = "MOVED 1111 10.1.2.3:4321"; // Simulate redirection failure. - EXPECT_CALL(callbacks1, onRedirection_(Ref(response1))).WillOnce(Return(false)); + EXPECT_CALL(callbacks1, onRedirection_(Ref(response1), "10.1.2.3:4321", false)) + .WillOnce(Return(false)); EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); EXPECT_CALL(host_->outlier_detector_, putResult(Upstream::Outlier::Result::ExtOriginRequestSuccess, _)); @@ -739,7 +742,8 @@ TEST_F(RedisClientImplTest, MovedRedirection) { response2->type(Common::Redis::RespType::Error); // The exact values of the hash slot and IP info are not important. response2->asString() = "MOVED 2222 10.1.2.4:4321"; - EXPECT_CALL(callbacks2, onRedirection_(Ref(response2))).WillOnce(Return(true)); + EXPECT_CALL(callbacks2, onRedirection_(Ref(response2), "10.1.2.4:4321", false)) + .WillOnce(Return(true)); EXPECT_CALL(*connect_or_op_timer_, disableTimer()); EXPECT_CALL(host_->outlier_detector_, putResult(Upstream::Outlier::Result::ExtOriginRequestSuccess, _)); @@ -754,6 +758,71 @@ TEST_F(RedisClientImplTest, MovedRedirection) { client_->close(); } +TEST_F(RedisClientImplTest, RedirectionFailure) { + InSequence s; + + setup(); + + Common::Redis::RespValue request1; + MockClientCallbacks callbacks1; + EXPECT_CALL(*encoder_, encode(Ref(request1), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); + EXPECT_NE(nullptr, handle1); + + onConnected(); + + Common::Redis::RespValue request2; + MockClientCallbacks callbacks2; + EXPECT_CALL(*encoder_, encode(Ref(request2), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + PoolRequest* handle2 = client_->makeRequest(request2, callbacks2); + EXPECT_NE(nullptr, handle2); + + EXPECT_EQ(2UL, host_->cluster_.stats_.upstream_rq_total_.value()); + EXPECT_EQ(2UL, host_->cluster_.stats_.upstream_rq_active_.value()); + EXPECT_EQ(2UL, host_->stats_.rq_total_.value()); + EXPECT_EQ(2UL, host_->stats_.rq_active_.value()); + + Buffer::OwnedImpl fake_data; + EXPECT_CALL(*decoder_, decode(Ref(fake_data))).WillOnce(Invoke([&](Buffer::Instance&) -> void { + InSequence s; + + // Test an error that looks like it might be a MOVED or ASK redirection error except for the + // first non-whitespace substring. + Common::Redis::RespValuePtr response1{new Common::Redis::RespValue()}; + response1->type(Common::Redis::RespType::Error); + response1->asString() = "NOTMOVEDORASK 1111 1.1.1.1:1"; + + EXPECT_CALL(callbacks1, onResponse_(Ref(response1))); + EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); + EXPECT_CALL(host_->outlier_detector_, + putResult(Upstream::Outlier::Result::ExtOriginRequestSuccess, _)); + callbacks_->onRespValue(std::move(response1)); + + EXPECT_EQ(0UL, host_->cluster_.stats_.upstream_internal_redirect_succeeded_total_.value()); + EXPECT_EQ(0UL, host_->cluster_.stats_.upstream_internal_redirect_failed_total_.value()); + + // Test a truncated MOVED error response that cannot be parsed properly. + Common::Redis::RespValuePtr response2(new Common::Redis::RespValue()); + response2->type(Common::Redis::RespType::Error); + response2->asString() = "MOVED 1111"; + EXPECT_CALL(callbacks2, onResponse_(Ref(response2))); + EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); + EXPECT_CALL(host_->outlier_detector_, + putResult(Upstream::Outlier::Result::ExtOriginRequestSuccess, _)); + callbacks_->onRespValue(std::move(response2)); + + EXPECT_EQ(0UL, host_->cluster_.stats_.upstream_internal_redirect_succeeded_total_.value()); + EXPECT_EQ(0UL, host_->cluster_.stats_.upstream_internal_redirect_failed_total_.value()); + })); + upstream_read_filter_->onData(fake_data, false); + + EXPECT_CALL(*upstream_connection_, close(Network::ConnectionCloseType::NoFlush)); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + client_->close(); +} + TEST_F(RedisClientImplTest, AskRedirectionNotEnabled) { InSequence s; diff --git a/test/extensions/filters/network/common/redis/mocks.h b/test/extensions/filters/network/common/redis/mocks.h index e21260c5b85e..c7e8505f8a82 100644 --- a/test/extensions/filters/network/common/redis/mocks.h +++ b/test/extensions/filters/network/common/redis/mocks.h @@ -99,11 +99,15 @@ class MockClientCallbacks : public ClientCallbacks { ~MockClientCallbacks() override; void onResponse(Common::Redis::RespValuePtr&& value) override { onResponse_(value); } - bool onRedirection(Common::Redis::RespValuePtr&& value) override { return onRedirection_(value); } + bool onRedirection(Common::Redis::RespValuePtr&& value, const std::string& host_address, + bool ask_redirection) override { + return onRedirection_(value, host_address, ask_redirection); + } MOCK_METHOD1(onResponse_, void(Common::Redis::RespValuePtr& value)); MOCK_METHOD0(onFailure, void()); - MOCK_METHOD1(onRedirection_, bool(Common::Redis::RespValuePtr& value)); + MOCK_METHOD3(onRedirection_, bool(Common::Redis::RespValuePtr& value, + const std::string& host_address, bool ask_redirection)); }; } // namespace Client diff --git a/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc b/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc index d50cccc4964d..f2700abfb2be 100644 --- a/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc @@ -250,16 +250,17 @@ class RedisConnPoolImplTest : public testing::Test, public Common::Redis::Client } void verifyInvalidMoveResponse(Common::Redis::Client::MockClient* client, - const std::string& move_response, bool create_client) { + const std::string& host_address, bool create_client) { Common::Redis::RespValueSharedPtr request_value = std::make_shared(); Common::Redis::Client::MockPoolRequest active_request; MockPoolCallbacks callbacks; makeRequest(client, request_value, callbacks, active_request, create_client); Common::Redis::RespValuePtr moved_response{new Common::Redis::RespValue()}; moved_response->type(Common::Redis::RespType::Error); - moved_response->asString() = move_response; + moved_response->asString() = "MOVE 1111 " + host_address; EXPECT_CALL(callbacks, onResponse_(Ref(moved_response))); - EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(moved_response))); + EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(moved_response), + host_address, false)); } MOCK_METHOD1(create_, Common::Redis::Client::Client*(Upstream::HostConstSharedPtr host)); @@ -1013,7 +1014,8 @@ TEST_F(RedisConnPoolImplTest, MovedRedirectionSuccess) { EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host1), Return(client2))); EXPECT_CALL(*client2, makeRequest_(Ref(*request_value), _)).WillOnce(Return(&active_request2)); - EXPECT_TRUE(client->client_callbacks_.back()->onRedirection(std::move(moved_response))); + EXPECT_TRUE(client->client_callbacks_.back()->onRedirection(std::move(moved_response), + "10.1.2.3:4000", false)); EXPECT_EQ(host1->address()->asString(), "10.1.2.3:4000"); respond(callbacks, client2); @@ -1029,42 +1031,29 @@ TEST_F(RedisConnPoolImplTest, MovedRedirectionFailure) { Common::Redis::Client::MockClient* client = new NiceMock(); - // Test a truncated MOVED error response that cannot be parsed properly. - verifyInvalidMoveResponse(client, "MOVED 1111", true); - // Test with a badly specified host address (no colon, no address, no port). - verifyInvalidMoveResponse(client, "MOVED 1111 bad", false); + verifyInvalidMoveResponse(client, "bad", true); // Test with a badly specified IPv4 address. - verifyInvalidMoveResponse(client, "MOVED 1111 10.0.bad:3000", false); + verifyInvalidMoveResponse(client, "10.0.bad:3000", false); // Test with a badly specified TCP port. - verifyInvalidMoveResponse(client, "MOVED 1111 10.0.bad:3000", false); + verifyInvalidMoveResponse(client, "10.0.bad:3000", false); // Test with a TCP port outside of the acceptable range for a 32-bit integer. - verifyInvalidMoveResponse(client, "MOVED 1111 10.0.0.1:4294967297", false); // 2^32 + 1 + verifyInvalidMoveResponse(client, "10.0.0.1:4294967297", false); // 2^32 + 1 // Test with a TCP port outside of the acceptable range for a TCP port (0 .. 65535). - verifyInvalidMoveResponse(client, "MOVED 1111 10.0.0.1:65536", false); + verifyInvalidMoveResponse(client, "10.0.0.1:65536", false); // Test with a badly specified IPv6-like address. - verifyInvalidMoveResponse(client, "MOVED 1111 bad:ipv6:3000", false); + verifyInvalidMoveResponse(client, "bad:ipv6:3000", false); // Test with a valid IPv6 address and a badly specified TCP port (out of range). - verifyInvalidMoveResponse(client, "MOVED 1111 2001:470:813b:::70000", false); - - // Test a wrong response - MockPoolCallbacks callbacks; - Common::Redis::RespValueSharedPtr request2 = std::make_shared(); - Common::Redis::Client::MockPoolRequest active_request2; - makeRequest(client, request2, callbacks, active_request2, false); - Common::Redis::RespValuePtr moved_response2{new Common::Redis::RespValue()}; - moved_response2->type(Common::Redis::RespType::Integer); - moved_response2->asInteger() = 1; - EXPECT_CALL(callbacks, onResponse_(Ref(moved_response2))); - EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(moved_response2))); + verifyInvalidMoveResponse(client, "2001:470:813b:::70000", false); // Test an upstream error preventing the request from being sent. + MockPoolCallbacks callbacks; Common::Redis::RespValueSharedPtr request3 = std::make_shared(); Common::Redis::Client::MockPoolRequest active_request3; Common::Redis::Client::MockClient* client2 = new NiceMock(); @@ -1076,37 +1065,14 @@ TEST_F(RedisConnPoolImplTest, MovedRedirectionFailure) { EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host1), Return(client2))); EXPECT_CALL(*client2, makeRequest_(Ref(*request3), _)).WillOnce(Return(nullptr)); EXPECT_CALL(callbacks, onResponse_(Ref(moved_response3))); - EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(moved_response3))); + EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(moved_response3), + "10.1.2.3:4000", false)); EXPECT_EQ(host1->address()->asString(), "10.1.2.3:4000"); EXPECT_CALL(*client, close()); tls_.shutdownThread(); } -TEST_F(RedisConnPoolImplTest, RedirectionFailure) { - InSequence s; - - setup(); - - Common::Redis::RespValueSharedPtr request = std::make_shared(); - Common::Redis::Client::MockPoolRequest active_request; - MockPoolCallbacks callbacks; - Common::Redis::Client::MockClient* client = new NiceMock(); - - makeRequest(client, request, callbacks, active_request); - - // Test an error that looks like it might be a MOVED or ASK redirection error except for the first - // non-whitespace substring. - Common::Redis::RespValuePtr moved_response{new Common::Redis::RespValue()}; - moved_response->type(Common::Redis::RespType::Error); - moved_response->asString() = "NOTMOVEDORASK 1111 1.1.1.1:1"; - EXPECT_CALL(callbacks, onResponse_(Ref(moved_response))); - EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(moved_response))); - - EXPECT_CALL(*client, close()); - tls_.shutdownThread(); -} - TEST_F(RedisConnPoolImplTest, AskRedirectionSuccess) { InSequence s; @@ -1130,7 +1096,8 @@ TEST_F(RedisConnPoolImplTest, AskRedirectionSuccess) { EXPECT_CALL(*client2, makeRequest_(Ref(Common::Redis::Utility::AskingRequest::instance()), _)) .WillOnce(Return(&ask_request)); EXPECT_CALL(*client2, makeRequest_(Ref(*request_value), _)).WillOnce(Return(&active_request2)); - EXPECT_TRUE(client->client_callbacks_.back()->onRedirection(std::move(ask_response))); + EXPECT_TRUE(client->client_callbacks_.back()->onRedirection(std::move(ask_response), + "10.1.2.3:4000", true)); EXPECT_EQ(host1->address()->asString(), "10.1.2.3:4000"); respond(callbacks, client2); @@ -1144,35 +1111,15 @@ TEST_F(RedisConnPoolImplTest, AskRedirectionFailure) { setup(); - Common::Redis::RespValueSharedPtr request_value = std::make_shared(); - Common::Redis::Client::MockPoolRequest active_request; MockPoolCallbacks callbacks; Common::Redis::Client::MockClient* client = new NiceMock(); - makeRequest(client, request_value, callbacks, active_request); - - // Test a truncated ASK error response that cannot be parsed properly. - Common::Redis::Client::MockPoolRequest ask_request; - Common::Redis::RespValuePtr ask_response{new Common::Redis::RespValue()}; - ask_response->type(Common::Redis::RespType::Error); - ask_response->asString() = "ASK 1111"; - EXPECT_CALL(callbacks, onResponse_(Ref(ask_response))); - EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(ask_response))); - - Common::Redis::Client::MockPoolRequest active_request2; - Common::Redis::RespValueSharedPtr request2 = std::make_shared(); - makeRequest(client, request2, callbacks, active_request2, false); - Common::Redis::RespValuePtr ask_response2{new Common::Redis::RespValue()}; - ask_response2->type(Common::Redis::RespType::Integer); - ask_response2->asInteger() = 1; - EXPECT_CALL(callbacks, onResponse_(Ref(ask_response2))); - EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(ask_response2))); // Test an upstream error from trying to send an "asking" command upstream. Common::Redis::Client::MockPoolRequest active_request3; Common::Redis::RespValueSharedPtr request3 = std::make_shared(); Common::Redis::Client::MockClient* client2 = new NiceMock(); Upstream::HostConstSharedPtr host1; - makeRequest(client, request3, callbacks, active_request3, false); + makeRequest(client, request3, callbacks, active_request3); Common::Redis::RespValuePtr ask_response3{new Common::Redis::RespValue()}; ask_response3->type(Common::Redis::RespType::Error); ask_response3->asString() = "ASK 1111 10.1.2.3:4000"; @@ -1180,7 +1127,8 @@ TEST_F(RedisConnPoolImplTest, AskRedirectionFailure) { EXPECT_CALL(*client2, makeRequest_(Ref(Common::Redis::Utility::AskingRequest::instance()), _)) .WillOnce(Return(nullptr)); EXPECT_CALL(callbacks, onResponse_(Ref(ask_response3))); - EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(ask_response3))); + EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(ask_response3), + "10.1.2.3:4000", true)); EXPECT_EQ(host1->address()->asString(), "10.1.2.3:4000"); // Test an upstream error from trying to send the original request after the "asking" command is @@ -1195,7 +1143,8 @@ TEST_F(RedisConnPoolImplTest, AskRedirectionFailure) { .WillOnce(Return(&active_request5)); EXPECT_CALL(*client2, makeRequest_(Ref(*request4), _)).WillOnce(Return(nullptr)); EXPECT_CALL(callbacks, onResponse_(Ref(ask_response4))); - EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(ask_response4))); + EXPECT_FALSE(client->client_callbacks_.back()->onRedirection(std::move(ask_response4), + "10.1.2.3:4000", true)); EXPECT_CALL(*client, close()); tls_.shutdownThread(); diff --git a/test/extensions/health_checkers/redis/redis_test.cc b/test/extensions/health_checkers/redis/redis_test.cc index 878b4c8ec4be..010c455acd30 100644 --- a/test/extensions/health_checkers/redis/redis_test.cc +++ b/test/extensions/health_checkers/redis/redis_test.cc @@ -441,7 +441,7 @@ TEST_F(RedisHealthCheckerTest, ExistsRedirected) { new NetworkFilters::Common::Redis::RespValue()}; moved_response->type(NetworkFilters::Common::Redis::RespType::Error); moved_response->asString() = "MOVED 1111 127.0.0.1:81"; // exact values not important - pool_callbacks_->onRedirection(std::move(moved_response)); + pool_callbacks_->onRedirection(std::move(moved_response), "127.0.0.1:81", false); expectExistsRequestCreate(); interval_timer_->invokeCallback(); @@ -453,7 +453,7 @@ TEST_F(RedisHealthCheckerTest, ExistsRedirected) { new NetworkFilters::Common::Redis::RespValue()}; ask_response->type(NetworkFilters::Common::Redis::RespType::Error); ask_response->asString() = "ASK 1111 127.0.0.1:81"; // exact values not important - pool_callbacks_->onRedirection(std::move(ask_response)); + pool_callbacks_->onRedirection(std::move(ask_response), "127.0.0.1:81", true); EXPECT_CALL(*client_, close()); From 4896554d418cf646c77b103d200094ec645e25d3 Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Wed, 27 Nov 2019 03:01:15 -0800 Subject: [PATCH 24/27] fix unit test Signed-off-by: Henry Yang --- .../extensions/filters/network/common/redis/client_impl_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extensions/filters/network/common/redis/client_impl_test.cc b/test/extensions/filters/network/common/redis/client_impl_test.cc index bf2a9a218fb2..db3f0b35d116 100644 --- a/test/extensions/filters/network/common/redis/client_impl_test.cc +++ b/test/extensions/filters/network/common/redis/client_impl_test.cc @@ -808,7 +808,7 @@ TEST_F(RedisClientImplTest, RedirectionFailure) { response2->type(Common::Redis::RespType::Error); response2->asString() = "MOVED 1111"; EXPECT_CALL(callbacks2, onResponse_(Ref(response2))); - EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); EXPECT_CALL(host_->outlier_detector_, putResult(Upstream::Outlier::Result::ExtOriginRequestSuccess, _)); callbacks_->onRespValue(std::move(response2)); From d8384cb32feba00437456800fadc766779e241ce Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Wed, 27 Nov 2019 15:34:06 -0800 Subject: [PATCH 25/27] fix PR feedback Signed-off-by: Henry Yang --- .../network/common/redis/client_impl.cc | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/source/extensions/filters/network/common/redis/client_impl.cc b/source/extensions/filters/network/common/redis/client_impl.cc index 64674cee95f9..cf1d8b6c98b5 100644 --- a/source/extensions/filters/network/common/redis/client_impl.cc +++ b/source/extensions/filters/network/common/redis/client_impl.cc @@ -224,26 +224,22 @@ void ClientImpl::onRespValue(RespValuePtr&& value) { } else if (config_.enableRedirection() && (value->type() == Common::Redis::RespType::Error)) { std::vector err = StringUtil::splitToken(value->asString(), " ", false); bool redirected = false; - bool redirect_succeeded = false; if (err.size() == 3) { // MOVED and ASK redirection errors have the following substrings: MOVED or ASK (err[0]), hash // key slot (err[1]), and IP address and TCP port separated by a colon (err[2]) - if (err[0] == RedirectionResponse::get().MOVED) { + if (err[0] == RedirectionResponse::get().MOVED || err[0] == RedirectionResponse::get().ASK) { redirected = true; - redirect_succeeded = callbacks.onRedirection(std::move(value), std::string(err[2]), false); - } else if (err[0] == RedirectionResponse::get().ASK) { - redirected = true; - redirect_succeeded = callbacks.onRedirection(std::move(value), std::string(err[2]), true); + bool redirect_succeeded = callbacks.onRedirection(std::move(value), std::string(err[2]), + err[0] == RedirectionResponse::get().ASK); + if (redirect_succeeded) { + host_->cluster().stats().upstream_internal_redirect_succeeded_total_.inc(); + } else { + host_->cluster().stats().upstream_internal_redirect_failed_total_.inc(); + } } } if (!redirected) { callbacks.onResponse(std::move(value)); - } else { - if (redirect_succeeded) { - host_->cluster().stats().upstream_internal_redirect_succeeded_total_.inc(); - } else { - host_->cluster().stats().upstream_internal_redirect_failed_total_.inc(); - } } } else { callbacks.onResponse(std::move(value)); From 2df5066b86c875c2dd904492b73f95fb380bf5dc Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Mon, 2 Dec 2019 15:45:55 -0800 Subject: [PATCH 26/27] fix PR feedback Signed-off-by: Henry Yang --- test/extensions/filters/network/common/redis/mocks.cc | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/extensions/filters/network/common/redis/mocks.cc b/test/extensions/filters/network/common/redis/mocks.cc index 9e8de5d29fba..0def373cffad 100644 --- a/test/extensions/filters/network/common/redis/mocks.cc +++ b/test/extensions/filters/network/common/redis/mocks.cc @@ -42,15 +42,7 @@ MockClient::MockClient() { })); ON_CALL(*this, close()).WillByDefault(Invoke([this]() -> void { raiseEvent(Network::ConnectionEvent::LocalClose); - // for (ClientCallbacks * callbacks : client_callbacks_) { - // callbacks->onFailure(); - // } })); - // ON_CALL(*this, makeRequest(_, _)).WillByDefault(Invoke([this](const Common::Redis::RespValue&, - // ClientCallbacks& callbacks) -> PoolRequest* { - // client_callbacks_.push_back(&callbacks); - // return &pool_request_; - // })); } MockClient::~MockClient() = default; From eb2e2e3faf8d16a6226d8c500e3f3e3d84ac6a55 Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Fri, 6 Dec 2019 08:15:55 -0800 Subject: [PATCH 27/27] fix PR feedback Signed-off-by: Henry Yang --- .../filters/network/redis_proxy/conn_pool_impl_test.cc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc b/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc index f2700abfb2be..221d6944aaad 100644 --- a/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc @@ -117,11 +117,6 @@ class RedisConnPoolImplTest : public testing::Test, public Common::Redis::Client EXPECT_CALL(active_request, cancel()); request->cancel(); - // Common::Redis::RespValuePtr resp_value = std::make_unique(); - // resp_value->type(Common::Redis::RespType::BulkString); - // resp_value->asString() = "bar"; - // EXPECT_CALL(callbacks, onResponse_(_)); - // client_callbacks.back((->onResponse(std::move(resp_value)); } void makeRequest(Common::Redis::Client::MockClient* client,