001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.io.asyncfs;
019
020import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_CLIENT_SOCKET_TIMEOUT_KEY;
021import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_DATA_ENCRYPTION_ALGORITHM_KEY;
022import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_ENCRYPT_DATA_TRANSFER_CIPHER_SUITES_KEY;
023import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_ENCRYPT_DATA_TRANSFER_KEY;
024
025import java.io.File;
026import java.io.IOException;
027import java.lang.reflect.Method;
028import java.net.BindException;
029import java.net.URI;
030import java.util.ArrayList;
031import java.util.Arrays;
032import java.util.List;
033import java.util.Properties;
034import java.util.concurrent.ExecutionException;
035import org.apache.commons.io.FileUtils;
036import org.apache.commons.lang3.StringUtils;
037import org.apache.hadoop.conf.Configuration;
038import org.apache.hadoop.crypto.CipherSuite;
039import org.apache.hadoop.crypto.key.KeyProvider;
040import org.apache.hadoop.crypto.key.KeyProviderFactory;
041import org.apache.hadoop.fs.Path;
042import org.apache.hadoop.hbase.HBaseClassTestRule;
043import org.apache.hadoop.hbase.io.asyncfs.monitor.StreamSlowMonitor;
044import org.apache.hadoop.hbase.security.HBaseKerberosUtils;
045import org.apache.hadoop.hbase.security.SecurityConstants;
046import org.apache.hadoop.hbase.testclassification.LargeTests;
047import org.apache.hadoop.hbase.testclassification.MiscTests;
048import org.apache.hadoop.hdfs.DistributedFileSystem;
049import org.apache.hadoop.minikdc.MiniKdc;
050import org.apache.hadoop.security.UserGroupInformation;
051import org.junit.After;
052import org.junit.AfterClass;
053import org.junit.Before;
054import org.junit.BeforeClass;
055import org.junit.ClassRule;
056import org.junit.Rule;
057import org.junit.Test;
058import org.junit.experimental.categories.Category;
059import org.junit.rules.TestName;
060import org.junit.runner.RunWith;
061import org.junit.runners.Parameterized;
062import org.junit.runners.Parameterized.Parameter;
063import org.junit.runners.Parameterized.Parameters;
064import org.slf4j.Logger;
065import org.slf4j.LoggerFactory;
066
067import org.apache.hbase.thirdparty.io.netty.channel.Channel;
068import org.apache.hbase.thirdparty.io.netty.channel.EventLoop;
069import org.apache.hbase.thirdparty.io.netty.channel.EventLoopGroup;
070import org.apache.hbase.thirdparty.io.netty.channel.nio.NioEventLoopGroup;
071import org.apache.hbase.thirdparty.io.netty.channel.socket.nio.NioSocketChannel;
072
073@RunWith(Parameterized.class)
074@Category({ MiscTests.class, LargeTests.class })
075public class TestSaslFanOutOneBlockAsyncDFSOutput extends AsyncFSTestBase {
076
077  private static final Logger LOG =
078    LoggerFactory.getLogger(TestSaslFanOutOneBlockAsyncDFSOutput.class);
079
080  @ClassRule
081  public static final HBaseClassTestRule CLASS_RULE =
082    HBaseClassTestRule.forClass(TestSaslFanOutOneBlockAsyncDFSOutput.class);
083
084  private static DistributedFileSystem FS;
085
086  private static EventLoopGroup EVENT_LOOP_GROUP;
087
088  private static Class<? extends Channel> CHANNEL_CLASS;
089
090  private static int READ_TIMEOUT_MS = 200000;
091
092  private static final File KEYTAB_FILE = new File(UTIL.getDataTestDir("keytab").toUri().getPath());
093
094  private static MiniKdc KDC;
095
096  private static String HOST = "localhost";
097
098  private static String USERNAME;
099
100  private static String PRINCIPAL;
101
102  private static String HTTP_PRINCIPAL;
103
104  private static String TEST_KEY_NAME = "test_key";
105
106  private static StreamSlowMonitor MONITOR;
107
108  @Rule
109  public TestName name = new TestName();
110
111  @Parameter(0)
112  public String protection;
113
114  @Parameter(1)
115  public String encryptionAlgorithm;
116
117  @Parameter(2)
118  public String cipherSuite;
119
120  @Parameters(name = "{index}: protection={0}, encryption={1}, cipherSuite={2}")
121  public static Iterable<Object[]> data() {
122    List<Object[]> params = new ArrayList<>();
123    for (String protection : Arrays.asList("authentication", "integrity", "privacy")) {
124      for (String encryptionAlgorithm : Arrays.asList("", "3des", "rc4")) {
125        for (String cipherSuite : Arrays.asList("", CipherSuite.AES_CTR_NOPADDING.getName())) {
126          params.add(new Object[] { protection, encryptionAlgorithm, cipherSuite });
127        }
128      }
129    }
130    return params;
131  }
132
133  private static void setUpKeyProvider(Configuration conf) throws Exception {
134    URI keyProviderUri =
135      new URI("jceks://file" + UTIL.getDataTestDir("test.jks").toUri().toString());
136    conf.set("dfs.encryption.key.provider.uri", keyProviderUri.toString());
137    KeyProvider keyProvider = KeyProviderFactory.get(keyProviderUri, conf);
138    keyProvider.createKey(TEST_KEY_NAME, KeyProvider.options(conf));
139    keyProvider.flush();
140    keyProvider.close();
141  }
142
143  /**
144   * Sets up {@link MiniKdc} for testing security. Uses {@link HBaseKerberosUtils} to set the given
145   * keytab file as {@link HBaseKerberosUtils#KRB_KEYTAB_FILE}.
146   */
147  private static MiniKdc setupMiniKdc(File keytabFile) throws Exception {
148    Properties conf = MiniKdc.createConf();
149    conf.put(MiniKdc.DEBUG, true);
150    MiniKdc kdc = null;
151    File dir = null;
152    // There is time lag between selecting a port and trying to bind with it. It's possible that
153    // another service captures the port in between which'll result in BindException.
154    boolean bindException;
155    int numTries = 0;
156    do {
157      try {
158        bindException = false;
159        dir = new File(UTIL.getDataTestDir("kdc").toUri().getPath());
160        kdc = new MiniKdc(conf, dir);
161        kdc.start();
162      } catch (BindException e) {
163        FileUtils.deleteDirectory(dir); // clean directory
164        numTries++;
165        if (numTries == 3) {
166          LOG.error("Failed setting up MiniKDC. Tried " + numTries + " times.");
167          throw e;
168        }
169        LOG.error("BindException encountered when setting up MiniKdc. Trying again.");
170        bindException = true;
171      }
172    } while (bindException);
173    System.setProperty(SecurityConstants.REGIONSERVER_KRB_KEYTAB_FILE,
174      keytabFile.getAbsolutePath());
175    return kdc;
176  }
177
178  @BeforeClass
179  public static void setUpBeforeClass() throws Exception {
180    EVENT_LOOP_GROUP = new NioEventLoopGroup();
181    CHANNEL_CLASS = NioSocketChannel.class;
182    UTIL.getConfiguration().setInt(DFS_CLIENT_SOCKET_TIMEOUT_KEY, READ_TIMEOUT_MS);
183    KDC = setupMiniKdc(KEYTAB_FILE);
184    USERNAME = UserGroupInformation.getLoginUser().getShortUserName();
185    PRINCIPAL = USERNAME + "/" + HOST;
186    HTTP_PRINCIPAL = "HTTP/" + HOST;
187    KDC.createPrincipal(KEYTAB_FILE, PRINCIPAL, HTTP_PRINCIPAL);
188
189    setUpKeyProvider(UTIL.getConfiguration());
190    HBaseKerberosUtils.setSecuredConfiguration(UTIL.getConfiguration(),
191      PRINCIPAL + "@" + KDC.getRealm(), HTTP_PRINCIPAL + "@" + KDC.getRealm());
192    HBaseKerberosUtils.setSSLConfiguration(UTIL, TestSaslFanOutOneBlockAsyncDFSOutput.class);
193    MONITOR = StreamSlowMonitor.create(UTIL.getConfiguration(), "testMonitor");
194  }
195
196  @AfterClass
197  public static void tearDownAfterClass() throws Exception {
198    if (EVENT_LOOP_GROUP != null) {
199      EVENT_LOOP_GROUP.shutdownGracefully().get();
200    }
201    if (KDC != null) {
202      KDC.stop();
203    }
204    shutdownMiniDFSCluster();
205  }
206
207  private Path testDirOnTestFs;
208
209  private Path entryptionTestDirOnTestFs;
210
211  private void createEncryptionZone() throws Exception {
212    Method method =
213      DistributedFileSystem.class.getMethod("createEncryptionZone", Path.class, String.class);
214    method.invoke(FS, entryptionTestDirOnTestFs, TEST_KEY_NAME);
215  }
216
217  @Before
218  public void setUp() throws Exception {
219    UTIL.getConfiguration().set("dfs.data.transfer.protection", protection);
220    if (StringUtils.isBlank(encryptionAlgorithm) && StringUtils.isBlank(cipherSuite)) {
221      UTIL.getConfiguration().setBoolean(DFS_ENCRYPT_DATA_TRANSFER_KEY, false);
222    } else {
223      UTIL.getConfiguration().setBoolean(DFS_ENCRYPT_DATA_TRANSFER_KEY, true);
224    }
225    if (StringUtils.isBlank(encryptionAlgorithm)) {
226      UTIL.getConfiguration().unset(DFS_DATA_ENCRYPTION_ALGORITHM_KEY);
227    } else {
228      UTIL.getConfiguration().set(DFS_DATA_ENCRYPTION_ALGORITHM_KEY, encryptionAlgorithm);
229    }
230    if (StringUtils.isBlank(cipherSuite)) {
231      UTIL.getConfiguration().unset(DFS_ENCRYPT_DATA_TRANSFER_CIPHER_SUITES_KEY);
232    } else {
233      UTIL.getConfiguration().set(DFS_ENCRYPT_DATA_TRANSFER_CIPHER_SUITES_KEY, cipherSuite);
234    }
235
236    startMiniDFSCluster(3);
237    FS = CLUSTER.getFileSystem();
238    testDirOnTestFs = new Path("/" + name.getMethodName().replaceAll("[^0-9a-zA-Z]", "_"));
239    FS.mkdirs(testDirOnTestFs);
240    entryptionTestDirOnTestFs = new Path("/" + testDirOnTestFs.getName() + "_enc");
241    FS.mkdirs(entryptionTestDirOnTestFs);
242    createEncryptionZone();
243  }
244
245  @After
246  public void tearDown() throws IOException {
247    shutdownMiniDFSCluster();
248  }
249
250  private Path getTestFile() {
251    return new Path(testDirOnTestFs, "test");
252  }
253
254  private Path getEncryptionTestFile() {
255    return new Path(entryptionTestDirOnTestFs, "test");
256  }
257
258  private void test(Path file) throws IOException, InterruptedException, ExecutionException {
259    EventLoop eventLoop = EVENT_LOOP_GROUP.next();
260    FanOutOneBlockAsyncDFSOutput out = FanOutOneBlockAsyncDFSOutputHelper.createOutput(FS, file,
261      true, false, (short) 3, FS.getDefaultBlockSize(), eventLoop, CHANNEL_CLASS, MONITOR, true);
262    TestFanOutOneBlockAsyncDFSOutput.writeAndVerify(FS, file, out);
263  }
264
265  @Test
266  public void test() throws IOException, InterruptedException, ExecutionException {
267    test(getTestFile());
268    test(getEncryptionTestFile());
269  }
270}