View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to you under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    * http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.hadoop.hbase.client;
18  
19  import static org.junit.Assert.*;
20  
21  import org.apache.hadoop.hbase.HConstants;
22  import org.apache.hadoop.hbase.HRegionInfo;
23  import org.apache.hadoop.hbase.HRegionLocation;
24  import org.apache.hadoop.hbase.TableName;
25  import org.apache.hadoop.hbase.client.HTableMultiplexer.FlushWorker;
26  import org.apache.hadoop.hbase.client.HTableMultiplexer.PutStatus;
27  import org.apache.hadoop.hbase.testclassification.SmallTests;
28  import org.junit.Before;
29  import org.junit.Test;
30  import org.junit.experimental.categories.Category;
31  import org.mockito.invocation.InvocationOnMock;
32  import org.mockito.stubbing.Answer;
33  
34  import java.io.IOException;
35  import java.util.concurrent.LinkedBlockingQueue;
36  import java.util.concurrent.ScheduledExecutorService;
37  import java.util.concurrent.TimeUnit;
38  import java.util.concurrent.atomic.AtomicInteger;
39  import java.util.concurrent.atomic.AtomicLong;
40  
41  import static java.nio.charset.StandardCharsets.UTF_8;
42  import static org.mockito.Matchers.any;
43  import static org.mockito.Matchers.anyBoolean;
44  import static org.mockito.Matchers.anyInt;
45  import static org.mockito.Mockito.doCallRealMethod;
46  import static org.mockito.Mockito.eq;
47  import static org.mockito.Mockito.mock;
48  import static org.mockito.Mockito.times;
49  import static org.mockito.Mockito.verify;
50  import static org.mockito.Mockito.when;
51  
52  @Category(SmallTests.class)
53  public class TestHTableMultiplexerViaMocks {
54  
55    private static final int NUM_RETRIES = HConstants.DEFAULT_HBASE_CLIENT_RETRIES_NUMBER;
56    private HTableMultiplexer mockMultiplexer;
57    private ClusterConnection mockConnection;
58    private HRegionLocation mockRegionLocation;
59    private HRegionInfo mockRegionInfo;
60  
61    private TableName tableName;
62    private Put put;
63  
64    @Before
65    public void setupTest() {
66      mockMultiplexer = mock(HTableMultiplexer.class);
67      mockConnection = mock(ClusterConnection.class);
68      mockRegionLocation = mock(HRegionLocation.class);
69      mockRegionInfo = mock(HRegionInfo.class);
70  
71      tableName = TableName.valueOf("my_table");
72      put = new Put(getBytes("row1"));
73      put.addColumn(getBytes("f1"), getBytes("q1"), getBytes("v11"));
74      put.addColumn(getBytes("f1"), getBytes("q2"), getBytes("v12"));
75      put.addColumn(getBytes("f2"), getBytes("q1"), getBytes("v21"));
76  
77      // Call the real put(TableName, Put, int) method
78      when(mockMultiplexer.put(any(TableName.class), any(Put.class), anyInt())).thenCallRealMethod();
79  
80      // Return the mocked ClusterConnection
81      when(mockMultiplexer.getConnection()).thenReturn(mockConnection);
82  
83      // Return the regionInfo from the region location
84      when(mockRegionLocation.getRegionInfo()).thenReturn(mockRegionInfo);
85  
86      // Make sure this RegionInfo points to our table
87      when(mockRegionInfo.getTable()).thenReturn(tableName);
88    }
89  
90    @Test public void useCacheOnInitialPut() throws Exception {
91      mockMultiplexer.put(tableName, put, NUM_RETRIES);
92  
93      verify(mockMultiplexer)._put(tableName, put, NUM_RETRIES, false);
94    }
95  
96    @Test public void nonNullLocationQueuesPut() throws Exception {
97      final LinkedBlockingQueue<PutStatus> queue = new LinkedBlockingQueue<>();
98  
99      // Call the real method for _put(TableName, Put, int, boolean)
100     when(mockMultiplexer._put(any(TableName.class), any(Put.class), anyInt(), anyBoolean())).thenCallRealMethod();
101 
102     // Return a region location
103     when(mockConnection.getRegionLocation(tableName, put.getRow(), false)).thenReturn(mockRegionLocation);
104     when(mockMultiplexer.getQueue(mockRegionLocation)).thenReturn(queue);
105 
106     assertTrue("Put should have been queued", mockMultiplexer.put(tableName, put, NUM_RETRIES));
107 
108     assertEquals(1, queue.size());
109     final PutStatus ps = queue.take();
110     assertEquals(put, ps.put);
111     assertEquals(mockRegionInfo, ps.regionInfo);
112   }
113 
114   @Test public void ignoreCacheOnRetriedPut() throws Exception {
115     FlushWorker mockFlushWorker = mock(FlushWorker.class);
116     ScheduledExecutorService mockExecutor = mock(ScheduledExecutorService.class);
117     final AtomicInteger retryInQueue = new AtomicInteger(0);
118     final AtomicLong totalFailedPuts = new AtomicLong(0L);
119     final int maxRetryInQueue = 20;
120     final long delay = 100L;
121 
122     final PutStatus ps = new PutStatus(mockRegionInfo, put, NUM_RETRIES);
123 
124     // Call the real resubmitFailedPut(PutStatus, HRegionLocation) method
125     when(mockFlushWorker.resubmitFailedPut(any(PutStatus.class), any(HRegionLocation.class))).thenCallRealMethod();
126     // Succeed on the re-submit without caching
127     when(mockMultiplexer._put(tableName, put, NUM_RETRIES - 1, true)).thenReturn(true);
128 
129     // Stub out the getters for resubmitFailedPut(PutStatus, HRegionLocation)
130     when(mockFlushWorker.getExecutor()).thenReturn(mockExecutor);
131     when(mockFlushWorker.getNextDelay(anyInt())).thenReturn(delay);
132     when(mockFlushWorker.getMultiplexer()).thenReturn(mockMultiplexer);
133     when(mockFlushWorker.getRetryInQueue()).thenReturn(retryInQueue);
134     when(mockFlushWorker.getMaxRetryInQueue()).thenReturn(maxRetryInQueue);
135     when(mockFlushWorker.getTotalFailedPutCount()).thenReturn(totalFailedPuts);
136 
137     // When a Runnable is scheduled, run that Runnable
138     when(mockExecutor.schedule(any(Runnable.class), eq(delay), eq(TimeUnit.MILLISECONDS))).thenAnswer(
139         new Answer<Void>() {
140           @Override
141           public Void answer(InvocationOnMock invocation) throws Throwable {
142             // Before we run this, should have one retry in progress.
143             assertEquals(1L, retryInQueue.get());
144 
145             Object[] args = invocation.getArguments();
146             assertEquals(3, args.length);
147             assertTrue("Argument should be an instance of Runnable", args[0] instanceof Runnable);
148             Runnable runnable = (Runnable) args[0];
149             runnable.run();
150             return null;
151           }
152         });
153 
154     // The put should be rescheduled
155     assertTrue("Put should have been rescheduled", mockFlushWorker.resubmitFailedPut(ps, mockRegionLocation));
156 
157     verify(mockMultiplexer)._put(tableName, put, NUM_RETRIES - 1, true);
158     assertEquals(0L, totalFailedPuts.get());
159     // Net result should be zero (added one before rerunning, subtracted one after running).
160     assertEquals(0L, retryInQueue.get());
161   }
162 
163   @SuppressWarnings("deprecation")
164   @Test public void testConnectionClosing() throws IOException {
165     doCallRealMethod().when(mockMultiplexer).close();
166     // If the connection is not closed
167     when(mockConnection.isClosed()).thenReturn(false);
168 
169     mockMultiplexer.close();
170 
171     // We should close it
172     verify(mockConnection).close();
173   }
174 
175   @SuppressWarnings("deprecation")
176   @Test public void testClosingAlreadyClosedConnection() throws IOException {
177     doCallRealMethod().when(mockMultiplexer).close();
178     // If the connection is already closed
179     when(mockConnection.isClosed()).thenReturn(true);
180 
181     mockMultiplexer.close();
182 
183     // We should not close it again
184     verify(mockConnection, times(0)).close();
185   }
186 
187   /**
188    * @return UTF-8 byte representation for {@code str}
189    */
190   private static byte[] getBytes(String str) {
191     return str.getBytes(UTF_8);
192   }
193 }