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