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.tool; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertNotEquals; 022import static org.mockito.ArgumentMatchers.argThat; 023import static org.mockito.Matchers.anyLong; 024import static org.mockito.Matchers.eq; 025import static org.mockito.Matchers.isA; 026import static org.mockito.Mockito.atLeastOnce; 027import static org.mockito.Mockito.never; 028import static org.mockito.Mockito.spy; 029import static org.mockito.Mockito.times; 030import static org.mockito.Mockito.verify; 031 032import java.util.concurrent.ExecutorService; 033import java.util.concurrent.ScheduledThreadPoolExecutor; 034import org.apache.hadoop.hbase.HBaseClassTestRule; 035import org.apache.hadoop.hbase.HBaseTestingUtility; 036import org.apache.hadoop.hbase.HConstants; 037import org.apache.hadoop.hbase.ServerName; 038import org.apache.hadoop.hbase.TableName; 039import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 040import org.apache.hadoop.hbase.client.Put; 041import org.apache.hadoop.hbase.client.RegionInfo; 042import org.apache.hadoop.hbase.client.Table; 043import org.apache.hadoop.hbase.testclassification.MediumTests; 044import org.apache.hadoop.hbase.util.Bytes; 045import org.apache.hadoop.util.ToolRunner; 046import org.apache.log4j.Appender; 047import org.apache.log4j.LogManager; 048import org.apache.log4j.spi.LoggingEvent; 049import org.junit.After; 050import org.junit.Before; 051import org.junit.ClassRule; 052import org.junit.Ignore; 053import org.junit.Rule; 054import org.junit.Test; 055import org.junit.experimental.categories.Category; 056import org.junit.rules.TestName; 057import org.junit.runner.RunWith; 058import org.mockito.ArgumentMatcher; 059import org.mockito.Mock; 060import org.mockito.runners.MockitoJUnitRunner; 061 062import org.apache.hbase.thirdparty.com.google.common.collect.Iterables; 063 064@RunWith(MockitoJUnitRunner.class) 065@Category({MediumTests.class}) 066public class TestCanaryTool { 067 068 @ClassRule 069 public static final HBaseClassTestRule CLASS_RULE = 070 HBaseClassTestRule.forClass(TestCanaryTool.class); 071 072 private HBaseTestingUtility testingUtility; 073 private static final byte[] FAMILY = Bytes.toBytes("f"); 074 private static final byte[] COLUMN = Bytes.toBytes("col"); 075 076 @Rule 077 public TestName name = new TestName(); 078 079 @Before 080 public void setUp() throws Exception { 081 testingUtility = new HBaseTestingUtility(); 082 testingUtility.startMiniCluster(); 083 LogManager.getRootLogger().addAppender(mockAppender); 084 } 085 086 @After 087 public void tearDown() throws Exception { 088 testingUtility.shutdownMiniCluster(); 089 LogManager.getRootLogger().removeAppender(mockAppender); 090 } 091 092 @Mock 093 Appender mockAppender; 094 095 @Test 096 public void testBasicZookeeperCanaryWorks() throws Exception { 097 final String[] args = { "-t", "10000", "-zookeeper" }; 098 testZookeeperCanaryWithArgs(args); 099 } 100 101 @Test 102 public void testZookeeperCanaryPermittedFailuresArgumentWorks() throws Exception { 103 final String[] args = { "-t", "10000", "-zookeeper", "-treatFailureAsError", "-permittedZookeeperFailures", "1" }; 104 testZookeeperCanaryWithArgs(args); 105 } 106 107 @Test 108 public void testBasicCanaryWorks() throws Exception { 109 final TableName tableName = TableName.valueOf(name.getMethodName()); 110 Table table = testingUtility.createTable(tableName, new byte[][] { FAMILY }); 111 // insert some test rows 112 for (int i=0; i<1000; i++) { 113 byte[] iBytes = Bytes.toBytes(i); 114 Put p = new Put(iBytes); 115 p.addColumn(FAMILY, COLUMN, iBytes); 116 table.put(p); 117 } 118 ExecutorService executor = new ScheduledThreadPoolExecutor(1); 119 Canary.RegionStdOutSink sink = spy(new Canary.RegionStdOutSink()); 120 Canary canary = new Canary(executor, sink); 121 String[] args = { "-writeSniffing", "-t", "10000", tableName.getNameAsString() }; 122 assertEquals(0, ToolRunner.run(testingUtility.getConfiguration(), canary, args)); 123 assertEquals("verify no read error count", 0, canary.getReadFailures().size()); 124 assertEquals("verify no write error count", 0, canary.getWriteFailures().size()); 125 verify(sink, atLeastOnce()).publishReadTiming(isA(ServerName.class), isA(RegionInfo.class), 126 isA(ColumnFamilyDescriptor.class), anyLong()); 127 } 128 129 @Test 130 @Ignore("Intermittent argument matching failures, see HBASE-18813") 131 public void testReadTableTimeouts() throws Exception { 132 final TableName [] tableNames = new TableName[2]; 133 tableNames[0] = TableName.valueOf(name.getMethodName() + "1"); 134 tableNames[1] = TableName.valueOf(name.getMethodName() + "2"); 135 // Create 2 test tables. 136 for (int j = 0; j<2; j++) { 137 Table table = testingUtility.createTable(tableNames[j], new byte[][] { FAMILY }); 138 // insert some test rows 139 for (int i=0; i<1000; i++) { 140 byte[] iBytes = Bytes.toBytes(i + j); 141 Put p = new Put(iBytes); 142 p.addColumn(FAMILY, COLUMN, iBytes); 143 table.put(p); 144 } 145 } 146 ExecutorService executor = new ScheduledThreadPoolExecutor(1); 147 Canary.RegionStdOutSink sink = spy(new Canary.RegionStdOutSink()); 148 Canary canary = new Canary(executor, sink); 149 String configuredTimeoutStr = tableNames[0].getNameAsString() + "=" + Long.MAX_VALUE + "," + 150 tableNames[1].getNameAsString() + "=0"; 151 String[] args = {"-readTableTimeouts", configuredTimeoutStr, name.getMethodName() + "1", 152 name.getMethodName() + "2"}; 153 assertEquals(0, ToolRunner.run(testingUtility.getConfiguration(), canary, args)); 154 verify(sink, times(tableNames.length)).initializeAndGetReadLatencyForTable(isA(String.class)); 155 for (int i=0; i<2; i++) { 156 assertNotEquals("verify non-null read latency", null, sink.getReadLatencyMap().get(tableNames[i].getNameAsString())); 157 assertNotEquals("verify non-zero read latency", 0L, sink.getReadLatencyMap().get(tableNames[i].getNameAsString())); 158 } 159 // One table's timeout is set for 0 ms and thus, should lead to an error. 160 verify(mockAppender, times(1)).doAppend(argThat(new ArgumentMatcher<LoggingEvent>() { 161 @Override 162 public boolean matches(LoggingEvent argument) { 163 return ((LoggingEvent) argument).getRenderedMessage().contains("exceeded the configured read timeout."); 164 } 165 })); 166 verify(mockAppender, times(2)).doAppend(argThat(new ArgumentMatcher<LoggingEvent>() { 167 @Override 168 public boolean matches(LoggingEvent argument) { 169 return argument.getRenderedMessage().contains("The configured read timeout was"); 170 } 171 })); 172 } 173 174 @Test 175 @Ignore("Intermittent argument matching failures, see HBASE-18813") 176 public void testWriteTableTimeout() throws Exception { 177 ExecutorService executor = new ScheduledThreadPoolExecutor(1); 178 Canary.RegionStdOutSink sink = spy(new Canary.RegionStdOutSink()); 179 Canary canary = new Canary(executor, sink); 180 String[] args = { "-writeSniffing", "-writeTableTimeout", String.valueOf(Long.MAX_VALUE)}; 181 assertEquals(0, ToolRunner.run(testingUtility.getConfiguration(), canary, args)); 182 assertNotEquals("verify non-null write latency", null, sink.getWriteLatency()); 183 assertNotEquals("verify non-zero write latency", 0L, sink.getWriteLatency()); 184 verify(mockAppender, times(1)).doAppend(argThat( 185 new ArgumentMatcher<LoggingEvent>() { 186 @Override 187 public boolean matches(LoggingEvent argument) { 188 return argument.getRenderedMessage().contains("The configured write timeout was"); 189 } 190 })); 191 } 192 193 //no table created, so there should be no regions 194 @Test 195 public void testRegionserverNoRegions() throws Exception { 196 runRegionserverCanary(); 197 verify(mockAppender).doAppend(argThat(new ArgumentMatcher<LoggingEvent>() { 198 @Override 199 public boolean matches(LoggingEvent argument) { 200 return argument.getRenderedMessage().contains("Regionserver not serving any regions"); 201 } 202 })); 203 } 204 205 //by creating a table, there shouldn't be any region servers not serving any regions 206 @Test 207 public void testRegionserverWithRegions() throws Exception { 208 final TableName tableName = TableName.valueOf(name.getMethodName()); 209 testingUtility.createTable(tableName, new byte[][] { FAMILY }); 210 runRegionserverCanary(); 211 verify(mockAppender, never()).doAppend(argThat(new ArgumentMatcher<LoggingEvent>() { 212 @Override 213 public boolean matches(LoggingEvent argument) { 214 return argument.getRenderedMessage().contains("Regionserver not serving any regions"); 215 } 216 })); 217 } 218 219 @Test 220 public void testRawScanConfig() throws Exception { 221 final TableName tableName = TableName.valueOf(name.getMethodName()); 222 Table table = testingUtility.createTable(tableName, new byte[][] { FAMILY }); 223 // insert some test rows 224 for (int i=0; i<1000; i++) { 225 byte[] iBytes = Bytes.toBytes(i); 226 Put p = new Put(iBytes); 227 p.addColumn(FAMILY, COLUMN, iBytes); 228 table.put(p); 229 } 230 ExecutorService executor = new ScheduledThreadPoolExecutor(1); 231 Canary.RegionStdOutSink sink = spy(new Canary.RegionStdOutSink()); 232 Canary canary = new Canary(executor, sink); 233 String[] args = { "-t", "10000", name.getMethodName() }; 234 org.apache.hadoop.conf.Configuration conf = 235 new org.apache.hadoop.conf.Configuration(testingUtility.getConfiguration()); 236 conf.setBoolean(HConstants.HBASE_CANARY_READ_RAW_SCAN_KEY, true); 237 assertEquals(0, ToolRunner.run(conf, canary, args)); 238 verify(sink, atLeastOnce()) 239 .publishReadTiming(isA(ServerName.class), isA(RegionInfo.class), 240 isA(ColumnFamilyDescriptor.class), anyLong()); 241 assertEquals("verify no read error count", 0, canary.getReadFailures().size()); 242 } 243 244 private void runRegionserverCanary() throws Exception { 245 ExecutorService executor = new ScheduledThreadPoolExecutor(1); 246 Canary canary = new Canary(executor, new Canary.RegionServerStdOutSink()); 247 String[] args = { "-t", "10000", "-regionserver"}; 248 assertEquals(0, ToolRunner.run(testingUtility.getConfiguration(), canary, args)); 249 assertEquals("verify no read error count", 0, canary.getReadFailures().size()); 250 } 251 252 private void testZookeeperCanaryWithArgs(String[] args) throws Exception { 253 Integer port = 254 Iterables.getOnlyElement(testingUtility.getZkCluster().getClientPortList(), null); 255 testingUtility.getConfiguration().set(HConstants.ZOOKEEPER_QUORUM, 256 "localhost:" + port + "/hbase"); 257 ExecutorService executor = new ScheduledThreadPoolExecutor(2); 258 Canary.ZookeeperStdOutSink sink = spy(new Canary.ZookeeperStdOutSink()); 259 Canary canary = new Canary(executor, sink); 260 assertEquals(0, ToolRunner.run(testingUtility.getConfiguration(), canary, args)); 261 262 String baseZnode = testingUtility.getConfiguration() 263 .get(HConstants.ZOOKEEPER_ZNODE_PARENT, HConstants.DEFAULT_ZOOKEEPER_ZNODE_PARENT); 264 verify(sink, atLeastOnce()) 265 .publishReadTiming(eq(baseZnode), eq("localhost:" + port), anyLong()); 266 } 267}