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; 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.conf.Configuration; 033import org.apache.hadoop.hbase.client.AsyncAdmin; 034import org.apache.hadoop.hbase.client.AsyncConnection; 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.ClassRule; 040import org.junit.Rule; 041import org.junit.Test; 042import org.junit.experimental.categories.Category; 043 044/** 045 * Test for the {@link ShellExecEndpointCoprocessor}. 046 */ 047@Category(MediumTests.class) 048public class TestShellExecEndpointCoprocessor { 049 050 @ClassRule 051 public static final HBaseClassTestRule testRule = 052 HBaseClassTestRule.forClass(TestShellExecEndpointCoprocessor.class); 053 054 @ClassRule 055 public static final MiniClusterRule miniClusterRule = 056 MiniClusterRule.newBuilder().setConfiguration(createConfiguration()).build(); 057 058 @Rule 059 public final ConnectionRule connectionRule = 060 ConnectionRule.createAsyncConnectionRule(miniClusterRule::createAsyncConnection); 061 062 @Test 063 public void testShellExecUnspecified() { 064 testShellExecForeground(b -> { 065 }); 066 } 067 068 @Test 069 public void testShellExecForeground() { 070 testShellExecForeground(b -> b.setAwaitResponse(true)); 071 } 072 073 private void testShellExecForeground(final Consumer<ShellExecRequest.Builder> consumer) { 074 final AsyncConnection conn = connectionRule.getAsyncConnection(); 075 final AsyncAdmin admin = conn.getAdmin(); 076 077 final String command = "echo -n \"hello world\""; 078 final ShellExecRequest.Builder builder = ShellExecRequest.newBuilder().setCommand(command); 079 consumer.accept(builder); 080 final ShellExecResponse resp = 081 admin.<ShellExecService.Stub, ShellExecResponse> coprocessorService(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.getAsyncConnection(); 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 = 100 ShellExecRequest.newBuilder().setCommand(command).setAwaitResponse(false).build(); 101 final ShellExecResponse resp = 102 admin.<ShellExecService.Stub, ShellExecResponse> coprocessorService(ShellExecService::newStub, 103 (stub, controller, callback) -> stub.shellExec(controller, req, callback)).join(); 104 105 assertFalse("the response from a background task should have no exit code", resp.hasExitCode()); 106 assertFalse("the response from a background task should have no stdout", resp.hasStdout()); 107 assertFalse("the response from a background task should have no stderr", resp.hasStderr()); 108 109 Waiter.waitFor(conn.getConfiguration(), 5_000, () -> testFile.length() > 0); 110 final String content = 111 new String(Files.readAllBytes(testFile.toPath()), StandardCharsets.UTF_8).trim(); 112 assertEquals("hello world", content); 113 } 114 115 private static File ensureTestDataDirExists(final HBaseTestingUtil testingUtility) 116 throws IOException { 117 final Path testDataDir = Optional.of(testingUtility).map(HBaseTestingUtil::getDataTestDir) 118 .map(Object::toString).map(Paths::get) 119 .orElseThrow(() -> new RuntimeException("Unable to locate temp directory path.")); 120 final File testDataDirFile = Files.createDirectories(testDataDir).toFile(); 121 assertTrue(testDataDirFile.exists()); 122 return testDataDirFile; 123 } 124 125 private static Configuration createConfiguration() { 126 final Configuration conf = HBaseConfiguration.create(); 127 conf.set("hbase.coprocessor.master.classes", ShellExecEndpointCoprocessor.class.getName()); 128 return conf; 129 } 130}