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}