Skip to content

Commit 84af631

Browse files
jimwwalkerdaverigby
authored andcommitted
MB-31141: Don't reject snappy|raw DCP deletes
A related bug means that is possible for 5.x to create deletes with a non-zero raw value. When 5.5x backfills such an item for transmission to another 5.5x node (and snappy is enabled), the delete gets sent with a snappy datatype and rejected by the target node, which triggers a rebalance failure. Change-Id: Ib91453f96882ef4e01ee0e2a3e5e51ed0786a325 Reviewed-on: http://review.couchbase.org/99414 Well-Formed: Build Bot <[email protected]> Reviewed-by: Dave Rigby <[email protected]> Tested-by: Dave Rigby <[email protected]>
1 parent bb60376 commit 84af631

File tree

5 files changed

+103
-39
lines changed

5 files changed

+103
-39
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/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.

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)