This is an automated email from the ASF dual-hosted git repository.

umamahesh pushed a commit to branch HDDS-3816-ec
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/HDDS-3816-ec by this push:
     new 22e3482  HDDS-6258. EC: Read with stopped but not dead nodes gives 
IllegalStateException rather than InsufficientNodesException (#3048)
22e3482 is described below

commit 22e34820cf228b23cc648c5833b63c8ed3523481
Author: Stephen O'Donnell <[email protected]>
AuthorDate: Thu Feb 10 07:42:09 2022 +0000

    HDDS-6258. EC: Read with stopped but not dead nodes gives 
IllegalStateException rather than InsufficientNodesException (#3048)
---
 .../hadoop/ozone/client/io/ECBlockInputStream.java | 15 ++++++--
 .../ozone/client/io/ECBlockInputStreamProxy.java   | 16 ++++----
 .../io/ECBlockReconstructedStripeInputStream.java  |  2 +-
 .../ozone/client/rpc/read/ECStreamTestUtil.java    |  9 +++++
 .../client/rpc/read/TestECBlockInputStream.java    | 23 +++++++++++
 .../rpc/read/TestECBlockInputStreamProxy.java      | 16 +++++---
 .../TestECBlockReconstructedStripeInputStream.java | 45 ++++++++++++++++++++++
 7 files changed, 109 insertions(+), 17 deletions(-)

diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStream.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStream.java
index 5212eea..b71d475 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStream.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStream.java
@@ -78,9 +78,18 @@ public class ECBlockInputStream extends 
BlockExtendedInputStream {
     return stripeSize;
   }
 
-  protected int availableDataLocations() {
+  /**
+   * Returns the number of available data locations, taking account of the
+   * expected number of locations. Eg, if the block is less than 1 EC chunk,
+   * we only expect 1 data location. If it is between 1 and 2 chunks, we expect
+   * there to be 2 locations, and so on.
+   * @param expectedLocations The maximum number of allowed data locations,
+   *                          depending on the block size.
+   * @return The number of available data locations.
+   */
+  protected int availableDataLocations(int expectedLocations) {
     int count = 0;
-    for (int i = 0; i < repConfig.getData(); i++) {
+    for (int i = 0; i < repConfig.getData() && i < expectedLocations; i++) {
       if (dataLocations[i] != null) {
         count++;
       }
@@ -127,7 +136,7 @@ public class ECBlockInputStream extends 
BlockExtendedInputStream {
     // must have all data_num locations.
     // We only consider data locations here.
     int expectedDataBlocks = calculateExpectedDataBlocks(repConfig);
-    return expectedDataBlocks == availableDataLocations();
+    return expectedDataBlocks == availableDataLocations(expectedDataBlocks);
   }
 
   protected int calculateExpectedDataBlocks(ECReplicationConfig rConfig) {
diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStreamProxy.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStreamProxy.java
index 2f7680e..ecde9c6 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStreamProxy.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStreamProxy.java
@@ -74,19 +74,21 @@ public class ECBlockInputStreamProxy extends 
BlockExtendedInputStream {
   }
 
   /**
-   * From ECReplicationConfig and Pipeline with the block locations and 
location
-   * indexes, determine the number of data locations available.
-   * @param repConfig The EC Replication Config
+   * From the Pipeline and expected number of locations, determine the number
+   * of data locations available.
    * @param pipeline The pipeline for the data block, givings its locations and
    *                 the index of each location.
+   * @param expectedLocs The number of locations we expect for the block to 
have
+   *                     based on its block length and replication config. The
+   *                     max value should be the repConfig data number.
    * @return The number of locations available
    */
-  public static int availableDataLocations(ECReplicationConfig repConfig,
-      Pipeline pipeline) {
+  public static int availableDataLocations(Pipeline pipeline,
+      int expectedLocs) {
     Set<Integer> locations = new HashSet<>();
     for (DatanodeDetails dn : pipeline.getNodes()) {
       int index = pipeline.getReplicaIndex(dn);
-      if (index > 0 && index <= repConfig.getData()) {
+      if (index > 0 && index <= expectedLocs) {
         locations.add(index);
       }
     }
@@ -110,7 +112,7 @@ public class ECBlockInputStreamProxy extends 
BlockExtendedInputStream {
 
   private synchronized void setReaderType() {
     int expected = expectedDataLocations(repConfig, getLength());
-    int available = availableDataLocations(repConfig, blockInfo.getPipeline());
+    int available = availableDataLocations(blockInfo.getPipeline(), expected);
     reconstructionReader = available < expected;
   }
 
diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockReconstructedStripeInputStream.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockReconstructedStripeInputStream.java
index e3fa0a7..26fbd15 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockReconstructedStripeInputStream.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockReconstructedStripeInputStream.java
@@ -558,7 +558,7 @@ public class ECBlockReconstructedStripeInputStream extends 
ECBlockInputStream {
     ECReplicationConfig repConfig = getRepConfig();
     int expectedDataBlocks = calculateExpectedDataBlocks(repConfig);
     int availableLocations =
-        availableDataLocations() + availableParityLocations();
+        availableDataLocations(expectedDataBlocks) + 
availableParityLocations();
     int paddedLocations = repConfig.getData() - expectedDataBlocks;
     int failedLocations = failedDataIndexes.size();
 
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/read/ECStreamTestUtil.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/read/ECStreamTestUtil.java
index c2f51c2..f1f4a44 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/read/ECStreamTestUtil.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/read/ECStreamTestUtil.java
@@ -221,6 +221,8 @@ public final class ECStreamTestUtil {
 
     private List<TestBlockInputStream> blockStreams = new ArrayList<>();
     private List<ByteBuffer> blockStreamData;
+    // List of EC indexes that should fail immediately on read
+    private List<Integer> failIndexes = new ArrayList<>();
 
     private Pipeline currentPipeline;
 
@@ -236,6 +238,10 @@ public final class ECStreamTestUtil {
       this.currentPipeline = pipeline;
     }
 
+    public void setFailIndexes(List<Integer> fail) {
+      failIndexes.addAll(fail);
+    }
+
     public BlockExtendedInputStream create(ReplicationConfig repConfig,
         OmKeyLocationInfo blockInfo, Pipeline pipeline,
         Token<OzoneBlockTokenIdentifier> token, boolean verifyChecksum,
@@ -246,6 +252,9 @@ public final class ECStreamTestUtil {
       TestBlockInputStream stream = new TestBlockInputStream(
           blockInfo.getBlockID(), blockInfo.getLength(),
           blockStreamData.get(repInd - 1), repInd);
+      if (failIndexes.contains(repInd)) {
+        stream.setShouldError(true);
+      }
       blockStreams.add(stream);
       return stream;
     }
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/read/TestECBlockInputStream.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/read/TestECBlockInputStream.java
index d681c61..60a3a85 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/read/TestECBlockInputStream.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/read/TestECBlockInputStream.java
@@ -230,6 +230,29 @@ public class TestECBlockInputStream {
     }
   }
 
+  /**
+   * This test is to ensure we can read a small key of 1 chunk or less when 
only
+   * the first replica index is available.
+   */
+  @Test
+  public void testSimpleReadUnderOneChunk() throws IOException {
+    OmKeyLocationInfo keyInfo =
+        ECStreamTestUtil.createKeyInfo(repConfig, 1, ONEMB);
+    try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig,
+        keyInfo, true, null, null, streamFactory)) {
+
+      ByteBuffer buf = ByteBuffer.allocate(100);
+
+      int read = ecb.read(buf);
+      Assert.assertEquals(100, read);
+      validateBufferContents(buf, 0, 100, (byte) 0);
+      Assert.assertEquals(100, ecb.getPos());
+    }
+    for (TestBlockInputStream s : streamFactory.getBlockStreams()) {
+      Assert.assertTrue(s.isClosed());
+    }
+  }
+
   @Test
   public void testReadPastEOF() throws IOException {
     OmKeyLocationInfo keyInfo =
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/read/TestECBlockInputStreamProxy.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/read/TestECBlockInputStreamProxy.java
index f9b5f38..160ded4 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/read/TestECBlockInputStreamProxy.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/read/TestECBlockInputStreamProxy.java
@@ -92,21 +92,25 @@ public class TestECBlockInputStreamProxy {
   @Test
   public void testAvailableDataLocations() {
     Map<DatanodeDetails, Integer> dnMap =
-        ECStreamTestUtil.createIndexMap(1, 2, 4, 5);
+        ECStreamTestUtil.createIndexMap(1, 2, 3, 4, 5);
     OmKeyLocationInfo blockInfo =
         ECStreamTestUtil.createKeyInfo(repConfig, 1024, dnMap);
+    Assert.assertEquals(1, ECBlockInputStreamProxy.availableDataLocations(
+        blockInfo.getPipeline(), 1));
     Assert.assertEquals(2, ECBlockInputStreamProxy.availableDataLocations(
-        repConfig, blockInfo.getPipeline()));
+        blockInfo.getPipeline(), 2));
+    Assert.assertEquals(3, ECBlockInputStreamProxy.availableDataLocations(
+        blockInfo.getPipeline(), 3));
 
     dnMap = ECStreamTestUtil.createIndexMap(1, 4, 5);
     blockInfo = ECStreamTestUtil.createKeyInfo(repConfig, 1024, dnMap);
     Assert.assertEquals(1, ECBlockInputStreamProxy.availableDataLocations(
-        repConfig, blockInfo.getPipeline()));
+        blockInfo.getPipeline(), 3));
 
-    dnMap = ECStreamTestUtil.createIndexMap(1, 2, 3, 4, 5);
+    dnMap = ECStreamTestUtil.createIndexMap(2, 3, 4, 5);
     blockInfo = ECStreamTestUtil.createKeyInfo(repConfig, 1024, dnMap);
-    Assert.assertEquals(3, ECBlockInputStreamProxy.availableDataLocations(
-        repConfig, blockInfo.getPipeline()));
+    Assert.assertEquals(0, ECBlockInputStreamProxy.availableDataLocations(
+        blockInfo.getPipeline(), 1));
   }
 
   @Test
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/read/TestECBlockReconstructedStripeInputStream.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/read/TestECBlockReconstructedStripeInputStream.java
index 079e944..ec9bbdc 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/read/TestECBlockReconstructedStripeInputStream.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/read/TestECBlockReconstructedStripeInputStream.java
@@ -117,6 +117,15 @@ public class TestECBlockReconstructedStripeInputStream {
       ((ECBlockReconstructedStripeInputStream)ecb).addFailedDatanodes(failed);
       Assert.assertFalse(ecb.hasSufficientLocations());
     }
+
+    // One chunk, indexes 2 and 3 are padding, but still reported in the
+    // container list. The other locations are missing so we should have
+    // insufficient locations.
+    dnMap = ECStreamTestUtil.createIndexMap(2, 3);
+    keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, ONEMB, dnMap);
+    try (ECBlockInputStream ecb = createInputStream(keyInfo)) {
+      Assert.assertFalse(ecb.hasSufficientLocations());
+    }
   }
 
   @Test
@@ -528,6 +537,42 @@ public class TestECBlockReconstructedStripeInputStream {
     }
   }
 
+  @Test(expected=InsufficientLocationsException.class)
+  public void testAllLocationsFailOnFirstRead() throws IOException {
+    // This test simulates stale nodes. When the nodes are stale, but not yet
+    // dead, the locations will still be given to the client and it will try to
+    // read them, but the read will always fail.
+    // Additionally, if the key is small (less than 2 EC chunks), the locations
+    // for the indexes which are all padding will be returned to the client and
+    // this can confuse the "sufficient locations" check, resulting in a 
strange
+    // error when selecting parity indexes (HDDS-6258)
+    int chunkSize = repConfig.getEcChunkSize();
+    int partialStripeSize = chunkSize;
+    int blockLength = partialStripeSize;
+    ByteBuffer[] dataBufs = allocateBuffers(repConfig.getData(), chunkSize);
+    ECStreamTestUtil
+        .randomFill(dataBufs, repConfig.getEcChunkSize(), dataGen, 
blockLength);
+    ByteBuffer[] parity = generateParity(dataBufs, repConfig);
+
+    streamFactory = new TestBlockInputStreamFactory();
+    addDataStreamsToFactory(dataBufs, parity);
+    // Fail all the indexes containing data on their first read.
+    streamFactory.setFailIndexes(indexesToList(1, 4, 5));
+    // The locations contain the padded indexes, as will often be the case
+    // when containers are reported by SCM.
+    Map<DatanodeDetails, Integer> dnMap =
+          ECStreamTestUtil.createIndexMap(1, 2, 3, 4, 5);
+    OmKeyLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig,
+        blockLength, dnMap);
+    streamFactory.setCurrentPipeline(keyInfo.getPipeline());
+
+    ByteBuffer[] bufs = allocateByteBuffers(repConfig);
+    try (ECBlockReconstructedStripeInputStream ecb =
+             createInputStream(keyInfo)) {
+      ecb.readStripe(bufs);
+    }
+  }
+
   @Test
   public void testFailedLocationsAreNotRead() throws IOException {
     // Generate the input data for 3 full stripes and generate the parity.

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to