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.TestThriftServerCmdLine.createBoundServer; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.fail; 023 024import java.io.IOException; 025import java.net.HttpURLConnection; 026import java.net.URL; 027import java.util.function.Supplier; 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.hadoop.hbase.util.TableDescriptorChecker; 039import org.apache.thrift.protocol.TBinaryProtocol; 040import org.apache.thrift.protocol.TProtocol; 041import org.apache.thrift.transport.THttpClient; 042import org.apache.thrift.transport.TTransportException; 043import org.junit.AfterClass; 044import org.junit.Assert; 045import org.junit.BeforeClass; 046import org.junit.ClassRule; 047import org.junit.Rule; 048import org.junit.Test; 049import org.junit.experimental.categories.Category; 050import org.junit.rules.ExpectedException; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054import org.apache.hbase.thirdparty.com.google.common.base.Joiner; 055 056/** 057 * Start the HBase Thrift HTTP server on a random port through the command-line 058 * interface and talk to it from client side. 059 */ 060@Category({ClientTests.class, LargeTests.class}) 061public class TestThriftHttpServer { 062 @ClassRule 063 public static final HBaseClassTestRule CLASS_RULE = 064 HBaseClassTestRule.forClass(TestThriftHttpServer.class); 065 066 private static final Logger LOG = 067 LoggerFactory.getLogger(TestThriftHttpServer.class); 068 069 protected static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 070 071 @BeforeClass 072 public static void setUpBeforeClass() throws Exception { 073 TEST_UTIL.getConfiguration().setBoolean(Constants.USE_HTTP_CONF_KEY, true); 074 TEST_UTIL.getConfiguration().setBoolean(TableDescriptorChecker.TABLE_SANITY_CHECKS, false); 075 TEST_UTIL.startMiniCluster(); 076 //ensure that server time increments every time we do an operation, otherwise 077 //successive puts having the same timestamp will override each other 078 EnvironmentEdgeManagerTestHelper.injectEdge(new IncrementingEnvironmentEdge()); 079 } 080 081 @AfterClass 082 public static void tearDownAfterClass() throws Exception { 083 TEST_UTIL.shutdownMiniCluster(); 084 EnvironmentEdgeManager.reset(); 085 } 086 087 @Test 088 public void testExceptionThrownWhenMisConfigured() throws IOException { 089 Configuration conf = new Configuration(TEST_UTIL.getConfiguration()); 090 conf.set("hbase.thrift.security.qop", "privacy"); 091 conf.setBoolean("hbase.thrift.ssl.enabled", false); 092 ExpectedException thrown = ExpectedException.none(); 093 ThriftServerRunner tsr = null; 094 try { 095 thrown.expect(IllegalArgumentException.class); 096 thrown.expectMessage("Thrift HTTP Server's QoP is privacy, " + 097 "but hbase.thrift.ssl.enabled is false"); 098 tsr = TestThriftServerCmdLine.createBoundServer(() -> new ThriftServer(conf)); 099 fail("Thrift HTTP Server starts up even with wrong security configurations."); 100 } catch (Exception e) { 101 LOG.info("Expected!", e); 102 } finally { 103 if (tsr != null) { 104 tsr.close(); 105 } 106 } 107 } 108 109 @Rule 110 public ExpectedException exception = ExpectedException.none(); 111 112 @Test 113 public void testRunThriftServerWithHeaderBufferLength() throws Exception { 114 // Test thrift server with HTTP header length less than 64k 115 try { 116 runThriftServer(1024 * 63); 117 } catch (TTransportException tex) { 118 assertFalse(tex.getMessage().equals("HTTP Response code: 431")); 119 } 120 121 // Test thrift server with HTTP header length more than 64k, expect an exception 122 exception.expect(TTransportException.class); 123 exception.expectMessage("HTTP Response code: 431"); 124 runThriftServer(1024 * 64); 125 } 126 127 protected Supplier<ThriftServer> getThriftServerSupplier() { 128 return () -> new ThriftServer(TEST_UTIL.getConfiguration()); 129 } 130 131 @Test 132 public void testRunThriftServer() throws Exception { 133 runThriftServer(0); 134 } 135 136 void runThriftServer(int customHeaderSize) throws Exception { 137 // Add retries in case we see stuff like connection reset 138 Exception clientSideException = null; 139 for (int i = 0; i < 10; i++) { 140 clientSideException = null; 141 ThriftServerRunner tsr = createBoundServer(getThriftServerSupplier()); 142 String url = "http://" + HConstants.LOCALHOST + ":" + tsr.getThriftServer().listenPort; 143 try { 144 checkHttpMethods(url); 145 talkToThriftServer(url, customHeaderSize); 146 break; 147 } catch (Exception ex) { 148 clientSideException = ex; 149 LOG.info("Client-side Exception", ex); 150 } finally { 151 tsr.close(); 152 tsr.join(); 153 if (tsr.getRunException() != null) { 154 LOG.error("Invocation of HBase Thrift server threw exception", tsr.getRunException()); 155 throw tsr.getRunException(); 156 } 157 } 158 } 159 160 if (clientSideException != null) { 161 LOG.error("Thrift Client", clientSideException); 162 throw clientSideException; 163 } 164 } 165 166 private void checkHttpMethods(String url) throws Exception { 167 // HTTP TRACE method should be disabled for security 168 // See https://www.owasp.org/index.php/Cross_Site_Tracing 169 HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); 170 conn.setRequestMethod("TRACE"); 171 conn.connect(); 172 Assert.assertEquals(conn.getResponseMessage(), 173 HttpURLConnection.HTTP_FORBIDDEN, conn.getResponseCode()); 174 } 175 176 protected static volatile boolean tableCreated = false; 177 178 protected void talkToThriftServer(String url, int customHeaderSize) throws Exception { 179 THttpClient httpClient = new THttpClient(url); 180 httpClient.open(); 181 182 if (customHeaderSize > 0) { 183 StringBuilder sb = new StringBuilder(); 184 for (int i = 0; i < customHeaderSize; i++) { 185 sb.append("a"); 186 } 187 httpClient.setCustomHeader("User-Agent", sb.toString()); 188 } 189 190 try { 191 TProtocol prot; 192 prot = new TBinaryProtocol(httpClient); 193 Hbase.Client client = new Hbase.Client(prot); 194 if (!tableCreated){ 195 TestThriftServer.createTestTables(client); 196 tableCreated = true; 197 } 198 TestThriftServer.checkTableList(client); 199 } finally { 200 httpClient.close(); 201 } 202 } 203}