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.assignment;
019
020import static org.apache.hadoop.hbase.TestMetaTableAccessor.assertEmptyMetaLocation;
021import static org.junit.jupiter.api.Assertions.assertEquals;
022import static org.junit.jupiter.api.Assertions.assertFalse;
023import static org.junit.jupiter.api.Assertions.assertNotEquals;
024import static org.junit.jupiter.api.Assertions.assertNotNull;
025import static org.junit.jupiter.api.Assertions.assertNull;
026import static org.junit.jupiter.api.Assertions.assertTrue;
027
028import java.io.IOException;
029import java.util.ArrayList;
030import java.util.List;
031import java.util.concurrent.ThreadLocalRandom;
032import java.util.concurrent.TimeUnit;
033import java.util.concurrent.atomic.AtomicBoolean;
034import org.apache.hadoop.hbase.CatalogFamilyFormat;
035import org.apache.hadoop.hbase.Cell;
036import org.apache.hadoop.hbase.HBaseTestingUtil;
037import org.apache.hadoop.hbase.HConstants;
038import org.apache.hadoop.hbase.MetaTableAccessor;
039import org.apache.hadoop.hbase.ServerName;
040import org.apache.hadoop.hbase.TableName;
041import org.apache.hadoop.hbase.TableNameTestExtension;
042import org.apache.hadoop.hbase.client.Admin;
043import org.apache.hadoop.hbase.client.Get;
044import org.apache.hadoop.hbase.client.Put;
045import org.apache.hadoop.hbase.client.RegionInfo;
046import org.apache.hadoop.hbase.client.RegionInfoBuilder;
047import org.apache.hadoop.hbase.client.Result;
048import org.apache.hadoop.hbase.client.Table;
049import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
050import org.apache.hadoop.hbase.master.RegionState;
051import org.apache.hadoop.hbase.regionserver.HRegion;
052import org.apache.hadoop.hbase.testclassification.MasterTests;
053import org.apache.hadoop.hbase.testclassification.MediumTests;
054import org.apache.hadoop.hbase.util.Bytes;
055import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
056import org.apache.hadoop.hbase.util.ManualEnvironmentEdge;
057import org.junit.jupiter.api.AfterAll;
058import org.junit.jupiter.api.BeforeAll;
059import org.junit.jupiter.api.Tag;
060import org.junit.jupiter.api.Test;
061import org.junit.jupiter.api.extension.RegisterExtension;
062
063import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
064
065@Tag(MasterTests.TAG)
066@Tag(MediumTests.TAG)
067public class TestRegionStateStore {
068
069  private static HBaseTestingUtil UTIL = new HBaseTestingUtil();
070
071  @RegisterExtension
072  public final TableNameTestExtension name = new TableNameTestExtension();
073
074  @BeforeAll
075  public static void beforeClass() throws Exception {
076    UTIL.startMiniCluster();
077  }
078
079  @AfterAll
080  public static void tearDown() throws Exception {
081    UTIL.shutdownMiniCluster();
082  }
083
084  @Test
085  public void testVisitMetaForRegionExistingRegion() throws Exception {
086    final TableName tableName = TableName.valueOf("testVisitMetaForRegion");
087    UTIL.createTable(tableName, "cf");
088    final List<HRegion> regions = UTIL.getHBaseCluster().getRegions(tableName);
089    final String encodedName = regions.get(0).getRegionInfo().getEncodedName();
090    final RegionStateStore regionStateStore =
091      UTIL.getHBaseCluster().getMaster().getAssignmentManager().getRegionStateStore();
092    final AtomicBoolean visitorCalled = new AtomicBoolean(false);
093    regionStateStore.visitMetaForRegion(encodedName, new RegionStateStore.RegionStateVisitor() {
094      @Override
095      public void visitRegionState(Result result, RegionInfo regionInfo, RegionState.State state,
096        ServerName regionLocation, ServerName lastHost, long openSeqNum) {
097        assertEquals(encodedName, regionInfo.getEncodedName());
098        visitorCalled.set(true);
099      }
100    });
101    assertTrue(visitorCalled.get(), "Visitor has not been called.");
102  }
103
104  @Test
105  public void testVisitMetaForBadRegionState() throws Exception {
106    final TableName tableName = TableName.valueOf("testVisitMetaForBadRegionState");
107    UTIL.createTable(tableName, "cf");
108    final List<HRegion> regions = UTIL.getHBaseCluster().getRegions(tableName);
109    final String encodedName = regions.get(0).getRegionInfo().getEncodedName();
110    final RegionStateStore regionStateStore =
111      UTIL.getHBaseCluster().getMaster().getAssignmentManager().getRegionStateStore();
112
113    // add the BAD_STATE which does not exist in enum RegionState.State
114    Put put =
115      new Put(regions.get(0).getRegionInfo().getRegionName(), EnvironmentEdgeManager.currentTime());
116    put.addColumn(HConstants.CATALOG_FAMILY, HConstants.STATE_QUALIFIER,
117      Bytes.toBytes("BAD_STATE"));
118
119    try (Table table = UTIL.getConnection().getTable(TableName.META_TABLE_NAME)) {
120      table.put(put);
121    }
122
123    final AtomicBoolean visitorCalled = new AtomicBoolean(false);
124    regionStateStore.visitMetaForRegion(encodedName, new RegionStateStore.RegionStateVisitor() {
125      @Override
126      public void visitRegionState(Result result, RegionInfo regionInfo, RegionState.State state,
127        ServerName regionLocation, ServerName lastHost, long openSeqNum) {
128        assertEquals(encodedName, regionInfo.getEncodedName());
129        assertNull(state);
130        visitorCalled.set(true);
131      }
132    });
133    assertTrue(visitorCalled.get(), "Visitor has not been called.");
134  }
135
136  @Test
137  public void testVisitMetaForRegionNonExistingRegion() throws Exception {
138    final String encodedName = "fakeencodedregionname";
139    final RegionStateStore regionStateStore =
140      UTIL.getHBaseCluster().getMaster().getAssignmentManager().getRegionStateStore();
141    final AtomicBoolean visitorCalled = new AtomicBoolean(false);
142    regionStateStore.visitMetaForRegion(encodedName, new RegionStateStore.RegionStateVisitor() {
143      @Override
144      public void visitRegionState(Result result, RegionInfo regionInfo, RegionState.State state,
145        ServerName regionLocation, ServerName lastHost, long openSeqNum) {
146        visitorCalled.set(true);
147      }
148    });
149    assertFalse(visitorCalled.get(), "Visitor has been called, but it shouldn't.");
150  }
151
152  @Test
153  public void testMetaLocationForRegionReplicasIsAddedAtRegionSplit() throws IOException {
154    long regionId = EnvironmentEdgeManager.currentTime();
155    ServerName serverName0 =
156      ServerName.valueOf("foo", 60010, ThreadLocalRandom.current().nextLong());
157    TableName tableName = name.getTableName();
158    RegionInfo parent = RegionInfoBuilder.newBuilder(tableName)
159      .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false)
160      .setRegionId(regionId).setReplicaId(0).build();
161
162    RegionInfo splitA = RegionInfoBuilder.newBuilder(tableName)
163      .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(Bytes.toBytes("a")).setSplit(false)
164      .setRegionId(regionId + 1).setReplicaId(0).build();
165    RegionInfo splitB = RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("a"))
166      .setEndKey(HConstants.EMPTY_END_ROW).setSplit(false).setRegionId(regionId + 1).setReplicaId(0)
167      .build();
168    List<RegionInfo> regionInfos = Lists.newArrayList(parent);
169    MetaTableAccessor.addRegionsToMeta(UTIL.getConnection(), regionInfos, 3);
170    final RegionStateStore regionStateStore =
171      UTIL.getHBaseCluster().getMaster().getAssignmentManager().getRegionStateStore();
172    regionStateStore.splitRegion(parent, splitA, splitB, serverName0,
173      TableDescriptorBuilder.newBuilder(tableName).setRegionReplication(3).build());
174    try (Table meta = MetaTableAccessor.getMetaHTable(UTIL.getConnection())) {
175      assertEmptyMetaLocation(meta, splitA.getRegionName(), 1);
176      assertEmptyMetaLocation(meta, splitA.getRegionName(), 2);
177      assertEmptyMetaLocation(meta, splitB.getRegionName(), 1);
178      assertEmptyMetaLocation(meta, splitB.getRegionName(), 2);
179    }
180  }
181
182  @Test
183  public void testEmptyMetaDaughterLocationDuringSplit() throws IOException {
184    TableName tableName = name.getTableName();
185    long regionId = EnvironmentEdgeManager.currentTime();
186    ServerName serverName0 =
187      ServerName.valueOf("foo", 60010, ThreadLocalRandom.current().nextLong());
188    RegionInfo parent = RegionInfoBuilder.newBuilder(tableName)
189      .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false)
190      .setRegionId(regionId).setReplicaId(0).build();
191    RegionInfo splitA = RegionInfoBuilder.newBuilder(tableName)
192      .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(Bytes.toBytes("a")).setSplit(false)
193      .setRegionId(regionId + 1).setReplicaId(0).build();
194    RegionInfo splitB = RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("a"))
195      .setEndKey(HConstants.EMPTY_END_ROW).setSplit(false).setRegionId(regionId + 1).setReplicaId(0)
196      .build();
197    List<RegionInfo> regionInfos = Lists.newArrayList(parent);
198    MetaTableAccessor.addRegionsToMeta(UTIL.getConnection(), regionInfos, 3);
199    final RegionStateStore regionStateStore =
200      UTIL.getHBaseCluster().getMaster().getAssignmentManager().getRegionStateStore();
201    regionStateStore.splitRegion(parent, splitA, splitB, serverName0,
202      TableDescriptorBuilder.newBuilder(tableName).setRegionReplication(3).build());
203    try (Table meta = MetaTableAccessor.getMetaHTable(UTIL.getConnection())) {
204      Get get1 = new Get(splitA.getRegionName());
205      Result resultA = meta.get(get1);
206      Cell serverCellA = resultA.getColumnLatestCell(HConstants.CATALOG_FAMILY,
207        CatalogFamilyFormat.getServerColumn(splitA.getReplicaId()));
208      Cell startCodeCellA = resultA.getColumnLatestCell(HConstants.CATALOG_FAMILY,
209        CatalogFamilyFormat.getStartCodeColumn(splitA.getReplicaId()));
210      assertNull(serverCellA);
211      assertNull(startCodeCellA);
212
213      Get get2 = new Get(splitB.getRegionName());
214      Result resultB = meta.get(get2);
215      Cell serverCellB = resultB.getColumnLatestCell(HConstants.CATALOG_FAMILY,
216        CatalogFamilyFormat.getServerColumn(splitB.getReplicaId()));
217      Cell startCodeCellB = resultB.getColumnLatestCell(HConstants.CATALOG_FAMILY,
218        CatalogFamilyFormat.getStartCodeColumn(splitB.getReplicaId()));
219      assertNull(serverCellB);
220      assertNull(startCodeCellB);
221    }
222  }
223
224  @Test
225  public void testMetaLocationForRegionReplicasIsAddedAtRegionMerge() throws IOException {
226    long regionId = EnvironmentEdgeManager.currentTime();
227    ServerName serverName0 =
228      ServerName.valueOf("foo", 60010, ThreadLocalRandom.current().nextLong());
229
230    TableName tableName = name.getTableName();
231    RegionInfo parentA = RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("a"))
232      .setEndKey(HConstants.EMPTY_END_ROW).setSplit(false).setRegionId(regionId).setReplicaId(0)
233      .build();
234
235    RegionInfo parentB = RegionInfoBuilder.newBuilder(tableName)
236      .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(Bytes.toBytes("a")).setSplit(false)
237      .setRegionId(regionId).setReplicaId(0).build();
238    RegionInfo merged = RegionInfoBuilder.newBuilder(tableName)
239      .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false)
240      .setRegionId(regionId + 1).setReplicaId(0).build();
241
242    final RegionStateStore regionStateStore =
243      UTIL.getHBaseCluster().getMaster().getAssignmentManager().getRegionStateStore();
244
245    try (Table meta = MetaTableAccessor.getMetaHTable(UTIL.getConnection())) {
246      List<RegionInfo> regionInfos = Lists.newArrayList(parentA, parentB);
247      MetaTableAccessor.addRegionsToMeta(UTIL.getConnection(), regionInfos, 3);
248      regionStateStore.mergeRegions(merged, new RegionInfo[] { parentA, parentB }, serverName0,
249        TableDescriptorBuilder.newBuilder(tableName).setRegionReplication(3).build());
250      assertEmptyMetaLocation(meta, merged.getRegionName(), 1);
251      assertEmptyMetaLocation(meta, merged.getRegionName(), 2);
252    }
253  }
254
255  @Test
256  public void testMastersSystemTimeIsUsedInMergeRegions() throws IOException {
257    long regionId = EnvironmentEdgeManager.currentTime();
258    TableName tableName = name.getTableName();
259
260    RegionInfo regionInfoA = RegionInfoBuilder.newBuilder(tableName)
261      .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(new byte[] { 'a' }).setSplit(false)
262      .setRegionId(regionId).setReplicaId(0).build();
263
264    RegionInfo regionInfoB = RegionInfoBuilder.newBuilder(tableName).setStartKey(new byte[] { 'a' })
265      .setEndKey(HConstants.EMPTY_END_ROW).setSplit(false).setRegionId(regionId).setReplicaId(0)
266      .build();
267    RegionInfo mergedRegionInfo = RegionInfoBuilder.newBuilder(tableName)
268      .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false)
269      .setRegionId(regionId).setReplicaId(0).build();
270
271    ServerName sn = ServerName.valueOf("bar", 0, 0);
272    try (Table meta = MetaTableAccessor.getMetaHTable(UTIL.getConnection())) {
273      List<RegionInfo> regionInfos = Lists.newArrayList(regionInfoA, regionInfoB);
274      MetaTableAccessor.addRegionsToMeta(UTIL.getConnection(), regionInfos, 1);
275
276      // write the serverName column with a big current time, but set the masters time as even
277      // bigger. When region merge deletes the rows for regionA and regionB, the serverName columns
278      // should not be seen by the following get
279      long serverNameTime = EnvironmentEdgeManager.currentTime() + 100000000;
280      long masterSystemTime = EnvironmentEdgeManager.currentTime() + 123456789;
281
282      // write the serverName columns
283      MetaTableAccessor.updateRegionLocation(UTIL.getConnection(), regionInfoA, sn, 1,
284        serverNameTime);
285
286      // assert that we have the serverName column with expected ts
287      Get get = new Get(mergedRegionInfo.getRegionName());
288      Result result = meta.get(get);
289      Cell serverCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY,
290        CatalogFamilyFormat.getServerColumn(0));
291      assertNotNull(serverCell);
292      assertEquals(serverNameTime, serverCell.getTimestamp());
293
294      final RegionStateStore regionStateStore =
295        UTIL.getHBaseCluster().getMaster().getAssignmentManager().getRegionStateStore();
296
297      ManualEnvironmentEdge edge = new ManualEnvironmentEdge();
298      edge.setValue(masterSystemTime);
299      EnvironmentEdgeManager.injectEdge(edge);
300      try {
301        // now merge the regions, effectively deleting the rows for region a and b.
302        regionStateStore.mergeRegions(mergedRegionInfo,
303          new RegionInfo[] { regionInfoA, regionInfoB }, sn,
304          TableDescriptorBuilder.newBuilder(tableName).build());
305      } finally {
306        EnvironmentEdgeManager.reset();
307      }
308
309      result = meta.get(get);
310      serverCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY,
311        CatalogFamilyFormat.getServerColumn(0));
312      Cell startCodeCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY,
313        CatalogFamilyFormat.getStartCodeColumn(0));
314      Cell seqNumCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY,
315        CatalogFamilyFormat.getSeqNumColumn(0));
316      assertNull(serverCell);
317      assertNull(startCodeCell);
318      assertNull(seqNumCell);
319    }
320  }
321
322  /**
323   * Test for HBASE-23044.
324   */
325  @Test
326  public void testGetMergeRegions() throws Exception {
327    TableName tn = name.getTableName();
328    UTIL.createMultiRegionTable(tn, Bytes.toBytes("CF"), 4);
329    UTIL.waitTableAvailable(tn);
330    Admin admin = UTIL.getAdmin();
331    List<RegionInfo> regions = admin.getRegions(tn);
332    assertEquals(4, regions.size());
333    admin
334      .mergeRegionsAsync(
335        new byte[][] { regions.get(0).getRegionName(), regions.get(1).getRegionName() }, false)
336      .get(60, TimeUnit.SECONDS);
337    admin
338      .mergeRegionsAsync(
339        new byte[][] { regions.get(2).getRegionName(), regions.get(3).getRegionName() }, false)
340      .get(60, TimeUnit.SECONDS);
341
342    List<RegionInfo> mergedRegions = admin.getRegions(tn);
343    assertEquals(2, mergedRegions.size());
344    RegionInfo mergedRegion0 = mergedRegions.get(0);
345    RegionInfo mergedRegion1 = mergedRegions.get(1);
346
347    final RegionStateStore regionStateStore =
348      UTIL.getHBaseCluster().getMaster().getAssignmentManager().getRegionStateStore();
349
350    List<RegionInfo> mergeParents = regionStateStore.getMergeRegions(mergedRegion0);
351    assertTrue(mergeParents.contains(regions.get(0)));
352    assertTrue(mergeParents.contains(regions.get(1)));
353    mergeParents = regionStateStore.getMergeRegions(mergedRegion1);
354    assertTrue(mergeParents.contains(regions.get(2)));
355    assertTrue(mergeParents.contains(regions.get(3)));
356
357    // Delete merge qualifiers for mergedRegion0, then cannot getMergeRegions again
358    regionStateStore.deleteMergeQualifiers(mergedRegion0);
359    mergeParents = regionStateStore.getMergeRegions(mergedRegion0);
360    assertNull(mergeParents);
361
362    mergeParents = regionStateStore.getMergeRegions(mergedRegion1);
363    assertTrue(mergeParents.contains(regions.get(2)));
364    assertTrue(mergeParents.contains(regions.get(3)));
365  }
366
367  @Test
368  public void testAddMergeRegions() throws IOException {
369    TableName tn = name.getTableName();
370    Put put = new Put(Bytes.toBytes(name.getTableName().getNameAsString()));
371    List<RegionInfo> ris = new ArrayList<>();
372    int limit = 10;
373    byte[] previous = HConstants.EMPTY_START_ROW;
374    for (int i = 0; i < limit; i++) {
375      RegionInfo ri =
376        RegionInfoBuilder.newBuilder(tn).setStartKey(previous).setEndKey(Bytes.toBytes(i)).build();
377      ris.add(ri);
378    }
379    put = RegionStateStore.addMergeRegions(put, ris);
380    List<Cell> cells = put.getFamilyCellMap().get(HConstants.CATALOG_FAMILY);
381    String previousQualifier = null;
382    assertEquals(limit, cells.size());
383    for (Cell cell : cells) {
384      String qualifier = Bytes.toString(cell.getQualifierArray());
385      assertTrue(qualifier.startsWith(HConstants.MERGE_QUALIFIER_PREFIX_STR));
386      assertNotEquals(qualifier, previousQualifier);
387      previousQualifier = qualifier;
388    }
389  }
390
391  @Test
392  public void testMetaLocationForRegionReplicasIsRemovedAtTableDeletion() throws IOException {
393    long regionId = EnvironmentEdgeManager.currentTime();
394    TableName tableName = name.getTableName();
395    RegionInfo primary = RegionInfoBuilder.newBuilder(tableName)
396      .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false)
397      .setRegionId(regionId).setReplicaId(0).build();
398
399    try (Table meta = MetaTableAccessor.getMetaHTable(UTIL.getConnection())) {
400      List<RegionInfo> regionInfos = Lists.newArrayList(primary);
401      MetaTableAccessor.addRegionsToMeta(UTIL.getConnection(), regionInfos, 3);
402      final RegionStateStore regionStateStore =
403        UTIL.getHBaseCluster().getMaster().getAssignmentManager().getRegionStateStore();
404      regionStateStore.removeRegionReplicas(tableName, 3, 1);
405      Get get = new Get(primary.getRegionName());
406      Result result = meta.get(get);
407      for (int replicaId = 0; replicaId < 3; replicaId++) {
408        Cell serverCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY,
409          CatalogFamilyFormat.getServerColumn(replicaId));
410        Cell startCodeCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY,
411          CatalogFamilyFormat.getStartCodeColumn(replicaId));
412        Cell stateCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY,
413          CatalogFamilyFormat.getRegionStateColumn(replicaId));
414        Cell snCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY,
415          CatalogFamilyFormat.getServerNameColumn(replicaId));
416        if (replicaId == 0) {
417          assertNotNull(stateCell);
418        } else {
419          assertNull(serverCell);
420          assertNull(startCodeCell);
421          assertNull(stateCell);
422          assertNull(snCell);
423        }
424      }
425    }
426  }
427}