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.procedure;
019
020import static org.apache.hadoop.hbase.master.assignment.AssignmentTestingUtil.insertData;
021import static org.junit.jupiter.api.Assertions.assertEquals;
022import static org.junit.jupiter.api.Assertions.assertTrue;
023
024import java.io.IOException;
025import java.util.Arrays;
026import java.util.List;
027import java.util.stream.Collectors;
028import org.apache.hadoop.conf.Configuration;
029import org.apache.hadoop.hbase.HConstants;
030import org.apache.hadoop.hbase.TableName;
031import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
032import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
033import org.apache.hadoop.hbase.client.RegionInfo;
034import org.apache.hadoop.hbase.client.RegionReplicaUtil;
035import org.apache.hadoop.hbase.client.TableDescriptor;
036import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
037import org.apache.hadoop.hbase.procedure2.Procedure;
038import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
039import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
040import org.apache.hadoop.hbase.testclassification.LargeTests;
041import org.apache.hadoop.hbase.testclassification.MasterTests;
042import org.apache.hadoop.hbase.util.Bytes;
043import org.junit.jupiter.api.AfterAll;
044import org.junit.jupiter.api.AfterEach;
045import org.junit.jupiter.api.BeforeAll;
046import org.junit.jupiter.api.BeforeEach;
047import org.junit.jupiter.api.Tag;
048import org.junit.jupiter.api.Test;
049import org.junit.jupiter.api.TestInfo;
050import org.slf4j.Logger;
051import org.slf4j.LoggerFactory;
052
053import org.apache.hadoop.hbase.shaded.protobuf.generated.ProcedureProtos;
054
055@SuppressWarnings("OptionalGetWithoutIsPresent")
056@Tag(MasterTests.TAG)
057@Tag(LargeTests.TAG)
058public class TestTruncateRegionProcedure extends TestTableDDLProcedureBase {
059  private static final Logger LOG = LoggerFactory.getLogger(TestTruncateRegionProcedure.class);
060  private String testMethodName;
061
062  @BeforeEach
063  public void setTestMethod(TestInfo testInfo) {
064    testMethodName = testInfo.getTestMethod().get().getName();
065  }
066
067  protected static void setupConf(Configuration conf) {
068    conf.setInt(MasterProcedureConstants.MASTER_PROCEDURE_THREADS, 1);
069    conf.setLong(HConstants.MAJOR_COMPACTION_PERIOD, 0);
070    conf.setInt("hbase.client.sync.wait.timeout.msec", 60000);
071  }
072
073  @BeforeAll
074  public static void setupCluster() throws Exception {
075    setupConf(UTIL.getConfiguration());
076    UTIL.startMiniCluster(3);
077  }
078
079  @AfterAll
080  public static void cleanupTest() throws Exception {
081    TestTableDDLProcedureBase.cleanupTest();
082  }
083
084  @BeforeEach
085  public void setup() throws Exception {
086    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(getMasterProcedureExecutor(), false);
087
088    // Turn off balancer, so it doesn't cut in and mess up our placements.
089    UTIL.getAdmin().balancerSwitch(false, true);
090    // Turn off the meta scanner, so it doesn't remove, parent on us.
091    UTIL.getHBaseCluster().getMaster().setCatalogJanitorEnabled(false);
092  }
093
094  @AfterEach
095  public void tearDown() throws Exception {
096    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(getMasterProcedureExecutor(), false);
097    for (TableDescriptor htd : UTIL.getAdmin().listTableDescriptors()) {
098      UTIL.deleteTable(htd.getTableName());
099    }
100  }
101
102  @Test
103  public void testTruncateRegionProcedure() throws Exception {
104    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
105    // Arrange - Load table and prepare arguments values.
106    final TableName tableName = TableName.valueOf(testMethodName);
107    final String[] families = new String[] { "f1", "f2" };
108    final byte[][] splitKeys =
109      new byte[][] { Bytes.toBytes("30"), Bytes.toBytes("60"), Bytes.toBytes("90") };
110
111    MasterProcedureTestingUtility.createTable(procExec, tableName, splitKeys, families);
112
113    insertData(UTIL, tableName, 2, 20, families);
114    insertData(UTIL, tableName, 2, 31, families);
115    insertData(UTIL, tableName, 2, 61, families);
116    insertData(UTIL, tableName, 2, 91, families);
117
118    assertEquals(8, UTIL.countRows(tableName));
119
120    int rowsBeforeDropRegion = 8;
121
122    MasterProcedureEnv environment = procExec.getEnvironment();
123    RegionInfo regionToBeTruncated = environment.getAssignmentManager().getAssignedRegions()
124      .stream().filter(r -> tableName.getNameAsString().equals(r.getTable().getNameAsString()))
125      .min((o1, o2) -> Bytes.compareTo(o1.getStartKey(), o2.getStartKey())).get();
126
127    // Act - Execute Truncate region procedure
128    long procId =
129      procExec.submitProcedure(new TruncateRegionProcedure(environment, regionToBeTruncated));
130    ProcedureTestingUtility.waitProcedure(procExec, procId);
131    assertEquals(8 - 2, UTIL.countRows(tableName));
132
133    int rowsAfterDropRegion = UTIL.countRows(tableName);
134    assertTrue(rowsAfterDropRegion < rowsBeforeDropRegion,
135      "Row counts after truncate region should be less than row count before it");
136    assertEquals(rowsBeforeDropRegion, rowsAfterDropRegion + 2);
137
138    insertData(UTIL, tableName, 2, 20, families);
139    assertEquals(8, UTIL.countRows(tableName));
140  }
141
142  @Test
143  public void testTruncateRegionProcedureErrorWhenSpecifiedReplicaRegionID() throws Exception {
144    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
145    // Arrange - Load table and prepare arguments values.
146    final TableName tableName = TableName.valueOf(testMethodName);
147    final String[] families = new String[] { "f1", "f2" };
148    createTable(tableName, families, 2);
149    insertData(UTIL, tableName, 2, 20, families);
150    insertData(UTIL, tableName, 2, 30, families);
151    insertData(UTIL, tableName, 2, 60, families);
152
153    assertEquals(6, UTIL.countRows(tableName));
154
155    MasterProcedureEnv environment = procExec.getEnvironment();
156    RegionInfo regionToBeTruncated = environment.getAssignmentManager().getAssignedRegions()
157      .stream().filter(r -> tableName.getNameAsString().equals(r.getTable().getNameAsString()))
158      .min((o1, o2) -> Bytes.compareTo(o1.getStartKey(), o2.getStartKey())).get();
159
160    RegionInfo replicatedRegionId =
161      RegionReplicaUtil.getRegionInfoForReplica(regionToBeTruncated, 1);
162
163    // Act - Execute Truncate region procedure
164    long procId =
165      procExec.submitProcedure(new TruncateRegionProcedure(environment, replicatedRegionId));
166
167    ProcedureTestingUtility.waitProcedure(procExec, procId);
168    Procedure<MasterProcedureEnv> result = procExec.getResult(procId);
169    // Asserts
170
171    assertEquals(ProcedureProtos.ProcedureState.ROLLEDBACK, result.getState());
172    assertTrue(result.getException().getMessage()
173      .endsWith("Can't truncate replicas directly. Replicas are auto-truncated "
174        + "when their primary is truncated."));
175  }
176
177  private TableDescriptor tableDescriptor(final TableName tableName, String[] families,
178    final int replicaCount) {
179    return TableDescriptorBuilder.newBuilder(tableName).setRegionReplication(replicaCount)
180      .setColumnFamilies(columnFamilyDescriptor(families)).build();
181  }
182
183  private List<ColumnFamilyDescriptor> columnFamilyDescriptor(String[] families) {
184    return Arrays.stream(families).map(ColumnFamilyDescriptorBuilder::of)
185      .collect(Collectors.toList());
186  }
187
188  @SuppressWarnings("SameParameterValue")
189  private void createTable(final TableName tableName, String[] families, final int replicaCount)
190    throws IOException {
191    UTIL.getAdmin().createTable(tableDescriptor(tableName, families, replicaCount));
192  }
193}