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