//////////////////////////////////////////////////////////////////////////////// /// @brief test suite for Network/Methods.cpp /// /// @file /// /// DISCLAIMER /// /// Copyright 2019 ArangoDB GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Simon Grätzer //////////////////////////////////////////////////////////////////////////////// #include "gtest/gtest.h" #include #include #include #include #include "Mocks/LogLevels.h" #include "Mocks/Servers.h" #include "ApplicationFeatures/GreetingsFeaturePhase.h" #include "Cluster/ClusterFeature.h" #include "Network/ConnectionPool.h" #include "Network/Methods.h" #include "Network/NetworkFeature.h" #include "RestServer/FileDescriptorsFeature.h" #include "Scheduler/Scheduler.h" #include "Scheduler/SchedulerFeature.h" using namespace arangodb; struct DummyConnection final : public fuerte::Connection { DummyConnection(fuerte::detail::ConnectionConfiguration const& conf) : fuerte::Connection(conf) {} fuerte::MessageID sendRequest(std::unique_ptr r, fuerte::RequestCallback cb) override { _sendRequestNum++; cb(_err, std::move(r), std::move(_response)); return 0; } std::size_t requestsLeft() const override { return 1; } State state() const override { return _state; } void cancel() override {} void startConnection() override {} fuerte::Connection::State _state = fuerte::Connection::State::Connected; fuerte::Error _err = fuerte::Error::NoError; std::unique_ptr _response; int _sendRequestNum = 0; }; struct DummyPool : public network::ConnectionPool { DummyPool(network::ConnectionPool::Config const& c) : network::ConnectionPool(c), _conn(std::make_shared(fuerte::detail::ConnectionConfiguration())) { } std::shared_ptr createConnection(fuerte::ConnectionBuilder&) override { return _conn; } std::shared_ptr _conn; }; struct NetworkMethodsTest : public ::testing::Test, public arangodb::tests::LogSuppressor { NetworkMethodsTest() : server(false) { server.addFeature(true); server.startFeatures(); pool = std::make_unique(config()); } private: network::ConnectionPool::Config config() { network::ConnectionPool::Config config; config.clusterInfo = &server.getFeature().clusterInfo(); config.numIOThreads = 1; config.minOpenConnections = 1; config.maxOpenConnections = 3; config.verifyHosts = false; return config; } protected: tests::mocks::MockCoordinator server; std::unique_ptr pool; }; TEST_F(NetworkMethodsTest, simple_request) { pool->_conn->_err = fuerte::Error::NoError; network::RequestOptions reqOpts; reqOpts.timeout = network::Timeout(60.0); fuerte::ResponseHeader header; header.responseCode = fuerte::StatusAccepted; header.contentType(fuerte::ContentType::VPack); pool->_conn->_response = std::make_unique(std::move(header)); std::shared_ptr b = VPackParser::fromJson("{\"error\":false}"); auto resBuffer = b->steal(); pool->_conn->_response->setPayload(std::move(*resBuffer), 0); VPackBuffer buffer; auto f = network::sendRequest(pool.get(), "tcp://example.org:80", fuerte::RestVerb::Get, "/", buffer, reqOpts); network::Response res = std::move(f).get(); ASSERT_EQ(res.destination, "tcp://example.org:80"); ASSERT_EQ(res.error, fuerte::Error::NoError); ASSERT_NE(res.response, nullptr); ASSERT_EQ(res.response->statusCode(), fuerte::StatusAccepted); } TEST_F(NetworkMethodsTest, request_failure) { pool->_conn->_err = fuerte::Error::ConnectionClosed; network::RequestOptions reqOpts; reqOpts.timeout = network::Timeout(60.0); VPackBuffer buffer; auto f = network::sendRequest(pool.get(), "tcp://example.org:80", fuerte::RestVerb::Get, "/", buffer, reqOpts); network::Response res = std::move(f).get(); ASSERT_EQ(res.destination, "tcp://example.org:80"); ASSERT_EQ(res.error, fuerte::Error::ConnectionClosed); ASSERT_EQ(res.response, nullptr); } TEST_F(NetworkMethodsTest, request_with_retry_after_error) { // Step 1: Provoke a connection error pool->_conn->_err = fuerte::Error::CouldNotConnect; network::RequestOptions reqOpts; reqOpts.timeout = network::Timeout(5.0); VPackBuffer buffer; auto f = network::sendRequestRetry(pool.get(), "tcp://example.org:80", fuerte::RestVerb::Get, "/", buffer, reqOpts); // the default behaviour should be to retry after 200 ms std::this_thread::sleep_for(std::chrono::milliseconds(5)); ASSERT_FALSE(f.isReady()); ASSERT_EQ(pool->_conn->_sendRequestNum, 1); // Step 2: Now respond with no error pool->_conn->_err = fuerte::Error::NoError; fuerte::ResponseHeader header; header.contentType(fuerte::ContentType::VPack); header.responseCode = fuerte::StatusAccepted; pool->_conn->_response = std::make_unique(std::move(header)); std::shared_ptr b = VPackParser::fromJson("{\"error\":false}"); auto resBuffer = b->steal(); pool->_conn->_response->setPayload(std::move(*resBuffer), 0); auto status = f.wait_for(std::chrono::milliseconds(350)); ASSERT_EQ(futures::FutureStatus::Ready, status); network::Response res = std::move(f).get(); ASSERT_EQ(res.destination, "tcp://example.org:80"); ASSERT_EQ(res.error, fuerte::Error::NoError); ASSERT_NE(res.response, nullptr); ASSERT_EQ(res.response->statusCode(), fuerte::StatusAccepted); } TEST_F(NetworkMethodsTest, request_with_retry_after_not_found_error) { // Step 1: Provoke a data source not found error pool->_conn->_err = fuerte::Error::NoError; fuerte::ResponseHeader header; header.contentType(fuerte::ContentType::VPack); header.responseCode = fuerte::StatusNotFound; pool->_conn->_response = std::make_unique(std::move(header)); std::shared_ptr b = VPackParser::fromJson("{\"errorNum\":1203}"); auto resBuffer = b->steal(); pool->_conn->_response->setPayload(std::move(*resBuffer), 0); network::RequestOptions reqOpts; reqOpts.timeout = network::Timeout(60.0); reqOpts.retryNotFound = true; VPackBuffer buffer; auto f = network::sendRequestRetry(pool.get(), "tcp://example.org:80", fuerte::RestVerb::Get, "/", buffer, reqOpts); // the default behaviour should be to retry after 200 ms std::this_thread::sleep_for(std::chrono::milliseconds(5)); ASSERT_FALSE(f.isReady()); // Step 2: Now respond with no error pool->_conn->_err = fuerte::Error::NoError; header.responseCode = fuerte::StatusAccepted; header.contentType(fuerte::ContentType::VPack); pool->_conn->_response = std::make_unique(std::move(header)); b = VPackParser::fromJson("{\"error\":false}"); resBuffer = b->steal(); pool->_conn->_response->setPayload(std::move(*resBuffer), 0); auto status = f.wait_for(std::chrono::milliseconds(350)); ASSERT_EQ(futures::FutureStatus::Ready, status); network::Response res = std::move(f).get(); ASSERT_EQ(res.destination, "tcp://example.org:80"); ASSERT_EQ(res.error, fuerte::Error::NoError); ASSERT_NE(res.response, nullptr); ASSERT_EQ(res.response->statusCode(), fuerte::StatusAccepted); }