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