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.client;
019
020import static org.awaitility.Awaitility.await;
021import static org.junit.jupiter.api.Assertions.assertEquals;
022import static org.junit.jupiter.api.Assertions.assertNotNull;
023
024import java.io.IOException;
025import java.time.Duration;
026import java.util.List;
027import org.apache.hadoop.hbase.MetaTableAccessor;
028import org.apache.hadoop.hbase.TableName;
029import org.apache.hadoop.hbase.master.RegionState;
030import org.apache.hadoop.hbase.master.assignment.RegionStates;
031import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils;
032import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
033import org.junit.jupiter.api.TestTemplate;
034
035public class CloneSnapshotFromClientAfterSplittingRegionTestBase
036  extends CloneSnapshotFromClientTestBase {
037
038  protected CloneSnapshotFromClientAfterSplittingRegionTestBase(int numReplicas) {
039    super(numReplicas);
040  }
041
042  private void splitRegion() throws IOException {
043    int numRegions = admin.getRegions(tableName).size();
044    // Major-compact every region into a single store file that spans the whole key range of the
045    // region. This guarantees that the split row falls inside an existing store file, so the split
046    // produces a reference file rather than a whole-file link (see HBASE-26421, which builds an
047    // HFileLink instead of a Reference when a store file lies entirely on one side of the split
048    // point). Without this, the randomly generated row keys may all fall on one side of the split
049    // point, leaving the snapshot with no reference files. The cloned table's meta would then be
050    // missing the parent split information, which is what HBASE-29111 guards against below. The
051    // region server has compaction disabled (see CloneSnapshotFromClientTestBase), so we
052    // major-compact the table's regions directly to bypass that; auto-compaction stays disabled
053    // afterward so the post-split reference files are not compacted away.
054    TEST_UTIL.compact(tableName, true);
055    try (Table k = TEST_UTIL.getConnection().getTable(tableName);
056      ResultScanner scanner = k.getScanner(new Scan())) {
057      // Split on the second row to make sure that the snapshot contains reference files.
058      scanner.next();
059      admin.split(tableName, scanner.next().getRow());
060    }
061    await().atMost(Duration.ofSeconds(30)).untilAsserted(
062      () -> assertEquals(numRegions + numReplicas, admin.getRegions(tableName).size()));
063  }
064
065  @TestTemplate
066  public void testCloneSnapshotAfterSplittingRegion() throws IOException, InterruptedException {
067    // Turn off the CatalogJanitor
068    admin.catalogJanitorSwitch(false);
069
070    TableName clonedTableName =
071      TableName.valueOf(getValidMethodName() + "-" + EnvironmentEdgeManager.currentTime());
072    try {
073      List<RegionInfo> regionInfos = admin.getRegions(tableName);
074      RegionReplicaUtil.removeNonDefaultRegions(regionInfos);
075
076      // Split a region
077      splitRegion();
078
079      // Take a snapshot
080      admin.snapshot(snapshotName2, tableName);
081      // Clone the snapshot to another table
082      admin.cloneSnapshot(snapshotName2, clonedTableName);
083      SnapshotTestingUtils.waitForTableToBeOnline(TEST_UTIL, clonedTableName);
084
085      verifyRowCount(TEST_UTIL, clonedTableName, snapshot1Rows);
086
087      RegionStates regionStates =
088        TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager().getRegionStates();
089
090      // The region count of the cloned table should be the same as the one of the original table
091      int openRegionCountOfOriginalTable =
092        regionStates.getRegionByStateOfTable(tableName).get(RegionState.State.OPEN).size();
093      int openRegionCountOfClonedTable =
094        regionStates.getRegionByStateOfTable(clonedTableName).get(RegionState.State.OPEN).size();
095      assertEquals(openRegionCountOfOriginalTable, openRegionCountOfClonedTable);
096
097      int splitRegionCountOfOriginalTable =
098        regionStates.getRegionByStateOfTable(tableName).get(RegionState.State.SPLIT).size();
099      List<RegionInfo> splitParents =
100        regionStates.getRegionByStateOfTable(clonedTableName).get(RegionState.State.SPLIT);
101      int splitRegionCountOfClonedTable = splitParents.size();
102      assertEquals(splitRegionCountOfOriginalTable, splitRegionCountOfClonedTable);
103
104      // Make sure that the meta table was updated with the correct split information
105      for (RegionInfo splitParent : splitParents) {
106        Result result = MetaTableAccessor.getRegionResult(TEST_UTIL.getConnection(), splitParent);
107        for (RegionInfo daughter : MetaTableAccessor.getDaughterRegions(result)) {
108          assertNotNull(daughter);
109        }
110      }
111    } finally {
112      if (admin.tableExists(clonedTableName)) {
113        TEST_UTIL.deleteTable(clonedTableName);
114      }
115      admin.catalogJanitorSwitch(true);
116    }
117  }
118
119  @TestTemplate
120  public void testCloneSnapshotBeforeSplittingRegionAndDroppingTable()
121    throws IOException, InterruptedException {
122    // Turn off the CatalogJanitor
123    admin.catalogJanitorSwitch(false);
124    TableName clonedTableName =
125      TableName.valueOf(getValidMethodName() + "-" + EnvironmentEdgeManager.currentTime());
126    try {
127      // Take a snapshot
128      admin.snapshot(snapshotName2, tableName);
129
130      // Clone the snapshot to another table
131      admin.cloneSnapshot(snapshotName2, clonedTableName);
132      SnapshotTestingUtils.waitForTableToBeOnline(TEST_UTIL, clonedTableName);
133
134      // Split a region of the original table
135      List<RegionInfo> regionInfos = admin.getRegions(tableName);
136      RegionReplicaUtil.removeNonDefaultRegions(regionInfos);
137      splitRegion();
138
139      // Drop the original table
140      admin.disableTable(tableName);
141      admin.deleteTable(tableName);
142
143      // Disable and enable the cloned table. This should be successful
144      admin.disableTable(clonedTableName);
145      admin.enableTable(clonedTableName);
146      SnapshotTestingUtils.waitForTableToBeOnline(TEST_UTIL, clonedTableName);
147
148      verifyRowCount(TEST_UTIL, clonedTableName, snapshot1Rows);
149    } finally {
150      if (admin.tableExists(clonedTableName)) {
151        TEST_UTIL.deleteTable(clonedTableName);
152      }
153      admin.catalogJanitorSwitch(true);
154    }
155  }
156}