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.net.HttpURLConnection; 025import java.net.URL; 026import java.util.ArrayList; 027import java.util.List; 028import org.apache.hadoop.conf.Configuration; 029import org.apache.hadoop.hbase.HBaseClassTestRule; 030import org.apache.hadoop.hbase.HBaseTestingUtility; 031import org.apache.hadoop.hbase.HConstants; 032import org.apache.hadoop.hbase.testclassification.ClientTests; 033import org.apache.hadoop.hbase.testclassification.LargeTests; 034import org.apache.hadoop.hbase.thrift.generated.Hbase; 035import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 036import org.apache.hadoop.hbase.util.EnvironmentEdgeManagerTestHelper; 037import org.apache.hadoop.hbase.util.IncrementingEnvironmentEdge; 038import org.apache.thrift.protocol.TBinaryProtocol; 039import org.apache.thrift.protocol.TProtocol; 040import org.apache.thrift.transport.THttpClient; 041import org.apache.thrift.transport.TTransportException; 042import org.junit.AfterClass; 043import org.junit.Assert; 044import org.junit.BeforeClass; 045import org.junit.ClassRule; 046import org.junit.Rule; 047import org.junit.Test; 048import org.junit.experimental.categories.Category; 049import org.junit.rules.ExpectedException; 050import org.slf4j.Logger; 051import org.slf4j.LoggerFactory; 052 053import org.apache.hbase.thirdparty.com.google.common.base.Joiner; 054 055/** 056 * Start the HBase Thrift HTTP server on a random port through the command-line 057 * interface and talk to it from client side. 058 */ 059@Category({ClientTests.class, LargeTests.class}) 060public class TestThriftHttpServer { 061 @ClassRule 062 public static final HBaseClassTestRule CLASS_RULE = 063 HBaseClassTestRule.forClass(TestThriftHttpServer.class); 064 065 private static final Logger LOG = 066 LoggerFactory.getLogger(TestThriftHttpServer.class); 067 068 static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 069 070 private Thread httpServerThread; 071 private volatile Exception httpServerException; 072 073 private Exception clientSideException; 074 075 private ThriftServer thriftServer; 076 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(() -> { 119 try { 120 thriftServer.doMain(args); 121 } catch (Exception e) { 122 httpServerException = e; 123 } 124 }); 125 httpServerThread.setName(ThriftServer.class.getSimpleName() + "-httpServer"); 126 httpServerThread.start(); 127 } 128 129 @Rule 130 public ExpectedException exception = ExpectedException.none(); 131 132 @Test 133 public void testRunThriftServerWithHeaderBufferLength() throws Exception { 134 135 // Test thrift server with HTTP header length less than 64k 136 try { 137 runThriftServer(1024 * 63); 138 } catch (TTransportException tex) { 139 assertFalse(tex.getMessage().equals("HTTP Response code: 431")); 140 } 141 142 // Test thrift server with HTTP header length more than 64k, expect an exception 143 exception.expect(TTransportException.class); 144 exception.expectMessage("HTTP Response code: 431"); 145 runThriftServer(1024 * 64); 146 } 147 148 @Test 149 public void testRunThriftServer() throws Exception { 150 runThriftServer(0); 151 } 152 153 void runThriftServer(int customHeaderSize) throws Exception { 154 List<String> args = new ArrayList<>(3); 155 port = HBaseTestingUtility.randomFreePort(); 156 args.add("-" + ThriftServer.PORT_OPTION); 157 args.add(String.valueOf(port)); 158 args.add("-" + ThriftServer.INFOPORT_OPTION); 159 int infoPort = HBaseTestingUtility.randomFreePort(); 160 args.add(String.valueOf(infoPort)); 161 args.add("start"); 162 163 thriftServer = new ThriftServer(TEST_UTIL.getConfiguration()); 164 startHttpServerThread(args.toArray(new String[args.size()])); 165 166 // wait up to 10s for the server to start 167 HBaseTestingUtility.waitForHostPort(HConstants.LOCALHOST, port); 168 169 String url = "http://" + HConstants.LOCALHOST + ":" + port; 170 try { 171 checkHttpMethods(url); 172 talkToThriftServer(url, customHeaderSize); 173 } catch (Exception ex) { 174 clientSideException = ex; 175 } finally { 176 stopHttpServerThread(); 177 } 178 179 if (clientSideException != null) { 180 LOG.error("Thrift client threw an exception " + clientSideException); 181 if (clientSideException instanceof TTransportException) { 182 throw clientSideException; 183 } else { 184 throw new Exception(clientSideException); 185 } 186 } 187 } 188 189 private void checkHttpMethods(String url) throws Exception { 190 // HTTP TRACE method should be disabled for security 191 // See https://www.owasp.org/index.php/Cross_Site_Tracing 192 HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); 193 conn.setRequestMethod("TRACE"); 194 conn.connect(); 195 Assert.assertEquals(HttpURLConnection.HTTP_FORBIDDEN, conn.getResponseCode()); 196 } 197 198 static volatile boolean tableCreated = false; 199 200 void talkToThriftServer(String url, int customHeaderSize) throws Exception { 201 THttpClient httpClient = new THttpClient(url); 202 httpClient.open(); 203 204 if (customHeaderSize > 0) { 205 StringBuilder sb = new StringBuilder(); 206 for (int i = 0; i < customHeaderSize; i++) { 207 sb.append("a"); 208 } 209 httpClient.setCustomHeader("User-Agent", sb.toString()); 210 } 211 212 try { 213 TProtocol prot; 214 prot = new TBinaryProtocol(httpClient); 215 Hbase.Client client = new Hbase.Client(prot); 216 if (!tableCreated){ 217 TestThriftServer.createTestTables(client); 218 tableCreated = true; 219 } 220 TestThriftServer.checkTableList(client); 221 } finally { 222 httpClient.close(); 223 } 224 } 225 226 private void stopHttpServerThread() throws Exception { 227 LOG.debug("Stopping " + " Thrift HTTP server"); 228 thriftServer.stop(); 229 httpServerThread.join(); 230 if (httpServerException != null) { 231 LOG.error("Command-line invocation of HBase Thrift server threw an " + 232 "exception", httpServerException); 233 throw new Exception(httpServerException); 234 } 235 } 236}