/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* * Copyright 2011-2020 Couchbase, Inc. * * 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. */ #include "config.h" #include "iotests.h" #include "internal.h" #include class ErrmapUnitTest : public MockUnitTest { protected: virtual void createErrmapConnection(HandleWrap &hw, lcb_INSTANCE **instance) { MockEnvironment::getInstance()->createConnection(hw, instance); ASSERT_EQ(LCB_SUCCESS, lcb_connect(*instance)); lcb_wait(*instance, LCB_WAIT_DEFAULT); ASSERT_EQ(LCB_SUCCESS, lcb_get_bootstrap_status(*instance)); } void checkRetryVerify(uint16_t errcode); void TearDown() { if (!MockEnvironment::getInstance()->isRealCluster()) { MockOpFailClearCommand clearCmd(MockEnvironment::getInstance()->getNumNodes()); doMockTxn(clearCmd); } MockUnitTest::TearDown(); } }; struct ResultCookie { lcb_STATUS rc; bool called; void reset() { rc = LCB_SUCCESS; called = false; } ResultCookie() : rc(LCB_SUCCESS), called(false) {} }; extern "C" { static void opcb(lcb_INSTANCE *, int, const lcb_RESPSTORE *resp) { ResultCookie *cookie; lcb_respstore_cookie(resp, (void **)&cookie); cookie->called = true; cookie->rc = lcb_respstore_status(resp); } } TEST_F(ErrmapUnitTest, hasRecognizedErrors) { SKIP_UNLESS_MOCK(); HandleWrap hw; lcb_INSTANCE *instance; createErrmapConnection(hw, &instance); // Test the actual error map.. using namespace lcb; const errmap::ErrorMap &em = *instance->settings->errmap; const errmap::Error &err = em.getError(PROTOCOL_BINARY_RESPONSE_KEY_ENOENT); ASSERT_TRUE(err.isValid()); ASSERT_TRUE(err.hasAttribute(errmap::CONSTRAINT_FAILURE)); } TEST_F(ErrmapUnitTest, closesOnUnrecognizedError) { // For now, EINTERNAL is an error code we don't know! SKIP_UNLESS_MOCK(); HandleWrap hw; lcb_INSTANCE *instance; createErrmapConnection(hw, &instance); const char *key = "key"; lcb_CMDSTORE *scmd; lcb_cmdstore_create(&scmd, LCB_STORE_UPSERT); lcb_cmdstore_key(scmd, key, strlen(key)); lcb_cmdstore_value(scmd, "val", 3); ResultCookie cookie; lcb_install_callback(instance, LCB_CALLBACK_STORE, (lcb_RESPCALLBACK)opcb); ASSERT_EQ(LCB_SUCCESS, lcb_store(instance, &cookie, scmd)); lcb_wait(instance, LCB_WAIT_DEFAULT); ASSERT_EQ(LCB_SUCCESS, cookie.rc); MockCommand cmd(MockCommand::OPFAIL); // Determine the server int srvix = instance->map_key(key); cmd.set("server", srvix); cmd.set("code", PROTOCOL_BINARY_RESPONSE_EINTERNAL); // Invalidate the connection! cmd.set("count", 1); doMockTxn(cmd); cookie.reset(); ASSERT_EQ(LCB_SUCCESS, lcb_store(instance, &cookie, scmd)); lcb_wait(instance, LCB_WAIT_DEFAULT); ASSERT_TRUE(cookie.called); ASSERT_NE(LCB_SUCCESS, cookie.rc); cookie.reset(); ASSERT_EQ(LCB_SUCCESS, lcb_store(instance, &cookie, scmd)); lcb_wait(instance, LCB_WAIT_DEFAULT); ASSERT_TRUE(cookie.called); // Note, we can't determine what the actual error here is. It would be nice // if we were able to reconnect and retry the other commands, but right now // detecting a failed connection is better than having no detection at all: // // ASSERT_EQ(LCB_SUCCESS, cookie.rc); lcb_cmdstore_destroy(scmd); } void ErrmapUnitTest::checkRetryVerify(uint16_t errcode) { HandleWrap hw; lcb_INSTANCE *instance; createErrmapConnection(hw, &instance); lcb_install_callback(instance, LCB_CALLBACK_STORE, (lcb_RESPCALLBACK)opcb); ResultCookie cookie; std::string key("hello"); lcb_CMDSTORE *scmd; lcb_cmdstore_create(&scmd, LCB_STORE_UPSERT); lcb_cmdstore_key(scmd, key.c_str(), key.size()); lcb_cmdstore_value(scmd, "val", 3); // Store the item once to ensure the server is actually connected // (we don't want opfail to be active during negotiation). lcb_store(instance, &cookie, scmd); lcb_wait(instance, LCB_WAIT_DEFAULT); ASSERT_TRUE(cookie.called); ASSERT_EQ(LCB_SUCCESS, cookie.rc); // Figure out the server this key belongs to. int srvix = instance->map_key(key); MockCommand cmd(MockCommand::START_RETRY_VERIFY); cmd.set("idx", srvix); cmd.set("bucket", instance->get_bucketname()); doMockTxn(cmd); // Set up opfail MockOpfailCommand failCmd(errcode, srvix, -1, instance->get_bucketname()); doMockTxn(failCmd); // Run the command! cookie.reset(); lcb_STATUS rc = lcb_store(instance, &cookie, scmd); ASSERT_EQ(LCB_SUCCESS, rc); lcb_wait(instance, LCB_WAIT_DEFAULT); ASSERT_TRUE(cookie.called); ASSERT_EQ(LCB_ERR_TEMPORARY_FAILURE, cookie.rc); // Check that we executed correctly: MockBucketCommand verifyCmd(MockCommand::CHECK_RETRY_VERIFY, srvix, instance->get_bucketname()); verifyCmd.set("opcode", PROTOCOL_BINARY_CMD_SET); verifyCmd.set("errcode", errcode); #ifdef __APPLE__ // FIXME: on Jenkins OSX actual expected time does not match actual and mock raises exception like following: // VerificationException: Not enough/too many retries. Last TS=1498594892704. Last expected=1498594892728. Diff=24. // MaxDiff=20 verifyCmd.set("fuzz_ms", 35); #else verifyCmd.set("fuzz_ms", 20); #endif doMockTxn(verifyCmd); lcb_cmdstore_destroy(scmd); } static const uint16_t ERRCODE_CONSTANT = 0x7ff0; static const uint16_t ERRCODE_LINEAR = 0x7ff1; static const uint16_t ERRCODE_EXPONENTIAL = 0x7ff2; TEST_F(ErrmapUnitTest, retrySpecConstant) { SKIP_UNLESS_MOCK(); checkRetryVerify(ERRCODE_CONSTANT); } TEST_F(ErrmapUnitTest, retrySpecLinear) { SKIP_UNLESS_MOCK(); checkRetryVerify(ERRCODE_LINEAR); } TEST_F(ErrmapUnitTest, retrySpecExponential) { SKIP_UNLESS_MOCK(); checkRetryVerify(ERRCODE_EXPONENTIAL); }