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.junit.Assert.assertEquals; 021import static org.junit.Assert.assertTrue; 022 023import org.apache.hadoop.hbase.HBaseClassTestRule; 024import org.apache.hadoop.hbase.HBaseIOException; 025import org.apache.hadoop.hbase.HConstants; 026import org.apache.hadoop.hbase.TableName; 027import org.apache.hadoop.hbase.client.SnapshotDescription; 028import org.apache.hadoop.hbase.procedure2.Procedure; 029import org.apache.hadoop.hbase.procedure2.ProcedureExecutor; 030import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility; 031import org.apache.hadoop.hbase.testclassification.LargeTests; 032import org.apache.hadoop.hbase.testclassification.MasterTests; 033import org.junit.BeforeClass; 034import org.junit.ClassRule; 035import org.junit.Rule; 036import org.junit.Test; 037import org.junit.experimental.categories.Category; 038import org.junit.rules.TestName; 039 040import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.TruncateTableState; 041 042@Category({ MasterTests.class, LargeTests.class }) 043public class TestTruncateTableProcedureWithRecovery extends TestTableDDLProcedureBase { 044 045 @ClassRule 046 public static final HBaseClassTestRule CLASS_RULE = 047 HBaseClassTestRule.forClass(TestTruncateTableProcedureWithRecovery.class); 048 049 @Rule 050 public TestName name = new TestName(); 051 052 @BeforeClass 053 public static void setupCluster() throws Exception { 054 // Enable recovery snapshots 055 UTIL.getConfiguration().setBoolean(HConstants.SNAPSHOT_BEFORE_DESTRUCTIVE_ACTION_ENABLED_KEY, 056 true); 057 TestTableDDLProcedureBase.setupCluster(); 058 } 059 060 @Test 061 public void testRecoverySnapshotRollback() throws Exception { 062 final TableName tableName = TableName.valueOf(name.getMethodName()); 063 final String[] families = new String[] { "f1", "f2" }; 064 final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor(); 065 066 // Create table with data 067 MasterProcedureTestingUtility.createTable(getMasterProcedureExecutor(), tableName, null, 068 families); 069 MasterProcedureTestingUtility.loadData(UTIL.getConnection(), tableName, 100, new byte[0][], 070 families); 071 assertEquals(100, UTIL.countRows(tableName)); 072 073 // Disable the table 074 UTIL.getAdmin().disableTable(tableName); 075 076 // Submit the failing procedure 077 long procId = procExec.submitProcedure( 078 new FailingTruncateTableProcedure(procExec.getEnvironment(), tableName, false)); 079 080 // Wait for procedure to complete (should fail) 081 ProcedureTestingUtility.waitProcedure(procExec, procId); 082 Procedure<MasterProcedureEnv> result = procExec.getResult(procId); 083 assertTrue("Procedure should have failed", result.isFailed()); 084 085 // Verify no recovery snapshots remain after rollback 086 boolean snapshotFound = false; 087 for (SnapshotDescription snapshot : UTIL.getAdmin().listSnapshots()) { 088 if (snapshot.getName().startsWith("auto_" + tableName.getNameAsString())) { 089 snapshotFound = true; 090 break; 091 } 092 } 093 assertTrue("Recovery snapshot should have been cleaned up during rollback", !snapshotFound); 094 } 095 096 @Test 097 public void testRecoverySnapshotAndRestore() throws Exception { 098 final TableName tableName = TableName.valueOf(name.getMethodName()); 099 final TableName restoredTableName = TableName.valueOf(name.getMethodName() + "_restored"); 100 final String[] families = new String[] { "f1", "f2" }; 101 102 // Create table with data 103 MasterProcedureTestingUtility.createTable(getMasterProcedureExecutor(), tableName, null, 104 families); 105 MasterProcedureTestingUtility.loadData(UTIL.getConnection(), tableName, 100, new byte[0][], 106 families); 107 assertEquals(100, UTIL.countRows(tableName)); 108 109 // Disable the table 110 UTIL.getAdmin().disableTable(tableName); 111 112 // Truncate the table (this should create a recovery snapshot) 113 final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor(); 114 long procId = ProcedureTestingUtility.submitAndWait(procExec, 115 new TruncateTableProcedure(procExec.getEnvironment(), tableName, false)); 116 ProcedureTestingUtility.assertProcNotFailed(procExec, procId); 117 118 // Verify table is truncated 119 UTIL.waitUntilAllRegionsAssigned(tableName); 120 assertEquals(0, UTIL.countRows(tableName)); 121 122 // Find the recovery snapshot 123 String recoverySnapshotName = null; 124 for (SnapshotDescription snapshot : UTIL.getAdmin().listSnapshots()) { 125 if (snapshot.getName().startsWith("auto_" + tableName.getNameAsString())) { 126 recoverySnapshotName = snapshot.getName(); 127 break; 128 } 129 } 130 assertTrue("Recovery snapshot should exist", recoverySnapshotName != null); 131 132 // Restore from snapshot by cloning to a new table 133 UTIL.getAdmin().cloneSnapshot(recoverySnapshotName, restoredTableName); 134 UTIL.waitUntilAllRegionsAssigned(restoredTableName); 135 136 // Verify restored table has original data 137 assertEquals(100, UTIL.countRows(restoredTableName)); 138 139 // Clean up the cloned table 140 UTIL.getAdmin().disableTable(restoredTableName); 141 UTIL.getAdmin().deleteTable(restoredTableName); 142 } 143 144 public static class FailingTruncateTableProcedure extends TruncateTableProcedure { 145 private boolean failOnce = false; 146 147 public FailingTruncateTableProcedure() { 148 super(); 149 } 150 151 public FailingTruncateTableProcedure(MasterProcedureEnv env, TableName tableName, 152 boolean preserveSplits) throws HBaseIOException { 153 super(env, tableName, preserveSplits); 154 } 155 156 @Override 157 protected Flow executeFromState(MasterProcedureEnv env, TruncateTableState state) 158 throws InterruptedException { 159 if (!failOnce && state == TruncateTableState.TRUNCATE_TABLE_CLEAR_FS_LAYOUT) { 160 failOnce = true; 161 throw new RuntimeException("Simulated failure"); 162 } 163 return super.executeFromState(env, state); 164 } 165 } 166}