summaryrefslogtreecommitdiffstats
path: root/storage
diff options
context:
space:
mode:
authorHenning Baldersheim <balder@yahoo-inc.com>2022-05-19 13:15:47 +0000
committerHenning Baldersheim <balder@yahoo-inc.com>2022-05-19 13:15:47 +0000
commit7292aeb064b53c1a72c57ce8d3ad8e0c7886e446 (patch)
treed92c5fd725189d08ebdea00693600d4924137925 /storage
parentb875d128cd2ca5368c2a3ab1dc3e185c62c4a237 (diff)
Fold storageapi into storage.
Diffstat (limited to 'storage')
-rw-r--r--storage/CMakeLists.txt10
-rw-r--r--storage/src/tests/persistence/filestorage/CMakeLists.txt1
-rw-r--r--storage/src/tests/storageapi/.gitignore10
-rw-r--r--storage/src/tests/storageapi/CMakeLists.txt18
-rw-r--r--storage/src/tests/storageapi/buckets/.gitignore9
-rw-r--r--storage/src/tests/storageapi/buckets/CMakeLists.txt8
-rw-r--r--storage/src/tests/storageapi/buckets/bucketinfotest.cpp28
-rw-r--r--storage/src/tests/storageapi/gtest_runner.cpp8
-rw-r--r--storage/src/tests/storageapi/mbusprot/.gitignore11
-rw-r--r--storage/src/tests/storageapi/mbusprot/CMakeLists.txt8
-rw-r--r--storage/src/tests/storageapi/mbusprot/mbusprot.4.2.serialization.V_4_2_STABLE66
-rw-r--r--storage/src/tests/storageapi/mbusprot/storageprotocoltest.cpp855
-rw-r--r--storage/src/tests/storageapi/messageapi/.gitignore9
-rw-r--r--storage/src/tests/storageapi/messageapi/CMakeLists.txt8
-rw-r--r--storage/src/tests/storageapi/messageapi/storage_message_address_test.cpp40
-rw-r--r--storage/src/vespa/storage/CMakeLists.txt6
-rw-r--r--storage/src/vespa/storageapi/.gitignore5
-rw-r--r--storage/src/vespa/storageapi/app/.gitignore14
-rw-r--r--storage/src/vespa/storageapi/app/CMakeLists.txt7
-rw-r--r--storage/src/vespa/storageapi/app/getbucketid.cpp18
-rw-r--r--storage/src/vespa/storageapi/buckets/.gitignore9
-rw-r--r--storage/src/vespa/storageapi/buckets/CMakeLists.txt6
-rw-r--r--storage/src/vespa/storageapi/buckets/bucketinfo.cpp132
-rw-r--r--storage/src/vespa/storageapi/buckets/bucketinfo.h84
-rw-r--r--storage/src/vespa/storageapi/defs.h18
-rw-r--r--storage/src/vespa/storageapi/mbusprot/.gitignore10
-rw-r--r--storage/src/vespa/storageapi/mbusprot/CMakeLists.txt34
-rw-r--r--storage/src/vespa/storageapi/mbusprot/legacyprotocolserialization.h31
-rw-r--r--storage/src/vespa/storageapi/mbusprot/oldreturncodemapper.h68
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protobuf/common.proto68
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protobuf/feed.proto104
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protobuf/inspect.proto19
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protobuf/maintenance.proto167
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protobuf/visiting.proto66
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protobuf_includes.h16
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protocolserialization.cpp281
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protocolserialization.h159
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protocolserialization4_2.cpp553
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protocolserialization4_2.h72
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protocolserialization5_0.cpp624
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protocolserialization5_0.h73
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protocolserialization5_1.cpp198
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protocolserialization5_1.h34
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protocolserialization5_2.cpp64
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protocolserialization5_2.h32
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protocolserialization6_0.cpp40
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protocolserialization6_0.h24
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protocolserialization7.cpp1351
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protocolserialization7.h147
-rw-r--r--storage/src/vespa/storageapi/mbusprot/serializationhelper.h110
-rw-r--r--storage/src/vespa/storageapi/mbusprot/storagecommand.cpp11
-rw-r--r--storage/src/vespa/storageapi/mbusprot/storagecommand.h37
-rw-r--r--storage/src/vespa/storageapi/mbusprot/storagemessage.cpp0
-rw-r--r--storage/src/vespa/storageapi/mbusprot/storagemessage.h20
-rw-r--r--storage/src/vespa/storageapi/mbusprot/storageprotocol.cpp203
-rw-r--r--storage/src/vespa/storageapi/mbusprot/storageprotocol.h34
-rw-r--r--storage/src/vespa/storageapi/mbusprot/storagereply.cpp55
-rw-r--r--storage/src/vespa/storageapi/mbusprot/storagereply.h46
-rw-r--r--storage/src/vespa/storageapi/message/.gitignore7
-rw-r--r--storage/src/vespa/storageapi/message/CMakeLists.txt17
-rw-r--r--storage/src/vespa/storageapi/message/bucket.cpp652
-rw-r--r--storage/src/vespa/storageapi/message/bucket.h502
-rw-r--r--storage/src/vespa/storageapi/message/bucketsplitting.cpp134
-rw-r--r--storage/src/vespa/storageapi/message/bucketsplitting.h120
-rw-r--r--storage/src/vespa/storageapi/message/datagram.cpp100
-rw-r--r--storage/src/vespa/storageapi/message/datagram.h77
-rw-r--r--storage/src/vespa/storageapi/message/documentsummary.cpp43
-rw-r--r--storage/src/vespa/storageapi/message/documentsummary.h39
-rw-r--r--storage/src/vespa/storageapi/message/internal.cpp45
-rw-r--r--storage/src/vespa/storageapi/message/internal.h69
-rw-r--r--storage/src/vespa/storageapi/message/persistence.cpp345
-rw-r--r--storage/src/vespa/storageapi/message/persistence.h333
-rw-r--r--storage/src/vespa/storageapi/message/queryresult.cpp45
-rw-r--r--storage/src/vespa/storageapi/message/queryresult.h48
-rw-r--r--storage/src/vespa/storageapi/message/removelocation.cpp34
-rw-r--r--storage/src/vespa/storageapi/message/removelocation.h35
-rw-r--r--storage/src/vespa/storageapi/message/searchresult.cpp47
-rw-r--r--storage/src/vespa/storageapi/message/searchresult.h37
-rw-r--r--storage/src/vespa/storageapi/message/stat.cpp104
-rw-r--r--storage/src/vespa/storageapi/message/stat.h90
-rw-r--r--storage/src/vespa/storageapi/message/state.cpp142
-rw-r--r--storage/src/vespa/storageapi/message/state.h119
-rw-r--r--storage/src/vespa/storageapi/message/visitor.cpp233
-rw-r--r--storage/src/vespa/storageapi/message/visitor.h244
-rw-r--r--storage/src/vespa/storageapi/messageapi/.gitignore6
-rw-r--r--storage/src/vespa/storageapi/messageapi/CMakeLists.txt14
-rw-r--r--storage/src/vespa/storageapi/messageapi/bucketcommand.cpp46
-rw-r--r--storage/src/vespa/storageapi/messageapi/bucketcommand.h33
-rw-r--r--storage/src/vespa/storageapi/messageapi/bucketinfocommand.cpp21
-rw-r--r--storage/src/vespa/storageapi/messageapi/bucketinfocommand.h30
-rw-r--r--storage/src/vespa/storageapi/messageapi/bucketinforeply.cpp19
-rw-r--r--storage/src/vespa/storageapi/messageapi/bucketinforeply.h38
-rw-r--r--storage/src/vespa/storageapi/messageapi/bucketreply.cpp36
-rw-r--r--storage/src/vespa/storageapi/messageapi/bucketreply.h42
-rw-r--r--storage/src/vespa/storageapi/messageapi/maintenancecommand.cpp9
-rw-r--r--storage/src/vespa/storageapi/messageapi/maintenancecommand.h28
-rw-r--r--storage/src/vespa/storageapi/messageapi/messagehandler.h196
-rw-r--r--storage/src/vespa/storageapi/messageapi/returncode.cpp169
-rw-r--r--storage/src/vespa/storageapi/messageapi/returncode.h117
-rw-r--r--storage/src/vespa/storageapi/messageapi/storagecommand.cpp41
-rw-r--r--storage/src/vespa/storageapi/messageapi/storagecommand.h56
-rw-r--r--storage/src/vespa/storageapi/messageapi/storagemessage.cpp306
-rw-r--r--storage/src/vespa/storageapi/messageapi/storagemessage.h455
-rw-r--r--storage/src/vespa/storageapi/messageapi/storagereply.cpp38
-rw-r--r--storage/src/vespa/storageapi/messageapi/storagereply.h39
105 files changed, 11537 insertions, 2 deletions
diff --git a/storage/CMakeLists.txt b/storage/CMakeLists.txt
index fc084bf2e80..a3768f9a193 100644
--- a/storage/CMakeLists.txt
+++ b/storage/CMakeLists.txt
@@ -2,7 +2,6 @@
vespa_define_module(
DEPENDS
vespadefaults
- storageapi
fastos
fastlib_fast
metrics
@@ -49,6 +48,11 @@ vespa_define_module(
src/vespa/storageframework/generic/metric
src/vespa/storageframework/generic/status
src/vespa/storageframework/generic/thread
+ src/vespa/storageapi/app
+ src/vespa/storageapi/buckets
+ src/vespa/storageapi/mbusprot
+ src/vespa/storageapi/message
+ src/vespa/storageapi/messageapi
TEST_DEPENDS
messagebus_messagebus-test
@@ -66,6 +70,10 @@ vespa_define_module(
src/tests/persistence
src/tests/persistence/common
src/tests/persistence/filestorage
+ src/tests/storageapi
+ src/tests/storageapi/buckets
+ src/tests/storageapi/mbusprot
+ src/tests/storageapi/messageapi
src/tests/storageframework
src/tests/storageframework/clock
src/tests/storageframework/thread
diff --git a/storage/src/tests/persistence/filestorage/CMakeLists.txt b/storage/src/tests/persistence/filestorage/CMakeLists.txt
index c9623e991e0..00a57410b54 100644
--- a/storage/src/tests/persistence/filestorage/CMakeLists.txt
+++ b/storage/src/tests/persistence/filestorage/CMakeLists.txt
@@ -15,7 +15,6 @@ vespa_add_executable(storage_filestorage_gtest_runner_app TEST
gtest_runner.cpp
DEPENDS
storage
- storageapi
storage_testhostreporter
storage_testpersistence_common
GTest::GTest
diff --git a/storage/src/tests/storageapi/.gitignore b/storage/src/tests/storageapi/.gitignore
new file mode 100644
index 00000000000..29911acfe67
--- /dev/null
+++ b/storage/src/tests/storageapi/.gitignore
@@ -0,0 +1,10 @@
+*.core
+.*.swp
+.config.log
+.depend
+Makefile
+docstorage
+test.vlog
+testrunner
+storageapi_gtest_runner_app
+storageapi_testrunner_app
diff --git a/storage/src/tests/storageapi/CMakeLists.txt b/storage/src/tests/storageapi/CMakeLists.txt
new file mode 100644
index 00000000000..cfcdfd55350
--- /dev/null
+++ b/storage/src/tests/storageapi/CMakeLists.txt
@@ -0,0 +1,18 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+vespa_add_executable(storageapi_gtest_runner_app TEST
+ SOURCES
+ gtest_runner.cpp
+ DEPENDS
+ storageapi_testbuckets
+ storageapi_testmbusprot
+ storageapi_testmessageapi
+ storage
+ GTest::GTest
+)
+
+vespa_add_test(
+ NAME storageapi_gtest_runner_app
+ COMMAND storageapi_gtest_runner_app
+)
+
diff --git a/storage/src/tests/storageapi/buckets/.gitignore b/storage/src/tests/storageapi/buckets/.gitignore
new file mode 100644
index 00000000000..1d859456ef9
--- /dev/null
+++ b/storage/src/tests/storageapi/buckets/.gitignore
@@ -0,0 +1,9 @@
+*.So
+*.core
+*.so
+.*.swp
+.config.log
+.depend
+Makefile
+test.vlog
+testrunner
diff --git a/storage/src/tests/storageapi/buckets/CMakeLists.txt b/storage/src/tests/storageapi/buckets/CMakeLists.txt
new file mode 100644
index 00000000000..7d2bdb77557
--- /dev/null
+++ b/storage/src/tests/storageapi/buckets/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_library(storageapi_testbuckets
+ SOURCES
+ bucketinfotest.cpp
+ DEPENDS
+ storage
+ GTest::GTest
+)
diff --git a/storage/src/tests/storageapi/buckets/bucketinfotest.cpp b/storage/src/tests/storageapi/buckets/bucketinfotest.cpp
new file mode 100644
index 00000000000..25648fa7e96
--- /dev/null
+++ b/storage/src/tests/storageapi/buckets/bucketinfotest.cpp
@@ -0,0 +1,28 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/storageapi/buckets/bucketinfo.h>
+#include <gtest/gtest.h>
+
+namespace storage::api {
+
+/** Tests simple operations */
+TEST(BucketInfoTest, testSimple)
+{
+ BucketInfo info;
+
+ EXPECT_FALSE(info.valid());
+ EXPECT_EQ(0u, info.getChecksum());
+ EXPECT_EQ(0u, info.getDocumentCount());
+ EXPECT_EQ(1u, info.getTotalDocumentSize());
+
+ info.setChecksum(0xa000bbbb);
+ info.setDocumentCount(15);
+ info.setTotalDocumentSize(64000);
+
+ EXPECT_TRUE(info.valid());
+ EXPECT_EQ(0xa000bbbb, info.getChecksum());
+ EXPECT_EQ(15u, info.getDocumentCount());
+ EXPECT_EQ(64000u, info.getTotalDocumentSize());
+};
+
+}
diff --git a/storage/src/tests/storageapi/gtest_runner.cpp b/storage/src/tests/storageapi/gtest_runner.cpp
new file mode 100644
index 00000000000..2ae414830b9
--- /dev/null
+++ b/storage/src/tests/storageapi/gtest_runner.cpp
@@ -0,0 +1,8 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/gtest/gtest.h>
+
+#include <vespa/log/log.h>
+LOG_SETUP("storageapi_gtest_runner");
+
+GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/storage/src/tests/storageapi/mbusprot/.gitignore b/storage/src/tests/storageapi/mbusprot/.gitignore
new file mode 100644
index 00000000000..1b779fa320a
--- /dev/null
+++ b/storage/src/tests/storageapi/mbusprot/.gitignore
@@ -0,0 +1,11 @@
+*.So
+*.core
+*.so
+.*.swp
+.config.log
+.depend
+Makefile
+test.vlog
+testrunner
+/mbusprot.4.2.serialization.HEAD
+/mbusprot.5.0.serialization.5.1
diff --git a/storage/src/tests/storageapi/mbusprot/CMakeLists.txt b/storage/src/tests/storageapi/mbusprot/CMakeLists.txt
new file mode 100644
index 00000000000..1f10f6d519a
--- /dev/null
+++ b/storage/src/tests/storageapi/mbusprot/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_library(storageapi_testmbusprot
+ SOURCES
+ storageprotocoltest.cpp
+ DEPENDS
+ storage
+ GTest::GTest
+)
diff --git a/storage/src/tests/storageapi/mbusprot/mbusprot.4.2.serialization.V_4_2_STABLE b/storage/src/tests/storageapi/mbusprot/mbusprot.4.2.serialization.V_4_2_STABLE
new file mode 100644
index 00000000000..5045a98b037
--- /dev/null
+++ b/storage/src/tests/storageapi/mbusprot/mbusprot.4.2.serialization.V_4_2_STABLE
@@ -0,0 +1,66 @@
+
+MessageType(10, Put)
+\00\00\00
+\00\00\00j\00\08\00\00\00ddoc:test:test\00\05testdoctype1\00\00\01\00\00\00=\00\01\05\00= ;This is the contents of the test document.
+It ain't much.
+\00@\00\00\00\00\00\00Q\00\00\00\00\00\00\00\0e\00\00\00\00\00\00\00\00\01\ff\ff
+MessageType(11, Put Reply, reply of Put)
+\00\00\00\0b\00\00\00\0ddoc:test:test@\00\00\00\00\00\00Q\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\01
+MessageType(82, Update)
+\00\00\00R\00\00\005\00\08doc:test:test\00\01testdoctype1\00\00\01\00\00\00\01\00\00\00\02\00\00\00\01\00\00\10\1b\01\00\00\00\11@\00\00\00\00\00\00Q\00\00\00\00\00\00\00\0e\00\00\00\00\00\00\00
+\00\00\00\00\00\00\00\00\01\ff\ff
+MessageType(83, Update Reply, reply of Update)
+\00\00\00S\00\00\00\0ddoc:test:test@\00\00\00\00\00\00Q\00\00\00\00\00\00\00\0e\00\00\00\00\00\00\00\08\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\01
+MessageType(4, Get)
+\00\00\00\04\00\00\00\0ddoc:test:test@\00\00\00\00\00\00Q\00\00\00\00\00\00\00{\01\00\00\00\00\00\00\00\00\01\ff\ff
+MessageType(5, Get Reply, reply of Get)
+\00\00\00\05\00\00\00j\00\08\00\00\00ddoc:test:test\00\05testdoctype1\00\00\01\00\00\00=\00\01\05\00= ;This is the contents of the test document.
+It ain't much.
+\00\00\00\00\00\00\00\00d\00\00\00\0ddoc:test:test\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\01
+MessageType(12, Remove)
+\00\00\00\0c\00\00\00\0ddoc:test:test@\00\00\00\00\00\00Q\00\00\00\00\00\00\00\9f\00\00\00\00\00\00\00\00\01\ff\ff
+MessageType(13, Remove Reply, reply of Remove)
+\00\00\00\0d\00\00\00\0ddoc:test:test@\00\00\00\00\00\00Q\00\00\00\00\00\00\000\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\01
+MessageType(14, Revert)
+\00\00\00\0e@\00\00\00\00\00\00Q\00\00\00\01\00\00\00\00\00\00\00;\00\00\00\00\00\00\00\00\01\ff\ff
+MessageType(15, Revert Reply, reply of Revert)
+\00\00\00\0f@\00\00\00\00\00\00Q\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\01
+MessageType(54, Request bucket info)
+\00\00\006\00\00\00\00\00\03\00\00\00\14distributor:3 .1.s:d\00\00\00\00\00\00\00\00\01\ff\ff
+MessageType(55, Request bucket info reply, reply of Request bucket info)
+\00\00\007\00\00\00\01\00\00\00\00\00\00\00\04\00\00\00+\00\00\00\18\00\00\00{\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\01
+MessageType(56, Notify bucket change)
+\00\00\008P\00\00\00\00\00\03\e8\00\00\00\02\00\00\00\03\00\00\00\04\00\00\00\00\00\00\00\00\01\ff\ff
+MessageType(57, Notify bucket change reply, reply of Notify bucket change)
+\00\00\009\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\01
+MessageType(26, Create bucket)
+\00\00\00\1a\00\00\00\00\00\00\02o\00\00\00\00\00\00\00\00\01\ff\ff
+MessageType(27, Create bucket reply, reply of Create bucket)
+\00\00\00\1b\00\00\00\00\00\00\02o\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\01
+MessageType(34, Delete bucket)
+\00\00\00"\00\00\00\00\00\00\02o\00\00\00\00\00\00\00\00\01\ff\ff
+MessageType(35, Delete bucket reply, reply of Delete bucket)
+\00\00\00#\00\00\00\00\00\00\02o\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\01
+MessageType(32, Merge bucket)
+\00\00\00 \00\00\00\00\00\00\02o\00\03\00\04\00\00\0d\01\00\1a\01\00\00\00\00\00\00\04\d2\00\00\00\00\00\00\00\00\01\ff\ff
+MessageType(33, Merge bucket reply, reply of Merge bucket)
+\00\00\00!\00\00\00\00\00\00\02o\00\03\00\04\00\00\0d\01\00\1a\01\00\00\00\00\00\00\04\d2\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\04\00\00\00
+\00\00\00d
+MessageType(50, GetBucketDiff)
+\00\00\002\00\00\00\00\00\00\02o\00\02\00\04\00\00\0d\00\00\00\00\00\00\00\04 \00\00\00\01\00\00\00\00\00\01\e2@\00\0c1234567890ab\00\00\00d\00\01\00\00\00\01\00\03\00\00\00\00\00\00\00\00\01\ff\ff
+MessageType(51, GetBucketDiff reply, reply of GetBucketDiff)
+\00\00\003\00\00\00\00\00\00\02o\00\02\00\04\00\00\0d\00\00\00\00\00\00\00\04 \00\00\00\01\00\00\00\00\00\01\e2@\00\0c1234567890ab\00\00\00d\00\01\00\00\00\01\00\03\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\01
+MessageType(52, ApplyBucketDiff)
+\00\00\004@\00\00\00\00\00\02o\00\02\00\04\00\00\0d\00\00\00\04\d2\00\00\00\01\00\00\00\00\00\00\00\00\00\0c\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\01\ff\ff
+MessageType(53, ApplyBucketDiff reply, reply of ApplyBucketDiff)
+\00\00\005@\00\00\00\00\00\02o\00\02\00\04\00\00\0d\00\00\00\04\d2\00\00\00\01\00\00\00\00\00\00\00\00\00\0c\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\01
+MessageType(66, SplitBucket)
+\00\00\00B@\00\00\00\00\00\00\00\14(\00\00\03\e8\00\00\00\05\00\00\00\00\00\00\00\00\01\ff\ff
+MessageType(67, SplitBucket reply, reply of SplitBucket)
+\00\00\00C@\00\00\00\00\00\00\00\00\00\00\02D\00\00\00\00\00\00\00\00\00\00d\00\00\03\e8\00\00'\10D\00\00\00\00\00\00\01\00\00\00e\00\00\03\e9\00\00'\11\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\01
+MessageType(18, Visitor Create)
+\00\00\00\12\00\00\00\07library\00\00\00\02id\00\00\00\0ddoc selection\00\00\00\01\00\00\00\0bcontroldest\00\00\00\08datadest\00\00\00\02\00\00\00\00\00\00\00{\00\00\00\00\00\00\01\c8\00\00\00\02@\00\00\00\00\00\00\01@\00\00\00\00\00\00\02\00\01\01\00\00\00d\00\00\00\03\00\00\00\0dinto darkness\00\00\00\09bind them\00\00\00\08one ring\00\00\00\10to rule them all\00\00\00\0bone ring to\00\00\00\0dfind them and\00\00\00\00\00\00\00\00\01\ff\ff
+MessageType(19, Visitor Create Reply, reply of Visitor Create)
+\00\00\00\13\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\01
+It ain't much.
+\00\00P\00\00\f1\f1\f1\f1\f1\00\00\00\00\00\00\00\00\01\ff\ff
diff --git a/storage/src/tests/storageapi/mbusprot/storageprotocoltest.cpp b/storage/src/tests/storageapi/mbusprot/storageprotocoltest.cpp
new file mode 100644
index 00000000000..c130e433285
--- /dev/null
+++ b/storage/src/tests/storageapi/mbusprot/storageprotocoltest.cpp
@@ -0,0 +1,855 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/storageapi/message/persistence.h>
+#include <vespa/storageapi/message/bucket.h>
+#include <vespa/storageapi/message/bucketsplitting.h>
+#include <vespa/storageapi/message/internal.h>
+#include <vespa/storageapi/message/removelocation.h>
+#include <vespa/storageapi/message/stat.h>
+#include <vespa/storageapi/mbusprot/storageprotocol.h>
+#include <vespa/storageapi/mbusprot/storagecommand.h>
+#include <vespa/storageapi/mbusprot/storagereply.h>
+#include <vespa/storageapi/message/visitor.h>
+#include <vespa/vdslib/state/clusterstate.h>
+#include <vespa/document/base/testdocman.h>
+#include <vespa/document/update/documentupdate.h>
+#include <vespa/document/update/assignvalueupdate.h>
+#include <vespa/document/update/fieldpathupdates.h>
+#include <vespa/document/fieldvalue/intfieldvalue.h>
+#include <vespa/document/test/make_document_bucket.h>
+#include <vespa/document/test/make_bucket_space.h>
+#include <vespa/vespalib/util/growablebytebuffer.h>
+#include <vespa/vespalib/util/size_literals.h>
+
+#include <sstream>
+
+#include <vespa/vespalib/gtest/gtest.h>
+
+using namespace ::testing;
+
+using std::shared_ptr;
+using document::BucketSpace;
+using document::ByteBuffer;
+using document::Document;
+using document::DocumentId;
+using document::DocumentType;
+using document::DocumentTypeRepo;
+using document::FieldUpdate;
+using document::FieldPathUpdate;
+using document::AssignValueUpdate;
+using document::IntFieldValue;
+using document::RemoveFieldPathUpdate;
+using document::test::makeDocumentBucket;
+using document::test::makeBucketSpace;
+using storage::lib::ClusterState;
+using vespalib::string;
+
+namespace vespalib {
+
+// Needed for GTest to properly understand how to print Version values.
+// If not present, it will print the byte values of the presumed memory area
+// (which will be overrun for some reason, causing Valgrind to scream at us).
+void PrintTo(const vespalib::Version& v, std::ostream* os) {
+ *os << v.toString();
+}
+
+}
+
+namespace storage::api {
+
+struct StorageProtocolTest : TestWithParam<vespalib::Version> {
+ document::TestDocMan _docMan;
+ document::Document::SP _testDoc;
+ document::DocumentId _testDocId;
+ document::BucketId _bucket_id;
+ document::Bucket _bucket;
+ document::BucketId _dummy_remap_bucket{17, 12345};
+ BucketInfo _dummy_bucket_info{1,2,3,4,5, true, false, 48};
+ mbusprot::StorageProtocol _protocol;
+ static auto constexpr CONDITION_STRING = "There's just one condition";
+
+ StorageProtocolTest()
+ : _docMan(),
+ _testDoc(_docMan.createDocument()),
+ _testDocId(_testDoc->getId()),
+ _bucket_id(16, 0x51),
+ _bucket(makeDocumentBucket(_bucket_id)),
+ _protocol(_docMan.getTypeRepoSP())
+ {
+ }
+ ~StorageProtocolTest();
+
+ void set_dummy_bucket_info_reply_fields(BucketInfoReply& reply) {
+ reply.setBucketInfo(_dummy_bucket_info);
+ reply.remapBucketId(_dummy_remap_bucket);
+ }
+
+ void assert_bucket_info_reply_fields_propagated(const BucketInfoReply& reply) {
+ EXPECT_EQ(_dummy_bucket_info, reply.getBucketInfo());
+ EXPECT_TRUE(reply.hasBeenRemapped());
+ EXPECT_EQ(_dummy_remap_bucket, reply.getBucketId());
+ EXPECT_EQ(_bucket_id, reply.getOriginalBucketId());
+ }
+
+ template<typename Command>
+ std::shared_ptr<Command> copyCommand(const std::shared_ptr<Command>&);
+ template<typename Reply>
+ std::shared_ptr<Reply> copyReply(const std::shared_ptr<Reply>&);
+};
+
+StorageProtocolTest::~StorageProtocolTest() = default;
+
+namespace {
+
+std::string version_as_gtest_string(TestParamInfo<vespalib::Version> info) {
+ std::ostringstream ss;
+ auto& p = info.param;
+ // Dots are not allowed in test names, so convert to underscores.
+ ss << p.getMajor() << '_' << p.getMinor() << '_' << p.getMicro();
+ return ss.str();
+}
+
+}
+
+VESPA_GTEST_INSTANTIATE_TEST_SUITE_P(MultiVersionTest, StorageProtocolTest,
+ Values(vespalib::Version(6, 240, 0),
+ vespalib::Version(7, 41, 19)),
+ version_as_gtest_string);
+
+namespace {
+ mbus::Message::UP lastCommand;
+ mbus::Reply::UP lastReply;
+}
+
+TEST_F(StorageProtocolTest, testAddress50) {
+ vespalib::string cluster("foo");
+ StorageMessageAddress address(&cluster, lib::NodeType::STORAGE, 3);
+ EXPECT_EQ(vespalib::string("storage/cluster.foo/storage/3/default"),
+ address.to_mbus_route().toString());
+}
+
+template<typename Command> std::shared_ptr<Command>
+StorageProtocolTest::copyCommand(const std::shared_ptr<Command>& m)
+{
+ auto mbusMessage = std::make_unique<mbusprot::StorageCommand>(m);
+ auto version = GetParam();
+ mbus::Blob blob = _protocol.encode(version, *mbusMessage);
+ mbus::Routable::UP copy(_protocol.decode(version, blob));
+ assert(copy.get());
+
+ auto* copy2 = dynamic_cast<mbusprot::StorageCommand*>(copy.get());
+ assert(copy2 != nullptr);
+
+ StorageCommand::SP internalMessage(copy2->getCommand());
+ lastCommand = std::move(mbusMessage);
+
+ return std::dynamic_pointer_cast<Command>(internalMessage);
+}
+
+template<typename Reply> std::shared_ptr<Reply>
+StorageProtocolTest::copyReply(const std::shared_ptr<Reply>& m)
+{
+ auto mbusMessage = std::make_unique<mbusprot::StorageReply>(m);
+ auto version = GetParam();
+ mbus::Blob blob = _protocol.encode(version, *mbusMessage);
+ mbus::Routable::UP copy(_protocol.decode(version, blob));
+ assert(copy.get());
+
+ auto* copy2 = dynamic_cast<mbusprot::StorageReply*>(copy.get());
+ assert(copy2 != nullptr);
+
+ copy2->setMessage(std::move(lastCommand));
+ auto internalMessage = copy2->getReply();
+ lastReply = std::move(mbusMessage);
+ lastCommand = copy2->getMessage();
+ return std::dynamic_pointer_cast<Reply>(internalMessage);
+}
+
+TEST_P(StorageProtocolTest, put) {
+ auto cmd = std::make_shared<PutCommand>(_bucket, _testDoc, 14);
+ cmd->setUpdateTimestamp(Timestamp(13));
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(_bucket, cmd2->getBucket());
+ EXPECT_EQ(*_testDoc, *cmd2->getDocument());
+ EXPECT_EQ(Timestamp(14), cmd2->getTimestamp());
+ EXPECT_EQ(Timestamp(13), cmd2->getUpdateTimestamp());
+
+ auto reply = std::make_shared<PutReply>(*cmd2);
+ ASSERT_TRUE(reply->hasDocument());
+ EXPECT_EQ(*_testDoc, *reply->getDocument());
+ set_dummy_bucket_info_reply_fields(*reply);
+ auto reply2 = copyReply(reply);
+ ASSERT_TRUE(reply2->hasDocument());
+ EXPECT_EQ(*_testDoc, *reply->getDocument());
+ EXPECT_EQ(_testDoc->getId(), reply2->getDocumentId());
+ EXPECT_EQ(Timestamp(14), reply2->getTimestamp());
+ EXPECT_NO_FATAL_FAILURE(assert_bucket_info_reply_fields_propagated(*reply2));
+}
+
+TEST_P(StorageProtocolTest, response_without_remapped_bucket_preserves_original_bucket) {
+ auto cmd = std::make_shared<PutCommand>(_bucket, _testDoc, 14);
+ auto cmd2 = copyCommand(cmd);
+ auto reply = std::make_shared<PutReply>(*cmd2);
+ auto reply2 = copyReply(reply);
+
+ EXPECT_FALSE(reply2->hasBeenRemapped());
+ EXPECT_EQ(_bucket_id, reply2->getBucketId());
+ EXPECT_EQ(document::BucketId(), reply2->getOriginalBucketId());
+}
+
+TEST_P(StorageProtocolTest, invalid_bucket_info_is_propagated) {
+ auto cmd = std::make_shared<PutCommand>(_bucket, _testDoc, 14);
+ auto cmd2 = copyCommand(cmd);
+ auto reply = std::make_shared<PutReply>(*cmd2);
+ BucketInfo invalid_info;
+ EXPECT_FALSE(invalid_info.valid());
+ reply->setBucketInfo(invalid_info);
+ auto reply2 = copyReply(reply);
+
+ EXPECT_EQ(invalid_info, reply2->getBucketInfo());
+ EXPECT_FALSE(reply2->getBucketInfo().valid());
+}
+
+TEST_P(StorageProtocolTest, all_zero_bucket_info_is_propagated) {
+ auto cmd = std::make_shared<PutCommand>(_bucket, _testDoc, 14);
+ auto cmd2 = copyCommand(cmd);
+ auto reply = std::make_shared<PutReply>(*cmd2);
+ BucketInfo zero_info(0, 0, 0, 0, 0, false, false, 0);
+ reply->setBucketInfo(zero_info);
+ auto reply2 = copyReply(reply);
+
+ EXPECT_EQ(zero_info, reply2->getBucketInfo());
+}
+
+TEST_P(StorageProtocolTest, request_metadata_is_propagated) {
+ auto cmd = std::make_shared<PutCommand>(_bucket, _testDoc, 14);
+ cmd->forceMsgId(12345);
+ cmd->setPriority(50);
+ cmd->setSourceIndex(321);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(12345, cmd2->getMsgId());
+ EXPECT_EQ(50, cmd2->getPriority());
+ EXPECT_EQ(321, cmd2->getSourceIndex());
+}
+
+TEST_P(StorageProtocolTest, response_metadata_is_propagated) {
+ auto cmd = std::make_shared<PutCommand>(_bucket, _testDoc, 14);
+ auto cmd2 = copyCommand(cmd);
+ auto reply = std::make_shared<PutReply>(*cmd2);
+ reply->forceMsgId(1234);
+ reply->setPriority(101);
+ ReturnCode result(ReturnCode::TEST_AND_SET_CONDITION_FAILED, "foo is not bar");
+ reply->setResult(result);
+
+ auto reply2 = copyReply(reply);
+ EXPECT_EQ(result, reply2->getResult());
+ EXPECT_EQ(1234, reply->getMsgId());
+ EXPECT_EQ(101, reply->getPriority());
+}
+
+TEST_P(StorageProtocolTest, update) {
+ auto update = std::make_shared<document::DocumentUpdate>(_docMan.getTypeRepo(), *_testDoc->getDataType(), _testDoc->getId());
+ update->addUpdate(FieldUpdate(_testDoc->getField("headerval")).addUpdate(std::make_unique<AssignValueUpdate>(std::make_unique<IntFieldValue>(17))));
+
+ update->addFieldPathUpdate(std::make_unique<RemoveFieldPathUpdate>("headerval", "testdoctype1.headerval > 0"));
+
+ auto cmd = std::make_shared<UpdateCommand>(_bucket, update, 14);
+ EXPECT_EQ(Timestamp(0), cmd->getOldTimestamp());
+ cmd->setOldTimestamp(10);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(_bucket, cmd2->getBucket());
+ EXPECT_EQ(_testDocId, cmd2->getDocumentId());
+ EXPECT_EQ(Timestamp(14), cmd2->getTimestamp());
+ EXPECT_EQ(Timestamp(10), cmd2->getOldTimestamp());
+ EXPECT_EQ(*update, *cmd2->getUpdate());
+
+ auto reply = std::make_shared<UpdateReply>(*cmd2, 8);
+ set_dummy_bucket_info_reply_fields(*reply);
+ auto reply2 = copyReply(reply);
+ EXPECT_EQ(_testDocId, reply2->getDocumentId());
+ EXPECT_EQ(Timestamp(14), reply2->getTimestamp());
+ EXPECT_EQ(Timestamp(8), reply->getOldTimestamp());
+ EXPECT_NO_FATAL_FAILURE(assert_bucket_info_reply_fields_propagated(*reply2));
+}
+
+TEST_P(StorageProtocolTest, get) {
+ auto cmd = std::make_shared<GetCommand>(_bucket, _testDocId, "foo,bar,vekterli", 123);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(_bucket, cmd2->getBucket());
+ EXPECT_EQ(_testDocId, cmd2->getDocumentId());
+ EXPECT_EQ(Timestamp(123), cmd2->getBeforeTimestamp());
+ EXPECT_EQ(vespalib::string("foo,bar,vekterli"), cmd2->getFieldSet());
+
+ auto reply = std::make_shared<GetReply>(*cmd2, _testDoc, 100);
+ set_dummy_bucket_info_reply_fields(*reply);
+ auto reply2 = copyReply(reply);
+ ASSERT_TRUE(reply2.get() != nullptr);
+ ASSERT_TRUE(reply2->getDocument().get() != nullptr);
+ EXPECT_EQ(*_testDoc, *reply2->getDocument());
+ EXPECT_EQ(_testDoc->getId(), reply2->getDocumentId());
+ EXPECT_EQ(Timestamp(123), reply2->getBeforeTimestamp());
+ EXPECT_EQ(Timestamp(100), reply2->getLastModifiedTimestamp());
+ EXPECT_FALSE(reply2->is_tombstone());
+ EXPECT_NO_FATAL_FAILURE(assert_bucket_info_reply_fields_propagated(*reply2));
+}
+
+TEST_P(StorageProtocolTest, get_internal_read_consistency_is_strong_by_default) {
+ auto cmd = std::make_shared<GetCommand>(_bucket, _testDocId, "foo,bar,vekterli", 123);
+ EXPECT_EQ(cmd->internal_read_consistency(), InternalReadConsistency::Strong);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(cmd2->internal_read_consistency(), InternalReadConsistency::Strong);
+}
+
+TEST_P(StorageProtocolTest, can_set_internal_read_consistency_on_get_commands) {
+ // Only supported on protocol version 7+. Will default to Strong on older versions, which is what we want.
+ if (GetParam().getMajor() < 7) {
+ return;
+ }
+ auto cmd = std::make_shared<GetCommand>(_bucket, _testDocId, "foo,bar,vekterli", 123);
+ cmd->set_internal_read_consistency(InternalReadConsistency::Weak);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(cmd2->internal_read_consistency(), InternalReadConsistency::Weak);
+
+ cmd->set_internal_read_consistency(InternalReadConsistency::Strong);
+ cmd2 = copyCommand(cmd);
+ EXPECT_EQ(cmd2->internal_read_consistency(), InternalReadConsistency::Strong);
+}
+
+TEST_P(StorageProtocolTest, tombstones_propagated_for_gets) {
+ // Only supported on protocol version 7+.
+ if (GetParam().getMajor() < 7) {
+ return;
+ }
+ auto cmd = std::make_shared<GetCommand>(_bucket, _testDocId, "foo,bar", 123);
+ auto reply = std::make_shared<GetReply>(*cmd, std::shared_ptr<Document>(), 100, false, true);
+ set_dummy_bucket_info_reply_fields(*reply);
+ auto reply2 = copyReply(reply);
+
+ EXPECT_TRUE(reply2->getDocument().get() == nullptr);
+ EXPECT_EQ(_testDoc->getId(), reply2->getDocumentId());
+ EXPECT_EQ(Timestamp(123), reply2->getBeforeTimestamp());
+ EXPECT_EQ(Timestamp(100), reply2->getLastModifiedTimestamp()); // In this case, the tombstone timestamp.
+ EXPECT_TRUE(reply2->is_tombstone());
+}
+
+// TODO remove this once pre-protobuf serialization is removed
+TEST_P(StorageProtocolTest, old_serialization_format_treats_tombstone_get_replies_as_not_found) {
+ if (GetParam().getMajor() >= 7) {
+ return;
+ }
+ auto cmd = std::make_shared<GetCommand>(_bucket, _testDocId, "foo,bar", 123);
+ auto reply = std::make_shared<GetReply>(*cmd, std::shared_ptr<Document>(), 100, false, true);
+ set_dummy_bucket_info_reply_fields(*reply);
+ auto reply2 = copyReply(reply);
+
+ EXPECT_TRUE(reply2->getDocument().get() == nullptr);
+ EXPECT_EQ(_testDoc->getId(), reply2->getDocumentId());
+ EXPECT_EQ(Timestamp(123), reply2->getBeforeTimestamp());
+ EXPECT_EQ(Timestamp(0), reply2->getLastModifiedTimestamp());
+ EXPECT_FALSE(reply2->is_tombstone()); // Protocol version doesn't understand explicit tombstones.
+}
+
+TEST_P(StorageProtocolTest, remove) {
+ auto cmd = std::make_shared<RemoveCommand>(_bucket, _testDocId, 159);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(_bucket, cmd2->getBucket());
+ EXPECT_EQ(_testDocId, cmd2->getDocumentId());
+ EXPECT_EQ(Timestamp(159), cmd2->getTimestamp());
+
+ auto reply = std::make_shared<RemoveReply>(*cmd2, 48);
+ set_dummy_bucket_info_reply_fields(*reply);
+
+ auto reply2 = copyReply(reply);
+ EXPECT_EQ(_testDocId, reply2->getDocumentId());
+ EXPECT_EQ(Timestamp(159), reply2->getTimestamp());
+ EXPECT_EQ(Timestamp(48), reply2->getOldTimestamp());
+ EXPECT_NO_FATAL_FAILURE(assert_bucket_info_reply_fields_propagated(*reply2));
+}
+
+TEST_P(StorageProtocolTest, revert) {
+ std::vector<Timestamp> tokens;
+ tokens.push_back(59);
+ auto cmd = std::make_shared<RevertCommand>(_bucket, tokens);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(_bucket, cmd2->getBucket());
+ EXPECT_EQ(tokens, cmd2->getRevertTokens());
+
+ auto reply = std::make_shared<RevertReply>(*cmd2);
+ set_dummy_bucket_info_reply_fields(*reply);
+ auto reply2 = copyReply(reply);
+ EXPECT_NO_FATAL_FAILURE(assert_bucket_info_reply_fields_propagated(*reply2));
+}
+
+TEST_P(StorageProtocolTest, request_bucket_info) {
+ {
+ std::vector<document::BucketId> ids;
+ ids.push_back(document::BucketId(3));
+ ids.push_back(document::BucketId(7));
+ auto cmd = std::make_shared<RequestBucketInfoCommand>(makeBucketSpace(), ids);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(ids, cmd2->getBuckets());
+ EXPECT_FALSE(cmd2->hasSystemState());
+ }
+ {
+ ClusterState state("distributor:3 .1.s:d");
+ auto cmd = std::make_shared<RequestBucketInfoCommand>(makeBucketSpace(), 3, state, "14");
+ auto cmd2 = copyCommand(cmd);
+ ASSERT_TRUE(cmd2->hasSystemState());
+ EXPECT_EQ(uint16_t(3), cmd2->getDistributor());
+ EXPECT_EQ(state, cmd2->getSystemState());
+ EXPECT_EQ(size_t(0), cmd2->getBuckets().size());
+
+ auto reply = std::make_shared<RequestBucketInfoReply>(*cmd);
+ RequestBucketInfoReply::Entry e;
+ e._bucketId = document::BucketId(4);
+ const uint64_t lastMod = 0x1337cafe98765432ULL;
+ e._info = BucketInfo(43, 24, 123, 44, 124, false, true, lastMod);
+ reply->getBucketInfo().push_back(e);
+ auto reply2 = copyReply(reply);
+ EXPECT_EQ(size_t(1), reply2->getBucketInfo().size());
+ auto& entries(reply2->getBucketInfo());
+ EXPECT_EQ(e, entries[0]);
+ // "Last modified" not counted by operator== for some reason. Testing
+ // separately until we can figure out if this is by design or not.
+ EXPECT_EQ(lastMod, entries[0]._info.getLastModified());
+
+ if (GetParam().getMajor() >= 7) {
+ EXPECT_TRUE(reply2->supported_node_features().unordered_merge_chaining);
+ } else {
+ EXPECT_FALSE(reply2->supported_node_features().unordered_merge_chaining);
+ }
+ }
+}
+
+TEST_P(StorageProtocolTest, notify_bucket_change) {
+ auto cmd = std::make_shared<NotifyBucketChangeCommand>(_bucket, _dummy_bucket_info);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(_bucket, cmd2->getBucket());
+ EXPECT_EQ(_dummy_bucket_info, cmd2->getBucketInfo());
+
+ auto reply = std::make_shared<NotifyBucketChangeReply>(*cmd);
+ auto reply2 = copyReply(reply);
+}
+
+TEST_P(StorageProtocolTest, create_bucket_without_activation) {
+ auto cmd = std::make_shared<CreateBucketCommand>(_bucket);
+ EXPECT_FALSE(cmd->getActive());
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(_bucket, cmd2->getBucket());
+ EXPECT_FALSE(cmd2->getActive());
+
+ auto reply = std::make_shared<CreateBucketReply>(*cmd);
+ set_dummy_bucket_info_reply_fields(*reply);
+ auto reply2 = copyReply(reply);
+ EXPECT_NO_FATAL_FAILURE(assert_bucket_info_reply_fields_propagated(*reply2));
+}
+
+TEST_P(StorageProtocolTest, create_bucket_propagates_activation_flag) {
+ auto cmd = std::make_shared<CreateBucketCommand>(_bucket);
+ cmd->setActive(true);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_TRUE(cmd2->getActive());
+}
+
+TEST_P(StorageProtocolTest, delete_bucket) {
+ auto cmd = std::make_shared<DeleteBucketCommand>(_bucket);
+ cmd->setBucketInfo(_dummy_bucket_info);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(_bucket, cmd2->getBucket());
+ EXPECT_EQ(_dummy_bucket_info, cmd2->getBucketInfo());
+
+ auto reply = std::make_shared<DeleteBucketReply>(*cmd);
+ // Not set automatically by constructor
+ reply->setBucketInfo(cmd2->getBucketInfo());
+ auto reply2 = copyReply(reply);
+ EXPECT_EQ(_bucket_id, reply2->getBucketId());
+ EXPECT_EQ(_dummy_bucket_info, reply2->getBucketInfo());
+}
+
+TEST_P(StorageProtocolTest, merge_bucket) {
+ typedef api::MergeBucketCommand::Node Node;
+ std::vector<Node> nodes;
+ nodes.push_back(Node(4, false));
+ nodes.push_back(Node(13, true));
+ nodes.push_back(Node(26, true));
+
+ std::vector<uint16_t> chain;
+ // Not a valid chain wrt. the nodes, but just want to have unique values
+ chain.push_back(7);
+ chain.push_back(14);
+
+ auto cmd = std::make_shared<MergeBucketCommand>(_bucket, nodes, Timestamp(1234), 567, chain);
+ cmd->set_use_unordered_forwarding(true);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(_bucket, cmd2->getBucket());
+ EXPECT_EQ(nodes, cmd2->getNodes());
+ EXPECT_EQ(Timestamp(1234), cmd2->getMaxTimestamp());
+ EXPECT_EQ(uint32_t(567), cmd2->getClusterStateVersion());
+ EXPECT_EQ(chain, cmd2->getChain());
+ if (GetParam().getMajor() >= 7) {
+ EXPECT_EQ(cmd2->use_unordered_forwarding(), cmd->use_unordered_forwarding());
+ } else {
+ EXPECT_FALSE(cmd2->use_unordered_forwarding());
+ }
+
+ auto reply = std::make_shared<MergeBucketReply>(*cmd);
+ auto reply2 = copyReply(reply);
+ EXPECT_EQ(_bucket_id, reply2->getBucketId());
+ EXPECT_EQ(nodes, reply2->getNodes());
+ EXPECT_EQ(Timestamp(1234), reply2->getMaxTimestamp());
+ EXPECT_EQ(uint32_t(567), reply2->getClusterStateVersion());
+ EXPECT_EQ(chain, reply2->getChain());
+}
+
+TEST_P(StorageProtocolTest, split_bucket) {
+ auto cmd = std::make_shared<SplitBucketCommand>(_bucket);
+ EXPECT_EQ(0u, cmd->getMinSplitBits());
+ EXPECT_EQ(58u, cmd->getMaxSplitBits());
+ EXPECT_EQ(std::numeric_limits<uint32_t>().max(), cmd->getMinByteSize());
+ EXPECT_EQ(std::numeric_limits<uint32_t>().max(), cmd->getMinDocCount());
+ cmd->setMinByteSize(1000);
+ cmd->setMinDocCount(5);
+ cmd->setMaxSplitBits(40);
+ cmd->setMinSplitBits(20);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(_bucket, cmd2->getBucket());
+ EXPECT_EQ(20u, cmd2->getMinSplitBits());
+ EXPECT_EQ(40u, cmd2->getMaxSplitBits());
+ EXPECT_EQ(1000u, cmd2->getMinByteSize());
+ EXPECT_EQ(5u, cmd2->getMinDocCount());
+
+ auto reply = std::make_shared<SplitBucketReply>(*cmd2);
+ reply->getSplitInfo().emplace_back(document::BucketId(17, 0), BucketInfo(100, 1000, 10000, true, true));
+ reply->getSplitInfo().emplace_back(document::BucketId(17, 1), BucketInfo(101, 1001, 10001, true, true));
+ auto reply2 = copyReply(reply);
+
+ EXPECT_EQ(_bucket, reply2->getBucket());
+ EXPECT_EQ(size_t(2), reply2->getSplitInfo().size());
+ EXPECT_EQ(document::BucketId(17, 0), reply2->getSplitInfo()[0].first);
+ EXPECT_EQ(document::BucketId(17, 1), reply2->getSplitInfo()[1].first);
+ EXPECT_EQ(BucketInfo(100, 1000, 10000, true, true), reply2->getSplitInfo()[0].second);
+ EXPECT_EQ(BucketInfo(101, 1001, 10001, true, true), reply2->getSplitInfo()[1].second);
+}
+
+TEST_P(StorageProtocolTest, join_buckets) {
+ std::vector<document::BucketId> sources;
+ sources.push_back(document::BucketId(17, 0));
+ sources.push_back(document::BucketId(17, 1));
+ auto cmd = std::make_shared<JoinBucketsCommand>(_bucket);
+ cmd->getSourceBuckets() = sources;
+ cmd->setMinJoinBits(3);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(_bucket, cmd2->getBucket());
+
+ auto reply = std::make_shared<JoinBucketsReply>(*cmd2);
+ reply->setBucketInfo(BucketInfo(3,4,5));
+ auto reply2 = copyReply(reply);
+
+ EXPECT_EQ(sources, reply2->getSourceBuckets());
+ EXPECT_EQ(3, cmd2->getMinJoinBits());
+ EXPECT_EQ(BucketInfo(3,4,5), reply2->getBucketInfo());
+ EXPECT_EQ(_bucket, reply2->getBucket());
+}
+
+TEST_P(StorageProtocolTest, destroy_visitor) {
+ auto cmd = std::make_shared<DestroyVisitorCommand>("instance");
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ("instance", cmd2->getInstanceId());
+
+ auto reply = std::make_shared<DestroyVisitorReply>(*cmd2);
+ auto reply2 = copyReply(reply);
+}
+
+TEST_P(StorageProtocolTest, remove_location) {
+ auto cmd = std::make_shared<RemoveLocationCommand>("id.group == \"mygroup\"", _bucket);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ("id.group == \"mygroup\"", cmd2->getDocumentSelection());
+ EXPECT_EQ(_bucket, cmd2->getBucket());
+
+ uint32_t n_docs_removed = 12345;
+ auto reply = std::make_shared<RemoveLocationReply>(*cmd2, n_docs_removed);
+ auto reply2 = copyReply(reply);
+ if (GetParam().getMajor() == 7) {
+ // Statistics are only available for protobuf-enabled version.
+ EXPECT_EQ(n_docs_removed, reply2->documents_removed());
+ } else {
+ EXPECT_EQ(0, reply2->documents_removed());
+ }
+}
+
+TEST_P(StorageProtocolTest, stat_bucket) {
+ if (GetParam().getMajor() < 7) {
+ return; // Only available for protobuf-backed protocol version.
+ }
+ auto cmd = std::make_shared<StatBucketCommand>(_bucket, "id.group == 'mygroup'");
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ("id.group == 'mygroup'", cmd2->getDocumentSelection());
+ EXPECT_EQ(_bucket, cmd2->getBucket());
+
+ auto reply = std::make_shared<StatBucketReply>(*cmd2, "neat bucket info goes here");
+ reply->remapBucketId(_dummy_remap_bucket);
+ auto reply2 = copyReply(reply);
+ EXPECT_EQ(reply2->getResults(), "neat bucket info goes here");
+ EXPECT_TRUE(reply2->hasBeenRemapped());
+ EXPECT_EQ(_dummy_remap_bucket, reply2->getBucketId());
+ EXPECT_EQ(_bucket_id, reply2->getOriginalBucketId());
+}
+
+TEST_P(StorageProtocolTest, create_visitor) {
+ std::vector<document::BucketId> buckets;
+ buckets.push_back(document::BucketId(16, 1));
+ buckets.push_back(document::BucketId(16, 2));
+
+ auto cmd = std::make_shared<CreateVisitorCommand>(makeBucketSpace(), "library", "id", "doc selection");
+ cmd->setControlDestination("controldest");
+ cmd->setDataDestination("datadest");
+ cmd->setVisitorCmdId(1);
+ cmd->getParameters().set("one ring", "to rule them all");
+ cmd->getParameters().set("one ring to", "find them and");
+ cmd->getParameters().set("into darkness", "bind them");
+ cmd->setMaximumPendingReplyCount(2);
+ cmd->setFromTime(123);
+ cmd->setToTime(456);
+ cmd->getBuckets() = buckets;
+ cmd->setFieldSet("foo,bar,vekterli");
+ cmd->setVisitInconsistentBuckets();
+ cmd->setQueueTimeout(100ms);
+ cmd->setPriority(149);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ("library", cmd2->getLibraryName());
+ EXPECT_EQ("id", cmd2->getInstanceId());
+ EXPECT_EQ("doc selection", cmd2->getDocumentSelection());
+ EXPECT_EQ("controldest", cmd2->getControlDestination());
+ EXPECT_EQ("datadest", cmd2->getDataDestination());
+ EXPECT_EQ(api::Timestamp(123), cmd2->getFromTime());
+ EXPECT_EQ(api::Timestamp(456), cmd2->getToTime());
+ EXPECT_EQ(2u, cmd2->getMaximumPendingReplyCount());
+ EXPECT_EQ(buckets, cmd2->getBuckets());
+ EXPECT_EQ("foo,bar,vekterli", cmd2->getFieldSet());
+ EXPECT_TRUE(cmd2->visitInconsistentBuckets());
+ EXPECT_EQ(149, cmd2->getPriority());
+
+ auto reply = std::make_shared<CreateVisitorReply>(*cmd2);
+ auto reply2 = copyReply(reply);
+}
+
+TEST_P(StorageProtocolTest, get_bucket_diff) {
+ std::vector<api::MergeBucketCommand::Node> nodes;
+ nodes.push_back(4);
+ nodes.push_back(13);
+ std::vector<GetBucketDiffCommand::Entry> entries;
+ entries.push_back(GetBucketDiffCommand::Entry());
+ entries.back()._gid = document::GlobalId("1234567890abcdef");
+ entries.back()._timestamp = 123456;
+ entries.back()._headerSize = 100;
+ entries.back()._bodySize = 64_Ki;
+ entries.back()._flags = 1;
+ entries.back()._hasMask = 3;
+
+ EXPECT_EQ("Entry(timestamp: 123456, gid(0x313233343536373839306162), hasMask: 0x3,\n"
+ " header size: 100, body size: 65536, flags 0x1)",
+ entries.back().toString(true));
+
+ auto cmd = std::make_shared<GetBucketDiffCommand>(_bucket, nodes, 1056);
+ cmd->getDiff() = entries;
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(_bucket, cmd2->getBucket());
+
+ auto reply = std::make_shared<GetBucketDiffReply>(*cmd2);
+ EXPECT_EQ(entries, reply->getDiff());
+ auto reply2 = copyReply(reply);
+
+ EXPECT_EQ(nodes, reply2->getNodes());
+ EXPECT_EQ(entries, reply2->getDiff());
+ EXPECT_EQ(Timestamp(1056), reply2->getMaxTimestamp());
+}
+
+namespace {
+
+ApplyBucketDiffCommand::Entry dummy_apply_entry() {
+ ApplyBucketDiffCommand::Entry e;
+ e._docName = "my cool id";
+ vespalib::string header_data = "fancy header";
+ e._headerBlob.resize(header_data.size());
+ memcpy(&e._headerBlob[0], header_data.data(), header_data.size());
+
+ vespalib::string body_data = "fancier body!";
+ e._bodyBlob.resize(body_data.size());
+ memcpy(&e._bodyBlob[0], body_data.data(), body_data.size());
+
+ GetBucketDiffCommand::Entry meta;
+ meta._timestamp = 567890;
+ meta._hasMask = 0x3;
+ meta._flags = 0x1;
+ meta._headerSize = 12345;
+ meta._headerSize = header_data.size();
+ meta._bodySize = body_data.size();
+
+ e._entry = meta;
+ return e;
+}
+
+}
+
+TEST_P(StorageProtocolTest, apply_bucket_diff) {
+ std::vector<api::MergeBucketCommand::Node> nodes;
+ nodes.push_back(4);
+ nodes.push_back(13);
+ std::vector<ApplyBucketDiffCommand::Entry> entries = {dummy_apply_entry()};
+
+ auto cmd = std::make_shared<ApplyBucketDiffCommand>(_bucket, nodes);
+ cmd->getDiff() = entries;
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(_bucket, cmd2->getBucket());
+
+ auto reply = std::make_shared<ApplyBucketDiffReply>(*cmd2);
+ auto reply2 = copyReply(reply);
+
+ EXPECT_EQ(nodes, reply2->getNodes());
+ EXPECT_EQ(entries, reply2->getDiff());
+}
+
+namespace {
+ struct MyCommand : public api::InternalCommand {
+ MyCommand() : InternalCommand(101) {}
+
+ api::StorageReply::UP makeReply() override;
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override {
+ out << "MyCommand()";
+ if (verbose) {
+ out << " : ";
+ InternalCommand::print(out, verbose, indent);
+ }
+ }
+ };
+
+ struct MyReply : public api::InternalReply {
+ MyReply(const MyCommand& cmd) : InternalReply(102, cmd) {}
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override {
+ out << "MyReply()";
+ if (verbose) {
+ out << " : ";
+ InternalReply::print(out, verbose, indent);
+ }
+ }
+ };
+
+ api::StorageReply::UP MyCommand::makeReply() {
+ return std::make_unique<MyReply>(*this);
+ }
+}
+
+TEST_P(StorageProtocolTest, internal_message) {
+ MyCommand cmd;
+ MyReply reply(cmd);
+ // TODO what's this even intended to test?
+}
+
+TEST_P(StorageProtocolTest, set_bucket_state_with_inactive_state) {
+ auto cmd = std::make_shared<SetBucketStateCommand>(_bucket, SetBucketStateCommand::INACTIVE);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(_bucket, cmd2->getBucket());
+
+ auto reply = std::make_shared<SetBucketStateReply>(*cmd2);
+ auto reply2 = copyReply(reply);
+
+ EXPECT_EQ(SetBucketStateCommand::INACTIVE, cmd2->getState());
+ EXPECT_EQ(_bucket, reply2->getBucket());
+}
+
+TEST_P(StorageProtocolTest, set_bucket_state_with_active_state) {
+ auto cmd = std::make_shared<SetBucketStateCommand>(_bucket, SetBucketStateCommand::ACTIVE);
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(SetBucketStateCommand::ACTIVE, cmd2->getState());
+}
+
+TEST_P(StorageProtocolTest, put_command_with_condition) {
+ auto cmd = std::make_shared<PutCommand>(_bucket, _testDoc, 14);
+ cmd->setCondition(TestAndSetCondition(CONDITION_STRING));
+
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(cmd->getCondition().getSelection(), cmd2->getCondition().getSelection());
+}
+
+TEST_P(StorageProtocolTest, update_command_with_condition) {
+ auto update = std::make_shared<document::DocumentUpdate>(
+ _docMan.getTypeRepo(), *_testDoc->getDataType(), _testDoc->getId());
+ auto cmd = std::make_shared<UpdateCommand>(_bucket, update, 14);
+ cmd->setCondition(TestAndSetCondition(CONDITION_STRING));
+
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(cmd->getCondition().getSelection(), cmd2->getCondition().getSelection());
+}
+
+TEST_P(StorageProtocolTest, remove_command_with_condition) {
+ auto cmd = std::make_shared<RemoveCommand>(_bucket, _testDocId, 159);
+ cmd->setCondition(TestAndSetCondition(CONDITION_STRING));
+
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(cmd->getCondition().getSelection(), cmd2->getCondition().getSelection());
+}
+
+TEST_P(StorageProtocolTest, put_command_with_bucket_space) {
+ document::Bucket bucket(document::BucketSpace(5), _bucket_id);
+ auto cmd = std::make_shared<PutCommand>(bucket, _testDoc, 14);
+
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(bucket, cmd2->getBucket());
+}
+
+TEST_P(StorageProtocolTest, create_visitor_with_bucket_space) {
+ document::BucketSpace bucketSpace(5);
+ auto cmd = std::make_shared<CreateVisitorCommand>(bucketSpace, "library", "id", "doc selection");
+
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(bucketSpace, cmd2->getBucketSpace());
+}
+
+TEST_P(StorageProtocolTest, request_bucket_info_with_bucket_space) {
+ document::BucketSpace bucketSpace(5);
+ std::vector<document::BucketId> ids = {document::BucketId(3)};
+ auto cmd = std::make_shared<RequestBucketInfoCommand>(bucketSpace, ids);
+
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(bucketSpace, cmd2->getBucketSpace());
+ EXPECT_EQ(ids, cmd2->getBuckets());
+}
+
+TEST_P(StorageProtocolTest, serialized_size_is_used_to_set_approx_size_of_storage_message) {
+ auto cmd = std::make_shared<PutCommand>(_bucket, _testDoc, 14);
+ EXPECT_EQ(50u, cmd->getApproxByteSize());
+
+ auto cmd2 = copyCommand(cmd);
+ auto version = GetParam();
+ if (version.getMajor() == 7) { // Protobuf-based encoding
+ EXPECT_EQ(158u, cmd2->getApproxByteSize());
+ } else { // Legacy encoding
+ EXPECT_EQ(181u, cmd2->getApproxByteSize());
+ }
+}
+
+TEST_P(StorageProtocolTest, track_memory_footprint_for_some_messages) {
+ EXPECT_EQ(72u, sizeof(StorageMessage));
+ EXPECT_EQ(88u, sizeof(StorageReply));
+ EXPECT_EQ(112u, sizeof(BucketReply));
+ EXPECT_EQ(8u, sizeof(document::BucketId));
+ EXPECT_EQ(16u, sizeof(document::Bucket));
+ EXPECT_EQ(32u, sizeof(BucketInfo));
+ EXPECT_EQ(144u, sizeof(BucketInfoReply));
+ EXPECT_EQ(288u, sizeof(PutReply));
+ EXPECT_EQ(272u, sizeof(UpdateReply));
+ EXPECT_EQ(264u, sizeof(RemoveReply));
+ EXPECT_EQ(352u, sizeof(GetReply));
+ EXPECT_EQ(88u, sizeof(StorageCommand));
+ EXPECT_EQ(112u, sizeof(BucketCommand));
+ EXPECT_EQ(112u, sizeof(BucketInfoCommand));
+ EXPECT_EQ(112u + sizeof(vespalib::string), sizeof(TestAndSetCommand));
+ EXPECT_EQ(144u + sizeof(vespalib::string), sizeof(PutCommand));
+ EXPECT_EQ(144u + sizeof(vespalib::string), sizeof(UpdateCommand));
+ EXPECT_EQ(224u + sizeof(vespalib::string), sizeof(RemoveCommand));
+ EXPECT_EQ(296u, sizeof(GetCommand));
+}
+
+} // storage::api
diff --git a/storage/src/tests/storageapi/messageapi/.gitignore b/storage/src/tests/storageapi/messageapi/.gitignore
new file mode 100644
index 00000000000..1d859456ef9
--- /dev/null
+++ b/storage/src/tests/storageapi/messageapi/.gitignore
@@ -0,0 +1,9 @@
+*.So
+*.core
+*.so
+.*.swp
+.config.log
+.depend
+Makefile
+test.vlog
+testrunner
diff --git a/storage/src/tests/storageapi/messageapi/CMakeLists.txt b/storage/src/tests/storageapi/messageapi/CMakeLists.txt
new file mode 100644
index 00000000000..4866aa63079
--- /dev/null
+++ b/storage/src/tests/storageapi/messageapi/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_library(storageapi_testmessageapi
+ SOURCES
+ storage_message_address_test.cpp
+ DEPENDS
+ storage
+ GTest::GTest
+)
diff --git a/storage/src/tests/storageapi/messageapi/storage_message_address_test.cpp b/storage/src/tests/storageapi/messageapi/storage_message_address_test.cpp
new file mode 100644
index 00000000000..ea59fefc924
--- /dev/null
+++ b/storage/src/tests/storageapi/messageapi/storage_message_address_test.cpp
@@ -0,0 +1,40 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/storageapi/messageapi/storagemessage.h>
+#include <vespa/vespalib/gtest/gtest.h>
+
+using namespace ::testing;
+
+namespace storage::api {
+
+namespace {
+
+size_t hash_of(const vespalib::string & cluster, const lib::NodeType& type, uint16_t index) {
+ return StorageMessageAddress(&cluster, type, index).internal_storage_hash();
+}
+
+}
+
+TEST(StorageMessageAddressTest, storage_hash_covers_all_expected_fields) {
+ EXPECT_EQ(hash_of("foo", lib::NodeType::STORAGE, 0),
+ hash_of("foo", lib::NodeType::STORAGE, 0));
+ EXPECT_EQ(hash_of("foo", lib::NodeType::DISTRIBUTOR, 0),
+ hash_of("foo", lib::NodeType::DISTRIBUTOR, 0));
+ EXPECT_EQ(hash_of("foo", lib::NodeType::STORAGE, 123),
+ hash_of("foo", lib::NodeType::STORAGE, 123));
+ EXPECT_EQ(hash_of("foo", lib::NodeType::STORAGE, 0),
+ hash_of("bar", lib::NodeType::STORAGE, 0));
+
+ // These tests are all true with extremely high probability, though they do
+ // depend on a hash function that may inherently cause collisions.
+ EXPECT_NE(hash_of("foo", lib::NodeType::STORAGE, 0),
+ hash_of("foo", lib::NodeType::DISTRIBUTOR, 0));
+ EXPECT_NE(hash_of("foo", lib::NodeType::STORAGE, 0),
+ hash_of("foo", lib::NodeType::STORAGE, 1));
+
+ EXPECT_EQ(16u, sizeof(StorageMessageAddress));
+ EXPECT_EQ(72u, sizeof(StorageMessage));
+ EXPECT_EQ(16u, sizeof(mbus::Trace));
+}
+
+} // storage::api
diff --git a/storage/src/vespa/storage/CMakeLists.txt b/storage/src/vespa/storage/CMakeLists.txt
index 22aad2a147d..3cb9358b56d 100644
--- a/storage/src/vespa/storage/CMakeLists.txt
+++ b/storage/src/vespa/storage/CMakeLists.txt
@@ -27,6 +27,12 @@ vespa_add_library(storage
$<TARGET_OBJECTS:storageframework_clockimpl>
$<TARGET_OBJECTS:storageframework_componentimpl>
$<TARGET_OBJECTS:storageframework_threadimpl>
+ $<TARGET_OBJECTS:storageapi_message>
+ $<TARGET_OBJECTS:storageapi_buckets>
+ $<TARGET_OBJECTS:storageapi_messageapi>
+ $<TARGET_OBJECTS:storageapi_mbusprot>
INSTALL lib64
DEPENDS
)
+
+vespa_add_target_package_dependency(storage Protobuf)
diff --git a/storage/src/vespa/storageapi/.gitignore b/storage/src/vespa/storageapi/.gitignore
new file mode 100644
index 00000000000..43485abf58c
--- /dev/null
+++ b/storage/src/vespa/storageapi/.gitignore
@@ -0,0 +1,5 @@
+.cvsignore
+.depend
+Makefile
+features.h
+/libstorageapi.so.5.1
diff --git a/storage/src/vespa/storageapi/app/.gitignore b/storage/src/vespa/storageapi/app/.gitignore
new file mode 100644
index 00000000000..fa917bb5ae5
--- /dev/null
+++ b/storage/src/vespa/storageapi/app/.gitignore
@@ -0,0 +1,14 @@
+*.lo
+.depend
+.depend.NEW
+.deps
+.libs
+Makefile
+dumpstatusfile
+getbucketid
+inspectfeedapistatus
+testdocstorefile
+testdocstorefile2
+testdocstoremem
+testresendctrl
+storageapi_getbucketid_app
diff --git a/storage/src/vespa/storageapi/app/CMakeLists.txt b/storage/src/vespa/storageapi/app/CMakeLists.txt
new file mode 100644
index 00000000000..a8c183b01de
--- /dev/null
+++ b/storage/src/vespa/storageapi/app/CMakeLists.txt
@@ -0,0 +1,7 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(storageapi_getbucketid_app
+ SOURCES
+ getbucketid.cpp
+ DEPENDS
+ storage
+)
diff --git a/storage/src/vespa/storageapi/app/getbucketid.cpp b/storage/src/vespa/storageapi/app/getbucketid.cpp
new file mode 100644
index 00000000000..21f7912d1a1
--- /dev/null
+++ b/storage/src/vespa/storageapi/app/getbucketid.cpp
@@ -0,0 +1,18 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/document/bucket/bucketidfactory.h>
+#include <vespa/document/base/documentid.h>
+#include <iostream>
+
+int main(int argc, char** argv)
+{
+ if (argc != 2) {
+ std::cerr << "Usage: getbucketid <documentid>\n";
+ return 1;
+ }
+ document::BucketIdFactory factory;
+ document::BucketId id = factory.getBucketId(document::DocumentId(argv[1]));
+
+ printf("%s has bucketid %s\n", argv[1], id.toString().c_str());
+}
+
+
diff --git a/storage/src/vespa/storageapi/buckets/.gitignore b/storage/src/vespa/storageapi/buckets/.gitignore
new file mode 100644
index 00000000000..7ad21cf1c5e
--- /dev/null
+++ b/storage/src/vespa/storageapi/buckets/.gitignore
@@ -0,0 +1,9 @@
+*.lo
+.*.swp
+.depend
+.depend.NEW
+.deps
+.libs
+Makefile
+config-stor-bucketidgen.cpp
+config-stor-bucketidgen.h
diff --git a/storage/src/vespa/storageapi/buckets/CMakeLists.txt b/storage/src/vespa/storageapi/buckets/CMakeLists.txt
new file mode 100644
index 00000000000..ee087f6f566
--- /dev/null
+++ b/storage/src/vespa/storageapi/buckets/CMakeLists.txt
@@ -0,0 +1,6 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_library(storageapi_buckets OBJECT
+ SOURCES
+ bucketinfo.cpp
+ DEPENDS
+)
diff --git a/storage/src/vespa/storageapi/buckets/bucketinfo.cpp b/storage/src/vespa/storageapi/buckets/bucketinfo.cpp
new file mode 100644
index 00000000000..ff2f40e736b
--- /dev/null
+++ b/storage/src/vespa/storageapi/buckets/bucketinfo.cpp
@@ -0,0 +1,132 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "bucketinfo.h"
+#include <vespa/vespalib/stllike/asciistream.h>
+#include <vespa/vespalib/util/xmlstream.h>
+#include <ostream>
+
+namespace storage::api {
+
+static_assert(sizeof(BucketInfo) == 32, "BucketInfo should be 32 bytes");
+
+BucketInfo::BucketInfo() noexcept
+ : _lastModified(0),
+ _checksum(0),
+ _docCount(0),
+ _totDocSize(1),
+ _metaCount(0),
+ _usedFileSize(1),
+ _ready(false),
+ _active(false)
+{}
+
+BucketInfo::BucketInfo(uint32_t checksum, uint32_t docCount, uint32_t totDocSize) noexcept
+ : _lastModified(0),
+ _checksum(checksum),
+ _docCount(docCount),
+ _totDocSize(totDocSize),
+ _metaCount(docCount),
+ _usedFileSize(totDocSize),
+ _ready(false),
+ _active(false)
+{}
+
+BucketInfo::BucketInfo(uint32_t checksum, uint32_t docCount,
+ uint32_t totDocSize, uint32_t metaCount,
+ uint32_t usedFileSize) noexcept
+ : _lastModified(0),
+ _checksum(checksum),
+ _docCount(docCount),
+ _totDocSize(totDocSize),
+ _metaCount(metaCount),
+ _usedFileSize(usedFileSize),
+ _ready(false),
+ _active(false)
+{}
+
+BucketInfo::BucketInfo(uint32_t checksum, uint32_t docCount,
+ uint32_t totDocSize, uint32_t metaCount,
+ uint32_t usedFileSize,
+ bool ready, bool active) noexcept
+ : _lastModified(0),
+ _checksum(checksum),
+ _docCount(docCount),
+ _totDocSize(totDocSize),
+ _metaCount(metaCount),
+ _usedFileSize(usedFileSize),
+ _ready(ready),
+ _active(active)
+{}
+
+BucketInfo::BucketInfo(uint32_t checksum, uint32_t docCount,
+ uint32_t totDocSize, uint32_t metaCount,
+ uint32_t usedFileSize,
+ bool ready, bool active, Timestamp lastModified) noexcept
+ : _lastModified(lastModified),
+ _checksum(checksum),
+ _docCount(docCount),
+ _totDocSize(totDocSize),
+ _metaCount(metaCount),
+ _usedFileSize(usedFileSize),
+ _ready(ready),
+ _active(active)
+{}
+
+bool
+BucketInfo::operator==(const BucketInfo& info) const noexcept
+{
+ return (_checksum == info._checksum &&
+ _docCount == info._docCount &&
+ _totDocSize == info._totDocSize &&
+ _metaCount == info._metaCount &&
+ _usedFileSize == info._usedFileSize &&
+ _ready == info._ready &&
+ _active == info._active);
+}
+
+// TODO: add ready/active to printing
+vespalib::string
+BucketInfo::toString() const
+{
+ vespalib::asciistream out;
+ out << "BucketInfo(";
+ if (valid()) {
+ out << "crc 0x" << vespalib::hex << _checksum << vespalib::dec
+ << ", docCount " << _docCount
+ << ", totDocSize " << _totDocSize;
+ if (_totDocSize != _usedFileSize) {
+ out << ", metaCount " << _metaCount
+ << ", usedFileSize " << _usedFileSize;
+ }
+ out << ", ready " << (_ready ? "true" : "false")
+ << ", active " << (_active ? "true" : "false");
+
+ if (_lastModified != 0) {
+ out << ", last modified " << _lastModified;
+ }
+ } else {
+ out << "invalid";
+ }
+ out << ")";
+ return out.str();
+}
+
+void
+BucketInfo::printXml(vespalib::XmlOutputStream& xos) const
+{
+ using namespace vespalib::xml;
+ xos << XmlAttribute("checksum", _checksum, XmlAttribute::HEX)
+ << XmlAttribute("docs", _docCount)
+ << XmlAttribute("size", _totDocSize)
+ << XmlAttribute("metacount", _metaCount)
+ << XmlAttribute("usedfilesize", _usedFileSize)
+ << XmlAttribute("ready", _ready)
+ << XmlAttribute("active", _active)
+ << XmlAttribute("lastmodified", _lastModified);
+}
+
+std::ostream &
+operator << (std::ostream & os, const BucketInfo & bucketInfo) {
+ return os << bucketInfo.toString();
+}
+
+}
diff --git a/storage/src/vespa/storageapi/buckets/bucketinfo.h b/storage/src/vespa/storageapi/buckets/bucketinfo.h
new file mode 100644
index 00000000000..d7b407185f6
--- /dev/null
+++ b/storage/src/vespa/storageapi/buckets/bucketinfo.h
@@ -0,0 +1,84 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @class BucketInfo
+ * @ingroup bucket
+ *
+ * @brief Contains metadata about a bucket.
+ *
+ * This class contains metadata about a bucket. It is used to send metadata
+ * within storage nodes and to distributors.
+ *
+ * @version $Id$
+ */
+
+#pragma once
+
+#include <vespa/storageapi/defs.h>
+#include <vespa/vespalib/util/xmlserializable.h>
+#include <vespa/vespalib/stllike/string.h>
+
+namespace storage::api {
+
+class BucketInfo
+{
+ Timestamp _lastModified;
+ uint32_t _checksum;
+ uint32_t _docCount;
+ uint32_t _totDocSize;
+ uint32_t _metaCount;
+ uint32_t _usedFileSize;
+ bool _ready;
+ bool _active;
+
+public:
+ BucketInfo() noexcept;
+ BucketInfo(uint32_t checksum, uint32_t docCount, uint32_t totDocSize) noexcept;
+ BucketInfo(uint32_t checksum, uint32_t docCount, uint32_t totDocSize,
+ uint32_t metaCount, uint32_t usedFileSize) noexcept;
+ BucketInfo(uint32_t checksum, uint32_t docCount, uint32_t totDocSize,
+ uint32_t metaCount, uint32_t usedFileSize,
+ bool ready, bool active) noexcept;
+ BucketInfo(uint32_t checksum, uint32_t docCount, uint32_t totDocSize,
+ uint32_t metaCount, uint32_t usedFileSize,
+ bool ready, bool active, Timestamp lastModified) noexcept;
+
+ Timestamp getLastModified() const noexcept { return _lastModified; }
+ uint32_t getChecksum() const noexcept { return _checksum; }
+ uint32_t getDocumentCount() const noexcept { return _docCount; }
+ uint32_t getTotalDocumentSize() const noexcept { return _totDocSize; }
+ uint32_t getMetaCount() const noexcept { return _metaCount; }
+ uint32_t getUsedFileSize() const noexcept { return _usedFileSize; }
+ bool isReady() const noexcept { return _ready; }
+ bool isActive() const noexcept { return _active; }
+
+ void setChecksum(uint32_t crc) noexcept { _checksum = crc; }
+ void setDocumentCount(uint32_t count) noexcept { _docCount = count; }
+ void setTotalDocumentSize(uint32_t size) noexcept { _totDocSize = size; }
+ void setMetaCount(uint32_t count) noexcept { _metaCount = count; }
+ void setUsedFileSize(uint32_t size) noexcept { _usedFileSize = size; }
+ void setReady(bool ready = true) noexcept { _ready = ready; }
+ void setActive(bool active = true) noexcept { _active = active; }
+ void setLastModified(Timestamp lastModified) noexcept { _lastModified = lastModified; }
+
+ /**
+ * Only compare checksum, total document count and document
+ * size, not meta count or used file size.
+ */
+ bool equalDocumentInfo(const BucketInfo& other) const noexcept {
+ return (_checksum == other._checksum
+ && _docCount == other._docCount
+ && _totDocSize == other._totDocSize);
+ }
+
+ bool operator==(const BucketInfo& info) const noexcept;
+ bool valid() const noexcept { return (_docCount > 0 || _totDocSize == 0); }
+ bool empty() const noexcept {
+ return _metaCount == 0 && _usedFileSize == 0 && _checksum == 0;
+ }
+ vespalib::string toString() const;
+ void printXml(vespalib::XmlOutputStream&) const;
+};
+
+std::ostream & operator << (std::ostream & os, const BucketInfo & bucketInfo);
+
+}
diff --git a/storage/src/vespa/storageapi/defs.h b/storage/src/vespa/storageapi/defs.h
new file mode 100644
index 00000000000..9ee9fdf2218
--- /dev/null
+++ b/storage/src/vespa/storageapi/defs.h
@@ -0,0 +1,18 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * \file defs.h
+ *
+ * \brief Common declarations for the storage API code.
+ */
+#pragma once
+
+#include <cstdint>
+
+namespace storage:: api {
+
+typedef uint64_t Timestamp;
+typedef uint32_t VisitorId;
+
+const Timestamp MAX_TIMESTAMP = (Timestamp)-1ll;
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/.gitignore b/storage/src/vespa/storageapi/mbusprot/.gitignore
new file mode 100644
index 00000000000..8e91fe9cab0
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/.gitignore
@@ -0,0 +1,10 @@
+*.lo
+.*.swp
+.depend
+.depend.NEW
+.deps
+.libs
+Makefile
+*.pb.h
+*.pb.cc
+
diff --git a/storage/src/vespa/storageapi/mbusprot/CMakeLists.txt b/storage/src/vespa/storageapi/mbusprot/CMakeLists.txt
new file mode 100644
index 00000000000..43c1da32502
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/CMakeLists.txt
@@ -0,0 +1,34 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+find_package(Protobuf REQUIRED)
+PROTOBUF_GENERATE_CPP(storageapi_PROTOBUF_SRCS storageapi_PROTOBUF_HDRS
+ protobuf/common.proto
+ protobuf/feed.proto
+ protobuf/inspect.proto
+ protobuf/visiting.proto
+ protobuf/maintenance.proto)
+
+vespa_add_source_target(protobufgen_storageapi_mbusprot DEPENDS ${storageapi_PROTOBUF_SRCS} ${storageapi_PROTOBUF_HDRS})
+
+vespa_suppress_warnings_for_protobuf_sources(SOURCES ${storageapi_PROTOBUF_SRCS})
+
+# protoc explicitly annotates methods with inline, which triggers -Werror=inline when
+# the header file grows over a certain size.
+set_source_files_properties(protocolserialization7.cpp PROPERTIES COMPILE_FLAGS "-Wno-inline")
+
+vespa_add_library(storageapi_mbusprot OBJECT
+ SOURCES
+ storagemessage.cpp
+ storagecommand.cpp
+ storagereply.cpp
+ protocolserialization.cpp
+ storageprotocol.cpp
+ protocolserialization4_2.cpp
+ protocolserialization5_0.cpp
+ protocolserialization5_1.cpp
+ protocolserialization5_2.cpp
+ protocolserialization6_0.cpp
+ protocolserialization7.cpp
+ ${storageapi_PROTOBUF_SRCS}
+ DEPENDS
+)
diff --git a/storage/src/vespa/storageapi/mbusprot/legacyprotocolserialization.h b/storage/src/vespa/storageapi/mbusprot/legacyprotocolserialization.h
new file mode 100644
index 00000000000..f3c6a6f6856
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/legacyprotocolserialization.h
@@ -0,0 +1,31 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "protocolserialization.h"
+
+namespace storage::mbusprot {
+
+/*
+ * Utility base class for pre-v7 (protobuf) serialization implementations.
+ *
+ * TODO remove on Vespa 8 alongside legacy serialization formats.
+ */
+class LegacyProtocolSerialization : public ProtocolSerialization {
+ const std::shared_ptr<const document::DocumentTypeRepo> _repo;
+public:
+ explicit LegacyProtocolSerialization(const std::shared_ptr<const document::DocumentTypeRepo>& repo)
+ : _repo(repo)
+ {}
+
+ const document::DocumentTypeRepo& getTypeRepo() const { return *_repo; }
+ const std::shared_ptr<const document::DocumentTypeRepo> getTypeRepoSp() const { return _repo; }
+
+ virtual document::Bucket getBucket(document::ByteBuffer& buf) const = 0;
+ virtual void putBucket(const document::Bucket& bucket, vespalib::GrowableByteBuffer& buf) const = 0;
+ virtual document::BucketSpace getBucketSpace(document::ByteBuffer& buf) const = 0;
+ virtual void putBucketSpace(document::BucketSpace bucketSpace, vespalib::GrowableByteBuffer& buf) const = 0;
+ virtual api::BucketInfo getBucketInfo(document::ByteBuffer& buf) const = 0;
+ virtual void putBucketInfo(const api::BucketInfo& info, vespalib::GrowableByteBuffer& buf) const = 0;
+};
+
+} // storage::mbusprot
diff --git a/storage/src/vespa/storageapi/mbusprot/oldreturncodemapper.h b/storage/src/vespa/storageapi/mbusprot/oldreturncodemapper.h
new file mode 100644
index 00000000000..40e09f23697
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/oldreturncodemapper.h
@@ -0,0 +1,68 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * \brief Maps return code values used between 4.2 and 5.0
+ */
+#pragma once
+
+namespace storage {
+namespace mbusprot {
+
+int getOldErrorCode(api::ReturnCode::Result newErrorCode) {
+ switch (newErrorCode) {
+ case api::ReturnCode::OK: return 1;
+ case api::ReturnCode::EXISTS: return 1001;
+ case api::ReturnCode::NOT_READY: return 2000;
+ case api::ReturnCode::WRONG_DISTRIBUTION: return 2001;
+ case api::ReturnCode::REJECTED: return 2002;
+ case api::ReturnCode::ABORTED: return 2003;
+ case api::ReturnCode::BUCKET_NOT_FOUND: return 2004;
+ case api::ReturnCode::BUCKET_DELETED: return 2004;
+ case api::ReturnCode::TIMESTAMP_EXIST: return 2005;
+ case api::ReturnCode::UNKNOWN_COMMAND: return 3000;
+ case api::ReturnCode::NOT_IMPLEMENTED: return 3001;
+ case api::ReturnCode::ILLEGAL_PARAMETERS: return 3002;
+ case api::ReturnCode::IGNORED: return 3003;
+ case api::ReturnCode::UNPARSEABLE: return 3004;
+ case api::ReturnCode::NOT_CONNECTED: return 4000;
+ case api::ReturnCode::TIMEOUT: return 4003;
+ case api::ReturnCode::BUSY: return 4004;
+ case api::ReturnCode::NO_SPACE: return 5000;
+ case api::ReturnCode::DISK_FAILURE: return 5001;
+ case api::ReturnCode::IO_FAILURE: return 5003;
+ case api::ReturnCode::INTERNAL_FAILURE: return 6000;
+ default: return 6001;
+ }
+}
+
+api::ReturnCode::Result getNewErrorCode(int oldErrorCode) {
+ switch (oldErrorCode) {
+ case 1: return api::ReturnCode::OK;
+ case 1000: return api::ReturnCode::OK; // NOT_FOUND
+ case 1001: return api::ReturnCode::EXISTS;
+ case 2000: return api::ReturnCode::NOT_READY;
+ case 2001: return api::ReturnCode::WRONG_DISTRIBUTION;
+ case 2002: return api::ReturnCode::REJECTED;
+ case 2003: return api::ReturnCode::ABORTED;
+ case 2004: return api::ReturnCode::BUCKET_NOT_FOUND;
+ case 2005: return api::ReturnCode::TIMESTAMP_EXIST;
+ case 3000: return api::ReturnCode::UNKNOWN_COMMAND;
+ case 3001: return api::ReturnCode::NOT_IMPLEMENTED;
+ case 3002: return api::ReturnCode::ILLEGAL_PARAMETERS;
+ case 3003: return api::ReturnCode::IGNORED;
+ case 3004: return api::ReturnCode::UNPARSEABLE;
+ case 4000: return api::ReturnCode::NOT_CONNECTED;
+ case 4001: return api::ReturnCode::BUSY; // OVERLOAD;
+ case 4002: return api::ReturnCode::NOT_READY; // REMOTE_DISABLED;
+ case 4003: return api::ReturnCode::TIMEOUT;
+ case 4004: return api::ReturnCode::BUSY;
+ case 5000: return api::ReturnCode::NO_SPACE;
+ case 5001: return api::ReturnCode::DISK_FAILURE;
+ case 5003: return api::ReturnCode::IO_FAILURE;
+ case 6000: return api::ReturnCode::INTERNAL_FAILURE;
+ default: return api::ReturnCode::INTERNAL_FAILURE;
+ }
+}
+
+} // mbusprot
+} // storage
+
diff --git a/storage/src/vespa/storageapi/mbusprot/protobuf/common.proto b/storage/src/vespa/storageapi/mbusprot/protobuf/common.proto
new file mode 100644
index 00000000000..49e1a8f8aba
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protobuf/common.proto
@@ -0,0 +1,68 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+syntax = "proto3";
+
+option cc_enable_arenas = true;
+
+package storage.mbusprot.protobuf;
+
+// Note: we use a *Request/*Response naming convention rather than *Command/*Reply,
+// as the former is the gRPC convention and that's where we intend to move.
+
+message BucketSpace {
+ uint64 space_id = 1;
+}
+
+message BucketId {
+ fixed64 raw_id = 1;
+}
+
+message Bucket {
+ uint64 space_id = 1;
+ fixed64 raw_bucket_id = 2;
+}
+
+// Next tag to use: 3
+message BucketInfo {
+ uint64 last_modified_timestamp = 1;
+ fixed32 legacy_checksum = 2;
+ // TODO v2 checksum
+ uint32 doc_count = 3;
+ uint32 total_doc_size = 4;
+ uint32 meta_count = 5;
+ uint32 used_file_size = 6;
+ bool ready = 7;
+ bool active = 8;
+}
+
+message GlobalId {
+ // 96 bits of GID data in _little_ endian. High entropy, so fixed encoding is better than varint.
+ // Low 64 bits as if memcpy()ed from bytes [0, 8) of the GID buffer
+ fixed64 lo_64 = 1;
+ // High 32 bits as if memcpy()ed from bytes [8, 12) of the GID buffer
+ fixed32 hi_32 = 2;
+}
+
+// TODO these should ideally be gRPC headers..
+message RequestHeader {
+ uint64 message_id = 1;
+ uint32 priority = 2; // Always in range [0, 255]
+ uint32 source_index = 3; // Always in range [0, 65535]
+ fixed32 loadtype_id = 4; // It's a hash with high entropy, so fixed encoding is better than varint
+}
+
+// TODO these should ideally be gRPC headers..
+message ResponseHeader {
+ // TODO this should ideally be gRPC Status...
+ uint32 return_code_id = 1;
+ bytes return_code_message = 2; // FIXME it's `bytes` since `string` will check for UTF-8... might not hold...
+ uint64 message_id = 3;
+ uint32 priority = 4; // Always in range [0, 255]
+}
+
+message Document {
+ bytes payload = 1;
+}
+
+message DocumentId {
+ bytes id = 1;
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/protobuf/feed.proto b/storage/src/vespa/storageapi/mbusprot/protobuf/feed.proto
new file mode 100644
index 00000000000..cb923db3c3c
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protobuf/feed.proto
@@ -0,0 +1,104 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+syntax = "proto3";
+
+option cc_enable_arenas = true;
+
+package storage.mbusprot.protobuf;
+
+import "common.proto";
+
+message TestAndSetCondition {
+ bytes selection = 1;
+}
+
+message PutRequest {
+ Bucket bucket = 1;
+ Document document = 2;
+ uint64 new_timestamp = 3;
+ uint64 expected_old_timestamp = 4; // If zero; no expectation.
+ TestAndSetCondition condition = 5;
+}
+
+message PutResponse {
+ BucketInfo bucket_info = 1;
+ BucketId remapped_bucket_id = 2;
+ bool was_found = 3;
+}
+
+message Update {
+ bytes payload = 1;
+}
+
+message UpdateRequest {
+ Bucket bucket = 1;
+ Update update = 2;
+ uint64 new_timestamp = 3;
+ uint64 expected_old_timestamp = 4; // If zero; no expectation.
+ TestAndSetCondition condition = 5;
+}
+
+message UpdateResponse {
+ BucketInfo bucket_info = 1;
+ BucketId remapped_bucket_id = 2;
+ uint64 updated_timestamp = 3;
+}
+
+message RemoveRequest {
+ Bucket bucket = 1;
+ bytes document_id = 2;
+ uint64 new_timestamp = 3;
+ TestAndSetCondition condition = 4;
+}
+
+message RemoveResponse {
+ BucketInfo bucket_info = 1;
+ BucketId remapped_bucket_id = 2;
+ uint64 removed_timestamp = 3;
+}
+
+message GetRequest {
+ Bucket bucket = 1;
+ bytes document_id = 2;
+ bytes field_set = 3;
+ uint64 before_timestamp = 4;
+ enum InternalReadConsistency {
+ Strong = 0; // Default for a good reason.
+ Weak = 1;
+ }
+ InternalReadConsistency internal_read_consistency = 5;
+}
+
+message GetResponse {
+ Document document = 1;
+ uint64 last_modified_timestamp = 2;
+ BucketInfo bucket_info = 3;
+ BucketId remapped_bucket_id = 4;
+ // Note: last_modified_timestamp and tombstone_timestamp are mutually exclusive.
+ // Tracked separately (rather than being a flag bool) to avoid issues during rolling upgrades.
+ uint64 tombstone_timestamp = 5;
+}
+
+message RevertRequest {
+ Bucket bucket = 1;
+ repeated uint64 revert_tokens = 2;
+}
+
+message RevertResponse {
+ BucketInfo bucket_info = 1;
+ BucketId remapped_bucket_id = 2;
+}
+
+message RemoveLocationRequest {
+ Bucket bucket = 1;
+ bytes document_selection = 2;
+}
+
+message RemoveLocationStats {
+ uint32 documents_removed = 1;
+}
+
+message RemoveLocationResponse {
+ BucketInfo bucket_info = 1;
+ BucketId remapped_bucket_id = 2;
+ RemoveLocationStats stats = 3;
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/protobuf/inspect.proto b/storage/src/vespa/storageapi/mbusprot/protobuf/inspect.proto
new file mode 100644
index 00000000000..c3f4b1263a1
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protobuf/inspect.proto
@@ -0,0 +1,19 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+syntax = "proto3";
+
+option cc_enable_arenas = true;
+
+package storage.mbusprot.protobuf;
+
+import "common.proto";
+
+message StatBucketRequest {
+ Bucket bucket = 1;
+ bytes document_selection = 2;
+}
+
+message StatBucketResponse {
+ BucketId remapped_bucket_id = 1;
+ bytes results = 2;
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/protobuf/maintenance.proto b/storage/src/vespa/storageapi/mbusprot/protobuf/maintenance.proto
new file mode 100644
index 00000000000..7f7ab1d7c0b
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protobuf/maintenance.proto
@@ -0,0 +1,167 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+syntax = "proto3";
+
+option cc_enable_arenas = true;
+
+package storage.mbusprot.protobuf;
+
+import "common.proto";
+
+message DeleteBucketRequest {
+ Bucket bucket = 1;
+ BucketInfo expected_bucket_info = 2;
+}
+
+message DeleteBucketResponse {
+ BucketInfo bucket_info = 1;
+ BucketId remapped_bucket_id = 2;
+}
+
+message CreateBucketRequest {
+ Bucket bucket = 1;
+ bool create_as_active = 2;
+}
+
+message CreateBucketResponse {
+ BucketInfo bucket_info = 1;
+ BucketId remapped_bucket_id = 2;
+}
+
+message MergeNode {
+ uint32 index = 1;
+ bool source_only = 2;
+}
+
+message MergeBucketRequest {
+ Bucket bucket = 1;
+ uint32 cluster_state_version = 2;
+ uint64 max_timestamp = 3;
+ repeated MergeNode nodes = 4;
+ repeated uint32 node_chain = 5;
+ bool unordered_forwarding = 6;
+}
+
+message MergeBucketResponse {
+ BucketId remapped_bucket_id = 1;
+}
+
+message MetaDiffEntry {
+ uint64 timestamp = 1;
+ GlobalId gid = 2;
+ uint32 header_size = 3;
+ uint32 body_size = 4;
+ uint32 flags = 5;
+ uint32 presence_mask = 6;
+}
+
+message GetBucketDiffRequest {
+ Bucket bucket = 1;
+ uint64 max_timestamp = 2;
+ repeated MergeNode nodes = 3;
+ repeated MetaDiffEntry diff = 4;
+}
+
+message GetBucketDiffResponse {
+ BucketId remapped_bucket_id = 1;
+ repeated MetaDiffEntry diff = 2;
+}
+
+message ApplyDiffEntry {
+ MetaDiffEntry entry_meta = 1;
+ bytes document_id = 2;
+ bytes header_blob = 3;
+ bytes body_blob = 4;
+}
+
+message ApplyBucketDiffRequest {
+ Bucket bucket = 1;
+ repeated MergeNode nodes = 2;
+ uint32 max_buffer_size = 3;
+ repeated ApplyDiffEntry entries = 4;
+}
+
+message ApplyBucketDiffResponse {
+ BucketId remapped_bucket_id = 1;
+ repeated ApplyDiffEntry entries = 4;
+}
+
+message ExplicitBucketSet {
+ // `Bucket` is not needed, as the space is inferred from the owning message.
+ repeated BucketId bucket_ids = 2;
+}
+
+message AllBuckets {
+ uint32 distributor_index = 1;
+ bytes cluster_state = 2;
+ bytes distribution_hash = 3;
+}
+
+message RequestBucketInfoRequest {
+ BucketSpace bucket_space = 1;
+ oneof request_for {
+ ExplicitBucketSet explicit_bucket_set = 2;
+ AllBuckets all_buckets = 3;
+ }
+}
+
+message BucketAndBucketInfo {
+ fixed64 raw_bucket_id = 1;
+ BucketInfo bucket_info = 2;
+}
+
+message SupportedNodeFeatures {
+ bool unordered_merge_chaining = 1;
+}
+
+message RequestBucketInfoResponse {
+ repeated BucketAndBucketInfo bucket_infos = 1;
+ // Only present for full bucket info fetches (not for explicit buckets)
+ SupportedNodeFeatures supported_node_features = 2;
+}
+
+message NotifyBucketChangeRequest {
+ Bucket bucket = 1;
+ BucketInfo bucket_info = 2;
+}
+
+message NotifyBucketChangeResponse {
+ // Currently empty
+}
+
+message SplitBucketRequest {
+ Bucket bucket = 1;
+ uint32 min_split_bits = 2;
+ uint32 max_split_bits = 3;
+ uint32 min_byte_size = 4;
+ uint32 min_doc_count = 5;
+}
+
+message SplitBucketResponse {
+ BucketId remapped_bucket_id = 1;
+ repeated BucketAndBucketInfo split_info = 2;
+}
+
+message JoinBucketsRequest {
+ Bucket bucket = 1;
+ repeated BucketId source_buckets = 2;
+ uint32 min_join_bits = 3;
+}
+
+message JoinBucketsResponse {
+ BucketInfo bucket_info = 1;
+ BucketId remapped_bucket_id = 2;
+}
+
+message SetBucketStateRequest {
+ enum BucketState {
+ Inactive = 0;
+ Active = 1;
+ }
+
+ Bucket bucket = 1;
+ BucketState state = 2;
+}
+
+message SetBucketStateResponse {
+ BucketId remapped_bucket_id = 1;
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/protobuf/visiting.proto b/storage/src/vespa/storageapi/mbusprot/protobuf/visiting.proto
new file mode 100644
index 00000000000..35d69bc2d3e
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protobuf/visiting.proto
@@ -0,0 +1,66 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+syntax = "proto3";
+
+option cc_enable_arenas = true;
+
+package storage.mbusprot.protobuf;
+
+import "common.proto";
+
+message ClientVisitorParameter {
+ bytes key = 1;
+ bytes value = 2;
+}
+
+message VisitorConstraints {
+ bytes document_selection = 1;
+ uint64 from_time_usec = 2;
+ uint64 to_time_usec = 3;
+ bool visit_removes = 4;
+ bytes field_set = 5;
+ bool visit_inconsistent_buckets = 6;
+}
+
+message VisitorControlMeta {
+ bytes instance_id = 1;
+ bytes library_name = 2;
+ uint32 visitor_command_id = 3;
+ bytes control_destination = 4;
+ bytes data_destination = 5;
+
+ // TODO move?
+ uint32 max_pending_reply_count = 6;
+ uint32 queue_timeout = 7;
+ uint32 max_buckets_per_visitor = 8;
+}
+
+message CreateVisitorRequest {
+ BucketSpace bucket_space = 1;
+ repeated BucketId buckets = 2;
+
+ VisitorConstraints constraints = 3;
+ VisitorControlMeta control_meta = 4;
+ repeated ClientVisitorParameter client_parameters = 5;
+}
+
+message VisitorStatistics {
+ uint32 buckets_visited = 1;
+ uint64 documents_visited = 2;
+ uint64 bytes_visited = 3;
+ uint64 documents_returned = 4;
+ uint64 bytes_returned = 5;
+ uint64 second_pass_documents_returned = 6; // TODO don't include? orderdoc only
+ uint64 second_pass_bytes_returned = 7; // TODO don't include? orderdoc only
+}
+
+message CreateVisitorResponse {
+ VisitorStatistics visitor_statistics = 1;
+}
+
+message DestroyVisitorRequest {
+ bytes instance_id = 1;
+}
+
+message DestroyVisitorResponse {
+ // Currently empty
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/protobuf_includes.h b/storage/src/vespa/storageapi/mbusprot/protobuf_includes.h
new file mode 100644
index 00000000000..9accfdf75ee
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protobuf_includes.h
@@ -0,0 +1,16 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+// Disable warnings emitted by protoc generated files
+#pragma GCC diagnostic push
+#ifndef __clang__
+#pragma GCC diagnostic ignored "-Wsuggest-override"
+#endif
+
+#include <vespa/storageapi/mbusprot/feed.pb.h>
+#include <vespa/storageapi/mbusprot/inspect.pb.h>
+#include <vespa/storageapi/mbusprot/visiting.pb.h>
+#include <vespa/storageapi/mbusprot/maintenance.pb.h>
+
+#pragma GCC diagnostic pop
diff --git a/storage/src/vespa/storageapi/mbusprot/protocolserialization.cpp b/storage/src/vespa/storageapi/mbusprot/protocolserialization.cpp
new file mode 100644
index 00000000000..4614659c458
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protocolserialization.cpp
@@ -0,0 +1,281 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "protocolserialization4_2.h"
+#include "serializationhelper.h"
+#include "storagecommand.h"
+#include "storagereply.h"
+#include <vespa/storageapi/message/bucketsplitting.h>
+#include <vespa/storageapi/message/visitor.h>
+#include <vespa/storageapi/message/removelocation.h>
+#include <vespa/storageapi/message/stat.h>
+#include <vespa/vespalib/util/exceptions.h>
+
+
+#include <sstream>
+
+#include <vespa/log/log.h>
+LOG_SETUP(".storage.api.mbusprot.serialization.base");
+
+namespace storage::mbusprot {
+
+mbus::Blob
+ProtocolSerialization::encode(const api::StorageMessage& msg) const
+{
+ vespalib::GrowableByteBuffer buf;
+
+ buf.putInt(msg.getType().getId());
+ switch (msg.getType().getId()) {
+ case api::MessageType::PUT_ID:
+ onEncode(buf, static_cast<const api::PutCommand&>(msg));
+ break;
+ case api::MessageType::PUT_REPLY_ID:
+ onEncode(buf, static_cast<const api::PutReply&>(msg));
+ break;
+ case api::MessageType::UPDATE_ID:
+ onEncode(buf, static_cast<const api::UpdateCommand&>(msg));
+ break;
+ case api::MessageType::UPDATE_REPLY_ID:
+ onEncode(buf, static_cast<const api::UpdateReply&>(msg));
+ break;
+ case api::MessageType::GET_ID:
+ onEncode(buf, static_cast<const api::GetCommand&>(msg));
+ break;
+ case api::MessageType::GET_REPLY_ID:
+ onEncode(buf, static_cast<const api::GetReply&>(msg));
+ break;
+ case api::MessageType::REMOVE_ID:
+ onEncode(buf, static_cast<const api::RemoveCommand&>(msg));
+ break;
+ case api::MessageType::REMOVE_REPLY_ID:
+ onEncode(buf, static_cast<const api::RemoveReply&>(msg));
+ break;
+ case api::MessageType::REVERT_ID:
+ onEncode(buf, static_cast<const api::RevertCommand&>(msg));
+ break;
+ case api::MessageType::REVERT_REPLY_ID:
+ onEncode(buf, static_cast<const api::RevertReply&>(msg));
+ break;
+ case api::MessageType::DELETEBUCKET_ID:
+ onEncode(buf, static_cast<const api::DeleteBucketCommand&>(msg));
+ break;
+ case api::MessageType::DELETEBUCKET_REPLY_ID:
+ onEncode(buf, static_cast<const api::DeleteBucketReply&>(msg));
+ break;
+ case api::MessageType::CREATEBUCKET_ID:
+ onEncode(buf, static_cast<const api::CreateBucketCommand&>(msg));
+ break;
+ case api::MessageType::CREATEBUCKET_REPLY_ID:
+ onEncode(buf, static_cast<const api::CreateBucketReply&>(msg));
+ break;
+ case api::MessageType::MERGEBUCKET_ID:
+ onEncode(buf, static_cast<const api::MergeBucketCommand&>(msg));
+ break;
+ case api::MessageType::MERGEBUCKET_REPLY_ID:
+ onEncode(buf, static_cast<const api::MergeBucketReply&>(msg));
+ break;
+ case api::MessageType::GETBUCKETDIFF_ID:
+ onEncode(buf, static_cast<const api::GetBucketDiffCommand&>(msg));
+ break;
+ case api::MessageType::GETBUCKETDIFF_REPLY_ID:
+ onEncode(buf, static_cast<const api::GetBucketDiffReply&>(msg));
+ break;
+ case api::MessageType::APPLYBUCKETDIFF_ID:
+ onEncode(buf, static_cast<const api::ApplyBucketDiffCommand&>(msg));
+ break;
+ case api::MessageType::APPLYBUCKETDIFF_REPLY_ID:
+ onEncode(buf, static_cast<const api::ApplyBucketDiffReply&>(msg));
+ break;
+ case api::MessageType::REQUESTBUCKETINFO_ID:
+ onEncode(buf, static_cast<const api::RequestBucketInfoCommand&>(msg));
+ break;
+ case api::MessageType::REQUESTBUCKETINFO_REPLY_ID:
+ onEncode(buf, static_cast<const api::RequestBucketInfoReply&>(msg));
+ break;
+ case api::MessageType::NOTIFYBUCKETCHANGE_ID:
+ onEncode(buf, static_cast<const api::NotifyBucketChangeCommand&>(msg));
+ break;
+ case api::MessageType::NOTIFYBUCKETCHANGE_REPLY_ID:
+ onEncode(buf, static_cast<const api::NotifyBucketChangeReply&>(msg));
+ break;
+ case api::MessageType::SPLITBUCKET_ID:
+ onEncode(buf, static_cast<const api::SplitBucketCommand&>(msg));
+ break;
+ case api::MessageType::SPLITBUCKET_REPLY_ID:
+ onEncode(buf, static_cast<const api::SplitBucketReply&>(msg));
+ break;
+ case api::MessageType::JOINBUCKETS_ID:
+ onEncode(buf, static_cast<const api::JoinBucketsCommand&>(msg));
+ break;
+ case api::MessageType::JOINBUCKETS_REPLY_ID:
+ onEncode(buf, static_cast<const api::JoinBucketsReply&>(msg));
+ break;
+ case api::MessageType::VISITOR_CREATE_ID:
+ onEncode(buf, static_cast<const api::CreateVisitorCommand&>(msg));
+ break;
+ case api::MessageType::VISITOR_CREATE_REPLY_ID:
+ onEncode(buf, static_cast<const api::CreateVisitorReply&>(msg));
+ break;
+ case api::MessageType::VISITOR_DESTROY_ID:
+ onEncode(buf, static_cast<const api::DestroyVisitorCommand&>(msg));
+ break;
+ case api::MessageType::VISITOR_DESTROY_REPLY_ID:
+ onEncode(buf, static_cast<const api::DestroyVisitorReply&>(msg));
+ break;
+ case api::MessageType::STATBUCKET_ID:
+ onEncode(buf, static_cast<const api::StatBucketCommand&>(msg));
+ break;
+ case api::MessageType::STATBUCKET_REPLY_ID:
+ onEncode(buf, static_cast<const api::StatBucketReply&>(msg));
+ break;
+ case api::MessageType::REMOVELOCATION_ID:
+ onEncode(buf, static_cast<const api::RemoveLocationCommand&>(msg));
+ break;
+ case api::MessageType::REMOVELOCATION_REPLY_ID:
+ onEncode(buf, static_cast<const api::RemoveLocationReply&>(msg));
+ break;
+ case api::MessageType::SETBUCKETSTATE_ID:
+ onEncode(buf, static_cast<const api::SetBucketStateCommand&>(msg));
+ break;
+ case api::MessageType::SETBUCKETSTATE_REPLY_ID:
+ onEncode(buf, static_cast<const api::SetBucketStateReply&>(msg));
+ break;
+ default:
+ LOG(error, "Trying to encode unhandled type %s",
+ msg.getType().toString().c_str());
+ break;
+ }
+
+ mbus::Blob retVal(buf.position());
+ memcpy(retVal.data(), buf.getBuffer(), buf.position());
+ return retVal;
+}
+
+StorageCommand::UP
+ProtocolSerialization::decodeCommand(mbus::BlobRef data) const
+{
+ LOG(spam, "Decode %d bytes of data.", data.size());
+ if (data.size() < sizeof(int32_t)) {
+ std::ostringstream ost;
+ ost << "Request of size " << data.size() << " is not big enough to be "
+ "able to store a request.";
+ throw vespalib::IllegalArgumentException(ost.str(), VESPA_STRLOC);
+ }
+
+ document::ByteBuffer buf(data.data(), data.size());
+ int type;
+ buf.getIntNetwork(type);
+ SCmd::UP cmd;
+ switch (type) {
+ case api::MessageType::PUT_ID:
+ cmd = onDecodePutCommand(buf); break;
+ case api::MessageType::UPDATE_ID:
+ cmd = onDecodeUpdateCommand(buf); break;
+ case api::MessageType::GET_ID:
+ cmd = onDecodeGetCommand(buf); break;
+ case api::MessageType::REMOVE_ID:
+ cmd = onDecodeRemoveCommand(buf); break;
+ case api::MessageType::REVERT_ID:
+ cmd = onDecodeRevertCommand(buf); break;
+ case api::MessageType::CREATEBUCKET_ID:
+ cmd = onDecodeCreateBucketCommand(buf); break;
+ case api::MessageType::DELETEBUCKET_ID:
+ cmd = onDecodeDeleteBucketCommand(buf); break;
+ case api::MessageType::MERGEBUCKET_ID:
+ cmd = onDecodeMergeBucketCommand(buf); break;
+ case api::MessageType::GETBUCKETDIFF_ID:
+ cmd = onDecodeGetBucketDiffCommand(buf); break;
+ case api::MessageType::APPLYBUCKETDIFF_ID:
+ cmd = onDecodeApplyBucketDiffCommand(buf); break;
+ case api::MessageType::REQUESTBUCKETINFO_ID:
+ cmd = onDecodeRequestBucketInfoCommand(buf); break;
+ case api::MessageType::NOTIFYBUCKETCHANGE_ID:
+ cmd = onDecodeNotifyBucketChangeCommand(buf); break;
+ case api::MessageType::SPLITBUCKET_ID:
+ cmd = onDecodeSplitBucketCommand(buf); break;
+ case api::MessageType::JOINBUCKETS_ID:
+ cmd = onDecodeJoinBucketsCommand(buf); break;
+ case api::MessageType::VISITOR_CREATE_ID:
+ cmd = onDecodeCreateVisitorCommand(buf); break;
+ case api::MessageType::VISITOR_DESTROY_ID:
+ cmd = onDecodeDestroyVisitorCommand(buf); break;
+ case api::MessageType::STATBUCKET_ID:
+ cmd = onDecodeStatBucketCommand(buf); break;
+ case api::MessageType::REMOVELOCATION_ID:
+ cmd = onDecodeRemoveLocationCommand(buf); break;
+ case api::MessageType::SETBUCKETSTATE_ID:
+ cmd = onDecodeSetBucketStateCommand(buf); break;
+ default:
+ {
+ std::ostringstream ost;
+ ost << "Unknown storage command type " << type;
+ throw vespalib::IllegalArgumentException(ost.str(), VESPA_STRLOC);
+ }
+ }
+ return std::make_unique<StorageCommand>(std::move(cmd));
+}
+
+StorageReply::UP
+ProtocolSerialization::decodeReply(mbus::BlobRef data, const api::StorageCommand& cmd) const
+{
+ LOG(spam, "Decode %d bytes of data.", data.size());
+ if (data.size() < sizeof(int32_t)) {
+ std::ostringstream ost;
+ ost << "Request of size " << data.size() << " is not big enough to be "
+ "able to store a request.";
+ throw vespalib::IllegalArgumentException(ost.str(), VESPA_STRLOC);
+ }
+
+ document::ByteBuffer buf(data.data(), data.size());
+ int type;
+ buf.getIntNetwork(type);
+ SRep::UP reply;
+ switch (type) {
+ case api::MessageType::PUT_REPLY_ID:
+ reply = onDecodePutReply(cmd, buf); break;
+ case api::MessageType::UPDATE_REPLY_ID:
+ reply = onDecodeUpdateReply(cmd, buf); break;
+ case api::MessageType::GET_REPLY_ID:
+ reply = onDecodeGetReply(cmd, buf); break;
+ case api::MessageType::REMOVE_REPLY_ID:
+ reply = onDecodeRemoveReply(cmd, buf); break;
+ case api::MessageType::REVERT_REPLY_ID:
+ reply = onDecodeRevertReply(cmd, buf); break;
+ case api::MessageType::CREATEBUCKET_REPLY_ID:
+ reply = onDecodeCreateBucketReply(cmd, buf); break;
+ case api::MessageType::DELETEBUCKET_REPLY_ID:
+ reply = onDecodeDeleteBucketReply(cmd, buf); break;
+ case api::MessageType::MERGEBUCKET_REPLY_ID:
+ reply = onDecodeMergeBucketReply(cmd, buf); break;
+ case api::MessageType::GETBUCKETDIFF_REPLY_ID:
+ reply = onDecodeGetBucketDiffReply(cmd, buf); break;
+ case api::MessageType::APPLYBUCKETDIFF_REPLY_ID:
+ reply = onDecodeApplyBucketDiffReply(cmd, buf); break;
+ case api::MessageType::REQUESTBUCKETINFO_REPLY_ID:
+ reply = onDecodeRequestBucketInfoReply(cmd, buf); break;
+ case api::MessageType::NOTIFYBUCKETCHANGE_REPLY_ID:
+ reply = onDecodeNotifyBucketChangeReply(cmd, buf); break;
+ case api::MessageType::SPLITBUCKET_REPLY_ID:
+ reply = onDecodeSplitBucketReply(cmd, buf); break;
+ case api::MessageType::JOINBUCKETS_REPLY_ID:
+ reply = onDecodeJoinBucketsReply(cmd, buf); break;
+ case api::MessageType::VISITOR_CREATE_REPLY_ID:
+ reply = onDecodeCreateVisitorReply(cmd, buf); break;
+ case api::MessageType::VISITOR_DESTROY_REPLY_ID:
+ reply = onDecodeDestroyVisitorReply(cmd, buf); break;
+ case api::MessageType::STATBUCKET_REPLY_ID:
+ reply = onDecodeStatBucketReply(cmd, buf); break;
+ case api::MessageType::REMOVELOCATION_REPLY_ID:
+ reply = onDecodeRemoveLocationReply(cmd, buf); break;
+ case api::MessageType::SETBUCKETSTATE_REPLY_ID:
+ reply = onDecodeSetBucketStateReply(cmd, buf); break;
+ default:
+ {
+ std::ostringstream ost;
+ ost << "Unknown message type " << type;
+ throw vespalib::IllegalArgumentException(ost.str(), VESPA_STRLOC);
+ }
+ }
+ return std::make_unique<StorageReply>(std::move(reply));
+}
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/protocolserialization.h b/storage/src/vespa/storageapi/mbusprot/protocolserialization.h
new file mode 100644
index 00000000000..653e9ded85a
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protocolserialization.h
@@ -0,0 +1,159 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/document/bucket/bucket.h>
+#include <vespa/messagebus/routable.h>
+#include <vespa/storageapi/mbusprot/storagemessage.h>
+#include <vespa/storageapi/message/bucket.h>
+
+namespace document {
+ class ByteBuffer;
+}
+namespace mbus {
+ class Blob;
+ class BlobRef;
+}
+namespace vespalib {
+ class GrowableByteBuffer;
+}
+namespace storage::api {
+class StorageCommand;
+class StorageReply;
+class PutCommand;
+class PutReply;
+class GetCommand;
+class GetReply;
+class RemoveCommand;
+class RemoveReply;
+class RevertCommand;
+class RevertReply;
+class DeleteBucketCommand;
+class DeleteBucketReply;
+class CreateBucketCommand;
+class CreateBucketReply;
+class MergeBucketCommand;
+class MergeBucketReply;
+class GetBucketDiffCommand;
+class GetBucketDiffReply;
+class ApplyBucketDiffCommand;
+class ApplyBucketDiffReply;
+class RequestBucketInfoCommand;
+class RequestBucketInfoReply;
+class NotifyBucketChangeCommand;
+class NotifyBucketChangeReply;
+class SplitBucketCommand;
+class SplitBucketReply;
+class StatBucketCommand;
+class StatBucketReply;
+class JoinBucketsCommand;
+class JoinBucketsReply;
+class SetBucketStateCommand;
+class SetBucketStateReply;
+class CreateVisitorCommand;
+class RemoveLocationCommand;
+class RemoveLocationReply;
+}
+
+namespace storage::mbusprot {
+
+class SerializationHelper;
+class StorageCommand;
+class StorageReply;
+
+class ProtocolSerialization {
+public:
+ virtual mbus::Blob encode(const api::StorageMessage&) const;
+ virtual std::unique_ptr<StorageCommand> decodeCommand(mbus::BlobRef) const;
+ virtual std::unique_ptr<StorageReply> decodeReply(
+ mbus::BlobRef, const api::StorageCommand&) const;
+protected:
+ ProtocolSerialization() = default;
+ virtual ~ProtocolSerialization() = default;
+
+ typedef api::StorageCommand SCmd;
+ typedef api::StorageReply SRep;
+ typedef document::ByteBuffer BBuf;
+ typedef vespalib::GrowableByteBuffer GBBuf;
+ typedef SerializationHelper SH;
+ typedef StorageMessage SM;
+
+ virtual void onEncode(GBBuf&, const api::PutCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::PutReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::UpdateCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::UpdateReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::GetCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::GetReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::RemoveCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::RemoveReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::RevertCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::RevertReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::DeleteBucketCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::DeleteBucketReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::CreateBucketCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::CreateBucketReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::MergeBucketCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::MergeBucketReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::GetBucketDiffCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::GetBucketDiffReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::ApplyBucketDiffCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::ApplyBucketDiffReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::RequestBucketInfoCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::RequestBucketInfoReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::NotifyBucketChangeCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::NotifyBucketChangeReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::SplitBucketCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::SplitBucketReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::JoinBucketsCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::JoinBucketsReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::SetBucketStateCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::SetBucketStateReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::CreateVisitorCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::CreateVisitorReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::DestroyVisitorCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::DestroyVisitorReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::RemoveLocationCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::RemoveLocationReply&) const = 0;
+ virtual void onEncode(GBBuf&, const api::StatBucketCommand&) const = 0;
+ virtual void onEncode(GBBuf&, const api::StatBucketReply&) const = 0;
+
+ virtual SCmd::UP onDecodePutCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodePutReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeUpdateCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeUpdateReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeGetCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeGetReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeRemoveCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeRemoveReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeRevertCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeRevertReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeDeleteBucketCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeDeleteBucketReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeCreateBucketCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeCreateBucketReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeMergeBucketCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeMergeBucketReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeGetBucketDiffCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeGetBucketDiffReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeApplyBucketDiffCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeApplyBucketDiffReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeRequestBucketInfoCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeRequestBucketInfoReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeNotifyBucketChangeCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeNotifyBucketChangeReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeSplitBucketCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeSplitBucketReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeJoinBucketsCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeJoinBucketsReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeSetBucketStateCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeSetBucketStateReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeCreateVisitorCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeCreateVisitorReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeDestroyVisitorCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeDestroyVisitorReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeRemoveLocationCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeRemoveLocationReply(const SCmd&, BBuf&) const = 0;
+ virtual SCmd::UP onDecodeStatBucketCommand(BBuf&) const = 0;
+ virtual SRep::UP onDecodeStatBucketReply(const SCmd&, BBuf&) const = 0;
+};
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/protocolserialization4_2.cpp b/storage/src/vespa/storageapi/mbusprot/protocolserialization4_2.cpp
new file mode 100644
index 00000000000..1bea0ea74c9
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protocolserialization4_2.cpp
@@ -0,0 +1,553 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "protocolserialization4_2.h"
+#include "oldreturncodemapper.h"
+#include "serializationhelper.h"
+#include "storagecommand.h"
+#include "storagereply.h"
+
+#include <vespa/storageapi/message/bucketsplitting.h>
+#include <vespa/storageapi/message/visitor.h>
+#include <vespa/storageapi/message/removelocation.h>
+#include <vespa/vespalib/util/exceptions.h>
+#include <vespa/document/fieldset/fieldsets.h>
+
+#include <vespa/log/log.h>
+LOG_SETUP(".storage.api.mbusprot.serialization.4_2");
+
+using document::BucketSpace;
+using document::AllFields;
+
+namespace storage::mbusprot {
+
+ProtocolSerialization4_2::ProtocolSerialization4_2(
+ const std::shared_ptr<const document::DocumentTypeRepo>& repo)
+ : LegacyProtocolSerialization(repo)
+{
+}
+
+void ProtocolSerialization4_2::onEncode(GBBuf& buf, const api::GetCommand& msg) const
+{
+ buf.putString(msg.getDocumentId().toString());
+ putBucket(msg.getBucket(), buf);
+ buf.putLong(msg.getBeforeTimestamp());
+ buf.putBoolean(false);
+ onEncodeCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization4_2::onDecodeGetCommand(BBuf& buf) const
+{
+ document::DocumentId did(SH::getString(buf));
+ document::Bucket bucket = getBucket(buf);
+ api::Timestamp beforeTimestamp(SH::getLong(buf));
+ bool headerOnly(SH::getBoolean(buf)); // Ignored header only flag
+ (void) headerOnly;
+ auto msg = std::make_unique<api::GetCommand>(bucket, did, AllFields::NAME, beforeTimestamp);
+ onDecodeCommand(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization4_2::onEncode(GBBuf& buf, const api::RemoveCommand& msg) const
+{
+ buf.putString(msg.getDocumentId().toString());
+ putBucket(msg.getBucket(), buf);
+ buf.putLong(msg.getTimestamp());
+ onEncodeBucketInfoCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization4_2::onDecodeRemoveCommand(BBuf& buf) const
+{
+ document::DocumentId did(SH::getString(buf));
+ document::Bucket bucket = getBucket(buf);
+ api::Timestamp timestamp(SH::getLong(buf));
+ auto msg = std::make_unique<api::RemoveCommand>(bucket, did, timestamp);
+ onDecodeBucketInfoCommand(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization4_2::onEncode(GBBuf& buf, const api::RevertCommand& msg) const
+{
+ putBucket(msg.getBucket(), buf);
+ buf.putInt(msg.getRevertTokens().size());
+ for (uint32_t i=0, n=msg.getRevertTokens().size(); i<n; ++i) {
+ buf.putLong(msg.getRevertTokens()[i]);
+ }
+ onEncodeBucketInfoCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization4_2::onDecodeRevertCommand(BBuf& buf) const
+{
+ document::Bucket bucket = getBucket(buf);
+ std::vector<api::Timestamp> tokens(SH::getInt(buf));
+ for (uint32_t i=0, n=tokens.size(); i<n; ++i) {
+ tokens[i] = SH::getLong(buf);
+ }
+ auto msg = std::make_unique<api::RevertCommand>(bucket, tokens);
+ onDecodeBucketInfoCommand(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization4_2::onEncode(GBBuf& buf, const api::CreateBucketCommand& msg) const
+{
+ putBucket(msg.getBucket(), buf);
+ onEncodeBucketInfoCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization4_2::onDecodeCreateBucketCommand(BBuf& buf) const
+{
+ document::Bucket bucket = getBucket(buf);
+ auto msg = std::make_unique<api::CreateBucketCommand>(bucket);
+ onDecodeBucketInfoCommand(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization4_2::onEncode(GBBuf& buf, const api::MergeBucketCommand& msg) const
+{
+ putBucket(msg.getBucket(), buf);
+ const std::vector<api::MergeBucketCommand::Node>& nodes(msg.getNodes());
+ buf.putShort(nodes.size());
+ for (uint32_t i=0; i<nodes.size(); ++i) {
+ buf.putShort(nodes[i].index);
+ buf.putBoolean(nodes[i].sourceOnly);
+ }
+ buf.putLong(msg.getMaxTimestamp());
+ onEncodeCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization4_2::onDecodeMergeBucketCommand(BBuf& buf) const
+{
+ typedef api::MergeBucketCommand::Node Node;
+ document::Bucket bucket = getBucket(buf);
+ uint16_t nodeCount = SH::getShort(buf);
+ std::vector<Node> nodes;
+ nodes.reserve(nodeCount);
+ for (uint32_t i=0; i<nodeCount; ++i) {
+ uint16_t index(SH::getShort(buf));
+ bool sourceOnly = SH::getBoolean(buf);
+ nodes.push_back(Node(index, sourceOnly));
+ }
+ api::Timestamp timestamp(SH::getLong(buf));
+ auto msg = std::make_unique<api::MergeBucketCommand>(bucket, nodes, timestamp);
+ onDecodeCommand(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization4_2::onEncode(GBBuf& buf, const api::GetBucketDiffCommand& msg) const
+{
+ putBucket(msg.getBucket(), buf);
+ const std::vector<api::MergeBucketCommand::Node>& nodes(msg.getNodes());
+ buf.putShort(nodes.size());
+ for (uint32_t i=0; i<nodes.size(); ++i) {
+ buf.putShort(nodes[i].index);
+ buf.putBoolean(nodes[i].sourceOnly);
+ }
+ buf.putLong(msg.getMaxTimestamp());
+ const std::vector<api::GetBucketDiffCommand::Entry>& entries(msg.getDiff());
+ buf.putInt(entries.size());
+ for (uint32_t i=0; i<entries.size(); ++i) {
+ onEncodeDiffEntry(buf, entries[i]);
+ }
+ onEncodeCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization4_2::onDecodeGetBucketDiffCommand(BBuf& buf) const
+{
+ typedef api::MergeBucketCommand::Node Node;
+ document::Bucket bucket = getBucket(buf);
+ uint16_t nodeCount = SH::getShort(buf);
+ std::vector<Node> nodes;
+ nodes.reserve(nodeCount);
+ for (uint32_t i=0; i<nodeCount; ++i) {
+ uint16_t index(SH::getShort(buf));
+ bool sourceOnly = SH::getBoolean(buf);
+ nodes.push_back(Node(index, sourceOnly));
+ }
+ api::Timestamp timestamp = SH::getLong(buf);
+ auto msg = std::make_unique<api::GetBucketDiffCommand>(bucket, nodes, timestamp);
+ std::vector<api::GetBucketDiffCommand::Entry>& entries(msg->getDiff());
+ uint32_t entryCount = SH::getInt(buf);
+ if (entryCount > buf.getRemaining()) {
+ // Trigger out of bounds exception rather than out of memory error
+ buf.incPos(entryCount);
+ }
+ entries.resize(entryCount);
+ for (uint32_t i=0; i<entries.size(); ++i) {
+ onDecodeDiffEntry(buf, entries[i]);
+ }
+ onDecodeCommand(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization4_2::onEncode(GBBuf& buf, const api::ApplyBucketDiffCommand& msg) const
+{
+ putBucket(msg.getBucket(), buf);
+ const std::vector<api::MergeBucketCommand::Node>& nodes(msg.getNodes());
+ buf.putShort(nodes.size());
+ for (uint32_t i=0; i<nodes.size(); ++i) {
+ buf.putShort(nodes[i].index);
+ buf.putBoolean(nodes[i].sourceOnly);
+ }
+ buf.putInt(0x400000);
+ const std::vector<api::ApplyBucketDiffCommand::Entry>& entries(msg.getDiff());
+ buf.putInt(entries.size());
+ for (uint32_t i=0; i<entries.size(); ++i) {
+ onEncodeDiffEntry(buf, entries[i]._entry);
+ buf.putString(entries[i]._docName);
+ buf.putInt(entries[i]._headerBlob.size());
+ buf.putBytes(&entries[i]._headerBlob[0], entries[i]._headerBlob.size());
+ buf.putInt(entries[i]._bodyBlob.size());
+ buf.putBytes(&entries[i]._bodyBlob[0], entries[i]._bodyBlob.size());
+ }
+ onEncodeBucketInfoCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization4_2::onDecodeApplyBucketDiffCommand(BBuf& buf) const
+{
+ typedef api::MergeBucketCommand::Node Node;
+ document::Bucket bucket = getBucket(buf);
+ uint16_t nodeCount = SH::getShort(buf);
+ std::vector<Node> nodes;
+ nodes.reserve(nodeCount);
+ for (uint32_t i=0; i<nodeCount; ++i) {
+ uint16_t index(SH::getShort(buf));
+ bool sourceOnly = SH::getBoolean(buf);
+ nodes.push_back(Node(index, sourceOnly));
+ }
+ (void) SH::getInt(buf); // Unused field
+ auto msg = std::make_unique<api::ApplyBucketDiffCommand>(bucket, nodes);
+ std::vector<api::ApplyBucketDiffCommand::Entry>& entries(msg->getDiff());
+ uint32_t entryCount = SH::getInt(buf);
+ if (entryCount > buf.getRemaining()) {
+ // Trigger out of bounds exception rather than out of memory error
+ buf.incPos(entryCount);
+ }
+ entries.resize(entryCount);
+ for (uint32_t i=0; i<entries.size(); ++i) {
+ onDecodeDiffEntry(buf, entries[i]._entry);
+ entries[i]._docName = SH::getString(buf);
+ uint32_t headerSize = SH::getInt(buf);
+ if (headerSize > buf.getRemaining()) {
+ buf.incPos(headerSize);
+ }
+ entries[i]._headerBlob.resize(headerSize);
+ buf.getBytes(&entries[i]._headerBlob[0], entries[i]._headerBlob.size());
+ uint32_t bodySize = SH::getInt(buf);
+ if (bodySize > buf.getRemaining()) {
+ buf.incPos(bodySize);
+ }
+ entries[i]._bodyBlob.resize(bodySize);
+ buf.getBytes(&entries[i]._bodyBlob[0], entries[i]._bodyBlob.size());
+ }
+ onDecodeBucketInfoCommand(buf, *msg);
+ return msg;
+}
+
+void
+ProtocolSerialization4_2::onEncode(GBBuf& buf, const api::RequestBucketInfoReply& msg) const
+{
+ buf.putInt(msg.getBucketInfo().size());
+ for (const auto & entry : msg.getBucketInfo()) {
+ buf.putLong(entry._bucketId.getRawId());
+ putBucketInfo(entry._info, buf);
+ }
+ onEncodeReply(buf, msg);
+}
+
+api::StorageReply::UP
+ProtocolSerialization4_2::onDecodeRequestBucketInfoReply(const SCmd& cmd, BBuf& buf) const
+{
+ auto msg = std::make_unique<api::RequestBucketInfoReply>(static_cast<const api::RequestBucketInfoCommand&>(cmd));
+ api::RequestBucketInfoReply::EntryVector & entries(msg->getBucketInfo());
+ uint32_t entryCount = SH::getInt(buf);
+ if (entryCount > buf.getRemaining()) {
+ // Trigger out of bounds exception rather than out of memory error
+ buf.incPos(entryCount);
+ }
+ entries.resize(entryCount);
+ for (auto & entry : entries) {
+ entry._bucketId = document::BucketId(SH::getLong(buf));
+ entry._info = getBucketInfo(buf);
+ }
+ onDecodeReply(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization4_2::onEncode(GBBuf& buf, const api::NotifyBucketChangeCommand& msg) const
+{
+ putBucket(msg.getBucket(), buf);
+ putBucketInfo(msg.getBucketInfo(), buf);
+ onEncodeCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization4_2::onDecodeNotifyBucketChangeCommand(BBuf& buf) const
+{
+ document::Bucket bucket = getBucket(buf);
+ api::BucketInfo info(getBucketInfo(buf));
+ auto msg = std::make_unique<api::NotifyBucketChangeCommand>(bucket, info);
+ onDecodeCommand(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization4_2::onEncode(GBBuf& buf, const api::NotifyBucketChangeReply& msg) const
+{
+ onEncodeReply(buf, msg);
+}
+
+api::StorageReply::UP
+ProtocolSerialization4_2::onDecodeNotifyBucketChangeReply(const SCmd& cmd,BBuf& buf) const
+{
+ auto msg = std::make_unique<api::NotifyBucketChangeReply>(static_cast<const api::NotifyBucketChangeCommand&>(cmd));
+ onDecodeReply(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization4_2::onEncode(GBBuf& buf, const api::SplitBucketCommand& msg) const
+{
+ putBucket(msg.getBucket(), buf);
+ buf.putByte(msg.getMinSplitBits());
+ buf.putByte(msg.getMaxSplitBits());
+ buf.putInt(msg.getMinByteSize());
+ buf.putInt(msg.getMinDocCount());
+ onEncodeCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization4_2::onDecodeSplitBucketCommand(BBuf& buf) const
+{
+ document::Bucket bucket = getBucket(buf);
+ auto msg = std::make_unique<api::SplitBucketCommand>(bucket);
+ msg->setMinSplitBits(SH::getByte(buf));
+ msg->setMaxSplitBits(SH::getByte(buf));
+ msg->setMinByteSize(SH::getInt(buf));
+ msg->setMinDocCount(SH::getInt(buf));
+ onDecodeCommand(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization4_2::onEncode(GBBuf&, const api::SetBucketStateCommand&) const
+{
+ throw vespalib::IllegalStateException("Unsupported serialization", VESPA_STRLOC);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization4_2::onDecodeSetBucketStateCommand(BBuf&) const
+{
+ throw vespalib::IllegalStateException("Unsupported deserialization", VESPA_STRLOC);
+}
+
+void ProtocolSerialization4_2::onEncode(GBBuf&, const api::SetBucketStateReply&) const
+{
+ throw vespalib::IllegalStateException("Unsupported serialization", VESPA_STRLOC);
+}
+
+api::StorageReply::UP
+ProtocolSerialization4_2::onDecodeSetBucketStateReply(const SCmd&, BBuf&) const
+{
+ throw vespalib::IllegalStateException("Unsupported deserialization", VESPA_STRLOC);
+}
+
+void
+ProtocolSerialization4_2::onEncode(GBBuf& buf, const api::CreateVisitorCommand& msg) const
+{
+ putBucketSpace(msg.getBucketSpace(), buf);
+ buf.putString(msg.getLibraryName());
+ buf.putString(msg.getInstanceId());
+ buf.putString(msg.getDocumentSelection());
+ buf.putInt(msg.getVisitorCmdId());
+ buf.putString(msg.getControlDestination());
+ buf.putString(msg.getDataDestination());
+ buf.putInt(msg.getMaximumPendingReplyCount());
+ buf.putLong(msg.getFromTime());
+ buf.putLong(msg.getToTime());
+
+ buf.putInt(msg.getBuckets().size());
+ for (uint32_t i = 0; i < msg.getBuckets().size(); i++) {
+ buf.putLong(msg.getBuckets()[i].getRawId());
+ }
+
+ buf.putBoolean(msg.visitRemoves());
+ buf.putBoolean(false);
+ buf.putBoolean(msg.visitInconsistentBuckets());
+ buf.putInt(vespalib::count_ms(msg.getQueueTimeout()));
+ msg.getParameters().serialize(buf);
+
+ onEncodeCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization4_2::onDecodeCreateVisitorCommand(BBuf& buf) const
+{
+ BucketSpace bucketSpace = getBucketSpace(buf);
+ vespalib::stringref libraryName = SH::getString(buf);
+ vespalib::stringref instanceId = SH::getString(buf);
+ vespalib::stringref selection = SH::getString(buf);
+ auto msg = std::make_unique<api::CreateVisitorCommand>(bucketSpace, libraryName, instanceId, selection);
+ msg->setVisitorCmdId(SH::getInt(buf));
+ msg->setControlDestination(SH::getString(buf));
+ msg->setDataDestination(SH::getString(buf));
+ msg->setMaximumPendingReplyCount(SH::getInt(buf));
+
+ msg->setFromTime(SH::getLong(buf));
+ msg->setToTime(SH::getLong(buf));
+ uint32_t count = SH::getInt(buf);
+
+ if (count > buf.getRemaining()) {
+ // Trigger out of bounds exception rather than out of memory error
+ buf.incPos(count);
+ }
+
+ for (uint32_t i = 0; i < count; i++) {
+ msg->getBuckets().push_back(document::BucketId(SH::getLong(buf)));
+ }
+
+ if (SH::getBoolean(buf)) {
+ msg->setVisitRemoves();
+ }
+ if (SH::getBoolean(buf)) {
+ msg->setFieldSet(AllFields::NAME);
+ }
+ if (SH::getBoolean(buf)) {
+ msg->setVisitInconsistentBuckets();
+ }
+ msg->setQueueTimeout(std::chrono::milliseconds(SH::getInt(buf)));
+ msg->getParameters().deserialize(buf);
+
+ onDecodeCommand(buf, *msg);
+ msg->setVisitorDispatcherVersion(42);
+ return msg;
+}
+
+void
+ProtocolSerialization4_2::onEncode(GBBuf& buf, const api::DestroyVisitorCommand& msg) const
+{
+ buf.putString(msg.getInstanceId());
+ onEncodeCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization4_2::onDecodeDestroyVisitorCommand(BBuf& buf) const
+{
+ vespalib::stringref instanceId = SH::getString(buf);
+ auto msg = std::make_unique<api::DestroyVisitorCommand>(instanceId);
+ onDecodeCommand(buf, *msg);
+ return msg;
+}
+
+void
+ProtocolSerialization4_2::onEncode(GBBuf& buf, const api::DestroyVisitorReply& msg) const
+{
+ onEncodeReply(buf, msg);
+}
+
+api::StorageReply::UP
+ProtocolSerialization4_2::onDecodeDestroyVisitorReply(const SCmd& cmd, BBuf& buf) const
+{
+ auto msg = std::make_unique<api::DestroyVisitorReply>(static_cast<const api::DestroyVisitorCommand&>(cmd));
+ onDecodeReply(buf, *msg);
+ return msg;
+}
+
+void
+ProtocolSerialization4_2::onEncode(GBBuf& buf, const api::RemoveLocationCommand& msg) const
+{
+ buf.putString(msg.getDocumentSelection());
+ putBucket(msg.getBucket(), buf);
+ onEncodeCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization4_2::onDecodeRemoveLocationCommand(BBuf& buf) const
+{
+ vespalib::stringref documentSelection = SH::getString(buf);
+ document::Bucket bucket = getBucket(buf);
+
+ auto msg = std::make_unique<api::RemoveLocationCommand>(documentSelection, bucket);
+ onDecodeCommand(buf, *msg);
+ return msg;
+}
+
+void
+ProtocolSerialization4_2::onEncode(GBBuf& buf, const api::RemoveLocationReply& msg) const
+{
+ onEncodeBucketInfoReply(buf, msg);
+}
+
+api::StorageReply::UP
+ProtocolSerialization4_2::onDecodeRemoveLocationReply(const SCmd& cmd, BBuf& buf) const
+{
+ auto msg = std::make_unique<api::RemoveLocationReply>(static_cast<const api::RemoveLocationCommand&>(cmd));
+ onDecodeBucketInfoReply(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization4_2::onEncode(GBBuf&, const api::StatBucketCommand&) const {
+ throw vespalib::IllegalStateException("StatBucketCommand not expected for legacy protocol version", VESPA_STRLOC);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization4_2::onDecodeStatBucketCommand(BBuf&) const {
+ throw vespalib::IllegalStateException("StatBucketCommand not expected for legacy protocol version", VESPA_STRLOC);
+}
+
+void ProtocolSerialization4_2::onEncode(GBBuf&, const api::StatBucketReply&) const {
+ throw vespalib::IllegalStateException("StatBucketReply not expected for legacy protocol version", VESPA_STRLOC);
+}
+
+api::StorageReply::UP
+ProtocolSerialization4_2::onDecodeStatBucketReply(const SCmd&, BBuf&) const {
+ throw vespalib::IllegalStateException("StatBucketReply not expected for legacy protocol version", VESPA_STRLOC);
+}
+
+// Utility functions for serialization
+
+void
+ProtocolSerialization4_2::onEncodeBucketInfoCommand(GBBuf& buf, const api::BucketInfoCommand& msg) const
+{
+ onEncodeCommand(buf, msg);
+}
+
+void
+ProtocolSerialization4_2::onDecodeBucketInfoCommand(BBuf& buf, api::BucketInfoCommand& msg) const
+{
+ onDecodeCommand(buf, msg);
+}
+
+void
+ProtocolSerialization4_2::onEncode(GBBuf& buf, const api::ReturnCode& rc) const
+{
+ // Convert error code to codes used in 4.2
+ buf.putInt(getOldErrorCode(rc.getResult()));
+ buf.putString(rc.getMessage());
+}
+
+void
+ProtocolSerialization4_2::onEncodeDiffEntry(GBBuf& buf, const api::GetBucketDiffCommand::Entry& entry) const
+{
+ buf.putLong(entry._timestamp);
+ SH::putGlobalId(entry._gid, buf);
+ buf.putInt(entry._headerSize);
+ buf.putInt(entry._bodySize);
+ buf.putShort(entry._flags);
+ buf.putShort(entry._hasMask);
+}
+
+void
+ProtocolSerialization4_2::onDecodeDiffEntry(BBuf& buf, api::GetBucketDiffCommand::Entry& entry) const
+{
+ entry._timestamp = SH::getLong(buf);
+ entry._gid = SH::getGlobalId(buf);
+ entry._headerSize = SH::getInt(buf);
+ entry._bodySize = SH::getInt(buf);
+ entry._flags = SH::getShort(buf);
+ entry._hasMask = SH::getShort(buf);
+}
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/protocolserialization4_2.h b/storage/src/vespa/storageapi/mbusprot/protocolserialization4_2.h
new file mode 100644
index 00000000000..b8a20e9e401
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protocolserialization4_2.h
@@ -0,0 +1,72 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "legacyprotocolserialization.h"
+
+namespace storage::mbusprot {
+
+class ProtocolSerialization4_2 : public LegacyProtocolSerialization {
+public:
+ explicit ProtocolSerialization4_2(const std::shared_ptr<const document::DocumentTypeRepo>&);
+
+protected:
+ void onEncode(GBBuf&, const api::GetCommand&) const override;
+ void onEncode(GBBuf&, const api::RemoveCommand&) const override;
+ void onEncode(GBBuf&, const api::RevertCommand&) const override;
+ void onEncode(GBBuf&, const api::CreateBucketCommand&) const override;
+ void onEncode(GBBuf&, const api::MergeBucketCommand&) const override;
+ void onEncode(GBBuf&, const api::GetBucketDiffCommand&) const override;
+ void onEncode(GBBuf&, const api::ApplyBucketDiffCommand&) const override;
+ void onEncode(GBBuf&, const api::RequestBucketInfoReply&) const override;
+ void onEncode(GBBuf&, const api::NotifyBucketChangeCommand&) const override;
+ void onEncode(GBBuf&, const api::NotifyBucketChangeReply&) const override;
+ void onEncode(GBBuf&, const api::SplitBucketCommand&) const override;
+ void onEncode(GBBuf&, const api::CreateVisitorCommand&) const override;
+ void onEncode(GBBuf&, const api::DestroyVisitorCommand&) const override;
+ void onEncode(GBBuf&, const api::DestroyVisitorReply&) const override;
+ void onEncode(GBBuf&, const api::RemoveLocationCommand&) const override;
+ void onEncode(GBBuf&, const api::RemoveLocationReply&) const override;
+ void onEncode(GBBuf&, const api::StatBucketCommand&) const override;
+ void onEncode(GBBuf&, const api::StatBucketReply&) const override;
+
+ // Not supported on 4.2, but implemented here for simplicity.
+ void onEncode(GBBuf&, const api::SetBucketStateCommand&) const override;
+ void onEncode(GBBuf&, const api::SetBucketStateReply&) const override;
+
+ virtual void onEncodeBucketInfoCommand(GBBuf&, const api::BucketInfoCommand&) const;
+ virtual void onEncodeBucketInfoReply(GBBuf&, const api::BucketInfoReply&) const = 0;
+ virtual void onEncodeCommand(GBBuf&, const api::StorageCommand&) const = 0;
+ virtual void onEncodeReply(GBBuf&, const api::StorageReply&) const = 0;
+
+ virtual void onEncodeDiffEntry(GBBuf&, const api::GetBucketDiffCommand::Entry&) const;
+ virtual void onEncode(GBBuf&, const api::ReturnCode&) const;
+ SCmd::UP onDecodeGetCommand(BBuf&) const override;
+ SCmd::UP onDecodeRemoveCommand(BBuf&) const override;
+ SCmd::UP onDecodeRevertCommand(BBuf&) const override;
+ SCmd::UP onDecodeCreateBucketCommand(BBuf&) const override;
+ SCmd::UP onDecodeMergeBucketCommand(BBuf&) const override;
+ SCmd::UP onDecodeGetBucketDiffCommand(BBuf&) const override;
+ SCmd::UP onDecodeApplyBucketDiffCommand(BBuf&) const override;
+ SRep::UP onDecodeRequestBucketInfoReply(const SCmd&, BBuf&) const override;
+ SCmd::UP onDecodeNotifyBucketChangeCommand(BBuf&) const override;
+ SRep::UP onDecodeNotifyBucketChangeReply(const SCmd&, BBuf&) const override;
+ SCmd::UP onDecodeSplitBucketCommand(BBuf&) const override;
+ SCmd::UP onDecodeSetBucketStateCommand(BBuf&) const override;
+ SRep::UP onDecodeSetBucketStateReply(const SCmd&, BBuf&) const override;
+ SCmd::UP onDecodeCreateVisitorCommand(BBuf&) const override;
+ SCmd::UP onDecodeDestroyVisitorCommand(BBuf&) const override;
+ SRep::UP onDecodeDestroyVisitorReply(const SCmd&, BBuf&) const override;
+ SCmd::UP onDecodeRemoveLocationCommand(BBuf&) const override;
+ SRep::UP onDecodeRemoveLocationReply(const SCmd&, BBuf&) const override;
+ SCmd::UP onDecodeStatBucketCommand(BBuf&) const override;
+ SRep::UP onDecodeStatBucketReply(const SCmd&, BBuf&) const override;
+
+ virtual void onDecodeBucketInfoCommand(BBuf&, api::BucketInfoCommand&) const;
+ virtual void onDecodeBucketInfoReply(BBuf&, api::BucketInfoReply&) const = 0;
+ virtual void onDecodeCommand(BBuf& buf, api::StorageCommand& msg) const = 0;
+ virtual void onDecodeReply(BBuf&, api::StorageReply&) const = 0;
+
+ virtual void onDecodeDiffEntry(BBuf&, api::GetBucketDiffCommand::Entry&) const;
+};
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/protocolserialization5_0.cpp b/storage/src/vespa/storageapi/mbusprot/protocolserialization5_0.cpp
new file mode 100644
index 00000000000..fa400b565b2
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protocolserialization5_0.cpp
@@ -0,0 +1,624 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "protocolserialization5_0.h"
+#include "serializationhelper.h"
+#include "storagecommand.h"
+#include "storagereply.h"
+#include <vespa/storageapi/message/bucketsplitting.h>
+#include <vespa/storageapi/message/visitor.h>
+#include <vespa/vdslib/state/clusterstate.h>
+#include <vespa/document/update/documentupdate.h>
+#include <vespa/document/bucket/fixed_bucket_spaces.h>
+#include <sstream>
+
+using document::BucketSpace;
+using document::FixedBucketSpaces;
+
+namespace storage::mbusprot {
+
+document::Bucket
+ProtocolSerialization5_0::getBucket(document::ByteBuffer& buf) const
+{
+ document::BucketId bucketId(SH::getLong(buf));
+ return document::Bucket(FixedBucketSpaces::default_space(), bucketId);
+}
+
+void
+ProtocolSerialization5_0::putBucket(const document::Bucket& bucket, vespalib::GrowableByteBuffer& buf) const
+{
+ buf.putLong(bucket.getBucketId().getRawId());
+ if (bucket.getBucketSpace() != FixedBucketSpaces::default_space()) {
+ std::ostringstream ost;
+ ost << "Bucket with bucket space " << bucket.getBucketSpace() << " cannot be serialized on old storageapi protocol.";
+ throw vespalib::IllegalArgumentException(ost.str(), VESPA_STRLOC);
+ }
+}
+
+document::BucketSpace
+ProtocolSerialization5_0::getBucketSpace(document::ByteBuffer&) const
+{
+ return FixedBucketSpaces::default_space();
+}
+
+void
+ProtocolSerialization5_0::putBucketSpace(document::BucketSpace bucketSpace, vespalib::GrowableByteBuffer&) const
+{
+ if (bucketSpace != FixedBucketSpaces::default_space()) {
+ std::ostringstream ost;
+ ost << "Bucket space " << bucketSpace << " cannot be serialized on old storageapi protocol.";
+ throw vespalib::IllegalArgumentException(ost.str(), VESPA_STRLOC);
+ }
+}
+
+api::BucketInfo
+ProtocolSerialization5_0::getBucketInfo(document::ByteBuffer& buf) const
+{
+ uint32_t crc(SH::getInt(buf));
+ uint32_t doccount(SH::getInt(buf));
+ uint32_t docsize(SH::getInt(buf));
+ uint32_t metacount(SH::getInt(buf));
+ uint32_t usedsize(SH::getInt(buf));
+ return api::BucketInfo(crc, doccount, docsize, metacount, usedsize);
+}
+
+void
+ProtocolSerialization5_0::putBucketInfo(const api::BucketInfo& info, vespalib::GrowableByteBuffer& buf) const
+{
+ buf.putInt(info.getChecksum());
+ buf.putInt(info.getDocumentCount());
+ buf.putInt(info.getTotalDocumentSize());
+ buf.putInt(info.getMetaCount());
+ buf.putInt(info.getUsedFileSize());
+}
+
+void
+ProtocolSerialization5_0::onEncodeReply(GBBuf& buf, const api::StorageReply& msg) const
+{
+ SH::putReturnCode(msg.getResult(), buf);
+ buf.putLong(msg.getMsgId());
+ buf.putByte(msg.getPriority());
+}
+
+void
+ProtocolSerialization5_0::onDecodeReply(BBuf& buf, api::StorageReply& msg) const
+{
+ msg.setResult(SH::getReturnCode(buf));
+ msg.forceMsgId(SH::getLong(buf));
+ msg.setPriority(SH::getByte(buf));
+}
+
+void
+ProtocolSerialization5_0::onEncodeCommand(GBBuf& buf, const api::StorageCommand& msg) const
+{
+ buf.putLong(msg.getMsgId());
+ buf.putByte(msg.getPriority());
+ buf.putShort(msg.getSourceIndex());
+ buf.putInt(0); // LoadType 'default'
+}
+
+void
+ProtocolSerialization5_0::onDecodeCommand(BBuf& buf, api::StorageCommand& msg) const
+{
+ msg.forceMsgId(SH::getLong(buf));
+ uint8_t priority = SH::getByte(buf);
+ msg.setPriority(priority);
+ msg.setSourceIndex(SH::getShort(buf));
+ (void)SH::getInt(buf); // LoadType
+}
+
+
+ProtocolSerialization5_0::ProtocolSerialization5_0(const std::shared_ptr<const document::DocumentTypeRepo>& repo)
+ : ProtocolSerialization4_2(repo)
+{
+}
+
+void ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::PutReply& msg) const
+{
+ buf.putBoolean(msg.wasFound());
+ onEncodeBucketInfoReply(buf, msg);
+}
+
+void ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::PutCommand& msg) const
+{
+ SH::putDocument(msg.getDocument().get(), buf);
+ putBucket(msg.getBucket(), buf);
+ buf.putLong(msg.getTimestamp());
+ buf.putLong(msg.getUpdateTimestamp());
+ onEncodeBucketInfoCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization5_0::onDecodePutCommand(BBuf& buf) const
+{
+ document::Document::SP doc(SH::getDocument(buf, getTypeRepo()));
+ document::Bucket bucket = getBucket(buf);
+ api::Timestamp ts(SH::getLong(buf));
+ auto msg = std::make_unique<api::PutCommand>(bucket, doc, ts);
+ msg->setUpdateTimestamp(SH::getLong(buf));
+ onDecodeBucketInfoCommand(buf, *msg);
+ return msg;
+}
+
+api::StorageReply::UP
+ProtocolSerialization5_0::onDecodePutReply(const SCmd& cmd, BBuf& buf) const
+{
+ bool wasFound = SH::getBoolean(buf);
+ auto msg = std::make_unique<api::PutReply>(static_cast<const api::PutCommand&>(cmd), wasFound);
+ onDecodeBucketInfoReply(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::UpdateReply& msg) const
+{
+ buf.putLong(msg.getOldTimestamp());
+ onEncodeBucketInfoReply(buf, msg);
+}
+
+api::StorageReply::UP
+ProtocolSerialization5_0::onDecodeUpdateReply(const SCmd& cmd, BBuf& buf) const
+{
+ api::Timestamp oldTimestamp(SH::getLong(buf));
+ auto msg = std::make_unique<api::UpdateReply>(static_cast<const api::UpdateCommand&>(cmd), oldTimestamp);
+ onDecodeBucketInfoReply(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::GetReply& msg) const
+{
+ SH::putDocument(msg.getDocument().get(), buf);
+ // Old protocol version doesn't understand tombstones. Make it appear as Not Found.
+ buf.putLong(msg.is_tombstone() ? api::Timestamp(0) : msg.getLastModifiedTimestamp());
+ onEncodeBucketInfoReply(buf, msg);
+}
+
+api::StorageReply::UP
+ProtocolSerialization5_0::onDecodeGetReply(const SCmd& cmd, BBuf& buf) const
+{
+ try {
+ document::Document::SP doc(SH::getDocument(buf, getTypeRepo()));
+ api::Timestamp lastModified(SH::getLong(buf));
+ auto msg = std::make_unique<api::GetReply>(static_cast<const api::GetCommand&>(cmd), doc,lastModified);
+ onDecodeBucketInfoReply(buf, *msg);
+ return msg;
+ } catch (std::exception& e) {
+ auto msg = std::make_unique<api::GetReply>(static_cast<const api::GetCommand&>(cmd), document::Document::SP(),0);
+ msg->setResult(api::ReturnCode(api::ReturnCode::UNPARSEABLE, e.what()));
+ return msg;
+ }
+}
+
+void ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::RemoveReply& msg) const
+{
+ buf.putLong(msg.getOldTimestamp());
+ onEncodeBucketInfoReply(buf, msg);
+}
+
+api::StorageReply::UP
+ProtocolSerialization5_0::onDecodeRemoveReply(const SCmd& cmd, BBuf& buf) const
+{
+ api::Timestamp oldTimestamp(SH::getLong(buf));
+ auto msg = std::make_unique<api::RemoveReply>(static_cast<const api::RemoveCommand&>(cmd), oldTimestamp);
+ onDecodeBucketInfoReply(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::UpdateCommand& msg) const
+{
+ document::DocumentUpdate* update = msg.getUpdate().get();
+ if (update) {
+ vespalib::nbostream stream;
+ update->serializeHEAD(stream);
+ buf.putInt(stream.size());
+ buf.putBytes(stream.peek(), stream.size());
+ } else {
+ buf.putInt(0);
+ }
+
+ putBucket(msg.getBucket(), buf);
+ buf.putLong(msg.getTimestamp());
+ buf.putLong(msg.getOldTimestamp());
+ onEncodeBucketInfoCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization5_0::onDecodeUpdateCommand(BBuf& buf) const
+{
+ document::DocumentUpdate::SP update;
+
+ uint32_t size = SH::getInt(buf);
+ if (size != 0) {
+ update = document::DocumentUpdate::createHEAD(getTypeRepo(), vespalib::nbostream(buf.getBufferAtPos(), size));
+ buf.incPos(size);
+ }
+
+ document::Bucket bucket = getBucket(buf);
+ api::Timestamp timestamp(SH::getLong(buf));
+ api::UpdateCommand::UP msg = std::make_unique<api::UpdateCommand>(bucket, update, timestamp);
+ msg->setOldTimestamp(SH::getLong(buf));
+ onDecodeBucketInfoCommand(buf, *msg);
+ return msg;
+}
+
+
+void ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::RevertReply& msg) const
+{
+ onEncodeBucketInfoReply(buf, msg);
+}
+
+api::StorageReply::UP
+ProtocolSerialization5_0::onDecodeRevertReply(const SCmd& cmd, BBuf& buf) const
+{
+ auto msg = std::make_unique<api::RevertReply>(static_cast<const api::RevertCommand&>(cmd));
+ onDecodeBucketInfoReply(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::CreateBucketReply& msg) const
+{
+ onEncodeBucketInfoReply(buf, msg);
+}
+
+api::StorageReply::UP
+ProtocolSerialization5_0::onDecodeCreateBucketReply(const SCmd& cmd, BBuf& buf) const
+{
+ auto msg = std::make_unique<api::CreateBucketReply>(static_cast<const api::CreateBucketCommand&>(cmd));
+ onDecodeBucketInfoReply(buf, *msg);
+ return msg;
+}
+
+void
+ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::DeleteBucketCommand& msg) const
+{
+ putBucket(msg.getBucket(), buf);
+ onEncodeBucketInfoCommand(buf, msg);
+ putBucketInfo(msg.getBucketInfo(), buf);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization5_0::onDecodeDeleteBucketCommand(BBuf& buf) const
+{
+ document::Bucket bucket = getBucket(buf);
+ auto msg = std::make_unique<api::DeleteBucketCommand>(bucket);
+ onDecodeBucketInfoCommand(buf, *msg);
+ if (buf.getRemaining() >= SH::BUCKET_INFO_SERIALIZED_SIZE) {
+ msg->setBucketInfo(getBucketInfo(buf));
+ }
+ return msg;
+}
+
+void ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::DeleteBucketReply& msg) const
+{
+ onEncodeBucketInfoReply(buf, msg);
+}
+
+api::StorageReply::UP
+ProtocolSerialization5_0::onDecodeDeleteBucketReply(const SCmd& cmd, BBuf& buf) const
+{
+ auto msg = std::make_unique<api::DeleteBucketReply>(static_cast<const api::DeleteBucketCommand&>(cmd));
+ onDecodeBucketInfoReply(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::MergeBucketCommand& msg) const
+{
+ ProtocolSerialization4_2::onEncode(buf, msg);
+
+ buf.putInt(msg.getClusterStateVersion());
+ const std::vector<uint16_t>& chain(msg.getChain());
+ buf.putShort(chain.size());
+ for (std::size_t i = 0; i < chain.size(); ++i) {
+ buf.putShort(chain[i]);
+ }
+}
+
+api::StorageCommand::UP
+ProtocolSerialization5_0::onDecodeMergeBucketCommand(BBuf& buf) const
+{
+ api::StorageCommand::UP cmd = ProtocolSerialization4_2::onDecodeMergeBucketCommand(buf);
+ uint32_t clusterStateVersion = SH::getInt(buf);
+ uint16_t chainSize = SH::getShort(buf);
+ std::vector<uint16_t> chain;
+ chain.reserve(chainSize);
+ for (std::size_t i = 0; i < chainSize; ++i) {
+ uint16_t index = SH::getShort(buf);
+ chain.push_back(index);
+ }
+ api::MergeBucketCommand& mergeCmd = static_cast<api::MergeBucketCommand&>(*cmd);
+ mergeCmd.setChain(chain);
+ mergeCmd.setClusterStateVersion(clusterStateVersion);
+ return cmd;
+}
+
+void ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::MergeBucketReply& msg) const
+{
+ onEncodeBucketReply(buf, msg);
+}
+
+api::StorageReply::UP
+ProtocolSerialization5_0::onDecodeMergeBucketReply(const SCmd& cmd, BBuf& buf) const
+{
+ auto msg = std::make_unique<api::MergeBucketReply>(static_cast<const api::MergeBucketCommand&>(cmd));
+ onDecodeBucketReply(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::GetBucketDiffReply& msg) const
+{
+ const std::vector<api::GetBucketDiffCommand::Entry>& entries(msg.getDiff());
+ buf.putInt(entries.size());
+ for (uint32_t i=0; i<entries.size(); ++i) {
+ onEncodeDiffEntry(buf, entries[i]);
+ }
+ onEncodeBucketReply(buf, msg);
+}
+
+api::StorageReply::UP
+ProtocolSerialization5_0::onDecodeGetBucketDiffReply(const SCmd& cmd, BBuf& buf) const
+{
+ auto msg = std::make_unique<api::GetBucketDiffReply>(static_cast<const api::GetBucketDiffCommand&>(cmd));
+ std::vector<api::GetBucketDiffCommand::Entry>& entries(msg->getDiff());
+ uint32_t entryCount = SH::getInt(buf);
+ if (entryCount > buf.getRemaining()) {
+ // Trigger out of bounds exception rather than out of memory error
+ buf.incPos(entryCount);
+ }
+ entries.resize(entryCount);
+ for (uint32_t i=0; i<entries.size(); ++i) {
+ onDecodeDiffEntry(buf, entries[i]);
+ }
+ onDecodeBucketReply(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::ApplyBucketDiffReply& msg) const
+{
+ const std::vector<api::ApplyBucketDiffCommand::Entry>& entries(msg.getDiff());
+ buf.putInt(entries.size());
+ for (uint32_t i=0; i<entries.size(); ++i) {
+ onEncodeDiffEntry(buf, entries[i]._entry);
+ buf.putString(entries[i]._docName);
+ buf.putInt(entries[i]._headerBlob.size());
+ buf.putBytes(&entries[i]._headerBlob[0], entries[i]._headerBlob.size());
+ buf.putInt(entries[i]._bodyBlob.size());
+ buf.putBytes(&entries[i]._bodyBlob[0], entries[i]._bodyBlob.size());
+ }
+ onEncodeBucketInfoReply(buf, msg);
+}
+
+api::StorageReply::UP
+ProtocolSerialization5_0::onDecodeApplyBucketDiffReply(const SCmd& cmd,
+ BBuf& buf) const
+{
+ auto msg = std::make_unique<api::ApplyBucketDiffReply>(static_cast<const api::ApplyBucketDiffCommand&>(cmd));
+ std::vector<api::ApplyBucketDiffCommand::Entry>& entries(msg->getDiff());
+ uint32_t entryCount = SH::getInt(buf);
+ if (entryCount > buf.getRemaining()) {
+ // Trigger out of bounds exception rather than out of memory error
+ buf.incPos(entryCount);
+ }
+ entries.resize(entryCount);
+ for (uint32_t i=0; i<entries.size(); ++i) {
+ onDecodeDiffEntry(buf, entries[i]._entry);
+ entries[i]._docName = SH::getString(buf);
+ uint32_t headerSize = SH::getInt(buf);
+ if (headerSize > buf.getRemaining()) {
+ buf.incPos(headerSize);
+ }
+ entries[i]._headerBlob.resize(headerSize);
+ buf.getBytes(&entries[i]._headerBlob[0], entries[i]._headerBlob.size());
+ uint32_t bodySize = SH::getInt(buf);
+ if (bodySize > buf.getRemaining()) {
+ buf.incPos(bodySize);
+ }
+ entries[i]._bodyBlob.resize(bodySize);
+ buf.getBytes(&entries[i]._bodyBlob[0], entries[i]._bodyBlob.size());
+ }
+ onDecodeBucketInfoReply(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::SplitBucketReply& msg) const
+{
+ const std::vector<api::SplitBucketReply::Entry>& entries(msg.getSplitInfo());
+ buf.putInt(entries.size());
+ for (std::vector<api::SplitBucketReply::Entry>::const_iterator it
+ = entries.begin(); it != entries.end(); ++it)
+ {
+ buf.putLong(it->first.getRawId());
+ putBucketInfo(it->second, buf);
+ }
+ onEncodeBucketReply(buf, msg);
+}
+
+api::StorageReply::UP
+ProtocolSerialization5_0::onDecodeSplitBucketReply(const SCmd& cmd, BBuf& buf) const
+{
+ auto msg = std::make_unique<api::SplitBucketReply>(static_cast<const api::SplitBucketCommand&>(cmd));
+ std::vector<api::SplitBucketReply::Entry>& entries(msg->getSplitInfo());
+ uint32_t targetCount = SH::getInt(buf);
+ if (targetCount > buf.getRemaining()) {
+ // Trigger out of bounds exception rather than out of memory error
+ buf.incPos(targetCount);
+ }
+ entries.resize(targetCount);
+ for (std::vector<api::SplitBucketReply::Entry>::iterator it
+ = entries.begin(); it != entries.end(); ++it)
+ {
+ it->first = document::BucketId(SH::getLong(buf));
+ it->second = getBucketInfo(buf);
+ }
+ onDecodeBucketReply(buf, *msg);
+ return msg;
+}
+
+void
+ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::JoinBucketsCommand& msg) const
+{
+ putBucket(msg.getBucket(), buf);
+ buf.putInt(msg.getSourceBuckets().size());
+ for (uint32_t i=0, n=msg.getSourceBuckets().size(); i<n; ++i) {
+ buf.putLong(msg.getSourceBuckets()[i].getRawId());
+ }
+ buf.putByte(msg.getMinJoinBits());
+ onEncodeCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization5_0::onDecodeJoinBucketsCommand(BBuf& buf) const
+{
+ document::Bucket bucket = getBucket(buf);
+ auto msg = std::make_unique<api::JoinBucketsCommand>(bucket);
+ uint32_t size = SH::getInt(buf);
+ if (size > buf.getRemaining()) {
+ // Trigger out of bounds exception rather than out of memory error
+ buf.incPos(size);
+ }
+ std::vector<document::BucketId>& entries(msg->getSourceBuckets());
+ for (uint32_t i=0; i<size; ++i) {
+ entries.push_back(document::BucketId(SH::getLong(buf)));
+ }
+ msg->setMinJoinBits(SH::getByte(buf));
+ onDecodeCommand(buf, *msg);
+ return msg;
+}
+
+void
+ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::JoinBucketsReply& msg) const
+{
+ putBucketInfo(msg.getBucketInfo(), buf);
+ onEncodeBucketReply(buf, msg);
+}
+
+api::StorageReply::UP
+ProtocolSerialization5_0::onDecodeJoinBucketsReply(const SCmd& cmd, BBuf& buf) const
+{
+ auto msg = std::make_unique<api::JoinBucketsReply>(static_cast<const api::JoinBucketsCommand&>(cmd));
+ msg->setBucketInfo(getBucketInfo(buf));
+ onDecodeBucketReply(buf, *msg);
+ return msg;
+}
+
+void
+ProtocolSerialization5_0::onEncodeBucketInfoReply(GBBuf& buf, const api::BucketInfoReply& msg) const
+{
+ onEncodeBucketReply(buf, msg);
+ putBucketInfo(msg.getBucketInfo(), buf);
+}
+
+void
+ProtocolSerialization5_0::onDecodeBucketInfoReply(BBuf& buf, api::BucketInfoReply& msg) const
+{
+ onDecodeBucketReply(buf, msg);
+ msg.setBucketInfo(getBucketInfo(buf));
+}
+
+void
+ProtocolSerialization5_0::onEncodeBucketReply(GBBuf& buf, const api::BucketReply& msg) const
+{
+ onEncodeReply(buf, msg);
+ buf.putLong(msg.hasBeenRemapped() ? msg.getBucketId().getRawId() : 0);
+}
+
+void
+ProtocolSerialization5_0::onDecodeBucketReply(BBuf& buf, api::BucketReply& msg) const
+{
+ onDecodeReply(buf, msg);
+ document::BucketId bucket(SH::getLong(buf));
+ if (bucket.getRawId() != 0) {
+ msg.remapBucketId(bucket);
+ }
+}
+
+void
+ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::CreateVisitorReply& msg) const
+{
+ onEncodeReply(buf, msg);
+ buf.putInt(msg.getVisitorStatistics().getBucketsVisited());
+ buf.putLong(msg.getVisitorStatistics().getDocumentsVisited());
+ buf.putLong(msg.getVisitorStatistics().getBytesVisited());
+ buf.putLong(msg.getVisitorStatistics().getDocumentsReturned());
+ buf.putLong(msg.getVisitorStatistics().getBytesReturned());
+ // TODO remove second pass concept on Vespa 8
+ buf.putLong(msg.getVisitorStatistics().getSecondPassDocumentsReturned());
+ buf.putLong(msg.getVisitorStatistics().getSecondPassBytesReturned());
+}
+
+api::StorageReply::UP
+ProtocolSerialization5_0::onDecodeCreateVisitorReply(const SCmd& cmd, BBuf& buf) const
+{
+ auto msg = std::make_unique<api::CreateVisitorReply>(static_cast<const api::CreateVisitorCommand&>(cmd));
+ onDecodeReply(buf, *msg);
+
+ vdslib::VisitorStatistics vs;
+ vs.setBucketsVisited(SH::getInt(buf));
+ vs.setDocumentsVisited(SH::getLong(buf));
+ vs.setBytesVisited(SH::getLong(buf));
+ vs.setDocumentsReturned(SH::getLong(buf));
+ vs.setBytesReturned(SH::getLong(buf));
+ // TODO remove second pass concept on Vespa 8
+ vs.setSecondPassDocumentsReturned(SH::getLong(buf));
+ vs.setSecondPassBytesReturned(SH::getLong(buf));
+ msg->setVisitorStatistics(vs);
+
+ return msg;
+}
+
+void ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::RequestBucketInfoCommand& msg) const
+{
+ const std::vector<document::BucketId>& buckets(msg.getBuckets());
+ buf.putInt(buckets.size());
+ for (uint32_t i=0; i<buckets.size(); ++i) {
+ buf.putLong(buckets[i].getRawId());
+ }
+ putBucketSpace(msg.getBucketSpace(), buf);
+ if (buckets.size() == 0) {
+ buf.putShort(msg.getDistributor());
+ buf.putString(msg.getSystemState().toString());
+ buf.putString(msg.getDistributionHash());
+ }
+
+ onEncodeCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization5_0::onDecodeRequestBucketInfoCommand(BBuf& buf) const
+{
+ std::vector<document::BucketId> buckets(SH::getInt(buf));
+ for (uint32_t i=0; i<buckets.size(); ++i) {
+ buckets[i] = document::BucketId(SH::getLong(buf));
+ }
+ api::RequestBucketInfoCommand::UP msg;
+ BucketSpace bucketSpace = getBucketSpace(buf);
+ if (buckets.size() != 0) {
+ msg.reset(new api::RequestBucketInfoCommand(bucketSpace, buckets));
+ } else {
+ int distributor = SH::getShort(buf);
+ lib::ClusterState state(SH::getString(buf));
+ msg.reset(new api::RequestBucketInfoCommand(bucketSpace, distributor, state, SH::getString(buf)));
+ }
+ onDecodeCommand(buf, *msg);
+ return msg;
+}
+
+void
+ProtocolSerialization5_0::onEncode(GBBuf& buf, const api::CreateVisitorCommand& cmd) const
+{
+ ProtocolSerialization4_2::onEncode(buf, cmd);
+
+ buf.putInt(0); // Unused
+ buf.putInt(cmd.getMaxBucketsPerVisitor());
+}
+
+api::StorageCommand::UP
+ProtocolSerialization5_0::onDecodeCreateVisitorCommand(BBuf& buf) const
+{
+ api::StorageCommand::UP cvc = ProtocolSerialization4_2::onDecodeCreateVisitorCommand(buf);
+ SH::getInt(buf); // Unused
+
+ static_cast<api::CreateVisitorCommand*>(cvc.get())->setMaxBucketsPerVisitor(SH::getInt(buf));
+
+ static_cast<api::CreateVisitorCommand*>(cvc.get())->setVisitorDispatcherVersion(50);
+ return cvc;
+}
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/protocolserialization5_0.h b/storage/src/vespa/storageapi/mbusprot/protocolserialization5_0.h
new file mode 100644
index 00000000000..8de2edab17f
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protocolserialization5_0.h
@@ -0,0 +1,73 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "protocolserialization4_2.h"
+
+namespace storage::mbusprot {
+
+class ProtocolSerialization5_0 : public ProtocolSerialization4_2 {
+public:
+ ProtocolSerialization5_0(const std::shared_ptr<const document::DocumentTypeRepo>&);
+
+ document::Bucket getBucket(document::ByteBuffer& buf) const override;
+ void putBucket(const document::Bucket& bucket, vespalib::GrowableByteBuffer& buf) const override;
+ document::BucketSpace getBucketSpace(document::ByteBuffer& buf) const override;
+ void putBucketSpace(document::BucketSpace bucketSpace, vespalib::GrowableByteBuffer& buf) const override;
+ api::BucketInfo getBucketInfo(document::ByteBuffer& buf) const override;
+ void putBucketInfo(const api::BucketInfo& info, vespalib::GrowableByteBuffer& buf) const override;
+
+ void onEncode(GBBuf&, const api::PutCommand&) const override;
+ void onEncode(GBBuf&, const api::PutReply&) const override;
+ void onEncode(GBBuf&, const api::UpdateCommand&) const override;
+ void onEncode(GBBuf&, const api::UpdateReply&) const override;
+ void onEncode(GBBuf&, const api::GetReply&) const override;
+ void onEncode(GBBuf&, const api::RemoveReply&) const override;
+ void onEncode(GBBuf&, const api::RevertReply&) const override;
+ void onEncode(GBBuf&, const api::CreateBucketReply&) const override;
+ void onEncode(GBBuf&, const api::DeleteBucketCommand&) const override;
+ void onEncode(GBBuf&, const api::DeleteBucketReply&) const override;
+ void onEncode(GBBuf&, const api::MergeBucketCommand&) const override;
+ void onEncode(GBBuf&, const api::MergeBucketReply&) const override;
+ void onEncode(GBBuf&, const api::GetBucketDiffReply&) const override;
+ void onEncode(GBBuf&, const api::ApplyBucketDiffReply&) const override;
+ void onEncode(GBBuf&, const api::SplitBucketReply&) const override;
+ void onEncode(GBBuf&, const api::JoinBucketsCommand&) const override;
+ void onEncode(GBBuf&, const api::JoinBucketsReply&) const override;
+ void onEncode(GBBuf&, const api::RequestBucketInfoCommand&) const override;
+
+ void onEncodeBucketInfoReply(GBBuf&, const api::BucketInfoReply&) const override;
+ virtual void onEncodeBucketReply(GBBuf&, const api::BucketReply&) const;
+
+ void onEncode(GBBuf&, const api::CreateVisitorCommand& msg) const override;
+ void onEncode(GBBuf&, const api::CreateVisitorReply& msg) const override;
+ void onEncodeCommand(GBBuf&, const api::StorageCommand&) const override;
+ void onEncodeReply(GBBuf&, const api::StorageReply&) const override;
+
+ SCmd::UP onDecodePutCommand(BBuf&) const override;
+ SRep::UP onDecodePutReply(const SCmd&, BBuf&) const override;
+ SCmd::UP onDecodeUpdateCommand(BBuf&) const override;
+ SRep::UP onDecodeUpdateReply(const SCmd&, BBuf&) const override;
+ SRep::UP onDecodeGetReply(const SCmd&, BBuf&) const override;
+ SRep::UP onDecodeRemoveReply(const SCmd&, BBuf&) const override;
+ SRep::UP onDecodeRevertReply(const SCmd&, BBuf&) const override;
+ SRep::UP onDecodeCreateBucketReply(const SCmd&, BBuf&) const override;
+ SCmd::UP onDecodeDeleteBucketCommand(BBuf&) const override;
+ SRep::UP onDecodeDeleteBucketReply(const SCmd&, BBuf&) const override;
+ SCmd::UP onDecodeMergeBucketCommand(BBuf&) const override;
+ SRep::UP onDecodeMergeBucketReply(const SCmd&, BBuf&) const override;
+ SRep::UP onDecodeGetBucketDiffReply(const SCmd&, BBuf&) const override;
+ SRep::UP onDecodeApplyBucketDiffReply(const SCmd&, BBuf&) const override;
+ SRep::UP onDecodeSplitBucketReply(const SCmd&, BBuf&) const override;
+ SCmd::UP onDecodeJoinBucketsCommand(BBuf& buf) const override;
+ SRep::UP onDecodeJoinBucketsReply(const SCmd& cmd, BBuf& buf) const override;
+ SCmd::UP onDecodeCreateVisitorCommand(BBuf&) const override;
+ SCmd::UP onDecodeRequestBucketInfoCommand(BBuf& buf) const override;
+
+ void onDecodeBucketInfoReply(BBuf&, api::BucketInfoReply&) const override;
+ virtual void onDecodeBucketReply(BBuf&, api::BucketReply&) const;
+ SRep::UP onDecodeCreateVisitorReply(const SCmd& cmd, BBuf& buf) const override;
+ void onDecodeCommand(BBuf& buf, api::StorageCommand& msg) const override;
+ void onDecodeReply(BBuf&, api::StorageReply&) const override;
+};
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/protocolserialization5_1.cpp b/storage/src/vespa/storageapi/mbusprot/protocolserialization5_1.cpp
new file mode 100644
index 00000000000..97ceceac33d
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protocolserialization5_1.cpp
@@ -0,0 +1,198 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "protocolserialization5_1.h"
+#include "serializationhelper.h"
+#include "storagecommand.h"
+#include "storagereply.h"
+#include <vespa/storageapi/message/bucketsplitting.h>
+#include <vespa/storageapi/message/visitor.h>
+
+using document::BucketSpace;
+
+namespace storage::mbusprot {
+
+api::BucketInfo
+ProtocolSerialization5_1::getBucketInfo(document::ByteBuffer& buf) const
+{
+ uint64_t lastModified(SH::getLong(buf));
+ uint32_t crc(SH::getInt(buf));
+ uint32_t doccount(SH::getInt(buf));
+ uint32_t docsize(SH::getInt(buf));
+ uint32_t metacount(SH::getInt(buf));
+ uint32_t usedsize(SH::getInt(buf));
+ uint8_t flags(SH::getByte(buf));
+ bool ready = (flags & BUCKET_READY) != 0;
+ bool active = (flags & BUCKET_ACTIVE) != 0;
+ return api::BucketInfo(crc, doccount, docsize,
+ metacount, usedsize,
+ ready, active, lastModified);
+}
+
+void
+ProtocolSerialization5_1::putBucketInfo(
+ const api::BucketInfo& info, vespalib::GrowableByteBuffer& buf) const
+{
+ buf.putLong(info.getLastModified());
+ buf.putInt(info.getChecksum());
+ buf.putInt(info.getDocumentCount());
+ buf.putInt(info.getTotalDocumentSize());
+ buf.putInt(info.getMetaCount());
+ buf.putInt(info.getUsedFileSize());
+ uint8_t flags = (info.isReady() ? BUCKET_READY : 0) |
+ (info.isActive() ? BUCKET_ACTIVE : 0);
+ buf.putByte(flags);
+}
+
+ProtocolSerialization5_1::ProtocolSerialization5_1(
+ const std::shared_ptr<const document::DocumentTypeRepo>& repo)
+ : ProtocolSerialization5_0(repo)
+{
+}
+
+void ProtocolSerialization5_1::onEncode(GBBuf& buf, const api::SetBucketStateCommand& msg) const
+{
+ putBucket(msg.getBucket(), buf);
+ buf.putByte(static_cast<uint8_t>(msg.getState()));
+ onEncodeCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization5_1::onDecodeSetBucketStateCommand(BBuf& buf) const
+{
+ document::Bucket bucket = getBucket(buf);
+ api::SetBucketStateCommand::BUCKET_STATE state(
+ static_cast<api::SetBucketStateCommand::BUCKET_STATE>(SH::getByte(buf)));
+ auto msg = std::make_unique<api::SetBucketStateCommand>(bucket, state);
+ onDecodeCommand(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization5_1::onEncode(GBBuf& buf, const api::SetBucketStateReply& msg) const
+{
+ onEncodeBucketReply(buf, msg);
+}
+
+api::StorageReply::UP
+ProtocolSerialization5_1::onDecodeSetBucketStateReply(const SCmd& cmd, BBuf& buf) const
+{
+ auto msg = std::make_unique<api::SetBucketStateReply>(static_cast<const api::SetBucketStateCommand&>(cmd));
+ onDecodeBucketReply(buf, *msg);
+ return msg;
+}
+
+void ProtocolSerialization5_1::onEncode(GBBuf& buf, const api::GetCommand& msg) const
+{
+ buf.putString(msg.getDocumentId().toString());
+ putBucket(msg.getBucket(), buf);
+ buf.putLong(msg.getBeforeTimestamp());
+ buf.putString(msg.getFieldSet());
+ onEncodeCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization5_1::onDecodeGetCommand(BBuf& buf) const
+{
+ document::DocumentId did(SH::getString(buf));
+ document::Bucket bucket = getBucket(buf);
+ api::Timestamp beforeTimestamp(SH::getLong(buf));
+ std::string fieldSet(SH::getString(buf));
+ auto msg = std::make_unique<api::GetCommand>(bucket, did, fieldSet, beforeTimestamp);
+ onDecodeCommand(buf, *msg);
+ return msg;
+}
+
+void
+ProtocolSerialization5_1::onEncode(GBBuf& buf, const api::CreateVisitorCommand& msg) const
+{
+ putBucketSpace(msg.getBucketSpace(), buf);
+ buf.putString(msg.getLibraryName());
+ buf.putString(msg.getInstanceId());
+ buf.putString(msg.getDocumentSelection());
+ buf.putInt(msg.getVisitorCmdId());
+ buf.putString(msg.getControlDestination());
+ buf.putString(msg.getDataDestination());
+ buf.putInt(msg.getMaximumPendingReplyCount());
+ buf.putLong(msg.getFromTime());
+ buf.putLong(msg.getToTime());
+
+ buf.putInt(msg.getBuckets().size());
+ for (uint32_t i = 0; i < msg.getBuckets().size(); i++) {
+ buf.putLong(msg.getBuckets()[i].getRawId());
+ }
+
+ buf.putBoolean(msg.visitRemoves());
+ buf.putString(msg.getFieldSet());
+ buf.putBoolean(msg.visitInconsistentBuckets());
+ buf.putInt(vespalib::count_ms(msg.getQueueTimeout()));
+ msg.getParameters().serialize(buf);
+
+ onEncodeCommand(buf, msg);
+
+ buf.putInt(0); // Unused
+ buf.putInt(msg.getMaxBucketsPerVisitor());
+}
+
+api::StorageCommand::UP
+ProtocolSerialization5_1::onDecodeCreateVisitorCommand(BBuf& buf) const
+{
+ BucketSpace bucketSpace = getBucketSpace(buf);
+ vespalib::stringref libraryName = SH::getString(buf);
+ vespalib::stringref instanceId = SH::getString(buf);
+ vespalib::stringref selection = SH::getString(buf);
+ auto msg = std::make_unique<api::CreateVisitorCommand>(bucketSpace, libraryName, instanceId, selection);
+ msg->setVisitorCmdId(SH::getInt(buf));
+ msg->setControlDestination(SH::getString(buf));
+ msg->setDataDestination(SH::getString(buf));
+ msg->setMaximumPendingReplyCount(SH::getInt(buf));
+
+ msg->setFromTime(SH::getLong(buf));
+ msg->setToTime(SH::getLong(buf));
+ uint32_t count = SH::getInt(buf);
+
+ if (count > buf.getRemaining()) {
+ // Trigger out of bounds exception rather than out of memory error
+ buf.incPos(count);
+ }
+
+ for (uint32_t i = 0; i < count; i++) {
+ msg->getBuckets().push_back(document::BucketId(SH::getLong(buf)));
+ }
+
+ if (SH::getBoolean(buf)) {
+ msg->setVisitRemoves();
+ }
+
+ msg->setFieldSet(SH::getString(buf));
+
+ if (SH::getBoolean(buf)) {
+ msg->setVisitInconsistentBuckets();
+ }
+ msg->setQueueTimeout(std::chrono::milliseconds(SH::getInt(buf)));
+ msg->getParameters().deserialize(buf);
+
+ onDecodeCommand(buf, *msg);
+ SH::getInt(buf); // Unused
+ msg->setMaxBucketsPerVisitor(SH::getInt(buf));
+ msg->setVisitorDispatcherVersion(50);
+ return msg;
+}
+
+void ProtocolSerialization5_1::onEncode(GBBuf& buf, const api::CreateBucketCommand& msg) const
+{
+ putBucket(msg.getBucket(), buf);
+ buf.putBoolean(msg.getActive());
+ onEncodeBucketInfoCommand(buf, msg);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization5_1::onDecodeCreateBucketCommand(BBuf& buf) const
+{
+ document::Bucket bucket = getBucket(buf);
+ bool setActive = SH::getBoolean(buf);
+ auto msg = std::make_unique<api::CreateBucketCommand>(bucket);
+ msg->setActive(setActive);
+ onDecodeBucketInfoCommand(buf, *msg);
+ return msg;
+}
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/protocolserialization5_1.h b/storage/src/vespa/storageapi/mbusprot/protocolserialization5_1.h
new file mode 100644
index 00000000000..bfad492e653
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protocolserialization5_1.h
@@ -0,0 +1,34 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "protocolserialization5_0.h"
+
+namespace storage::mbusprot {
+
+class ProtocolSerialization5_1 : public ProtocolSerialization5_0
+{
+ enum BucketState {
+ BUCKET_READY = 0x1,
+ BUCKET_ACTIVE = 0x2,
+ };
+public:
+ ProtocolSerialization5_1(const std::shared_ptr<const document::DocumentTypeRepo>&);
+
+ api::BucketInfo getBucketInfo(document::ByteBuffer& buf) const override;
+ void putBucketInfo(const api::BucketInfo& info, vespalib::GrowableByteBuffer& buf) const override;
+
+protected:
+ void onEncode(GBBuf&, const api::SetBucketStateCommand&) const override;
+ void onEncode(GBBuf&, const api::SetBucketStateReply&) const override;
+ void onEncode(GBBuf&, const api::GetCommand&) const override;
+ void onEncode(GBBuf&, const api::CreateVisitorCommand&) const override;
+ void onEncode(GBBuf&, const api::CreateBucketCommand&) const override;
+
+ SCmd::UP onDecodeSetBucketStateCommand(BBuf&) const override;
+ SRep::UP onDecodeSetBucketStateReply(const SCmd&, BBuf&) const override;
+ SCmd::UP onDecodeGetCommand(BBuf&) const override;
+ SCmd::UP onDecodeCreateVisitorCommand(BBuf&) const override;
+ SCmd::UP onDecodeCreateBucketCommand(BBuf&) const override;
+};
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/protocolserialization5_2.cpp b/storage/src/vespa/storageapi/mbusprot/protocolserialization5_2.cpp
new file mode 100644
index 00000000000..10aa9d69fbc
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protocolserialization5_2.cpp
@@ -0,0 +1,64 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// @author Vegard Sjonfjell
+
+#include "protocolserialization5_2.h"
+#include "storagecommand.h"
+#include "serializationhelper.h"
+
+namespace storage::mbusprot {
+
+using documentapi::TestAndSetCondition;
+
+void ProtocolSerialization5_2::onEncode(GBBuf & buf, const api::PutCommand & cmd) const
+{
+ ProtocolSerialization5_0::onEncode(buf, cmd);
+ encodeTasCondition(buf, cmd);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization5_2::onDecodePutCommand(BBuf & buf) const
+{
+ auto cmd = ProtocolSerialization5_0::onDecodePutCommand(buf);
+ decodeTasCondition(*cmd, buf);
+ return cmd;
+}
+
+void ProtocolSerialization5_2::onEncode(GBBuf & buf, const api::RemoveCommand & cmd) const
+{
+ ProtocolSerialization4_2::onEncode(buf, cmd);
+ encodeTasCondition(buf, cmd);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization5_2::onDecodeRemoveCommand(BBuf & buf) const
+{
+ auto cmd = ProtocolSerialization4_2::onDecodeRemoveCommand(buf);
+ decodeTasCondition(*cmd, buf);
+ return cmd;
+}
+
+void ProtocolSerialization5_2::onEncode(GBBuf & buf, const api::UpdateCommand & cmd) const
+{
+ ProtocolSerialization5_0::onEncode(buf, cmd);
+ encodeTasCondition(buf, cmd);
+}
+
+api::StorageCommand::UP
+ProtocolSerialization5_2::onDecodeUpdateCommand(BBuf & buf) const
+{
+ auto cmd = ProtocolSerialization5_0::onDecodeUpdateCommand(buf);
+ decodeTasCondition(*cmd, buf);
+ return cmd;
+}
+
+void ProtocolSerialization5_2::decodeTasCondition(api::StorageCommand & storageCmd, BBuf & buf) {
+ auto & cmd = static_cast<api::TestAndSetCommand &>(storageCmd);
+ cmd.setCondition(TestAndSetCondition(SH::getString(buf)));
+}
+
+void ProtocolSerialization5_2::encodeTasCondition(GBBuf & buf, const api::StorageCommand & storageCmd) {
+ auto & cmd = static_cast<const api::TestAndSetCommand &>(storageCmd);
+ buf.putString(cmd.getCondition().getSelection());
+}
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/protocolserialization5_2.h b/storage/src/vespa/storageapi/mbusprot/protocolserialization5_2.h
new file mode 100644
index 00000000000..e56b9942fa5
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protocolserialization5_2.h
@@ -0,0 +1,32 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// @author Vegard Sjonfjell
+
+#pragma once
+
+#include "protocolserialization5_1.h"
+#include <vespa/vespalib/util/growablebytebuffer.h>
+#include <vespa/storageapi/message/persistence.h>
+
+namespace storage::mbusprot {
+
+class ProtocolSerialization5_2 : public ProtocolSerialization5_1
+{
+public:
+ ProtocolSerialization5_2(const std::shared_ptr<const document::DocumentTypeRepo>& repo)
+ : ProtocolSerialization5_1(repo)
+ {}
+
+protected:
+ void onEncode(GBBuf &, const api::PutCommand &) const override;
+ void onEncode(GBBuf &, const api::RemoveCommand &) const override;
+ void onEncode(GBBuf &, const api::UpdateCommand &) const override;
+
+ SCmd::UP onDecodePutCommand(BBuf &) const override;
+ SCmd::UP onDecodeRemoveCommand(BBuf &) const override;
+ SCmd::UP onDecodeUpdateCommand(BBuf &) const override;
+
+ static void decodeTasCondition(api::StorageCommand & cmd, BBuf & buf);
+ static void encodeTasCondition(GBBuf & buf, const api::StorageCommand & cmd);
+};
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/protocolserialization6_0.cpp b/storage/src/vespa/storageapi/mbusprot/protocolserialization6_0.cpp
new file mode 100644
index 00000000000..799aff8f8b3
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protocolserialization6_0.cpp
@@ -0,0 +1,40 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "protocolserialization6_0.h"
+#include "serializationhelper.h"
+
+namespace storage::mbusprot {
+
+ProtocolSerialization6_0::ProtocolSerialization6_0(const std::shared_ptr<const document::DocumentTypeRepo> &repo)
+ : ProtocolSerialization5_2(repo)
+{
+}
+
+document::Bucket
+ProtocolSerialization6_0::getBucket(document::ByteBuffer &buf) const
+{
+ document::BucketSpace bucketSpace(SH::getLong(buf));
+ document::BucketId bucketId(SH::getLong(buf));
+ return document::Bucket(bucketSpace, bucketId);
+}
+
+void
+ProtocolSerialization6_0::putBucket(const document::Bucket &bucket, vespalib::GrowableByteBuffer &buf) const
+{
+ buf.putLong(bucket.getBucketSpace().getId());
+ buf.putLong(bucket.getBucketId().getRawId());
+}
+
+document::BucketSpace
+ProtocolSerialization6_0::getBucketSpace(document::ByteBuffer &buf) const
+{
+ return document::BucketSpace(SH::getLong(buf));
+}
+
+void
+ProtocolSerialization6_0::putBucketSpace(document::BucketSpace bucketSpace, vespalib::GrowableByteBuffer &buf) const
+{
+ buf.putLong(bucketSpace.getId());
+}
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/protocolserialization6_0.h b/storage/src/vespa/storageapi/mbusprot/protocolserialization6_0.h
new file mode 100644
index 00000000000..5467cf6c5d2
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protocolserialization6_0.h
@@ -0,0 +1,24 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "protocolserialization5_2.h"
+
+namespace storage::mbusprot {
+
+/**
+ * Protocol serialization version adding decoding and encoding
+ * of bucket space to almost all commands.
+ */
+class ProtocolSerialization6_0 : public ProtocolSerialization5_2
+{
+public:
+ ProtocolSerialization6_0(const std::shared_ptr<const document::DocumentTypeRepo> &repo);
+
+ document::Bucket getBucket(document::ByteBuffer &buf) const override;
+ void putBucket(const document::Bucket &bucket, vespalib::GrowableByteBuffer &buf) const override;
+ document::BucketSpace getBucketSpace(document::ByteBuffer &buf) const override;
+ void putBucketSpace(document::BucketSpace bucketSpace, vespalib::GrowableByteBuffer &buf) const override;
+};
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/protocolserialization7.cpp b/storage/src/vespa/storageapi/mbusprot/protocolserialization7.cpp
new file mode 100644
index 00000000000..91b5999e34c
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protocolserialization7.cpp
@@ -0,0 +1,1351 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "protocolserialization7.h"
+#include "serializationhelper.h"
+#include "protobuf_includes.h"
+
+#include <vespa/document/update/documentupdate.h>
+#include <vespa/document/util/bufferexceptions.h>
+#include <vespa/storageapi/message/bucketsplitting.h>
+#include <vespa/storageapi/message/persistence.h>
+#include <vespa/storageapi/message/removelocation.h>
+#include <vespa/storageapi/message/visitor.h>
+#include <vespa/storageapi/message/stat.h>
+#include <vespa/vdslib/state/clusterstate.h>
+
+namespace storage::mbusprot {
+
+ProtocolSerialization7::ProtocolSerialization7(std::shared_ptr<const document::DocumentTypeRepo> repo)
+ : ProtocolSerialization(),
+ _repo(std::move(repo))
+{
+}
+
+namespace {
+
+void set_bucket(protobuf::Bucket& dest, const document::Bucket& src) {
+ dest.set_raw_bucket_id(src.getBucketId().getRawId());
+ dest.set_space_id(src.getBucketSpace().getId());
+}
+
+void set_bucket_id(protobuf::BucketId& dest, const document::BucketId& src) {
+ dest.set_raw_id(src.getRawId());
+}
+
+document::BucketId get_bucket_id(const protobuf::BucketId& src) {
+ return document::BucketId(src.raw_id());
+}
+
+void set_bucket_space(protobuf::BucketSpace& dest, const document::BucketSpace& src) {
+ dest.set_space_id(src.getId());
+}
+
+document::BucketSpace get_bucket_space(const protobuf::BucketSpace& src) {
+ return document::BucketSpace(src.space_id());
+}
+
+void set_bucket_info(protobuf::BucketInfo& dest, const api::BucketInfo& src) {
+ dest.set_last_modified_timestamp(src.getLastModified());
+ dest.set_legacy_checksum(src.getChecksum());
+ dest.set_doc_count(src.getDocumentCount());
+ dest.set_total_doc_size(src.getTotalDocumentSize());
+ dest.set_meta_count(src.getMetaCount());
+ dest.set_used_file_size(src.getUsedFileSize());
+ dest.set_active(src.isActive());
+ dest.set_ready(src.isReady());
+}
+
+document::Bucket get_bucket(const protobuf::Bucket& src) {
+ return document::Bucket(document::BucketSpace(src.space_id()),
+ document::BucketId(src.raw_bucket_id()));
+}
+
+api::BucketInfo get_bucket_info(const protobuf::BucketInfo& src) {
+ api::BucketInfo info;
+ info.setLastModified(src.last_modified_timestamp());
+ info.setChecksum(src.legacy_checksum());
+ info.setDocumentCount(src.doc_count());
+ info.setTotalDocumentSize(src.total_doc_size());
+ info.setMetaCount(src.meta_count());
+ info.setUsedFileSize(src.used_file_size());
+ info.setActive(src.active());
+ info.setReady(src.ready());
+ return info;
+}
+
+documentapi::TestAndSetCondition get_tas_condition(const protobuf::TestAndSetCondition& src) {
+ return documentapi::TestAndSetCondition(src.selection());
+}
+
+void set_tas_condition(protobuf::TestAndSetCondition& dest, const documentapi::TestAndSetCondition& src) {
+ dest.set_selection(src.getSelection().data(), src.getSelection().size());
+}
+
+std::shared_ptr<document::Document> get_document(const protobuf::Document& src_doc,
+ const document::DocumentTypeRepo& type_repo)
+{
+ if (!src_doc.payload().empty()) {
+ vespalib::nbostream doc_buf(src_doc.payload().data(), src_doc.payload().size());
+ return std::make_shared<document::Document>(type_repo, doc_buf);
+ }
+ return std::shared_ptr<document::Document>();
+}
+
+void set_update(protobuf::Update& dest, const document::DocumentUpdate& src) {
+ vespalib::nbostream stream;
+ src.serializeHEAD(stream);
+ dest.set_payload(stream.peek(), stream.size());
+}
+
+std::shared_ptr<document::DocumentUpdate> get_update(const protobuf::Update& src,
+ const document::DocumentTypeRepo& type_repo)
+{
+ if (!src.payload().empty()) {
+ return document::DocumentUpdate::createHEAD(
+ type_repo, vespalib::nbostream(src.payload().data(), src.payload().size()));
+ }
+ return std::shared_ptr<document::DocumentUpdate>();
+}
+
+void write_request_header(vespalib::GrowableByteBuffer& buf, const api::StorageCommand& cmd) {
+ protobuf::RequestHeader hdr; // Arena alloc not needed since there are no nested messages
+ hdr.set_message_id(cmd.getMsgId());
+ hdr.set_priority(cmd.getPriority());
+ hdr.set_source_index(cmd.getSourceIndex());
+
+ uint8_t dest[128]; // Only primitive fields, should be plenty large enough.
+ auto encoded_size = static_cast<uint32_t>(hdr.ByteSizeLong());
+ assert(encoded_size <= sizeof(dest));
+ [[maybe_unused]] bool ok = hdr.SerializeWithCachedSizesToArray(dest);
+ assert(ok);
+ buf.putInt(encoded_size);
+ buf.putBytes(reinterpret_cast<const char*>(dest), encoded_size);
+}
+
+void write_response_header(vespalib::GrowableByteBuffer& buf, const api::StorageReply& reply) {
+ protobuf::ResponseHeader hdr; // Arena alloc not needed since there are no nested messages
+ const auto& result = reply.getResult();
+ hdr.set_return_code_id(static_cast<uint32_t>(result.getResult()));
+ if (!result.getMessage().empty()) {
+ hdr.set_return_code_message(result.getMessage().data(), result.getMessage().size());
+ }
+ hdr.set_message_id(reply.getMsgId());
+ hdr.set_priority(reply.getPriority());
+
+ const auto header_size = hdr.ByteSizeLong();
+ assert(header_size <= UINT32_MAX);
+ buf.putInt(static_cast<uint32_t>(header_size));
+
+ auto* dest_buf = reinterpret_cast<uint8_t*>(buf.allocate(header_size));
+ [[maybe_unused]] bool ok = hdr.SerializeWithCachedSizesToArray(dest_buf);
+ assert(ok);
+}
+
+void decode_request_header(document::ByteBuffer& buf, protobuf::RequestHeader& hdr) {
+ auto hdr_len = static_cast<uint32_t>(SerializationHelper::getInt(buf));
+ if (hdr_len > buf.getRemaining()) {
+ throw document::BufferOutOfBoundsException(buf.getPos(), hdr_len);
+ }
+ bool ok = hdr.ParseFromArray(buf.getBufferAtPos(), hdr_len);
+ if (!ok) {
+ throw vespalib::IllegalArgumentException("Malformed protobuf request header");
+ }
+ buf.incPos(hdr_len);
+}
+
+void decode_response_header(document::ByteBuffer& buf, protobuf::ResponseHeader& hdr) {
+ auto hdr_len = static_cast<uint32_t>(SerializationHelper::getInt(buf));
+ if (hdr_len > buf.getRemaining()) {
+ throw document::BufferOutOfBoundsException(buf.getPos(), hdr_len);
+ }
+ bool ok = hdr.ParseFromArray(buf.getBufferAtPos(), hdr_len);
+ if (!ok) {
+ throw vespalib::IllegalArgumentException("Malformed protobuf response header");
+ }
+ buf.incPos(hdr_len);
+}
+
+} // anonymous namespace
+
+template <typename ProtobufType>
+class BaseEncoder {
+ vespalib::GrowableByteBuffer& _out_buf;
+ ::google::protobuf::Arena _arena;
+ ProtobufType* _proto_obj;
+public:
+ explicit BaseEncoder(vespalib::GrowableByteBuffer& out_buf)
+ : _out_buf(out_buf),
+ _arena(),
+ _proto_obj(::google::protobuf::Arena::Create<ProtobufType>(&_arena))
+ {
+ }
+
+ void encode() {
+ assert(_proto_obj != nullptr);
+ const auto sz = _proto_obj->ByteSizeLong();
+ assert(sz <= UINT32_MAX);
+ auto* buf = reinterpret_cast<uint8_t*>(_out_buf.allocate(sz));
+ [[maybe_unused]] bool ok = _proto_obj->SerializeWithCachedSizesToArray(buf);
+ assert(ok);
+ _proto_obj = nullptr;
+ }
+protected:
+ vespalib::GrowableByteBuffer& buffer() noexcept { return _out_buf; }
+
+ // Precondition: encode() is not called
+ ProtobufType& proto_obj() noexcept { return *_proto_obj; }
+ const ProtobufType& proto_obj() const noexcept { return *_proto_obj; }
+};
+
+template <typename ProtobufType>
+class RequestEncoder : public BaseEncoder<ProtobufType> {
+public:
+ RequestEncoder(vespalib::GrowableByteBuffer& out_buf, const api::StorageCommand& cmd)
+ : BaseEncoder<ProtobufType>(out_buf)
+ {
+ write_request_header(out_buf, cmd);
+ }
+
+ // Precondition: encode() is not called
+ ProtobufType& request() noexcept { return this->proto_obj(); }
+ const ProtobufType& request() const noexcept { return this->proto_obj(); }
+};
+
+template <typename ProtobufType>
+class ResponseEncoder : public BaseEncoder<ProtobufType> {
+public:
+ ResponseEncoder(vespalib::GrowableByteBuffer& out_buf, const api::StorageReply& reply)
+ : BaseEncoder<ProtobufType>(out_buf)
+ {
+ write_response_header(out_buf, reply);
+ }
+
+ // Precondition: encode() is not called
+ ProtobufType& response() noexcept { return this->proto_obj(); }
+ const ProtobufType& response() const noexcept { return this->proto_obj(); }
+};
+
+template <typename ProtobufType>
+class RequestDecoder {
+ protobuf::RequestHeader _hdr;
+ ::google::protobuf::Arena _arena;
+ ProtobufType* _proto_obj;
+public:
+ RequestDecoder(document::ByteBuffer& in_buf)
+ : _arena(),
+ _proto_obj(::google::protobuf::Arena::Create<ProtobufType>(&_arena))
+ {
+ decode_request_header(in_buf, _hdr);
+ assert(in_buf.getRemaining() <= INT_MAX);
+ bool ok = _proto_obj->ParseFromArray(in_buf.getBufferAtPos(), in_buf.getRemaining());
+ if (!ok) {
+ throw vespalib::IllegalArgumentException(
+ vespalib::make_string("Malformed protobuf request payload for %s",
+ ProtobufType::descriptor()->full_name().c_str()));
+ }
+ }
+
+ void transfer_meta_information_to(api::StorageCommand& dest) {
+ dest.forceMsgId(_hdr.message_id());
+ dest.setPriority(static_cast<uint8_t>(_hdr.priority()));
+ dest.setSourceIndex(static_cast<uint16_t>(_hdr.source_index()));
+ }
+
+ ProtobufType& request() noexcept { return *_proto_obj; }
+ const ProtobufType& request() const noexcept { return *_proto_obj; }
+};
+
+template <typename ProtobufType>
+class ResponseDecoder {
+ protobuf::ResponseHeader _hdr;
+ ::google::protobuf::Arena _arena;
+ ProtobufType* _proto_obj;
+public:
+ explicit ResponseDecoder(document::ByteBuffer& in_buf)
+ : _arena(),
+ _proto_obj(::google::protobuf::Arena::Create<ProtobufType>(&_arena))
+ {
+ decode_response_header(in_buf, _hdr);
+ assert(in_buf.getRemaining() <= INT_MAX);
+ bool ok = _proto_obj->ParseFromArray(in_buf.getBufferAtPos(), in_buf.getRemaining());
+ if (!ok) {
+ throw vespalib::IllegalArgumentException(
+ vespalib::make_string("Malformed protobuf response payload for %s",
+ ProtobufType::descriptor()->full_name().c_str()));
+ }
+ }
+
+ void transfer_meta_information_to(api::StorageReply& dest) {
+ dest.forceMsgId(_hdr.message_id());
+ dest.setPriority(static_cast<uint8_t>(_hdr.priority()));
+ dest.setResult(api::ReturnCode(static_cast<api::ReturnCode::Result>(_hdr.return_code_id()),
+ _hdr.return_code_message()));
+ }
+
+ ProtobufType& response() noexcept { return *_proto_obj; }
+ const ProtobufType& response() const noexcept { return *_proto_obj; }
+};
+
+template <typename ProtobufType, typename Func>
+void encode_request(vespalib::GrowableByteBuffer& out_buf, const api::StorageCommand& msg, Func&& f) {
+ RequestEncoder<ProtobufType> enc(out_buf, msg);
+ f(enc.request());
+ enc.encode();
+}
+
+template <typename ProtobufType, typename Func>
+void encode_response(vespalib::GrowableByteBuffer& out_buf, const api::StorageReply& reply, Func&& f) {
+ ResponseEncoder<ProtobufType> enc(out_buf, reply);
+ auto& res = enc.response();
+ f(res);
+ enc.encode();
+}
+
+template <typename ProtobufType, typename Func>
+std::unique_ptr<api::StorageCommand>
+ProtocolSerialization7::decode_request(document::ByteBuffer& in_buf, Func&& f) const {
+ RequestDecoder<ProtobufType> dec(in_buf);
+ const auto& req = dec.request();
+ auto cmd = f(req);
+ dec.transfer_meta_information_to(*cmd);
+ return cmd;
+}
+
+template <typename ProtobufType, typename Func>
+std::unique_ptr<api::StorageReply>
+ProtocolSerialization7::decode_response(document::ByteBuffer& in_buf, Func&& f) const {
+ ResponseDecoder<ProtobufType> dec(in_buf);
+ const auto& res = dec.response();
+ auto reply = f(res);
+ dec.transfer_meta_information_to(*reply);
+ return reply;
+}
+
+template <typename ProtobufType, typename Func>
+void encode_bucket_request(vespalib::GrowableByteBuffer& out_buf, const api::BucketCommand& msg, Func&& f) {
+ encode_request<ProtobufType>(out_buf, msg, [&](ProtobufType& req) {
+ set_bucket(*req.mutable_bucket(), msg.getBucket());
+ f(req);
+ });
+}
+
+template <typename ProtobufType, typename Func>
+std::unique_ptr<api::StorageCommand>
+ProtocolSerialization7::decode_bucket_request(document::ByteBuffer& in_buf, Func&& f) const {
+ return decode_request<ProtobufType>(in_buf, [&](const ProtobufType& req) {
+ if (!req.has_bucket()) {
+ throw vespalib::IllegalArgumentException(
+ vespalib::make_string("Malformed protocol buffer request for %s; no bucket",
+ ProtobufType::descriptor()->full_name().c_str()));
+ }
+ const auto bucket = get_bucket(req.bucket());
+ return f(req, bucket);
+ });
+}
+
+template <typename ProtobufType, typename Func>
+void encode_bucket_response(vespalib::GrowableByteBuffer& out_buf, const api::BucketReply& reply, Func&& f) {
+ encode_response<ProtobufType>(out_buf, reply, [&](ProtobufType& res) {
+ if (reply.hasBeenRemapped()) {
+ set_bucket_id(*res.mutable_remapped_bucket_id(), reply.getBucketId());
+ }
+ f(res);
+ });
+}
+
+template <typename ProtobufType, typename Func>
+std::unique_ptr<api::StorageReply>
+ProtocolSerialization7::decode_bucket_response(document::ByteBuffer& in_buf, Func&& f) const {
+ return decode_response<ProtobufType>(in_buf, [&](const ProtobufType& res) {
+ auto reply = f(res);
+ if (res.has_remapped_bucket_id()) {
+ reply->remapBucketId(get_bucket_id(res.remapped_bucket_id()));
+ }
+ return reply;
+ });
+}
+
+template <typename ProtobufType, typename Func>
+void encode_bucket_info_response(vespalib::GrowableByteBuffer& out_buf, const api::BucketInfoReply& reply, Func&& f) {
+ encode_bucket_response<ProtobufType>(out_buf, reply, [&](ProtobufType& res) {
+ set_bucket_info(*res.mutable_bucket_info(), reply.getBucketInfo());
+ f(res);
+ });
+}
+
+template <typename ProtobufType, typename Func>
+std::unique_ptr<api::StorageReply>
+ProtocolSerialization7::decode_bucket_info_response(document::ByteBuffer& in_buf, Func&& f) const {
+ return decode_bucket_response<ProtobufType>(in_buf, [&](const ProtobufType& res) {
+ auto reply = f(res);
+ reply->setBucketInfo(get_bucket_info(res.bucket_info())); // If not present, default of all zeroes is correct
+ return reply;
+ });
+}
+
+// TODO document protobuf ducktyping assumptions
+
+namespace {
+// Inherit from known base class just to avoid having to template this. We don't care about its subtype anyway.
+void no_op_encode([[maybe_unused]] ::google::protobuf::Message&) {
+ // nothing to do here.
+}
+
+void set_document(protobuf::Document& target_doc, const document::Document& src_doc) {
+ vespalib::nbostream stream;
+ src_doc.serialize(stream);
+ target_doc.set_payload(stream.peek(), stream.size());
+}
+
+}
+
+// -----------------------------------------------------------------
+// Put
+// -----------------------------------------------------------------
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::PutCommand& msg) const {
+ encode_bucket_request<protobuf::PutRequest>(buf, msg, [&](auto& req) {
+ req.set_new_timestamp(msg.getTimestamp());
+ req.set_expected_old_timestamp(msg.getUpdateTimestamp());
+ if (msg.getCondition().isPresent()) {
+ set_tas_condition(*req.mutable_condition(), msg.getCondition());
+ }
+ if (msg.getDocument()) {
+ set_document(*req.mutable_document(), *msg.getDocument());
+ }
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::PutReply& msg) const {
+ encode_bucket_info_response<protobuf::PutResponse>(buf, msg, [&](auto& res) {
+ res.set_was_found(msg.wasFound());
+ });
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodePutCommand(BBuf& buf) const {
+ return decode_bucket_request<protobuf::PutRequest>(buf, [&](auto& req, auto& bucket) {
+ auto document = get_document(req.document(), type_repo());
+ auto cmd = std::make_unique<api::PutCommand>(bucket, std::move(document), req.new_timestamp());
+ cmd->setUpdateTimestamp(req.expected_old_timestamp());
+ if (req.has_condition()) {
+ cmd->setCondition(get_tas_condition(req.condition()));
+ }
+ return cmd;
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodePutReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_bucket_info_response<protobuf::PutResponse>(buf, [&](auto& res) {
+ return std::make_unique<api::PutReply>(static_cast<const api::PutCommand&>(cmd), res.was_found());
+ });
+}
+
+// -----------------------------------------------------------------
+// Update
+// -----------------------------------------------------------------
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::UpdateCommand& msg) const {
+ encode_bucket_request<protobuf::UpdateRequest>(buf, msg, [&](auto& req) {
+ auto* update = msg.getUpdate().get();
+ if (update) {
+ set_update(*req.mutable_update(), *update);
+ }
+ req.set_new_timestamp(msg.getTimestamp());
+ req.set_expected_old_timestamp(msg.getOldTimestamp());
+ if (msg.getCondition().isPresent()) {
+ set_tas_condition(*req.mutable_condition(), msg.getCondition());
+ }
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::UpdateReply& msg) const {
+ encode_bucket_info_response<protobuf::UpdateResponse>(buf, msg, [&](auto& res) {
+ res.set_updated_timestamp(msg.getOldTimestamp());
+ });
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeUpdateCommand(BBuf& buf) const {
+ return decode_bucket_request<protobuf::UpdateRequest>(buf, [&](auto& req, auto& bucket) {
+ auto update = get_update(req.update(), type_repo());
+ auto cmd = std::make_unique<api::UpdateCommand>(bucket, std::move(update), req.new_timestamp());
+ cmd->setOldTimestamp(req.expected_old_timestamp());
+ if (req.has_condition()) {
+ cmd->setCondition(get_tas_condition(req.condition()));
+ }
+ return cmd;
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeUpdateReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_bucket_info_response<protobuf::UpdateResponse>(buf, [&](auto& res) {
+ return std::make_unique<api::UpdateReply>(static_cast<const api::UpdateCommand&>(cmd),
+ res.updated_timestamp());
+ });
+}
+
+// -----------------------------------------------------------------
+// Remove
+// -----------------------------------------------------------------
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::RemoveCommand& msg) const {
+ encode_bucket_request<protobuf::RemoveRequest>(buf, msg, [&](auto& req) {
+ auto doc_id_str = msg.getDocumentId().toString();
+ req.set_document_id(doc_id_str.data(), doc_id_str.size());
+ req.set_new_timestamp(msg.getTimestamp());
+ if (msg.getCondition().isPresent()) {
+ set_tas_condition(*req.mutable_condition(), msg.getCondition());
+ }
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::RemoveReply& msg) const {
+ encode_bucket_info_response<protobuf::RemoveResponse>(buf, msg, [&](auto& res) {
+ res.set_removed_timestamp(msg.getOldTimestamp());
+ });
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeRemoveCommand(BBuf& buf) const {
+ return decode_bucket_request<protobuf::RemoveRequest>(buf, [&](auto& req, auto& bucket) {
+ document::DocumentId doc_id(vespalib::stringref(req.document_id().data(), req.document_id().size()));
+ auto cmd = std::make_unique<api::RemoveCommand>(bucket, doc_id, req.new_timestamp());
+ if (req.has_condition()) {
+ cmd->setCondition(get_tas_condition(req.condition()));
+ }
+ return cmd;
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeRemoveReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_bucket_info_response<protobuf::RemoveResponse>(buf, [&](auto& res) {
+ return std::make_unique<api::RemoveReply>(static_cast<const api::RemoveCommand&>(cmd),
+ res.removed_timestamp());
+ });
+}
+
+// -----------------------------------------------------------------
+// Get
+// -----------------------------------------------------------------
+
+namespace {
+
+protobuf::GetRequest_InternalReadConsistency read_consistency_to_protobuf(api::InternalReadConsistency consistency) {
+ switch (consistency) {
+ case api::InternalReadConsistency::Strong: return protobuf::GetRequest_InternalReadConsistency_Strong;
+ case api::InternalReadConsistency::Weak: return protobuf::GetRequest_InternalReadConsistency_Weak;
+ default: return protobuf::GetRequest_InternalReadConsistency_Strong;
+ }
+}
+
+api::InternalReadConsistency read_consistency_from_protobuf(protobuf::GetRequest_InternalReadConsistency consistency) {
+ switch (consistency) {
+ case protobuf::GetRequest_InternalReadConsistency_Strong: return api::InternalReadConsistency::Strong;
+ case protobuf::GetRequest_InternalReadConsistency_Weak: return api::InternalReadConsistency::Weak;
+ default: return api::InternalReadConsistency::Strong;
+ }
+}
+
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::GetCommand& msg) const {
+ encode_bucket_request<protobuf::GetRequest>(buf, msg, [&](auto& req) {
+ auto doc_id = msg.getDocumentId().toString();
+ req.set_document_id(doc_id.data(), doc_id.size());
+ req.set_before_timestamp(msg.getBeforeTimestamp());
+ if (!msg.getFieldSet().empty()) {
+ req.set_field_set(msg.getFieldSet().data(), msg.getFieldSet().size());
+ }
+ req.set_internal_read_consistency(read_consistency_to_protobuf(msg.internal_read_consistency()));
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::GetReply& msg) const {
+ encode_bucket_info_response<protobuf::GetResponse>(buf, msg, [&](auto& res) {
+ if (msg.getDocument()) {
+ set_document(*res.mutable_document(), *msg.getDocument());
+ }
+ if (!msg.is_tombstone()) {
+ res.set_last_modified_timestamp(msg.getLastModifiedTimestamp());
+ } else {
+ // This field will be ignored by older versions, making the behavior as if
+ // a timestamp of zero was returned for tombstones, as it the legacy behavior.
+ res.set_tombstone_timestamp(msg.getLastModifiedTimestamp());
+ // Will not be encoded onto the wire, but we include it here to hammer down the
+ // point that it's intentional to have the last modified time appear as a not
+ // found document for older versions.
+ res.set_last_modified_timestamp(0);
+ }
+ });
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeGetCommand(BBuf& buf) const {
+ return decode_bucket_request<protobuf::GetRequest>(buf, [&](auto& req, auto& bucket) {
+ document::DocumentId doc_id(vespalib::stringref(req.document_id().data(), req.document_id().size()));
+ auto op = std::make_unique<api::GetCommand>(bucket, std::move(doc_id),
+ req.field_set(), req.before_timestamp());
+ op->set_internal_read_consistency(read_consistency_from_protobuf(req.internal_read_consistency()));
+ return op;
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeGetReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_bucket_info_response<protobuf::GetResponse>(buf, [&](auto& res) {
+ try {
+ auto document = get_document(res.document(), type_repo());
+ const bool is_tombstone = (res.tombstone_timestamp() != 0);
+ const auto effective_timestamp = (is_tombstone ? res.tombstone_timestamp()
+ : res.last_modified_timestamp());
+ return std::make_unique<api::GetReply>(static_cast<const api::GetCommand&>(cmd),
+ std::move(document), effective_timestamp,
+ false, is_tombstone);
+ } catch (std::exception& e) {
+ auto reply = std::make_unique<api::GetReply>(static_cast<const api::GetCommand&>(cmd),
+ std::shared_ptr<document::Document>(), 0u);
+ reply->setResult(api::ReturnCode(api::ReturnCode::UNPARSEABLE, e.what()));
+ return reply;
+ }
+ });
+}
+
+// -----------------------------------------------------------------
+// Revert
+// -----------------------------------------------------------------
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::RevertCommand& msg) const {
+ encode_bucket_request<protobuf::RevertRequest>(buf, msg, [&](auto& req) {
+ auto* tokens = req.mutable_revert_tokens();
+ assert(msg.getRevertTokens().size() <= INT_MAX);
+ tokens->Reserve(static_cast<int>(msg.getRevertTokens().size()));
+ for (auto token : msg.getRevertTokens()) {
+ tokens->Add(token);
+ }
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::RevertReply& msg) const {
+ encode_bucket_info_response<protobuf::RevertResponse>(buf, msg, no_op_encode);
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeRevertCommand(BBuf& buf) const {
+ return decode_bucket_request<protobuf::RevertRequest>(buf, [&](auto& req, auto& bucket) {
+ std::vector<api::Timestamp> tokens;
+ tokens.reserve(req.revert_tokens_size());
+ for (auto token : req.revert_tokens()) {
+ tokens.emplace_back(api::Timestamp(token));
+ }
+ return std::make_unique<api::RevertCommand>(bucket, std::move(tokens));
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeRevertReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_bucket_info_response<protobuf::RevertResponse>(buf, [&]([[maybe_unused]] auto& res) {
+ return std::make_unique<api::RevertReply>(static_cast<const api::RevertCommand&>(cmd));
+ });
+}
+
+// -----------------------------------------------------------------
+// RemoveLocation
+// -----------------------------------------------------------------
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::RemoveLocationCommand& msg) const {
+ encode_bucket_request<protobuf::RemoveLocationRequest>(buf, msg, [&](auto& req) {
+ req.set_document_selection(msg.getDocumentSelection().data(), msg.getDocumentSelection().size());
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::RemoveLocationReply& msg) const {
+ encode_bucket_info_response<protobuf::RemoveLocationResponse>(buf, msg, [&](auto& res) {
+ res.mutable_stats()->set_documents_removed(msg.documents_removed());
+ });
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeRemoveLocationCommand(BBuf& buf) const {
+ return decode_bucket_request<protobuf::RemoveLocationRequest>(buf, [&](auto& req, auto& bucket) {
+ return std::make_unique<api::RemoveLocationCommand>(req.document_selection(), bucket);
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeRemoveLocationReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_bucket_info_response<protobuf::RemoveLocationResponse>(buf, [&](auto& res) {
+ uint32_t documents_removed = (res.has_stats() ? res.stats().documents_removed() : 0u);
+ return std::make_unique<api::RemoveLocationReply>(
+ static_cast<const api::RemoveLocationCommand&>(cmd),
+ documents_removed);
+ });
+}
+
+// -----------------------------------------------------------------
+// DeleteBucket
+// -----------------------------------------------------------------
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::DeleteBucketCommand& msg) const {
+ encode_bucket_request<protobuf::DeleteBucketRequest>(buf, msg, [&](auto& req) {
+ set_bucket_info(*req.mutable_expected_bucket_info(), msg.getBucketInfo());
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::DeleteBucketReply& msg) const {
+ encode_bucket_info_response<protobuf::DeleteBucketResponse>(buf, msg, no_op_encode);
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeDeleteBucketCommand(BBuf& buf) const {
+ return decode_bucket_request<protobuf::DeleteBucketRequest>(buf, [&](auto& req, auto& bucket) {
+ auto cmd = std::make_unique<api::DeleteBucketCommand>(bucket);
+ if (req.has_expected_bucket_info()) {
+ cmd->setBucketInfo(get_bucket_info(req.expected_bucket_info()));
+ }
+ return cmd;
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeDeleteBucketReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_bucket_info_response<protobuf::DeleteBucketResponse>(buf, [&]([[maybe_unused]] auto& res) {
+ return std::make_unique<api::DeleteBucketReply>(static_cast<const api::DeleteBucketCommand&>(cmd));
+ });
+}
+
+// -----------------------------------------------------------------
+// CreateBucket
+// -----------------------------------------------------------------
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::CreateBucketCommand& msg) const {
+ encode_bucket_request<protobuf::CreateBucketRequest>(buf, msg, [&](auto& req) {
+ req.set_create_as_active(msg.getActive());
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::CreateBucketReply& msg) const {
+ encode_bucket_info_response<protobuf::CreateBucketResponse>(buf, msg, no_op_encode);
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeCreateBucketCommand(BBuf& buf) const {
+ return decode_bucket_request<protobuf::CreateBucketRequest>(buf, [&](auto& req, auto& bucket) {
+ auto cmd = std::make_unique<api::CreateBucketCommand>(bucket);
+ cmd->setActive(req.create_as_active());
+ return cmd;
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeCreateBucketReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_bucket_info_response<protobuf::CreateBucketResponse>(buf, [&]([[maybe_unused]] auto& res) {
+ return std::make_unique<api::CreateBucketReply>(static_cast<const api::CreateBucketCommand&>(cmd));
+ });
+}
+
+// -----------------------------------------------------------------
+// MergeBucket
+// -----------------------------------------------------------------
+
+namespace {
+
+void set_merge_nodes(::google::protobuf::RepeatedPtrField<protobuf::MergeNode>& dest,
+ const std::vector<api::MergeBucketCommand::Node>& src)
+{
+ dest.Reserve(src.size());
+ for (const auto& src_node : src) {
+ auto* dest_node = dest.Add();
+ dest_node->set_index(src_node.index);
+ dest_node->set_source_only(src_node.sourceOnly);
+ }
+}
+
+std::vector<api::MergeBucketCommand::Node> get_merge_nodes(
+ const ::google::protobuf::RepeatedPtrField<protobuf::MergeNode>& src)
+{
+ std::vector<api::MergeBucketCommand::Node> nodes;
+ nodes.reserve(src.size());
+ for (const auto& node : src) {
+ nodes.emplace_back(node.index(), node.source_only());
+ }
+ return nodes;
+}
+
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::MergeBucketCommand& msg) const {
+ encode_bucket_request<protobuf::MergeBucketRequest>(buf, msg, [&](auto& req) {
+ set_merge_nodes(*req.mutable_nodes(), msg.getNodes());
+ req.set_max_timestamp(msg.getMaxTimestamp());
+ req.set_cluster_state_version(msg.getClusterStateVersion());
+ req.set_unordered_forwarding(msg.use_unordered_forwarding());
+ for (uint16_t chain_node : msg.getChain()) {
+ req.add_node_chain(chain_node);
+ }
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::MergeBucketReply& msg) const {
+ encode_bucket_response<protobuf::MergeBucketResponse>(buf, msg, no_op_encode);
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeMergeBucketCommand(BBuf& buf) const {
+ return decode_bucket_request<protobuf::MergeBucketRequest>(buf, [&](auto& req, auto& bucket) {
+ auto nodes = get_merge_nodes(req.nodes());
+ auto cmd = std::make_unique<api::MergeBucketCommand>(bucket, std::move(nodes), req.max_timestamp());
+ cmd->setClusterStateVersion(req.cluster_state_version());
+ std::vector<uint16_t> chain;
+ chain.reserve(req.node_chain_size());
+ for (uint16_t node : req.node_chain()) {
+ chain.emplace_back(node);
+ }
+ cmd->setChain(std::move(chain));
+ cmd->set_use_unordered_forwarding(req.unordered_forwarding());
+ return cmd;
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeMergeBucketReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_bucket_response<protobuf::MergeBucketResponse>(buf, [&]([[maybe_unused]] auto& res) {
+ return std::make_unique<api::MergeBucketReply>(static_cast<const api::MergeBucketCommand&>(cmd));
+ });
+}
+
+// -----------------------------------------------------------------
+// GetBucketDiff
+// -----------------------------------------------------------------
+
+namespace {
+
+void set_global_id(protobuf::GlobalId& dest, const document::GlobalId& src) {
+ static_assert(document::GlobalId::LENGTH == 12);
+ uint64_t lo64;
+ uint32_t hi32;
+ memcpy(&lo64, src.get(), sizeof(uint64_t));
+ memcpy(&hi32, src.get() + sizeof(uint64_t), sizeof(uint32_t));
+ dest.set_lo_64(lo64);
+ dest.set_hi_32(hi32);
+}
+
+document::GlobalId get_global_id(const protobuf::GlobalId& src) {
+ static_assert(document::GlobalId::LENGTH == 12);
+ const uint64_t lo64 = src.lo_64();
+ const uint32_t hi32 = src.hi_32();
+
+ char buf[document::GlobalId::LENGTH];
+ memcpy(buf, &lo64, sizeof(uint64_t));
+ memcpy(buf + sizeof(uint64_t), &hi32, sizeof(uint32_t));
+ return document::GlobalId(buf);
+}
+
+void set_diff_entry(protobuf::MetaDiffEntry& dest, const api::GetBucketDiffCommand::Entry& src) {
+ dest.set_timestamp(src._timestamp);
+ set_global_id(*dest.mutable_gid(), src._gid);
+ dest.set_header_size(src._headerSize);
+ dest.set_body_size(src._bodySize);
+ dest.set_flags(src._flags);
+ dest.set_presence_mask(src._hasMask);
+}
+
+api::GetBucketDiffCommand::Entry get_diff_entry(const protobuf::MetaDiffEntry& src) {
+ api::GetBucketDiffCommand::Entry e;
+ e._timestamp = src.timestamp();
+ e._gid = get_global_id(src.gid());
+ e._headerSize = src.header_size();
+ e._bodySize = src.body_size();
+ e._flags = src.flags();
+ e._hasMask = src.presence_mask();
+ return e;
+}
+
+void fill_proto_meta_diff(::google::protobuf::RepeatedPtrField<protobuf::MetaDiffEntry>& dest,
+ const std::vector<api::GetBucketDiffCommand::Entry>& src) {
+ for (const auto& diff_entry : src) {
+ set_diff_entry(*dest.Add(), diff_entry);
+ }
+}
+
+void fill_api_meta_diff(std::vector<api::GetBucketDiffCommand::Entry>& dest,
+ const ::google::protobuf::RepeatedPtrField<protobuf::MetaDiffEntry>& src) {
+ // FIXME GetBucketDiffReply ctor copies the diff from the request for some reason
+ // TODO verify this isn't actually used anywhere and remove this "feature".
+ dest.clear();
+ dest.reserve(src.size());
+ for (const auto& diff_entry : src) {
+ dest.emplace_back(get_diff_entry(diff_entry));
+ }
+}
+
+} // anonymous namespace
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::GetBucketDiffCommand& msg) const {
+ encode_bucket_request<protobuf::GetBucketDiffRequest>(buf, msg, [&](auto& req) {
+ set_merge_nodes(*req.mutable_nodes(), msg.getNodes());
+ req.set_max_timestamp(msg.getMaxTimestamp());
+ fill_proto_meta_diff(*req.mutable_diff(), msg.getDiff());
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::GetBucketDiffReply& msg) const {
+ encode_bucket_response<protobuf::GetBucketDiffResponse>(buf, msg, [&](auto& res) {
+ fill_proto_meta_diff(*res.mutable_diff(), msg.getDiff());
+ });
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeGetBucketDiffCommand(BBuf& buf) const {
+ return decode_bucket_request<protobuf::GetBucketDiffRequest>(buf, [&](auto& req, auto& bucket) {
+ auto nodes = get_merge_nodes(req.nodes());
+ auto cmd = std::make_unique<api::GetBucketDiffCommand>(bucket, std::move(nodes), req.max_timestamp());
+ fill_api_meta_diff(cmd->getDiff(), req.diff());
+ return cmd;
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeGetBucketDiffReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_bucket_response<protobuf::GetBucketDiffResponse>(buf, [&](auto& res) {
+ auto reply = std::make_unique<api::GetBucketDiffReply>(static_cast<const api::GetBucketDiffCommand&>(cmd));
+ fill_api_meta_diff(reply->getDiff(), res.diff());
+ return reply;
+ });
+}
+
+// -----------------------------------------------------------------
+// ApplyBucketDiff
+// -----------------------------------------------------------------
+
+namespace {
+
+void fill_api_apply_diff_vector(std::vector<api::ApplyBucketDiffCommand::Entry>& diff,
+ const ::google::protobuf::RepeatedPtrField<protobuf::ApplyDiffEntry>& src)
+{
+ // We use the same approach as the legacy protocols here in that we pre-reserve and
+ // directly write into the vector. This avoids having to ensure all buffer management is movable.
+ size_t n_entries = src.size();
+ diff.resize(n_entries);
+ for (size_t i = 0; i < n_entries; ++i) {
+ auto& proto_entry = src.Get(i);
+ auto& dest = diff[i];
+ dest._entry = get_diff_entry(proto_entry.entry_meta());
+ dest._docName = proto_entry.document_id();
+ // TODO consider making buffers std::strings instead to avoid explicit zeroing-on-resize overhead
+ dest._headerBlob.resize(proto_entry.header_blob().size());
+ memcpy(dest._headerBlob.data(), proto_entry.header_blob().data(), proto_entry.header_blob().size());
+ dest._bodyBlob.resize(proto_entry.body_blob().size());
+ memcpy(dest._bodyBlob.data(), proto_entry.body_blob().data(), proto_entry.body_blob().size());
+ }
+}
+
+void fill_proto_apply_diff_vector(::google::protobuf::RepeatedPtrField<protobuf::ApplyDiffEntry>& dest,
+ const std::vector<api::ApplyBucketDiffCommand::Entry>& src)
+{
+ dest.Reserve(src.size());
+ for (const auto& entry : src) {
+ auto* proto_entry = dest.Add();
+ set_diff_entry(*proto_entry->mutable_entry_meta(), entry._entry);
+ proto_entry->set_document_id(entry._docName.data(), entry._docName.size());
+ proto_entry->set_header_blob(entry._headerBlob.data(), entry._headerBlob.size());
+ proto_entry->set_body_blob(entry._bodyBlob.data(), entry._bodyBlob.size());
+ }
+}
+
+} // anonymous namespace
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::ApplyBucketDiffCommand& msg) const {
+ encode_bucket_request<protobuf::ApplyBucketDiffRequest>(buf, msg, [&](auto& req) {
+ set_merge_nodes(*req.mutable_nodes(), msg.getNodes());
+ req.set_max_buffer_size(0x400000); // Unused, GC soon.
+ fill_proto_apply_diff_vector(*req.mutable_entries(), msg.getDiff());
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::ApplyBucketDiffReply& msg) const {
+ encode_bucket_response<protobuf::ApplyBucketDiffResponse>(buf, msg, [&](auto& res) {
+ fill_proto_apply_diff_vector(*res.mutable_entries(), msg.getDiff());
+ });
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeApplyBucketDiffCommand(BBuf& buf) const {
+ return decode_bucket_request<protobuf::ApplyBucketDiffRequest>(buf, [&](auto& req, auto& bucket) {
+ auto nodes = get_merge_nodes(req.nodes());
+ auto cmd = std::make_unique<api::ApplyBucketDiffCommand>(bucket, std::move(nodes));
+ fill_api_apply_diff_vector(cmd->getDiff(), req.entries());
+ return cmd;
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeApplyBucketDiffReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_bucket_response<protobuf::ApplyBucketDiffResponse>(buf, [&](auto& res) {
+ auto reply = std::make_unique<api::ApplyBucketDiffReply>(static_cast<const api::ApplyBucketDiffCommand&>(cmd));
+ fill_api_apply_diff_vector(reply->getDiff(), res.entries());
+ return reply;
+ });
+}
+
+// -----------------------------------------------------------------
+// RequestBucketInfo
+// -----------------------------------------------------------------
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::RequestBucketInfoCommand& msg) const {
+ encode_request<protobuf::RequestBucketInfoRequest>(buf, msg, [&](auto& req) {
+ set_bucket_space(*req.mutable_bucket_space(), msg.getBucketSpace());
+ auto& buckets = msg.getBuckets();
+ if (!buckets.empty()) {
+ auto* proto_buckets = req.mutable_explicit_bucket_set();
+ for (const auto& b : buckets) {
+ set_bucket_id(*proto_buckets->add_bucket_ids(), b);
+ }
+ } else {
+ auto* all_buckets = req.mutable_all_buckets();
+ auto cluster_state = msg.getSystemState().toString();
+ all_buckets->set_distributor_index(msg.getDistributor());
+ all_buckets->set_cluster_state(cluster_state.data(), cluster_state.size());
+ all_buckets->set_distribution_hash(msg.getDistributionHash().data(), msg.getDistributionHash().size());
+ }
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::RequestBucketInfoReply& msg) const {
+ encode_response<protobuf::RequestBucketInfoResponse>(buf, msg, [&](auto& res) {
+ auto* proto_info = res.mutable_bucket_infos();
+ proto_info->Reserve(msg.getBucketInfo().size());
+ for (const auto& entry : msg.getBucketInfo()) {
+ auto* bucket_and_info = proto_info->Add();
+ bucket_and_info->set_raw_bucket_id(entry._bucketId.getRawId());
+ set_bucket_info(*bucket_and_info->mutable_bucket_info(), entry._info);
+ }
+ // We mark features as available at protocol level. Only included for full bucket fetch responses.
+ if (msg.full_bucket_fetch()) {
+ res.mutable_supported_node_features()->set_unordered_merge_chaining(true);
+ }
+ });
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeRequestBucketInfoCommand(BBuf& buf) const {
+ return decode_request<protobuf::RequestBucketInfoRequest>(buf, [&](auto& req) {
+ auto bucket_space = get_bucket_space(req.bucket_space());
+ if (req.has_explicit_bucket_set()) {
+ const uint32_t n_buckets = req.explicit_bucket_set().bucket_ids_size();
+ std::vector<document::BucketId> buckets(n_buckets);
+ const auto& proto_buckets = req.explicit_bucket_set().bucket_ids();
+ for (uint32_t i = 0; i < n_buckets; ++i) {
+ buckets[i] = get_bucket_id(proto_buckets.Get(i));
+ }
+ return std::make_unique<api::RequestBucketInfoCommand>(bucket_space, std::move(buckets));
+ } else if (req.has_all_buckets()) {
+ const auto& all_req = req.all_buckets();
+ return std::make_unique<api::RequestBucketInfoCommand>(
+ bucket_space, all_req.distributor_index(),
+ lib::ClusterState(all_req.cluster_state()), all_req.distribution_hash());
+ } else {
+ throw vespalib::IllegalArgumentException("RequestBucketInfo does not have any applicable fields set");
+ }
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeRequestBucketInfoReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_response<protobuf::RequestBucketInfoResponse>(buf, [&](auto& res) {
+ auto reply = std::make_unique<api::RequestBucketInfoReply>(static_cast<const api::RequestBucketInfoCommand&>(cmd));
+ auto& dest_entries = reply->getBucketInfo();
+ uint32_t n_entries = res.bucket_infos_size();
+ dest_entries.resize(n_entries);
+ for (uint32_t i = 0; i < n_entries; ++i) {
+ const auto& proto_entry = res.bucket_infos(i);
+ dest_entries[i]._bucketId = document::BucketId(proto_entry.raw_bucket_id());
+ dest_entries[i]._info = get_bucket_info(proto_entry.bucket_info());
+ }
+ if (res.has_supported_node_features()) {
+ const auto& src_features = res.supported_node_features();
+ auto& dest_features = reply->supported_node_features();
+ dest_features.unordered_merge_chaining = src_features.unordered_merge_chaining();
+ }
+ return reply;
+ });
+}
+
+// -----------------------------------------------------------------
+// NotifyBucketChange
+// -----------------------------------------------------------------
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::NotifyBucketChangeCommand& msg) const {
+ encode_bucket_request<protobuf::NotifyBucketChangeRequest>(buf, msg, [&](auto& req) {
+ set_bucket_info(*req.mutable_bucket_info(), msg.getBucketInfo());
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::NotifyBucketChangeReply& msg) const {
+ encode_response<protobuf::NotifyBucketChangeResponse>(buf, msg, no_op_encode);
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeNotifyBucketChangeCommand(BBuf& buf) const {
+ return decode_bucket_request<protobuf::NotifyBucketChangeRequest>(buf, [&](auto& req, auto& bucket) {
+ auto bucket_info = get_bucket_info(req.bucket_info());
+ return std::make_unique<api::NotifyBucketChangeCommand>(bucket, bucket_info);
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeNotifyBucketChangeReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_response<protobuf::NotifyBucketChangeResponse>(buf, [&]([[maybe_unused]] auto& res) {
+ return std::make_unique<api::NotifyBucketChangeReply>(static_cast<const api::NotifyBucketChangeCommand&>(cmd));
+ });
+}
+
+// -----------------------------------------------------------------
+// SplitBucket
+// -----------------------------------------------------------------
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::SplitBucketCommand& msg) const {
+ encode_bucket_request<protobuf::SplitBucketRequest>(buf, msg, [&](auto& req) {
+ req.set_min_split_bits(msg.getMinSplitBits());
+ req.set_max_split_bits(msg.getMaxSplitBits());
+ req.set_min_byte_size(msg.getMinByteSize());
+ req.set_min_doc_count(msg.getMinDocCount());
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::SplitBucketReply& msg) const {
+ encode_bucket_response<protobuf::SplitBucketResponse>(buf, msg, [&](auto& res) {
+ for (const auto& split_info : msg.getSplitInfo()) {
+ auto* proto_info = res.add_split_info();
+ proto_info->set_raw_bucket_id(split_info.first.getRawId());
+ set_bucket_info(*proto_info->mutable_bucket_info(), split_info.second);
+ }
+ });
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeSplitBucketCommand(BBuf& buf) const {
+ return decode_bucket_request<protobuf::SplitBucketRequest>(buf, [&](auto& req, auto& bucket) {
+ auto cmd = std::make_unique<api::SplitBucketCommand>(bucket);
+ cmd->setMinSplitBits(static_cast<uint8_t>(req.min_split_bits()));
+ cmd->setMaxSplitBits(static_cast<uint8_t>(req.max_split_bits()));
+ cmd->setMinByteSize(req.min_byte_size());
+ cmd->setMinDocCount(req.min_doc_count());
+ return cmd;
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeSplitBucketReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_bucket_response<protobuf::SplitBucketResponse>(buf, [&](auto& res) {
+ auto reply = std::make_unique<api::SplitBucketReply>(static_cast<const api::SplitBucketCommand&>(cmd));
+ auto& dest_info = reply->getSplitInfo();
+ dest_info.reserve(res.split_info_size());
+ for (const auto& proto_info : res.split_info()) {
+ dest_info.emplace_back(document::BucketId(proto_info.raw_bucket_id()),
+ get_bucket_info(proto_info.bucket_info()));
+ }
+ return reply;
+ });
+}
+
+// -----------------------------------------------------------------
+// JoinBuckets
+// -----------------------------------------------------------------
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::JoinBucketsCommand& msg) const {
+ encode_bucket_request<protobuf::JoinBucketsRequest>(buf, msg, [&](auto& req) {
+ for (const auto& source : msg.getSourceBuckets()) {
+ set_bucket_id(*req.add_source_buckets(), source);
+ }
+ req.set_min_join_bits(msg.getMinJoinBits());
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::JoinBucketsReply& msg) const {
+ encode_bucket_info_response<protobuf::JoinBucketsResponse>(buf, msg, no_op_encode);
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeJoinBucketsCommand(BBuf& buf) const {
+ return decode_bucket_request<protobuf::JoinBucketsRequest>(buf, [&](auto& req, auto& bucket) {
+ auto cmd = std::make_unique<api::JoinBucketsCommand>(bucket);
+ auto& entries = cmd->getSourceBuckets();
+ for (const auto& proto_bucket : req.source_buckets()) {
+ entries.emplace_back(get_bucket_id(proto_bucket));
+ }
+ cmd->setMinJoinBits(static_cast<uint8_t>(req.min_join_bits()));
+ return cmd;
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeJoinBucketsReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_bucket_info_response<protobuf::JoinBucketsResponse>(buf, [&]([[maybe_unused]] auto& res) {
+ return std::make_unique<api::JoinBucketsReply>(static_cast<const api::JoinBucketsCommand&>(cmd));
+ });
+}
+
+// -----------------------------------------------------------------
+// SetBucketState
+// -----------------------------------------------------------------
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::SetBucketStateCommand& msg) const {
+ encode_bucket_request<protobuf::SetBucketStateRequest>(buf, msg, [&](auto& req) {
+ auto state = (msg.getState() == api::SetBucketStateCommand::BUCKET_STATE::ACTIVE
+ ? protobuf::SetBucketStateRequest_BucketState_Active
+ : protobuf::SetBucketStateRequest_BucketState_Inactive);
+ req.set_state(state);
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::SetBucketStateReply& msg) const {
+ // SetBucketStateReply is _technically_ a BucketInfoReply, but the legacy protocol impls
+ // do _not_ encode bucket info as part of the wire format (and it's not used on the distributor),
+ // so we follow that here and only encode remapping information.
+ encode_bucket_response<protobuf::SetBucketStateResponse>(buf, msg, no_op_encode);
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeSetBucketStateCommand(BBuf& buf) const {
+ return decode_bucket_request<protobuf::SetBucketStateRequest>(buf, [&](auto& req, auto& bucket) {
+ auto state = (req.state() == protobuf::SetBucketStateRequest_BucketState_Active
+ ? api::SetBucketStateCommand::BUCKET_STATE::ACTIVE
+ : api::SetBucketStateCommand::BUCKET_STATE::INACTIVE);
+ return std::make_unique<api::SetBucketStateCommand>(bucket, state);
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeSetBucketStateReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_bucket_response<protobuf::SetBucketStateResponse>(buf, [&]([[maybe_unused]] auto& res) {
+ return std::make_unique<api::SetBucketStateReply>(static_cast<const api::SetBucketStateCommand&>(cmd));
+ });
+}
+
+// -----------------------------------------------------------------
+// CreateVisitor
+// -----------------------------------------------------------------
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::CreateVisitorCommand& msg) const {
+ encode_request<protobuf::CreateVisitorRequest>(buf, msg, [&](auto& req) {
+ set_bucket_space(*req.mutable_bucket_space(), msg.getBucketSpace());
+ for (const auto& bucket : msg.getBuckets()) {
+ set_bucket_id(*req.add_buckets(), bucket);
+ }
+
+ auto* ctrl_meta = req.mutable_control_meta();
+ ctrl_meta->set_library_name(msg.getLibraryName().data(), msg.getLibraryName().size());
+ ctrl_meta->set_instance_id(msg.getInstanceId().data(), msg.getInstanceId().size());
+ ctrl_meta->set_visitor_command_id(msg.getVisitorCmdId());
+ ctrl_meta->set_control_destination(msg.getControlDestination().data(), msg.getControlDestination().size());
+ ctrl_meta->set_data_destination(msg.getDataDestination().data(), msg.getDataDestination().size());
+ ctrl_meta->set_queue_timeout(vespalib::count_ms(msg.getQueueTimeout()));
+ ctrl_meta->set_max_pending_reply_count(msg.getMaximumPendingReplyCount());
+ ctrl_meta->set_max_buckets_per_visitor(msg.getMaxBucketsPerVisitor());
+
+ auto* constraints = req.mutable_constraints();
+ constraints->set_document_selection(msg.getDocumentSelection().data(), msg.getDocumentSelection().size());
+ constraints->set_from_time_usec(msg.getFromTime());
+ constraints->set_to_time_usec(msg.getToTime());
+ constraints->set_visit_inconsistent_buckets(msg.visitInconsistentBuckets());
+ constraints->set_visit_removes(msg.visitRemoves());
+ constraints->set_field_set(msg.getFieldSet().data(), msg.getFieldSet().size());
+
+ for (const auto& param : msg.getParameters()) {
+ auto* proto_param = req.add_client_parameters();
+ proto_param->set_key(param.first.data(), param.first.size());
+ proto_param->set_value(param.second.data(), param.second.size());
+ }
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::CreateVisitorReply& msg) const {
+ encode_response<protobuf::CreateVisitorResponse>(buf, msg, [&](auto& res) {
+ auto& stats = msg.getVisitorStatistics();
+ auto* proto_stats = res.mutable_visitor_statistics();
+ proto_stats->set_buckets_visited(stats.getBucketsVisited());
+ proto_stats->set_documents_visited(stats.getDocumentsVisited());
+ proto_stats->set_bytes_visited(stats.getBytesVisited());
+ proto_stats->set_documents_returned(stats.getDocumentsReturned());
+ proto_stats->set_bytes_returned(stats.getBytesReturned());
+ proto_stats->set_second_pass_documents_returned(stats.getSecondPassDocumentsReturned()); // TODO remove on Vespa 8
+ proto_stats->set_second_pass_bytes_returned(stats.getSecondPassBytesReturned()); // TODO remove on Vespa 8
+ });
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeCreateVisitorCommand(BBuf& buf) const {
+ return decode_request<protobuf::CreateVisitorRequest>(buf, [&](auto& req) {
+ auto bucket_space = get_bucket_space(req.bucket_space());
+ auto& ctrl_meta = req.control_meta();
+ auto& constraints = req.constraints();
+ auto cmd = std::make_unique<api::CreateVisitorCommand>(bucket_space, ctrl_meta.library_name(),
+ ctrl_meta.instance_id(), constraints.document_selection());
+ for (const auto& proto_bucket : req.buckets()) {
+ cmd->getBuckets().emplace_back(get_bucket_id(proto_bucket));
+ }
+
+ cmd->setVisitorCmdId(ctrl_meta.visitor_command_id());
+ cmd->setControlDestination(ctrl_meta.control_destination());
+ cmd->setDataDestination(ctrl_meta.data_destination());
+ cmd->setMaximumPendingReplyCount(ctrl_meta.max_pending_reply_count());
+ cmd->setQueueTimeout(std::chrono::milliseconds(ctrl_meta.queue_timeout()));
+ cmd->setMaxBucketsPerVisitor(ctrl_meta.max_buckets_per_visitor());
+ cmd->setVisitorDispatcherVersion(50); // FIXME this magic number is lifted verbatim from the 5.1 protocol impl
+
+ for (const auto& proto_param : req.client_parameters()) {
+ cmd->getParameters().set(proto_param.key(), proto_param.value());
+ }
+
+ cmd->setFromTime(constraints.from_time_usec());
+ cmd->setToTime(constraints.to_time_usec());
+ cmd->setVisitRemoves(constraints.visit_removes());
+ cmd->setFieldSet(constraints.field_set());
+ cmd->setVisitInconsistentBuckets(constraints.visit_inconsistent_buckets());
+ return cmd;
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeCreateVisitorReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_response<protobuf::CreateVisitorResponse>(buf, [&](auto& res) {
+ auto reply = std::make_unique<api::CreateVisitorReply>(static_cast<const api::CreateVisitorCommand&>(cmd));
+ vdslib::VisitorStatistics vs;
+ const auto& proto_stats = res.visitor_statistics();
+ vs.setBucketsVisited(proto_stats.buckets_visited());
+ vs.setDocumentsVisited(proto_stats.documents_visited());
+ vs.setBytesVisited(proto_stats.bytes_visited());
+ vs.setDocumentsReturned(proto_stats.documents_returned());
+ vs.setBytesReturned(proto_stats.bytes_returned());
+ vs.setSecondPassDocumentsReturned(proto_stats.second_pass_documents_returned()); // TODO remove on Vespa 8
+ vs.setSecondPassBytesReturned(proto_stats.second_pass_bytes_returned()); // TODO remove on Vespa 8
+ reply->setVisitorStatistics(vs);
+ return reply;
+ });
+}
+
+// -----------------------------------------------------------------
+// DestroyVisitor
+// -----------------------------------------------------------------
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::DestroyVisitorCommand& msg) const {
+ encode_request<protobuf::DestroyVisitorRequest>(buf, msg, [&](auto& req) {
+ req.set_instance_id(msg.getInstanceId().data(), msg.getInstanceId().size());
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::DestroyVisitorReply& msg) const {
+ encode_response<protobuf::DestroyVisitorResponse>(buf, msg, no_op_encode);
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeDestroyVisitorCommand(BBuf& buf) const {
+ return decode_request<protobuf::DestroyVisitorRequest>(buf, [&](auto& req) {
+ return std::make_unique<api::DestroyVisitorCommand>(req.instance_id());
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeDestroyVisitorReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_response<protobuf::DestroyVisitorResponse>(buf, [&]([[maybe_unused]] auto& res) {
+ return std::make_unique<api::DestroyVisitorReply>(static_cast<const api::DestroyVisitorCommand&>(cmd));
+ });
+}
+
+// -----------------------------------------------------------------
+// StatBucket
+// -----------------------------------------------------------------
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::StatBucketCommand& msg) const {
+ encode_bucket_request<protobuf::StatBucketRequest>(buf, msg, [&](auto& req) {
+ req.set_document_selection(msg.getDocumentSelection().data(), msg.getDocumentSelection().size());
+ });
+}
+
+void ProtocolSerialization7::onEncode(GBBuf& buf, const api::StatBucketReply& msg) const {
+ encode_bucket_response<protobuf::StatBucketResponse>(buf, msg, [&](auto& res) {
+ res.set_results(msg.getResults().data(), msg.getResults().size());
+ });
+}
+
+api::StorageCommand::UP ProtocolSerialization7::onDecodeStatBucketCommand(BBuf& buf) const {
+ return decode_bucket_request<protobuf::StatBucketRequest>(buf, [&](auto& req, auto& bucket) {
+ return std::make_unique<api::StatBucketCommand>(bucket, req.document_selection());
+ });
+}
+
+api::StorageReply::UP ProtocolSerialization7::onDecodeStatBucketReply(const SCmd& cmd, BBuf& buf) const {
+ return decode_bucket_response<protobuf::StatBucketResponse>(buf, [&](auto& res) {
+ return std::make_unique<api::StatBucketReply>(static_cast<const api::StatBucketCommand&>(cmd), res.results());
+ });
+}
+
+} // storage::mbusprot
diff --git a/storage/src/vespa/storageapi/mbusprot/protocolserialization7.h b/storage/src/vespa/storageapi/mbusprot/protocolserialization7.h
new file mode 100644
index 00000000000..a61397c85ac
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/protocolserialization7.h
@@ -0,0 +1,147 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "protocolserialization.h"
+
+namespace storage::mbusprot {
+
+/**
+ * Protocol serialization version that uses Protocol Buffers for all its binary
+ * encoding and decoding.
+ */
+class ProtocolSerialization7 final : public ProtocolSerialization {
+ const std::shared_ptr<const document::DocumentTypeRepo> _repo;
+public:
+ explicit ProtocolSerialization7(std::shared_ptr<const document::DocumentTypeRepo> repo);
+
+ const document::DocumentTypeRepo& type_repo() const noexcept { return *_repo; }
+
+ // Put
+ void onEncode(GBBuf&, const api::PutCommand&) const override;
+ void onEncode(GBBuf&, const api::PutReply&) const override;
+ SCmd::UP onDecodePutCommand(BBuf&) const override;
+ SRep::UP onDecodePutReply(const SCmd&, BBuf&) const override;
+
+ // Update
+ void onEncode(GBBuf&, const api::UpdateCommand&) const override;
+ void onEncode(GBBuf&, const api::UpdateReply&) const override;
+ SCmd::UP onDecodeUpdateCommand(BBuf&) const override;
+ SRep::UP onDecodeUpdateReply(const SCmd&, BBuf&) const override;
+
+ // Remove
+ void onEncode(GBBuf&, const api::RemoveCommand&) const override;
+ void onEncode(GBBuf&, const api::RemoveReply&) const override;
+ SCmd::UP onDecodeRemoveCommand(BBuf&) const override;
+ SRep::UP onDecodeRemoveReply(const SCmd&, BBuf&) const override;
+
+ // Get
+ void onEncode(GBBuf&, const api::GetCommand&) const override;
+ void onEncode(GBBuf&, const api::GetReply&) const override;
+ SCmd::UP onDecodeGetCommand(BBuf&) const override;
+ SRep::UP onDecodeGetReply(const SCmd&, BBuf&) const override;
+
+ // Revert - TODO this is deprecated, no?
+ void onEncode(GBBuf&, const api::RevertCommand&) const override;
+ void onEncode(GBBuf&, const api::RevertReply&) const override;
+ SCmd::UP onDecodeRevertCommand(BBuf&) const override;
+ SRep::UP onDecodeRevertReply(const SCmd&, BBuf&) const override;
+
+ // DeleteBucket
+ void onEncode(GBBuf&, const api::DeleteBucketCommand&) const override;
+ void onEncode(GBBuf&, const api::DeleteBucketReply&) const override;
+ SCmd::UP onDecodeDeleteBucketCommand(BBuf&) const override;
+ SRep::UP onDecodeDeleteBucketReply(const SCmd&, BBuf&) const override;
+
+ // CreateBucket
+ void onEncode(GBBuf&, const api::CreateBucketCommand&) const override;
+ void onEncode(GBBuf&, const api::CreateBucketReply&) const override;
+ SCmd::UP onDecodeCreateBucketCommand(BBuf&) const override;
+ SRep::UP onDecodeCreateBucketReply(const SCmd&, BBuf&) const override;
+
+ // MergeBucket
+ void onEncode(GBBuf&, const api::MergeBucketCommand&) const override;
+ void onEncode(GBBuf&, const api::MergeBucketReply&) const override;
+ SCmd::UP onDecodeMergeBucketCommand(BBuf&) const override;
+ SRep::UP onDecodeMergeBucketReply(const SCmd&, BBuf&) const override;
+
+ // GetBucketDiff
+ void onEncode(GBBuf&, const api::GetBucketDiffCommand&) const override;
+ void onEncode(GBBuf&, const api::GetBucketDiffReply&) const override;
+ SCmd::UP onDecodeGetBucketDiffCommand(BBuf&) const override;
+ SRep::UP onDecodeGetBucketDiffReply(const SCmd&, BBuf&) const override;
+
+ // ApplyBucketDiff
+ void onEncode(GBBuf&, const api::ApplyBucketDiffCommand&) const override;
+ void onEncode(GBBuf&, const api::ApplyBucketDiffReply&) const override;
+ SCmd::UP onDecodeApplyBucketDiffCommand(BBuf&) const override;
+ SRep::UP onDecodeApplyBucketDiffReply(const SCmd&, BBuf&) const override;
+
+ // RequestBucketInfo
+ void onEncode(GBBuf&, const api::RequestBucketInfoCommand&) const override;
+ void onEncode(GBBuf&, const api::RequestBucketInfoReply&) const override;
+ SCmd::UP onDecodeRequestBucketInfoCommand(BBuf&) const override;
+ SRep::UP onDecodeRequestBucketInfoReply(const SCmd&, BBuf&) const override;
+
+ // NotifyBucketChange
+ void onEncode(GBBuf&, const api::NotifyBucketChangeCommand&) const override;
+ void onEncode(GBBuf&, const api::NotifyBucketChangeReply&) const override;
+ SCmd::UP onDecodeNotifyBucketChangeCommand(BBuf&) const override;
+ SRep::UP onDecodeNotifyBucketChangeReply(const SCmd&, BBuf&) const override;
+
+ // SplitBucket
+ void onEncode(GBBuf&, const api::SplitBucketCommand&) const override;
+ void onEncode(GBBuf&, const api::SplitBucketReply&) const override;
+ SCmd::UP onDecodeSplitBucketCommand(BBuf&) const override;
+ SRep::UP onDecodeSplitBucketReply(const SCmd&, BBuf&) const override;
+
+ // JoinBuckets
+ void onEncode(GBBuf&, const api::JoinBucketsCommand&) const override;
+ void onEncode(GBBuf&, const api::JoinBucketsReply&) const override;
+ SCmd::UP onDecodeJoinBucketsCommand(BBuf&) const override;
+ SRep::UP onDecodeJoinBucketsReply(const SCmd&, BBuf&) const override;
+
+ // SetBucketState
+ void onEncode(GBBuf&, const api::SetBucketStateCommand&) const override;
+ void onEncode(GBBuf&, const api::SetBucketStateReply&) const override;
+ SCmd::UP onDecodeSetBucketStateCommand(BBuf&) const override;
+ SRep::UP onDecodeSetBucketStateReply(const SCmd&, BBuf&) const override;
+
+ // CreateVisitor
+ void onEncode(GBBuf&, const api::CreateVisitorCommand&) const override;
+ void onEncode(GBBuf&, const api::CreateVisitorReply&) const override;
+ SCmd::UP onDecodeCreateVisitorCommand(BBuf&) const override;
+ SRep::UP onDecodeCreateVisitorReply(const SCmd&, BBuf&) const override;
+
+ // DestroyVisitor
+ void onEncode(GBBuf&, const api::DestroyVisitorCommand&) const override;
+ void onEncode(GBBuf&, const api::DestroyVisitorReply&) const override;
+ SCmd::UP onDecodeDestroyVisitorCommand(BBuf&) const override;
+ SRep::UP onDecodeDestroyVisitorReply(const SCmd&, BBuf&) const override;
+
+ // RemoveLocation
+ void onEncode(GBBuf&, const api::RemoveLocationCommand&) const override;
+ void onEncode(GBBuf&, const api::RemoveLocationReply&) const override;
+ SCmd::UP onDecodeRemoveLocationCommand(BBuf&) const override;
+ SRep::UP onDecodeRemoveLocationReply(const SCmd&, BBuf&) const override;
+
+ // StatBucket
+ void onEncode(GBBuf&, const api::StatBucketCommand&) const override;
+ void onEncode(GBBuf&, const api::StatBucketReply&) const override;
+ SCmd::UP onDecodeStatBucketCommand(BBuf&) const override;
+ SRep::UP onDecodeStatBucketReply(const SCmd&, BBuf&) const override;
+
+private:
+ template <typename ProtobufType, typename Func>
+ std::unique_ptr<api::StorageCommand> decode_request(document::ByteBuffer& in_buf, Func&& f) const;
+ template <typename ProtobufType, typename Func>
+ std::unique_ptr<api::StorageReply> decode_response(document::ByteBuffer& in_buf, Func&& f) const;
+ template <typename ProtobufType, typename Func>
+ std::unique_ptr<api::StorageCommand> decode_bucket_request(document::ByteBuffer& in_buf, Func&& f) const;
+ template <typename ProtobufType, typename Func>
+ std::unique_ptr<api::StorageReply> decode_bucket_response(document::ByteBuffer& in_buf, Func&& f) const;
+ template <typename ProtobufType, typename Func>
+ std::unique_ptr<api::StorageReply> decode_bucket_info_response(document::ByteBuffer& in_buf, Func&& f) const;
+};
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/serializationhelper.h b/storage/src/vespa/storageapi/mbusprot/serializationhelper.h
new file mode 100644
index 00000000000..457a6178704
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/serializationhelper.h
@@ -0,0 +1,110 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/fastos/types.h>
+#include <vespa/document/base/globalid.h>
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/util/bytebuffer.h>
+#include <vespa/vespalib/objects/nbostream.h>
+#include <vespa/vespalib/util/growablebytebuffer.h>
+
+namespace storage::mbusprot {
+
+class SerializationHelper
+{
+public:
+ static int64_t getLong(document::ByteBuffer& buf) {
+ int64_t tmp;
+ buf.getLongNetwork(tmp);
+ return tmp;
+ }
+
+ static int32_t getInt(document::ByteBuffer& buf) {
+ int32_t tmp;
+ buf.getIntNetwork(tmp);
+ return tmp;
+ }
+
+ static int16_t getShort(document::ByteBuffer& buf) {
+ int16_t tmp;
+ buf.getShortNetwork(tmp);
+ return tmp;
+ }
+
+ static uint8_t getByte(document::ByteBuffer& buf) {
+ uint8_t tmp;
+ buf.getByte(tmp);
+ return tmp;
+ }
+
+ static vespalib::stringref getString(document::ByteBuffer& buf) {
+ uint32_t tmp;
+ buf.getIntNetwork((int32_t&) tmp);
+ const char * p = buf.getBufferAtPos();
+ buf.incPos(tmp);
+ vespalib::stringref s(p, tmp);
+ return s;
+ }
+
+ static bool getBoolean(document::ByteBuffer& buf) {
+ uint8_t tmp;
+ buf.getByte(tmp);
+ return (tmp == 1);
+ }
+
+ static api::ReturnCode getReturnCode(document::ByteBuffer& buf) {
+ api::ReturnCode::Result result = (api::ReturnCode::Result) getInt(buf);
+ vespalib::stringref message = getString(buf);
+ return api::ReturnCode(result, message);
+ }
+
+ static void putReturnCode(const api::ReturnCode& code, vespalib::GrowableByteBuffer& buf)
+ {
+ buf.putInt(code.getResult());
+ buf.putString(code.getMessage());
+ }
+
+ static const uint32_t BUCKET_INFO_SERIALIZED_SIZE = sizeof(uint32_t) * 3;
+
+ static document::GlobalId getGlobalId(document::ByteBuffer& buf) {
+ std::vector<char> buffer(getShort(buf));
+ for (uint32_t i=0; i<buffer.size(); ++i) {
+ buffer[i] = getByte(buf);
+ }
+ return document::GlobalId(&buffer[0]);
+ }
+
+ static void putGlobalId(const document::GlobalId& gid, vespalib::GrowableByteBuffer& buf)
+ {
+ buf.putShort(document::GlobalId::LENGTH);
+ for (uint32_t i=0; i<document::GlobalId::LENGTH; ++i) {
+ buf.putByte(gid.get()[i]);
+ }
+ }
+ static document::Document::UP getDocument(document::ByteBuffer& buf, const document::DocumentTypeRepo& repo)
+ {
+ uint32_t size = getInt(buf);
+ if (size == 0) {
+ return document::Document::UP();
+ } else {
+ vespalib::nbostream stream(buf.getBufferAtPos(), size);
+ buf.incPos(size);
+ return std::make_unique<document::Document>(repo, stream);
+ }
+ }
+
+ static void putDocument(document::Document* doc, vespalib::GrowableByteBuffer& buf)
+ {
+ if (doc) {
+ vespalib::nbostream stream;
+ doc->serialize(stream);
+ buf.putInt(stream.size());
+ buf.putBytes(stream.peek(), stream.size());
+ } else {
+ buf.putInt(0);
+ }
+ }
+
+};
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/storagecommand.cpp b/storage/src/vespa/storageapi/mbusprot/storagecommand.cpp
new file mode 100644
index 00000000000..34fd0992adb
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/storagecommand.cpp
@@ -0,0 +1,11 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "storagecommand.h"
+
+namespace storage::mbusprot {
+
+StorageCommand::StorageCommand(api::StorageCommand::SP cmd)
+ : mbus::Message(),
+ _cmd(std::move(cmd))
+{ }
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/storagecommand.h b/storage/src/vespa/storageapi/mbusprot/storagecommand.h
new file mode 100644
index 00000000000..e65c0295e31
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/storagecommand.h
@@ -0,0 +1,37 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "storagemessage.h"
+#include "storageprotocol.h"
+#include <vespa/messagebus/message.h>
+#include <vespa/storageapi/messageapi/storagecommand.h>
+
+namespace storage::mbusprot {
+
+class StorageCommand : public mbus::Message, public StorageMessage {
+public:
+ typedef std::unique_ptr<StorageCommand> UP;
+
+ explicit StorageCommand(api::StorageCommand::SP);
+
+ const mbus::string & getProtocol() const override { return StorageProtocol::NAME; }
+ uint32_t getType() const override { return _cmd->getType().getId(); }
+ const api::StorageCommand::SP& getCommand() { return _cmd; }
+ api::StorageCommand::CSP getCommand() const { return _cmd; }
+ api::StorageMessage::SP getInternalMessage() override { return _cmd; }
+ api::StorageMessage::CSP getInternalMessage() const override { return _cmd; }
+
+ bool has_command() const noexcept { return (_cmd.get() != nullptr); }
+ api::StorageCommand::SP steal_command() { return std::move(_cmd); }
+
+ bool hasBucketSequence() const override { return false; }
+
+ uint8_t priority() const override {
+ return ((getInternalMessage()->getPriority()) / 255) * 16;
+ }
+
+private:
+ api::StorageCommand::SP _cmd;
+};
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/storagemessage.cpp b/storage/src/vespa/storageapi/mbusprot/storagemessage.cpp
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/storagemessage.cpp
diff --git a/storage/src/vespa/storageapi/mbusprot/storagemessage.h b/storage/src/vespa/storageapi/mbusprot/storagemessage.h
new file mode 100644
index 00000000000..61323222b89
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/storagemessage.h
@@ -0,0 +1,20 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/storageapi/messageapi/storagemessage.h>
+
+namespace storage::mbusprot {
+
+class StorageMessage {
+public:
+ typedef std::unique_ptr<StorageMessage> UP;
+
+ virtual ~StorageMessage() {}
+
+ virtual api::StorageMessage::SP getInternalMessage() = 0;
+ virtual api::StorageMessage::CSP getInternalMessage() const = 0;
+
+};
+
+}
+
diff --git a/storage/src/vespa/storageapi/mbusprot/storageprotocol.cpp b/storage/src/vespa/storageapi/mbusprot/storageprotocol.cpp
new file mode 100644
index 00000000000..3cf54860f7c
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/storageprotocol.cpp
@@ -0,0 +1,203 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "storageprotocol.h"
+#include "serializationhelper.h"
+#include "storagecommand.h"
+#include "storagereply.h"
+#include <vespa/vespalib/util/exceptions.h>
+#include <vespa/document/util/stringutil.h>
+#include <vespa/document/bucket/fixed_bucket_spaces.h>
+#include <sstream>
+
+#include <vespa/log/bufferedlogger.h>
+LOG_SETUP(".storage.api.mbusprot.protocol");
+
+namespace storage::mbusprot {
+
+mbus::string StorageProtocol::NAME = "StorageProtocol";
+
+StorageProtocol::StorageProtocol(const std::shared_ptr<const document::DocumentTypeRepo> repo)
+ : _serializer5_0(repo),
+ _serializer5_1(repo),
+ _serializer5_2(repo),
+ _serializer6_0(repo),
+ _serializer7_0(repo)
+{
+}
+
+StorageProtocol::~StorageProtocol() = default;
+
+mbus::IRoutingPolicy::UP
+StorageProtocol::createPolicy(const mbus::string&, const mbus::string&) const
+{
+ return mbus::IRoutingPolicy::UP();
+}
+
+namespace {
+ vespalib::Version version7_0(7, 41, 19);
+ vespalib::Version version6_0(6, 240, 0);
+ vespalib::Version version5_2(5, 93, 30);
+ vespalib::Version version5_1(5, 1, 0);
+ vespalib::Version version5_0(5, 0, 12);
+ vespalib::Version version5_0beta(4, 3, 0);
+}
+
+
+static bool
+suppressEncodeWarning(const api::StorageMessage *msg)
+{
+ const auto *req = dynamic_cast<const api::RequestBucketInfoCommand *>(msg);
+ return ((req != nullptr) && (req->getBucketSpace() != document::FixedBucketSpaces::default_space()));
+}
+
+static mbus::Blob
+encodeMessage(const ProtocolSerialization & serializer,
+ const mbus::Routable & routable,
+ const StorageMessage & message,
+ const vespalib::Version & serializerVersion,
+ const vespalib::Version & actualVersion)
+{
+ mbus::Blob blob(serializer.encode(*message.getInternalMessage()));
+
+ if (LOG_WOULD_LOG(spam)) {
+ std::ostringstream messageStream;
+ document::StringUtil::printAsHex(messageStream, blob.data(), blob.size());
+
+ LOG(spam, "Encoded message of protocol %s type %s using "
+ "%s serialization as version is %s:\n%s",
+ routable.getProtocol().c_str(),
+ message.getInternalMessage()->getType().toString().c_str(),
+ serializerVersion.toString().c_str(),
+ actualVersion.toString().c_str(),
+ messageStream.str().c_str());
+ }
+
+ return blob;
+}
+
+
+mbus::Blob
+StorageProtocol::encode(const vespalib::Version& version,
+ const mbus::Routable& routable) const
+{
+ const StorageMessage & message(dynamic_cast<const StorageMessage &>(routable));
+
+ try {
+ if (message.getInternalMessage().get() == 0) {
+ throw vespalib::IllegalArgumentException(
+ "Given storage message wrapper does not contain a "
+ "storage message.",
+ VESPA_STRLOC);
+ }
+
+ if (version < version5_1) {
+ if (version < version5_0beta) {
+ LOGBP(warning,
+ "No support for using messagebus for version %s."
+ "Minimum version is %s. Thus we cannot serialize %s.",
+ version.toString().c_str(),
+ version5_0beta.toString().c_str(),
+ message.getInternalMessage()->toString().c_str());
+
+ return mbus::Blob(0);
+ } else {
+ return encodeMessage(_serializer5_0, routable, message, version5_0, version);
+ }
+ } else if (version < version5_2) {
+ return encodeMessage(_serializer5_1, routable, message, version5_1, version);
+ } else {
+ if (version < version6_0) {
+ return encodeMessage(_serializer5_2, routable, message, version5_2, version);
+ } else if (version < version7_0) {
+ return encodeMessage(_serializer6_0, routable, message, version6_0, version);
+ } else {
+ return encodeMessage(_serializer7_0, routable, message, version7_0, version);
+ }
+ }
+
+ } catch (std::exception & e) {
+ if (!(version < version6_0 &&
+ suppressEncodeWarning(message.getInternalMessage().get()))) {
+ LOGBP(warning, "Failed to encode %s storage protocol message %s: %s",
+ version.toString().c_str(),
+ message.getInternalMessage()->toString().c_str(),
+ e.what());
+ }
+ }
+
+ return mbus::Blob(0);
+}
+
+static mbus::Routable::UP
+decodeMessage(const ProtocolSerialization & serializer,
+ mbus::BlobRef data,
+ const api::MessageType & type,
+ const vespalib::Version & serializerVersion,
+ const vespalib::Version & actualVersion)
+{
+ if (LOG_WOULD_LOG(spam)) {
+ std::ostringstream messageStream;
+ document::StringUtil::printAsHex(messageStream, data.data(), data.size());
+
+ LOG(spam,
+ "Decoding %s of version %s "
+ "using %s decoder from:\n%s",
+ type.toString().c_str(),
+ actualVersion.toString().c_str(),
+ serializerVersion.toString().c_str(),
+ messageStream.str().c_str());
+ }
+
+ if (type.isReply()) {
+ return std::make_unique<StorageReply>(data, serializer);
+ } else {
+ auto command = serializer.decodeCommand(data);
+ if (command && command->getInternalMessage()) {
+ command->getInternalMessage()->setApproxByteSize(data.size());
+ }
+ return mbus::Routable::UP(command.release());
+ }
+}
+
+mbus::Routable::UP
+StorageProtocol::decode(const vespalib::Version & version,
+ mbus::BlobRef data) const
+{
+ try {
+ document::ByteBuffer buf(data.data(), data.size());
+ auto & type = api::MessageType::get(
+ static_cast<api::MessageType::Id>(SerializationHelper::getInt(buf)));
+
+ StorageMessage::UP message;
+ if (version < version5_1) {
+ if (version < version5_0beta) {
+ LOGBP(error,
+ "No support for using messagebus for version %s."
+ "Minimum version is %s.",
+ version.toString().c_str(),
+ version5_0beta.toString().c_str());
+ } else {
+ return decodeMessage(_serializer5_0, data, type, version5_0, version);
+ }
+ } else if (version < version5_2) {
+ return decodeMessage(_serializer5_1, data, type, version5_1, version);
+ } else {
+ if (version < version6_0) {
+ return decodeMessage(_serializer5_2, data, type, version5_2, version);
+ } else if (version < version7_0) {
+ return decodeMessage(_serializer6_0, data, type, version6_0, version);
+ } else {
+ return decodeMessage(_serializer7_0, data, type, version7_0, version);
+ }
+ }
+ } catch (std::exception & e) {
+ std::ostringstream ost;
+ ost << "Failed to decode " << version.toString() << " messagebus "
+ << "storage protocol message: " << e.what() << "\n";
+ document::StringUtil::printAsHex(ost, data.data(), data.size());
+ LOGBP(warning, "%s", ost.str().c_str());
+ }
+
+ return mbus::Routable::UP();
+}
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/storageprotocol.h b/storage/src/vespa/storageapi/mbusprot/storageprotocol.h
new file mode 100644
index 00000000000..3f36ac42117
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/storageprotocol.h
@@ -0,0 +1,34 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "protocolserialization5_2.h"
+#include "protocolserialization6_0.h"
+#include "protocolserialization7.h"
+#include <vespa/messagebus/iprotocol.h>
+
+namespace storage::mbusprot {
+
+class StorageProtocol final : public mbus::IProtocol
+{
+public:
+ typedef std::shared_ptr<StorageProtocol> SP;
+
+ static mbus::string NAME;
+
+ explicit StorageProtocol(const std::shared_ptr<const document::DocumentTypeRepo>);
+ ~StorageProtocol() override;
+
+ const mbus::string& getName() const override { return NAME; }
+ mbus::IRoutingPolicy::UP createPolicy(const mbus::string& name, const mbus::string& param) const override;
+ mbus::Blob encode(const vespalib::Version&, const mbus::Routable&) const override;
+ mbus::Routable::UP decode(const vespalib::Version&, mbus::BlobRef) const override;
+ bool requireSequencing() const override { return true; }
+private:
+ ProtocolSerialization5_0 _serializer5_0;
+ ProtocolSerialization5_1 _serializer5_1;
+ ProtocolSerialization5_2 _serializer5_2;
+ ProtocolSerialization6_0 _serializer6_0;
+ ProtocolSerialization7 _serializer7_0;
+};
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/storagereply.cpp b/storage/src/vespa/storageapi/mbusprot/storagereply.cpp
new file mode 100644
index 00000000000..1db6912dd33
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/storagereply.cpp
@@ -0,0 +1,55 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "storagereply.h"
+#include "storagecommand.h"
+#include <vespa/vespalib/util/exceptions.h>
+#include <vespa/vespalib/objects/nbostream.h>
+
+
+using vespalib::alloc::Alloc;
+using vespalib::IllegalStateException;
+
+namespace storage::mbusprot {
+
+StorageReply::StorageReply(mbus::BlobRef data, const ProtocolSerialization& serializer)
+ : _serializer(&serializer),
+ _sz(data.size()),
+ _buffer(Alloc::alloc(_sz)),
+ _mbusType(0),
+ _reply()
+{
+ memcpy(_buffer.get(), data.data(), _sz);
+ vespalib::nbostream nbo(data.data(), _sz);
+ nbo >> _mbusType;
+}
+
+StorageReply::StorageReply(api::StorageReply::SP reply)
+ : _serializer(0),
+ _sz(0),
+ _buffer(),
+ _mbusType(reply->getType().getId()),
+ _reply(std::move(reply))
+{}
+
+StorageReply::~StorageReply() = default;
+
+void
+StorageReply::deserialize() const
+{
+ if (_reply.get()) return;
+ StorageReply& reply(const_cast<StorageReply&>(*this));
+ mbus::Message::UP msg(reply.getMessage());
+ if (msg.get() == 0) {
+ throw IllegalStateException("Cannot deserialize storage reply before message have been set", VESPA_STRLOC);
+ }
+ const StorageCommand* cmd(dynamic_cast<const StorageCommand*>(msg.get()));
+ reply.setMessage(std::move(msg));
+ if (cmd == 0) {
+ throw IllegalStateException("Storage reply get message did not return a storage command", VESPA_STRLOC);
+ }
+ mbus::BlobRef blobRef(static_cast<char *>(_buffer.get()), _sz);
+ _reply = _serializer->decodeReply(blobRef, *cmd->getCommand())->getReply();
+ Alloc().swap(_buffer);
+}
+
+}
diff --git a/storage/src/vespa/storageapi/mbusprot/storagereply.h b/storage/src/vespa/storageapi/mbusprot/storagereply.h
new file mode 100644
index 00000000000..538b7caa678
--- /dev/null
+++ b/storage/src/vespa/storageapi/mbusprot/storagereply.h
@@ -0,0 +1,46 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "storagemessage.h"
+#include "storageprotocol.h"
+#include <vespa/messagebus/reply.h>
+#include <vespa/storageapi/messageapi/storagereply.h>
+
+namespace storage::mbusprot {
+
+class StorageReply : public mbus::Reply, public StorageMessage {
+ const ProtocolSerialization* _serializer;
+ size_t _sz;
+ mutable vespalib::alloc::Alloc _buffer;
+ uint32_t _mbusType;
+ mutable api::StorageReply::SP _reply;
+
+public:
+ typedef std::unique_ptr<StorageReply> UP;
+
+ StorageReply(mbus::BlobRef data, const ProtocolSerialization&);
+ StorageReply(api::StorageReply::SP reply);
+ ~StorageReply();
+
+ const mbus::string& getProtocol() const override { return StorageProtocol::NAME; }
+
+ uint32_t getType() const override { return _mbusType; }
+
+ const api::StorageReply::SP& getReply() { deserialize(); return _reply; }
+ api::StorageReply::CSP getReply() const { deserialize(); return _reply; }
+
+ api::StorageMessage::SP getInternalMessage() override { deserialize(); return _reply; }
+ api::StorageMessage::CSP getInternalMessage() const override { deserialize(); return _reply; }
+
+ uint8_t priority() const override {
+ if (_reply) {
+ return _reply->getPriority();
+ }
+ return 0;
+ }
+
+private:
+ void deserialize() const;
+};
+
+}
diff --git a/storage/src/vespa/storageapi/message/.gitignore b/storage/src/vespa/storageapi/message/.gitignore
new file mode 100644
index 00000000000..526f91c6668
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/.gitignore
@@ -0,0 +1,7 @@
+*.lo
+.*.swp
+.depend
+.depend.NEW
+.deps
+.libs
+Makefile
diff --git a/storage/src/vespa/storageapi/message/CMakeLists.txt b/storage/src/vespa/storageapi/message/CMakeLists.txt
new file mode 100644
index 00000000000..2a761921dff
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/CMakeLists.txt
@@ -0,0 +1,17 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_library(storageapi_message OBJECT
+ SOURCES
+ datagram.cpp
+ persistence.cpp
+ bucket.cpp
+ visitor.cpp
+ state.cpp
+ searchresult.cpp
+ bucketsplitting.cpp
+ documentsummary.cpp
+ stat.cpp
+ removelocation.cpp
+ queryresult.cpp
+ internal.cpp
+ DEPENDS
+)
diff --git a/storage/src/vespa/storageapi/message/bucket.cpp b/storage/src/vespa/storageapi/message/bucket.cpp
new file mode 100644
index 00000000000..520f1aa2741
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/bucket.cpp
@@ -0,0 +1,652 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "bucket.h"
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/vdslib/state/clusterstate.h>
+#include <vespa/vespalib/stllike/asciistream.h>
+#include <vespa/vespalib/objects/nbostream.h>
+#include <vespa/vespalib/util/array.hpp>
+#include <ostream>
+#include <iterator>
+
+namespace storage::api {
+
+IMPLEMENT_COMMAND(CreateBucketCommand, CreateBucketReply)
+IMPLEMENT_REPLY(CreateBucketReply)
+IMPLEMENT_COMMAND(DeleteBucketCommand, DeleteBucketReply)
+IMPLEMENT_REPLY(DeleteBucketReply)
+IMPLEMENT_COMMAND(MergeBucketCommand, MergeBucketReply)
+IMPLEMENT_REPLY(MergeBucketReply)
+IMPLEMENT_COMMAND(GetBucketDiffCommand, GetBucketDiffReply)
+IMPLEMENT_REPLY(GetBucketDiffReply)
+IMPLEMENT_COMMAND(ApplyBucketDiffCommand, ApplyBucketDiffReply)
+IMPLEMENT_REPLY(ApplyBucketDiffReply)
+IMPLEMENT_COMMAND(RequestBucketInfoCommand, RequestBucketInfoReply)
+IMPLEMENT_REPLY(RequestBucketInfoReply)
+IMPLEMENT_COMMAND(NotifyBucketChangeCommand, NotifyBucketChangeReply)
+IMPLEMENT_REPLY(NotifyBucketChangeReply)
+IMPLEMENT_COMMAND(SetBucketStateCommand, SetBucketStateReply)
+IMPLEMENT_REPLY(SetBucketStateReply)
+
+CreateBucketCommand::CreateBucketCommand(const document::Bucket &bucket)
+ : MaintenanceCommand(MessageType::CREATEBUCKET, bucket),
+ _active(false)
+{ }
+
+void
+CreateBucketCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "CreateBucketCommand(" << getBucketId();
+ if (_active) {
+ out << ", active";
+ } else {
+ out << ", inactive";
+ }
+ out << ")";
+ out << " Reasons to start: " << _reason;
+ if (verbose) {
+ out << " : ";
+ MaintenanceCommand::print(out, verbose, indent);
+ }
+}
+
+CreateBucketReply::CreateBucketReply(const CreateBucketCommand& cmd)
+ : BucketInfoReply(cmd)
+{
+}
+
+void
+CreateBucketReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "CreateBucketReply(" << getBucketId() << ")";
+ if (verbose) {
+ out << " : ";
+ BucketInfoReply::print(out, verbose, indent);
+ }
+}
+
+DeleteBucketCommand::DeleteBucketCommand(const document::Bucket &bucket)
+ : MaintenanceCommand(MessageType::DELETEBUCKET, bucket)
+{ }
+
+void
+DeleteBucketCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "DeleteBucketCommand(" << getBucketId() << ")";
+ out << " Reasons to start: " << _reason;
+ if (verbose) {
+ out << " : ";
+ MaintenanceCommand::print(out, verbose, indent);
+ }
+}
+
+DeleteBucketReply::DeleteBucketReply(const DeleteBucketCommand& cmd)
+ : BucketInfoReply(cmd)
+{
+}
+
+void
+DeleteBucketReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "DeleteBucketReply(" << getBucketId() << ")";
+ if (verbose) {
+ out << " : ";
+ BucketInfoReply::print(out, verbose, indent);
+ }
+}
+
+MergeBucketCommand::MergeBucketCommand(
+ const document::Bucket &bucket, const std::vector<Node>& nodes,
+ Timestamp maxTimestamp, uint32_t clusterStateVersion,
+ const std::vector<uint16_t>& chain)
+ : MaintenanceCommand(MessageType::MERGEBUCKET, bucket),
+ _nodes(nodes),
+ _maxTimestamp(maxTimestamp),
+ _clusterStateVersion(clusterStateVersion),
+ _chain(chain),
+ _use_unordered_forwarding(false)
+{}
+
+MergeBucketCommand::~MergeBucketCommand() = default;
+
+void
+MergeBucketCommand::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ out << "MergeBucketCommand(" << getBucketId() << ", to time "
+ << _maxTimestamp << ", cluster state version: "
+ << _clusterStateVersion << ", nodes: [";
+ for (uint32_t i=0; i<_nodes.size(); ++i) {
+ if (i != 0) out << ", ";
+ out << _nodes[i];
+ }
+ out << "], chain: [";
+ for (uint32_t i = 0; i < _chain.size(); ++i) {
+ if (i != 0) out << ", ";
+ out << _chain[i];
+ }
+ out << "]";
+ if (_use_unordered_forwarding) {
+ out << " (unordered forwarding)";
+ }
+ out << ", reasons to start: " << _reason;
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ BucketCommand::print(out, verbose, indent);
+ }
+}
+
+std::ostream&
+operator<<(std::ostream& out, const MergeBucketCommand::Node& n) {
+ return out << n.index << (n.sourceOnly ? " (source only)" : "");
+}
+
+MergeBucketReply::MergeBucketReply(const MergeBucketCommand& cmd)
+ : BucketReply(cmd),
+ _nodes(cmd.getNodes()),
+ _maxTimestamp(cmd.getMaxTimestamp()),
+ _clusterStateVersion(cmd.getClusterStateVersion()),
+ _chain(cmd.getChain())
+{
+}
+
+void
+MergeBucketReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "MergeBucketReply(" << getBucketId() << ", to time "
+ << _maxTimestamp << ", cluster state version: "
+ << _clusterStateVersion << ", nodes: [";
+ for (uint32_t i=0; i<_nodes.size(); ++i) {
+ if (i != 0) out << ", ";
+ out << _nodes[i];
+ }
+ out << "], chain: [";
+ for (uint32_t i = 0; i < _chain.size(); ++i) {
+ if (i != 0) out << ", ";
+ out << _chain[i];
+ }
+ out << "])";
+ if (verbose) {
+ out << " : ";
+ BucketReply::print(out, verbose, indent);
+ }
+}
+
+GetBucketDiffCommand::Entry::Entry()
+ : _timestamp(0),
+ _gid(),
+ _headerSize(0),
+ _bodySize(0),
+ _flags(0),
+ _hasMask(0)
+{
+}
+
+void GetBucketDiffCommand::Entry::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "Entry(timestamp: " << _timestamp
+ << ", " << _gid.toString() << ", hasMask: 0x" << _hasMask;
+ if (verbose) {
+ out << ",\n" << indent << " " << "header size: "
+ << std::dec << _headerSize << ", body size: " << _bodySize
+ << ", flags 0x" << std::hex << _flags << std::dec;
+ }
+ out << ")";
+}
+
+bool GetBucketDiffCommand::Entry::operator==(const Entry& e) const
+{
+ return (_timestamp == e._timestamp &&
+ _headerSize == e._headerSize &&
+ _bodySize == e._bodySize &&
+ _gid == e._gid &&
+ _flags == e._flags);
+}
+
+GetBucketDiffCommand::GetBucketDiffCommand(
+ const document::Bucket &bucket, const std::vector<Node>& nodes,
+ Timestamp maxTimestamp)
+ : BucketCommand(MessageType::GETBUCKETDIFF, bucket),
+ _nodes(nodes),
+ _maxTimestamp(maxTimestamp)
+{}
+
+GetBucketDiffCommand::~GetBucketDiffCommand() = default;
+
+void
+GetBucketDiffCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "GetBucketDiffCommand(" << getBucketId() << ", to time "
+ << _maxTimestamp << ", nodes: [";
+ for (uint32_t i=0; i<_nodes.size(); ++i) {
+ if (i != 0) out << ", ";
+ out << _nodes[i];
+ }
+
+ if (_diff.empty()) {
+ out << "], no entries";
+ } else if (verbose) {
+ out << "],";
+ for (uint32_t i=0; i<_diff.size(); ++i) {
+ out << "\n" << indent << " ";
+ _diff[i].print(out, verbose, indent + " ");
+ }
+ } else {
+ out << ", " << _diff.size() << " entries";
+ out << ", id " << _msgId;
+ }
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ BucketCommand::print(out, verbose, indent);
+ }
+}
+
+GetBucketDiffReply::GetBucketDiffReply(const GetBucketDiffCommand& cmd)
+ : BucketReply(cmd),
+ _nodes(cmd.getNodes()),
+ _maxTimestamp(cmd.getMaxTimestamp()),
+ _diff(cmd.getDiff())
+{}
+
+GetBucketDiffReply::~GetBucketDiffReply() = default;
+
+void
+GetBucketDiffReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "GetBucketDiffReply(" << getBucketId() << ", to time "
+ << _maxTimestamp << ", nodes: [";
+ for (uint32_t i=0; i<_nodes.size(); ++i) {
+ if (i != 0) out << ", ";
+ out << _nodes[i];
+ }
+ if (_diff.empty()) {
+ out << "], no entries";
+ } else if (verbose) {
+ out << "],";
+ for (uint32_t i=0; i<_diff.size(); ++i) {
+ out << "\n" << indent << " ";
+ _diff[i].print(out, verbose, indent + " ");
+ }
+ } else {
+ out << ", " << _diff.size() << " entries";
+ out << ", id " << _msgId;
+ }
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ BucketReply::print(out, verbose, indent);
+ }
+}
+
+ApplyBucketDiffCommand::Entry::Entry()
+ : _entry(),
+ _docName(),
+ _headerBlob(),
+ _bodyBlob(),
+ _repo()
+{}
+
+ApplyBucketDiffCommand::Entry::Entry(const GetBucketDiffCommand::Entry& e)
+ : _entry(e),
+ _docName(),
+ _headerBlob(),
+ _bodyBlob(),
+ _repo()
+{}
+
+ApplyBucketDiffCommand::Entry::~Entry() = default;
+ApplyBucketDiffCommand::Entry::Entry(const Entry &) = default;
+ApplyBucketDiffCommand::Entry & ApplyBucketDiffCommand::Entry::operator = (const Entry &) = default;
+
+bool
+ApplyBucketDiffCommand::Entry::filled() const
+{
+ return ((_headerBlob.size() > 0 ||
+ (_entry._headerSize == 0 && !_docName.empty())) &&
+ (_bodyBlob.size() > 0 ||
+ _entry._bodySize == 0));
+}
+
+void
+ApplyBucketDiffCommand::Entry::print(
+ std::ostream& out, bool verbose, const std::string& indent) const
+{
+ out << "ApplyEntry(";
+ _entry.print(out, verbose, indent + " ");
+ out << ",\n" << indent << " name(" << _docName
+ << "), headerBlob(" << _headerBlob.size()
+ << "), bodyBlob(" << _bodyBlob.size() << ")";
+ if (_headerBlob.size() > 0) {
+ vespalib::nbostream buf(&_headerBlob[0],
+ _headerBlob.size());
+ if (_repo) {
+ document::Document doc(*_repo, buf);
+ out << ",\n" << indent << " " << doc.getId().getGlobalId().toString();
+ } else {
+ out << ",\n" << indent << " unknown global id. (repo missing)";
+ }
+ }
+ out << ")";
+}
+
+bool
+ApplyBucketDiffCommand::Entry::operator==(const Entry& e) const
+{
+ return (_entry == e._entry &&
+ _headerBlob == e._headerBlob &&
+ _bodyBlob == e._bodyBlob);
+}
+
+ApplyBucketDiffCommand::ApplyBucketDiffCommand(
+ const document::Bucket &bucket, const std::vector<Node>& nodes)
+ : BucketInfoCommand(MessageType::APPLYBUCKETDIFF, bucket),
+ _nodes(nodes),
+ _diff()
+{}
+
+ApplyBucketDiffCommand::~ApplyBucketDiffCommand() = default;
+
+void
+ApplyBucketDiffCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ uint32_t totalSize = 0;
+ uint32_t filled = 0;
+ for (std::vector<Entry>::const_iterator it = _diff.begin();
+ it != _diff.end(); ++it)
+ {
+ totalSize += it->_headerBlob.size();
+ totalSize += it->_bodyBlob.size();
+ if (it->filled()) ++filled;
+ }
+ out << "ApplyBucketDiffCommand(" << getBucketId() << ", nodes: [";
+ for (uint32_t i=0; i<_nodes.size(); ++i) {
+ if (i != 0) out << ", ";
+ out << _nodes[i];
+ }
+ out << "], " << _diff.size() << " entries of " << totalSize << " bytes, "
+ << (100.0 * filled / _diff.size()) << " \% filled)";
+ if (_diff.empty()) {
+ out << ", no entries";
+ } else if (verbose) {
+ out << ",";
+ for (uint32_t i=0; i<_diff.size(); ++i) {
+ out << "\n" << indent << " ";
+ _diff[i].print(out, verbose, indent + " ");
+ }
+ } else {
+ out << ", " << _diff.size() << " entries";
+ out << ", id " << _msgId;
+ }
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ BucketCommand::print(out, verbose, indent);
+ }
+}
+
+ApplyBucketDiffReply::ApplyBucketDiffReply(const ApplyBucketDiffCommand& cmd)
+ : BucketInfoReply(cmd),
+ _nodes(cmd.getNodes()),
+ _diff(cmd.getDiff())
+{}
+
+ApplyBucketDiffReply::~ApplyBucketDiffReply() = default;
+
+void
+ApplyBucketDiffReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ uint32_t totalSize = 0;
+ uint32_t filled = 0;
+ for (const Entry & entry : _diff) {
+ totalSize += entry._headerBlob.size();
+ totalSize += entry._bodyBlob.size();
+ if (entry.filled()) ++filled;
+ }
+ out << "ApplyBucketDiffReply(" << getBucketId() << ", nodes: [";
+ for (uint32_t i=0; i<_nodes.size(); ++i) {
+ if (i != 0) out << ", ";
+ out << _nodes[i];
+ }
+ out << "], " << _diff.size() << " entries of " << totalSize << " bytes, "
+ << (100.0 * filled / _diff.size()) << " \% filled)";
+ if (_diff.empty()) {
+ out << ", no entries";
+ } else if (verbose) {
+ out << ",";
+ for (uint32_t i=0; i<_diff.size(); ++i) {
+ out << "\n" << indent << " ";
+ _diff[i].print(out, verbose, indent + " ");
+ }
+ } else {
+ out << ", " << _diff.size() << " entries";
+ out << ", id " << _msgId;
+ }
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ BucketInfoReply::print(out, verbose, indent);
+ }
+}
+
+RequestBucketInfoCommand::RequestBucketInfoCommand(
+ document::BucketSpace bucketSpace,
+ const std::vector<document::BucketId>& buckets)
+ : StorageCommand(MessageType::REQUESTBUCKETINFO),
+ _bucketSpace(bucketSpace),
+ _buckets(buckets),
+ _state(),
+ _distributor(0xFFFF)
+{
+}
+
+RequestBucketInfoCommand::RequestBucketInfoCommand(
+ document::BucketSpace bucketSpace,
+ uint16_t distributor, const lib::ClusterState& state,
+ vespalib::stringref distributionHash)
+ : StorageCommand(MessageType::REQUESTBUCKETINFO),
+ _bucketSpace(bucketSpace),
+ _buckets(),
+ _state(new lib::ClusterState(state)),
+ _distributor(distributor),
+ _distributionHash(distributionHash)
+{
+}
+
+RequestBucketInfoCommand::RequestBucketInfoCommand(
+ document::BucketSpace bucketSpace,
+ uint16_t distributor, const lib::ClusterState& state)
+ : StorageCommand(MessageType::REQUESTBUCKETINFO),
+ _bucketSpace(bucketSpace),
+ _buckets(),
+ _state(new lib::ClusterState(state)),
+ _distributor(distributor),
+ _distributionHash("")
+{
+}
+
+RequestBucketInfoCommand::~RequestBucketInfoCommand() = default;
+
+document::Bucket
+RequestBucketInfoCommand::getBucket() const
+{
+ return document::Bucket(_bucketSpace, document::BucketId());
+}
+
+document::BucketId
+RequestBucketInfoCommand::super_bucket_id() const
+{
+ return _buckets.empty() ? document::BucketId() : _buckets[0];
+}
+
+void
+RequestBucketInfoCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "RequestBucketInfoCommand(";
+ if ( ! _buckets.empty()) {
+ out << _buckets.size() << " buckets";
+ }
+ if (hasSystemState()) {
+ out << "distributor " << _distributor << " in ";
+ _state->print(out, verbose, indent + " ");
+ } else if (super_bucket_id().isSet()) {
+ out << ", super bucket " << super_bucket_id() << ". ";
+ }
+ if (verbose && !_buckets.empty()) {
+ out << "\n" << indent << " Specified buckets:\n" << indent << " ";
+ std::copy(_buckets.begin(), _buckets.end(),
+ std::ostream_iterator<document::BucketId>(
+ out, ("\n" + indent + " ").c_str()));
+ }
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ StorageCommand::print(out, verbose, indent);
+ }
+}
+
+std::ostream& operator<<(std::ostream& out, const RequestBucketInfoReply::Entry& e)
+{
+ return out << e._bucketId << " - " << e._info;
+}
+
+
+RequestBucketInfoReply::RequestBucketInfoReply(const RequestBucketInfoCommand& cmd)
+ : StorageReply(cmd),
+ _buckets(),
+ _full_bucket_fetch(cmd.hasSystemState()),
+ _super_bucket_id(cmd.super_bucket_id())
+{ }
+
+RequestBucketInfoReply::~RequestBucketInfoReply() = default;
+
+void
+RequestBucketInfoReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "RequestBucketInfoReply(" << _buckets.size();
+ if (_full_bucket_fetch) {
+ out << ", full fetch";
+ } else if (super_bucket_id().isSet()) {
+ out << ", super bucket " << super_bucket_id();
+ }
+ if (verbose) {
+ out << "\n" << indent << " ";
+ std::copy(_buckets.begin(), _buckets.end(),
+ std::ostream_iterator<Entry>(out,
+ ("\n" + indent + " ").c_str()));
+ }
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ StorageReply::print(out, verbose, indent);
+ }
+}
+
+NotifyBucketChangeCommand::NotifyBucketChangeCommand(
+ const document::Bucket &bucket, const BucketInfo& info)
+ : BucketCommand(MessageType::NOTIFYBUCKETCHANGE, bucket),
+ _info(info)
+{
+}
+
+void
+NotifyBucketChangeCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "NotifyBucketChangeCommand(" << getBucketId() << ", ";
+ out << _info;
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ StorageCommand::print(out, verbose, indent);
+ }
+}
+
+NotifyBucketChangeReply::NotifyBucketChangeReply(
+ const NotifyBucketChangeCommand& cmd)
+ : BucketReply(cmd)
+{
+}
+
+void
+NotifyBucketChangeReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "NotifyBucketChangeReply(" << getBucketId() << ")";
+ if (verbose) {
+ out << " : ";
+ BucketReply::print(out, verbose, indent);
+ }
+}
+
+SetBucketStateCommand::SetBucketStateCommand(
+ const document::Bucket &bucket,
+ BUCKET_STATE state)
+ : MaintenanceCommand(MessageType::SETBUCKETSTATE, bucket),
+ _state(state)
+{
+}
+
+void
+SetBucketStateCommand::print(std::ostream& out,
+ bool verbose,
+ const std::string& indent) const
+{
+ out << "SetBucketStateCommand(" << getBucketId() << ", ";
+ switch (_state) {
+ case INACTIVE:
+ out << "INACTIVE";
+ break;
+ case ACTIVE:
+ out << "ACTIVE";
+ break;
+ }
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ MaintenanceCommand::print(out, verbose, indent);
+ }
+}
+
+vespalib::string
+SetBucketStateCommand::getSummary() const
+{
+ vespalib::asciistream stream;
+ stream << "SetBucketStateCommand(" << getBucketId().toString() << ", "
+ << ((_state == ACTIVE) ? "ACTIVE" : "INACTIVE") << ")";
+ return stream.str();
+}
+
+SetBucketStateReply::SetBucketStateReply(
+ const SetBucketStateCommand& cmd)
+ : BucketInfoReply(cmd)
+{
+}
+
+void
+SetBucketStateReply::print(std::ostream& out,
+ bool verbose,
+ const std::string& indent) const
+{
+ out << "SetBucketStateReply(" << getBucketId() << ")";
+ if (verbose) {
+ out << " : ";
+ BucketInfoReply::print(out, verbose, indent);
+ }
+}
+
+}
+
+template class vespalib::Array<storage::api::RequestBucketInfoReply::Entry>;
diff --git a/storage/src/vespa/storageapi/message/bucket.h b/storage/src/vespa/storageapi/message/bucket.h
new file mode 100644
index 00000000000..47785a92039
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/bucket.h
@@ -0,0 +1,502 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @file bucketinfo.h
+ *
+ * Bucket related commands.
+ */
+
+#pragma once
+
+#include <vespa/storageapi/messageapi/bucketcommand.h>
+#include <vespa/storageapi/messageapi/bucketreply.h>
+#include <vespa/storageapi/messageapi/bucketinfocommand.h>
+#include <vespa/storageapi/messageapi/bucketinforeply.h>
+#include <vespa/storageapi/messageapi/maintenancecommand.h>
+#include <vespa/document/base/globalid.h>
+#include <vespa/document/util/printable.h>
+#include <vespa/vespalib/util/array.h>
+#include <vespa/storageapi/defs.h>
+
+namespace document { class DocumentTypeRepo; }
+
+namespace storage::lib { class ClusterState; }
+
+namespace storage::api {
+
+/**
+ * @class CreateBucketCommand
+ * @ingroup message
+ *
+ * @brief Command for creating a new bucket on a storage node.
+ */
+class CreateBucketCommand : public MaintenanceCommand {
+ bool _active;
+
+public:
+ explicit CreateBucketCommand(const document::Bucket &bucket);
+ void setActive(bool active) { _active = active; }
+ bool getActive() const { return _active; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGECOMMAND(CreateBucketCommand, onCreateBucket)
+};
+
+/**
+ * @class CreateBucketReply
+ * @ingroup message
+ *
+ * @brief Reply of a create bucket command.
+ */
+class CreateBucketReply : public BucketInfoReply {
+public:
+ explicit CreateBucketReply(const CreateBucketCommand& cmd);
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGEREPLY(CreateBucketReply, onCreateBucketReply);
+};
+
+/**
+ * @class DeleteBucketCommand
+ * @ingroup message
+ *
+ * @brief Command for deleting a bucket from one or more storage nodes.
+ */
+class DeleteBucketCommand : public MaintenanceCommand {
+ BucketInfo _info;
+public:
+ explicit DeleteBucketCommand(const document::Bucket &bucket);
+
+ const BucketInfo& getBucketInfo() const { return _info; }
+ void setBucketInfo(const BucketInfo& info) { _info = info; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGECOMMAND(DeleteBucketCommand, onDeleteBucket)
+};
+
+/**
+ * @class DeleteBucketReply
+ * @ingroup message
+ *
+ * @brief Reply of a delete bucket command.
+ */
+class DeleteBucketReply : public BucketInfoReply {
+public:
+ explicit DeleteBucketReply(const DeleteBucketCommand& cmd);
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGEREPLY(DeleteBucketReply, onDeleteBucketReply)
+};
+
+
+/**
+ * @class MergeBucketCommand
+ * @ingroup message
+ *
+ * @brief Merge a bucket
+ *
+ * Merges given bucket copies, held on the given node list. A maximum timestamp
+ * should be given, such that the buckets may be used during merge. If not
+ * given, storage will set current time for it, but distributors should really
+ * set it, as they have the reference clock for a bucket.
+ *
+ * An optional "only for source" node list can be provided. In this case, the
+ * nodes in that list are only used for sources in the merge, and never as
+ * targets, even if they are missing documents from the other nodes.
+ *
+ */
+class MergeBucketCommand : public MaintenanceCommand {
+public:
+ struct Node {
+ uint16_t index;
+ bool sourceOnly;
+
+ Node(uint16_t index_, bool sourceOnly_ = false) noexcept
+ : index(index_), sourceOnly(sourceOnly_) {}
+
+ bool operator==(const Node& n) const noexcept
+ { return (index == n.index && sourceOnly == n.sourceOnly); }
+ };
+
+private:
+ std::vector<Node> _nodes;
+ Timestamp _maxTimestamp;
+ uint32_t _clusterStateVersion;
+ std::vector<uint16_t> _chain;
+ bool _use_unordered_forwarding;
+
+public:
+ MergeBucketCommand(const document::Bucket &bucket,
+ const std::vector<Node>&,
+ Timestamp maxTimestamp,
+ uint32_t clusterStateVersion = 0,
+ const std::vector<uint16_t>& chain = std::vector<uint16_t>());
+ ~MergeBucketCommand() override;
+
+ const std::vector<Node>& getNodes() const { return _nodes; }
+ Timestamp getMaxTimestamp() const { return _maxTimestamp; }
+ const std::vector<uint16_t>& getChain() const { return _chain; }
+ uint32_t getClusterStateVersion() const { return _clusterStateVersion; }
+ void setClusterStateVersion(uint32_t version) { _clusterStateVersion = version; }
+ void setChain(const std::vector<uint16_t>& chain) { _chain = chain; }
+ void set_use_unordered_forwarding(bool unordered_forwarding) noexcept {
+ _use_unordered_forwarding = unordered_forwarding;
+ }
+ [[nodiscard]] bool use_unordered_forwarding() const noexcept { return _use_unordered_forwarding; }
+ [[nodiscard]] bool from_distributor() const noexcept { return _chain.empty(); }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGECOMMAND(MergeBucketCommand, onMergeBucket)
+};
+
+std::ostream&
+operator<<(std::ostream& out, const MergeBucketCommand::Node& n);
+
+/**
+ * @class MergeBucketReply
+ * @ingroup message
+ *
+ * @brief Reply of a merge bucket command.
+ */
+class MergeBucketReply : public BucketReply {
+public:
+ typedef MergeBucketCommand::Node Node;
+
+private:
+ std::vector<Node> _nodes;
+ Timestamp _maxTimestamp;
+ uint32_t _clusterStateVersion;
+ std::vector<uint16_t> _chain;
+
+public:
+ explicit MergeBucketReply(const MergeBucketCommand& cmd);
+
+ const std::vector<Node>& getNodes() const { return _nodes; }
+ Timestamp getMaxTimestamp() const { return _maxTimestamp; }
+ const std::vector<uint16_t>& getChain() const { return _chain; }
+ uint32_t getClusterStateVersion() const { return _clusterStateVersion; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGEREPLY(MergeBucketReply, onMergeBucketReply)
+};
+
+/**
+ * @class GetBucketDiff
+ * @ingroup message
+ *
+ * @brief Message sent between storage nodes as the first step of merge.
+ */
+class GetBucketDiffCommand : public BucketCommand {
+public:
+ typedef MergeBucketCommand::Node Node;
+
+ struct Entry : public document::Printable {
+ Timestamp _timestamp;
+ document::GlobalId _gid;
+ uint32_t _headerSize;
+ uint32_t _bodySize;
+ uint16_t _flags;
+ uint16_t _hasMask;
+
+ Entry();
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ bool operator==(const Entry&) const;
+ bool operator<(const Entry& e) const
+ { return (_timestamp < e._timestamp); }
+ };
+private:
+ std::vector<Node> _nodes;
+ Timestamp _maxTimestamp;
+ std::vector<Entry> _diff;
+
+public:
+ GetBucketDiffCommand(const document::Bucket &bucket,
+ const std::vector<Node>&,
+ Timestamp maxTimestamp);
+ ~GetBucketDiffCommand() override;
+
+ const std::vector<Node>& getNodes() const { return _nodes; }
+ Timestamp getMaxTimestamp() const { return _maxTimestamp; }
+ const std::vector<Entry>& getDiff() const { return _diff; }
+ std::vector<Entry>& getDiff() { return _diff; }
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGECOMMAND(GetBucketDiffCommand, onGetBucketDiff)
+};
+
+/**
+ * @class GetBucketDiffReply
+ * @ingroup message
+ *
+ * @brief Reply of GetBucketDiffCommand
+ */
+class GetBucketDiffReply : public BucketReply {
+public:
+ typedef MergeBucketCommand::Node Node;
+ typedef GetBucketDiffCommand::Entry Entry;
+
+private:
+ std::vector<Node> _nodes;
+ Timestamp _maxTimestamp;
+ std::vector<Entry> _diff;
+
+public:
+ explicit GetBucketDiffReply(const GetBucketDiffCommand& cmd);
+ ~GetBucketDiffReply();
+
+ const std::vector<Node>& getNodes() const { return _nodes; }
+ Timestamp getMaxTimestamp() const { return _maxTimestamp; }
+ const std::vector<Entry>& getDiff() const { return _diff; }
+ std::vector<Entry>& getDiff() { return _diff; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGEREPLY(GetBucketDiffReply, onGetBucketDiffReply)
+};
+
+/**
+ * @class ApplyBucketDiff
+ * @ingroup message
+ *
+ * @brief Sends a chunk of document entries, which the bucket copies can use
+ * to update themselves.
+ */
+class ApplyBucketDiffCommand : public BucketInfoCommand {
+public:
+ typedef MergeBucketCommand::Node Node;
+ struct Entry : public document::Printable {
+ GetBucketDiffCommand::Entry _entry;
+ vespalib::string _docName;
+ std::vector<char> _headerBlob;
+ // TODO: In theory the body blob could be removed now as all is in one blob
+ // That will enable simplification of code in document.
+ std::vector<char> _bodyBlob;
+ const document::DocumentTypeRepo *_repo;
+
+ Entry();
+ Entry(const GetBucketDiffCommand::Entry&);
+ Entry(const Entry &);
+ Entry & operator = (const Entry &);
+ Entry(Entry &&) = default;
+ Entry & operator = (Entry &&) = default;
+ ~Entry();
+
+ bool filled() const;
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ bool operator==(const Entry&) const;
+ };
+private:
+ std::vector<Node> _nodes;
+ std::vector<Entry> _diff;
+
+public:
+ ApplyBucketDiffCommand(const document::Bucket &bucket,
+ const std::vector<Node>& nodes);
+ ~ApplyBucketDiffCommand() override;
+
+ const std::vector<Node>& getNodes() const { return _nodes; }
+ const std::vector<Entry>& getDiff() const { return _diff; }
+ std::vector<Entry>& getDiff() { return _diff; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGECOMMAND(ApplyBucketDiffCommand, onApplyBucketDiff)
+};
+
+/**
+ * @class ApplyBucketDiffReply
+ * @ingroup message
+ *
+ * @brief Reply of ApplyBucketDiffCommand
+ */
+class ApplyBucketDiffReply : public BucketInfoReply {
+public:
+ typedef MergeBucketCommand::Node Node;
+ typedef ApplyBucketDiffCommand::Entry Entry;
+
+private:
+ std::vector<Node> _nodes;
+ std::vector<Entry> _diff;
+
+public:
+ explicit ApplyBucketDiffReply(const ApplyBucketDiffCommand& cmd);
+ ~ApplyBucketDiffReply() override;
+
+ const std::vector<Node>& getNodes() const { return _nodes; }
+ const std::vector<Entry>& getDiff() const { return _diff; }
+ std::vector<Entry>& getDiff() { return _diff; }
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGEREPLY(ApplyBucketDiffReply, onApplyBucketDiffReply)
+};
+
+/**
+ * @class RequestBucketInfoCommand
+ * @ingroup message
+ *
+ * @brief Command for getting bucket info.
+ *
+ * Used to get checksums of buckets from a storage node.
+ * If list of buckets for which to retrieve info is given. If it is empty,
+ * it means all buckets.
+ * A system state and a distributor index may be given. If given, only info for
+ * the buckets that belong to the given distributor should be returned.
+ */
+class RequestBucketInfoCommand : public StorageCommand {
+ document::BucketSpace _bucketSpace;
+ std::vector<document::BucketId> _buckets;
+ std::unique_ptr<lib::ClusterState> _state;
+ uint16_t _distributor;
+ vespalib::string _distributionHash;
+
+public:
+ RequestBucketInfoCommand(document::BucketSpace bucketSpace,
+ const std::vector<document::BucketId>& buckets);
+ RequestBucketInfoCommand(document::BucketSpace bucketSpace,
+ uint16_t distributor,
+ const lib::ClusterState& state,
+ vespalib::stringref _distributionHash);
+
+ RequestBucketInfoCommand(document::BucketSpace bucketSpace,
+ uint16_t distributor,
+ const lib::ClusterState& state);
+ ~RequestBucketInfoCommand() override;
+
+ const std::vector<document::BucketId>& getBuckets() const { return _buckets; }
+
+ bool hasSystemState() const { return (_state.get() != 0); }
+ uint16_t getDistributor() const { return _distributor; }
+ const lib::ClusterState& getSystemState() const { return *_state; }
+
+ const vespalib::string& getDistributionHash() const { return _distributionHash; }
+ document::BucketSpace getBucketSpace() const { return _bucketSpace; }
+ document::Bucket getBucket() const override;
+ document::BucketId super_bucket_id() const;
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGECOMMAND(RequestBucketInfoCommand, onRequestBucketInfo)
+};
+
+
+/**
+ * @class RequestBucketInfoReply
+ * @ingroup message
+ *
+ * @brief Answer of a bucket info command.
+ */
+class RequestBucketInfoReply : public StorageReply {
+public:
+ struct Entry {
+ document::BucketId _bucketId;
+ BucketInfo _info;
+
+ bool operator==(const Entry& e) const { return (_bucketId == e._bucketId && _info == e._info); }
+ bool operator!=(const Entry& e) const { return !(*this == e); }
+ Entry() : _bucketId(), _info() {}
+ Entry(const document::BucketId& id, const BucketInfo& info)
+ : _bucketId(id), _info(info) {}
+ friend std::ostream& operator<<(std::ostream& os, const Entry&);
+ };
+ struct SupportedNodeFeatures {
+ bool unordered_merge_chaining = false;
+ };
+ using EntryVector = vespalib::Array<Entry>;
+private:
+ EntryVector _buckets;
+ bool _full_bucket_fetch;
+ document::BucketId _super_bucket_id;
+ SupportedNodeFeatures _supported_node_features;
+
+public:
+
+ explicit RequestBucketInfoReply(const RequestBucketInfoCommand& cmd);
+ ~RequestBucketInfoReply() override;
+ const EntryVector & getBucketInfo() const { return _buckets; }
+ EntryVector & getBucketInfo() { return _buckets; }
+ [[nodiscard]] bool full_bucket_fetch() const noexcept { return _full_bucket_fetch; }
+ // Only contains useful information if full_bucket_fetch() == true
+ [[nodiscard]] const SupportedNodeFeatures& supported_node_features() const noexcept {
+ return _supported_node_features;
+ }
+ [[nodiscard]] SupportedNodeFeatures& supported_node_features() noexcept {
+ return _supported_node_features;
+ }
+ const document::BucketId& super_bucket_id() const { return _super_bucket_id; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGEREPLY(RequestBucketInfoReply, onRequestBucketInfoReply)
+};
+
+/**
+ * @class NotifyBucketChangeCommand
+ * @ingroup message
+ *
+ * @brief Command for letting others know a bucket have been altered.
+ *
+ * When the persistence layer notices a bucket has been corrupted, such that
+ * it needs to be repaired, this message will be sent to notify others
+ * of change. Others being bucket database on storage node, and possibly
+ * distributor.
+ */
+class NotifyBucketChangeCommand : public BucketCommand {
+ BucketInfo _info;
+public:
+ NotifyBucketChangeCommand(const document::Bucket &bucket,
+ const BucketInfo& bucketInfo);
+ const BucketInfo& getBucketInfo() const { return _info; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGECOMMAND(NotifyBucketChangeCommand, onNotifyBucketChange)
+};
+
+
+/**
+ * @class NotifyBucketChangeReply
+ * @ingroup message
+ *
+ * @brief Answer of notify bucket command.
+ *
+ * Noone will resend these messages, and they're not needed, but all commands
+ * need to have a reply.
+ */
+class NotifyBucketChangeReply : public BucketReply {
+public:
+ explicit NotifyBucketChangeReply(const NotifyBucketChangeCommand& cmd);
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGEREPLY(NotifyBucketChangeReply, onNotifyBucketChangeReply)
+};
+
+/**
+ * @class SetBucketStateCommand
+ * @ingroup message
+ *
+ * @brief Sent by distributor to set the ready/active state of a bucket.
+ */
+class SetBucketStateCommand : public MaintenanceCommand
+{
+public:
+ enum BUCKET_STATE
+ {
+ INACTIVE,
+ ACTIVE
+ };
+private:
+ BUCKET_STATE _state;
+public:
+ SetBucketStateCommand(const document::Bucket &bucket, BUCKET_STATE state);
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ BUCKET_STATE getState() const { return _state; }
+ DECLARE_STORAGECOMMAND(SetBucketStateCommand, onSetBucketState)
+private:
+
+ vespalib::string getSummary() const override;
+};
+
+/**
+ * @class SetBucketStateReply
+ * @ingroup message
+ *
+ * @brief Answer to SetBucketStateCommand.
+ */
+class SetBucketStateReply : public BucketInfoReply
+{
+public:
+ explicit SetBucketStateReply(const SetBucketStateCommand&);
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGEREPLY(SetBucketStateReply, onSetBucketStateReply)
+};
+
+}
diff --git a/storage/src/vespa/storageapi/message/bucketsplitting.cpp b/storage/src/vespa/storageapi/message/bucketsplitting.cpp
new file mode 100644
index 00000000000..784ba6edbd1
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/bucketsplitting.cpp
@@ -0,0 +1,134 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "bucketsplitting.h"
+#include <ostream>
+#include <limits>
+
+namespace storage::api {
+
+IMPLEMENT_COMMAND(SplitBucketCommand, SplitBucketReply)
+IMPLEMENT_REPLY(SplitBucketReply)
+IMPLEMENT_COMMAND(JoinBucketsCommand, JoinBucketsReply)
+IMPLEMENT_REPLY(JoinBucketsReply)
+
+SplitBucketCommand::SplitBucketCommand(const document::Bucket &bucket)
+ : MaintenanceCommand(MessageType::SPLITBUCKET, bucket),
+ _minSplitBits(0),
+ _maxSplitBits(58),
+ _minByteSize(std::numeric_limits<uint32_t>::max()),
+ _minDocCount(std::numeric_limits<uint32_t>::max())
+{
+ // By default, set very large sizes, to ensure we trigger 'already big
+ // enough' behaviour, only splitting one step by default. The distributor
+ // should always overwrite one of these values to get correct behaviour.
+}
+
+void
+SplitBucketCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "SplitBucketCommand(" << getBucketId();
+ if (_minDocCount != std::numeric_limits<uint32_t>::max()
+ || _minByteSize != std::numeric_limits<uint32_t>::max())
+ {
+ out << "Max doc count: " << _minDocCount
+ << ", Max total doc size: " << _minByteSize;
+ } else if (_maxSplitBits != 58) {
+ out << "Max split bits to use: " << _maxSplitBits;
+ }
+ out << ")";
+ out << " Reasons to start: " << _reason;
+ if (verbose) {
+ out << " : ";
+ BucketCommand::print(out, verbose, indent);
+ }
+}
+
+SplitBucketReply::SplitBucketReply(const SplitBucketCommand& cmd)
+ : BucketReply(cmd)
+{
+}
+
+void
+SplitBucketReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "SplitBucketReply(" << getBucketId();
+ if (_result.empty()) {
+ out << " - No target files created.";
+ } else {
+ out << " ->";
+ for (uint32_t i=0; i<_result.size(); ++i) {
+ out << "\n" << indent << " " << _result[i].first << ": "
+ << _result[i].second;
+ }
+ }
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ BucketReply::print(out, verbose, indent);
+ }
+}
+
+JoinBucketsCommand::JoinBucketsCommand(const document::Bucket &target)
+ : MaintenanceCommand(MessageType::JOINBUCKETS, target),
+ _minJoinBits(0)
+{
+}
+
+void
+JoinBucketsCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "JoinBucketsCommand(" << getBucketId();
+ if (_sources.empty()) {
+ out << " - No files to join.";
+ } else {
+ out << " <-";
+ for (uint32_t i=0; i<_sources.size(); ++i) {
+ out << " " << _sources[i];
+ }
+ }
+ out << ")";
+ out << " Reasons to start: " << _reason;
+ if (verbose) {
+ out << " : ";
+ BucketCommand::print(out, verbose, indent);
+ }
+}
+
+
+JoinBucketsReply::JoinBucketsReply(const JoinBucketsCommand& cmd)
+ : BucketInfoReply(cmd),
+ _sources(cmd.getSourceBuckets())
+{
+}
+
+JoinBucketsReply::JoinBucketsReply(const JoinBucketsCommand& cmd, const BucketInfo& bucketInfo)
+ : BucketInfoReply(cmd),
+ _sources(cmd.getSourceBuckets())
+{
+ setBucketInfo(bucketInfo);
+}
+
+void
+JoinBucketsReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "JoinBucketsReply(" << getBucketId();
+ if (_sources.empty()) {
+ out << " - No files to join.";
+ } else {
+ out << " <-";
+ for (uint32_t i=0; i<_sources.size(); ++i) {
+ out << " " << _sources[i];
+ }
+ }
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ BucketReply::print(out, verbose, indent);
+ }
+}
+
+}
diff --git a/storage/src/vespa/storageapi/message/bucketsplitting.h b/storage/src/vespa/storageapi/message/bucketsplitting.h
new file mode 100644
index 00000000000..584cdfd5638
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/bucketsplitting.h
@@ -0,0 +1,120 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/storageapi/buckets/bucketinfo.h>
+#include <vespa/storageapi/messageapi/bucketcommand.h>
+#include <vespa/storageapi/messageapi/bucketinforeply.h>
+#include <vespa/storageapi/messageapi/maintenancecommand.h>
+
+namespace storage::api {
+
+/**
+ * @class SplitBucketCommand
+ * @ingroup message
+ *
+ * @brief Split a bucket
+ *
+ * Splits a bucket into two parts using the next split bit that is unused.
+ *
+ * Distributors can issue splits for multiple reasons:
+ * - Inconsistent buckets, so we need to split buckets containing others until
+ * they are either split equally, or no longer contains others.
+ * - Buckets that are too large are split to reduce file size.
+ * - Buckets with too many entries are split to reduce amount of metadata.
+ *
+ * In the first case, min and max split bits can be set. This will make storage
+ * able to split several bits at a time, but know where to stop.
+ *
+ * In the second case, min byte size can be set, to ensure that we don't split
+ * bucket more one step if the copy at the time of processing is
+ * actually smaller. Since removes can happen in the meantime, the min byte size
+ * should be smaller than the limit we use for splitting. Suggesting half.
+ *
+ * Similarily we can do as the second case in the third case too, just using
+ * min doc count as limiter instead.
+ *
+ * If neither are specified, min/max split bits limits nothing, but the sizes
+ * are set to max, which ensures that only one split step is taken.
+ */
+class SplitBucketCommand : public MaintenanceCommand {
+private:
+ uint8_t _minSplitBits;
+ uint8_t _maxSplitBits;
+ uint32_t _minByteSize;
+ uint32_t _minDocCount;
+
+public:
+ SplitBucketCommand(const document::Bucket& bucket);
+
+ uint8_t getMinSplitBits() const { return _minSplitBits; }
+ uint8_t getMaxSplitBits() const { return _maxSplitBits; }
+ uint32_t getMinByteSize() const { return _minByteSize; }
+ uint32_t getMinDocCount() const { return _minDocCount; }
+
+ void setMinSplitBits(uint8_t v) { _minSplitBits = v; }
+ void setMaxSplitBits(uint8_t v) { _maxSplitBits = v; }
+ void setMinByteSize(uint32_t v) { _minByteSize = v; }
+ void setMinDocCount(uint32_t v) { _minDocCount = v; }
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGECOMMAND(SplitBucketCommand, onSplitBucket)
+};
+
+/**
+ * @class SplitBucketReply
+ * @ingroup message
+ *
+ * @brief Reply of a split bucket command.
+ */
+class SplitBucketReply : public BucketReply {
+public:
+ typedef std::pair<document::BucketId, BucketInfo> Entry;
+ explicit SplitBucketReply(const SplitBucketCommand& cmd);
+ std::vector<Entry>& getSplitInfo() { return _result; }
+ const std::vector<Entry>& getSplitInfo() const { return _result; }
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGEREPLY(SplitBucketReply, onSplitBucketReply)
+private:
+ std::vector<Entry> _result;
+};
+
+/**
+ * @class JoinBucketCommand
+ * @ingroup message
+ *
+ * @brief Join two buckets
+ *
+ * Joins two buckets on the same node into a bucket with one fewer split bit.
+ */
+class JoinBucketsCommand : public MaintenanceCommand {
+ std::vector<document::BucketId> _sources;
+ uint8_t _minJoinBits;
+public:
+ explicit JoinBucketsCommand(const document::Bucket &target);
+ std::vector<document::BucketId>& getSourceBuckets() { return _sources; }
+ const std::vector<document::BucketId>& getSourceBuckets() const { return _sources; }
+ void setMinJoinBits(uint8_t minJoinBits) { _minJoinBits = minJoinBits; }
+ uint8_t getMinJoinBits() const { return _minJoinBits; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGECOMMAND(JoinBucketsCommand, onJoinBuckets)
+};
+
+/**
+ * @class JoinBucketsReply
+ * @ingroup message
+ *
+ * @brief Reply of a join bucket command.
+ */
+class JoinBucketsReply : public BucketInfoReply {
+ std::vector<document::BucketId> _sources;
+public:
+ explicit JoinBucketsReply(const JoinBucketsCommand& cmd);
+ JoinBucketsReply(const JoinBucketsCommand& cmd, const BucketInfo& bucketInfo);
+ const std::vector<document::BucketId>& getSourceBuckets() const { return _sources; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGEREPLY(JoinBucketsReply, onJoinBucketsReply)
+};
+
+}
diff --git a/storage/src/vespa/storageapi/message/datagram.cpp b/storage/src/vespa/storageapi/message/datagram.cpp
new file mode 100644
index 00000000000..546e0edecc1
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/datagram.cpp
@@ -0,0 +1,100 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "datagram.h"
+#include <ostream>
+
+using document::BucketSpace;
+
+namespace storage {
+namespace api {
+
+IMPLEMENT_COMMAND(MapVisitorCommand, MapVisitorReply)
+IMPLEMENT_REPLY(MapVisitorReply)
+IMPLEMENT_COMMAND(EmptyBucketsCommand, EmptyBucketsReply)
+IMPLEMENT_REPLY(EmptyBucketsReply)
+
+MapVisitorCommand::MapVisitorCommand()
+ : StorageCommand(MessageType::MAPVISITOR)
+{
+}
+
+void
+MapVisitorCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "MapVisitor(" << _statistics.size() << " entries";
+ if (verbose) {
+ for (vdslib::Parameters::ParametersMap::const_iterator it
+ = _statistics.begin(); it != _statistics.end(); ++it)
+ {
+ out << ",\n" << indent << " " << it->first << ": "
+ << vespalib::stringref(it->second.c_str(), it->second.length());
+ }
+ out << ") : ";
+ StorageCommand::print(out, verbose, indent);
+ } else {
+ out << ")";
+ }
+}
+
+MapVisitorReply::MapVisitorReply(const MapVisitorCommand& cmd)
+ : StorageReply(cmd)
+{
+}
+
+void
+MapVisitorReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "MapVisitorReply()";
+ if (verbose) {
+ out << " : ";
+ StorageReply::print(out, verbose, indent);
+ }
+}
+
+EmptyBucketsCommand::EmptyBucketsCommand(
+ const std::vector<document::BucketId>& buckets)
+ : StorageCommand(MessageType::EMPTYBUCKETS),
+ _buckets(buckets)
+{
+}
+
+void
+EmptyBucketsCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "EmptyBuckets(";
+ if (verbose) {
+ for (uint32_t i=0; i<_buckets.size(); ++i) {
+ out << "\n" << indent << " ";
+ out << _buckets[i];
+ }
+ } else {
+ out << _buckets.size() << " buckets";
+ }
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ StorageCommand::print(out, verbose, indent);
+ }
+}
+
+EmptyBucketsReply::EmptyBucketsReply(const EmptyBucketsCommand& cmd)
+ : StorageReply(cmd)
+{
+}
+
+void
+EmptyBucketsReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "EmptyBucketsReply()";
+ if (verbose) {
+ out << " : ";
+ StorageReply::print(out, verbose, indent);
+ }
+}
+
+} // api
+} // storage
diff --git a/storage/src/vespa/storageapi/message/datagram.h b/storage/src/vespa/storageapi/message/datagram.h
new file mode 100644
index 00000000000..e0f5a9f7b30
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/datagram.h
@@ -0,0 +1,77 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "visitor.h"
+#include <vespa/storageapi/messageapi/storagecommand.h>
+#include <vespa/storageapi/messageapi/storagereply.h>
+#include <vespa/storageapi/defs.h>
+
+namespace storage::api {
+
+/**
+ * @class MapStorageCommand
+ * @ingroup message
+ *
+ * @brief Sends a map of data to a visitor.
+ *
+ * This is a generic way to transfer data to the visitor data handler.
+ * It is for instance used when doing a specialized visitor to gather statistics
+ * on usage of document types and namespaces.
+ */
+class MapVisitorCommand : public StorageCommand {
+ vdslib::Parameters _statistics;
+public:
+ MapVisitorCommand();
+ vdslib::Parameters& getData() { return _statistics; };
+ const vdslib::Parameters& getData() const { return _statistics; };
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGECOMMAND(MapVisitorCommand, onMapVisitor)
+};
+
+/**
+ * @class MapStorageReply
+ * @ingroup message
+ *
+ * @brief Confirm that a given map visitor command has been received.
+ */
+class MapVisitorReply : public StorageReply {
+public:
+ explicit MapVisitorReply(const MapVisitorCommand&);
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGEREPLY(MapVisitorReply, onMapVisitorReply)
+};
+
+/**
+ * @class EmptyBucketsCommand
+ * @ingroup message
+ *
+ * @brief Sends a vector of bucket ids to a visitor.
+ *
+ * This message is used in synchronization to tell the synchronization client
+ * that a bucket contains no data at all. This is needed to let the follower be
+ * able to delete documents from these buckets, as they would otherwise be
+ * ignored by the synch agent.
+ */
+class EmptyBucketsCommand : public StorageCommand {
+ std::vector<document::BucketId> _buckets;
+public:
+ EmptyBucketsCommand(const std::vector<document::BucketId>&);
+ const std::vector<document::BucketId>& getBuckets() const { return _buckets; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGECOMMAND(EmptyBucketsCommand, onEmptyBuckets)
+};
+
+/**
+ * @class EmptyBucketsReply
+ * @ingroup message
+ *
+ * @brief Confirm that a given emptybucketscommad has been received.
+ */
+class EmptyBucketsReply : public StorageReply {
+public:
+ explicit EmptyBucketsReply(const EmptyBucketsCommand&);
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGEREPLY(EmptyBucketsReply, onEmptyBucketsReply)
+};
+
+}
diff --git a/storage/src/vespa/storageapi/message/documentsummary.cpp b/storage/src/vespa/storageapi/message/documentsummary.cpp
new file mode 100644
index 00000000000..6909b4d223c
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/documentsummary.cpp
@@ -0,0 +1,43 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "documentsummary.h"
+#include <ostream>
+
+namespace storage {
+namespace api {
+
+IMPLEMENT_COMMAND(DocumentSummaryCommand, DocumentSummaryReply)
+IMPLEMENT_REPLY(DocumentSummaryReply)
+
+DocumentSummaryCommand::DocumentSummaryCommand()
+ : StorageCommand(MessageType::DOCUMENTSUMMARY),
+ DocumentSummary()
+{ }
+
+void
+DocumentSummaryCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "DocumentSummary(" << getSummaryCount() << " summaries)";
+ if (verbose) {
+ out << " : ";
+ StorageCommand::print(out, verbose, indent);
+ }
+}
+
+DocumentSummaryReply::DocumentSummaryReply(const DocumentSummaryCommand& cmd)
+ : StorageReply(cmd)
+{ }
+
+void
+DocumentSummaryReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "DocumentSummaryReply()";
+ if (verbose) {
+ out << " : ";
+ StorageReply::print(out, verbose, indent);
+ }
+}
+
+} // api
+} // storage
diff --git a/storage/src/vespa/storageapi/message/documentsummary.h b/storage/src/vespa/storageapi/message/documentsummary.h
new file mode 100644
index 00000000000..5e2c1af3cfd
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/documentsummary.h
@@ -0,0 +1,39 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "visitor.h"
+#include <vespa/vdslib/container/documentsummary.h>
+
+namespace storage {
+namespace api {
+
+/**
+ * @class DocumentSummaryCommand
+ * @ingroup message
+ *
+ * @brief The result of a searchvisitor.
+ */
+class DocumentSummaryCommand : public StorageCommand,
+ public vdslib::DocumentSummary
+{
+public:
+ explicit DocumentSummaryCommand();
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGECOMMAND(DocumentSummaryCommand, onDocumentSummary)
+};
+
+/**
+ * @class DocumentSummaryReply
+ * @ingroup message
+ *
+ * @brief Response to a document summary command.
+ */
+class DocumentSummaryReply : public StorageReply {
+public:
+ explicit DocumentSummaryReply(const DocumentSummaryCommand& command);
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGEREPLY(DocumentSummaryReply, onDocumentSummaryReply)
+};
+
+} // api
+} // storage
diff --git a/storage/src/vespa/storageapi/message/internal.cpp b/storage/src/vespa/storageapi/message/internal.cpp
new file mode 100644
index 00000000000..5e1daaaf3be
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/internal.cpp
@@ -0,0 +1,45 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "internal.h"
+#include <ostream>
+
+
+namespace storage::api {
+
+InternalCommand::InternalCommand(uint32_t type)
+ : StorageCommand(MessageType::INTERNAL),
+ _type(type)
+{ }
+
+InternalCommand::~InternalCommand() = default;
+
+InternalReply::InternalReply(uint32_t type, const InternalCommand& cmd)
+ : StorageReply(cmd),
+ _type(type)
+{ }
+
+InternalReply::~InternalReply() = default;
+
+void
+InternalCommand::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ out << "InternalCommand(" << _type << ")";
+ if (verbose) {
+ out << " : ";
+ StorageCommand::print(out, verbose, indent);
+ }
+}
+
+void
+InternalReply::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ out << "InternalReply(" << _type << ")";
+ if (verbose) {
+ out << " : ";
+ StorageReply::print(out, verbose, indent);
+ }
+}
+
+
+}
+
diff --git a/storage/src/vespa/storageapi/message/internal.h b/storage/src/vespa/storageapi/message/internal.h
new file mode 100644
index 00000000000..e0fee3e5494
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/internal.h
@@ -0,0 +1,69 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @file internal.h
+ *
+ * Internal commands, used in storage. These are commands that don't need to
+ * be serialized as they never leave a node, implemented within storage itself
+ * to be able to include storage types not defined in the API.
+ *
+ * Historically these messages also existed so we could alter internal messages
+ * without recompiling clients, but currently no clients use storage API for
+ * communication anymore so this is no longer an issue.
+ */
+#pragma once
+
+#include <vespa/storageapi/messageapi/storagecommand.h>
+#include <vespa/storageapi/messageapi/storagereply.h>
+
+namespace storage::api {
+
+/**
+ * @class InternalCommand
+ * @ingroup message
+ *
+ * @brief Base class for commands local to a VDS node.
+ *
+ * This is the base class for internal server commands. They can not be
+ * serialized, so any attempt of sending such a command away from a storage
+ * node will fail.
+ */
+class InternalCommand : public StorageCommand {
+ uint32_t _type;
+
+public:
+ InternalCommand(uint32_t type);
+ ~InternalCommand() override;
+
+ uint32_t getType() const { return _type; }
+
+ bool callHandler(MessageHandler& h, const std::shared_ptr<StorageMessage> & m) const override {
+ return h.onInternal(std::static_pointer_cast<InternalCommand>(m));
+ }
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+};
+
+/**
+ * @class InternalReply
+ * @ingroup message
+ *
+ * @brief Response of an internal command.
+ */
+class InternalReply : public StorageReply {
+ uint32_t _type;
+
+public:
+ InternalReply(uint32_t type, const InternalCommand& cmd);
+ ~InternalReply() override;
+
+ uint32_t getType() const { return _type; }
+
+ bool callHandler(MessageHandler& h, const std::shared_ptr<StorageMessage> & m) const override {
+ return h.onInternalReply(std::static_pointer_cast<InternalReply>(m));
+ }
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+};
+
+}
+
diff --git a/storage/src/vespa/storageapi/message/persistence.cpp b/storage/src/vespa/storageapi/message/persistence.cpp
new file mode 100644
index 00000000000..41a53449b67
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/persistence.cpp
@@ -0,0 +1,345 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "persistence.h"
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/update/documentupdate.h>
+#include <vespa/vespalib/util/exceptions.h>
+#include <vespa/vespalib/stllike/asciistream.h>
+#include <ostream>
+
+namespace storage::api {
+
+IMPLEMENT_COMMAND(PutCommand, PutReply)
+IMPLEMENT_REPLY(PutReply)
+IMPLEMENT_COMMAND(UpdateCommand, UpdateReply)
+IMPLEMENT_REPLY(UpdateReply)
+IMPLEMENT_COMMAND(GetCommand, GetReply)
+IMPLEMENT_REPLY(GetReply)
+IMPLEMENT_COMMAND(RemoveCommand, RemoveReply)
+IMPLEMENT_REPLY(RemoveReply)
+IMPLEMENT_COMMAND(RevertCommand, RevertReply)
+IMPLEMENT_REPLY(RevertReply)
+
+TestAndSetCommand::TestAndSetCommand(const MessageType & messageType, const document::Bucket &bucket)
+ : BucketInfoCommand(messageType, bucket)
+{}
+TestAndSetCommand::~TestAndSetCommand() = default;
+
+PutCommand::PutCommand(const document::Bucket &bucket, const DocumentSP& doc, Timestamp time)
+ : TestAndSetCommand(MessageType::PUT, bucket),
+ _doc(doc),
+ _timestamp(time),
+ _updateTimestamp(0)
+{
+ if ( !_doc ) {
+ throw vespalib::IllegalArgumentException("Cannot put a null document", VESPA_STRLOC);
+ }
+}
+
+PutCommand::~PutCommand() = default;
+
+const document::DocumentId&
+PutCommand::getDocumentId() const {
+ return _doc->getId();
+}
+
+const document::DocumentType *
+PutCommand::getDocumentType() const {
+ return &_doc->getType();
+}
+
+vespalib::string
+PutCommand::getSummary() const
+{
+ vespalib::asciistream stream;
+ stream << "Put(BucketId(0x" << vespalib::hex << getBucketId().getId() << "), "
+ << _doc->getId().toString()
+ << ", timestamp " << vespalib::dec << _timestamp
+ << ')';
+
+ return stream.str();
+}
+
+void
+PutCommand::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ out << "Put(" << getBucketId() << ", " << _doc->getId()
+ << ", timestamp " << _timestamp << ", size "
+ << _doc->serialize().size() << ")";
+ if (verbose) {
+ out << " {\n" << indent << " ";
+ _doc->print(out, verbose, indent + " ");
+ out << "\n" << indent << "}" << " : ";
+ BucketInfoCommand::print(out, verbose, indent);
+ }
+}
+
+PutReply::PutReply(const PutCommand& cmd, bool wasFoundFlag)
+ : BucketInfoReply(cmd),
+ _docId(cmd.getDocumentId()),
+ _document(cmd.getDocument()),
+ _timestamp(cmd.getTimestamp()),
+ _updateTimestamp(cmd.getUpdateTimestamp()),
+ _wasFound(wasFoundFlag)
+{
+}
+
+PutReply::~PutReply() = default;
+
+void
+PutReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "PutReply(" << _docId << ", " << getBucketId() << ", timestamp " << _timestamp;
+
+ if (hasBeenRemapped()) {
+ out << " (was remapped)";
+ }
+
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ BucketInfoReply::print(out, verbose, indent);
+ }
+}
+
+UpdateCommand::UpdateCommand(const document::Bucket &bucket, const document::DocumentUpdate::SP& update, Timestamp time)
+ : TestAndSetCommand(MessageType::UPDATE, bucket),
+ _update(update),
+ _timestamp(time),
+ _oldTimestamp(0)
+{
+ if ( ! _update) {
+ throw vespalib::IllegalArgumentException("Cannot update a null update", VESPA_STRLOC);
+ }
+}
+
+const document::DocumentType *
+UpdateCommand::getDocumentType() const {
+ return &_update->getType();
+}
+
+UpdateCommand::~UpdateCommand() = default;
+
+const document::DocumentId&
+UpdateCommand::getDocumentId() const {
+ return _update->getId();
+}
+
+vespalib::string
+UpdateCommand::getSummary() const {
+ vespalib::asciistream stream;
+ stream << "Update(BucketId(0x" << vespalib::hex << getBucketId().getId() << "), "
+ << _update->getId().toString() << ", timestamp " << vespalib::dec << _timestamp;
+ if (_oldTimestamp != 0) {
+ stream << ", old timestamp " << _oldTimestamp;
+ }
+ stream << ')';
+
+ return stream.str();
+}
+
+void
+UpdateCommand::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ out << "Update(" << getBucketId() << ", " << _update->getId() << ", timestamp " << _timestamp;
+ if (_oldTimestamp != 0) {
+ out << ", old timestamp " << _oldTimestamp;
+ }
+ out << ")";
+ if (verbose) {
+ out << " {\n" << indent << " ";
+ _update->print(out, verbose, indent + " ");
+ out << "\n" << indent << "} : ";
+ BucketInfoCommand::print(out, verbose, indent);
+ }
+}
+
+UpdateReply::UpdateReply(const UpdateCommand& cmd, Timestamp oldTimestamp)
+ : BucketInfoReply(cmd),
+ _docId(cmd.getDocumentId()),
+ _timestamp(cmd.getTimestamp()),
+ _oldTimestamp(oldTimestamp),
+ _consistentNode((uint16_t)-1)
+{
+}
+
+UpdateReply::~UpdateReply() = default;
+
+void
+UpdateReply::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ out << "UpdateReply("
+ << _docId << ", " << getBucketId() << ", timestamp "
+ << _timestamp << ", timestamp of updated doc: " << _oldTimestamp;
+
+ if (_consistentNode != (uint16_t)-1) {
+ out << " Was inconsistent (best node " << _consistentNode << ")";
+ }
+
+ out << ")";
+
+ if (verbose) {
+ out << " : ";
+ BucketInfoReply::print(out, verbose, indent);
+ }
+}
+
+GetCommand::GetCommand(const document::Bucket &bucket, const document::DocumentId& docId,
+ vespalib::stringref fieldSet, Timestamp before)
+ : BucketInfoCommand(MessageType::GET, bucket),
+ _docId(docId),
+ _beforeTimestamp(before),
+ _fieldSet(fieldSet),
+ _internal_read_consistency(InternalReadConsistency::Strong)
+{
+}
+
+GetCommand::~GetCommand() = default;
+
+vespalib::string
+GetCommand::getSummary() const
+{
+ vespalib::asciistream stream;
+ stream << "Get(BucketId(" << vespalib::hex << getBucketId().getId() << "), " << _docId.toString()
+ << ", beforetimestamp " << vespalib::dec << _beforeTimestamp << ')';
+
+ return stream.str();
+}
+
+
+void
+GetCommand::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ out << "Get(" << getBucketId() << ", " << _docId << ")";
+ if (verbose) {
+ out << " : ";
+ BucketCommand::print(out, verbose, indent);
+ }
+}
+
+GetReply::GetReply(const GetCommand& cmd,
+ const DocumentSP& doc,
+ Timestamp lastModified,
+ bool had_consistent_replicas,
+ bool is_tombstone)
+ : BucketInfoReply(cmd),
+ _docId(cmd.getDocumentId()),
+ _fieldSet(cmd.getFieldSet()),
+ _doc(doc),
+ _beforeTimestamp(cmd.getBeforeTimestamp()),
+ _lastModifiedTime(lastModified),
+ _had_consistent_replicas(had_consistent_replicas),
+ _is_tombstone(is_tombstone)
+{
+}
+
+GetReply::~GetReply() = default;
+
+void
+GetReply::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ out << "GetReply(" << getBucketId() << ", " << _docId << ", timestamp " << _lastModifiedTime << ")";
+ if (verbose) {
+ out << " : ";
+ BucketReply::print(out, verbose, indent);
+ }
+}
+
+RemoveCommand::RemoveCommand(const document::Bucket &bucket, const document::DocumentId& docId, Timestamp timestamp)
+ : TestAndSetCommand(MessageType::REMOVE, bucket),
+ _docId(docId),
+ _timestamp(timestamp)
+{
+}
+
+RemoveCommand::~RemoveCommand() = default;
+
+vespalib::string
+RemoveCommand::getSummary() const {
+ vespalib::asciistream stream;
+ stream << "Remove(BucketId(0x" << vespalib::hex << getBucketId().getId() << "), "
+ << _docId.toString() << ", timestamp " << vespalib::dec << _timestamp << ')';
+
+ return stream.str();
+}
+void
+RemoveCommand::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ out << "Remove(" << getBucketId() << ", " << _docId << ", timestamp " << _timestamp << ")";
+ if (verbose) {
+ out << " : ";
+ BucketInfoCommand::print(out, verbose, indent);
+ }
+}
+
+RemoveReply::RemoveReply(const RemoveCommand& cmd, Timestamp oldTimestamp)
+ : BucketInfoReply(cmd),
+ _docId(cmd.getDocumentId()),
+ _timestamp(cmd.getTimestamp()),
+ _oldTimestamp(oldTimestamp)
+{
+}
+
+RemoveReply::~RemoveReply() = default;
+
+void
+RemoveReply::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ out << "RemoveReply(" << getBucketId() << ", " << _docId << ", timestamp " << _timestamp;
+ if (_oldTimestamp != 0) {
+ out << ", removed doc from " << _oldTimestamp;
+ } else {
+ out << ", not found";
+ }
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ BucketInfoReply::print(out, verbose, indent);
+ }
+}
+
+RevertCommand::RevertCommand(const document::Bucket &bucket, const std::vector<Timestamp>& revertTokens)
+ : BucketInfoCommand(MessageType::REVERT, bucket),
+ _tokens(revertTokens)
+{
+}
+
+RevertCommand::~RevertCommand() = default;
+
+void
+RevertCommand::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ out << "Revert(" << getBucketId();
+ if (verbose) {
+ out << ",";
+ for (Timestamp token : _tokens) {
+ out << "\n" << indent << " " << token;
+ }
+ }
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ BucketInfoCommand::print(out, verbose, indent);
+ }
+}
+
+RevertReply::RevertReply(const RevertCommand& cmd)
+ : BucketInfoReply(cmd),
+ _tokens(cmd.getRevertTokens())
+{
+}
+
+RevertReply::~RevertReply() = default;
+
+void
+RevertReply::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ out << "RevertReply(" << getBucketId() << ")";
+ if (verbose) {
+ out << " : ";
+ BucketInfoReply::print(out, verbose, indent);
+ }
+}
+
+}
diff --git a/storage/src/vespa/storageapi/message/persistence.h b/storage/src/vespa/storageapi/message/persistence.h
new file mode 100644
index 00000000000..1d8cfd9d277
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/persistence.h
@@ -0,0 +1,333 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @file persistence.h
+ *
+ * Persistence related commands, like put, get & remove
+ */
+#pragma once
+
+#include <vespa/storageapi/messageapi/bucketinforeply.h>
+#include <vespa/storageapi/defs.h>
+#include <vespa/document/base/documentid.h>
+#include <vespa/documentapi/messagebus/messages/testandsetcondition.h>
+
+namespace document {
+ class DocumentUpdate;
+ class Document;
+}
+namespace storage::api {
+
+using documentapi::TestAndSetCondition;
+using DocumentSP = std::shared_ptr<document::Document>;
+
+class TestAndSetCommand : public BucketInfoCommand {
+ TestAndSetCondition _condition;
+public:
+ TestAndSetCommand(const MessageType & messageType, const document::Bucket &bucket);
+ ~TestAndSetCommand() override;
+
+ void setCondition(const TestAndSetCondition & condition) { _condition = condition; }
+ const TestAndSetCondition & getCondition() const { return _condition; }
+
+ /**
+ * Uniform interface to get document id
+ * Used by test and set to retrieve already existing document
+ */
+ virtual const document::DocumentId & getDocumentId() const = 0;
+ virtual const document::DocumentType * getDocumentType() const { return nullptr; }
+};
+
+/**
+ * @class PutCommand
+ * @ingroup message
+ *
+ * @brief Command for adding a document to the storage system.
+ */
+class PutCommand : public TestAndSetCommand {
+ DocumentSP _doc;
+ Timestamp _timestamp;
+ Timestamp _updateTimestamp;
+
+public:
+ PutCommand(const document::Bucket &bucket, const DocumentSP&, Timestamp);
+ ~PutCommand() override;
+
+ void setTimestamp(Timestamp ts) { _timestamp = ts; }
+
+ /**
+ * If set, this PUT will only update the header of an existing document,
+ * rather than writing an entire new PUT. It will only perform the write if
+ * there exists a document already with the given timestamp.
+ */
+ void setUpdateTimestamp(Timestamp ts) { _updateTimestamp = ts; }
+ Timestamp getUpdateTimestamp() const { return _updateTimestamp; }
+
+ const DocumentSP& getDocument() const { return _doc; }
+ const document::DocumentId& getDocumentId() const override;
+ Timestamp getTimestamp() const { return _timestamp; }
+ const document::DocumentType * getDocumentType() const override;
+
+ vespalib::string getSummary() const override;
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGECOMMAND(PutCommand, onPut);
+};
+
+/**
+ * @class PutReply
+ * @ingroup message
+ *
+ * @brief Reply of a put command.
+ */
+class PutReply : public BucketInfoReply {
+ document::DocumentId _docId;
+ DocumentSP _document; // Not serialized
+ Timestamp _timestamp;
+ Timestamp _updateTimestamp;
+ bool _wasFound;
+
+public:
+ explicit PutReply(const PutCommand& cmd, bool wasFound = true);
+ ~PutReply() override;
+
+ const document::DocumentId& getDocumentId() const { return _docId; }
+ bool hasDocument() const { return _document.get(); }
+ const DocumentSP& getDocument() const { return _document; }
+ Timestamp getTimestamp() const { return _timestamp; };
+ Timestamp getUpdateTimestamp() const { return _updateTimestamp; }
+
+ bool wasFound() const { return _wasFound; }
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGEREPLY(PutReply, onPutReply)
+};
+
+/**
+ * @class UpdateCommand
+ * @ingroup message
+ *
+ * @brief Command for updating a document to the storage system.
+ */
+class UpdateCommand : public TestAndSetCommand {
+ std::shared_ptr<document::DocumentUpdate> _update;
+ Timestamp _timestamp;
+ Timestamp _oldTimestamp;
+
+public:
+ UpdateCommand(const document::Bucket &bucket,
+ const std::shared_ptr<document::DocumentUpdate>&, Timestamp);
+ ~UpdateCommand() override;
+
+ void setTimestamp(Timestamp ts) { _timestamp = ts; }
+ void setOldTimestamp(Timestamp ts) { _oldTimestamp = ts; }
+
+ const std::shared_ptr<document::DocumentUpdate>& getUpdate() const { return _update; }
+ const document::DocumentId& getDocumentId() const override;
+ Timestamp getTimestamp() const { return _timestamp; }
+ Timestamp getOldTimestamp() const { return _oldTimestamp; }
+
+ const document::DocumentType * getDocumentType() const override;
+ vespalib::string getSummary() const override;
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGECOMMAND(UpdateCommand, onUpdate);
+};
+
+/**
+ * @class UpdateReply
+ * @ingroup message
+ *
+ * @brief Reply of a update command.
+ */
+class UpdateReply : public BucketInfoReply {
+ document::DocumentId _docId;
+ Timestamp _timestamp;
+ Timestamp _oldTimestamp;
+ uint16_t _consistentNode;
+
+public:
+ UpdateReply(const UpdateCommand& cmd, Timestamp oldTimestamp = 0);
+ ~UpdateReply() override;
+
+ void setOldTimestamp(Timestamp ts) { _oldTimestamp = ts; }
+
+ const document::DocumentId& getDocumentId() const { return _docId; }
+ Timestamp getTimestamp() const { return _timestamp; }
+ Timestamp getOldTimestamp() const { return _oldTimestamp; }
+
+ bool wasFound() const { return (_oldTimestamp != 0); }
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ /**
+ * If this update was inconsistent (multiple different timestamps returned),
+ * set the "best" node.
+ */
+ void setNodeWithNewestTimestamp(uint16_t node) { _consistentNode = node; }
+
+ uint16_t getNodeWithNewestTimestamp() { return _consistentNode; }
+
+ DECLARE_STORAGEREPLY(UpdateReply, onUpdateReply)
+};
+
+
+/**
+ * @class GetCommand
+ * @ingroup message
+ *
+ * @brief Command for returning a single document.
+ *
+ * Normally, the newest version of a document is retrieved. The timestamp can
+ * be used to retrieve the newest copy, which is not newer than the given
+ * timestamp.
+ */
+class GetCommand : public BucketInfoCommand {
+ document::DocumentId _docId;
+ Timestamp _beforeTimestamp;
+ vespalib::string _fieldSet;
+ InternalReadConsistency _internal_read_consistency;
+public:
+ GetCommand(const document::Bucket &bucket, const document::DocumentId&,
+ vespalib::stringref fieldSet, Timestamp before = MAX_TIMESTAMP);
+ ~GetCommand() override;
+ void setBeforeTimestamp(Timestamp ts) { _beforeTimestamp = ts; }
+ const document::DocumentId& getDocumentId() const { return _docId; }
+ Timestamp getBeforeTimestamp() const { return _beforeTimestamp; }
+ const vespalib::string& getFieldSet() const { return _fieldSet; }
+ void setFieldSet(vespalib::stringref fieldSet) { _fieldSet = fieldSet; }
+ InternalReadConsistency internal_read_consistency() const noexcept {
+ return _internal_read_consistency;
+ }
+ void set_internal_read_consistency(InternalReadConsistency consistency) noexcept {
+ _internal_read_consistency = consistency;
+ }
+
+ vespalib::string getSummary() const override;
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ api::LockingRequirements lockingRequirements() const noexcept override {
+ return api::LockingRequirements::Shared;
+ }
+
+ DECLARE_STORAGECOMMAND(GetCommand, onGet)
+};
+
+/**
+ * @class GetReply
+ * @ingroup message
+ *
+ * @brief Reply for a get command.
+ */
+class GetReply : public BucketInfoReply {
+ document::DocumentId _docId; // In case of not found, we want id still
+ vespalib::string _fieldSet;
+ DocumentSP _doc; // Null pointer if not found
+ Timestamp _beforeTimestamp;
+ Timestamp _lastModifiedTime;
+ bool _had_consistent_replicas;
+ bool _is_tombstone;
+public:
+ explicit GetReply(const GetCommand& cmd,
+ const DocumentSP& doc = DocumentSP(),
+ Timestamp lastModified = 0,
+ bool had_consistent_replicas = false,
+ bool is_tombstone = false);
+
+ ~GetReply() override;
+
+ const DocumentSP& getDocument() const { return _doc; }
+ const document::DocumentId& getDocumentId() const { return _docId; }
+ const vespalib::string& getFieldSet() const { return _fieldSet; }
+
+ Timestamp getLastModifiedTimestamp() const noexcept { return _lastModifiedTime; }
+ Timestamp getBeforeTimestamp() const noexcept { return _beforeTimestamp; }
+
+ [[nodiscard]] bool had_consistent_replicas() const noexcept { return _had_consistent_replicas; }
+ [[nodiscard]] bool is_tombstone() const noexcept { return _is_tombstone; }
+
+ bool wasFound() const { return (_doc.get() != nullptr); }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGEREPLY(GetReply, onGetReply)
+};
+
+/**
+ * @class RemoveCommand
+ * @ingroup message
+ *
+ * @brief Command for removing a document.
+ */
+class RemoveCommand : public TestAndSetCommand {
+ document::DocumentId _docId;
+ Timestamp _timestamp;
+
+public:
+ RemoveCommand(const document::Bucket &bucket, const document::DocumentId& docId, Timestamp timestamp);
+ ~RemoveCommand() override;
+
+ void setTimestamp(Timestamp ts) { _timestamp = ts; }
+ const document::DocumentId& getDocumentId() const override { return _docId; }
+ Timestamp getTimestamp() const { return _timestamp; }
+ vespalib::string getSummary() const override;
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGECOMMAND(RemoveCommand, onRemove)
+};
+
+/**
+ * @class RemoveReply
+ * @ingroup message
+ *
+ * @brief Reply for a remove command.
+ */
+class RemoveReply : public BucketInfoReply {
+ document::DocumentId _docId;
+ Timestamp _timestamp;
+ Timestamp _oldTimestamp;
+public:
+ explicit RemoveReply(const RemoveCommand& cmd, Timestamp oldTimestamp = 0);
+ ~RemoveReply() override;
+
+ const document::DocumentId& getDocumentId() const { return _docId; }
+ Timestamp getTimestamp() { return _timestamp; };
+ Timestamp getOldTimestamp() const { return _oldTimestamp; }
+ void setOldTimestamp(Timestamp oldTimestamp) { _oldTimestamp = oldTimestamp; }
+ bool wasFound() const { return (_oldTimestamp != 0); }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGEREPLY(RemoveReply, onRemoveReply)
+};
+
+/**
+ * @class RevertCommand
+ * @ingroup message
+ *
+ * @brief Command for reverting a write or remove operation.
+ */
+class RevertCommand : public BucketInfoCommand {
+ std::vector<Timestamp> _tokens;
+public:
+ RevertCommand(const document::Bucket &bucket,
+ const std::vector<Timestamp>& revertTokens);
+ ~RevertCommand() override;
+ const std::vector<Timestamp>& getRevertTokens() const { return _tokens; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGECOMMAND(RevertCommand, onRevert)
+};
+
+/**
+ * @class RevertReply
+ * @ingroup message
+ *
+ * @brief Reply for a revert command.
+ */
+class RevertReply : public BucketInfoReply {
+ std::vector<Timestamp> _tokens;
+public:
+ explicit RevertReply(const RevertCommand& cmd);
+ ~RevertReply() override;
+ const std::vector<Timestamp>& getRevertTokens() const { return _tokens; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGEREPLY(RevertReply, onRevertReply)
+};
+
+}
diff --git a/storage/src/vespa/storageapi/message/queryresult.cpp b/storage/src/vespa/storageapi/message/queryresult.cpp
new file mode 100644
index 00000000000..b5d29cf4e02
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/queryresult.cpp
@@ -0,0 +1,45 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "queryresult.h"
+#include <ostream>
+
+namespace storage {
+namespace api {
+
+IMPLEMENT_COMMAND(QueryResultCommand, QueryResultReply)
+IMPLEMENT_REPLY(QueryResultReply)
+
+QueryResultCommand::QueryResultCommand()
+ : StorageCommand(MessageType::QUERYRESULT),
+ _searchResult(),
+ _summary()
+{ }
+
+void
+QueryResultCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "QueryResultCommand(" << _searchResult.getHitCount() << " hits)";
+ if (verbose) {
+ out << " : ";
+ StorageCommand::print(out, verbose, indent);
+ }
+}
+
+QueryResultReply::QueryResultReply(const QueryResultCommand& cmd)
+ : StorageReply(cmd)
+{ }
+
+void
+QueryResultReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "QueryResultReply()";
+ if (verbose) {
+ out << " : ";
+ StorageReply::print(out, verbose, indent);
+ }
+}
+
+} // api
+} // storage
diff --git a/storage/src/vespa/storageapi/message/queryresult.h b/storage/src/vespa/storageapi/message/queryresult.h
new file mode 100644
index 00000000000..c3bbbdc47ce
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/queryresult.h
@@ -0,0 +1,48 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "visitor.h"
+#include <vespa/vdslib/container/searchresult.h>
+#include <vespa/vdslib/container/documentsummary.h>
+
+namespace storage {
+namespace api {
+
+/**
+ * @class QueryResultCommand
+ * @ingroup message
+ *
+ * @brief The result of a searchvisitor.
+ */
+class QueryResultCommand : public StorageCommand {
+public:
+ QueryResultCommand();
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ const vdslib::SearchResult & getSearchResult() const { return _searchResult; }
+ vdslib::SearchResult & getSearchResult() { return _searchResult; }
+ const vdslib::DocumentSummary & getDocumentSummary() const { return _summary; }
+ vdslib::DocumentSummary & getDocumentSummary() { return _summary; }
+
+ DECLARE_STORAGECOMMAND(QueryResultCommand, onQueryResult)
+private:
+ vdslib::SearchResult _searchResult;
+ vdslib::DocumentSummary _summary;
+};
+
+/**
+ * @class QueryResultReply
+ * @ingroup message
+ *
+ * @brief Response to a search result command.
+ */
+class QueryResultReply : public StorageReply {
+public:
+ explicit QueryResultReply(const QueryResultCommand& command);
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGEREPLY(QueryResultReply, onQueryResultReply)
+};
+
+} // api
+} // storage
diff --git a/storage/src/vespa/storageapi/message/removelocation.cpp b/storage/src/vespa/storageapi/message/removelocation.cpp
new file mode 100644
index 00000000000..7b7ed894b2c
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/removelocation.cpp
@@ -0,0 +1,34 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "removelocation.h"
+#include <ostream>
+
+namespace storage::api {
+
+IMPLEMENT_COMMAND(RemoveLocationCommand, RemoveLocationReply)
+IMPLEMENT_REPLY(RemoveLocationReply)
+
+RemoveLocationCommand::RemoveLocationCommand(vespalib::stringref documentSelection,
+ const document::Bucket &bucket)
+ : BucketInfoCommand(MessageType::REMOVELOCATION, bucket),
+ _documentSelection(documentSelection)
+{}
+
+RemoveLocationCommand::~RemoveLocationCommand() {}
+
+void
+RemoveLocationCommand::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ if (_documentSelection.length()) {
+ out << "Remove selection(" << _documentSelection << "): ";
+ }
+ BucketInfoCommand::print(out, verbose, indent);
+}
+
+RemoveLocationReply::RemoveLocationReply(const RemoveLocationCommand& cmd, uint32_t docs_removed)
+ : BucketInfoReply(cmd),
+ _documents_removed(docs_removed)
+{
+}
+
+}
diff --git a/storage/src/vespa/storageapi/message/removelocation.h b/storage/src/vespa/storageapi/message/removelocation.h
new file mode 100644
index 00000000000..276b090cc57
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/removelocation.h
@@ -0,0 +1,35 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/storageapi/defs.h>
+#include <vespa/storageapi/messageapi/storagecommand.h>
+#include <vespa/storageapi/messageapi/bucketinforeply.h>
+
+namespace storage::api {
+
+class RemoveLocationCommand : public BucketInfoCommand
+{
+public:
+ RemoveLocationCommand(vespalib::stringref documentSelection, const document::Bucket &bucket);
+ ~RemoveLocationCommand() override;
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ const vespalib::string& getDocumentSelection() const { return _documentSelection; }
+ DECLARE_STORAGECOMMAND(RemoveLocationCommand, onRemoveLocation);
+private:
+ vespalib::string _documentSelection;
+};
+
+class RemoveLocationReply : public BucketInfoReply
+{
+ uint32_t _documents_removed;
+public:
+ explicit RemoveLocationReply(const RemoveLocationCommand& cmd, uint32_t docs_removed = 0);
+ void set_documents_removed(uint32_t docs_removed) noexcept {
+ _documents_removed = docs_removed;
+ }
+ uint32_t documents_removed() const noexcept { return _documents_removed; }
+ DECLARE_STORAGEREPLY(RemoveLocationReply, onRemoveLocationReply)
+};
+
+}
diff --git a/storage/src/vespa/storageapi/message/searchresult.cpp b/storage/src/vespa/storageapi/message/searchresult.cpp
new file mode 100644
index 00000000000..b2cf04b0410
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/searchresult.cpp
@@ -0,0 +1,47 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "searchresult.h"
+#include <ostream>
+
+using vdslib::SearchResult;
+
+namespace storage {
+namespace api {
+
+IMPLEMENT_COMMAND(SearchResultCommand, SearchResultReply)
+IMPLEMENT_REPLY(SearchResultReply)
+
+SearchResultCommand::SearchResultCommand()
+ : StorageCommand(MessageType::SEARCHRESULT),
+ SearchResult()
+{
+}
+
+void
+SearchResultCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "SearchResultCommand(" << getHitCount() << " hits)";
+ if (verbose) {
+ out << " : ";
+ StorageCommand::print(out, verbose, indent);
+ }
+}
+
+SearchResultReply::SearchResultReply(const SearchResultCommand& cmd)
+ : StorageReply(cmd)
+{ }
+
+void
+SearchResultReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "SearchResultReply()";
+ if (verbose) {
+ out << " : ";
+ StorageReply::print(out, verbose, indent);
+ }
+}
+
+} // api
+} // storage
diff --git a/storage/src/vespa/storageapi/message/searchresult.h b/storage/src/vespa/storageapi/message/searchresult.h
new file mode 100644
index 00000000000..4795876102d
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/searchresult.h
@@ -0,0 +1,37 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "visitor.h"
+#include <vespa/vdslib/container/searchresult.h>
+
+namespace storage {
+namespace api {
+
+/**
+ * @class SearchResultCommand
+ * @ingroup message
+ *
+ * @brief The result of a searchvisitor.
+ */
+class SearchResultCommand : public StorageCommand, public vdslib::SearchResult {
+public:
+ SearchResultCommand();
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGECOMMAND(SearchResultCommand, onSearchResult)
+};
+
+/**
+ * @class SearchResultReply
+ * @ingroup message
+ *
+ * @brief Response to a search result command.
+ */
+class SearchResultReply : public StorageReply {
+public:
+ explicit SearchResultReply(const SearchResultCommand& command);
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGEREPLY(SearchResultReply, onSearchResultReply)
+};
+
+} // api
+} // storage
diff --git a/storage/src/vespa/storageapi/message/stat.cpp b/storage/src/vespa/storageapi/message/stat.cpp
new file mode 100644
index 00000000000..3b97f4f5541
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/stat.cpp
@@ -0,0 +1,104 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "stat.h"
+#include <ostream>
+
+namespace storage::api {
+
+IMPLEMENT_COMMAND(StatBucketCommand, StatBucketReply)
+IMPLEMENT_REPLY(StatBucketReply)
+IMPLEMENT_COMMAND(GetBucketListCommand, GetBucketListReply)
+IMPLEMENT_REPLY(GetBucketListReply)
+
+StatBucketCommand::StatBucketCommand(const document::Bucket& bucket,
+ vespalib::stringref documentSelection)
+ : BucketCommand(MessageType::STATBUCKET, bucket),
+ _docSelection(documentSelection)
+{
+}
+
+StatBucketCommand::~StatBucketCommand() = default;
+
+void
+StatBucketCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "StatBucketCommand(" << getBucketId()
+ << ", selection: " << _docSelection << ")";
+ if (verbose) {
+ out << " : ";
+ BucketCommand::print(out, verbose, indent);
+ }
+}
+
+StatBucketReply::StatBucketReply(const StatBucketCommand& cmd,
+ vespalib::stringref results)
+ : BucketReply(cmd),
+ _results(results)
+{
+}
+
+void
+StatBucketReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "StatBucketReply(" << getBucketId();
+ if (verbose) {
+ out << ", result: " << _results << ") : ";
+ BucketReply::print(out, verbose, indent);
+ } else {
+ vespalib::string::size_type pos = _results.find('\n');
+ vespalib::string overview;
+ if (pos != vespalib::string::npos) {
+ overview = _results.substr(0, pos) + " ...";
+ } else {
+ overview = _results;
+ }
+ out << ", result: " << overview << ")";
+ }
+}
+
+GetBucketListCommand::GetBucketListCommand(const document::Bucket &bucket)
+ : BucketCommand(MessageType::GETBUCKETLIST, bucket)
+{
+}
+
+void
+GetBucketListCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "GetBucketList(" << getBucketId() << ")";
+ if (verbose) {
+ out << " : ";
+ BucketCommand::print(out, verbose, indent);
+ }
+}
+
+GetBucketListReply::GetBucketListReply(const GetBucketListCommand& cmd)
+ : BucketReply(cmd),
+ _buckets()
+{}
+
+GetBucketListReply::~GetBucketListReply() {}
+
+void
+GetBucketListReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "GetBucketListReply(" << getBucketId() << ", Info on "
+ << _buckets.size() << " buckets)";
+ if (verbose) {
+ out << " : ";
+ BucketReply::print(out, verbose, indent);
+ }
+}
+
+std::ostream&
+operator<<(std::ostream& out, const GetBucketListReply::BucketInfo& instance)
+{
+ out << "BucketInfo(" << instance._bucket << ": "
+ << instance._bucketInformation << ")";
+ return out;
+}
+
+}
diff --git a/storage/src/vespa/storageapi/message/stat.h b/storage/src/vespa/storageapi/message/stat.h
new file mode 100644
index 00000000000..0797ae43799
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/stat.h
@@ -0,0 +1,90 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/storageapi/messageapi/bucketcommand.h>
+#include <vespa/storageapi/messageapi/bucketreply.h>
+
+namespace storage::api {
+
+/**
+ * \class StatBucketCommand
+ * \ingroup stat
+ *
+ * \brief Command used to get information about a given bucket..
+ *
+ * Command used by stat to get detailed information about a bucket.
+ */
+class StatBucketCommand : public BucketCommand {
+private:
+ vespalib::string _docSelection;
+public:
+ StatBucketCommand(const document::Bucket &bucket,
+ vespalib::stringref documentSelection);
+ ~StatBucketCommand() override;
+
+ const vespalib::string& getDocumentSelection() const { return _docSelection; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGECOMMAND(StatBucketCommand, onStatBucket);
+};
+
+class StatBucketReply : public BucketReply {
+ vespalib::string _results;
+public:
+ explicit StatBucketReply(const StatBucketCommand&, vespalib::stringref results = "");
+ const vespalib::string& getResults() const noexcept { return _results; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGEREPLY(StatBucketReply, onStatBucketReply)
+};
+
+/**
+ * \class GetBucketListCommand
+ * \ingroup stat
+ *
+ * \brief Command used to find actual buckets related to a given one.
+ *
+ * Command used by stat to query distributor to find actual buckets contained
+ * by the given bucket, or buckets that contain the given bucket. (getAll() call
+ * on bucket database)
+ */
+class GetBucketListCommand : public BucketCommand {
+public:
+ explicit GetBucketListCommand(const document::Bucket &bucket);
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGECOMMAND(GetBucketListCommand, onGetBucketList);
+};
+
+class GetBucketListReply : public BucketReply {
+public:
+ struct BucketInfo {
+ document::BucketId _bucket;
+ vespalib::string _bucketInformation;
+
+ BucketInfo(const document::BucketId& id,
+ vespalib::stringref bucketInformation)
+ : _bucket(id),
+ _bucketInformation(bucketInformation)
+ {}
+
+ bool operator==(const BucketInfo& other) const {
+ return (_bucket == other._bucket
+ && _bucketInformation == other._bucketInformation);
+ }
+ };
+
+private:
+ std::vector<BucketInfo> _buckets;
+
+public:
+ explicit GetBucketListReply(const GetBucketListCommand&);
+ ~GetBucketListReply() override;
+ std::vector<BucketInfo>& getBuckets() { return _buckets; }
+ const std::vector<BucketInfo>& getBuckets() const { return _buckets; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGEREPLY(GetBucketListReply, onGetBucketListReply)
+
+};
+
+std::ostream& operator<<(std::ostream& out, const GetBucketListReply::BucketInfo& instance);
+
+}
diff --git a/storage/src/vespa/storageapi/message/state.cpp b/storage/src/vespa/storageapi/message/state.cpp
new file mode 100644
index 00000000000..23bd766ac2a
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/state.cpp
@@ -0,0 +1,142 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "state.h"
+#include <vespa/storageapi/messageapi/storagemessage.h>
+#include <vespa/vdslib/state/clusterstate.h>
+#include <ostream>
+
+namespace storage {
+namespace api {
+
+IMPLEMENT_COMMAND(GetNodeStateCommand, GetNodeStateReply)
+IMPLEMENT_REPLY(GetNodeStateReply)
+IMPLEMENT_COMMAND(SetSystemStateCommand, SetSystemStateReply)
+IMPLEMENT_REPLY(SetSystemStateReply)
+IMPLEMENT_COMMAND(ActivateClusterStateVersionCommand, ActivateClusterStateVersionReply)
+IMPLEMENT_REPLY(ActivateClusterStateVersionReply)
+
+GetNodeStateCommand::GetNodeStateCommand(lib::NodeState::UP expectedState)
+ : StorageCommand(MessageType::GETNODESTATE),
+ _expectedState(std::move(expectedState))
+{
+}
+
+void
+GetNodeStateCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "GetNodeStateCommand(";
+ if (_expectedState.get() != 0) {
+ out << "Expected state: " << *_expectedState;
+ }
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ StorageCommand::print(out, verbose, indent);
+ }
+}
+
+GetNodeStateReply::GetNodeStateReply(const GetNodeStateCommand& cmd)
+ : StorageReply(cmd),
+ _state()
+{
+}
+
+GetNodeStateReply::GetNodeStateReply(const GetNodeStateCommand& cmd,
+ const lib::NodeState& state)
+ : StorageReply(cmd),
+ _state(new lib::NodeState(state))
+{
+}
+
+void
+GetNodeStateReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "GetNodeStateReply(";
+ if (_state.get()) {
+ out << "State: " << *_state;
+ }
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ StorageReply::print(out, verbose, indent);
+ }
+}
+
+SetSystemStateCommand::SetSystemStateCommand(const lib::ClusterStateBundle& state)
+ : StorageCommand(MessageType::SETSYSTEMSTATE),
+ _state(state)
+{
+}
+
+SetSystemStateCommand::SetSystemStateCommand(const lib::ClusterState& state)
+ : StorageCommand(MessageType::SETSYSTEMSTATE),
+ _state(state)
+{
+}
+
+void
+SetSystemStateCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "SetSystemStateCommand(" << *_state.getBaselineClusterState() << ")";
+ if (verbose) {
+ out << " : ";
+ StorageCommand::print(out, verbose, indent);
+ }
+}
+
+SetSystemStateReply::SetSystemStateReply(const SetSystemStateCommand& cmd)
+ : StorageReply(cmd),
+ _state(cmd.getClusterStateBundle())
+{
+}
+
+void
+SetSystemStateReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "SetSystemStateReply()";
+ if (verbose) {
+ out << " : ";
+ StorageReply::print(out, verbose, indent);
+ }
+}
+
+ActivateClusterStateVersionCommand::ActivateClusterStateVersionCommand(uint32_t version)
+ : StorageCommand(MessageType::ACTIVATE_CLUSTER_STATE_VERSION),
+ _version(version)
+{
+}
+
+void ActivateClusterStateVersionCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "ActivateClusterStateVersionCommand(" << _version << ")";
+ if (verbose) {
+ out << " : ";
+ StorageCommand::print(out, verbose, indent);
+ }
+}
+
+ActivateClusterStateVersionReply::ActivateClusterStateVersionReply(const ActivateClusterStateVersionCommand& cmd)
+ : StorageReply(cmd),
+ _activateVersion(cmd.version()),
+ _actualVersion(0) // Must be set explicitly
+{
+}
+
+void ActivateClusterStateVersionReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "ActivateClusterStateVersionReply(activate " << _activateVersion
+ << ", actual " << _actualVersion << ")";
+ if (verbose) {
+ out << " : ";
+ StorageReply::print(out, verbose, indent);
+ }
+}
+
+} // api
+} // storage
diff --git a/storage/src/vespa/storageapi/message/state.h b/storage/src/vespa/storageapi/message/state.h
new file mode 100644
index 00000000000..aa562c77ef9
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/state.h
@@ -0,0 +1,119 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/storageapi/messageapi/storagecommand.h>
+#include <vespa/storageapi/messageapi/storagereply.h>
+#include <vespa/vdslib/state/nodestate.h>
+#include <vespa/vdslib/state/cluster_state_bundle.h>
+
+namespace storage::api {
+
+/**
+ * @class GetNodeStateCommand
+ * @ingroup message
+ *
+ * @brief Command for setting node state. No payload
+ */
+class GetNodeStateCommand : public StorageCommand {
+ lib::NodeState::UP _expectedState;
+
+public:
+ explicit GetNodeStateCommand(lib::NodeState::UP expectedState);
+
+ const lib::NodeState* getExpectedState() const { return _expectedState.get(); }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGECOMMAND(GetNodeStateCommand, onGetNodeState)
+};
+
+/**
+ * @class GetNodeStateReply
+ * @ingroup message
+ *
+ * @brief Reply to GetNodeStateCommand
+ */
+class GetNodeStateReply : public StorageReply {
+ lib::NodeState::UP _state;
+ std::string _nodeInfo;
+
+public:
+ GetNodeStateReply(const GetNodeStateCommand&); // Only used on makeReply()
+ GetNodeStateReply(const GetNodeStateCommand&, const lib::NodeState&);
+
+ bool hasNodeState() const { return (_state.get() != 0); }
+ const lib::NodeState& getNodeState() const { return *_state; }
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ void setNodeInfo(const std::string& info) { _nodeInfo = info; }
+ const std::string& getNodeInfo() const { return _nodeInfo; }
+
+ DECLARE_STORAGEREPLY(GetNodeStateReply, onGetNodeStateReply)
+};
+
+/**
+ * @class SetSystemStateCommand
+ * @ingroup message
+ *
+ * @brief Command for telling a node about the system state - state of each node
+ * in the system and state of the system (all ok, no merging, block
+ * put/get/remove etx)
+ */
+class SetSystemStateCommand : public StorageCommand {
+ lib::ClusterStateBundle _state;
+
+public:
+ explicit SetSystemStateCommand(const lib::ClusterStateBundle &state);
+ explicit SetSystemStateCommand(const lib::ClusterState &state);
+ const lib::ClusterState& getSystemState() const { return *_state.getBaselineClusterState(); }
+ const lib::ClusterStateBundle& getClusterStateBundle() const { return _state; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGECOMMAND(SetSystemStateCommand, onSetSystemState)
+};
+
+/**
+ * @class SetSystemStateReply
+ * @ingroup message
+ *
+ * @brief Reply received after a SetSystemStateCommand.
+ */
+class SetSystemStateReply : public StorageReply {
+ lib::ClusterStateBundle _state;
+
+public:
+ explicit SetSystemStateReply(const SetSystemStateCommand& cmd);
+
+ // Not serialized. Available locally
+ const lib::ClusterState& getSystemState() const { return *_state.getBaselineClusterState(); }
+ const lib::ClusterStateBundle& getClusterStateBundle() const { return _state; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGEREPLY(SetSystemStateReply, onSetSystemStateReply)
+};
+
+class ActivateClusterStateVersionCommand : public StorageCommand {
+ uint32_t _version;
+public:
+ explicit ActivateClusterStateVersionCommand(uint32_t version);
+ uint32_t version() const noexcept { return _version; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGECOMMAND(ActivateClusterStateVersionCommand, onActivateClusterStateVersion);
+};
+
+class ActivateClusterStateVersionReply : public StorageReply {
+ uint32_t _activateVersion;
+ uint32_t _actualVersion;
+public:
+ explicit ActivateClusterStateVersionReply(const ActivateClusterStateVersionCommand&);
+ uint32_t activateVersion() const noexcept { return _activateVersion; }
+ void setActualVersion(uint32_t version) noexcept { _actualVersion = version; }
+ uint32_t actualVersion() const noexcept { return _actualVersion; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGEREPLY(ActivateClusterStateVersionReply, onActivateClusterStateVersionReply);
+};
+
+}
diff --git a/storage/src/vespa/storageapi/message/visitor.cpp b/storage/src/vespa/storageapi/message/visitor.cpp
new file mode 100644
index 00000000000..fb3274273ac
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/visitor.cpp
@@ -0,0 +1,233 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "visitor.h"
+#include <vespa/document/fieldset/fieldsets.h>
+#include <vespa/vespalib/util/array.hpp>
+#include <climits>
+#include <ostream>
+
+namespace storage::api {
+
+IMPLEMENT_COMMAND(CreateVisitorCommand, CreateVisitorReply)
+IMPLEMENT_REPLY(CreateVisitorReply)
+IMPLEMENT_COMMAND(DestroyVisitorCommand, DestroyVisitorReply)
+IMPLEMENT_REPLY(DestroyVisitorReply)
+IMPLEMENT_COMMAND(VisitorInfoCommand, VisitorInfoReply)
+IMPLEMENT_REPLY(VisitorInfoReply)
+
+CreateVisitorCommand::CreateVisitorCommand(document::BucketSpace bucketSpace,
+ vespalib::stringref libraryName,
+ vespalib::stringref instanceId,
+ vespalib::stringref docSelection)
+ : StorageCommand(MessageType::VISITOR_CREATE),
+ _bucketSpace(bucketSpace),
+ _libName(libraryName),
+ _params(),
+ _controlDestination(),
+ _dataDestination(),
+ _docSelection(docSelection),
+ _buckets(),
+ _fromTime(0),
+ _toTime(api::MAX_TIMESTAMP),
+ _visitorCmdId(getMsgId()),
+ _instanceId(instanceId),
+ _visitorId(0),
+ _visitRemoves(false),
+ _fieldSet(document::AllFields::NAME),
+ _visitInconsistentBuckets(false),
+ _queueTimeout(2000ms),
+ _maxPendingReplyCount(2),
+ _version(50),
+ _maxBucketsPerVisitor(1)
+{
+}
+
+CreateVisitorCommand::CreateVisitorCommand(const CreateVisitorCommand& o)
+ : StorageCommand(o),
+ _bucketSpace(o._bucketSpace),
+ _libName(o._libName),
+ _params(o._params),
+ _controlDestination(o._controlDestination),
+ _dataDestination(o._dataDestination),
+ _docSelection(o._docSelection),
+ _buckets(o._buckets),
+ _fromTime(o._fromTime),
+ _toTime(o._toTime),
+ _visitorCmdId(getMsgId()),
+ _instanceId(o._instanceId),
+ _visitorId(o._visitorId),
+ _visitRemoves(o._visitRemoves),
+ _fieldSet(o._fieldSet),
+ _visitInconsistentBuckets(o._visitInconsistentBuckets),
+ _queueTimeout(o._queueTimeout),
+ _maxPendingReplyCount(o._maxPendingReplyCount),
+ _version(o._version),
+ _maxBucketsPerVisitor(o._maxBucketsPerVisitor)
+{
+}
+
+CreateVisitorCommand::~CreateVisitorCommand() = default;
+
+document::Bucket
+CreateVisitorCommand::getBucket() const
+{
+ return document::Bucket(_bucketSpace, document::BucketId());
+}
+
+document::BucketId
+CreateVisitorCommand::super_bucket_id() const
+{
+ if (_buckets.empty()) {
+ // TODO STRIPE: Is this actually an error situation? Should be fixed elsewhere.
+ return document::BucketId();
+ }
+ return _buckets[0];
+}
+
+void
+CreateVisitorCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "CreateVisitorCommand(" << _libName << ", " << _docSelection;
+ if (verbose) {
+ out << ") {";
+ out << "\n" << indent << " Library name: '" << _libName << "'";
+ out << "\n" << indent << " Instance Id: '" << _instanceId << "'";
+ out << "\n" << indent << " Control Destination: '" << _controlDestination << "'";
+ out << "\n" << indent << " Data Destination: '" << _dataDestination << "'";
+ out << "\n" << indent << " Doc Selection: '" << _docSelection << "'";
+ out << "\n" << indent << " Max pending: '" << _maxPendingReplyCount << "'";
+ out << "\n" << indent << " Timeout: " << vespalib::count_ms(getTimeout()) << " ms";
+ out << "\n" << indent << " Queue timeout: " << vespalib::count_ms(_queueTimeout) << " ms";
+ out << "\n" << indent << " VisitorDispatcher version: '" << _version << "'";
+ if (visitRemoves()) {
+ out << "\n" << indent << " Visiting remove entries too";
+ }
+
+ out << "\n" << indent << " Returning fields: " << _fieldSet;
+
+ if (visitInconsistentBuckets()) {
+ out << "\n" << indent << " Visiting inconsistent buckets";
+ }
+ out << "\n" << indent << " From " << _fromTime << " to " << _toTime;
+ for (std::vector<document::BucketId>::const_iterator it
+ = _buckets.begin(); it != _buckets.end(); ++it)
+ {
+ out << "\n" << indent << " " << (*it);
+ }
+ out << "\n" << indent << " ";
+ _params.print(out, verbose, indent + " ");
+ out << "\n" << indent << " Max buckets: '" << _maxBucketsPerVisitor << "'";
+ out << "\n" << indent << "} : ";
+ StorageCommand::print(out, verbose, indent);
+ } else if (_buckets.size() == 2) {
+ out << ", top " << _buckets[0] << ", progress " << _buckets[1] << ")";
+ } else {
+ out << ", " << _buckets.size() << " buckets)";
+ }
+
+}
+
+CreateVisitorReply::CreateVisitorReply(const CreateVisitorCommand& cmd)
+ : StorageReply(cmd),
+ _super_bucket_id(cmd.super_bucket_id()),
+ _lastBucket(document::BucketId(INT_MAX))
+{
+}
+
+void
+CreateVisitorReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "CreateVisitorReply(last=" << _lastBucket << ")";
+ if (verbose) {
+ out << " : ";
+ StorageReply::print(out, verbose, indent);
+ }
+}
+
+DestroyVisitorCommand::DestroyVisitorCommand(vespalib::stringref instanceId)
+ : StorageCommand(MessageType::VISITOR_DESTROY),
+ _instanceId(instanceId)
+{
+}
+
+void
+DestroyVisitorCommand::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ out << "DestroyVisitorCommand(" << _instanceId << ")";
+ if (verbose) {
+ out << " : ";
+ StorageCommand::print(out, verbose, indent);
+ }
+}
+
+DestroyVisitorReply::DestroyVisitorReply(const DestroyVisitorCommand& cmd)
+ : StorageReply(cmd)
+{
+}
+
+void
+DestroyVisitorReply::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ out << "DestroyVisitorReply()";
+ if (verbose) {
+ out << " : ";
+ StorageReply::print(out, verbose, indent);
+ }
+}
+
+VisitorInfoCommand::VisitorInfoCommand()
+ : StorageCommand(MessageType::VISITOR_INFO),
+ _completed(false),
+ _bucketsCompleted(),
+ _error(ReturnCode::OK)
+{
+}
+
+VisitorInfoCommand::~VisitorInfoCommand() = default;
+
+void
+VisitorInfoCommand::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ out << "VisitorInfoCommand(";
+ if (_completed) { out << "completed"; }
+ if (_error.failed()) {
+ out << _error;
+ }
+ if (verbose) {
+ out << ") : ";
+ StorageCommand::print(out, verbose, indent);
+ } else {
+ if (!_bucketsCompleted.empty()) {
+ out << _bucketsCompleted.size() << " buckets completed";
+ }
+ out << ")";
+ }
+}
+
+VisitorInfoReply::VisitorInfoReply(const VisitorInfoCommand& cmd)
+ : StorageReply(cmd),
+ _completed(cmd.visitorCompleted())
+{
+}
+
+void
+VisitorInfoReply::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ out << "VisitorInfoReply(";
+ if (_completed) { out << "completed"; }
+ if (verbose) {
+ out << ") : ";
+ StorageReply::print(out, verbose, indent);
+ } else {
+ out << ")";
+ }
+}
+
+std::ostream&
+operator<<(std::ostream& out, const VisitorInfoCommand::BucketTimestampPair& pair) {
+ return out << pair.bucketId << " - " << pair.timestamp;
+}
+
+}
diff --git a/storage/src/vespa/storageapi/message/visitor.h b/storage/src/vespa/storageapi/message/visitor.h
new file mode 100644
index 00000000000..e6835405768
--- /dev/null
+++ b/storage/src/vespa/storageapi/message/visitor.h
@@ -0,0 +1,244 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @file storageapi/message/visitor.h
+ *
+ * Messages related to visitors, used by the visitor manager.
+ */
+
+#pragma once
+
+#include <vespa/storageapi/defs.h>
+#include <vespa/document/bucket/bucketid.h>
+#include <vespa/vdslib/container/parameters.h>
+#include <vespa/vdslib/container/visitorstatistics.h>
+#include <vespa/storageapi/messageapi/storagecommand.h>
+#include <vespa/storageapi/messageapi/storagereply.h>
+
+namespace storage::api {
+
+/**
+ * @class CreateVisitorCommand
+ * @ingroup message
+ *
+ * @brief Command for creating a visitor.
+ */
+class CreateVisitorCommand : public StorageCommand {
+private:
+ document::BucketSpace _bucketSpace;
+ vespalib::string _libName; // Name of visitor library to use, ie. DumpVisitor.so
+ vdslib::Parameters _params;
+
+ vespalib::string _controlDestination;
+ vespalib::string _dataDestination;
+
+ vespalib::string _docSelection;
+ std::vector<document::BucketId> _buckets;
+ Timestamp _fromTime;
+ Timestamp _toTime;
+
+ uint32_t _visitorCmdId;
+ vespalib::string _instanceId;
+ VisitorId _visitorId; // Set on storage node
+
+ bool _visitRemoves;
+ vespalib::string _fieldSet;
+ bool _visitInconsistentBuckets;
+
+ duration _queueTimeout;
+ uint32_t _maxPendingReplyCount;
+ uint32_t _version;
+
+ uint32_t _maxBucketsPerVisitor;
+
+public:
+ CreateVisitorCommand(document::BucketSpace bucketSpace,
+ vespalib::stringref libraryName,
+ vespalib::stringref instanceId,
+ vespalib::stringref docSelection);
+
+ /** Create another command with similar visitor settings. */
+ CreateVisitorCommand(const CreateVisitorCommand& template_);
+ ~CreateVisitorCommand();
+
+ void setVisitorCmdId(uint32_t id) { _visitorCmdId = id; }
+ void setControlDestination(vespalib::stringref d) { _controlDestination = d; }
+ void setDataDestination(vespalib::stringref d) { _dataDestination = d; }
+ void setParameters(const vdslib::Parameters& params) { _params = params; }
+ void setMaximumPendingReplyCount(uint32_t count) { _maxPendingReplyCount = count; }
+ void setFieldSet(vespalib::stringref fieldSet) { _fieldSet = fieldSet; }
+ void setVisitRemoves(bool value = true) { _visitRemoves = value; }
+ void setVisitInconsistentBuckets(bool visitInconsistent = true) { _visitInconsistentBuckets = visitInconsistent; }
+ void addBucketToBeVisited(const document::BucketId& id) { _buckets.push_back(id); }
+ void setVisitorId(const VisitorId id) { _visitorId = id; }
+ void setInstanceId(vespalib::stringref id) { _instanceId = id; }
+ void setQueueTimeout(duration milliSecs) { _queueTimeout = milliSecs; }
+ void setFromTime(Timestamp ts) { _fromTime = ts; }
+ void setToTime(Timestamp ts) { _toTime = ts; }
+
+ VisitorId getVisitorId() const { return _visitorId; }
+ uint32_t getVisitorCmdId() const { return _visitorCmdId; }
+ document::BucketSpace getBucketSpace() const { return _bucketSpace; }
+ document::Bucket getBucket() const override;
+ document::BucketId super_bucket_id() const;
+ const vespalib::string & getLibraryName() const { return _libName; }
+ const vespalib::string & getInstanceId() const { return _instanceId; }
+ const vespalib::string & getControlDestination() const { return _controlDestination; }
+ const vespalib::string & getDataDestination() const { return _dataDestination; }
+ const vespalib::string & getDocumentSelection() const { return _docSelection; }
+ const vdslib::Parameters& getParameters() const { return _params; }
+ vdslib::Parameters& getParameters() { return _params; }
+ uint32_t getMaximumPendingReplyCount() const { return _maxPendingReplyCount; }
+ const std::vector<document::BucketId>& getBuckets() const { return _buckets; }
+ Timestamp getFromTime() const { return _fromTime; }
+ Timestamp getToTime() const { return _toTime; }
+ std::vector<document::BucketId>& getBuckets() { return _buckets; }
+ bool visitRemoves() const { return _visitRemoves; }
+ const vespalib::string& getFieldSet() const { return _fieldSet; }
+ bool visitInconsistentBuckets() const { return _visitInconsistentBuckets; }
+ duration getQueueTimeout() const { return _queueTimeout; }
+
+ void setVisitorDispatcherVersion(uint32_t version) { _version = version; }
+ uint32_t getVisitorDispatcherVersion() const { return _version; }
+
+ void setMaxBucketsPerVisitor(uint32_t max) { _maxBucketsPerVisitor = max; }
+ uint32_t getMaxBucketsPerVisitor() const { return _maxBucketsPerVisitor; }
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+ DECLARE_STORAGECOMMAND(CreateVisitorCommand, onCreateVisitor)
+};
+
+/**
+ * @class CreateVisitorReply
+ * @ingroup message
+ *
+ * @brief Response to a create visitor command.
+ */
+class CreateVisitorReply : public StorageReply {
+private:
+ document::BucketId _super_bucket_id;
+ document::BucketId _lastBucket;
+ vdslib::VisitorStatistics _visitorStatistics;
+
+public:
+ explicit CreateVisitorReply(const CreateVisitorCommand& cmd);
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ void setLastBucket(const document::BucketId& lastBucket) { _lastBucket = lastBucket; }
+
+ const document::BucketId& super_bucket_id() const { return _super_bucket_id; }
+ const document::BucketId& getLastBucket() const { return _lastBucket; }
+
+ void setVisitorStatistics(const vdslib::VisitorStatistics& stats) { _visitorStatistics = stats; }
+
+ const vdslib::VisitorStatistics& getVisitorStatistics() const { return _visitorStatistics; }
+
+ DECLARE_STORAGEREPLY(CreateVisitorReply, onCreateVisitorReply)
+};
+
+/**
+ * @class DestroyVisitorCommand
+ * @ingroup message
+ *
+ * @brief Command for removing a visitor.
+ */
+class DestroyVisitorCommand : public StorageCommand {
+private:
+ vespalib::string _instanceId;
+
+public:
+ explicit DestroyVisitorCommand(vespalib::stringref instanceId);
+
+ const vespalib::string & getInstanceId() const { return _instanceId; }
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGECOMMAND(DestroyVisitorCommand, onDestroyVisitor)
+};
+
+/**
+ * @class DestroyVisitorReply
+ * @ingroup message
+ *
+ * @brief Response to a destroy visitor command.
+ */
+class DestroyVisitorReply : public StorageReply {
+public:
+ explicit DestroyVisitorReply(const DestroyVisitorCommand& cmd);
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGEREPLY(DestroyVisitorReply, onDestroyVisitorReply)
+};
+
+/**
+ * @class VisitorInfoCommand
+ * @ingroup message
+ *
+ * @brief Sends status information of an ongoing visitor.
+ *
+ * Includes three different kinds of data.
+ * - Notification when visiting is complete.
+ * - Notification when individual buckets have been completely visited.
+ * (Including the timestamp of the newest document visited)
+ * - Notification that some error condition arose during visiting.
+ */
+class VisitorInfoCommand : public StorageCommand {
+public:
+ struct BucketTimestampPair {
+ document::BucketId bucketId;
+ Timestamp timestamp;
+
+ BucketTimestampPair() noexcept : bucketId(), timestamp(0) {}
+ BucketTimestampPair(const document::BucketId& bucket, const Timestamp& ts) noexcept
+ : bucketId(bucket), timestamp(ts)
+ {}
+
+ bool operator==(const BucketTimestampPair& other) const noexcept {
+ return (bucketId == other.bucketId && timestamp && other.timestamp);
+ }
+ };
+
+private:
+ bool _completed;
+ std::vector<BucketTimestampPair> _bucketsCompleted;
+ ReturnCode _error;
+
+public:
+ VisitorInfoCommand();
+ ~VisitorInfoCommand() override;
+
+ void setErrorCode(ReturnCode && code) { _error = std::move(code); }
+ void setCompleted() { _completed = true; }
+ void setBucketCompleted(const document::BucketId& id, Timestamp lastVisited) {
+ _bucketsCompleted.push_back(BucketTimestampPair(id, lastVisited));
+ }
+ void setBucketsCompleted(const std::vector<BucketTimestampPair>& bc) {
+ _bucketsCompleted = bc;
+ }
+
+ const ReturnCode& getErrorCode() const { return _error; }
+ const std::vector<BucketTimestampPair>& getCompletedBucketsList() const {
+ return _bucketsCompleted;
+ }
+ bool visitorCompleted() const { return _completed; }
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGECOMMAND(VisitorInfoCommand, onVisitorInfo)
+};
+
+std::ostream& operator<<(std::ostream& out, const VisitorInfoCommand::BucketTimestampPair& pair);
+
+class VisitorInfoReply : public StorageReply {
+ bool _completed;
+
+public:
+ VisitorInfoReply(const VisitorInfoCommand& cmd);
+ bool visitorCompleted() const { return _completed; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ DECLARE_STORAGEREPLY(VisitorInfoReply, onVisitorInfoReply)
+};
+
+}
diff --git a/storage/src/vespa/storageapi/messageapi/.gitignore b/storage/src/vespa/storageapi/messageapi/.gitignore
new file mode 100644
index 00000000000..6e555686642
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/.gitignore
@@ -0,0 +1,6 @@
+*.lo
+.depend
+.depend.NEW
+.deps
+.libs
+Makefile
diff --git a/storage/src/vespa/storageapi/messageapi/CMakeLists.txt b/storage/src/vespa/storageapi/messageapi/CMakeLists.txt
new file mode 100644
index 00000000000..6454bb1de7d
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/CMakeLists.txt
@@ -0,0 +1,14 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_library(storageapi_messageapi OBJECT
+ SOURCES
+ bucketcommand.cpp
+ bucketreply.cpp
+ bucketinfocommand.cpp
+ bucketinforeply.cpp
+ maintenancecommand.cpp
+ returncode.cpp
+ storagemessage.cpp
+ storagecommand.cpp
+ storagereply.cpp
+ DEPENDS
+)
diff --git a/storage/src/vespa/storageapi/messageapi/bucketcommand.cpp b/storage/src/vespa/storageapi/messageapi/bucketcommand.cpp
new file mode 100644
index 00000000000..c75267f560d
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/bucketcommand.cpp
@@ -0,0 +1,46 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "bucketcommand.h"
+#include <ostream>
+
+using document::Bucket;
+using document::BucketId;
+using document::BucketSpace;
+
+namespace storage {
+namespace api {
+
+BucketCommand::BucketCommand(const MessageType& type, const Bucket &bucket)
+ : StorageCommand(type),
+ _bucket(bucket),
+ _originalBucket()
+{
+}
+
+void
+BucketCommand::remapBucketId(const BucketId& bucket)
+{
+ if (_originalBucket.getRawId() == 0) {
+ _originalBucket = _bucket.getBucketId();
+ }
+ Bucket newBucket(_bucket.getBucketSpace(), bucket);
+ _bucket = newBucket;
+}
+
+void
+BucketCommand::print(std::ostream& out,
+ bool verbose, const std::string& indent) const
+{
+ out << "BucketCommand(" << _bucket.getBucketId();
+ if (hasBeenRemapped()) {
+ out << " <- " << _originalBucket;
+ }
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ StorageCommand::print(out, verbose, indent);
+ }
+}
+
+} // api
+} // storage
diff --git a/storage/src/vespa/storageapi/messageapi/bucketcommand.h b/storage/src/vespa/storageapi/messageapi/bucketcommand.h
new file mode 100644
index 00000000000..605653681b5
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/bucketcommand.h
@@ -0,0 +1,33 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @class storage::api::BucketCommand
+ * @ingroup messageapi
+ *
+ * @brief Superclass for storage commands that operate towards a single bucket.
+ */
+
+#pragma once
+
+#include "storagecommand.h"
+#include <vespa/document/bucket/bucket.h>
+
+namespace storage::api {
+
+class BucketCommand : public StorageCommand {
+ document::Bucket _bucket;
+ document::BucketId _originalBucket;
+
+protected:
+ BucketCommand(const MessageType& type, const document::Bucket &bucket);
+
+public:
+ DECLARE_POINTER_TYPEDEFS(BucketCommand);
+
+ void remapBucketId(const document::BucketId& bucket);
+ document::Bucket getBucket() const override { return _bucket; }
+ bool hasBeenRemapped() const { return (_originalBucket.getRawId() != 0); }
+ const document::BucketId& getOriginalBucketId() const { return _originalBucket; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+};
+
+}
diff --git a/storage/src/vespa/storageapi/messageapi/bucketinfocommand.cpp b/storage/src/vespa/storageapi/messageapi/bucketinfocommand.cpp
new file mode 100644
index 00000000000..8d5ad85b8aa
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/bucketinfocommand.cpp
@@ -0,0 +1,21 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "bucketinfocommand.h"
+#include <ostream>
+
+namespace storage {
+namespace api {
+
+void
+BucketInfoCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "BucketInfoCommand()";
+ if (verbose) {
+ out << " : ";
+ BucketCommand::print(out, verbose, indent);
+ }
+}
+
+} // api
+} // storage
diff --git a/storage/src/vespa/storageapi/messageapi/bucketinfocommand.h b/storage/src/vespa/storageapi/messageapi/bucketinfocommand.h
new file mode 100644
index 00000000000..0f6627328a9
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/bucketinfocommand.h
@@ -0,0 +1,30 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @class storage::api::BucketInfoCommand
+ * @ingroup messageapi
+ *
+ * @brief Superclass for storage commands that returns bucket info.
+ *
+ * This class doesn't add any functionality now, other than being able to check
+ * if a message is an instance of this class. But we want commands and replies
+ * to be in the same inheritance structure, and the reply adds functionality.
+ */
+
+#pragma once
+
+#include "bucketcommand.h"
+
+namespace storage::api {
+
+class BucketInfoCommand : public BucketCommand {
+protected:
+ BucketInfoCommand(const MessageType& type, const document::Bucket &bucket)
+ : BucketCommand(type, bucket) {}
+
+public:
+ DECLARE_POINTER_TYPEDEFS(BucketInfoCommand);
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+};
+
+}
+
diff --git a/storage/src/vespa/storageapi/messageapi/bucketinforeply.cpp b/storage/src/vespa/storageapi/messageapi/bucketinforeply.cpp
new file mode 100644
index 00000000000..6eb5f96e888
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/bucketinforeply.cpp
@@ -0,0 +1,19 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "bucketinforeply.h"
+#include <ostream>
+
+namespace storage::api {
+
+void
+BucketInfoReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "BucketInfoReply(" << _result << ")";
+ if (verbose) {
+ out << " : ";
+ BucketReply::print(out, verbose, indent);
+ }
+}
+
+}
diff --git a/storage/src/vespa/storageapi/messageapi/bucketinforeply.h b/storage/src/vespa/storageapi/messageapi/bucketinforeply.h
new file mode 100644
index 00000000000..961e63a7ac8
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/bucketinforeply.h
@@ -0,0 +1,38 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @class storage::api::BucketInfoReply
+ * @ingroup messageapi
+ *
+ * @brief Superclass for storage replies which returns bucket info in reply.
+ *
+ * A bucket info reply contains information about the state of a bucket. This
+ * can be altered from before the operation if this was a write operation or if
+ * the bucket was repaired in the process.
+ */
+
+#pragma once
+
+#include "bucketreply.h"
+#include "bucketinfocommand.h"
+#include <vespa/storageapi/buckets/bucketinfo.h>
+
+namespace storage::api {
+
+class BucketInfoReply : public BucketReply {
+ BucketInfo _result;
+
+protected:
+ BucketInfoReply(const BucketInfoCommand& cmd)
+ : BucketReply(cmd),
+ _result()
+ {}
+
+public:
+ DECLARE_POINTER_TYPEDEFS(BucketInfoReply);
+
+ const BucketInfo& getBucketInfo() const { return _result; };
+ void setBucketInfo(const BucketInfo& info) { _result = info; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+};
+
+}
diff --git a/storage/src/vespa/storageapi/messageapi/bucketreply.cpp b/storage/src/vespa/storageapi/messageapi/bucketreply.cpp
new file mode 100644
index 00000000000..08b5effbd11
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/bucketreply.cpp
@@ -0,0 +1,36 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "bucketreply.h"
+#include "bucketcommand.h"
+#include <ostream>
+
+using document::Bucket;
+using document::BucketId;
+
+namespace storage::api {
+
+void
+BucketReply::remapBucketId(const BucketId& bucket) {
+ if (_originalBucket.getRawId() == 0) {
+ _originalBucket = _bucket.getBucketId();
+ }
+ Bucket newBucket(_bucket.getBucketSpace(), bucket);
+ _bucket = newBucket;
+}
+
+void
+BucketReply::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ out << "BucketReply(" << _bucket.getBucketId();
+ if (hasBeenRemapped()) {
+ out << " <- " << _originalBucket;
+ }
+ out << ")";
+ if (verbose) {
+ out << " : ";
+ StorageReply::print(out, verbose, indent);
+ }
+}
+
+}
diff --git a/storage/src/vespa/storageapi/messageapi/bucketreply.h b/storage/src/vespa/storageapi/messageapi/bucketreply.h
new file mode 100644
index 00000000000..e7ded37c14d
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/bucketreply.h
@@ -0,0 +1,42 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @class storage::api::BucketReply
+ * @ingroup messageapi
+ *
+ * @brief Superclass for storage replies which operates on single bucket.
+ */
+
+#pragma once
+
+#include "storagereply.h"
+#include "bucketcommand.h"
+
+namespace storage::api {
+
+class BucketCommand;
+
+class BucketReply : public StorageReply {
+ document::Bucket _bucket;
+ document::BucketId _originalBucket;
+
+protected:
+ BucketReply(const BucketCommand& cmd)
+ : StorageReply(cmd),
+ _bucket(cmd.getBucket()),
+ _originalBucket(cmd.getOriginalBucketId())
+ { }
+
+public:
+ DECLARE_POINTER_TYPEDEFS(BucketReply);
+
+ document::Bucket getBucket() const override { return _bucket; }
+
+ bool hasBeenRemapped() const { return (_originalBucket.getRawId() != 0); }
+ const document::BucketId& getOriginalBucketId() const { return _originalBucket; }
+
+ /** The deserialization code need access to set the remapping. */
+ void remapBucketId(const document::BucketId& bucket);
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+};
+
+}
diff --git a/storage/src/vespa/storageapi/messageapi/maintenancecommand.cpp b/storage/src/vespa/storageapi/messageapi/maintenancecommand.cpp
new file mode 100644
index 00000000000..91551be0987
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/maintenancecommand.cpp
@@ -0,0 +1,9 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "maintenancecommand.h"
+
+namespace storage::api {
+
+MaintenanceCommand::~MaintenanceCommand() {}
+
+}
diff --git a/storage/src/vespa/storageapi/messageapi/maintenancecommand.h b/storage/src/vespa/storageapi/messageapi/maintenancecommand.h
new file mode 100644
index 00000000000..6bb36d0d32f
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/maintenancecommand.h
@@ -0,0 +1,28 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "bucketinfocommand.h"
+
+namespace storage {
+namespace api {
+
+class MaintenanceCommand : public BucketInfoCommand
+{
+public:
+ MaintenanceCommand(const MessageType& type, const document::Bucket &bucket)
+ : BucketInfoCommand(type, bucket)
+ {}
+ MaintenanceCommand(const MaintenanceCommand &) = default;
+ MaintenanceCommand(MaintenanceCommand &&) = default;
+ MaintenanceCommand & operator = (const MaintenanceCommand &) = delete;
+ MaintenanceCommand & operator = (MaintenanceCommand &&) = delete;
+ ~MaintenanceCommand();
+
+ const vespalib::string& getReason() const { return _reason; };
+ void setReason(vespalib::stringref reason) { _reason = reason; };
+protected:
+ vespalib::string _reason;
+};
+
+}
+}
diff --git a/storage/src/vespa/storageapi/messageapi/messagehandler.h b/storage/src/vespa/storageapi/messageapi/messagehandler.h
new file mode 100644
index 00000000000..9ba8542e9db
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/messagehandler.h
@@ -0,0 +1,196 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @class storage::api::MessageHandler
+ * @ingroup messageapi
+ *
+ * @brief Class to prevent manual casting and switches of message types.
+ *
+ * MessageHandler defines an interface for processing StorageMessage objects
+ * of various subclasses.
+ *
+ * @version $Id$
+ */
+
+#pragma once
+
+#include <memory>
+
+namespace storage::api {
+
+// Commands
+
+class GetCommand; // Retrieve document
+class PutCommand; // Add document
+class UpdateCommand; // Update document
+class RemoveCommand; // Remove document
+class RevertCommand; // Revert put/remove operation
+
+class CreateVisitorCommand; // Create a new visitor
+class DestroyVisitorCommand; // Destroy a running visitor
+class VisitorInfoCommand; // Sends visitor info to visitor controller
+class MapVisitorCommand;
+class SearchResultCommand;
+class DocumentSummaryCommand;
+class QueryResultCommand;
+
+class InternalCommand;
+
+class CreateBucketCommand;
+class DeleteBucketCommand;
+class MergeBucketCommand;
+class GetBucketDiffCommand;
+class ApplyBucketDiffCommand;
+class SplitBucketCommand;
+class JoinBucketsCommand;
+class SetBucketStateCommand;
+
+class RequestBucketInfoCommand;
+class NotifyBucketChangeCommand;
+class SetNodeStateCommand;
+class GetNodeStateCommand;
+class SetSystemStateCommand;
+class ActivateClusterStateVersionCommand;
+class ActivateClusterStateVersionReply;
+class GetSystemStateCommand;
+class BucketsAddedCommand;
+class BucketsRemovedCommand;
+
+// Replies
+
+class GetReply;
+class PutReply;
+class UpdateReply;
+class RemoveReply;
+class RevertReply;
+
+class CreateVisitorReply;
+class DestroyVisitorReply;
+class VisitorInfoReply;
+class MapVisitorReply;
+class SearchResultReply;
+class DocumentSummaryReply;
+class QueryResultReply;
+
+class InternalReply;
+
+class CreateBucketReply;
+class DeleteBucketReply;
+class MergeBucketReply;
+class GetBucketDiffReply;
+class ApplyBucketDiffReply;
+class SplitBucketReply;
+class JoinBucketsReply;
+class SetBucketStateReply;
+
+class RequestBucketInfoReply;
+class NotifyBucketChangeReply;
+class SetNodeStateReply;
+class GetNodeStateReply;
+class SetSystemStateReply;
+class GetSystemStateReply;
+class BucketsAddedReply;
+class BucketsRemovedReply;
+
+class StatBucketCommand;
+class StatBucketReply;
+class GetBucketListCommand;
+class GetBucketListReply;
+
+class EmptyBucketsCommand;
+class EmptyBucketsReply;
+
+class RemoveLocationCommand;
+class RemoveLocationReply;
+
+#define _INTERNAL_DEF_ON_MC(m, c) bool m(const std::shared_ptr<storage::api::c> & ) override
+#define _INTERNAL_DEF_IMPL_ON_MC(m, c) bool m(const std::shared_ptr<storage::api::c> & ) override { return false; }
+#define _INTERNAL_IMPL_ON_MC(cl, m, c, p) bool cl::m(const std::shared_ptr<storage::api::c> & p)
+#define DEF_IMPL_MSG_COMMAND_H(m) _INTERNAL_DEF_IMPL_ON_MC(on##m, m##Command)
+#define DEF_IMPL_MSG_REPLY_H(m) _INTERNAL_DEF_IMPL_ON_MC(on##m##Reply, m##Reply)
+#define DEF_MSG_COMMAND_H(m) _INTERNAL_DEF_ON_MC(on##m, m##Command)
+#define DEF_MSG_REPLY_H(m) _INTERNAL_DEF_ON_MC(on##m##Reply, m##Reply)
+#define IMPL_MSG_COMMAND_ARG_H(cl, m, p) _INTERNAL_IMPL_ON_MC(cl, on##m, m##Command, p)
+#define IMPL_MSG_REPLY_ARG_H(cl, m, p) _INTERNAL_IMPL_ON_MC(cl, on##m##Reply, m##Reply, p)
+#define IMPL_MSG_COMMAND_H(cl, m) IMPL_MSG_COMMAND_ARG_H(cl, m, cmd)
+#define IMPL_MSG_REPLY_H(cl, m) IMPL_MSG_REPLY_ARG_H(cl, m, reply)
+#define ON_M(m) DEF_IMPL_MSG_COMMAND_H(m); DEF_IMPL_MSG_REPLY_H(m)
+
+class MessageHandler {
+public:
+ // Basic operations
+ virtual bool onGet(const std::shared_ptr<api::GetCommand>&) { return false; }
+ virtual bool onGetReply(const std::shared_ptr<api::GetReply>&) { return false; }
+ virtual bool onPut(const std::shared_ptr<api::PutCommand>&) { return false; }
+ virtual bool onPutReply(const std::shared_ptr<api::PutReply>&) { return false; }
+ virtual bool onUpdate(const std::shared_ptr<api::UpdateCommand>&) { return false; }
+ virtual bool onUpdateReply(const std::shared_ptr<api::UpdateReply>&) { return false; }
+ virtual bool onRemove(const std::shared_ptr<api::RemoveCommand>&) { return false; }
+ virtual bool onRemoveReply(const std::shared_ptr<api::RemoveReply>&) { return false; }
+ virtual bool onRevert(const std::shared_ptr<api::RevertCommand>&) { return false; }
+ virtual bool onRevertReply(const std::shared_ptr<api::RevertReply>&) { return false; }
+
+ virtual bool onCreateVisitor(const std::shared_ptr<api::CreateVisitorCommand>&) { return false; }
+ virtual bool onCreateVisitorReply(const std::shared_ptr<api::CreateVisitorReply>&) { return false; }
+ virtual bool onDestroyVisitor(const std::shared_ptr<api::DestroyVisitorCommand>&) { return false; }
+ virtual bool onDestroyVisitorReply(const std::shared_ptr<api::DestroyVisitorReply>&) { return false; }
+ virtual bool onVisitorInfo(const std::shared_ptr<api::VisitorInfoCommand>&) { return false; }
+ virtual bool onVisitorInfoReply(const std::shared_ptr<api::VisitorInfoReply>&) { return false; }
+ virtual bool onMapVisitor(const std::shared_ptr<api::MapVisitorCommand>&) { return false; }
+ virtual bool onMapVisitorReply(const std::shared_ptr<api::MapVisitorReply>&) { return false; }
+ virtual bool onSearchResult(const std::shared_ptr<api::SearchResultCommand>&) { return false; }
+ virtual bool onSearchResultReply(const std::shared_ptr<api::SearchResultReply>&) { return false; }
+ virtual bool onQueryResult(const std::shared_ptr<api::QueryResultCommand>&) { return false; }
+ virtual bool onQueryResultReply(const std::shared_ptr<api::QueryResultReply>&) { return false; }
+ virtual bool onDocumentSummary(const std::shared_ptr<api::DocumentSummaryCommand>&) { return false; }
+ virtual bool onDocumentSummaryReply(const std::shared_ptr<api::DocumentSummaryReply>&) { return false; }
+ virtual bool onEmptyBuckets(const std::shared_ptr<api::EmptyBucketsCommand>&) { return false; }
+ virtual bool onEmptyBucketsReply(const std::shared_ptr<api::EmptyBucketsReply>&) { return false; }
+ virtual bool onInternal(const std::shared_ptr<api::InternalCommand>&) { return false; }
+ virtual bool onInternalReply(const std::shared_ptr<api::InternalReply>&) { return false; }
+ virtual bool onCreateBucket(const std::shared_ptr<api::CreateBucketCommand>&) { return false; }
+ virtual bool onCreateBucketReply(const std::shared_ptr<api::CreateBucketReply>&) { return false; }
+ virtual bool onDeleteBucket(const std::shared_ptr<api::DeleteBucketCommand>&) { return false; }
+ virtual bool onDeleteBucketReply(const std::shared_ptr<api::DeleteBucketReply>&) { return false; }
+ virtual bool onMergeBucket(const std::shared_ptr<api::MergeBucketCommand>&) { return false; }
+ virtual bool onMergeBucketReply(const std::shared_ptr<api::MergeBucketReply>&) { return false; }
+ virtual bool onGetBucketDiff(const std::shared_ptr<api::GetBucketDiffCommand>&) { return false; }
+ virtual bool onGetBucketDiffReply(const std::shared_ptr<api::GetBucketDiffReply>&) { return false; }
+ virtual bool onApplyBucketDiff(const std::shared_ptr<api::ApplyBucketDiffCommand>&) { return false; }
+ virtual bool onApplyBucketDiffReply(const std::shared_ptr<api::ApplyBucketDiffReply>&) { return false; }
+ virtual bool onSplitBucket(const std::shared_ptr<api::SplitBucketCommand>&) { return false; }
+ virtual bool onSplitBucketReply(const std::shared_ptr<api::SplitBucketReply>&) { return false; }
+ virtual bool onJoinBuckets(const std::shared_ptr<api::JoinBucketsCommand>&) { return false; }
+ virtual bool onJoinBucketsReply(const std::shared_ptr<api::JoinBucketsReply>&) { return false; }
+ virtual bool onSetBucketState(const std::shared_ptr<api::SetBucketStateCommand>&) { return false; }
+ virtual bool onSetBucketStateReply(const std::shared_ptr<api::SetBucketStateReply>&) { return false; }
+ virtual bool onRequestBucketInfo(const std::shared_ptr<api::RequestBucketInfoCommand>&) { return false; }
+ virtual bool onRequestBucketInfoReply(const std::shared_ptr<api::RequestBucketInfoReply>&) { return false; }
+ virtual bool onNotifyBucketChange(const std::shared_ptr<api::NotifyBucketChangeCommand>&) { return false; }
+ virtual bool onNotifyBucketChangeReply(const std::shared_ptr<api::NotifyBucketChangeReply>&) { return false; }
+ virtual bool onSetNodeState(const std::shared_ptr<api::SetNodeStateCommand>&) { return false; }
+ virtual bool onSetNodeStateReply(const std::shared_ptr<api::SetNodeStateReply>&) { return false; }
+ virtual bool onGetNodeState(const std::shared_ptr<api::GetNodeStateCommand>&) { return false; }
+ virtual bool onGetNodeStateReply(const std::shared_ptr<api::GetNodeStateReply>&) { return false; }
+ virtual bool onSetSystemState(const std::shared_ptr<api::SetSystemStateCommand>&) { return false; }
+ virtual bool onSetSystemStateReply(const std::shared_ptr<api::SetSystemStateReply>&) { return false; }
+ virtual bool onActivateClusterStateVersion(const std::shared_ptr<api::ActivateClusterStateVersionCommand>&) { return false; }
+ virtual bool onActivateClusterStateVersionReply(const std::shared_ptr<api::ActivateClusterStateVersionReply>&) { return false; }
+ virtual bool onGetSystemState(const std::shared_ptr<api::GetSystemStateCommand>&) { return false; }
+ virtual bool onGetSystemStateReply(const std::shared_ptr<api::GetSystemStateReply>&) { return false; }
+ virtual bool onBucketsAdded(const std::shared_ptr<api::BucketsAddedCommand>&) { return false; }
+ virtual bool onBucketsAddedReply(const std::shared_ptr<api::BucketsAddedReply>&) { return false; }
+ virtual bool onBucketsRemoved(const std::shared_ptr<api::BucketsRemovedCommand>&) { return false; }
+ virtual bool onBucketsRemovedReply(const std::shared_ptr<api::BucketsRemovedReply>&) { return false; }
+ virtual bool onStatBucket(const std::shared_ptr<api::StatBucketCommand>&) { return false; }
+ virtual bool onStatBucketReply(const std::shared_ptr<api::StatBucketReply>&) { return false; }
+ virtual bool onGetBucketList(const std::shared_ptr<api::GetBucketListCommand>&) { return false; }
+ virtual bool onGetBucketListReply(const std::shared_ptr<api::GetBucketListReply>&) { return false; }
+ virtual bool onRemoveLocation(const std::shared_ptr<api::RemoveLocationCommand>&) { return false; }
+ virtual bool onRemoveLocationReply(const std::shared_ptr<api::RemoveLocationReply>&) { return false; }
+
+ virtual ~MessageHandler() = default;
+};
+
+#undef ON_M
+
+}
diff --git a/storage/src/vespa/storageapi/messageapi/returncode.cpp b/storage/src/vespa/storageapi/messageapi/returncode.cpp
new file mode 100644
index 00000000000..ef587968515
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/returncode.cpp
@@ -0,0 +1,169 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "returncode.h"
+#include <ostream>
+
+namespace storage::api {
+
+ReturnCode & ReturnCode::operator = (ReturnCode &&) noexcept = default;
+
+ReturnCode::ReturnCode(Result result, vespalib::stringref msg)
+ : _result(result),
+ _message()
+{
+ if ( ! msg.empty()) {
+ _message = std::make_unique<vespalib::string>(msg);
+ }
+}
+
+ReturnCode::ReturnCode(const ReturnCode & rhs)
+ : _result(rhs._result),
+ _message()
+{
+ if (rhs._message) {
+ _message = std::make_unique<vespalib::string>(*rhs._message);
+ }
+}
+
+ReturnCode &
+ReturnCode::operator = (const ReturnCode & rhs) {
+ return operator=(ReturnCode(rhs));
+}
+
+vespalib::string
+ReturnCode::getResultString(Result result) {
+ return documentapi::DocumentProtocol::getErrorName(result);
+}
+
+vespalib::string
+ReturnCode::toString() const {
+ vespalib::string ret = "ReturnCode(";
+ ret += getResultString(_result);
+ if ( _message && ! _message->empty()) {
+ ret += ", ";
+ ret += *_message;
+ }
+ ret += ")";
+ return ret;
+}
+
+std::ostream &
+operator << (std::ostream & os, const ReturnCode & returnCode) {
+ return os << returnCode.toString();
+}
+
+bool
+ReturnCode::isBusy() const
+{
+ // Casting to suppress -Wswitch since we're comparing against enum values
+ // not present in the ReturnCode::Result enum.
+ switch (static_cast<uint32_t>(_result)) {
+ case mbus::ErrorCode::SEND_QUEUE_FULL:
+ case mbus::ErrorCode::SESSION_BUSY:
+ case mbus::ErrorCode::TIMEOUT:
+ case Protocol::ERROR_BUSY:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+ReturnCode::isNodeDownOrNetwork() const
+{
+ switch (static_cast<uint32_t>(_result)) {
+ case mbus::ErrorCode::NO_ADDRESS_FOR_SERVICE:
+ case mbus::ErrorCode::CONNECTION_ERROR:
+ case mbus::ErrorCode::UNKNOWN_SESSION:
+ case mbus::ErrorCode::HANDSHAKE_FAILED:
+ case mbus::ErrorCode::NO_SERVICES_FOR_ROUTE:
+ case mbus::ErrorCode::NETWORK_ERROR:
+ case mbus::ErrorCode::UNKNOWN_PROTOCOL:
+ case Protocol::ERROR_NODE_NOT_READY:
+ case Protocol::ERROR_NOT_CONNECTED:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+ReturnCode::isCriticalForMaintenance() const
+{
+ if (_result >= static_cast<uint32_t>(mbus::ErrorCode::FATAL_ERROR)) {
+ return true;
+ }
+
+ switch (static_cast<uint32_t>(_result)) {
+ case Protocol::ERROR_INTERNAL_FAILURE:
+ case Protocol::ERROR_NO_SPACE:
+ case Protocol::ERROR_UNPARSEABLE:
+ case Protocol::ERROR_ILLEGAL_PARAMETERS:
+ case Protocol::ERROR_NOT_IMPLEMENTED:
+ case Protocol::ERROR_UNKNOWN_COMMAND:
+ case Protocol::ERROR_PROCESSING_FAILURE:
+ case Protocol::ERROR_IGNORED:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+ReturnCode::isCriticalForVisitor() const
+{
+ return isCriticalForMaintenance();
+}
+
+bool
+ReturnCode::isCriticalForVisitorDispatcher() const
+{
+ return isCriticalForMaintenance();
+}
+
+bool
+ReturnCode::isNonCriticalForIntegrityChecker() const
+{
+ switch (static_cast<uint32_t>(_result)) {
+ case Protocol::ERROR_ABORTED:
+ case Protocol::ERROR_BUCKET_DELETED:
+ case Protocol::ERROR_BUCKET_NOT_FOUND:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+ReturnCode::isShutdownRelated() const
+{
+ switch (static_cast<uint32_t>(_result)) {
+ case Protocol::ERROR_ABORTED:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+ReturnCode::isBucketDisappearance() const
+{
+ switch (static_cast<uint32_t>(_result)) {
+ case Protocol::ERROR_BUCKET_NOT_FOUND:
+ case Protocol::ERROR_BUCKET_DELETED:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+ReturnCode::operator==(const ReturnCode& code) const {
+ return (_result == code._result) && (getMessage() == code.getMessage());
+}
+
+bool
+ReturnCode::operator!=(const ReturnCode& code) const {
+ return (_result != code._result) || (getMessage() != code.getMessage());
+}
+}
diff --git a/storage/src/vespa/storageapi/messageapi/returncode.h b/storage/src/vespa/storageapi/messageapi/returncode.h
new file mode 100644
index 00000000000..f60cbe2b840
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/returncode.h
@@ -0,0 +1,117 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @class storage::api::ReturnCode
+ * @ingroup messageapi
+ *
+ * @brief Class for representing return values from the processing chain
+ *
+ * @version $Id$
+ */
+
+#pragma once
+
+#include <vespa/documentapi/messagebus/documentprotocol.h>
+
+
+namespace storage::api {
+
+class ReturnCode {
+public:
+ typedef documentapi::DocumentProtocol Protocol;
+
+ /** Return status codes */
+ enum Result {
+ OK = mbus::ErrorCode::NONE,
+ ENCODE_ERROR = mbus::ErrorCode::ENCODE_ERROR,
+
+ EXISTS = Protocol::ERROR_EXISTS,
+
+ NOT_READY = Protocol::ERROR_NODE_NOT_READY,
+ WRONG_DISTRIBUTION = Protocol::ERROR_WRONG_DISTRIBUTION,
+ REJECTED = Protocol::ERROR_REJECTED,
+ ABORTED = Protocol::ERROR_ABORTED,
+ BUCKET_NOT_FOUND = Protocol::ERROR_BUCKET_NOT_FOUND,
+ BUCKET_DELETED = Protocol::ERROR_BUCKET_DELETED,
+ TIMESTAMP_EXIST = Protocol::ERROR_TIMESTAMP_EXIST,
+ STALE_TIMESTAMP = Protocol::ERROR_STALE_TIMESTAMP,
+ TEST_AND_SET_CONDITION_FAILED = Protocol::ERROR_TEST_AND_SET_CONDITION_FAILED,
+
+ // Wrong use
+ UNKNOWN_COMMAND = Protocol::ERROR_UNKNOWN_COMMAND,
+ NOT_IMPLEMENTED = Protocol::ERROR_NOT_IMPLEMENTED,
+ ILLEGAL_PARAMETERS = Protocol::ERROR_ILLEGAL_PARAMETERS,
+ IGNORED = Protocol::ERROR_IGNORED,
+ UNPARSEABLE = Protocol::ERROR_UNPARSEABLE,
+
+ // Network failure
+ NOT_CONNECTED = Protocol::ERROR_NOT_CONNECTED,
+ TIMEOUT = mbus::ErrorCode::TIMEOUT,
+ BUSY = Protocol::ERROR_BUSY,
+
+ // Disk operations
+ NO_SPACE = Protocol::ERROR_NO_SPACE,
+ DISK_FAILURE = Protocol::ERROR_DISK_FAILURE,
+ IO_FAILURE = Protocol::ERROR_IO_FAILURE,
+
+ // Don't know what happened (catch-all)
+ INTERNAL_FAILURE = Protocol::ERROR_INTERNAL_FAILURE
+ };
+
+private:
+ Result _result;
+ std::unique_ptr<vespalib::string> _message;
+public:
+ ReturnCode()
+ : _result(OK),
+ _message()
+ { }
+ explicit ReturnCode(Result result)
+ : _result(result),
+ _message()
+ {}
+ ReturnCode(Result result, vespalib::stringref msg);
+ ReturnCode(const ReturnCode &);
+ ReturnCode & operator = (const ReturnCode &);
+ ReturnCode(ReturnCode &&) noexcept = default;
+ ReturnCode & operator = (ReturnCode &&) noexcept;
+
+ vespalib::stringref getMessage() const {
+ return _message
+ ? _message->operator vespalib::stringref()
+ : vespalib::stringref();
+ }
+
+ Result getResult() const { return _result; }
+
+ /**
+ * Translate from status code to human-readable string
+ * @param result Status code returned from getResult()
+ */
+ static vespalib::string getResultString(Result result);
+
+ bool failed() const { return (_result != OK); }
+ bool success() const { return (_result == OK); }
+
+ bool operator==(Result res) const { return _result == res; }
+ bool operator!=(Result res) const { return _result != res; }
+ bool operator==(const ReturnCode& code) const;
+ bool operator!=(const ReturnCode& code) const;
+
+ // To avoid lots of code matching various return codes in storage, we define
+ // some functions they can use to match those codes that corresponds to what
+ // they want to match.
+
+ bool isBusy() const;
+ bool isNodeDownOrNetwork() const;
+ bool isCriticalForMaintenance() const;
+ bool isCriticalForVisitor() const;
+ bool isCriticalForVisitorDispatcher() const;
+ bool isShutdownRelated() const;
+ bool isBucketDisappearance() const;
+ bool isNonCriticalForIntegrityChecker() const;
+ vespalib::string toString() const;
+};
+
+std::ostream & operator << (std::ostream & os, const ReturnCode & returnCode);
+
+}
diff --git a/storage/src/vespa/storageapi/messageapi/storagecommand.cpp b/storage/src/vespa/storageapi/messageapi/storagecommand.cpp
new file mode 100644
index 00000000000..1e797ba4792
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/storagecommand.cpp
@@ -0,0 +1,41 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "storagecommand.h"
+#include <vespa/vespalib/util/exceptions.h>
+#include <ostream>
+
+namespace storage::api {
+
+StorageCommand::StorageCommand(const StorageCommand& other)
+ : StorageMessage(other, generateMsgId()),
+ _timeout(other._timeout),
+ _sourceIndex(other._sourceIndex)
+{
+}
+
+StorageCommand::StorageCommand(const MessageType& type, Priority p)
+ : StorageMessage(type, generateMsgId()),
+ // Default timeout is unlimited. Set from mbus message. Some internal
+ // use want unlimited timeout, (such as readbucketinfo, repair bucket
+ // etc)
+ _timeout(duration::max()),
+ _sourceIndex(0xFFFF)
+{
+ setPriority(p);
+}
+
+StorageCommand::~StorageCommand() = default;
+
+void
+StorageCommand::print(std::ostream& out, bool verbose,
+ const std::string& indent) const
+{
+ (void) verbose; (void) indent;
+ out << "StorageCommand(" << _type.getName();
+ if (_priority != NORMAL) out << ", priority = " << static_cast<int>(_priority);
+ if (_sourceIndex != 0xFFFF) out << ", source = " << _sourceIndex;
+ out << ", timeout = " << vespalib::count_ms(_timeout) << " ms";
+ out << ")";
+}
+
+}
diff --git a/storage/src/vespa/storageapi/messageapi/storagecommand.h b/storage/src/vespa/storageapi/messageapi/storagecommand.h
new file mode 100644
index 00000000000..30d59e5fe4b
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/storagecommand.h
@@ -0,0 +1,56 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @class storage::api::StorageCommand
+ * @ingroup messageapi
+ *
+ * @brief Superclass for all storage commands.
+ *
+ * A storage command is a storage message you will get a storage reply for.
+ *
+ * @version $Id$
+ */
+
+#pragma once
+
+#include "storagemessage.h"
+
+namespace storage::api {
+
+class StorageReply;
+
+class StorageCommand : public StorageMessage {
+ duration _timeout; /** Timeout of command in milliseconds */
+ /** Sets what node this message origins from. 0xFFFF is unset. */
+ uint16_t _sourceIndex;
+
+protected:
+ explicit StorageCommand(const StorageCommand& other);
+ explicit StorageCommand(const MessageType& type, Priority p = NORMAL);
+
+public:
+ DECLARE_POINTER_TYPEDEFS(StorageCommand);
+
+ ~StorageCommand() override;
+
+ bool sourceIndexSet() const { return (_sourceIndex != 0xffff); }
+ void setSourceIndex(uint16_t sourceIndex) { _sourceIndex = sourceIndex; }
+ uint16_t getSourceIndex() const { return _sourceIndex; }
+
+ void setTimeout(duration timeout) { _timeout = timeout; }
+ duration getTimeout() const { return _timeout; }
+
+ /** Used to set a new id so the message can be resent. */
+ void setNewId() { StorageMessage::setNewMsgId(); }
+
+ /** Overload this to get more descriptive message output. */
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+
+ /**
+ * A way for someone to make a reply to a storage message without
+ * knowing the type of the message. Should just call reply constructor
+ * taking command as input.
+ */
+ virtual std::unique_ptr<StorageReply> makeReply() = 0;
+};
+
+}
diff --git a/storage/src/vespa/storageapi/messageapi/storagemessage.cpp b/storage/src/vespa/storageapi/messageapi/storagemessage.cpp
new file mode 100644
index 00000000000..db5c86af989
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/storagemessage.cpp
@@ -0,0 +1,306 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "storagemessage.h"
+#include <vespa/messagebus/routing/verbatimdirective.h>
+#include <vespa/vespalib/util/exceptions.h>
+#include <vespa/vespalib/stllike/asciistream.h>
+#include <vespa/vespalib/stllike/hash_fun.h>
+#include <sstream>
+#include <cassert>
+#include <atomic>
+
+namespace storage::api {
+
+namespace {
+
+std::atomic<uint64_t> _G_lastMsgId(1000);
+
+}
+
+static const vespalib::string STORAGEADDRESS_PREFIX = "storage/cluster.";
+
+const char*
+StorageMessage::getPriorityString(Priority p) {
+ switch (p) {
+ case LOW: return "LOW";
+ case NORMAL: return "NORMAL";
+ case HIGH: return "HIGH";
+ case VERYHIGH: return "VERYHIGH";
+ default: return "UNKNOWN";
+ }
+}
+
+std::map<MessageType::Id, MessageType*> MessageType::_codes;
+
+const MessageType MessageType::DOCBLOCK("DocBlock", DOCBLOCK_ID);
+const MessageType MessageType::DOCBLOCK_REPLY("DocBlock Reply", DOCBLOCK_REPLY_ID, &MessageType::DOCBLOCK);
+const MessageType MessageType::GET("Get", GET_ID);
+const MessageType MessageType::GET_REPLY("Get Reply", GET_REPLY_ID, &MessageType::GET);
+const MessageType MessageType::INTERNAL("Internal", INTERNAL_ID);
+const MessageType MessageType::INTERNAL_REPLY("Internal Reply", INTERNAL_REPLY_ID, &MessageType::INTERNAL);
+const MessageType MessageType::PUT("Put", PUT_ID);
+const MessageType MessageType::PUT_REPLY("Put Reply", PUT_REPLY_ID, &MessageType::PUT);
+const MessageType MessageType::UPDATE("Update", UPDATE_ID);
+const MessageType MessageType::UPDATE_REPLY("Update Reply", UPDATE_REPLY_ID, &MessageType::UPDATE);
+const MessageType MessageType::REMOVE("Remove", REMOVE_ID);
+const MessageType MessageType::REMOVE_REPLY("Remove Reply", REMOVE_REPLY_ID, &MessageType::REMOVE);
+const MessageType MessageType::REVERT("Revert", REVERT_ID);
+const MessageType MessageType::REVERT_REPLY("Revert Reply", REVERT_REPLY_ID, &MessageType::REVERT);
+const MessageType MessageType::VISITOR_CREATE("Visitor Create", VISITOR_CREATE_ID);
+const MessageType MessageType::VISITOR_CREATE_REPLY("Visitor Create Reply", VISITOR_CREATE_REPLY_ID, &MessageType::VISITOR_CREATE);
+const MessageType MessageType::VISITOR_DESTROY("Visitor Destroy", VISITOR_DESTROY_ID);
+const MessageType MessageType::VISITOR_DESTROY_REPLY("Visitor Destroy Reply", VISITOR_DESTROY_REPLY_ID, &MessageType::VISITOR_DESTROY);
+const MessageType MessageType::REQUESTBUCKETINFO("Request bucket info", REQUESTBUCKETINFO_ID);
+const MessageType MessageType::REQUESTBUCKETINFO_REPLY("Request bucket info reply", REQUESTBUCKETINFO_REPLY_ID, &MessageType::REQUESTBUCKETINFO);
+const MessageType MessageType::NOTIFYBUCKETCHANGE("Notify bucket change", NOTIFYBUCKETCHANGE_ID);
+const MessageType MessageType::NOTIFYBUCKETCHANGE_REPLY("Notify bucket change reply", NOTIFYBUCKETCHANGE_REPLY_ID, &MessageType::NOTIFYBUCKETCHANGE);
+const MessageType MessageType::CREATEBUCKET("Create bucket", CREATEBUCKET_ID);
+const MessageType MessageType::CREATEBUCKET_REPLY("Create bucket reply", CREATEBUCKET_REPLY_ID, &MessageType::CREATEBUCKET);
+const MessageType MessageType::MERGEBUCKET("Merge bucket", MERGEBUCKET_ID);
+const MessageType MessageType::MERGEBUCKET_REPLY("Merge bucket reply", MERGEBUCKET_REPLY_ID, &MessageType::MERGEBUCKET);
+const MessageType MessageType::DELETEBUCKET("Delete bucket", DELETEBUCKET_ID);
+const MessageType MessageType::DELETEBUCKET_REPLY("Delete bucket reply", DELETEBUCKET_REPLY_ID, &MessageType::DELETEBUCKET);
+const MessageType MessageType::SETNODESTATE("Set node state", SETNODESTATE_ID);
+const MessageType MessageType::SETNODESTATE_REPLY("Set node state reply", SETNODESTATE_REPLY_ID, &MessageType::SETNODESTATE);
+const MessageType MessageType::GETNODESTATE("Get node state", GETNODESTATE_ID);
+const MessageType MessageType::GETNODESTATE_REPLY("Get node state reply", GETNODESTATE_REPLY_ID, &MessageType::GETNODESTATE);
+const MessageType MessageType::SETSYSTEMSTATE("Set system state", SETSYSTEMSTATE_ID);
+const MessageType MessageType::SETSYSTEMSTATE_REPLY("Set system state reply", SETSYSTEMSTATE_REPLY_ID, &MessageType::SETSYSTEMSTATE);
+const MessageType MessageType::GETSYSTEMSTATE("Get system state", GETSYSTEMSTATE_ID);
+const MessageType MessageType::GETSYSTEMSTATE_REPLY("get system state reply", GETSYSTEMSTATE_REPLY_ID, &MessageType::GETSYSTEMSTATE);
+const MessageType MessageType::ACTIVATE_CLUSTER_STATE_VERSION("Activate cluster state version", ACTIVATE_CLUSTER_STATE_VERSION_ID);
+const MessageType MessageType::ACTIVATE_CLUSTER_STATE_VERSION_REPLY("Activate cluster state version reply", ACTIVATE_CLUSTER_STATE_VERSION_REPLY_ID, &MessageType::ACTIVATE_CLUSTER_STATE_VERSION);
+const MessageType MessageType::GETBUCKETDIFF("GetBucketDiff", GETBUCKETDIFF_ID);
+const MessageType MessageType::GETBUCKETDIFF_REPLY("GetBucketDiff reply", GETBUCKETDIFF_REPLY_ID, &MessageType::GETBUCKETDIFF);
+const MessageType MessageType::APPLYBUCKETDIFF("ApplyBucketDiff", APPLYBUCKETDIFF_ID);
+const MessageType MessageType::APPLYBUCKETDIFF_REPLY("ApplyBucketDiff reply", APPLYBUCKETDIFF_REPLY_ID, &MessageType::APPLYBUCKETDIFF);
+const MessageType MessageType::VISITOR_INFO("VisitorInfo", VISITOR_INFO_ID);
+const MessageType MessageType::VISITOR_INFO_REPLY("VisitorInfo reply", VISITOR_INFO_REPLY_ID, &MessageType::VISITOR_INFO);
+const MessageType MessageType::SEARCHRESULT("SearchResult", SEARCHRESULT_ID);
+const MessageType MessageType::SEARCHRESULT_REPLY("SearchResult reply", SEARCHRESULT_REPLY_ID, &MessageType::SEARCHRESULT);
+const MessageType MessageType::DOCUMENTSUMMARY("DocumentSummary", DOCUMENTSUMMARY_ID);
+const MessageType MessageType::DOCUMENTSUMMARY_REPLY("DocumentSummary reply", DOCUMENTSUMMARY_REPLY_ID, &MessageType::DOCUMENTSUMMARY);
+const MessageType MessageType::MAPVISITOR("Mapvisitor", MAPVISITOR_ID);
+const MessageType MessageType::MAPVISITOR_REPLY("Mapvisitor reply", MAPVISITOR_REPLY_ID, &MessageType::MAPVISITOR);
+const MessageType MessageType::SPLITBUCKET("SplitBucket", SPLITBUCKET_ID);
+const MessageType MessageType::SPLITBUCKET_REPLY("SplitBucket reply", SPLITBUCKET_REPLY_ID, &MessageType::SPLITBUCKET);
+const MessageType MessageType::JOINBUCKETS("Joinbuckets", JOINBUCKETS_ID);
+const MessageType MessageType::JOINBUCKETS_REPLY("Joinbuckets reply", JOINBUCKETS_REPLY_ID, &MessageType::JOINBUCKETS);
+const MessageType MessageType::STATBUCKET("Statbucket", STATBUCKET_ID);
+const MessageType MessageType::STATBUCKET_REPLY("Statbucket Reply", STATBUCKET_REPLY_ID, &MessageType::STATBUCKET);
+const MessageType MessageType::GETBUCKETLIST("Getbucketlist", GETBUCKETLIST_ID);
+const MessageType MessageType::GETBUCKETLIST_REPLY("Getbucketlist Reply", GETBUCKETLIST_REPLY_ID, &MessageType::GETBUCKETLIST);
+const MessageType MessageType::DOCUMENTLIST("documentlist", DOCUMENTLIST_ID);
+const MessageType MessageType::DOCUMENTLIST_REPLY("documentlist Reply", DOCUMENTLIST_REPLY_ID, &MessageType::DOCUMENTLIST);
+const MessageType MessageType::EMPTYBUCKETS("Emptybuckets", EMPTYBUCKETS_ID);
+const MessageType MessageType::EMPTYBUCKETS_REPLY("Emptybuckets Reply", EMPTYBUCKETS_REPLY_ID, &MessageType::EMPTYBUCKETS);
+const MessageType MessageType::REMOVELOCATION("Removelocation", REMOVELOCATION_ID);
+const MessageType MessageType::REMOVELOCATION_REPLY("Removelocation Reply", REMOVELOCATION_REPLY_ID, &MessageType::REMOVELOCATION);
+const MessageType MessageType::QUERYRESULT("QueryResult", QUERYRESULT_ID);
+const MessageType MessageType::QUERYRESULT_REPLY("QueryResult reply", QUERYRESULT_REPLY_ID, &MessageType::QUERYRESULT);
+const MessageType MessageType::SETBUCKETSTATE("SetBucketState", SETBUCKETSTATE_ID);
+const MessageType MessageType::SETBUCKETSTATE_REPLY("SetBucketStateReply", SETBUCKETSTATE_REPLY_ID, &MessageType::SETBUCKETSTATE);
+
+const MessageType&
+MessageType::MessageType::get(Id id)
+{
+ auto it = _codes.find(id);
+ if (it == _codes.end()) {
+ std::ostringstream ost;
+ ost << "No message type with id " << id << ".";
+ throw vespalib::IllegalArgumentException(ost.str(), VESPA_STRLOC);
+ }
+ return *it->second;
+}
+MessageType::MessageType(vespalib::stringref name, Id id,
+ const MessageType* replyOf)
+ : _name(name), _id(id), _reply(nullptr), _replyOf(replyOf)
+{
+ _codes[id] = this;
+ if (_replyOf) {
+ assert(_replyOf->_reply == nullptr);
+ // Ugly cast to let initialization work
+ auto& type = const_cast<MessageType&>(*_replyOf);
+ type._reply = this;
+ }
+}
+
+MessageType::~MessageType() = default;
+
+void
+MessageType::print(std::ostream& out, bool verbose, const std::string& indent) const
+{
+ (void) verbose; (void) indent;
+ out << "MessageType(" << _id << ", " << _name;
+ if (_replyOf) {
+ out << ", reply of " << _replyOf->getName();
+ }
+ out << ")";
+}
+
+std::ostream & operator << (std::ostream & os, const StorageMessageAddress & addr) {
+ return os << addr.toString();
+}
+
+namespace {
+
+vespalib::string
+createAddress(vespalib::stringref cluster, const lib::NodeType &type, uint16_t index) {
+ vespalib::asciistream os;
+ os << STORAGEADDRESS_PREFIX << cluster << '/' << type.toString() << '/' << index << "/default";
+ return os.str();
+}
+
+uint32_t
+calculate_node_hash(const lib::NodeType &type, uint16_t index) {
+ uint16_t buf[] = {type, index};
+ size_t hash = vespalib::hashValue(&buf, sizeof(buf));
+ return uint32_t(hash & 0xffffffffl) ^ uint32_t(hash >> 32);
+}
+
+vespalib::string Empty;
+
+}
+
+// TODO we ideally want this removed. Currently just in place to support usage as map key when emplacement not available
+StorageMessageAddress::StorageMessageAddress() noexcept
+ : _cluster(&Empty),
+ _precomputed_storage_hash(0),
+ _type(lib::NodeType::Type::UNKNOWN),
+ _protocol(Protocol::STORAGE),
+ _index(0)
+{}
+
+StorageMessageAddress::StorageMessageAddress(const vespalib::string * cluster, const lib::NodeType& type, uint16_t index) noexcept
+ : StorageMessageAddress(cluster, type, index, Protocol::STORAGE)
+{ }
+
+StorageMessageAddress::StorageMessageAddress(const vespalib::string * cluster, const lib::NodeType& type,
+ uint16_t index, Protocol protocol) noexcept
+ : _cluster(cluster),
+ _precomputed_storage_hash(calculate_node_hash(type, index)),
+ _type(type.getType()),
+ _protocol(protocol),
+ _index(index)
+{ }
+
+StorageMessageAddress::~StorageMessageAddress() = default;
+
+mbus::Route
+StorageMessageAddress::to_mbus_route() const
+{
+ mbus::Route result;
+ auto address_as_str = createAddress(getCluster(), lib::NodeType::get(_type), _index);
+ std::vector<mbus::IHopDirective::SP> directives;
+ directives.emplace_back(std::make_shared<mbus::VerbatimDirective>(std::move(address_as_str)));
+ result.addHop(mbus::Hop(std::move(directives), false));
+ return result;
+}
+
+bool
+StorageMessageAddress::operator==(const StorageMessageAddress& other) const noexcept
+{
+ if (_protocol != other._protocol) return false;
+ if (_type != other._type) return false;
+ if (_index != other._index) return false;
+ if (getCluster() != other.getCluster()) return false;
+ return true;
+}
+
+vespalib::string
+StorageMessageAddress::toString() const
+{
+ vespalib::asciistream os;
+ print(os);
+ return os.str();
+}
+
+void
+StorageMessageAddress::print(vespalib::asciistream & out) const
+{
+ out << "StorageMessageAddress(";
+ if (_protocol == Protocol::STORAGE) {
+ out << "Storage protocol";
+ } else {
+ out << "Document protocol";
+ }
+ if (_type == lib::NodeType::Type::UNKNOWN) {
+ out << ", " << to_mbus_route().toString() << ")";
+ } else {
+ out << ", cluster " << getCluster() << ", nodetype " << lib::NodeType::get(_type)
+ << ", index " << _index << ")";
+ }
+}
+
+TransportContext::~TransportContext() = default;
+
+StorageMessage::Id
+StorageMessage::generateMsgId() noexcept
+{
+ return _G_lastMsgId.fetch_add(1, std::memory_order_relaxed);
+}
+
+StorageMessage::StorageMessage(const MessageType& type, Id id) noexcept
+ : _type(type),
+ _msgId(id),
+ _address(),
+ _trace(),
+ _approxByteSize(50),
+ _priority(NORMAL)
+{
+}
+
+StorageMessage::StorageMessage(const StorageMessage& other, Id id) noexcept
+ : _type(other._type),
+ _msgId(id),
+ _address(),
+ _trace(other.getTrace().getLevel()),
+ _approxByteSize(other._approxByteSize),
+ _priority(other._priority)
+{
+}
+
+StorageMessage::~StorageMessage() = default;
+
+void
+StorageMessage::setNewMsgId() noexcept
+{
+ _msgId = generateMsgId();
+}
+
+vespalib::string
+StorageMessage::getSummary() const {
+ return toString();
+}
+
+const char*
+to_string(LockingRequirements req) noexcept {
+ switch (req) {
+ case LockingRequirements::Exclusive: return "Exclusive";
+ case LockingRequirements::Shared: return "Shared";
+ default: abort();
+ }
+}
+
+std::ostream&
+operator<<(std::ostream& os, LockingRequirements req) {
+ os << to_string(req);
+ return os;
+}
+
+const char*
+to_string(InternalReadConsistency consistency) noexcept {
+ switch (consistency) {
+ case InternalReadConsistency::Strong: return "Strong";
+ case InternalReadConsistency::Weak: return "Weak";
+ default: abort();
+ }
+}
+
+std::ostream&
+operator<<(std::ostream& os, InternalReadConsistency consistency) {
+ os << to_string(consistency);
+ return os;
+}
+
+}
diff --git a/storage/src/vespa/storageapi/messageapi/storagemessage.h b/storage/src/vespa/storageapi/messageapi/storagemessage.h
new file mode 100644
index 00000000000..71567192bd9
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/storagemessage.h
@@ -0,0 +1,455 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+/**
+ * @class storage::api::StorageMessage
+ * @ingroup messageapi
+ *
+ * @brief Superclass for all storage messages.
+ *
+ * @version $Id$
+ */
+
+#pragma once
+
+#include "messagehandler.h"
+#include <vespa/messagebus/routing/route.h>
+#include <vespa/messagebus/trace.h>
+#include <vespa/vdslib/state/nodetype.h>
+#include <vespa/document/bucket/bucket.h>
+#include <vespa/vespalib/util/printable.h>
+#include <map>
+#include <iosfwd>
+
+namespace vespalib { class asciistream; }
+// The following macros are provided as a way to write storage messages simply.
+// They implement the parts of the code that can easily be automaticly
+// generated.
+
+/**
+ * Adds a messagehandler callback and some utilities
+ */
+#define DECLARE_POINTER_TYPEDEFS(message) \
+ typedef std::unique_ptr<message> UP; \
+ typedef std::shared_ptr<message> SP; \
+ typedef std::shared_ptr<const message> CSP;
+
+#define DECLARE_STORAGEREPLY(reply, callback) \
+public: \
+ DECLARE_POINTER_TYPEDEFS(reply) \
+private: \
+ bool callHandler(storage::api::MessageHandler& h, \
+ const std::shared_ptr<storage::api::StorageMessage>& m) const override \
+ { \
+ return h.callback(std::static_pointer_cast<reply>(m)); \
+ }
+
+/** Commands also has a command to implement to create the reply. */
+#define DECLARE_STORAGECOMMAND(command, callback) \
+public: \
+ std::unique_ptr<storage::api::StorageReply> makeReply() override; \
+ DECLARE_STORAGEREPLY(command, callback)
+
+/** This macro implements common stuff for all storage messages. */
+#define IMPLEMENT_COMMON(message) \
+
+/** This macro is used to implement common storage reply functionality. */
+#define IMPLEMENT_REPLY(reply) \
+ IMPLEMENT_COMMON(reply) \
+
+/** This macro is used to implement common storage command functionality. */
+#define IMPLEMENT_COMMAND(command, reply) \
+ IMPLEMENT_COMMON(command) \
+ std::unique_ptr<storage::api::StorageReply> \
+ storage::api::command::makeReply() \
+ { \
+ return std::make_unique<reply>(*this); \
+ }
+
+namespace storage::api {
+
+using duration = vespalib::duration;
+
+/**
+ * @class MessageType
+ * @ingroup messageapi
+ *
+ * @brief This class defines the different message types we have.
+ *
+ * This is used to be able to deserialize messages of various classes.
+ */
+class MessageType : public vespalib::Printable {
+public:
+ enum Id {
+ GET_ID = 4,
+ GET_REPLY_ID = 5,
+ INTERNAL_ID = 6,
+ INTERNAL_REPLY_ID = 7,
+ PUT_ID = 10,
+ PUT_REPLY_ID = 11,
+ REMOVE_ID = 12,
+ REMOVE_REPLY_ID = 13,
+ REVERT_ID = 14,
+ REVERT_REPLY_ID = 15,
+ STAT_ID = 16,
+ STAT_REPLY_ID = 17,
+ VISITOR_CREATE_ID = 18,
+ VISITOR_CREATE_REPLY_ID = 19,
+ VISITOR_DESTROY_ID = 20,
+ VISITOR_DESTROY_REPLY_ID = 21,
+ CREATEBUCKET_ID = 26,
+ CREATEBUCKET_REPLY_ID = 27,
+ MERGEBUCKET_ID = 32,
+ MERGEBUCKET_REPLY_ID = 33,
+ DELETEBUCKET_ID = 34,
+ DELETEBUCKET_REPLY_ID = 35,
+ SETNODESTATE_ID = 36,
+ SETNODESTATE_REPLY_ID = 37,
+ GETNODESTATE_ID = 38,
+ GETNODESTATE_REPLY_ID = 39,
+ SETSYSTEMSTATE_ID = 40,
+ SETSYSTEMSTATE_REPLY_ID = 41,
+ GETSYSTEMSTATE_ID = 42,
+ GETSYSTEMSTATE_REPLY_ID = 43,
+ GETBUCKETDIFF_ID = 50,
+ GETBUCKETDIFF_REPLY_ID = 51,
+ APPLYBUCKETDIFF_ID = 52,
+ APPLYBUCKETDIFF_REPLY_ID = 53,
+ REQUESTBUCKETINFO_ID = 54,
+ REQUESTBUCKETINFO_REPLY_ID = 55,
+ NOTIFYBUCKETCHANGE_ID = 56,
+ NOTIFYBUCKETCHANGE_REPLY_ID = 57,
+ DOCBLOCK_ID = 58,
+ DOCBLOCK_REPLY_ID = 59,
+ VISITOR_INFO_ID = 60,
+ VISITOR_INFO_REPLY_ID = 61,
+ SEARCHRESULT_ID = 64,
+ SEARCHRESULT_REPLY_ID = 65,
+ SPLITBUCKET_ID = 66,
+ SPLITBUCKET_REPLY_ID = 67,
+ JOINBUCKETS_ID = 68,
+ JOINBUCKETS_REPLY_ID = 69,
+ DOCUMENTSUMMARY_ID = 72,
+ DOCUMENTSUMMARY_REPLY_ID = 73,
+ MAPVISITOR_ID = 74,
+ MAPVISITOR_REPLY_ID = 75,
+ STATBUCKET_ID = 76,
+ STATBUCKET_REPLY_ID = 77,
+ GETBUCKETLIST_ID = 78,
+ GETBUCKETLIST_REPLY_ID = 79,
+ DOCUMENTLIST_ID = 80,
+ DOCUMENTLIST_REPLY_ID = 81,
+ UPDATE_ID = 82,
+ UPDATE_REPLY_ID = 83,
+ EMPTYBUCKETS_ID = 84,
+ EMPTYBUCKETS_REPLY_ID = 85,
+ REMOVELOCATION_ID = 86,
+ REMOVELOCATION_REPLY_ID = 87,
+ QUERYRESULT_ID = 88,
+ QUERYRESULT_REPLY_ID = 89,
+ SETBUCKETSTATE_ID = 94,
+ SETBUCKETSTATE_REPLY_ID = 95,
+ ACTIVATE_CLUSTER_STATE_VERSION_ID = 96,
+ ACTIVATE_CLUSTER_STATE_VERSION_REPLY_ID = 97,
+ MESSAGETYPE_MAX_ID
+ };
+
+private:
+ static std::map<Id, MessageType*> _codes;
+ const vespalib::string _name;
+ Id _id;
+ MessageType *_reply;
+ const MessageType *_replyOf;
+
+ MessageType(vespalib::stringref name, Id id, const MessageType* replyOf = 0);
+public:
+ static const MessageType DOCBLOCK;
+ static const MessageType DOCBLOCK_REPLY;
+ static const MessageType GET;
+ static const MessageType GET_REPLY;
+ static const MessageType INTERNAL;
+ static const MessageType INTERNAL_REPLY;
+ static const MessageType PUT;
+ static const MessageType PUT_REPLY;
+ static const MessageType REMOVE;
+ static const MessageType REMOVE_REPLY;
+ static const MessageType REVERT;
+ static const MessageType REVERT_REPLY;
+ static const MessageType VISITOR_CREATE;
+ static const MessageType VISITOR_CREATE_REPLY;
+ static const MessageType VISITOR_DESTROY;
+ static const MessageType VISITOR_DESTROY_REPLY;
+ static const MessageType REQUESTBUCKETINFO;
+ static const MessageType REQUESTBUCKETINFO_REPLY;
+ static const MessageType NOTIFYBUCKETCHANGE;
+ static const MessageType NOTIFYBUCKETCHANGE_REPLY;
+ static const MessageType CREATEBUCKET;
+ static const MessageType CREATEBUCKET_REPLY;
+ static const MessageType MERGEBUCKET;
+ static const MessageType MERGEBUCKET_REPLY;
+ static const MessageType DELETEBUCKET;
+ static const MessageType DELETEBUCKET_REPLY;
+ static const MessageType SETNODESTATE;
+ static const MessageType SETNODESTATE_REPLY;
+ static const MessageType GETNODESTATE;
+ static const MessageType GETNODESTATE_REPLY;
+ static const MessageType SETSYSTEMSTATE;
+ static const MessageType SETSYSTEMSTATE_REPLY;
+ static const MessageType GETSYSTEMSTATE;
+ static const MessageType GETSYSTEMSTATE_REPLY;
+ static const MessageType ACTIVATE_CLUSTER_STATE_VERSION;
+ static const MessageType ACTIVATE_CLUSTER_STATE_VERSION_REPLY;
+ static const MessageType BUCKETSADDED;
+ static const MessageType BUCKETSADDED_REPLY;
+ static const MessageType BUCKETSREMOVED;
+ static const MessageType BUCKETSREMOVED_REPLY;
+ static const MessageType GETBUCKETDIFF;
+ static const MessageType GETBUCKETDIFF_REPLY;
+ static const MessageType APPLYBUCKETDIFF;
+ static const MessageType APPLYBUCKETDIFF_REPLY;
+ static const MessageType VISITOR_INFO;
+ static const MessageType VISITOR_INFO_REPLY;
+ static const MessageType SEARCHRESULT;
+ static const MessageType SEARCHRESULT_REPLY;
+ static const MessageType SPLITBUCKET;
+ static const MessageType SPLITBUCKET_REPLY;
+ static const MessageType JOINBUCKETS;
+ static const MessageType JOINBUCKETS_REPLY;
+ static const MessageType DOCUMENTSUMMARY;
+ static const MessageType DOCUMENTSUMMARY_REPLY;
+ static const MessageType MAPVISITOR;
+ static const MessageType MAPVISITOR_REPLY;
+ static const MessageType STATBUCKET;
+ static const MessageType STATBUCKET_REPLY;
+ static const MessageType GETBUCKETLIST;
+ static const MessageType GETBUCKETLIST_REPLY;
+ static const MessageType DOCUMENTLIST;
+ static const MessageType DOCUMENTLIST_REPLY;
+ static const MessageType UPDATE;
+ static const MessageType UPDATE_REPLY;
+ static const MessageType EMPTYBUCKETS;
+ static const MessageType EMPTYBUCKETS_REPLY;
+ static const MessageType REMOVELOCATION;
+ static const MessageType REMOVELOCATION_REPLY;
+ static const MessageType QUERYRESULT;
+ static const MessageType QUERYRESULT_REPLY;
+ static const MessageType SETBUCKETSTATE;
+ static const MessageType SETBUCKETSTATE_REPLY;
+
+ static const MessageType& get(Id id);
+
+ MessageType(const MessageType &) = delete;
+ MessageType& operator=(const MessageType &) = delete;
+ ~MessageType();
+ Id getId() const noexcept { return _id; }
+ static Id getMaxId() noexcept { return MESSAGETYPE_MAX_ID; }
+ const vespalib::string& getName() const noexcept { return _name; }
+ bool isReply() const noexcept { return (_replyOf != 0); }
+ /** Only valid to call on replies. */
+ const MessageType& getCommandType() const noexcept { return *_replyOf; }
+ /** Only valid to call on commands. */
+ const MessageType& getReplyType() const noexcept { return *_reply; }
+ bool operator==(const MessageType& type) const noexcept { return (_id == type._id); }
+ bool operator!=(const MessageType& type) const noexcept { return (_id != type._id); }
+
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+};
+
+/**
+ * Represent an address we can send a storage message to.
+ * We have two kinds of addresses:
+ * - A VDS address used to send to a single VDS node.
+ * - An external mbus route, used to send to an external source.
+ */
+class StorageMessageAddress {
+public:
+ enum class Protocol : uint8_t { STORAGE, DOCUMENT };
+
+private:
+ const vespalib::string *_cluster;
+ // Used for internal VDS addresses only
+ uint32_t _precomputed_storage_hash;
+ lib::NodeType::Type _type;
+ Protocol _protocol;
+ uint16_t _index;
+
+public:
+ StorageMessageAddress() noexcept; // Only to be used when transient default ctor semantics are needed by containers
+ StorageMessageAddress(const vespalib::string * cluster, const lib::NodeType& type, uint16_t index) noexcept;
+ StorageMessageAddress(const vespalib::string * cluster, const lib::NodeType& type, uint16_t index, Protocol protocol) noexcept;
+ ~StorageMessageAddress();
+
+ void setProtocol(Protocol p) noexcept { _protocol = p; }
+
+ mbus::Route to_mbus_route() const;
+ Protocol getProtocol() const noexcept { return _protocol; }
+ uint16_t getIndex() const noexcept { return _index; }
+ lib::NodeType::Type getNodeType() const noexcept { return _type; }
+ const vespalib::string& getCluster() const noexcept { return *_cluster; }
+
+ // Returns precomputed hash over <type, index> pair. Other fields not included.
+ [[nodiscard]] uint32_t internal_storage_hash() const noexcept {
+ return _precomputed_storage_hash;
+ }
+
+ bool operator==(const StorageMessageAddress& other) const noexcept;
+ vespalib::string toString() const;
+ friend std::ostream & operator << (std::ostream & os, const StorageMessageAddress & addr);
+ static StorageMessageAddress create(const vespalib::string * cluster, const lib::NodeType& type, uint16_t index) noexcept {
+ return api::StorageMessageAddress(cluster, type, index);
+ }
+ static StorageMessageAddress createDocApi(const vespalib::string * cluster, const lib::NodeType& type, uint16_t index) noexcept {
+ return api::StorageMessageAddress(cluster, type, index, Protocol::DOCUMENT);
+ }
+private:
+ void print(vespalib::asciistream & out) const;
+};
+
+struct TransportContext {
+ virtual ~TransportContext() = 0;
+};
+
+enum class LockingRequirements : uint8_t {
+ // Operations with exclusive locking can only be executed iff no other
+ // exclusive or shared locks are taken for its bucket.
+ Exclusive = 0,
+ // Operations with shared locking can only be executed iff no exclusive
+ // lock is taken for its bucket. Should only be used for read-only operations
+ // that cannot mutate a bucket's state.
+ Shared
+};
+
+const char* to_string(LockingRequirements req) noexcept;
+std::ostream& operator<<(std::ostream&, LockingRequirements);
+
+// This mirrors spi::ReadConsistency and has the same semantics, but is
+// decoupled to avoid extra cross-module dependencies.
+// Note that the name _internal_ read consistency is intentional to lessen
+// any ambiguities on whether this is consistency in a distributed systems
+// setting (i.e. linearizability) on internally in the persistence provider.
+enum class InternalReadConsistency : uint8_t {
+ Strong = 0,
+ Weak
+};
+
+const char* to_string(InternalReadConsistency consistency) noexcept;
+std::ostream& operator<<(std::ostream&, InternalReadConsistency);
+
+class StorageMessage : public vespalib::Printable
+{
+ friend class StorageMessageTest; // Used for testing only
+public:
+ DECLARE_POINTER_TYPEDEFS(StorageMessage);
+ typedef uint64_t Id;
+ typedef uint8_t Priority;
+
+ enum LegacyPriorityValues {
+ LOW = 225,
+ NORMAL = 127,
+ HIGH = 50,
+ VERYHIGH = 0
+ }; // FIXME
+ //static const unsigned int NUM_PRIORITIES = UINT8_MAX;
+ static const char* getPriorityString(Priority);
+
+private:
+ static document::Bucket getDummyBucket() noexcept { return document::Bucket(document::BucketSpace::invalid(), document::BucketId()); }
+ mutable std::unique_ptr<TransportContext> _transportContext;
+
+protected:
+ static Id generateMsgId() noexcept;
+
+ const MessageType& _type;
+ Id _msgId;
+ StorageMessageAddress _address;
+ vespalib::Trace _trace;
+ uint32_t _approxByteSize;
+ Priority _priority;
+
+ StorageMessage(const MessageType& code, Id id) noexcept;
+ StorageMessage(const StorageMessage&, Id id) noexcept;
+
+public:
+ StorageMessage& operator=(const StorageMessage&) = delete;
+ StorageMessage(const StorageMessage&) = delete;
+ ~StorageMessage() override;
+
+ Id getMsgId() const noexcept { return _msgId; }
+
+ /** Method used by storage commands to set a new id. */
+ void setNewMsgId() noexcept;
+
+ /**
+ * Set the id of this message. Typically used to set the id to a
+ * unique value previously generated with the generateMsgId method.
+ **/
+ void forceMsgId(Id msgId) noexcept { _msgId = msgId; }
+
+ const MessageType& getType() const noexcept { return _type; }
+
+ void setPriority(Priority p) noexcept { _priority = p; }
+ Priority getPriority() const noexcept { return _priority; }
+
+ const StorageMessageAddress* getAddress() const noexcept { return (_address.getNodeType() != lib::NodeType::Type::UNKNOWN) ? &_address : nullptr; }
+
+ void setAddress(const StorageMessageAddress& address) noexcept {
+ _address = address;
+ }
+
+ /**
+ * Returns the approximate memory footprint (in bytes) of a storage message.
+ * By default, returns 50 bytes.
+ */
+ uint32_t getApproxByteSize() const noexcept {
+ return _approxByteSize;
+ }
+
+ void setApproxByteSize(uint32_t value) noexcept {
+ _approxByteSize = value;
+ }
+
+ /**
+ * Used by storage to remember the context in which this message was
+ * created, whether it was a storageprotocol message, a documentprotocol
+ * message, or an RPC call.
+ */
+ void setTransportContext(std::unique_ptr<TransportContext> context) noexcept {
+ _transportContext = std::move(context);
+ }
+
+ std::unique_ptr<TransportContext> getTransportContext() const noexcept {
+ return std::move(_transportContext);
+ }
+
+ bool has_transport_context() const noexcept {
+ return (_transportContext.get() != nullptr);
+ }
+
+ /**
+ * This method is overloaded in subclasses and will call the correct
+ * method in the MessageHandler interface.
+ */
+ virtual bool callHandler(MessageHandler&, const StorageMessage::SP&) const = 0;
+
+ mbus::Trace && steal_trace() noexcept { return std::move(_trace); }
+ mbus::Trace& getTrace() noexcept { return _trace; }
+ const mbus::Trace& getTrace() const noexcept { return _trace; }
+
+ /**
+ Sets the trace object for this message.
+ */
+ void setTrace(vespalib::Trace && trace) noexcept { _trace = std::move(trace); }
+
+ /**
+ * Cheap version of tostring().
+ */
+ virtual vespalib::string getSummary() const;
+
+ virtual document::Bucket getBucket() const { return getDummyBucket(); }
+ document::BucketId getBucketId() const noexcept { return getBucket().getBucketId(); }
+ virtual LockingRequirements lockingRequirements() const noexcept {
+ // Safe default: assume exclusive locking is required.
+ return LockingRequirements::Exclusive;
+ }
+};
+
+}
diff --git a/storage/src/vespa/storageapi/messageapi/storagereply.cpp b/storage/src/vespa/storageapi/messageapi/storagereply.cpp
new file mode 100644
index 00000000000..a6fefe57e08
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/storagereply.cpp
@@ -0,0 +1,38 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "storagereply.h"
+#include "storagecommand.h"
+#include <ostream>
+
+namespace storage::api {
+
+StorageReply::StorageReply(const StorageCommand& cmd)
+ : StorageReply(cmd, ReturnCode())
+{}
+
+StorageReply::StorageReply(const StorageCommand& cmd, ReturnCode code)
+ : StorageMessage(cmd.getType().getReplyType(), cmd.getMsgId()),
+ _result(std::move(code))
+{
+ setPriority(cmd.getPriority());
+ if (cmd.getAddress()) {
+ setAddress(*cmd.getAddress());
+ }
+ // TODD do we really need copy construction
+ if ( ! cmd.getTrace().isEmpty()) {
+ setTrace(vespalib::Trace(cmd.getTrace()));
+ } else {
+ getTrace().setLevel(cmd.getTrace().getLevel());
+ }
+ setTransportContext(cmd.getTransportContext());
+}
+
+StorageReply::~StorageReply() = default;
+
+void
+StorageReply::print(std::ostream& out, bool , const std::string& ) const
+{
+ out << "StorageReply(" << _type.getName() << ", " << _result << ")";
+}
+
+}
diff --git a/storage/src/vespa/storageapi/messageapi/storagereply.h b/storage/src/vespa/storageapi/messageapi/storagereply.h
new file mode 100644
index 00000000000..9128617096d
--- /dev/null
+++ b/storage/src/vespa/storageapi/messageapi/storagereply.h
@@ -0,0 +1,39 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @class storage::api::StorageReply
+ * @ingroup messageapi
+ *
+ * @brief Superclass for all storage replies.
+ *
+ * A storage reply is a storage message sent in reply to a storage command.
+ *
+ * @version $Id$
+ */
+
+#pragma once
+
+#include "returncode.h"
+#include "storagemessage.h"
+
+namespace storage::api {
+
+class StorageCommand;
+
+class StorageReply : public StorageMessage {
+ ReturnCode _result;
+
+protected:
+ explicit StorageReply(const StorageCommand& cmd);
+ StorageReply(const StorageCommand& cmd, ReturnCode code);
+
+public:
+ ~StorageReply() override;
+ DECLARE_POINTER_TYPEDEFS(StorageReply);
+
+ void setResult(ReturnCode r) { _result = std::move(r); }
+ void setResult(ReturnCode::Result r) { _result = ReturnCode(r); }
+ const ReturnCode& getResult() const { return _result; }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override;
+};
+
+}