Skip to content

Commit 0540f14

Browse files
committed
MB-31141: Merge couchbase/vulcan to couchbase/alice
* couchbase/vulcan: MB-31141: Don't reject snappy|raw DCP deletes MB-31141: Account for nmeta in deleteWithMeta Change-Id: Iaaa6e99ff0c868f8fe8874abd7673022bb9c4e87
2 parents 327a51c + 84af631 commit 0540f14

File tree

8 files changed

+174
-52
lines changed

8 files changed

+174
-52
lines changed

daemon/mcbp_validators.cc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,12 @@ static bool valid_dcp_delete_datatype(protocol_binary_datatype_t datatype) {
389389
// it may send XATTR|JSON (with snappy possible). These are now allowed
390390
// so rebalance won't be failed and the consumer will sanitise the faulty
391391
// documents.
392-
std::array<const protocol_binary_datatype_t, 5> valid = {
392+
// MB-31141: Allowing RAW+Snappy. A bug in delWithMeta has allowed us to
393+
// create deletes with a non-zero value tagged as RAW, which when snappy
394+
// is enabled gets DCP shipped as RAW+Snappy.
395+
std::array<const protocol_binary_datatype_t, 6> valid = {
393396
{PROTOCOL_BINARY_RAW_BYTES,
397+
PROTOCOL_BINARY_RAW_BYTES | PROTOCOL_BINARY_DATATYPE_SNAPPY,
394398
PROTOCOL_BINARY_DATATYPE_XATTR,
395399
PROTOCOL_BINARY_DATATYPE_XATTR | PROTOCOL_BINARY_DATATYPE_SNAPPY,
396400
PROTOCOL_BINARY_DATATYPE_XATTR | PROTOCOL_BINARY_DATATYPE_JSON,

engines/ep/src/dcp/consumer.cc

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -574,10 +574,16 @@ ENGINE_ERROR_CODE DcpConsumer::deletion(uint32_t opaque,
574574
// MB-29040: Producer may send deleted doc with value that still has
575575
// the user xattrs and the body. Fix up that mistake by running the
576576
// expiry hook which will correctly process the document
577-
if (mcbp::datatype::is_xattr(datatype) && value.size()) {
578-
auto vb = engine_.getVBucket(vbucket);
579-
if (vb) {
580-
engine_.getKVBucket()->runPreExpiryHook(*vb, *item);
577+
if (value.size()) {
578+
if (mcbp::datatype::is_xattr(datatype)) {
579+
auto vb = engine_.getVBucket(vbucket);
580+
if (vb) {
581+
engine_.getKVBucket()->runPreExpiryHook(*vb, *item);
582+
}
583+
} else {
584+
// MB-31141: Deletes cannot have a value
585+
item->replaceValue(Blob::New(0));
586+
item->setDataType(PROTOCOL_BINARY_RAW_BYTES);
581587
}
582588
}
583589

engines/ep/src/ep_engine.cc

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5122,9 +5122,12 @@ ENGINE_ERROR_CODE EventuallyPersistentEngine::deleteWithMeta(
51225122
keyOffset += 2; // 2 bytes for nmeta
51235123
nmeta = ntohs(nmeta);
51245124
if (nmeta > 0) {
5125-
emd = cb::const_byte_buffer(
5126-
request->bytes + sizeof(request->bytes) + nkey + keyOffset,
5127-
nmeta);
5125+
// Correct the vallen
5126+
vallen -= nmeta;
5127+
emd = cb::const_byte_buffer(request->bytes +
5128+
sizeof(request->bytes) + nkey +
5129+
keyOffset + vallen,
5130+
nmeta);
51285131
}
51295132
}
51305133

engines/ep/src/stored-value.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ std::ostream& operator<<(std::ostream& os, const StoredValue& sv) {
433433

434434
// seqno, revid, expiry / purge time
435435
os << "seq:" << sv.getBySeqno() << " rev:" << sv.getRevSeqno();
436+
os << " cas:" << sv.getCas();
436437
os << " key:\"" << sv.getKey() << "\"";
437438
if (sv.isOrdered() && sv.isDeleted()) {
438439
os << " del_time:" << sv.lock_expiry_or_delete_time;

engines/ep/tests/module_tests/dcp_test.cc

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2966,28 +2966,25 @@ TEST_P(ConnectionTest, test_mb24424_deleteResponse) {
29662966
ASSERT_TRUE(stream->isActive());
29672967

29682968
std::string key = "key";
2969-
std::string data = R"({"json":"yes"})";
29702969
const DocKey docKey{ reinterpret_cast<const uint8_t*>(key.data()),
29712970
key.size(),
29722971
DocNamespace::DefaultCollection};
2973-
cb::const_byte_buffer value{reinterpret_cast<const uint8_t*>(data.data()),
2974-
data.size()};
29752972
uint8_t extMeta[1] = {uint8_t(PROTOCOL_BINARY_DATATYPE_JSON)};
29762973
cb::const_byte_buffer meta{extMeta, sizeof(uint8_t)};
29772974

2978-
consumer->deletion(/*opaque*/1,
2979-
/*key*/docKey,
2980-
/*values*/value,
2981-
/*priv_bytes*/0,
2982-
/*datatype*/PROTOCOL_BINARY_DATATYPE_JSON,
2983-
/*cas*/0,
2984-
/*vbucket*/vbid,
2985-
/*bySeqno*/1,
2986-
/*revSeqno*/0,
2987-
/*meta*/meta);
2988-
2989-
auto messageSize = MutationResponse::deletionBaseMsgBytes +
2990-
key.size() + data.size() + sizeof(extMeta);
2975+
consumer->deletion(/*opaque*/ 1,
2976+
/*key*/ docKey,
2977+
/*value*/ {},
2978+
/*priv_bytes*/ 0,
2979+
/*datatype*/ PROTOCOL_BINARY_RAW_BYTES,
2980+
/*cas*/ 0,
2981+
/*vbucket*/ vbid,
2982+
/*bySeqno*/ 1,
2983+
/*revSeqno*/ 0,
2984+
/*meta*/ meta);
2985+
2986+
auto messageSize = MutationResponse::deletionBaseMsgBytes + key.size() +
2987+
sizeof(extMeta);
29912988

29922989
EXPECT_EQ(messageSize, stream->responseMessageSize);
29932990

engines/ep/tests/module_tests/evp_store_single_threaded_test.cc

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,16 +1613,13 @@ TEST_F(SingleThreadedEPBucketTest, MB_29861) {
16131613
/*startseq*/ 0,
16141614
/*endseq*/ 2,
16151615
/*flags*/ MARKER_FLAG_DISK);
1616-
std::string data = R"({"json":"yes"})";
1617-
cb::const_byte_buffer value{reinterpret_cast<const uint8_t*>(data.data()),
1618-
data.size()};
16191616

16201617
// 2. Now add a deletion.
16211618
consumer->deletion(/*opaque*/ 1,
16221619
{"key1", DocNamespace::DefaultCollection},
1623-
/*values*/ value,
1620+
/*value*/ {},
16241621
/*priv_bytes*/ 0,
1625-
/*datatype*/ PROTOCOL_BINARY_DATATYPE_JSON,
1622+
/*datatype*/ PROTOCOL_BINARY_RAW_BYTES,
16261623
/*cas*/ 0,
16271624
/*vbucket*/ vbid,
16281625
/*bySeqno*/ 1,
@@ -1659,7 +1656,7 @@ TEST_F(SingleThreadedEPBucketTest, MB_29861) {
16591656
deleted,
16601657
datatype));
16611658
EXPECT_EQ(1, deleted);
1662-
EXPECT_EQ(PROTOCOL_BINARY_DATATYPE_JSON, datatype);
1659+
EXPECT_EQ(PROTOCOL_BINARY_RAW_BYTES, datatype);
16631660
EXPECT_NE(0, metadata.exptime); // A locally created deleteTime
16641661
}
16651662

@@ -1684,15 +1681,12 @@ TEST_F(SingleThreadedEPBucketTest, MB_27457) {
16841681
/*startseq*/ 0,
16851682
/*endseq*/ 2,
16861683
/*flags*/ 0);
1687-
std::string data = R"({"json":"yes"})";
1688-
cb::const_byte_buffer value{reinterpret_cast<const uint8_t*>(data.data()),
1689-
data.size()};
16901684
// 2. Now add two deletions, one without deleteTime, one with
16911685
consumer->deletionV2(/*opaque*/ 1,
16921686
{"key1", DocNamespace::DefaultCollection},
1693-
/*values*/ value,
1687+
/*values*/ {},
16941688
/*priv_bytes*/ 0,
1695-
/*datatype*/ PROTOCOL_BINARY_DATATYPE_JSON,
1689+
/*datatype*/ PROTOCOL_BINARY_RAW_BYTES,
16961690
/*cas*/ 0,
16971691
/*vbucket*/ vbid,
16981692
/*bySeqno*/ 1,
@@ -1702,9 +1696,9 @@ TEST_F(SingleThreadedEPBucketTest, MB_27457) {
17021696
const uint32_t deleteTime = 10;
17031697
consumer->deletionV2(/*opaque*/ 1,
17041698
{"key2", DocNamespace::DefaultCollection},
1705-
/*values*/ value,
1699+
/*value*/ {},
17061700
/*priv_bytes*/ 0,
1707-
/*datatype*/ PROTOCOL_BINARY_DATATYPE_JSON,
1701+
/*datatype*/ PROTOCOL_BINARY_RAW_BYTES,
17081702
/*cas*/ 0,
17091703
/*vbucket*/ vbid,
17101704
/*bySeqno*/ 2,
@@ -1741,7 +1735,7 @@ TEST_F(SingleThreadedEPBucketTest, MB_27457) {
17411735
deleted,
17421736
datatype));
17431737
EXPECT_EQ(1, deleted);
1744-
EXPECT_EQ(PROTOCOL_BINARY_DATATYPE_JSON, datatype);
1738+
EXPECT_EQ(PROTOCOL_BINARY_RAW_BYTES, datatype);
17451739
EXPECT_NE(0, metadata.exptime); // A locally created deleteTime
17461740

17471741
deleted = 0;
@@ -1762,7 +1756,7 @@ TEST_F(SingleThreadedEPBucketTest, MB_27457) {
17621756
deleted,
17631757
datatype));
17641758
EXPECT_EQ(1, deleted);
1765-
EXPECT_EQ(PROTOCOL_BINARY_DATATYPE_JSON, datatype);
1759+
EXPECT_EQ(PROTOCOL_BINARY_RAW_BYTES, datatype);
17661760
EXPECT_EQ(deleteTime, metadata.exptime); // Our replicated deleteTime!
17671761
}
17681762

@@ -3037,6 +3031,69 @@ TEST_P(XattrCompressedTest, MB_29040_sanitise_input) {
30373031
gv.item->getDataType());
30383032
}
30393033

3034+
// Create a replica VB and consumer, then send it an delete with value which
3035+
// should never of been created on the source.
3036+
TEST_F(SingleThreadedEPBucketTest, MB_31141_sanitise_input) {
3037+
setVBucketStateAndRunPersistTask(vbid, vbucket_state_replica);
3038+
3039+
auto consumer = std::make_shared<MockDcpConsumer>(
3040+
*engine, cookie, "MB_31141_sanitise_input");
3041+
int opaque = 1;
3042+
ASSERT_EQ(ENGINE_SUCCESS, consumer->addStream(opaque, vbid, /*flags*/ 0));
3043+
3044+
std::string body = "value";
3045+
3046+
// Send deletion in a single seqno snapshot
3047+
int64_t bySeqno = 1;
3048+
EXPECT_EQ(ENGINE_SUCCESS,
3049+
consumer->snapshotMarker(
3050+
opaque, vbid, bySeqno, bySeqno, MARKER_FLAG_CHK));
3051+
3052+
EXPECT_EQ(ENGINE_SUCCESS,
3053+
consumer->deletion(opaque,
3054+
{"key", DocNamespace::DefaultCollection},
3055+
{reinterpret_cast<const uint8_t*>(body.data()),
3056+
body.size()},
3057+
/*priv_bytes*/ 0,
3058+
PROTOCOL_BINARY_DATATYPE_SNAPPY |
3059+
PROTOCOL_BINARY_RAW_BYTES,
3060+
/*cas*/ 3,
3061+
vbid,
3062+
bySeqno,
3063+
/*revSeqno*/ 0,
3064+
/*meta*/ {}));
3065+
3066+
EXPECT_EQ(std::make_pair(false, size_t(1)),
3067+
getEPBucket().flushVBucket(vbid));
3068+
3069+
ASSERT_EQ(ENGINE_SUCCESS, consumer->closeStream(opaque, vbid));
3070+
3071+
// Switch to active
3072+
setVBucketStateAndRunPersistTask(vbid, vbucket_state_active);
3073+
3074+
get_options_t options = static_cast<get_options_t>(
3075+
QUEUE_BG_FETCH | HONOR_STATES | TRACK_REFERENCE | DELETE_TEMP |
3076+
HIDE_LOCKED_CAS | TRACK_STATISTICS | GET_DELETED_VALUE);
3077+
auto gv = store->get(
3078+
{"key", DocNamespace::DefaultCollection}, vbid, cookie, options);
3079+
EXPECT_EQ(ENGINE_EWOULDBLOCK, gv.getStatus());
3080+
3081+
// Manually run the bgfetch task.
3082+
MockGlobalTask mockTask(engine->getTaskable(), TaskId::MultiBGFetcherTask);
3083+
store->getVBucket(vbid)->getShard()->getBgFetcher()->run(&mockTask);
3084+
gv = store->get({"key", DocNamespace::DefaultCollection},
3085+
vbid,
3086+
cookie,
3087+
GET_DELETED_VALUE);
3088+
ASSERT_EQ(ENGINE_SUCCESS, gv.getStatus());
3089+
3090+
EXPECT_TRUE(gv.item->isDeleted());
3091+
EXPECT_EQ(0, gv.item->getFlags());
3092+
EXPECT_EQ(3, gv.item->getCas());
3093+
EXPECT_EQ(0, gv.item->getValue()->valueSize());
3094+
EXPECT_EQ(PROTOCOL_BINARY_RAW_BYTES, gv.item->getDataType());
3095+
}
3096+
30403097
// Test highlighting MB_29480 - this is not demonstrating the issue is fixed.
30413098
TEST_F(SingleThreadedEPBucketTest, MB_29480) {
30423099
// Make vbucket active.

engines/ep/tests/module_tests/evp_store_with_meta.cc

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ class WithMetaTest : public SingleThreadedEPBucketTest {
148148
int options,
149149
protocol_binary_response_status expectedResponseStatus,
150150
const std::string& key,
151-
const std::string& value) {
151+
const std::string& value,
152+
const std::vector<char>& emd) {
152153
auto swm = buildWithMetaPacket(op,
153154
0 /*datatype*/,
154155
vbid,
@@ -157,7 +158,7 @@ class WithMetaTest : public SingleThreadedEPBucketTest {
157158
itemMeta,
158159
key,
159160
value,
160-
{},
161+
emd,
161162
options);
162163
if (expectedResponseStatus == PROTOCOL_BINARY_RESPONSE_NOT_MY_VBUCKET) {
163164
EXPECT_EQ(ENGINE_NOT_MY_VBUCKET, callEngine(op, swm));
@@ -175,18 +176,14 @@ class WithMetaTest : public SingleThreadedEPBucketTest {
175176
int options,
176177
bool withValue,
177178
protocol_binary_response_status expectedResponseStatus,
178-
ENGINE_ERROR_CODE expectedGetReturnValue) {
179+
ENGINE_ERROR_CODE expectedGetReturnValue,
180+
const std::vector<char>& emd = {}) {
179181
std::string key = "mykey";
180182
std::string value;
181183
if (withValue) {
182184
value = createXattrValue("myvalue"); // xattr but stored as raw
183185
}
184-
oneOp(op,
185-
itemMeta,
186-
options,
187-
expectedResponseStatus,
188-
key,
189-
value);
186+
oneOp(op, itemMeta, options, expectedResponseStatus, key, value, emd);
190187
checkGetItem(key, value, itemMeta, expectedGetReturnValue);
191188
}
192189

@@ -655,7 +652,7 @@ void WithMetaTest::conflict_lose(protocol_binary_command op,
655652
EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, getAddResponseStatus());
656653

657654
for (const auto& td : testData) {
658-
oneOp(op, td.meta, options, td.expectedStatus, key, value);
655+
oneOp(op, td.meta, options, td.expectedStatus, key, value, {});
659656
}
660657
}
661658

@@ -1359,6 +1356,63 @@ TEST_P(DelWithMetaTest, setting_deleteTime) {
13591356
EXPECT_EQ(itemMeta.exptime, metadata.exptime);
13601357
}
13611358

1359+
TEST_P(DelWithMetaTest, MB_31141) {
1360+
ItemMetaData itemMeta{0xdeadbeef, 0xf00dcafe, 0xfacefeed, expiry};
1361+
// Do a delete with valid extended meta
1362+
// see - ep-engine/docs/protocol/del_with_meta.md
1363+
oneOpAndCheck(op,
1364+
itemMeta,
1365+
0, // no-options
1366+
withValue,
1367+
PROTOCOL_BINARY_RESPONSE_SUCCESS,
1368+
withValue ? ENGINE_SUCCESS : ENGINE_EWOULDBLOCK,
1369+
{0x01, 0x01, 0x00, 0x01, 0x01});
1370+
1371+
EXPECT_EQ(std::make_pair(false, size_t(1)),
1372+
getEPBucket().flushVBucket(vbid));
1373+
1374+
auto options = get_options_t(QUEUE_BG_FETCH | GET_DELETED_VALUE);
1375+
auto result = store->get(
1376+
{"mykey", DocNamespace::DefaultCollection}, vbid, cookie, options);
1377+
EXPECT_EQ(ENGINE_EWOULDBLOCK, result.getStatus());
1378+
1379+
// Run the BGFetcher task
1380+
MockGlobalTask mockTask(engine->getTaskable(), TaskId::MultiBGFetcherTask);
1381+
store->getVBucket(vbid)->getShard()->getBgFetcher()->run(&mockTask);
1382+
1383+
result = store->get(
1384+
{"mykey", DocNamespace::DefaultCollection}, vbid, cookie, options);
1385+
ASSERT_EQ(ENGINE_SUCCESS, result.getStatus());
1386+
1387+
// Before the fix 5.0+ could of left the value as 5, the size of the
1388+
// extended metadata
1389+
int expectedSize = withValue ? 275 : 0;
1390+
EXPECT_EQ(expectedSize, result.item->getNBytes());
1391+
}
1392+
1393+
TEST_P(AddSetWithMetaTest, MB_31141) {
1394+
ItemMetaData itemMeta{0xdeadbeef, 0xf00dcafe, 0xfacefeed, expiry};
1395+
// Do a set/add with valid extended meta
1396+
// see - ep-engine/docs/protocol/del_with_meta.md
1397+
oneOpAndCheck(GetParam(),
1398+
itemMeta,
1399+
0, // no-options
1400+
true,
1401+
PROTOCOL_BINARY_RESPONSE_SUCCESS,
1402+
ENGINE_SUCCESS,
1403+
{0x01, 0x01, 0x00, 0x01, 0x01});
1404+
1405+
EXPECT_EQ(std::make_pair(false, size_t(1)),
1406+
getEPBucket().flushVBucket(vbid));
1407+
1408+
auto options = get_options_t(QUEUE_BG_FETCH);
1409+
auto result = store->get(
1410+
{"mykey", DocNamespace::DefaultCollection}, vbid, cookie, options);
1411+
ASSERT_EQ(ENGINE_SUCCESS, result.getStatus());
1412+
// Only the value should come back
1413+
EXPECT_EQ(275, result.item->getNBytes());
1414+
}
1415+
13621416
auto opcodeValues = ::testing::Values(PROTOCOL_BINARY_CMD_SET_WITH_META,
13631417
PROTOCOL_BINARY_CMD_SETQ_WITH_META,
13641418
PROTOCOL_BINARY_CMD_ADD_WITH_META,

tests/mcbp/mcbp_test.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1942,8 +1942,9 @@ TEST_P(DcpDeletionValidatorTest, InvalidMagic) {
19421942

19431943
TEST_P(DcpDeletionValidatorTest, ValidDatatype) {
19441944
using cb::mcbp::Datatype;
1945-
const std::array<uint8_t, 3> datatypes = {
1945+
const std::array<uint8_t, 4> datatypes = {
19461946
{uint8_t(Datatype::Raw),
1947+
uint8_t(Datatype::Raw) | uint8_t(Datatype::Snappy),
19471948
uint8_t(Datatype::Xattr),
19481949
uint8_t(Datatype::Xattr) | uint8_t(Datatype::Snappy)}};
19491950

@@ -1956,9 +1957,8 @@ TEST_P(DcpDeletionValidatorTest, ValidDatatype) {
19561957

19571958
TEST_P(DcpDeletionValidatorTest, InvalidDatatype) {
19581959
using cb::mcbp::Datatype;
1959-
const std::array<uint8_t, 3> datatypes = {
1960+
const std::array<uint8_t, 2> datatypes = {
19601961
{uint8_t(Datatype::JSON),
1961-
uint8_t(Datatype::Snappy),
19621962
uint8_t(Datatype::Snappy) | uint8_t(Datatype::JSON)}};
19631963

19641964
for (auto invalid : datatypes) {

0 commit comments

Comments
 (0)