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