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