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