diff options
author | Arnstein Ressem <aressem@yahoo-inc.com> | 2016-10-12 17:49:54 +0200 |
---|---|---|
committer | Arnstein Ressem <aressem@yahoo-inc.com> | 2016-10-12 17:49:54 +0200 |
commit | c008b2dfcd0e231491a2093ab2b00b539efc79be (patch) | |
tree | c0b454f8e34993c7b838e34aa2688e2f6065b210 /storage/src/tests/persistence/common | |
parent | 56f449121b5b24353b36399842aa88f8a3deb055 (diff) |
Finished with the storage dependency mess cleanup.
Diffstat (limited to 'storage/src/tests/persistence/common')
5 files changed, 639 insertions, 0 deletions
diff --git a/storage/src/tests/persistence/common/CMakeLists.txt b/storage/src/tests/persistence/common/CMakeLists.txt new file mode 100644 index 00000000000..561f516b8d1 --- /dev/null +++ b/storage/src/tests/persistence/common/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_library(storage_testpersistence_common TEST + SOURCES + persistenceproviderwrapper.cpp + filestortestfixture.cpp + DEPENDS + persistence + storage_testcommon +) diff --git a/storage/src/tests/persistence/common/filestortestfixture.cpp b/storage/src/tests/persistence/common/filestortestfixture.cpp new file mode 100644 index 00000000000..8f7a95f53c8 --- /dev/null +++ b/storage/src/tests/persistence/common/filestortestfixture.cpp @@ -0,0 +1,143 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/fastos/fastos.h> +#include <sstream> +#include <vespa/storage/persistence/messages.h> +#include <vespa/storage/persistence/filestorage/filestormanager.h> +#include <vespa/persistence/dummyimpl/dummypersistence.h> +#include <tests/persistence/common/filestortestfixture.h> + +namespace storage { + +spi::LoadType FileStorTestFixture::defaultLoadType = spi::LoadType(0, "default"); +const uint32_t FileStorTestFixture::MSG_WAIT_TIME; + +void +FileStorTestFixture::setupDisks(uint32_t diskCount) +{ + _config.reset(new vdstestlib::DirConfig(getStandardConfig(true))); + + _config2.reset(new vdstestlib::DirConfig(*_config)); + _config2->getConfig("stor-server").set("root_folder", "vdsroot.2"); + _config2->getConfig("stor-devices").set("root_folder", "vdsroot.2"); + _config2->getConfig("stor-server").set("node_index", "1"); + + _smallConfig.reset(new vdstestlib::DirConfig(*_config)); + _node.reset(new TestServiceLayerApp(DiskCount(diskCount), NodeIndex(1), + _config->getConfigId())); + _testdoctype1 = _node->getTypeRepo()->getDocumentType("testdoctype1"); +} + +// Default provider setup which should work out of the box for most tests. +void +FileStorTestFixture::setUp() +{ + setupDisks(1); + _node->setPersistenceProvider( + spi::PersistenceProvider::UP( + new spi::dummy::DummyPersistence(_node->getTypeRepo(), 1))); +} + +void +FileStorTestFixture::tearDown() +{ + _node.reset(0); +} + +void +FileStorTestFixture::createBucket(const document::BucketId& bid) +{ + spi::Context context(defaultLoadType, spi::Priority(0), + spi::Trace::TraceLevel(0)); + _node->getPersistenceProvider().createBucket( + spi::Bucket(bid, spi::PartitionId(0)), context); + + StorBucketDatabase::WrappedEntry entry( + _node->getStorageBucketDatabase().get(bid, "foo", + StorBucketDatabase::CREATE_IF_NONEXISTING)); + entry->disk = 0; + entry->info = api::BucketInfo(0, 0, 0, 0, 0, true, false); + entry.write(); +} + +bool +FileStorTestFixture::bucketExistsInDb(const document::BucketId& bucket) const +{ + StorBucketDatabase::WrappedEntry entry( + _node->getStorageBucketDatabase().get(bucket, "bucketExistsInDb")); + return entry.exist(); +} + +FileStorTestFixture::TestFileStorComponents::TestFileStorComponents( + FileStorTestFixture& fixture, + const char* testName, + const StorageLinkInjector& injector) + : _testName(testName), + _fixture(fixture), + manager(new FileStorManager(fixture._config->getConfigId(), + fixture._node->getPartitions(), + fixture._node->getPersistenceProvider(), + fixture._node->getComponentRegister())) +{ + injector.inject(top); + top.push_back(StorageLink::UP(manager)); + top.open(); +} + +api::StorageMessageAddress +FileStorTestFixture::TestFileStorComponents::makeSelfAddress() const { + return api::StorageMessageAddress("storage", lib::NodeType::STORAGE, 0); +} + +void +FileStorTestFixture::TestFileStorComponents::sendDummyGet( + const document::BucketId& bid) +{ + std::ostringstream id; + id << "id:foo:testdoctype1:n=" << bid.getId() << ":0"; + std::shared_ptr<api::GetCommand> cmd( + new api::GetCommand(bid, document::DocumentId(id.str()), "[all]")); + cmd->setAddress(makeSelfAddress()); + cmd->setPriority(255); + top.sendDown(cmd); +} + +void +FileStorTestFixture::TestFileStorComponents::sendDummyGetDiff( + const document::BucketId& bid) +{ + std::vector<api::GetBucketDiffCommand::Node> nodes; + nodes.push_back(0); + nodes.push_back(1); + std::shared_ptr<api::GetBucketDiffCommand> cmd( + new api::GetBucketDiffCommand(bid, nodes, 12345)); + cmd->setAddress(makeSelfAddress()); + cmd->setPriority(255); + top.sendDown(cmd); +} + +void +FileStorTestFixture::TestFileStorComponents::sendPut( + const document::BucketId& bid, + uint32_t docIdx, + uint64_t timestamp) +{ + std::ostringstream id; + id << "id:foo:testdoctype1:n=" << bid.getId() << ":" << docIdx; + document::Document::SP doc( + _fixture._node->getTestDocMan().createDocument("foobar", id.str())); + std::shared_ptr<api::PutCommand> cmd( + new api::PutCommand(bid, doc, timestamp)); + cmd->setAddress(makeSelfAddress()); + top.sendDown(cmd); +} + +void +FileStorTestFixture::setClusterState(const std::string& state) +{ + _node->getStateUpdater().setClusterState( + lib::ClusterState::CSP(new lib::ClusterState(state))); +} + + +} // ns storage diff --git a/storage/src/tests/persistence/common/filestortestfixture.h b/storage/src/tests/persistence/common/filestortestfixture.h new file mode 100644 index 00000000000..4f1de549f47 --- /dev/null +++ b/storage/src/tests/persistence/common/filestortestfixture.h @@ -0,0 +1,112 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <vespa/vdstestlib/cppunit/macros.h> +#include <tests/common/testhelper.h> +#include <vespa/persistence/spi/persistenceprovider.h> +#include <vespa/storage/persistence/filestorage/filestormanager.h> +#include <vespa/storageapi/message/persistence.h> +#include <tests/common/dummystoragelink.h> +#include <tests/common/teststorageapp.h> +#include <tests/common/testhelper.h> + +namespace storage { + +class FileStorTestFixture : public CppUnit::TestFixture +{ +public: + static spi::LoadType defaultLoadType; + + std::unique_ptr<TestServiceLayerApp> _node; + std::unique_ptr<vdstestlib::DirConfig> _config; + std::unique_ptr<vdstestlib::DirConfig> _config2; + std::unique_ptr<vdstestlib::DirConfig> _smallConfig; + const document::DocumentType* _testdoctype1; + + static const uint32_t MSG_WAIT_TIME = 60 * 1000; + + typedef uint32_t DocumentIndex; + typedef uint64_t PutTimestamp; + + void setUp() override; + void tearDown() override; + void setupDisks(uint32_t diskCount); + void createBucket(const document::BucketId& bid); + bool bucketExistsInDb(const document::BucketId& bucket) const; + + api::ReturnCode::Result resultOf(const api::StorageReply& reply) const { + return reply.getResult().getResult(); + } + void setClusterState(const std::string&); + + struct StorageLinkInjector + { + virtual ~StorageLinkInjector() {} + + virtual void inject(DummyStorageLink&) const = 0; + }; + + struct NoOpStorageLinkInjector : StorageLinkInjector + { + void inject(DummyStorageLink&) const {} + }; + + void + expectNoReplies(DummyStorageLink& link) { + CPPUNIT_ASSERT_EQUAL(size_t(0), link.getNumReplies()); + } + + template <typename ReplyType> + void + expectReply(DummyStorageLink& link, + api::ReturnCode::Result result) + { + link.waitForMessages(1, 60*1000); + api::StorageReply* reply( + dynamic_cast<ReplyType*>(link.getReply(0).get())); + if (reply == 0) { + std::ostringstream ss; + ss << "got unexpected reply " + << link.getReply(0)->toString(true); + CPPUNIT_FAIL(ss.str()); + } + CPPUNIT_ASSERT_EQUAL(result, reply->getResult().getResult()); + } + + template <typename ReplyType> + void + expectAbortedReply(DummyStorageLink& link) { + expectReply<ReplyType>(link, api::ReturnCode::ABORTED); + } + + template <typename ReplyType> + void + expectOkReply(DummyStorageLink& link) { + expectReply<ReplyType>(link, api::ReturnCode::OK); + } + + + struct TestFileStorComponents + { + private: + TestName _testName; + FileStorTestFixture& _fixture; + public: + DummyStorageLink top; + FileStorManager* manager; + + TestFileStorComponents(FileStorTestFixture& fixture, + const char* testName, + const StorageLinkInjector& i = NoOpStorageLinkInjector()); + + api::StorageMessageAddress makeSelfAddress() const; + + void sendDummyGet(const document::BucketId& bid); + void sendPut(const document::BucketId& bid, + uint32_t docIdx, + uint64_t timestamp); + void sendDummyGetDiff(const document::BucketId& bid); + }; +}; + +} // ns storage diff --git a/storage/src/tests/persistence/common/persistenceproviderwrapper.cpp b/storage/src/tests/persistence/common/persistenceproviderwrapper.cpp new file mode 100644 index 00000000000..9ec66590b24 --- /dev/null +++ b/storage/src/tests/persistence/common/persistenceproviderwrapper.cpp @@ -0,0 +1,222 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include <vespa/fastos/fastos.h> +#include <iostream> +#include <sstream> +#include <tests/persistence/common/persistenceproviderwrapper.h> + +#define LOG_SPI(ops) \ + { \ + std::ostringstream logStream; \ + logStream << ops; \ + _log.push_back(logStream.str()); \ + } + +#define CHECK_ERROR(className, failType) \ + { \ + if (_result.getErrorCode() != spi::Result::NONE && (_failureMask & (failType))) { \ + return className(_result.getErrorCode(), _result.getErrorMessage()); \ + } \ + } + +namespace storage { + +namespace { + +const char* +includedVersionsToString(spi::IncludedVersions versions) +{ + switch (versions) { + case spi::NEWEST_DOCUMENT_ONLY: + return "NEWEST_DOCUMENT_ONLY"; + case spi::NEWEST_DOCUMENT_OR_REMOVE: + return "NEWEST_DOCUMENT_OR_REMOVE"; + case spi::ALL_VERSIONS: + return "ALL_VERSIONS"; + } + return "!!UNKNOWN!!"; +} + +} // anon namespace + +std::string +PersistenceProviderWrapper::toString() const +{ + std::ostringstream ss; + for (size_t i = 0; i < _log.size(); ++i) { + ss << _log[i] << "\n"; + } + return ss.str(); +} + +spi::PartitionStateListResult +PersistenceProviderWrapper::getPartitionStates() const +{ + LOG_SPI("getPartitionStates()"); + return _spi.getPartitionStates(); +} + +spi::BucketIdListResult +PersistenceProviderWrapper::listBuckets(spi::PartitionId partitionId) const +{ + LOG_SPI("listBuckets(" << uint16_t(partitionId) << ")"); + CHECK_ERROR(spi::BucketIdListResult, FAIL_LIST_BUCKETS); + return _spi.listBuckets(partitionId); +} + +spi::Result +PersistenceProviderWrapper::createBucket(const spi::Bucket& bucket, + spi::Context& context) +{ + LOG_SPI("createBucket(" << bucket << ")"); + CHECK_ERROR(spi::Result, FAIL_CREATE_BUCKET); + return _spi.createBucket(bucket, context); +} + +spi::BucketInfoResult +PersistenceProviderWrapper::getBucketInfo(const spi::Bucket& bucket) const +{ + LOG_SPI("getBucketInfo(" << bucket << ")"); + CHECK_ERROR(spi::BucketInfoResult, FAIL_BUCKET_INFO); + return _spi.getBucketInfo(bucket); +} + +spi::Result +PersistenceProviderWrapper::put(const spi::Bucket& bucket, + spi::Timestamp timestamp, + const document::Document::SP& doc, + spi::Context& context) +{ + LOG_SPI("put(" << bucket << ", " << timestamp << ", " << doc->getId() << ")"); + CHECK_ERROR(spi::Result, FAIL_PUT); + return _spi.put(bucket, timestamp, doc, context); +} + +spi::RemoveResult +PersistenceProviderWrapper::remove(const spi::Bucket& bucket, + spi::Timestamp timestamp, + const spi::DocumentId& id, + spi::Context& context) +{ + LOG_SPI("remove(" << bucket << ", " << timestamp << ", " << id << ")"); + CHECK_ERROR(spi::RemoveResult, FAIL_REMOVE); + return _spi.remove(bucket, timestamp, id, context); +} + +spi::RemoveResult +PersistenceProviderWrapper::removeIfFound(const spi::Bucket& bucket, + spi::Timestamp timestamp, + const spi::DocumentId& id, + spi::Context& context) +{ + LOG_SPI("removeIfFound(" << bucket << ", " << timestamp << ", " << id << ")"); + CHECK_ERROR(spi::RemoveResult, FAIL_REMOVE_IF_FOUND); + return _spi.removeIfFound(bucket, timestamp, id, context); +} + +spi::UpdateResult +PersistenceProviderWrapper::update(const spi::Bucket& bucket, + spi::Timestamp timestamp, + const document::DocumentUpdate::SP& upd, + spi::Context& context) +{ + LOG_SPI("update(" << bucket << ", " << timestamp << ", " << upd->getId() << ")"); + CHECK_ERROR(spi::UpdateResult, FAIL_UPDATE); + return _spi.update(bucket, timestamp, upd, context); +} + +spi::GetResult +PersistenceProviderWrapper::get(const spi::Bucket& bucket, + const document::FieldSet& fieldSet, + const spi::DocumentId& id, + spi::Context& context) const +{ + LOG_SPI("get(" << bucket << ", " << id << ")"); + CHECK_ERROR(spi::GetResult, FAIL_GET); + return _spi.get(bucket, fieldSet, id, context); +} + +spi::Result +PersistenceProviderWrapper::flush(const spi::Bucket& bucket, + spi::Context& context) +{ + LOG_SPI("flush(" << bucket << ")"); + CHECK_ERROR(spi::Result, FAIL_FLUSH); + return _spi.flush(bucket, context); +} + +spi::CreateIteratorResult +PersistenceProviderWrapper::createIterator(const spi::Bucket& bucket, + const document::FieldSet& fields, + const spi::Selection& sel, + spi::IncludedVersions versions, + spi::Context& context) +{ + // TODO: proper printing of FieldSet and Selection + + LOG_SPI("createIterator(" << bucket << ", " + << includedVersionsToString(versions) << ")"); + CHECK_ERROR(spi::CreateIteratorResult, FAIL_CREATE_ITERATOR); + return _spi.createIterator(bucket, fields, sel, versions, context); +} + +spi::IterateResult +PersistenceProviderWrapper::iterate(spi::IteratorId iterId, + uint64_t maxByteSize, + spi::Context& context) const +{ + LOG_SPI("iterate(" << uint64_t(iterId) << ", " << maxByteSize << ")"); + CHECK_ERROR(spi::IterateResult, FAIL_ITERATE); + return _spi.iterate(iterId, maxByteSize, context); +} + +spi::Result +PersistenceProviderWrapper::destroyIterator(spi::IteratorId iterId, + spi::Context& context) +{ + LOG_SPI("destroyIterator(" << uint64_t(iterId) << ")"); + CHECK_ERROR(spi::Result, FAIL_DESTROY_ITERATOR); + return _spi.destroyIterator(iterId, context); +} + +spi::Result +PersistenceProviderWrapper::deleteBucket(const spi::Bucket& bucket, + spi::Context& context) +{ + LOG_SPI("deleteBucket(" << bucket << ")"); + CHECK_ERROR(spi::Result, FAIL_DELETE_BUCKET); + return _spi.deleteBucket(bucket, context); +} + +spi::Result +PersistenceProviderWrapper::split(const spi::Bucket& source, + const spi::Bucket& target1, + const spi::Bucket& target2, + spi::Context& context) +{ + LOG_SPI("split(" << source << ", " << target1 << ", " << target2 << ")"); + CHECK_ERROR(spi::Result, FAIL_SPLIT); + return _spi.split(source, target1, target2, context); +} + +spi::Result +PersistenceProviderWrapper::join(const spi::Bucket& source1, + const spi::Bucket& source2, + const spi::Bucket& target, + spi::Context& context) +{ + LOG_SPI("join(" << source1 << ", " << source2 << ", " << target << ")"); + CHECK_ERROR(spi::Result, FAIL_JOIN); + return _spi.join(source1, source2, target, context); +} + +spi::Result +PersistenceProviderWrapper::removeEntry(const spi::Bucket& bucket, + spi::Timestamp timestamp, + spi::Context& context) +{ + LOG_SPI("revert(" << bucket << ", " << timestamp << ")"); + CHECK_ERROR(spi::Result, FAIL_REVERT); + return _spi.removeEntry(bucket, timestamp, context); +} + +} diff --git a/storage/src/tests/persistence/common/persistenceproviderwrapper.h b/storage/src/tests/persistence/common/persistenceproviderwrapper.h new file mode 100644 index 00000000000..b115eb7ef3d --- /dev/null +++ b/storage/src/tests/persistence/common/persistenceproviderwrapper.h @@ -0,0 +1,153 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * \class storage::PersistenceProviderWrapper + * + * \brief Test utility class for intercepting all operations upon a + * persistence layer, injecting errors and performing logging. + * + * The PersistenceProviderWrapper class implements the basic SPI by + * logging all operations and then delegating handling the operation + * to the SPI instance given during construction. If an error result + * is specified and the operation invoked is tagged that it should be + * failed via setFailureMask(), the operation on the wrapped SPI will + * not be executed, but the given error result will be immediately + * returned instead (wrapped in the proper return type). + */ +#pragma once + +#include <vector> +#include <string> +#include <vespa/persistence/spi/abstractpersistenceprovider.h> + +namespace storage { + +class PersistenceProviderWrapper : public spi::AbstractPersistenceProvider +{ +public: + enum OPERATION_FAILURE_FLAGS + { + FAIL_LIST_BUCKETS = 1 << 0, + FAIL_BUCKET_INFO = 1 << 1, + FAIL_GET = 1 << 2, + FAIL_PUT = 1 << 3, + FAIL_REMOVE = 1 << 4, + FAIL_REMOVE_IF_FOUND = 1 << 5, + FAIL_REPLACE_WITH_REMOVE = 1 << 6, + FAIL_UPDATE = 1 << 7, + FAIL_REVERT = 1 << 8, + FAIL_FLUSH = 1 << 9, + FAIL_CREATE_ITERATOR = 1 << 10, + FAIL_ITERATE = 1 << 11, + FAIL_DESTROY_ITERATOR = 1 << 12, + FAIL_DELETE_BUCKET = 1 << 13, + FAIL_SPLIT = 1 << 14, + FAIL_JOIN = 1 << 15, + FAIL_CREATE_BUCKET = 1 << 16, + FAIL_BUCKET_PERSISTENCE = FAIL_PUT|FAIL_REMOVE|FAIL_UPDATE|FAIL_REVERT|FAIL_FLUSH, + FAIL_ALL_OPERATIONS = 0xffff, + // TODO: add more as needed + }; +private: + spi::PersistenceProvider& _spi; + spi::Result _result; + mutable std::vector<std::string> _log; + uint32_t _failureMask; +public: + PersistenceProviderWrapper(spi::PersistenceProvider& spi) + : _spi(spi), + _result(spi::Result(spi::Result::NONE, "")), + _log(), + _failureMask(0) + { + } + + /** + * Explicitly set result to anything != NONE to have all operations + * return the given error without the wrapped SPI ever being invoked. + */ + void setResult(const spi::Result& result) { + _result = result; + } + void clearResult() { + _result = spi::Result(spi::Result::NONE, ""); + } + const spi::Result& getResult() const { return _result; } + /** + * Set a mask for operations to fail with _result + */ + void setFailureMask(uint32_t mask) { _failureMask = mask; } + uint32_t getFailureMask() const { return _failureMask; } + + /** + * Get a string representation of all the operations performed on the + * SPI with a newline separating each operation. + */ + std::string toString() const; + /** + * Clear log of all operations performed. + */ + void clearOperationLog() { _log.clear(); } + const std::vector<std::string>& getOperationLog() const { return _log; } + + spi::Result createBucket(const spi::Bucket&, spi::Context&); + + spi::PartitionStateListResult getPartitionStates() const; + + spi::BucketIdListResult listBuckets(spi::PartitionId) const; + + spi::BucketInfoResult getBucketInfo(const spi::Bucket&) const; + + spi::Result put(const spi::Bucket&, spi::Timestamp, const document::Document::SP&, spi::Context&); + + spi::RemoveResult remove(const spi::Bucket&, + spi::Timestamp, + const spi::DocumentId&, + spi::Context&); + + spi::RemoveResult removeIfFound(const spi::Bucket&, + spi::Timestamp, + const spi::DocumentId&, + spi::Context&); + + spi::UpdateResult update(const spi::Bucket&, + spi::Timestamp, + const document::DocumentUpdate::SP&, + spi::Context&); + + spi::GetResult get(const spi::Bucket&, + const document::FieldSet&, + const spi::DocumentId&, + spi::Context&) const; + + spi::Result flush(const spi::Bucket&, spi::Context&); + + spi::CreateIteratorResult createIterator(const spi::Bucket&, + const document::FieldSet&, + const spi::Selection&, + spi::IncludedVersions versions, + spi::Context&); + + spi::IterateResult iterate(spi::IteratorId, + uint64_t maxByteSize, spi::Context&) const; + + spi::Result destroyIterator(spi::IteratorId, spi::Context&); + + spi::Result deleteBucket(const spi::Bucket&, spi::Context&); + + spi::Result split(const spi::Bucket& source, + const spi::Bucket& target1, + const spi::Bucket& target2, + spi::Context&); + + spi::Result join(const spi::Bucket& source1, + const spi::Bucket& source2, + const spi::Bucket& target, + spi::Context&); + + spi::Result removeEntry(const spi::Bucket&, + spi::Timestamp, + spi::Context&); +}; + +} // storage + |