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