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.thrift; 019 020import static org.junit.Assert.assertFalse; 021import static org.junit.Assert.assertNull; 022import static org.junit.Assert.fail; 023 024import java.util.ArrayList; 025import java.util.List; 026import org.apache.hadoop.conf.Configuration; 027import org.apache.hadoop.hbase.HBaseClassTestRule; 028import org.apache.hadoop.hbase.HBaseTestingUtility; 029import org.apache.hadoop.hbase.HConstants; 030import org.apache.hadoop.hbase.testclassification.ClientTests; 031import org.apache.hadoop.hbase.testclassification.LargeTests; 032import org.apache.hadoop.hbase.thrift.generated.Hbase; 033import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 034import org.apache.hadoop.hbase.util.EnvironmentEdgeManagerTestHelper; 035import org.apache.hadoop.hbase.util.IncrementingEnvironmentEdge; 036import org.apache.thrift.protocol.TBinaryProtocol; 037import org.apache.thrift.protocol.TProtocol; 038import org.apache.thrift.transport.THttpClient; 039import org.apache.thrift.transport.TTransportException; 040import org.junit.AfterClass; 041import org.junit.BeforeClass; 042import org.junit.ClassRule; 043import org.junit.Rule; 044import org.junit.Test; 045import org.junit.experimental.categories.Category; 046import org.junit.rules.ExpectedException; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049 050import org.apache.hbase.thirdparty.com.google.common.base.Joiner; 051 052/** 053 * Start the HBase Thrift HTTP server on a random port through the command-line 054 * interface and talk to it from client side. 055 */ 056@Category({ClientTests.class, LargeTests.class}) 057 058public class TestThriftHttpServer { 059 060 @ClassRule 061 public static final HBaseClassTestRule CLASS_RULE = 062 HBaseClassTestRule.forClass(TestThriftHttpServer.class); 063 064 private static final Logger LOG = 065 LoggerFactory.getLogger(TestThriftHttpServer.class); 066 067 private static final HBaseTestingUtility TEST_UTIL = 068 new HBaseTestingUtility(); 069 070 private Thread httpServerThread; 071 private volatile Exception httpServerException; 072 073 private Exception clientSideException; 074 075 private ThriftServer thriftServer; 076 private int port; 077 078 @BeforeClass 079 public static void setUpBeforeClass() throws Exception { 080 TEST_UTIL.getConfiguration().setBoolean("hbase.regionserver.thrift.http", true); 081 TEST_UTIL.getConfiguration().setBoolean("hbase.table.sanity.checks", false); 082 TEST_UTIL.startMiniCluster(); 083 //ensure that server time increments every time we do an operation, otherwise 084 //successive puts having the same timestamp will override each other 085 EnvironmentEdgeManagerTestHelper.injectEdge(new IncrementingEnvironmentEdge()); 086 } 087 088 @AfterClass 089 public static void tearDownAfterClass() throws Exception { 090 TEST_UTIL.shutdownMiniCluster(); 091 EnvironmentEdgeManager.reset(); 092 } 093 094 @Test 095 public void testExceptionThrownWhenMisConfigured() throws Exception { 096 Configuration conf = new Configuration(TEST_UTIL.getConfiguration()); 097 conf.set("hbase.thrift.security.qop", "privacy"); 098 conf.setBoolean("hbase.thrift.ssl.enabled", false); 099 100 ThriftServerRunner runner = null; 101 ExpectedException thrown = ExpectedException.none(); 102 try { 103 thrown.expect(IllegalArgumentException.class); 104 thrown.expectMessage("Thrift HTTP Server's QoP is privacy, " + 105 "but hbase.thrift.ssl.enabled is false"); 106 runner = new ThriftServerRunner(conf); 107 fail("Thrift HTTP Server starts up even with wrong security configurations."); 108 } catch (Exception e) { 109 } 110 111 assertNull(runner); 112 } 113 114 private void startHttpServerThread(final String[] args) { 115 LOG.info("Starting HBase Thrift server with HTTP server: " + Joiner.on(" ").join(args)); 116 117 httpServerException = null; 118 httpServerThread = new Thread(new Runnable() { 119 @Override 120 public void run() { 121 try { 122 thriftServer.doMain(args); 123 } catch (Exception e) { 124 httpServerException = e; 125 } 126 } 127 }); 128 httpServerThread.setName(ThriftServer.class.getSimpleName() + 129 "-httpServer"); 130 httpServerThread.start(); 131 } 132 133 @Rule 134 public ExpectedException exception = ExpectedException.none(); 135 136 @Test 137 public void testRunThriftServerWithHeaderBufferLength() throws Exception { 138 139 // Test thrift server with HTTP header length less than 64k 140 try { 141 runThriftServer(1024 * 63); 142 } catch (TTransportException tex) { 143 assertFalse(tex.getMessage().equals("HTTP Response code: 431")); 144 } 145 146 // Test thrift server with HTTP header length more than 64k, expect an exception 147 exception.expect(TTransportException.class); 148 exception.expectMessage("HTTP Response code: 431"); 149 runThriftServer(1024 * 64); 150 } 151 152 @Test 153 public void testRunThriftServer() throws Exception { 154 runThriftServer(0); 155 } 156 157 private void runThriftServer(int customHeaderSize) throws Exception { 158 List<String> args = new ArrayList<>(3); 159 port = HBaseTestingUtility.randomFreePort(); 160 args.add("-" + ThriftServer.PORT_OPTION); 161 args.add(String.valueOf(port)); 162 args.add("start"); 163 164 thriftServer = new ThriftServer(TEST_UTIL.getConfiguration()); 165 startHttpServerThread(args.toArray(new String[args.size()])); 166 167 // wait up to 10s for the server to start 168 for (int i = 0; i < 100 169 && ( thriftServer.serverRunner == null || thriftServer.serverRunner.httpServer == 170 null); i++) { 171 Thread.sleep(100); 172 } 173 174 try { 175 talkToThriftServer(customHeaderSize); 176 } catch (Exception ex) { 177 clientSideException = ex; 178 } finally { 179 stopHttpServerThread(); 180 } 181 182 if (clientSideException != null) { 183 LOG.error("Thrift client threw an exception " + clientSideException); 184 if (clientSideException instanceof TTransportException) { 185 throw clientSideException; 186 } else { 187 throw new Exception(clientSideException); 188 } 189 } 190 } 191 192 private static volatile boolean tableCreated = false; 193 194 private void talkToThriftServer(int customHeaderSize) throws Exception { 195 THttpClient httpClient = new THttpClient( 196 "http://"+ HConstants.LOCALHOST + ":" + port); 197 httpClient.open(); 198 199 if (customHeaderSize > 0) { 200 StringBuilder sb = new StringBuilder(); 201 for (int i = 0; i < customHeaderSize; i++) { 202 sb.append("a"); 203 } 204 httpClient.setCustomHeader("User-Agent", sb.toString()); 205 } 206 207 try { 208 TProtocol prot; 209 prot = new TBinaryProtocol(httpClient); 210 Hbase.Client client = new Hbase.Client(prot); 211 if (!tableCreated){ 212 TestThriftServer.createTestTables(client); 213 tableCreated = true; 214 } 215 TestThriftServer.checkTableList(client); 216 } finally { 217 httpClient.close(); 218 } 219 } 220 221 private void stopHttpServerThread() throws Exception { 222 LOG.debug("Stopping " + " Thrift HTTP server"); 223 thriftServer.stop(); 224 httpServerThread.join(); 225 if (httpServerException != null) { 226 LOG.error("Command-line invocation of HBase Thrift server threw an " + 227 "exception", httpServerException); 228 throw new Exception(httpServerException); 229 } 230 } 231}