Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1407,7 +1407,13 @@ public void readLastConfirmedDataComplete(int rc, DigestManager.RecoveryData dat
}
};

new ReadLastConfirmedOp(this, clientCtx.getBookieClient(), getCurrentEnsemble(), innercb).initiate();
new ReadLastConfirmedOp(clientCtx.getBookieClient(),
distributionSchedule,
macManager,
ledgerId,
getCurrentEnsemble(),
ledgerKey,
innercb).initiate();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,12 @@ LedgerRecoveryOp setEntryListener(ReadEntryListener entryListener) {
}

public CompletableFuture<LedgerHandle> initiate() {
ReadLastConfirmedOp rlcop = new ReadLastConfirmedOp(lh, clientCtx.getBookieClient(), lh.getCurrentEnsemble(),
ReadLastConfirmedOp rlcop = new ReadLastConfirmedOp(clientCtx.getBookieClient(),
lh.distributionSchedule,
lh.macManager,
lh.ledgerId,
lh.getCurrentEnsemble(),
lh.ledgerKey,
new ReadLastConfirmedOp.LastConfirmedDataCallback() {
public void readLastConfirmedDataComplete(int rc, RecoveryData data) {
if (rc == BKException.Code.OK) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
*/
package org.apache.bookkeeper.client;

import com.google.common.annotations.VisibleForTesting;

import io.netty.buffer.ByteBuf;
import java.util.List;

Expand All @@ -25,6 +27,7 @@
import org.apache.bookkeeper.proto.BookieClient;
import org.apache.bookkeeper.proto.BookieProtocol;
import org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.ReadEntryCallback;
import org.apache.bookkeeper.proto.checksum.DigestManager;
import org.apache.bookkeeper.proto.checksum.DigestManager.RecoveryData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -35,17 +38,18 @@
*/
class ReadLastConfirmedOp implements ReadEntryCallback {
static final Logger LOG = LoggerFactory.getLogger(ReadLastConfirmedOp.class);
LedgerHandle lh;
BookieClient bookieClient;
int numResponsesPending;
int numSuccessfulResponse;
RecoveryData maxRecoveredData;
volatile boolean completed = false;
int lastSeenError = BKException.Code.ReadException;

LastConfirmedDataCallback cb;
final DistributionSchedule.QuorumCoverageSet coverageSet;
final List<BookieSocketAddress> currentEnsemble;
private final long ledgerId;
private final byte[] ledgerKey;
private final BookieClient bookieClient;
private final DigestManager digestManager;
private int numResponsesPending;
private RecoveryData maxRecoveredData;
private volatile boolean completed = false;
private int lastSeenError = BKException.Code.ReadException;

private final LastConfirmedDataCallback cb;
private final DistributionSchedule.QuorumCoverageSet coverageSet;
private final List<BookieSocketAddress> currentEnsemble;

/**
* Wrapper to get all recovered data from the request.
Expand All @@ -54,22 +58,28 @@ interface LastConfirmedDataCallback {
void readLastConfirmedDataComplete(int rc, RecoveryData data);
}

public ReadLastConfirmedOp(LedgerHandle lh, BookieClient bookieClient,
List<BookieSocketAddress> ensemble, LastConfirmedDataCallback cb) {
public ReadLastConfirmedOp(BookieClient bookieClient,
DistributionSchedule schedule,
DigestManager digestManager,
long ledgerId,
List<BookieSocketAddress> ensemble,
byte[] ledgerKey,
LastConfirmedDataCallback cb) {
this.cb = cb;
this.bookieClient = bookieClient;
this.maxRecoveredData = new RecoveryData(LedgerHandle.INVALID_ENTRY_ID, 0);
this.lh = lh;
this.numSuccessfulResponse = 0;
this.numResponsesPending = lh.getLedgerMetadata().getEnsembleSize();
this.coverageSet = lh.distributionSchedule.getCoverageSet();
this.numResponsesPending = ensemble.size();
this.coverageSet = schedule.getCoverageSet();
this.currentEnsemble = ensemble;
this.ledgerId = ledgerId;
this.ledgerKey = ledgerKey;
this.digestManager = digestManager;
}

public void initiate() {
for (int i = 0; i < currentEnsemble.size(); i++) {
bookieClient.readEntry(currentEnsemble.get(i),
lh.ledgerId,
ledgerId,
BookieProtocol.LAST_ADD_CONFIRMED,
this, i, BookieProtocol.FLAG_NONE);
}
Expand All @@ -78,10 +88,10 @@ public void initiate() {
public void initiateWithFencing() {
for (int i = 0; i < currentEnsemble.size(); i++) {
bookieClient.readEntry(currentEnsemble.get(i),
lh.ledgerId,
ledgerId,
BookieProtocol.LAST_ADD_CONFIRMED,
this, i, BookieProtocol.FLAG_DO_FENCING,
lh.ledgerKey);
ledgerKey);
}
}

Expand All @@ -96,12 +106,11 @@ public synchronized void readEntryComplete(final int rc, final long ledgerId, fi
boolean heardValidResponse = false;
if (rc == BKException.Code.OK) {
try {
RecoveryData recoveryData = lh.macManager.verifyDigestAndReturnLastConfirmed(buffer);
RecoveryData recoveryData = digestManager.verifyDigestAndReturnLastConfirmed(buffer);
if (recoveryData.getLastAddConfirmed() > maxRecoveredData.getLastAddConfirmed()) {
maxRecoveredData = recoveryData;
}
heardValidResponse = true;
numSuccessfulResponse++;
} catch (BKDigestMatchException e) {
// Too bad, this bookie didn't give us a valid answer, we
// still might be able to recover though so continue
Expand Down Expand Up @@ -140,17 +149,15 @@ public synchronized void readEntryComplete(final int rc, final long ledgerId, fi
}

if (numResponsesPending == 0 && !completed) {
int totalExepctedResponse = lh.getLedgerMetadata().getWriteQuorumSize()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rdhabalia PTAL

- lh.getLedgerMetadata().getAckQuorumSize() + 1;
if (numSuccessfulResponse >= totalExepctedResponse) {
cb.readLastConfirmedDataComplete(BKException.Code.OK, maxRecoveredData);
return;
}
// Have got all responses back but was still not enough, just fail the operation
LOG.error("While readLastConfirmed ledger: {} did not hear success responses from all quorums {}", ledgerId,
lastSeenError);
LOG.error("While readLastConfirmed ledger: {} did not hear success responses from all quorums, {}",
ledgerId, coverageSet);
cb.readLastConfirmedDataComplete(lastSeenError, maxRecoveredData);
}

}

@VisibleForTesting
synchronized int getNumResponsesPending() {
return numResponsesPending;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -373,29 +373,43 @@ public synchronized void addBookie(int bookieIndexHeardFrom, int rc) {
public synchronized boolean checkCovered() {
// now check if there are any write quorums, with |ackQuorum| nodes available
for (int i = 0; i < ensembleSize; i++) {
int nodesNotCovered = 0;
int nodesOkay = 0;
int nodesUninitialized = 0;
/* Nodes which have either responded with an error other than NoSuch{Entry,Ledger},

@rdhabalia rdhabalia Apr 19, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I am missing something here So, I have a question. While doing a ledger recovery, bk client waits for response from (Qw - Qa) + 1 bookies
So, if we have ledger with E=3, W=2, A=2 and if bk-client receives ack from one of the bookie then: Qw-Qa+1 = (2-2+1) = 1 >= Response (1).
So, checkCovered() should return true.
However, with this change it fails on such useacase:
eg.

RoundRobinDistributionSchedule schedule = new RoundRobinDistributionSchedule(
        2, 2, 3);
Set<Integer> resultSet = Sets.newHashSet(BKException.Code.OK,
        BKException.Code.UNINITIALIZED, BKException.Code.UNINITIALIZED);
DistributionSchedule.QuorumCoverageSet covSet = schedule.getCoverageSet();
int index =0;
for (Integer i : resultSet) {
    covSet.addBookie(index++, i);
}
boolean covSetSays = covSet.checkCovered();
assertTrue(covSetSays);

So, can you please confirm the above assumption is correct or am I missing anything here?
and can't we just check Qw-Qa+1 in this method:

public synchronized boolean checkCovered() {
int nodesUnknown = 0;
for (int i = 0; i < covered.length; i++) {
    if (covered[i] != BKException.Code.OK
            && covered[i] != BKException.Code.NoSuchEntryException
            && covered[i] != BKException.Code.NoSuchLedgerExistsException) {
            nodesUnknown++;
        }
}
int expectedKnownNodes = (writeQuorumSize - ackQuorumSize) + 1;
return (ensembleSize - nodesUnknown) >= expectedKnownNodes;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage is not Qw - Qa + 1. It's at least one bookie from all possible ack quorums.

Take your example above. You have b1=OK, b2=Unknown, b3=Unknown. But an entry could possibly be written to b2 & b3 and have attained ack quorum. So we need to check all write sets, of which there are |ensemble|, since we are roundrobining.

Within an individual writeset, we only need to hear from (Qw - Qa) + 1 nodes, which is what the change submitted is doing.

or have not responded at all. We cannot know if these nodes ever accepted a entry. */
int nodesUnknown = 0;

for (int j = 0; j < writeQuorumSize; j++) {
int nodeIndex = (i + j) % ensembleSize;
if (covered[nodeIndex] == BKException.Code.OK) {
nodesOkay++;
} else if (covered[nodeIndex] != BKException.Code.NoSuchEntryException
&& covered[nodeIndex] != BKException.Code.NoSuchLedgerExistsException) {
nodesNotCovered++;
if (covered[nodeIndex] == BKException.Code.UNINITIALIZED) {
nodesUninitialized++;
}
if (covered[nodeIndex] != BKException.Code.OK
&& covered[nodeIndex] != BKException.Code.NoSuchEntryException
&& covered[nodeIndex] != BKException.Code.NoSuchLedgerExistsException) {
nodesUnknown++;
}
}
// if we haven't seen any OK responses and there are still nodes not heard from,
// let's wait until
if (nodesNotCovered >= ackQuorumSize || (nodesOkay == 0 && nodesUninitialized > 0)) {

/* If nodesUnknown is greater than the ack quorum size, then
it is possible those two unknown nodes accepted an entry which
we do not know about */
if (nodesUnknown >= ackQuorumSize) {
return false;
}
}
return true;
}

@Override
public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.append("QuorumCoverage(e:").append(ensembleSize)
.append(",w:").append(writeQuorumSize)
.append(",a:").append(ackQuorumSize)
.append(") = [");
int i = 0;
for (; i < covered.length - 1; i++) {
buffer.append(covered[i]).append(", ");
}
buffer.append(covered[i]).append("]");
return buffer.toString();
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -675,8 +675,12 @@ public void operationComplete(int rc, Void result) {
final AtomicInteger rcHolder = new AtomicInteger(-1234);
final CountDownLatch doneLatch = new CountDownLatch(1);

new ReadLastConfirmedOp(readLh, bkc.getBookieClient(),
new ReadLastConfirmedOp(bkc.getBookieClient(),
readLh.distributionSchedule,
readLh.macManager,
readLh.ledgerId,
readLh.getLedgerMetadata().getAllEnsembles().lastEntry().getValue(),
readLh.ledgerKey,
new ReadLastConfirmedOp.LastConfirmedDataCallback() {
@Override
public void readLastConfirmedDataComplete(int rc, DigestManager.RecoveryData data) {
Expand Down Expand Up @@ -760,8 +764,13 @@ public void testRecoveryWithUnavailableBookie() throws Exception {
private int readLACFromQuorum(LedgerHandle ledger, int... bookieLACResponse) throws Exception {
MutableInt responseCode = new MutableInt(100);
CountDownLatch responseLatch = new CountDownLatch(1);
ReadLastConfirmedOp readLCOp = new ReadLastConfirmedOp(ledger, bkc.getBookieClient(),
ReadLastConfirmedOp readLCOp = new ReadLastConfirmedOp(
bkc.getBookieClient(),
ledger.getDistributionSchedule(),
ledger.getDigestManager(),
ledger.getId(),
ledger.getLedgerMetadata().getAllEnsembles().lastEntry().getValue(),
ledger.getLedgerKey(),
new ReadLastConfirmedOp.LastConfirmedDataCallback() {
@Override
public void readLastConfirmedDataComplete(int rc, DigestManager.RecoveryData data) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.bookkeeper.client;

import io.netty.buffer.UnpooledByteBufAllocator;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.bookkeeper.common.util.OrderedExecutor;
import org.apache.bookkeeper.net.BookieSocketAddress;
import org.apache.bookkeeper.proto.MockBookieClient;
import org.apache.bookkeeper.proto.DataFormats.LedgerMetadataFormat.DigestType;
import org.apache.bookkeeper.proto.checksum.DigestManager;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ReadLastConfirmedOpTest {
private static final Logger log = LoggerFactory.getLogger(ReadLastConfirmedOpTest.class);
private final BookieSocketAddress bookie1 = new BookieSocketAddress("bookie1", 3181);
private final BookieSocketAddress bookie2 = new BookieSocketAddress("bookie2", 3181);

OrderedExecutor executor = null;

@Before
public void setup() throws Exception {
executor = OrderedExecutor.newBuilder()
.name("BookKeeperClientWorker")
.numThreads(1)
.build();
}

@After
public void teardown() throws Exception {
if (executor != null) {
executor.shutdown();
}
}

/**
* Test for specific bug that was introduced with dcdd1e88

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we describe the scenario ?

Maybe referring dcdd1e8 is not useful

*/
@Test
public void testBookieFailsAfterLedgerMissingOnFirst() throws Exception {
long ledgerId = 0xf00b;
List<BookieSocketAddress> ensemble = Lists.newArrayList(bookie1, bookie2);
byte[] ledgerKey = new byte[0];

MockBookieClient bookieClient = new MockBookieClient(executor);
DistributionSchedule schedule = new RoundRobinDistributionSchedule(2, 2, 2);
DigestManager digestManager = DigestManager.instantiate(ledgerId, ledgerKey,
DigestType.CRC32C,
UnpooledByteBufAllocator.DEFAULT,
true /* useV2 */);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is is necessary to use V2 ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only use V2 internally, so that's what I tested with originally.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we change it to v3 ?
there is no particular reason to use v2, it does not affect code coverage but it is "legacy"
so in my opinion new code/tests should use latest version


CompletableFuture<Void> blocker = new CompletableFuture<>();
bookieClient.setPreReadHook((bookie, _ledgerId, entryId) -> {
if (bookie.equals(bookie1)) {
return CompletableFuture.completedFuture(null);
} else {
return blocker;
}
});
CompletableFuture<DigestManager.RecoveryData> promise = new CompletableFuture<>();
ReadLastConfirmedOp op = new ReadLastConfirmedOp(
bookieClient, schedule,
digestManager, ledgerId, ensemble,
ledgerKey,
(rc, data) -> {
if (rc != BKException.Code.OK) {
promise.completeExceptionally(
BKException.create(rc));
} else {
promise.complete(data);
}
});
op.initiateWithFencing();

while (op.getNumResponsesPending() > 1) {
Thread.sleep(100);
}
blocker.completeExceptionally(
new BKException.BKBookieHandleNotAvailableException());
promise.get();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,6 @@ public void testCoverageSets() {
}
}
assertEquals("Should be no errors", 0, errors);

RoundRobinDistributionSchedule schedule = new RoundRobinDistributionSchedule(
5, 3, 5);
DistributionSchedule.QuorumCoverageSet covSet = schedule.getCoverageSet();
covSet.addBookie(0, BKException.Code.NoSuchLedgerExistsException);
covSet.addBookie(1, BKException.Code.NoSuchEntryException);
covSet.addBookie(2, BKException.Code.NoSuchLedgerExistsException);
covSet.addBookie(3, BKException.Code.UNINITIALIZED);
covSet.addBookie(4, BKException.Code.UNINITIALIZED);
assertFalse(covSet.checkCovered());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why dropping this test ?
can't we move this to assertTrue
and maybe add other cases ?

@ivankelly ivankelly Apr 8, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it's completely incorrect.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,13 @@ public void onFailure(Throwable throwable) {
op.submit();
};
// Read Last AddConfirmed
new ReadLastConfirmedOp(lh, clientCtx.getBookieClient(), lh.getCurrentEnsemble(), readLACCallback).initiate();
new ReadLastConfirmedOp(clientCtx.getBookieClient(),
lh.distributionSchedule,
lh.macManager,
lh.ledgerId,
lh.getCurrentEnsemble(),
lh.ledgerKey,
readLACCallback).initiate();
}

public void readLacs(final LedgerHandle lh, long eid,
Expand Down