summaryrefslogtreecommitdiffstats
path: root/persistence/src/tests/proxy
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /persistence/src/tests/proxy
Publish
Diffstat (limited to 'persistence/src/tests/proxy')
-rw-r--r--persistence/src/tests/proxy/.gitignore10
-rw-r--r--persistence/src/tests/proxy/CMakeLists.txt28
-rw-r--r--persistence/src/tests/proxy/dummy_provider_factory.h35
-rw-r--r--persistence/src/tests/proxy/external_providerproxy_conformancetest.cpp43
-rw-r--r--persistence/src/tests/proxy/mockprovider.h172
-rw-r--r--persistence/src/tests/proxy/providerproxy_conformancetest.cpp64
-rw-r--r--persistence/src/tests/proxy/providerproxy_test.cpp402
-rw-r--r--persistence/src/tests/proxy/providerstub_test.cpp538
-rw-r--r--persistence/src/tests/proxy/proxy_factory_wrapper.h59
-rw-r--r--persistence/src/tests/proxy/proxy_test.sh4
-rw-r--r--persistence/src/tests/proxy/proxyfactory.h42
11 files changed, 1397 insertions, 0 deletions
diff --git a/persistence/src/tests/proxy/.gitignore b/persistence/src/tests/proxy/.gitignore
new file mode 100644
index 00000000000..9bd2934723e
--- /dev/null
+++ b/persistence/src/tests/proxy/.gitignore
@@ -0,0 +1,10 @@
+/.depend
+/Makefile
+/providerstub_test
+/providerproxy_test
+/providerproxy_conformancetest
+/external_providerproxy_conformancetest
+persistence_providerproxy_conformance_test_app
+persistence_providerproxy_test_app
+persistence_providerstub_test_app
+persistence_external_providerproxy_conformancetest_app
diff --git a/persistence/src/tests/proxy/CMakeLists.txt b/persistence/src/tests/proxy/CMakeLists.txt
new file mode 100644
index 00000000000..c12eaf217a4
--- /dev/null
+++ b/persistence/src/tests/proxy/CMakeLists.txt
@@ -0,0 +1,28 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(persistence_providerstub_test_app
+ SOURCES
+ providerstub_test.cpp
+ DEPENDS
+ persistence
+)
+vespa_add_executable(persistence_providerproxy_test_app
+ SOURCES
+ providerproxy_test.cpp
+ DEPENDS
+ persistence
+)
+vespa_add_executable(persistence_providerproxy_conformance_test_app
+ SOURCES
+ providerproxy_conformancetest.cpp
+ DEPENDS
+ persistence
+ persistence_persistence_conformancetest
+)
+vespa_add_executable(persistence_external_providerproxy_conformancetest_app
+ SOURCES
+ external_providerproxy_conformancetest.cpp
+ DEPENDS
+ persistence
+ persistence_persistence_conformancetest
+)
+vespa_add_test(NAME persistence_providerproxy_conformance_test_app COMMAND sh proxy_test.sh)
diff --git a/persistence/src/tests/proxy/dummy_provider_factory.h b/persistence/src/tests/proxy/dummy_provider_factory.h
new file mode 100644
index 00000000000..8330b4a917b
--- /dev/null
+++ b/persistence/src/tests/proxy/dummy_provider_factory.h
@@ -0,0 +1,35 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/persistence/proxy/providerstub.h>
+#include <memory>
+
+namespace storage {
+namespace spi {
+
+/**
+ * A simple rpc server persistence provider factory that will only
+ * work once, by returning a precreated persistence provider instance.
+ **/
+struct DummyProviderFactory : ProviderStub::PersistenceProviderFactory
+{
+ typedef std::unique_ptr<DummyProviderFactory> UP;
+ typedef storage::spi::PersistenceProvider Provider;
+
+ mutable std::unique_ptr<Provider> provider;
+
+ DummyProviderFactory(std::unique_ptr<Provider> p) : provider(std::move(p)) {}
+
+ std::unique_ptr<Provider> create() const {
+ ASSERT_TRUE(provider.get() != 0);
+ std::unique_ptr<Provider> ret = std::move(provider);
+ ASSERT_TRUE(provider.get() == 0);
+ return ret;
+ }
+};
+
+} // namespace spi
+} // namespace storage
+
diff --git a/persistence/src/tests/proxy/external_providerproxy_conformancetest.cpp b/persistence/src/tests/proxy/external_providerproxy_conformancetest.cpp
new file mode 100644
index 00000000000..f24d81532b7
--- /dev/null
+++ b/persistence/src/tests/proxy/external_providerproxy_conformancetest.cpp
@@ -0,0 +1,43 @@
+// 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 <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/persistence/conformancetest/conformancetest.h>
+#include <vespa/persistence/proxy/providerproxy.h>
+#include <vespa/persistence/proxy/providerstub.h>
+#include "proxyfactory.h"
+
+using namespace storage::spi;
+typedef document::DocumentTypeRepo Repo;
+typedef ConformanceTest::PersistenceFactory Factory;
+
+namespace {
+
+struct ConformanceFixture : public ConformanceTest {
+ ConformanceFixture(Factory::UP f) : ConformanceTest(std::move(f)) { setUp(); }
+ ~ConformanceFixture() { tearDown(); }
+};
+
+Factory::UP getFactory() {
+ return Factory::UP(new ProxyFactory());
+}
+
+#define CONVERT_TEST(testFunction, makeFactory) \
+namespace ns_ ## testFunction { \
+TEST_F(TEST_STR(testFunction) " " TEST_STR(makeFactory), ConformanceFixture(makeFactory)) { \
+ f.testFunction(); \
+} \
+} // namespace testFunction
+
+#undef CPPUNIT_TEST
+#define CPPUNIT_TEST(testFunction) CONVERT_TEST(testFunction, MAKE_FACTORY)
+
+#define MAKE_FACTORY getFactory()
+DEFINE_CONFORMANCE_TESTS();
+
+} // namespace
+
+TEST_MAIN() {
+ TEST_RUN_ALL();
+}
diff --git a/persistence/src/tests/proxy/mockprovider.h b/persistence/src/tests/proxy/mockprovider.h
new file mode 100644
index 00000000000..c2fd844a010
--- /dev/null
+++ b/persistence/src/tests/proxy/mockprovider.h
@@ -0,0 +1,172 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/persistence/spi/persistenceprovider.h>
+
+namespace storage {
+namespace spi {
+
+struct MockProvider : PersistenceProvider {
+ enum Function { NONE, INITIALIZE, GET_PARTITION_STATES, LIST_BUCKETS,
+ SET_CLUSTER_STATE,
+ SET_ACTIVE_STATE, GET_BUCKET_INFO, PUT, REMOVE_BY_ID,
+ REMOVE_IF_FOUND, REPLACE_WITH_REMOVE, UPDATE, FLUSH, GET,
+ CREATE_ITERATOR, ITERATE, DESTROY_ITERATOR, CREATE_BUCKET,
+ DELETE_BUCKET, GET_MODIFIED_BUCKETS, SPLIT, JOIN, MOVE, MAINTAIN,
+ REMOVE_ENTRY };
+
+ mutable Function last_called;
+
+ MockProvider() : last_called(NONE) {}
+
+ virtual Result initialize() {
+ last_called = INITIALIZE;
+ return Result();
+ }
+
+ virtual PartitionStateListResult getPartitionStates() const {
+ last_called = GET_PARTITION_STATES;
+ return PartitionStateListResult(PartitionStateList(1u));
+ }
+
+ virtual BucketIdListResult listBuckets(PartitionId id) const {
+ last_called = LIST_BUCKETS;
+ BucketIdListResult::List result;
+ result.push_back(document::BucketId(id));
+ return BucketIdListResult(result);
+ }
+
+ virtual Result setClusterState(const ClusterState &) {
+ last_called = SET_CLUSTER_STATE;
+ return Result();
+ }
+
+ virtual Result setActiveState(const Bucket &,
+ BucketInfo::ActiveState) {
+ last_called = SET_ACTIVE_STATE;
+ return Result();
+ }
+
+ virtual BucketInfoResult getBucketInfo(const Bucket &bucket) const {
+ last_called = GET_BUCKET_INFO;
+ return BucketInfoResult(BucketInfo(BucketChecksum(1), 2, 3,
+ bucket.getBucketId().getRawId(),
+ bucket.getPartition(),
+ BucketInfo::READY,
+ BucketInfo::ACTIVE));
+ }
+
+ virtual Result put(const Bucket &, Timestamp, const Document::SP&, Context&) {
+ last_called = PUT;
+ return Result();
+ }
+
+ virtual RemoveResult remove(const Bucket &, Timestamp,
+ const DocumentId &, Context&) {
+ last_called = REMOVE_BY_ID;
+ return RemoveResult(true);
+ }
+
+ virtual RemoveResult removeIfFound(const Bucket &, Timestamp,
+ const DocumentId &, Context&) {
+ last_called = REMOVE_IF_FOUND;
+ return RemoveResult(true);
+ }
+
+ virtual RemoveResult replaceWithRemove(const Bucket &, Timestamp,
+ const DocumentId &, Context&) {
+ last_called = REPLACE_WITH_REMOVE;
+ return RemoveResult(true);
+ }
+
+ virtual UpdateResult update(const Bucket &, Timestamp timestamp,
+ const DocumentUpdate::SP&, Context&) {
+ last_called = UPDATE;
+ return UpdateResult(Timestamp(timestamp - 10));
+ }
+
+ virtual Result flush(const Bucket&, Context&) {
+ last_called = FLUSH;
+ return Result();
+ }
+
+ virtual GetResult get(const Bucket &, const document::FieldSet&,
+ const DocumentId&, Context&) const {
+ last_called = GET;
+ return GetResult(Document::UP(new Document),
+ Timestamp(6u));
+ }
+
+ virtual CreateIteratorResult createIterator(const Bucket& bucket,
+ const document::FieldSet&,
+ const Selection&,
+ IncludedVersions,
+ Context&)
+ {
+ last_called = CREATE_ITERATOR;
+ return CreateIteratorResult(IteratorId(bucket.getPartition()));
+ }
+
+ virtual IterateResult iterate(IteratorId, uint64_t, Context&) const {
+ last_called = ITERATE;
+ IterateResult::List result;
+ result.push_back(DocEntry::LP(new DocEntry(Timestamp(1), 0)));
+ return IterateResult(result, true);
+ }
+
+ virtual Result destroyIterator(IteratorId, Context&) {
+ last_called = DESTROY_ITERATOR;
+ return Result();
+ }
+
+ virtual Result createBucket(const Bucket&, Context&) {
+ last_called = CREATE_BUCKET;
+ return Result();
+ }
+ virtual Result deleteBucket(const Bucket&, Context&) {
+ last_called = DELETE_BUCKET;
+ return Result();
+ }
+
+ virtual BucketIdListResult getModifiedBuckets() const {
+ last_called = GET_MODIFIED_BUCKETS;
+ BucketIdListResult::List list;
+ list.push_back(document::BucketId(2));
+ list.push_back(document::BucketId(3));
+ return BucketIdListResult(list);
+ }
+
+ virtual Result split(const Bucket &, const Bucket &, const Bucket &,
+ Context&)
+ {
+ last_called = SPLIT;
+ return Result();
+ }
+
+ virtual Result join(const Bucket &, const Bucket &, const Bucket &,
+ Context&)
+ {
+ last_called = JOIN;
+ return Result();
+ }
+
+ virtual Result move(const Bucket &, PartitionId, Context&) {
+ last_called = MOVE;
+ return Result();
+ }
+
+
+ virtual Result maintain(const Bucket &, MaintenanceLevel) {
+ last_called = MAINTAIN;
+ return Result();
+ }
+
+ virtual Result removeEntry(const Bucket &, Timestamp, Context&) {
+ last_called = REMOVE_ENTRY;
+ return Result();
+ }
+};
+
+} // namespace spi
+} // namespace storage
+
diff --git a/persistence/src/tests/proxy/providerproxy_conformancetest.cpp b/persistence/src/tests/proxy/providerproxy_conformancetest.cpp
new file mode 100644
index 00000000000..cd2f711ffd5
--- /dev/null
+++ b/persistence/src/tests/proxy/providerproxy_conformancetest.cpp
@@ -0,0 +1,64 @@
+// 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 <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/persistence/conformancetest/conformancetest.h>
+#include <vespa/persistence/dummyimpl/dummypersistence.h>
+#include <vespa/persistence/proxy/providerproxy.h>
+#include <vespa/persistence/proxy/providerstub.h>
+#include "proxy_factory_wrapper.h"
+
+using namespace storage::spi;
+typedef document::DocumentTypeRepo Repo;
+typedef ConformanceTest::PersistenceFactory Factory;
+
+namespace {
+
+struct DummyFactory : Factory {
+ PersistenceProvider::UP getPersistenceImplementation(const Repo::SP& repo,
+ const document::DocumenttypesConfig &) {
+ return PersistenceProvider::UP(new dummy::DummyPersistence(repo, 4));
+ }
+
+ virtual bool
+ supportsActiveState() const
+ {
+ return true;
+ }
+};
+
+struct ConformanceFixture : public ConformanceTest {
+ ConformanceFixture(Factory::UP f) : ConformanceTest(std::move(f)) { setUp(); }
+ ~ConformanceFixture() { tearDown(); }
+};
+
+Factory::UP dummyViaProxy(size_t n) {
+ if (n == 0) {
+ return Factory::UP(new DummyFactory());
+ }
+ return Factory::UP(new ProxyFactoryWrapper(dummyViaProxy(n - 1)));
+}
+
+#define CONVERT_TEST(testFunction, makeFactory) \
+namespace ns_ ## testFunction { \
+TEST_F(TEST_STR(testFunction) " " TEST_STR(makeFactory), ConformanceFixture(makeFactory)) { \
+ f.testFunction(); \
+} \
+} // namespace testFunction
+
+#undef CPPUNIT_TEST
+#define CPPUNIT_TEST(testFunction) CONVERT_TEST(testFunction, MAKE_FACTORY)
+
+#define MAKE_FACTORY dummyViaProxy(1)
+DEFINE_CONFORMANCE_TESTS();
+
+#undef MAKE_FACTORY
+#define MAKE_FACTORY dummyViaProxy(7)
+DEFINE_CONFORMANCE_TESTS();
+
+} // namespace
+
+TEST_MAIN() {
+ TEST_RUN_ALL();
+}
diff --git a/persistence/src/tests/proxy/providerproxy_test.cpp b/persistence/src/tests/proxy/providerproxy_test.cpp
new file mode 100644
index 00000000000..34537b170e6
--- /dev/null
+++ b/persistence/src/tests/proxy/providerproxy_test.cpp
@@ -0,0 +1,402 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Unit tests for providerproxy.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("providerproxy_test");
+
+#include "dummy_provider_factory.h"
+#include "mockprovider.h"
+#include <vespa/document/bucket/bucketid.h>
+#include <vespa/document/datatype/datatype.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/persistence/proxy/providerproxy.h>
+#include <vespa/persistence/proxy/providerstub.h>
+#include <vespa/persistence/spi/abstractpersistenceprovider.h>
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/util/closure.h>
+#include <vespa/vespalib/util/closuretask.h>
+#include <vespa/vespalib/util/sync.h>
+#include <vespa/vespalib/util/threadstackexecutor.h>
+#include <vespa/document/fieldset/fieldsets.h>
+
+using document::BucketId;
+using document::DataType;
+using document::DocumentTypeRepo;
+using std::ostringstream;
+using vespalib::Gate;
+using vespalib::ThreadStackExecutor;
+using vespalib::makeClosure;
+using vespalib::makeTask;
+using namespace storage::spi;
+using namespace storage;
+
+namespace {
+
+const int port = 14863;
+const string connect_spec = "tcp/localhost:14863";
+LoadType defaultLoadType(0, "default");
+
+void startServer(const DocumentTypeRepo *repo, Gate *gate) {
+ DummyProviderFactory factory(MockProvider::UP(new MockProvider));
+ ProviderStub stub(port, 8, *repo, factory);
+ gate->await();
+ EXPECT_TRUE(stub.hasClient());
+}
+
+TEST("require that client can start connecting before server is up") {
+ const DocumentTypeRepo repo;
+ Gate gate;
+ ThreadStackExecutor executor(1, 65536);
+ executor.execute(makeTask(makeClosure(startServer, &repo, &gate)));
+ ProviderProxy proxy(connect_spec, repo);
+ gate.countDown();
+ executor.sync();
+}
+
+TEST("require that when the server goes down it causes permanent failure.") {
+ const DocumentTypeRepo repo;
+ DummyProviderFactory factory(MockProvider::UP(new MockProvider));
+ ProviderStub::UP server(new ProviderStub(port, 8, repo, factory));
+ ProviderProxy proxy(connect_spec, repo);
+ server.reset(0);
+
+ const uint64_t bucket_id = 21;
+ const PartitionId partition_id(42);
+ const Bucket bucket(BucketId(bucket_id), partition_id);
+ Context context(defaultLoadType, Priority(0), Trace::TraceLevel(0));
+ Result result = proxy.flush(bucket, context);
+ EXPECT_EQUAL(Result::FATAL_ERROR, result.getErrorCode());
+}
+
+struct Fixture {
+ MockProvider &mock_spi;
+ DummyProviderFactory factory;
+ DocumentTypeRepo repo;
+ ProviderStub stub;
+ ProviderProxy proxy;
+
+ Fixture()
+ : mock_spi(*(new MockProvider)),
+ factory(PersistenceProvider::UP(&mock_spi)),
+ repo(),
+ stub(port, 8, repo, factory),
+ proxy(connect_spec, repo) {}
+};
+
+TEST_F("require that client handles initialize", Fixture) {
+ Result result = f.proxy.initialize();
+ EXPECT_EQUAL(MockProvider::INITIALIZE, f.mock_spi.last_called);
+}
+
+TEST_F("require that client handles getPartitionStates", Fixture) {
+ PartitionStateListResult result = f.proxy.getPartitionStates();
+ EXPECT_EQUAL(MockProvider::GET_PARTITION_STATES, f.mock_spi.last_called);
+ EXPECT_EQUAL(1u, result.getList().size());
+}
+
+TEST_F("require that client handles listBuckets", Fixture) {
+ const PartitionId partition_id(42);
+
+ BucketIdListResult result = f.proxy.listBuckets(partition_id);
+ EXPECT_EQUAL(MockProvider::LIST_BUCKETS, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0, result.getErrorCode());
+ EXPECT_EQUAL("", result.getErrorMessage());
+ ASSERT_EQUAL(1u, result.getList().size());
+}
+
+TEST_F("require that client handles setClusterState", Fixture) {
+ lib::ClusterState s("version:1 storage:3 distributor:3");
+ lib::Distribution d(lib::Distribution::getDefaultDistributionConfig(3, 3));
+ ClusterState state(s, 0, d);
+
+ Result result = f.proxy.setClusterState(state);
+ EXPECT_EQUAL(MockProvider::SET_CLUSTER_STATE, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0, result.getErrorCode());
+ EXPECT_EQUAL("", result.getErrorMessage());
+}
+
+TEST_F("require that client handles setActiveState", Fixture) {
+ const uint64_t bucket_id = 21;
+ const PartitionId partition_id(42);
+ const Bucket bucket(BucketId(bucket_id), partition_id);
+ const BucketInfo::ActiveState bucket_state = BucketInfo::NOT_ACTIVE;
+
+ Result result = f.proxy.setActiveState(bucket, bucket_state);
+ EXPECT_EQUAL(MockProvider::SET_ACTIVE_STATE, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0, result.getErrorCode());
+ EXPECT_EQUAL("", result.getErrorMessage());
+}
+
+TEST_F("require that client handles getBucketInfo", Fixture) {
+ const uint64_t bucket_id = 21;
+ const PartitionId partition_id(42);
+ const Bucket bucket(BucketId(bucket_id), partition_id);
+
+ BucketInfoResult result = f.proxy.getBucketInfo(bucket);
+ EXPECT_EQUAL(MockProvider::GET_BUCKET_INFO, f.mock_spi.last_called);
+
+ const BucketInfo& info(result.getBucketInfo());
+ EXPECT_EQUAL(0, result.getErrorCode());
+ EXPECT_EQUAL("", result.getErrorMessage());
+ EXPECT_EQUAL(1u, info.getChecksum());
+ EXPECT_EQUAL(2u, info.getDocumentCount());
+ EXPECT_EQUAL(3u, info.getDocumentSize());
+ EXPECT_EQUAL(bucket_id, info.getEntryCount());
+ EXPECT_EQUAL(partition_id, info.getUsedSize());
+ EXPECT_EQUAL(true, info.isReady());
+ EXPECT_EQUAL(true, info.isActive());
+}
+
+TEST_F("require that client handles put", Fixture) {
+ const uint64_t bucket_id = 21;
+ const PartitionId partition_id(42);
+ const Bucket bucket(BucketId(bucket_id), partition_id);
+ const Timestamp timestamp(84);
+ Document::SP doc(new Document());
+
+ Context context(defaultLoadType, Priority(0), Trace::TraceLevel(0));
+ Result result = f.proxy.put(bucket, timestamp, doc, context);
+ EXPECT_EQUAL(MockProvider::PUT, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0, result.getErrorCode());
+ EXPECT_EQUAL("", result.getErrorMessage());
+}
+
+TEST_F("require that client handles remove by id", Fixture) {
+ const uint64_t bucket_id = 21;
+ const PartitionId partition_id(42);
+ const Bucket bucket(BucketId(bucket_id), partition_id);
+ const Timestamp timestamp(84);
+ const DocumentId id("doc:test:1");
+ Context context(defaultLoadType, Priority(0), Trace::TraceLevel(0));
+
+ RemoveResult result = f.proxy.remove(bucket, timestamp, id, context);
+ EXPECT_EQUAL(MockProvider::REMOVE_BY_ID, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0, result.getErrorCode());
+ EXPECT_EQUAL("", result.getErrorMessage());
+ EXPECT_EQUAL(true, result.wasFound());
+}
+
+TEST_F("require that client handles removeIfFound", Fixture) {
+ const uint64_t bucket_id = 21;
+ const PartitionId partition_id(42);
+ const Bucket bucket(BucketId(bucket_id), partition_id);
+ const Timestamp timestamp(84);
+ const DocumentId id("doc:test:1");
+ Context context(defaultLoadType, Priority(0), Trace::TraceLevel(0));
+
+ RemoveResult result = f.proxy.removeIfFound(bucket, timestamp, id, context);
+ EXPECT_EQUAL(MockProvider::REMOVE_IF_FOUND, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0, result.getErrorCode());
+ EXPECT_EQUAL("", result.getErrorMessage());
+ EXPECT_EQUAL(true, result.wasFound());
+}
+
+TEST_F("require that client handles update", Fixture) {
+ const uint64_t bucket_id = 21;
+ const PartitionId partition_id(42);
+ const Bucket bucket(BucketId(bucket_id), partition_id);
+ const Timestamp timestamp(84);
+ DocumentUpdate::SP update(new DocumentUpdate(*DataType::DOCUMENT, DocumentId("doc:test:1")));
+ Context context(defaultLoadType, Priority(0), Trace::TraceLevel(0));
+
+ UpdateResult result = f.proxy.update(bucket, timestamp, update, context);
+ EXPECT_EQUAL(MockProvider::UPDATE, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0, result.getErrorCode());
+ EXPECT_EQUAL("", result.getErrorMessage());
+ EXPECT_EQUAL(timestamp - 10, result.getExistingTimestamp());
+}
+
+TEST_F("require that client handles flush", Fixture) {
+ const uint64_t bucket_id = 21;
+ const PartitionId partition_id(42);
+ const Bucket bucket(BucketId(bucket_id), partition_id);
+ Context context(defaultLoadType, Priority(0), Trace::TraceLevel(0));
+
+ Result result = f.proxy.flush(bucket, context);
+ EXPECT_EQUAL(MockProvider::FLUSH, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0, result.getErrorCode());
+ EXPECT_EQUAL("", result.getErrorMessage());
+}
+
+TEST_F("require that client handles get", Fixture) {
+ const uint64_t bucket_id = 21;
+ const PartitionId partition_id(42);
+ const Bucket bucket(BucketId(bucket_id), partition_id);
+
+ document::AllFields field_set;
+ const DocumentId id("doc:test:1");
+ Context context(defaultLoadType, Priority(0), Trace::TraceLevel(0));
+
+ GetResult result = f.proxy.get(bucket, field_set, id, context);
+ EXPECT_EQUAL(MockProvider::GET, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0, result.getErrorCode());
+ EXPECT_EQUAL("", result.getErrorMessage());
+ EXPECT_EQUAL(6u, result.getTimestamp());
+ ASSERT_TRUE(result.hasDocument());
+ EXPECT_EQUAL(Document(), result.getDocument());
+}
+
+TEST_F("require that client handles createIterator", Fixture) {
+ const uint64_t bucket_id = 21;
+ const PartitionId partition_id(42);
+ const Bucket bucket(BucketId(bucket_id), partition_id);
+ const DocumentSelection doc_sel("docsel");
+ Context context(defaultLoadType, Priority(0), Trace::TraceLevel(0));
+
+ document::AllFields field_set;
+
+ Selection selection(doc_sel);
+ selection.setFromTimestamp(Timestamp(84));
+ selection.setToTimestamp(Timestamp(126));
+
+ CreateIteratorResult result =
+ f.proxy.createIterator(bucket, field_set, selection,
+ NEWEST_DOCUMENT_ONLY, context);
+
+ EXPECT_EQUAL(MockProvider::CREATE_ITERATOR, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0, result.getErrorCode());
+ EXPECT_EQUAL("", result.getErrorMessage());
+ EXPECT_EQUAL(partition_id, result.getIteratorId());
+}
+
+TEST_F("require that client handles iterate", Fixture) {
+ const IteratorId iterator_id(42);
+ const uint64_t max_byte_size = 21;
+ Context context(defaultLoadType, Priority(0), Trace::TraceLevel(0));
+
+ IterateResult result = f.proxy.iterate(iterator_id, max_byte_size, context);
+ EXPECT_EQUAL(MockProvider::ITERATE, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0, result.getErrorCode());
+ EXPECT_EQUAL("", result.getErrorMessage());
+ EXPECT_EQUAL(1u, result.getEntries().size());
+ EXPECT_TRUE(result.isCompleted());
+}
+
+TEST_F("require that client handles destroyIterator", Fixture) {
+ const IteratorId iterator_id(42);
+ Context context(defaultLoadType, Priority(0), Trace::TraceLevel(0));
+
+ f.proxy.destroyIterator(iterator_id, context);
+ EXPECT_EQUAL(MockProvider::DESTROY_ITERATOR, f.mock_spi.last_called);
+}
+
+TEST_F("require that client handles createBucket", Fixture) {
+ const uint64_t bucket_id = 21;
+ const PartitionId partition_id(42);
+ const Bucket bucket(BucketId(bucket_id), partition_id);
+ Context context(defaultLoadType, Priority(0), Trace::TraceLevel(0));
+
+ f.proxy.createBucket(bucket, context);
+ EXPECT_EQUAL(MockProvider::CREATE_BUCKET, f.mock_spi.last_called);
+}
+
+TEST_F("require that server accepts deleteBucket", Fixture) {
+ const uint64_t bucket_id = 21;
+ const PartitionId partition_id(42);
+ const Bucket bucket(BucketId(bucket_id), partition_id);
+ Context context(defaultLoadType, Priority(0), Trace::TraceLevel(0));
+
+ f.proxy.deleteBucket(bucket, context);
+ EXPECT_EQUAL(MockProvider::DELETE_BUCKET, f.mock_spi.last_called);
+}
+
+TEST_F("require that client handles getModifiedBuckets", Fixture) {
+ BucketIdListResult modifiedBuckets = f.proxy.getModifiedBuckets();
+ EXPECT_EQUAL(MockProvider::GET_MODIFIED_BUCKETS, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(2u, modifiedBuckets.getList().size());
+}
+
+TEST_F("require that client handles split", Fixture) {
+ const uint64_t bucket_id_1 = 21;
+ const PartitionId partition_id_1(42);
+ const Bucket bucket_1(BucketId(bucket_id_1), partition_id_1);
+ const uint64_t bucket_id_2 = 210;
+ const PartitionId partition_id_2(420);
+ const Bucket bucket_2(BucketId(bucket_id_2), partition_id_2);
+ const uint64_t bucket_id_3 = 2100;
+ const PartitionId partition_id_3(4200);
+ const Bucket bucket_3(BucketId(bucket_id_3), partition_id_3);
+ Context context(defaultLoadType, Priority(0), Trace::TraceLevel(0));
+
+ Result result = f.proxy.split(bucket_1, bucket_2, bucket_3, context);
+ EXPECT_EQUAL(MockProvider::SPLIT, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0, result.getErrorCode());
+ EXPECT_EQUAL("", result.getErrorMessage());
+}
+
+TEST_F("require that client handles join", Fixture) {
+ const uint64_t bucket_id_1 = 21;
+ const PartitionId partition_id_1(42);
+ const Bucket bucket_1(BucketId(bucket_id_1), partition_id_1);
+ const uint64_t bucket_id_2 = 210;
+ const PartitionId partition_id_2(420);
+ const Bucket bucket_2(BucketId(bucket_id_2), partition_id_2);
+ const uint64_t bucket_id_3 = 2100;
+ const PartitionId partition_id_3(4200);
+ const Bucket bucket_3(BucketId(bucket_id_3), partition_id_3);
+ Context context(defaultLoadType, Priority(0), Trace::TraceLevel(0));
+
+ Result result = f.proxy.join(bucket_1, bucket_2, bucket_3, context);
+ EXPECT_EQUAL(MockProvider::JOIN, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0, result.getErrorCode());
+ EXPECT_EQUAL("", result.getErrorMessage());
+}
+
+TEST_F("require that client handles move", Fixture) {
+ const uint64_t bucket_id = 21;
+ const PartitionId from_partition_id(42);
+ const PartitionId to_partition_id(43);
+ const Bucket bucket(BucketId(bucket_id), from_partition_id);
+ Context context(defaultLoadType, Priority(0), Trace::TraceLevel(0));
+
+ Result result = f.proxy.move(bucket, to_partition_id, context);
+ EXPECT_EQUAL(MockProvider::MOVE, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0, result.getErrorCode());
+ EXPECT_EQUAL("", result.getErrorMessage());
+}
+
+TEST_F("require that client handles maintain", Fixture) {
+ const uint64_t bucket_id = 21;
+ const PartitionId partition_id(42);
+ const Bucket bucket(BucketId(bucket_id), partition_id);
+
+ Result result = f.proxy.maintain(bucket, HIGH);
+ EXPECT_EQUAL(MockProvider::MAINTAIN, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0, result.getErrorCode());
+ EXPECT_EQUAL("", result.getErrorMessage());
+}
+
+TEST_F("require that client handles remove entry", Fixture) {
+ const uint64_t bucket_id = 21;
+ const PartitionId partition_id(42);
+ const Bucket bucket(BucketId(bucket_id), partition_id);
+ const Timestamp timestamp(345);
+ Context context(defaultLoadType, Priority(0), Trace::TraceLevel(0));
+
+ Result result = f.proxy.removeEntry(bucket, timestamp, context);
+ EXPECT_EQUAL(MockProvider::REMOVE_ENTRY, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0, result.getErrorCode());
+ EXPECT_EQUAL("", result.getErrorMessage());
+}
+
+} // namespace
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/persistence/src/tests/proxy/providerstub_test.cpp b/persistence/src/tests/proxy/providerstub_test.cpp
new file mode 100644
index 00000000000..07eed26db19
--- /dev/null
+++ b/persistence/src/tests/proxy/providerstub_test.cpp
@@ -0,0 +1,538 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Unit tests for providerstub.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("providerstub_test");
+
+#include <vespa/document/datatype/datatype.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/document/serialization/vespadocumentserializer.h>
+#include <vespa/document/util/bytebuffer.h>
+#include <vespa/persistence/proxy/buildid.h>
+#include <vespa/persistence/proxy/providerstub.h>
+#include <vespa/persistence/spi/abstractpersistenceprovider.h>
+#include <vespa/vespalib/objects/nbostream.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using document::BucketId;
+using document::ByteBuffer;
+using document::DataType;
+using document::DocumentTypeRepo;
+using document::VespaDocumentSerializer;
+using vespalib::nbostream;
+using namespace storage::spi;
+using namespace storage;
+
+#include <tests/proxy/mockprovider.h>
+#include "dummy_provider_factory.h"
+
+namespace {
+
+const int port = 14863;
+const char connect_spec[] = "tcp/localhost:14863";
+const string build_id = getBuildId();
+
+struct Fixture {
+ MockProvider &mock_spi;
+ DummyProviderFactory factory;
+ DocumentTypeRepo repo;
+ ProviderStub stub;
+ FRT_Supervisor supervisor;
+ FRT_RPCRequest *current_request;
+ FRT_Target *target;
+
+ Fixture()
+ : mock_spi(*(new MockProvider())),
+ factory(PersistenceProvider::UP(&mock_spi)),
+ repo(),
+ stub(port, 8, repo, factory),
+ supervisor(),
+ current_request(0),
+ target(supervisor.GetTarget(connect_spec))
+ {
+ supervisor.Start();
+ ASSERT_TRUE(target);
+ }
+ ~Fixture() {
+ if (current_request) {
+ current_request->SubRef();
+ }
+ target->SubRef();
+ supervisor.ShutDown(true);
+ }
+ FRT_RPCRequest *getRequest(const string &name) {
+ FRT_RPCRequest *req = supervisor.AllocRPCRequest(current_request);
+ current_request = req;
+ req->SetMethodName(name.c_str());
+ return req;
+ }
+ void callRpc(FRT_RPCRequest *req, const string &return_spec) {
+ target->InvokeSync(req, 5.0);
+ req->CheckReturnTypes(return_spec.c_str());
+ if (!EXPECT_EQUAL(uint32_t(FRTE_NO_ERROR), req->GetErrorCode())) {
+ TEST_FATAL(req->GetErrorMessage());
+ }
+ }
+ void failRpc(FRT_RPCRequest *req, uint32_t error_code) {
+ target->InvokeSync(req, 5.0);
+ EXPECT_EQUAL(error_code, req->GetErrorCode());
+ }
+};
+
+struct ConnectedFixture : Fixture {
+ ConnectedFixture() {
+ FRT_RPCRequest *req = getRequest("vespa.persistence.connect");
+ req->GetParams()->AddString(build_id.data(), build_id.size());
+ callRpc(req, "");
+ }
+};
+
+TEST("print build id") { fprintf(stderr, "build id: '%s'\n", getBuildId()); }
+
+TEST_F("require that server accepts connect", Fixture) {
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.connect");
+ req->GetParams()->AddString(build_id.data(), build_id.size());
+ f.callRpc(req, "");
+ EXPECT_TRUE(f.stub.hasClient());
+}
+
+TEST_F("require that connect can be called twice", ConnectedFixture) {
+ EXPECT_TRUE(f.stub.hasClient());
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.connect");
+ req->GetParams()->AddString(build_id.data(), build_id.size());
+ f.callRpc(req, "");
+ EXPECT_TRUE(f.stub.hasClient());
+}
+
+TEST_F("require that connect fails with wrong build id", Fixture) {
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.connect");
+ const string wrong_id = "wrong build id";
+ req->GetParams()->AddString(wrong_id.data(), wrong_id.size());
+ f.failRpc(req, FRTE_RPC_METHOD_FAILED);
+ string prefix("Wrong build id. Got 'wrong build id', required ");
+ EXPECT_EQUAL(prefix,
+ string(req->GetErrorMessage()).substr(0, prefix.size()));
+ EXPECT_FALSE(f.stub.hasClient());
+}
+
+TEST_F("require that only one client can connect", ConnectedFixture) {
+ EXPECT_TRUE(f.stub.hasClient());
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.connect");
+ req->GetParams()->AddString(build_id.data(), build_id.size());
+ FRT_Target *target = f.supervisor.GetTarget(connect_spec);
+ target->InvokeSync(req, 5.0);
+ target->SubRef();
+ EXPECT_EQUAL(uint32_t(FRTE_RPC_METHOD_FAILED), req->GetErrorCode());
+ EXPECT_EQUAL("Server is already connected",
+ string(req->GetErrorMessage()));
+}
+
+TEST_F("require that server accepts getPartitionStates", ConnectedFixture) {
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.getPartitionStates");
+ f.callRpc(req, "bsIS");
+ EXPECT_EQUAL(MockProvider::GET_PARTITION_STATES, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(0)._intval8);
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(1)._string._len);
+ EXPECT_EQUAL(1u, req->GetReturn()->GetValue(2)._int32_array._len);
+ EXPECT_EQUAL(1u, req->GetReturn()->GetValue(3)._string_array._len);
+}
+
+TEST_F("require that server accepts listBuckets", ConnectedFixture) {
+ const uint64_t partition_id = 42;
+
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.listBuckets");
+ req->GetParams()->AddInt64(partition_id);
+ f.callRpc(req, "bsL");
+ EXPECT_EQUAL(MockProvider::LIST_BUCKETS, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(0)._intval8);
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(1)._string._len);
+ EXPECT_EQUAL(1u, req->GetReturn()->GetValue(2)._int64_array._len);
+ EXPECT_EQUAL(partition_id,
+ req->GetReturn()->GetValue(2)._int64_array._pt[0]);
+}
+
+TEST_F("require that server accepts setClusterState", ConnectedFixture) {
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.setClusterState");
+
+ lib::ClusterState s("version:1 storage:3 distributor:3");
+ lib::Distribution d(lib::Distribution::getDefaultDistributionConfig(3, 3));
+ ClusterState state(s, 0, d);
+ vespalib::nbostream o;
+ state.serialize(o);
+ req->GetParams()->AddData(o.c_str(), o.size());
+ f.callRpc(req, "bs");
+ EXPECT_EQUAL(MockProvider::SET_CLUSTER_STATE, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(0)._intval8);
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(1)._string._len);
+}
+
+TEST_F("require that server accepts setActiveState", ConnectedFixture) {
+ const uint64_t bucket_id = 21;
+ const uint64_t partition_id = 42;
+ const BucketInfo::ActiveState bucket_state = BucketInfo::NOT_ACTIVE;
+
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.setActiveState");
+ req->GetParams()->AddInt64(bucket_id);
+ req->GetParams()->AddInt64(partition_id);
+ req->GetParams()->AddInt8(bucket_state);
+ f.callRpc(req, "bs");
+ EXPECT_EQUAL(MockProvider::SET_ACTIVE_STATE, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(0)._intval8);
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(1)._string._len);
+}
+
+TEST_F("require that server accepts getBucketInfo", ConnectedFixture) {
+ const uint64_t bucket_id = 21;
+ const uint64_t partition_id = 42;
+
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.getBucketInfo");
+ req->GetParams()->AddInt64(bucket_id);
+ req->GetParams()->AddInt64(partition_id);
+ f.callRpc(req, "bsiiiiibb");
+ EXPECT_EQUAL(MockProvider::GET_BUCKET_INFO, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(0)._intval8);
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(1)._string._len);
+ EXPECT_EQUAL(1u, req->GetReturn()->GetValue(2)._intval32);
+ EXPECT_EQUAL(2u, req->GetReturn()->GetValue(3)._intval32);
+ EXPECT_EQUAL(3u, req->GetReturn()->GetValue(4)._intval32);
+ EXPECT_EQUAL(bucket_id, req->GetReturn()->GetValue(5)._intval32);
+ EXPECT_EQUAL(partition_id, req->GetReturn()->GetValue(6)._intval32);
+ EXPECT_EQUAL(static_cast<uint8_t>(BucketInfo::READY),
+ req->GetReturn()->GetValue(7)._intval8);
+ EXPECT_EQUAL(static_cast<uint8_t>(BucketInfo::ACTIVE),
+ req->GetReturn()->GetValue(8)._intval8);
+}
+
+TEST_F("require that server accepts put", ConnectedFixture) {
+ const uint64_t bucket_id = 21;
+ const uint64_t partition_id = 42;
+ const Timestamp timestamp(84);
+ Document::UP doc(new Document);
+ nbostream stream;
+ VespaDocumentSerializer serializer(stream);
+ serializer.write(*doc, document::COMPLETE);
+
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.put");
+ req->GetParams()->AddInt64(bucket_id);
+ req->GetParams()->AddInt64(partition_id);
+ req->GetParams()->AddInt64(timestamp);
+ req->GetParams()->AddData(stream.c_str(), stream.size());
+ f.callRpc(req, "bs");
+ EXPECT_EQUAL(MockProvider::PUT, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(0)._intval8);
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(1)._string._len);
+}
+
+void testRemove(ConnectedFixture &f, const string &rpc_name,
+ MockProvider::Function func) {
+ const uint64_t bucket_id = 21;
+ const uint64_t partition_id = 42;
+ const Timestamp timestamp(84);
+ const DocumentId id("doc:test:1");
+
+ FRT_RPCRequest *req = f.getRequest(rpc_name);
+ req->GetParams()->AddInt64(bucket_id);
+ req->GetParams()->AddInt64(partition_id);
+ req->GetParams()->AddInt64(timestamp);
+ req->GetParams()->AddString(id.toString().data(), id.toString().size());
+ f.callRpc(req, "bsb");
+ EXPECT_EQUAL(func, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(0)._intval8);
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(1)._string._len);
+ EXPECT_TRUE(req->GetReturn()->GetValue(2)._intval8);
+}
+
+TEST_F("require that server accepts remove by id", ConnectedFixture) {
+ testRemove(f, "vespa.persistence.removeById", MockProvider::REMOVE_BY_ID);
+}
+
+TEST_F("require that server accepts removeIfFound", ConnectedFixture) {
+ testRemove(f, "vespa.persistence.removeIfFound",
+ MockProvider::REMOVE_IF_FOUND);
+}
+
+TEST_F("require that server accepts update", ConnectedFixture) {
+ const uint64_t bucket_id = 21;
+ const uint64_t partition_id = 42;
+ const Timestamp timestamp(84);
+ DocumentUpdate update(*DataType::DOCUMENT, DocumentId("doc:test:1"));
+ vespalib::nbostream stream;
+ update.serializeHEAD(stream);
+
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.update");
+ req->GetParams()->AddInt64(bucket_id);
+ req->GetParams()->AddInt64(partition_id);
+ req->GetParams()->AddInt64(timestamp);
+ req->GetParams()->AddData(stream.c_str(), stream.size());
+ f.callRpc(req, "bsl");
+ EXPECT_EQUAL(MockProvider::UPDATE, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(0)._intval8);
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(1)._string._len);
+ EXPECT_EQUAL(timestamp - 10, req->GetReturn()->GetValue(2)._intval64);
+}
+
+TEST_F("require that server accepts flush", ConnectedFixture) {
+ const uint64_t bucket_id = 21;
+ const uint64_t partition_id = 42;
+
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.flush");
+ req->GetParams()->AddInt64(bucket_id);
+ req->GetParams()->AddInt64(partition_id);
+ f.callRpc(req, "bs");
+ EXPECT_EQUAL(MockProvider::FLUSH, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(0)._intval8);
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(1)._string._len);
+}
+
+TEST_F("require that server accepts get", ConnectedFixture) {
+ const uint64_t bucket_id = 21;
+ const uint64_t partition_id = 42;
+ const string field_set_1 = "[all]";
+ const DocumentId id("doc:test:1");
+
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.get");
+ req->GetParams()->AddInt64(bucket_id);
+ req->GetParams()->AddInt64(partition_id);
+ req->GetParams()->AddString(field_set_1.data(), field_set_1.size());
+ req->GetParams()->AddString(id.toString().data(), id.toString().size());
+ f.callRpc(req, "bslx");
+ EXPECT_EQUAL(MockProvider::GET, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(0)._intval8);
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(1)._string._len);
+ EXPECT_EQUAL(6u, req->GetReturn()->GetValue(2)._intval64);
+ EXPECT_EQUAL(25u, req->GetReturn()->GetValue(3)._data._len);
+}
+
+TEST_F("require that server accepts createIterator", ConnectedFixture) {
+ const uint64_t bucket_id = 21;
+ const uint64_t partition_id = 42;
+ const string doc_sel = "docsel";
+ const Timestamp timestamp_from(84);
+ const Timestamp timestamp_to(126);
+ const Timestamp timestamp_subset(168);
+ const string field_set_1 = "[all]";
+ const bool include_removes = false;
+
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.createIterator");
+ req->GetParams()->AddInt64(bucket_id);
+ req->GetParams()->AddInt64(partition_id);
+ req->GetParams()->AddString(field_set_1.data(), field_set_1.size());
+ req->GetParams()->AddString(doc_sel.data(), doc_sel.size());
+ req->GetParams()->AddInt64(timestamp_from);
+ req->GetParams()->AddInt64(timestamp_to);
+ req->GetParams()->AddInt64Array(1)[0] = timestamp_subset;
+ req->GetParams()->AddInt8(include_removes);
+
+ f.callRpc(req, "bsl");
+ EXPECT_EQUAL(MockProvider::CREATE_ITERATOR, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(0)._intval8);
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(1)._string._len);
+ EXPECT_EQUAL(partition_id, req->GetReturn()->GetValue(2)._intval64);
+}
+
+TEST_F("require that server accepts iterate", ConnectedFixture) {
+ const uint64_t iterator_id = 42;
+ const uint64_t max_byte_size = 21;
+
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.iterate");
+ req->GetParams()->AddInt64(iterator_id);
+ req->GetParams()->AddInt64(max_byte_size);
+ f.callRpc(req, "bsLISXb");
+ EXPECT_EQUAL(MockProvider::ITERATE, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(0)._intval8);
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(1)._string._len);
+ EXPECT_EQUAL(1u, req->GetReturn()->GetValue(2)._int64_array._len);
+ EXPECT_EQUAL(1u, req->GetReturn()->GetValue(3)._int32_array._len);
+ EXPECT_EQUAL(1u, req->GetReturn()->GetValue(4)._string_array._len);
+ EXPECT_EQUAL(1u, req->GetReturn()->GetValue(5)._data_array._len);
+ EXPECT_TRUE(req->GetReturn()->GetValue(6)._intval8);
+}
+
+TEST_F("require that server accepts destroyIterator", ConnectedFixture) {
+ const uint64_t iterator_id = 42;
+
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.destroyIterator");
+ req->GetParams()->AddInt64(iterator_id);
+ f.callRpc(req, "bs");
+ EXPECT_EQUAL(MockProvider::DESTROY_ITERATOR, f.mock_spi.last_called);
+}
+
+TEST_F("require that server accepts createBucket", ConnectedFixture) {
+ const uint64_t bucket_id = 21;
+ const uint64_t partition_id = 42;
+
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.createBucket");
+ req->GetParams()->AddInt64(bucket_id);
+ req->GetParams()->AddInt64(partition_id);
+ f.callRpc(req, "bs");
+ EXPECT_EQUAL(MockProvider::CREATE_BUCKET, f.mock_spi.last_called);
+}
+
+TEST_F("require that server accepts deleteBucket", ConnectedFixture) {
+ const uint64_t bucket_id = 21;
+ const uint64_t partition_id = 42;
+
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.deleteBucket");
+ req->GetParams()->AddInt64(bucket_id);
+ req->GetParams()->AddInt64(partition_id);
+ f.callRpc(req, "bs");
+ EXPECT_EQUAL(MockProvider::DELETE_BUCKET, f.mock_spi.last_called);
+}
+
+TEST_F("require that server accepts getModifiedBuckets", ConnectedFixture) {
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.getModifiedBuckets");
+ f.callRpc(req, "bsL");
+ EXPECT_EQUAL(MockProvider::GET_MODIFIED_BUCKETS, f.mock_spi.last_called);
+ EXPECT_EQUAL(2u, req->GetReturn()->GetValue(2)._int64_array._len);
+}
+
+TEST_F("require that server accepts split", ConnectedFixture) {
+ const uint64_t bucket_id_1 = 21;
+ const uint64_t partition_id_1 = 42;
+ const uint64_t bucket_id_2 = 210;
+ const uint64_t partition_id_2 = 420;
+ const uint64_t bucket_id_3 = 2100;
+ const uint64_t partition_id_3 = 4200;
+
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.split");
+ req->GetParams()->AddInt64(bucket_id_1);
+ req->GetParams()->AddInt64(partition_id_1);
+ req->GetParams()->AddInt64(bucket_id_2);
+ req->GetParams()->AddInt64(partition_id_2);
+ req->GetParams()->AddInt64(bucket_id_3);
+ req->GetParams()->AddInt64(partition_id_3);
+ f.callRpc(req, "bs");
+ EXPECT_EQUAL(MockProvider::SPLIT, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(0)._intval8);
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(1)._string._len);
+}
+
+TEST_F("require that server accepts join", ConnectedFixture) {
+ const uint64_t bucket_id_1 = 21;
+ const uint64_t partition_id_1 = 42;
+ const uint64_t bucket_id_2 = 210;
+ const uint64_t partition_id_2 = 420;
+ const uint64_t bucket_id_3 = 2100;
+ const uint64_t partition_id_3 = 4200;
+
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.join");
+ req->GetParams()->AddInt64(bucket_id_1);
+ req->GetParams()->AddInt64(partition_id_1);
+ req->GetParams()->AddInt64(bucket_id_2);
+ req->GetParams()->AddInt64(partition_id_2);
+ req->GetParams()->AddInt64(bucket_id_3);
+ req->GetParams()->AddInt64(partition_id_3);
+ f.callRpc(req, "bs");
+ EXPECT_EQUAL(MockProvider::JOIN, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(0)._intval8);
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(1)._string._len);
+}
+
+TEST_F("require that server accepts move", ConnectedFixture) {
+ const uint64_t bucket_id = 21;
+ const uint64_t from_partition_id = 42;
+ const uint64_t to_partition_id = 43;
+
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.move");
+ req->GetParams()->AddInt64(bucket_id);
+ req->GetParams()->AddInt64(from_partition_id);
+ req->GetParams()->AddInt64(to_partition_id);
+ f.callRpc(req, "bs");
+ EXPECT_EQUAL(MockProvider::MOVE, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(0)._intval8);
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(1)._string._len);
+}
+
+TEST_F("require that server accepts maintain", ConnectedFixture) {
+ const uint64_t bucket_id = 21;
+ const uint64_t partition_id = 42;
+ const MaintenanceLevel verification_level = HIGH;
+
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.maintain");
+ req->GetParams()->AddInt64(bucket_id);
+ req->GetParams()->AddInt64(partition_id);
+ req->GetParams()->AddInt8(verification_level);
+ f.callRpc(req, "bs");
+ EXPECT_EQUAL(MockProvider::MAINTAIN, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(0)._intval8);
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(1)._string._len);
+}
+
+TEST_F("require that server accepts remove_entry", ConnectedFixture) {
+ const uint64_t bucket_id = 21;
+ const uint64_t partition_id = 42;
+ const Timestamp timestamp(345);
+
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence.removeEntry");
+ req->GetParams()->AddInt64(bucket_id);
+ req->GetParams()->AddInt64(partition_id);
+ req->GetParams()->AddInt64(timestamp);
+ f.callRpc(req, "bs");
+ EXPECT_EQUAL(MockProvider::REMOVE_ENTRY, f.mock_spi.last_called);
+
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(0)._intval8);
+ EXPECT_EQUAL(0u, req->GetReturn()->GetValue(1)._string._len);
+}
+
+void checkRpcFails(const string &name, const string &param_spec, Fixture &f) {
+ TEST_STATE(name.c_str());
+ FRT_RPCRequest *req = f.getRequest("vespa.persistence." + name);
+ for (size_t i = 0; i < param_spec.size(); ++i) {
+ switch(param_spec[i]) {
+ case 'b': req->GetParams()->AddInt8(0); break;
+ case 'l': req->GetParams()->AddInt64(0); break;
+ case 'L': req->GetParams()->AddInt64Array(0); break;
+ case 's': req->GetParams()->AddString(0, 0); break;
+ case 'S': req->GetParams()->AddStringArray(0); break;
+ case 'x': req->GetParams()->AddData(0, 0); break;
+ }
+ }
+ f.failRpc(req, FRTE_RPC_METHOD_FAILED);
+}
+
+TEST_F("require that unconnected server fails all SPI calls.", Fixture)
+{
+ checkRpcFails("initialize", "", f);
+ checkRpcFails("getPartitionStates", "", f);
+ checkRpcFails("listBuckets", "l", f);
+ checkRpcFails("setClusterState", "x", f);
+ checkRpcFails("setActiveState", "llb", f);
+ checkRpcFails("getBucketInfo", "ll", f);
+ checkRpcFails("put", "lllx", f);
+ checkRpcFails("removeById", "llls", f);
+ checkRpcFails("removeIfFound", "llls", f);
+ checkRpcFails("update", "lllx", f);
+ checkRpcFails("flush", "ll", f);
+ checkRpcFails("get", "llss", f);
+ checkRpcFails("createIterator", "llssllLb", f);
+ checkRpcFails("iterate", "ll", f);
+ checkRpcFails("destroyIterator", "l", f);
+ checkRpcFails("createBucket", "ll", f);
+ checkRpcFails("deleteBucket", "ll", f);
+ checkRpcFails("getModifiedBuckets", "", f);
+ checkRpcFails("split", "llllll", f);
+ checkRpcFails("join", "llllll", f);
+ checkRpcFails("maintain", "llb", f);
+ checkRpcFails("removeEntry", "lll", f);
+}
+
+} // namespace
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/persistence/src/tests/proxy/proxy_factory_wrapper.h b/persistence/src/tests/proxy/proxy_factory_wrapper.h
new file mode 100644
index 00000000000..10f251f2beb
--- /dev/null
+++ b/persistence/src/tests/proxy/proxy_factory_wrapper.h
@@ -0,0 +1,59 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vespalib/util/vstringfmt.h>
+#include <vespa/persistence/conformancetest/conformancetest.h>
+#include <vespa/persistence/proxy/providerstub.h>
+#include <vespa/persistence/proxy/providerproxy.h>
+#include "dummy_provider_factory.h"
+
+namespace storage {
+namespace spi {
+
+/**
+ * Generic wrapper for persistence conformance test factories. This
+ * wrapper will take any other factory and expose a factory interface
+ * that will create persistence instances that communicate with
+ * persistence instances created by the wrapped factory using the RPC
+ * persistence Proxy.
+ **/
+struct ProxyFactoryWrapper : ConformanceTest::PersistenceFactory
+{
+ typedef storage::spi::ConformanceTest::PersistenceFactory Factory;
+ typedef storage::spi::PersistenceProvider Provider;
+ typedef storage::spi::ProviderStub Server;
+ typedef storage::spi::ProviderProxy Client;
+ typedef document::DocumentTypeRepo Repo;
+
+ Factory::UP factory;
+ ProxyFactoryWrapper(Factory::UP f) : factory(std::move(f)) {}
+
+ struct Wrapper : Client {
+ DummyProviderFactory::UP provider;
+ Server::UP server;
+ Wrapper(DummyProviderFactory::UP p, Server::UP s, const Repo &repo)
+ : Client(vespalib::make_vespa_string("tcp/localhost:%u", s->getPort()), repo),
+ provider(std::move(p)),
+ server(std::move(s))
+ {}
+ };
+
+ virtual Provider::UP
+ getPersistenceImplementation(const document::DocumentTypeRepo::SP &repo,
+ const document::DocumenttypesConfig &typesCfg) {
+ DummyProviderFactory::UP provider(new DummyProviderFactory(factory->getPersistenceImplementation(repo,
+ typesCfg)));
+ Server::UP server(new Server(0, 8, *repo, *provider));
+ return Provider::UP(new Wrapper(std::move(provider), std::move(server), *repo));
+ }
+
+ virtual bool
+ supportsActiveState() const
+ {
+ return factory->supportsActiveState();
+ }
+};
+} // namespace spi
+} // namespace storage
+
diff --git a/persistence/src/tests/proxy/proxy_test.sh b/persistence/src/tests/proxy/proxy_test.sh
new file mode 100644
index 00000000000..a78487831d6
--- /dev/null
+++ b/persistence/src/tests/proxy/proxy_test.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+$VALGRIND ./persistence_providerstub_test_app
+$VALGRIND ./persistence_providerproxy_test_app
+$VALGRIND ./persistence_providerproxy_conformance_test_app
diff --git a/persistence/src/tests/proxy/proxyfactory.h b/persistence/src/tests/proxy/proxyfactory.h
new file mode 100644
index 00000000000..9de9a39e873
--- /dev/null
+++ b/persistence/src/tests/proxy/proxyfactory.h
@@ -0,0 +1,42 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vespalib/util/vstringfmt.h>
+#include <vespa/persistence/conformancetest/conformancetest.h>
+#include <vespa/persistence/proxy/providerstub.h>
+#include <vespa/persistence/proxy/providerproxy.h>
+
+namespace storage {
+namespace spi {
+
+/**
+ * Generic wrapper for persistence conformance test factories. This
+ * wrapper will take any other factory and expose a factory interface
+ * that will create persistence instances that communicate with
+ * persistence instances created by the wrapped factory using the RPC
+ * persistence Proxy.
+ **/
+struct ProxyFactory : ConformanceTest::PersistenceFactory
+{
+ typedef storage::spi::PersistenceProvider Provider;
+ typedef storage::spi::ProviderProxy Client;
+ typedef document::DocumentTypeRepo Repo;
+
+ ProxyFactory() {}
+
+ virtual Provider::UP
+ getPersistenceImplementation(const document::DocumentTypeRepo::SP &repo,
+ const document::DocumenttypesConfig &) {
+ return Provider::UP(new Client("tcp/localhost:3456", *repo));
+ }
+
+ virtual bool
+ supportsActiveState() const
+ {
+ return false;
+ }
+};
+} // namespace spi
+} // namespace storage
+