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;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023import java.io.File;
024import java.io.IOException;
025import java.nio.file.Files;
026import java.nio.file.Path;
027import java.nio.file.Paths;
028import java.util.Optional;
029import java.util.function.Consumer;
030import org.apache.hadoop.conf.Configuration;
031import org.apache.hadoop.hbase.client.AsyncAdmin;
032import org.apache.hadoop.hbase.client.AsyncConnection;
033import org.apache.hadoop.hbase.coprocessor.protobuf.generated.ShellExecEndpoint.ShellExecRequest;
034import org.apache.hadoop.hbase.coprocessor.protobuf.generated.ShellExecEndpoint.ShellExecResponse;
035import org.apache.hadoop.hbase.coprocessor.protobuf.generated.ShellExecEndpoint.ShellExecService;
036import org.apache.hadoop.hbase.testclassification.MediumTests;
037import org.junit.ClassRule;
038import org.junit.Rule;
039import org.junit.Test;
040import org.junit.experimental.categories.Category;
041
042/**
043 * Test for the {@link ShellExecEndpointCoprocessor}.
044 */
045@Category(MediumTests.class)
046public class TestShellExecEndpointCoprocessor {
047
048  @ClassRule
049  public static final HBaseClassTestRule testRule =
050    HBaseClassTestRule.forClass(TestShellExecEndpointCoprocessor.class);
051
052  @ClassRule
053  public static final MiniClusterRule miniClusterRule = MiniClusterRule.newBuilder()
054    .setConfiguration(createConfiguration())
055    .build();
056
057  @Rule
058  public final ConnectionRule connectionRule =
059    new ConnectionRule(miniClusterRule::createConnection);
060
061  @Test
062  public void testShellExecUnspecified() {
063    testShellExecForeground(b -> {});
064  }
065
066  @Test
067  public void testShellExecForeground() {
068    testShellExecForeground(b -> b.setAwaitResponse(true));
069  }
070
071  private void testShellExecForeground(final Consumer<ShellExecRequest.Builder> consumer) {
072    final AsyncConnection conn = connectionRule.getConnection();
073    final AsyncAdmin admin = conn.getAdmin();
074
075    final String command = "echo -n \"hello world\"";
076    final ShellExecRequest.Builder builder = ShellExecRequest.newBuilder()
077      .setCommand(command);
078    consumer.accept(builder);
079    final ShellExecResponse resp = admin
080      .<ShellExecService.Stub, ShellExecResponse>coprocessorService(
081        ShellExecService::newStub,
082        (stub, controller, callback) -> stub.shellExec(controller, builder.build(), callback))
083      .join();
084    assertEquals(0, resp.getExitCode());
085    assertEquals("hello world", resp.getStdout());
086  }
087
088  @Test
089  public void testShellExecBackground() throws IOException {
090    final AsyncConnection conn = connectionRule.getConnection();
091    final AsyncAdmin admin = conn.getAdmin();
092
093    final File testDataDir = ensureTestDataDirExists(miniClusterRule.getTestingUtility());
094    final File testFile = new File(testDataDir, "shell_exec_background.txt");
095    assertTrue(testFile.createNewFile());
096    assertEquals(0, testFile.length());
097
098    final String command = "echo \"hello world\" >> " + testFile.getAbsolutePath();
099    final ShellExecRequest req = ShellExecRequest.newBuilder()
100      .setCommand(command)
101      .setAwaitResponse(false)
102      .build();
103    final ShellExecResponse resp = admin
104      .<ShellExecService.Stub, ShellExecResponse>coprocessorService(
105        ShellExecService::newStub,
106        (stub, controller, callback) -> stub.shellExec(controller, req, callback))
107      .join();
108
109    assertFalse("the response from a background task should have no exit code", resp.hasExitCode());
110    assertFalse("the response from a background task should have no stdout", resp.hasStdout());
111    assertFalse("the response from a background task should have no stderr", resp.hasStderr());
112
113    Waiter.waitFor(conn.getConfiguration(), 5_000, () -> testFile.length() > 0);
114    final String content = new String(Files.readAllBytes(testFile.toPath())).trim();
115    assertEquals("hello world", content);
116  }
117
118  private static File ensureTestDataDirExists(
119    final HBaseTestingUtility testingUtility
120  ) throws IOException {
121    final Path testDataDir = Optional.of(testingUtility)
122      .map(HBaseTestingUtility::getDataTestDir)
123      .map(Object::toString)
124      .map(val -> Paths.get(val))
125      .orElseThrow(() -> new RuntimeException("Unable to locate temp directory path."));
126    final File testDataDirFile = Files.createDirectories(testDataDir).toFile();
127    assertTrue(testDataDirFile.exists());
128    return testDataDirFile;
129  }
130
131  private static Configuration createConfiguration() {
132    final Configuration conf = HBaseConfiguration.create();
133    conf.set("hbase.coprocessor.master.classes", ShellExecEndpointCoprocessor.class.getName());
134    return conf;
135  }
136}