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.master;
019
020import edu.umd.cs.findbugs.annotations.Nullable;
021import java.io.UnsupportedEncodingException;
022import java.nio.charset.StandardCharsets;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027import org.apache.hadoop.conf.Configuration;
028import org.apache.hadoop.hbase.ClusterMetrics;
029import org.apache.hadoop.hbase.HBaseTestingUtil;
030import org.apache.hadoop.hbase.RegionMetrics;
031import org.apache.hadoop.hbase.ServerMetrics;
032import org.apache.hadoop.hbase.ServerName;
033import org.apache.hadoop.hbase.ServerTask;
034import org.apache.hadoop.hbase.Size;
035import org.apache.hadoop.hbase.Stoppable;
036import org.apache.hadoop.hbase.TableName;
037import org.apache.hadoop.hbase.UserMetrics;
038import org.apache.hadoop.hbase.client.CompactionState;
039import org.apache.hadoop.hbase.client.RegionInfo;
040import org.apache.hadoop.hbase.client.RegionStatesCount;
041import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
042import org.apache.hadoop.hbase.replication.ReplicationLoadSink;
043import org.apache.hadoop.hbase.replication.ReplicationLoadSource;
044import org.apache.hadoop.hbase.testclassification.MasterTests;
045import org.apache.hadoop.hbase.testclassification.SmallTests;
046import org.apache.hadoop.hbase.util.Bytes;
047import org.junit.jupiter.api.AfterEach;
048import org.junit.jupiter.api.BeforeEach;
049import org.junit.jupiter.api.Tag;
050import org.junit.jupiter.api.Test;
051import org.mockito.Mockito;
052import org.slf4j.Logger;
053import org.slf4j.LoggerFactory;
054
055/**
056 * Test for RegionsRecoveryChore
057 */
058@Tag(MasterTests.TAG)
059@Tag(SmallTests.TAG)
060public class TestRegionsRecoveryChore {
061
062  private static final Logger LOG = LoggerFactory.getLogger(TestRegionsRecoveryChore.class);
063
064  private static final HBaseTestingUtil HBASE_TESTING_UTILITY = new HBaseTestingUtil();
065
066  private static final String UTF_8_CHARSET = StandardCharsets.UTF_8.name();
067
068  private HMaster hMaster;
069
070  private AssignmentManager assignmentManager;
071
072  private RegionsRecoveryChore regionsRecoveryChore;
073
074  private static int regionNo;
075  public static final byte[][] REGION_NAME_LIST =
076    new byte[][] { new byte[] { 114, 101, 103, 105, 111, 110, 50, 49, 95, 51 },
077      new byte[] { 114, 101, 103, 105, 111, 110, 50, 53, 95, 51 },
078      new byte[] { 114, 101, 103, 105, 111, 110, 50, 54, 95, 52 },
079      new byte[] { 114, 101, 103, 105, 111, 110, 51, 50, 95, 53 },
080      new byte[] { 114, 101, 103, 105, 111, 110, 51, 49, 95, 52 },
081      new byte[] { 114, 101, 103, 105, 111, 110, 51, 48, 95, 51 },
082      new byte[] { 114, 101, 103, 105, 111, 110, 50, 48, 95, 50 },
083      new byte[] { 114, 101, 103, 105, 111, 110, 50, 52, 95, 50 },
084      new byte[] { 114, 101, 103, 105, 111, 110, 50, 57, 95, 50 },
085      new byte[] { 114, 101, 103, 105, 111, 110, 51, 53, 95, 50 },
086      new byte[] { 114, 101, 103, 105, 111, 110, 49, 48, 56, 95, 49, 49 } };
087
088  private Configuration getCustomConf() {
089    Configuration conf = HBASE_TESTING_UTILITY.getConfiguration();
090    conf.setInt("hbase.master.regions.recovery.check.interval", 100);
091    return conf;
092  }
093
094  @BeforeEach
095  public void setUp() throws Exception {
096    this.hMaster = Mockito.mock(HMaster.class);
097    this.assignmentManager = Mockito.mock(AssignmentManager.class);
098    Mockito.when(hMaster.isInitialized()).thenReturn(true);
099  }
100
101  @AfterEach
102  public void tearDown() throws Exception {
103    Mockito.verifyNoMoreInteractions(this.hMaster);
104    Mockito.verifyNoMoreInteractions(this.assignmentManager);
105  }
106
107  @Test
108  public void testRegionReopensWithStoreRefConfig() throws Exception {
109    regionNo = 0;
110    ClusterMetrics clusterMetrics = TestRegionsRecoveryChore.getClusterMetrics(4);
111    final Map<ServerName, ServerMetrics> serverMetricsMap = clusterMetrics.getLiveServerMetrics();
112    LOG.debug("All Region Names with refCount....");
113    for (ServerMetrics serverMetrics : serverMetricsMap.values()) {
114      Map<byte[], RegionMetrics> regionMetricsMap = serverMetrics.getRegionMetrics();
115      for (RegionMetrics regionMetrics : regionMetricsMap.values()) {
116        LOG.debug("name: " + new String(regionMetrics.getRegionName()) + " refCount: "
117          + regionMetrics.getStoreRefCount());
118      }
119    }
120    Mockito.when(hMaster.getClusterMetrics()).thenReturn(clusterMetrics);
121    Mockito.when(hMaster.getAssignmentManager()).thenReturn(assignmentManager);
122    for (byte[] regionName : REGION_NAME_LIST) {
123      Mockito.when(assignmentManager.getRegionInfo(regionName))
124        .thenReturn(TestRegionsRecoveryChore.getRegionInfo(regionName));
125    }
126    Stoppable stoppable = new StoppableImplementation();
127    Configuration configuration = getCustomConf();
128    configuration.setInt("hbase.regions.recovery.store.file.ref.count", 300);
129    regionsRecoveryChore = new RegionsRecoveryChore(stoppable, configuration, hMaster);
130    regionsRecoveryChore.chore();
131
132    Mockito.verify(hMaster, Mockito.times(1)).isInitialized();
133
134    // Verify that we need to reopen regions of 2 tables
135    Mockito.verify(hMaster, Mockito.times(2)).reopenRegionsThrottled(Mockito.any(),
136      Mockito.anyList(), Mockito.anyLong(), Mockito.anyLong());
137    Mockito.verify(hMaster, Mockito.times(1)).getClusterMetrics();
138
139    // Verify that we need to reopen total 3 regions that have refCount > 300
140    Mockito.verify(hMaster, Mockito.times(3)).getAssignmentManager();
141    Mockito.verify(assignmentManager, Mockito.times(3)).getRegionInfo(Mockito.any(byte[].class));
142  }
143
144  @Test
145  public void testRegionReopensWithLessThreshold() throws Exception {
146    regionNo = 0;
147    ClusterMetrics clusterMetrics = TestRegionsRecoveryChore.getClusterMetrics(4);
148    final Map<ServerName, ServerMetrics> serverMetricsMap = clusterMetrics.getLiveServerMetrics();
149    LOG.debug("All Region Names with refCount....");
150    for (ServerMetrics serverMetrics : serverMetricsMap.values()) {
151      Map<byte[], RegionMetrics> regionMetricsMap = serverMetrics.getRegionMetrics();
152      for (RegionMetrics regionMetrics : regionMetricsMap.values()) {
153        LOG.debug("name: " + new String(regionMetrics.getRegionName()) + " refCount: "
154          + regionMetrics.getStoreRefCount());
155      }
156    }
157    Mockito.when(hMaster.getClusterMetrics()).thenReturn(clusterMetrics);
158    Mockito.when(hMaster.getAssignmentManager()).thenReturn(assignmentManager);
159    for (byte[] regionName : REGION_NAME_LIST) {
160      Mockito.when(assignmentManager.getRegionInfo(regionName))
161        .thenReturn(TestRegionsRecoveryChore.getRegionInfo(regionName));
162    }
163    Stoppable stoppable = new StoppableImplementation();
164    Configuration configuration = getCustomConf();
165    configuration.setInt("hbase.regions.recovery.store.file.ref.count", 400);
166    regionsRecoveryChore = new RegionsRecoveryChore(stoppable, configuration, hMaster);
167    regionsRecoveryChore.chore();
168
169    Mockito.verify(hMaster, Mockito.times(1)).isInitialized();
170
171    // Verify that we need to reopen regions of only 1 table
172    Mockito.verify(hMaster, Mockito.times(1)).reopenRegionsThrottled(Mockito.any(),
173      Mockito.anyList(), Mockito.anyLong(), Mockito.anyLong());
174    Mockito.verify(hMaster, Mockito.times(1)).getClusterMetrics();
175
176    // Verify that we need to reopen only 1 region with refCount > 400
177    Mockito.verify(hMaster, Mockito.times(1)).getAssignmentManager();
178    Mockito.verify(assignmentManager, Mockito.times(1)).getRegionInfo(Mockito.any(byte[].class));
179  }
180
181  @Test
182  public void testRegionReopensWithoutStoreRefConfig() throws Exception {
183    regionNo = 0;
184    ClusterMetrics clusterMetrics = TestRegionsRecoveryChore.getClusterMetrics(10);
185    final Map<ServerName, ServerMetrics> serverMetricsMap = clusterMetrics.getLiveServerMetrics();
186    LOG.debug("All Region Names with refCount....");
187    for (ServerMetrics serverMetrics : serverMetricsMap.values()) {
188      Map<byte[], RegionMetrics> regionMetricsMap = serverMetrics.getRegionMetrics();
189      for (RegionMetrics regionMetrics : regionMetricsMap.values()) {
190        LOG.debug("name: " + new String(regionMetrics.getRegionName()) + " refCount: "
191          + regionMetrics.getStoreRefCount());
192      }
193    }
194    Mockito.when(hMaster.getClusterMetrics()).thenReturn(clusterMetrics);
195    Mockito.when(hMaster.getAssignmentManager()).thenReturn(assignmentManager);
196    for (byte[] regionName : REGION_NAME_LIST) {
197      Mockito.when(assignmentManager.getRegionInfo(regionName))
198        .thenReturn(TestRegionsRecoveryChore.getRegionInfo(regionName));
199    }
200    Stoppable stoppable = new StoppableImplementation();
201    Configuration configuration = getCustomConf();
202    configuration.unset("hbase.regions.recovery.store.file.ref.count");
203    regionsRecoveryChore = new RegionsRecoveryChore(stoppable, configuration, hMaster);
204    regionsRecoveryChore.chore();
205
206    Mockito.verify(hMaster, Mockito.times(1)).isInitialized();
207
208    // Verify that by default the feature is turned off so no regions
209    // should be reopened
210    Mockito.verify(hMaster, Mockito.times(0)).reopenRegionsThrottled(Mockito.any(),
211      Mockito.anyList(), Mockito.anyLong(), Mockito.anyLong());
212
213    // default maxCompactedStoreFileRefCount is -1 (no regions to be reopened using AM)
214    Mockito.verify(hMaster, Mockito.times(0)).getAssignmentManager();
215    Mockito.verify(assignmentManager, Mockito.times(0)).getRegionInfo(Mockito.any(byte[].class));
216  }
217
218  @Test
219  public void testRegionReopensSkippedWhenMasterNotInitialized() throws Exception {
220    Mockito.when(hMaster.isInitialized()).thenReturn(false);
221
222    Stoppable stoppable = new StoppableImplementation();
223    Configuration configuration = getCustomConf();
224    configuration.setInt("hbase.regions.recovery.store.file.ref.count", 300);
225    regionsRecoveryChore = new RegionsRecoveryChore(stoppable, configuration, hMaster);
226    regionsRecoveryChore.chore();
227
228    Mockito.verify(hMaster, Mockito.times(1)).isInitialized();
229    Mockito.verify(hMaster, Mockito.times(0)).getClusterMetrics();
230    Mockito.verify(hMaster, Mockito.times(0)).reopenRegionsThrottled(Mockito.any(),
231      Mockito.anyList(), Mockito.anyLong(), Mockito.anyLong());
232    Mockito.verify(hMaster, Mockito.times(0)).getAssignmentManager();
233    Mockito.verify(assignmentManager, Mockito.times(0)).getRegionInfo(Mockito.any(byte[].class));
234  }
235
236  private static ClusterMetrics getClusterMetrics(int noOfLiveServer) {
237    ClusterMetrics clusterMetrics = new ClusterMetrics() {
238
239      @Nullable
240      @Override
241      public String getHBaseVersion() {
242        return null;
243      }
244
245      @Override
246      public List<ServerName> getDeadServerNames() {
247        return null;
248      }
249
250      @Override
251      public List<ServerName> getUnknownServerNames() {
252        return null;
253      }
254
255      @Override
256      public List<ServerName> getDecommissionedServerNames() {
257        return null;
258      }
259
260      @Override
261      public Map<ServerName, ServerMetrics> getLiveServerMetrics() {
262        Map<ServerName, ServerMetrics> liveServerMetrics = new HashMap<>();
263        for (int i = 0; i < noOfLiveServer; i++) {
264          ServerName serverName = ServerName.valueOf("rs_" + i, 16010, 12345);
265          liveServerMetrics.put(serverName, TestRegionsRecoveryChore.getServerMetrics(i + 3));
266        }
267        return liveServerMetrics;
268      }
269
270      @Nullable
271      @Override
272      public ServerName getMasterName() {
273        return null;
274      }
275
276      @Override
277      public List<ServerName> getBackupMasterNames() {
278        return null;
279      }
280
281      @Override
282      public List<RegionState> getRegionStatesInTransition() {
283        return null;
284      }
285
286      @Nullable
287      @Override
288      public String getClusterId() {
289        return null;
290      }
291
292      @Override
293      public List<String> getMasterCoprocessorNames() {
294        return null;
295      }
296
297      @Nullable
298      @Override
299      public Boolean getBalancerOn() {
300        return null;
301      }
302
303      @Override
304      public int getMasterInfoPort() {
305        return 0;
306      }
307
308      @Override
309      public List<ServerName> getServersName() {
310        return null;
311      }
312
313      @Override
314      public Map<TableName, RegionStatesCount> getTableRegionStatesCount() {
315        return null;
316      }
317
318      @Override
319      public List<ServerTask> getMasterTasks() {
320        return null;
321      }
322
323    };
324    return clusterMetrics;
325  }
326
327  private static ServerMetrics getServerMetrics(int noOfRegions) {
328    ServerMetrics serverMetrics = new ServerMetrics() {
329
330      @Override
331      public ServerName getServerName() {
332        return null;
333      }
334
335      @Override
336      public long getRequestCountPerSecond() {
337        return 0;
338      }
339
340      @Override
341      public long getRequestCount() {
342        return 0;
343      }
344
345      @Override
346      public long getReadRequestsCount() {
347        return 0;
348      }
349
350      @Override
351      public long getWriteRequestsCount() {
352        return 0;
353      }
354
355      @Override
356      public Size getUsedHeapSize() {
357        return null;
358      }
359
360      @Override
361      public Size getMaxHeapSize() {
362        return null;
363      }
364
365      @Override
366      public int getInfoServerPort() {
367        return 0;
368      }
369
370      @Override
371      public List<ReplicationLoadSource> getReplicationLoadSourceList() {
372        return null;
373      }
374
375      @Override
376      public Map<String, List<ReplicationLoadSource>> getReplicationLoadSourceMap() {
377        return null;
378      }
379
380      @Nullable
381      @Override
382      public ReplicationLoadSink getReplicationLoadSink() {
383        return null;
384      }
385
386      @Override
387      public Map<byte[], RegionMetrics> getRegionMetrics() {
388        Map<byte[], RegionMetrics> regionMetricsMap = new HashMap<>();
389        for (int i = 0; i < noOfRegions; i++) {
390          byte[] regionName = Bytes.toBytes("region" + regionNo + "_" + i);
391          regionMetricsMap.put(regionName,
392            TestRegionsRecoveryChore.getRegionMetrics(regionName, 100 * i));
393          ++regionNo;
394        }
395        return regionMetricsMap;
396      }
397
398      @Override
399      public Map<byte[], UserMetrics> getUserMetrics() {
400        return new HashMap<>();
401      }
402
403      @Override
404      public Set<String> getCoprocessorNames() {
405        return null;
406      }
407
408      @Override
409      public long getReportTimestamp() {
410        return 0;
411      }
412
413      @Override
414      public long getLastReportTimestamp() {
415        return 0;
416      }
417
418      @Override
419      public List<ServerTask> getTasks() {
420        return null;
421      }
422
423      @Override
424      public Map<String, Integer> getRegionCachedInfo() {
425        return new HashMap<>();
426      }
427
428      @Override
429      public Map<String, Integer> getRegionColdDataSize() {
430        return new HashMap<>();
431      }
432
433    };
434    return serverMetrics;
435  }
436
437  private static RegionMetrics getRegionMetrics(byte[] regionName, int compactedStoreRefCount) {
438    RegionMetrics regionMetrics = new RegionMetrics() {
439
440      @Override
441      public byte[] getRegionName() {
442        return regionName;
443      }
444
445      @Override
446      public int getStoreCount() {
447        return 0;
448      }
449
450      @Override
451      public int getStoreFileCount() {
452        return 0;
453      }
454
455      @Override
456      public Size getStoreFileSize() {
457        return null;
458      }
459
460      @Override
461      public Size getMemStoreSize() {
462        return null;
463      }
464
465      @Override
466      public long getReadRequestCount() {
467        return 0;
468      }
469
470      @Override
471      public long getWriteRequestCount() {
472        return 0;
473      }
474
475      @Override
476      public long getCpRequestCount() {
477        return 0;
478      }
479
480      @Override
481      public long getFilteredReadRequestCount() {
482        return 0;
483      }
484
485      @Override
486      public Size getStoreFileIndexSize() {
487        return null;
488      }
489
490      @Override
491      public Size getStoreFileRootLevelIndexSize() {
492        return null;
493      }
494
495      @Override
496      public Size getStoreFileUncompressedDataIndexSize() {
497        return null;
498      }
499
500      @Override
501      public Size getBloomFilterSize() {
502        return null;
503      }
504
505      @Override
506      public long getCompactingCellCount() {
507        return 0;
508      }
509
510      @Override
511      public long getCompactedCellCount() {
512        return 0;
513      }
514
515      @Override
516      public long getCompletedSequenceId() {
517        return 0;
518      }
519
520      @Override
521      public Map<byte[], Long> getStoreSequenceId() {
522        return null;
523      }
524
525      @Override
526      public Size getUncompressedStoreFileSize() {
527        return null;
528      }
529
530      @Override
531      public float getDataLocality() {
532        return 0;
533      }
534
535      @Override
536      public long getLastMajorCompactionTimestamp() {
537        return 0;
538      }
539
540      @Override
541      public int getStoreRefCount() {
542        return compactedStoreRefCount;
543      }
544
545      @Override
546      public int getMaxCompactedStoreFileRefCount() {
547        return compactedStoreRefCount;
548      }
549
550      @Override
551      public float getDataLocalityForSsd() {
552        return 0;
553      }
554
555      @Override
556      public long getBlocksLocalWeight() {
557        return 0;
558      }
559
560      @Override
561      public long getBlocksLocalWithSsdWeight() {
562        return 0;
563      }
564
565      @Override
566      public long getBlocksTotalWeight() {
567        return 0;
568      }
569
570      @Override
571      public CompactionState getCompactionState() {
572        return null;
573      }
574
575      @Override
576      public Size getRegionSizeMB() {
577        return null;
578      }
579
580      @Override
581      public float getCurrentRegionCachedRatio() {
582        return 0.0f;
583      }
584
585      @Override
586      public float getCurrentRegionColdDataRatio() {
587        return 0.0f;
588      }
589    };
590    return regionMetrics;
591  }
592
593  private static RegionInfo getRegionInfo(byte[] regionNameBytes) {
594    RegionInfo regionInfo = new RegionInfo() {
595
596      @Override
597      public String getShortNameToLog() {
598        return null;
599      }
600
601      @Override
602      public long getRegionId() {
603        return 0;
604      }
605
606      @Override
607      public byte[] getRegionName() {
608        return new byte[0];
609      }
610
611      @Override
612      public String getRegionNameAsString() {
613        try {
614          return new String(regionNameBytes, UTF_8_CHARSET);
615        } catch (UnsupportedEncodingException e) {
616          return "";
617        }
618      }
619
620      @Override
621      public String getEncodedName() {
622        return null;
623      }
624
625      @Override
626      public byte[] getEncodedNameAsBytes() {
627        return new byte[0];
628      }
629
630      @Override
631      public byte[] getStartKey() {
632        return new byte[0];
633      }
634
635      @Override
636      public byte[] getEndKey() {
637        return new byte[0];
638      }
639
640      @Override
641      public TableName getTable() {
642        String regionName;
643        try {
644          regionName = new String(regionNameBytes, UTF_8_CHARSET);
645        } catch (UnsupportedEncodingException e) {
646          regionName = "";
647        }
648        int regionNo = Integer.parseInt(regionName.split("_")[1]);
649        TableName tableName = TableName.valueOf("table_" + regionNo % 3);
650        return tableName;
651      }
652
653      @Override
654      public int getReplicaId() {
655        return 0;
656      }
657
658      @Override
659      public boolean isSplit() {
660        return false;
661      }
662
663      @Override
664      public boolean isOffline() {
665        return false;
666      }
667
668      @Override
669      public boolean isSplitParent() {
670        return false;
671      }
672
673      @Override
674      public boolean isMetaRegion() {
675        return false;
676      }
677
678      @Override
679      public boolean containsRange(byte[] rangeStartKey, byte[] rangeEndKey) {
680        return false;
681      }
682
683      @Override
684      public boolean containsRow(byte[] row) {
685        return false;
686      }
687
688    };
689    return regionInfo;
690  }
691
692  /**
693   * Simple helper class that just keeps track of whether or not its stopped.
694   */
695  private static class StoppableImplementation implements Stoppable {
696
697    private volatile boolean stop = false;
698
699    @Override
700    public void stop(String why) {
701      this.stop = true;
702    }
703
704    @Override
705    public boolean isStopped() {
706      return this.stop;
707    }
708
709  }
710
711}