You are viewing a plain text version of this content. The canonical link for it is here.
Posted to hdfs-commits@hadoop.apache.org by wa...@apache.org on 2014/07/30 20:39:49 UTC

svn commit: r1614735 - in /hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs: ./ src/main/java/org/apache/hadoop/hdfs/server/namenode/ src/test/java/org/apache/hadoop/hdfs/

Author: wang
Date: Wed Jul 30 18:39:48 2014
New Revision: 1614735

URL: http://svn.apache.org/r1614735
Log:
HDFS-6692. Add more HDFS encryption tests. (wang)

Added:
    hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/EncryptionFaultInjector.java   (with props)
Modified:
    hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/CHANGES-fs-encryption.txt
    hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java
    hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestEncryptionZones.java

Modified: hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/CHANGES-fs-encryption.txt
URL: http://svn.apache.org/viewvc/hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/CHANGES-fs-encryption.txt?rev=1614735&r1=1614734&r2=1614735&view=diff
==============================================================================
--- hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/CHANGES-fs-encryption.txt (original)
+++ hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/CHANGES-fs-encryption.txt Wed Jul 30 18:39:48 2014
@@ -70,6 +70,8 @@ fs-encryption (Unreleased)
 
     HDFS-6730. Create a .RAW extended attribute namespace. (clamb)
 
+    HDFS-6692. Add more HDFS encryption tests. (wang)
+
   OPTIMIZATIONS
 
   BUG FIXES

Added: hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/EncryptionFaultInjector.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/EncryptionFaultInjector.java?rev=1614735&view=auto
==============================================================================
--- hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/EncryptionFaultInjector.java (added)
+++ hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/EncryptionFaultInjector.java Wed Jul 30 18:39:48 2014
@@ -0,0 +1,22 @@
+package org.apache.hadoop.hdfs.server.namenode;
+
+import java.io.IOException;
+
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Used to inject certain faults for testing.
+ */
+public class EncryptionFaultInjector {
+  @VisibleForTesting
+  public static EncryptionFaultInjector instance =
+      new EncryptionFaultInjector();
+
+  @VisibleForTesting
+  public static EncryptionFaultInjector getInstance() {
+    return instance;
+  }
+
+  @VisibleForTesting
+  public void startFileAfterGenerateKey() throws IOException {}
+}

Propchange: hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/EncryptionFaultInjector.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java?rev=1614735&r1=1614734&r2=1614735&view=diff
==============================================================================
--- hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java (original)
+++ hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java Wed Jul 30 18:39:48 2014
@@ -2498,7 +2498,7 @@ public class FSNamesystem implements Nam
         // Generate EDEK if necessary while not holding the lock
         EncryptedKeyVersion edek =
             generateEncryptedDataEncryptionKey(ezKeyName);
-
+        EncryptionFaultInjector.getInstance().startFileAfterGenerateKey();
         // Try to create the file with the computed cipher suite and EDEK
         writeLock();
         try {

Modified: hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestEncryptionZones.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestEncryptionZones.java?rev=1614735&r1=1614734&r2=1614735&view=diff
==============================================================================
--- hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestEncryptionZones.java (original)
+++ hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestEncryptionZones.java Wed Jul 30 18:39:48 2014
@@ -23,6 +23,12 @@ import java.security.NoSuchAlgorithmExce
 import java.security.PrivilegedExceptionAction;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 
 import com.google.common.collect.Lists;
 import org.apache.hadoop.conf.Configuration;
@@ -42,6 +48,7 @@ import org.apache.hadoop.fs.permission.F
 import org.apache.hadoop.hdfs.client.HdfsAdmin;
 import org.apache.hadoop.hdfs.protocol.EncryptionZone;
 import org.apache.hadoop.hdfs.protocol.LocatedBlocks;
+import org.apache.hadoop.hdfs.server.namenode.EncryptionFaultInjector;
 import org.apache.hadoop.hdfs.server.namenode.EncryptionZoneManager;
 import org.apache.hadoop.security.AccessControlException;
 import org.apache.hadoop.security.UserGroupInformation;
@@ -103,6 +110,7 @@ public class TestEncryptionZones {
     if (cluster != null) {
       cluster.shutdown();
     }
+    EncryptionFaultInjector.instance = new EncryptionFaultInjector();
   }
 
   public void assertNumZones(final int numZones) throws IOException {
@@ -158,7 +166,8 @@ public class TestEncryptionZones {
     int numZones = 0;
 
     /* Test failure of create EZ on a directory that doesn't exist. */
-    final Path zone1 = new Path("/zone1");
+    final Path zoneParent = new Path("/zones");
+    final Path zone1 = new Path(zoneParent, "zone1");
     try {
       dfsAdmin.createEncryptionZone(zone1, TEST_KEY);
       fail("expected /test doesn't exist");
@@ -189,6 +198,14 @@ public class TestEncryptionZones {
       assertExceptionContains("already in an encryption zone", e);
     }
 
+    /* create EZ on parent of an EZ should fail */
+    try {
+      dfsAdmin.createEncryptionZone(zoneParent, TEST_KEY);
+      fail("EZ over an EZ");
+    } catch (IOException e) {
+      assertExceptionContains("encryption zone for a non-empty directory", e);
+    }
+
     /* create EZ on a folder with a folder fails */
     final Path notEmpty = new Path("/notEmpty");
     final Path notEmptyChild = new Path(notEmpty, "child");
@@ -449,6 +466,7 @@ public class TestEncryptionZones {
     final Configuration clusterConf = cluster.getConfiguration(0);
     clusterConf.set(KeyProviderFactory.KEY_PROVIDER_PATH, "");
     cluster.restartNameNode(true);
+    cluster.waitActive();
     /* Test failure of create EZ on a directory that doesn't exist. */
     final Path zone1 = new Path("/zone1");
     /* Normal creation of an EZ */
@@ -462,6 +480,169 @@ public class TestEncryptionZones {
     clusterConf.set(KeyProviderFactory.KEY_PROVIDER_PATH,
         JavaKeyStoreProvider.SCHEME_NAME + "://file" + testRootDir + "/test.jks"
     );
-    cluster.restartNameNode(true);
+    // Try listing EZs as well
+    List<EncryptionZone> zones = dfsAdmin.listEncryptionZones();
+    assertEquals("Expected no zones", 0, zones.size());
+  }
+
+  private class MyInjector extends EncryptionFaultInjector {
+    int generateCount;
+    CountDownLatch ready;
+    CountDownLatch wait;
+
+    public MyInjector() {
+      this.ready = new CountDownLatch(1);
+      this.wait = new CountDownLatch(1);
+    }
+
+    @Override
+    public void startFileAfterGenerateKey() throws IOException {
+      ready.countDown();
+      try {
+        wait.await();
+      } catch (InterruptedException e) {
+        throw new IOException(e);
+      }
+      generateCount++;
+    }
+  }
+
+  private class CreateFileTask implements Callable<Void> {
+    private FileSystemTestWrapper fsWrapper;
+    private Path name;
+
+    CreateFileTask(FileSystemTestWrapper fsWrapper, Path name) {
+      this.fsWrapper = fsWrapper;
+      this.name = name;
+    }
+
+    @Override
+    public Void call() throws Exception {
+      fsWrapper.createFile(name);
+      return null;
+    }
+  }
+
+  private class InjectFaultTask implements Callable<Void> {
+    final Path zone1 = new Path("/zone1");
+    final Path file = new Path(zone1, "file1");
+    final ExecutorService executor = Executors.newSingleThreadExecutor();
+
+    MyInjector injector;
+
+    @Override
+    public Void call() throws Exception {
+      // Set up the injector
+      injector = new MyInjector();
+      EncryptionFaultInjector.instance = injector;
+      Future<Void> future =
+          executor.submit(new CreateFileTask(fsWrapper, file));
+      injector.ready.await();
+      // Do the fault
+      doFault();
+      // Allow create to proceed
+      injector.wait.countDown();
+      future.get();
+      // Cleanup and postconditions
+      doCleanup();
+      return null;
+    }
+
+    public void doFault() throws Exception {}
+
+    public void doCleanup() throws Exception {}
+  }
+
+  /**
+   * Tests the retry logic in startFile. We release the lock while generating
+   * an EDEK, so tricky things can happen in the intervening time.
+   */
+  @Test(timeout = 120000)
+  public void testStartFileRetry() throws Exception {
+    final Path zone1 = new Path("/zone1");
+    final Path file = new Path(zone1, "file1");
+    fsWrapper.mkdir(zone1, FsPermission.getDirDefault(), true);
+    ExecutorService executor = Executors.newSingleThreadExecutor();
+
+    // Test when the parent directory becomes an EZ
+    executor.submit(new InjectFaultTask() {
+      @Override
+      public void doFault() throws Exception {
+        dfsAdmin.createEncryptionZone(zone1, TEST_KEY);
+      }
+      @Override
+      public void doCleanup() throws Exception {
+        assertEquals("Expected a startFile retry", 2, injector.generateCount);
+        fsWrapper.delete(file, false);
+      }
+    }).get();
+
+    // Test when the parent directory unbecomes an EZ
+    executor.submit(new InjectFaultTask() {
+      @Override
+      public void doFault() throws Exception {
+        fsWrapper.delete(zone1, true);
+      }
+      @Override
+      public void doCleanup() throws Exception {
+        assertEquals("Expected no startFile retries", 1, injector.generateCount);
+        fsWrapper.delete(file, false);
+      }
+    }).get();
+
+    // Test when the parent directory becomes a different EZ
+    fsWrapper.mkdir(zone1, FsPermission.getDirDefault(), true);
+    final String otherKey = "otherKey";
+    createKey(otherKey);
+    dfsAdmin.createEncryptionZone(zone1, TEST_KEY);
+
+    executor.submit(new InjectFaultTask() {
+      @Override
+      public void doFault() throws Exception {
+        fsWrapper.delete(zone1, true);
+        fsWrapper.mkdir(zone1, FsPermission.getDirDefault(), true);
+        dfsAdmin.createEncryptionZone(zone1, otherKey);
+      }
+      @Override
+      public void doCleanup() throws Exception {
+        assertEquals("Expected a startFile retry", 2, injector.generateCount);
+        fsWrapper.delete(zone1, true);
+      }
+    }).get();
+
+    // Test that the retry limit leads to an error
+    fsWrapper.mkdir(zone1, FsPermission.getDirDefault(), true);
+    final String anotherKey = "anotherKey";
+    createKey(anotherKey);
+    dfsAdmin.createEncryptionZone(zone1, anotherKey);
+    String keyToUse = otherKey;
+
+    MyInjector injector = new MyInjector();
+    EncryptionFaultInjector.instance = injector;
+    Future<?> future = executor.submit(new CreateFileTask(fsWrapper, file));
+
+    // Flip-flop between two EZs to repeatedly fail
+    for (int i=0; i<10; i++) {
+      injector.ready.await();
+      fsWrapper.delete(zone1, true);
+      fsWrapper.mkdir(zone1, FsPermission.getDirDefault(), true);
+      dfsAdmin.createEncryptionZone(zone1, keyToUse);
+      if (keyToUse == otherKey) {
+        keyToUse = anotherKey;
+      } else {
+        keyToUse = otherKey;
+      }
+      injector.wait.countDown();
+      injector = new MyInjector();
+      EncryptionFaultInjector.instance = injector;
+    }
+    try {
+      future.get();
+      fail("Expected exception from too many retries");
+    } catch (ExecutionException e) {
+      assertExceptionContains(
+          "Too many retries because of encryption zone operations",
+          e.getCause());
+    }
   }
 }