Skip to content

Commit 6d0be38

Browse files
authored
HDDS-15052. Revert HDDS-14040. Ozone client hang for data write in failure scenario (#10140)
1 parent 6537759 commit 6d0be38

3 files changed

Lines changed: 26 additions & 96 deletions

File tree

hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/transport/server/ratis/ContainerStateMachine.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -582,8 +582,7 @@ private CompletableFuture<Message> writeStateMachineData(
582582
try {
583583
validateLongRunningWrite();
584584
} catch (StorageContainerException e) {
585-
ContainerCommandResponseProto result = ContainerUtils.logAndReturnError(LOG, e, requestProto);
586-
return CompletableFuture.completedFuture(result::toByteString);
585+
return completeExceptionally(e);
587586
}
588587
final WriteChunkRequestProto write = requestProto.getWriteChunk();
589588
RaftServer server = ratisServer.getServer();
@@ -632,11 +631,8 @@ private CompletableFuture<Message> writeStateMachineData(
632631
// see the stateMachine is marked unhealthy by other parallel thread
633632
unhealthyContainers.add(write.getBlockID().getContainerID());
634633
stateMachineHealthy.set(false);
635-
StorageContainerException sce = new StorageContainerException("Failed to write chunk data",
636-
e, ContainerProtos.Result.CONTAINER_INTERNAL_ERROR);
637-
ContainerCommandResponseProto result = ContainerUtils.logAndReturnError(LOG, sce, requestProto);
638-
raftFuture.complete(result::toByteString);
639-
return result;
634+
raftFuture.completeExceptionally(e);
635+
throw e;
640636
} finally {
641637
// Remove the future once it finishes execution from the
642638
writeChunkFutureMap.remove(entryIndex);
@@ -661,6 +657,8 @@ private void handleCommandResult(ContainerCommandRequestProto requestProto, long
661657
// After concurrent flushes are allowed on the same key, chunk file inconsistencies can happen and
662658
// that should not crash the pipeline.
663659
&& r.getResult() != ContainerProtos.Result.CHUNK_FILE_INCONSISTENCY) {
660+
StorageContainerException sce =
661+
new StorageContainerException(r.getMessage(), r.getResult());
664662
LOG.error(getGroupId() + ": writeChunk writeStateMachineData failed: blockId" +
665663
write.getBlockID() + " logIndex " + entryIndex + " chunkName " +
666664
write.getChunkData().getChunkName() + " Error message: " +
@@ -671,7 +669,7 @@ private void handleCommandResult(ContainerCommandRequestProto requestProto, long
671669
// handling the entry for the write chunk in cache.
672670
stateMachineHealthy.set(false);
673671
unhealthyContainers.add(write.getBlockID().getContainerID());
674-
raftFuture.complete(r::toByteString);
672+
raftFuture.completeExceptionally(sce);
675673
} else {
676674
metrics.incNumBytesWrittenCount(
677675
requestProto.getWriteChunk().getChunkData().getLen());

hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/transport/server/ratis/TestContainerStateMachine.java

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
2323
import static org.junit.jupiter.api.Assertions.assertNotNull;
2424
import static org.junit.jupiter.api.Assertions.assertNull;
25-
import static org.junit.jupiter.api.Assertions.assertTrue;
2625
import static org.mockito.ArgumentMatchers.any;
2726
import static org.mockito.Mockito.doAnswer;
2827
import static org.mockito.Mockito.mock;
@@ -58,7 +57,6 @@
5857
import org.apache.ratis.server.RaftServer;
5958
import org.apache.ratis.statemachine.TransactionContext;
6059
import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
61-
import org.apache.ratis.thirdparty.com.google.protobuf.InvalidProtocolBufferException;
6260
import org.junit.jupiter.api.AfterAll;
6361
import org.junit.jupiter.api.AfterEach;
6462
import org.junit.jupiter.api.BeforeEach;
@@ -122,8 +120,7 @@ public void shutdown() {
122120

123121
@ParameterizedTest
124122
@ValueSource(booleans = {true, false})
125-
public void testWriteFailure(boolean failWithException)
126-
throws ExecutionException, InterruptedException, InvalidProtocolBufferException {
123+
public void testWriteFailure(boolean failWithException) throws ExecutionException, InterruptedException {
127124
RaftProtos.LogEntryProto entry = mock(RaftProtos.LogEntryProto.class);
128125
when(entry.getTerm()).thenReturn(1L);
129126
when(entry.getIndex()).thenReturn(1L);
@@ -137,28 +134,23 @@ public void testWriteFailure(boolean failWithException)
137134
setUpMockDispatcherReturn(failWithException);
138135
setUpMockRequestProtoReturn(context, 1, 1);
139136

140-
Message message = stateMachine.write(entry, trx).get();
137+
ThrowableCatcher catcher = new ThrowableCatcher();
138+
139+
stateMachine.write(entry, trx).exceptionally(catcher.asSetter()).get();
141140
verify(dispatcher, times(1)).dispatch(any(ContainerProtos.ContainerCommandRequestProto.class),
142141
any(DispatcherContext.class));
143142
reset(dispatcher);
144-
ContainerProtos.ContainerCommandResponseProto containerCommandResponseProto =
145-
ContainerProtos.ContainerCommandResponseProto.parseFrom(message.getContent());
146-
if (failWithException) {
147-
assertEquals("Failed to write chunk data", containerCommandResponseProto.getMessage());
148-
assertEquals(ContainerProtos.Result.CONTAINER_INTERNAL_ERROR, containerCommandResponseProto.getResult());
149-
} else {
150-
// If dispatcher returned failure response, the state machine should propagate the same failure.
151-
assertEquals(ContainerProtos.Result.CONTAINER_INTERNAL_ERROR, containerCommandResponseProto.getResult());
152-
}
143+
assertNotNull(catcher.getReceived());
144+
assertResults(failWithException, catcher.getCaught());
153145

154146
// Writing data to another container(containerId 2) should also fail.
155147
setUpMockRequestProtoReturn(context, 2, 1);
156-
message = stateMachine.write(entryNext, trx).get();
148+
stateMachine.write(entryNext, trx).exceptionally(catcher.asSetter()).get();
157149
verify(dispatcher, times(0)).dispatch(any(ContainerProtos.ContainerCommandRequestProto.class),
158150
any(DispatcherContext.class));
159-
containerCommandResponseProto
160-
= ContainerProtos.ContainerCommandResponseProto.parseFrom(message.getContent());
161-
assertTrue(containerCommandResponseProto.getMessage().contains("failed, stopping all writes to container"));
151+
assertInstanceOf(StorageContainerException.class, catcher.getReceived());
152+
StorageContainerException sce = (StorageContainerException) catcher.getReceived();
153+
assertEquals(ContainerProtos.Result.CONTAINER_UNHEALTHY, sce.getResult());
162154
}
163155

164156
@ParameterizedTest
@@ -232,17 +224,20 @@ public void testWriteTimout() throws Exception {
232224
}).when(dispatcher).dispatch(any(), any());
233225

234226
setUpMockRequestProtoReturn(context, 1, 1);
227+
ThrowableCatcher catcher = new ThrowableCatcher();
235228

236229
CompletableFuture<Message> firstWrite = stateMachine.write(entry, trx);
237230
Thread.sleep(2000);
238231
CompletableFuture<Message> secondWrite = stateMachine.write(entryNext, trx);
239-
ContainerProtos.ContainerCommandResponseProto containerCommandResponseProto
240-
= ContainerProtos.ContainerCommandResponseProto.parseFrom(firstWrite.get().getContent());
241-
assertEquals("Failed to write chunk data", containerCommandResponseProto.getMessage());
232+
firstWrite.exceptionally(catcher.asSetter()).get();
233+
assertNotNull(catcher.getCaught());
234+
assertInstanceOf(InterruptedException.class, catcher.getReceived());
242235

243-
containerCommandResponseProto
244-
= ContainerProtos.ContainerCommandResponseProto.parseFrom(secondWrite.get().getContent());
245-
assertEquals(ContainerProtos.Result.CONTAINER_INTERNAL_ERROR, containerCommandResponseProto.getResult());
236+
secondWrite.exceptionally(catcher.asSetter()).get();
237+
assertNotNull(catcher.getReceived());
238+
assertInstanceOf(StorageContainerException.class, catcher.getReceived());
239+
StorageContainerException sce = (StorageContainerException) catcher.getReceived();
240+
assertEquals(ContainerProtos.Result.CONTAINER_INTERNAL_ERROR, sce.getResult());
246241
}
247242

248243
private void setUpMockDispatcherReturn(boolean failWithException) {

hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestContainerStateMachineFailures.java

Lines changed: 1 addition & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
import java.util.concurrent.atomic.AtomicInteger;
5454
import org.apache.commons.io.FileUtils;
5555
import org.apache.commons.io.IOUtils;
56-
import org.apache.commons.lang3.tuple.Pair;
5756
import org.apache.hadoop.fs.FileUtil;
5857
import org.apache.hadoop.hdds.HddsUtils;
5958
import org.apache.hadoop.hdds.client.ReplicationConfig;
@@ -88,21 +87,17 @@
8887
import org.apache.hadoop.ozone.container.TestHelper;
8988
import org.apache.hadoop.ozone.container.common.impl.ContainerData;
9089
import org.apache.hadoop.ozone.container.common.impl.ContainerDataYaml;
91-
import org.apache.hadoop.ozone.container.common.impl.ContainerSet;
9290
import org.apache.hadoop.ozone.container.common.impl.HddsDispatcher;
9391
import org.apache.hadoop.ozone.container.common.interfaces.Container;
9492
import org.apache.hadoop.ozone.container.common.transport.server.ratis.ContainerStateMachine;
9593
import org.apache.hadoop.ozone.container.common.transport.server.ratis.XceiverServerRatis;
96-
import org.apache.hadoop.ozone.container.common.volume.StorageVolume;
97-
import org.apache.hadoop.ozone.container.common.volume.VolumeUsage;
9894
import org.apache.hadoop.ozone.container.keyvalue.KeyValueContainerData;
9995
import org.apache.hadoop.ozone.container.ozoneimpl.OzoneContainer;
10096
import org.apache.hadoop.ozone.om.helpers.OmKeyArgs;
10197
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
10298
import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo;
10399
import org.apache.hadoop.ozone.protocol.commands.CloseContainerCommand;
104100
import org.apache.hadoop.ozone.protocol.commands.SCMCommand;
105-
import org.apache.hadoop.util.Time;
106101
import org.apache.ozone.test.GenericTestUtils;
107102
import org.apache.ozone.test.LambdaTestUtils;
108103
import org.apache.ozone.test.tag.Flaky;
@@ -140,7 +135,7 @@ public static void init() throws Exception {
140135
TimeUnit.MILLISECONDS);
141136
conf.setTimeDuration(HDDS_COMMAND_STATUS_REPORT_INTERVAL, 200,
142137
TimeUnit.MILLISECONDS);
143-
conf.setTimeDuration(HDDS_PIPELINE_REPORT_INTERVAL, 2000,
138+
conf.setTimeDuration(HDDS_PIPELINE_REPORT_INTERVAL, 200,
144139
TimeUnit.MILLISECONDS);
145140
conf.setTimeDuration(HDDS_HEARTBEAT_INTERVAL, 200, TimeUnit.MILLISECONDS);
146141
conf.setTimeDuration(OZONE_SCM_STALENODE_INTERVAL, 30, TimeUnit.SECONDS);
@@ -770,14 +765,9 @@ void testContainerStateMachineSingleFailureRetry()
770765
assertEquals(1, locationInfoList.size());
771766

772767
OmKeyLocationInfo omKeyLocationInfo = locationInfoList.get(0);
773-
ContainerSet containerSet = cluster.getHddsDatanode(omKeyLocationInfo.getPipeline().getLeaderNode())
774-
.getDatanodeStateMachine().getContainer().getContainerSet();
775768

776769
induceFollowerFailure(omKeyLocationInfo, 2);
777770
key.flush();
778-
// wait for container close for failure in flush for both followers applyTransaction failure
779-
GenericTestUtils.waitFor(() -> containerSet.getContainer(omKeyLocationInfo.getContainerID()).getContainerData()
780-
.getState().equals(ContainerProtos.ContainerDataProto.State.CLOSED), 100, 30000);
781771
key.write("ratis".getBytes(UTF_8));
782772
key.flush();
783773
}
@@ -817,59 +807,6 @@ void testContainerStateMachineDualFailureRetry()
817807
validateData("ratis1", 2, "ratisratisratisratis");
818808
}
819809

820-
@Test
821-
void testContainerStateMachineAllNodeFailure()
822-
throws Exception {
823-
// mark all dn volume as full to induce failure
824-
List<Pair<VolumeUsage, Long>> increasedVolumeSpace = new ArrayList<>();
825-
cluster.getHddsDatanodes().forEach(
826-
dn -> {
827-
List<StorageVolume> volumesList = dn.getDatanodeStateMachine().getContainer().getVolumeSet().getVolumesList();
828-
volumesList.forEach(sv -> {
829-
final VolumeUsage volumeUsage = sv.getVolumeUsage();
830-
if (volumeUsage != null) {
831-
final long available = sv.getCurrentUsage().getAvailable();
832-
increasedVolumeSpace.add(Pair.of(volumeUsage, available));
833-
volumeUsage.incrementUsedSpace(available);
834-
}
835-
});
836-
}
837-
);
838-
839-
long startTime = Time.monotonicNow();
840-
ReplicationConfig replicationConfig = ReplicationConfig.fromTypeAndFactor(ReplicationType.RATIS,
841-
ReplicationFactor.THREE);
842-
try (OzoneOutputStream key = objectStore.getVolume(volumeName).getBucket(bucketName).createKey(
843-
"testkey1", 1024, replicationConfig, new HashMap<>())) {
844-
845-
key.write("ratis".getBytes(UTF_8));
846-
key.flush();
847-
fail();
848-
} catch (IOException ex) {
849-
assertThat(ex.getMessage()).contains("Retry request failed. retries get failed due to exceeded" +
850-
" maximum allowed retries number: 5");
851-
} finally {
852-
increasedVolumeSpace.forEach(e -> e.getLeft().decrementUsedSpace(e.getRight()));
853-
// test execution is less than 2 sec but to be safe putting 30 sec as without fix, taking more than 60 sec
854-
assertThat(Time.monotonicNow() - startTime)
855-
.describedAs("Operation took longer than expected")
856-
.isLessThan(30000);
857-
}
858-
859-
// previous pipeline gets closed due to disk full failure, so created a new pipeline and write should succeed,
860-
// and this ensures later test case can pass (should not fail due to pipeline unavailability as timeout is 200ms
861-
// for pipeline creation which can fail in testcase later on)
862-
Pipeline pipeline = cluster.getStorageContainerManager().getPipelineManager().createPipeline(replicationConfig);
863-
cluster.getStorageContainerManager().getPipelineManager().waitPipelineReady(pipeline.getId(), 60000);
864-
865-
try (OzoneOutputStream key = objectStore.getVolume(volumeName).getBucket(bucketName).createKey(
866-
"testkey2", 1024, replicationConfig, new HashMap<>())) {
867-
868-
key.write("ratis".getBytes(UTF_8));
869-
key.flush();
870-
}
871-
}
872-
873810
private void induceFollowerFailure(OmKeyLocationInfo omKeyLocationInfo,
874811
int failureCount) {
875812
DatanodeID leader = omKeyLocationInfo.getPipeline().getLeaderId();

0 commit comments

Comments
 (0)