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.backup; 019 020import static org.apache.hadoop.hbase.backup.BackupInfo.withState; 021import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.CONF_CONTINUOUS_BACKUP_PITR_WINDOW_DAYS; 022import static org.apache.hadoop.hbase.backup.replication.ContinuousBackupReplicationEndpoint.ONE_DAY_IN_MILLISECONDS; 023import static org.junit.jupiter.api.Assertions.assertEquals; 024import static org.junit.jupiter.api.Assertions.assertFalse; 025import static org.junit.jupiter.api.Assertions.assertNotEquals; 026import static org.junit.jupiter.api.Assertions.assertTrue; 027 028import java.io.IOException; 029import java.util.Set; 030import org.apache.hadoop.hbase.backup.impl.BackupSystemTable; 031import org.apache.hadoop.hbase.testclassification.LargeTests; 032import org.apache.hadoop.util.ToolRunner; 033import org.junit.jupiter.api.Tag; 034import org.junit.jupiter.api.Test; 035 036import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 037 038/** 039 * Tests the deletion of HBase backups under continuous backup and PITR settings. 040 * <p> 041 * Terminology: 042 * <ul> 043 * <li><b>ct (current time)</b>: Current timestamp</li> 044 * <li><b>maxAllowedPITRTime (mapt)</b>: Maximum allowed time range for PITR, typically a 045 * cluster-level config (e.g., 30 days ago)</li> 046 * <li><b>cst (continuousBackupStartTime)</b>: Earliest time from which continuous backup is 047 * available</li> 048 * <li><b>fs</b>: Full backup start time (not reliably usable)</li> 049 * <li><b>fm</b>: Time when snapshot (logical freeze) was taken (we don't have this)</li> 050 * <li><b>fe</b>: Full backup end time (used as conservative proxy for fm)</li> 051 * </ul> 052 */ 053@Tag(LargeTests.TAG) 054public class TestBackupDeleteWithContinuousBackupAndPITR extends TestBackupBase { 055 056 private BackupSystemTable backupSystemTable; 057 058 /** 059 * Configures continuous backup with the specified CST (continuous backup start time). 060 */ 061 private void configureContinuousBackup(long cstTimestamp) throws IOException { 062 conf1.setLong(CONF_CONTINUOUS_BACKUP_PITR_WINDOW_DAYS, 30); 063 backupSystemTable = new BackupSystemTable(TEST_UTIL.getConnection()); 064 065 backupSystemTable.addContinuousBackupTableSet(Set.of(table1), cstTimestamp); 066 } 067 068 private void cleanupContinuousBackup() throws IOException { 069 backupSystemTable.removeContinuousBackupTableSet(Set.of(table1)); 070 } 071 072 /** 073 * Main Case: continuousBackupStartTime < maxAllowedPITRTime 074 * <p> 075 * Sub Case: fe < cst 076 */ 077 @Test 078 public void testDeletionWhenBackupCompletesBeforeCST() throws Exception { 079 long now = System.currentTimeMillis(); 080 long cst = now - 40 * ONE_DAY_IN_MILLISECONDS; // CST = 40 days ago 081 configureContinuousBackup(cst); 082 083 String backupId = 084 createAndUpdateBackup(cst - ONE_DAY_IN_MILLISECONDS, cst - ONE_DAY_IN_MILLISECONDS + 1000); 085 assertDeletionSucceeds(backupSystemTable, backupId, false); 086 087 cleanupContinuousBackup(); 088 } 089 090 /** 091 * Main Case: continuousBackupStartTime < maxAllowedPITRTime 092 * <p> 093 * Sub Case: fs < cst < fe 094 */ 095 @Test 096 public void testDeletionWhenBackupStraddlesCST() throws Exception { 097 long now = System.currentTimeMillis(); 098 long cst = now - 40 * ONE_DAY_IN_MILLISECONDS; // CST = 40 days ago 099 configureContinuousBackup(cst); 100 101 String backupId = createAndUpdateBackup(cst - 1000, cst + 1000); 102 assertDeletionSucceeds(backupSystemTable, backupId, false); 103 104 cleanupContinuousBackup(); 105 } 106 107 /** 108 * Main Case: continuousBackupStartTime < maxAllowedPITRTime 109 * <p> 110 * Sub Case: fs >= cst && fe < mapt 111 */ 112 @Test 113 public void testDeletionWhenBackupWithinCSTToMAPTRangeAndUncovered() throws Exception { 114 long now = System.currentTimeMillis(); 115 long cst = now - 40 * ONE_DAY_IN_MILLISECONDS; 116 long mapt = now - 30 * ONE_DAY_IN_MILLISECONDS; 117 configureContinuousBackup(cst); 118 119 String backupId = createAndUpdateBackup(cst, mapt - 1000); 120 assertDeletionFails(backupSystemTable, backupId); 121 122 // Cover the backup with another backup 123 String coverId = createAndUpdateBackup(cst, mapt - 1000); 124 125 // Now, deletion should succeed because the backup is covered by the new one 126 assertDeletionSucceeds(backupSystemTable, backupId, false); 127 assertDeletionSucceeds(backupSystemTable, coverId, true); 128 129 cleanupContinuousBackup(); 130 } 131 132 /** 133 * Main Case: continuousBackupStartTime < maxAllowedPITRTime 134 * <p> 135 * Sub Case: fs >= cst && fe >= mapt 136 */ 137 @Test 138 public void testDeletionWhenBackupExtendsBeyondMAPTAndUncovered() throws Exception { 139 long now = System.currentTimeMillis(); 140 long cst = now - 40 * ONE_DAY_IN_MILLISECONDS; 141 long mapt = now - 30 * ONE_DAY_IN_MILLISECONDS; 142 configureContinuousBackup(cst); 143 144 String backupId = createAndUpdateBackup(cst + 1000, mapt + 1000); 145 assertDeletionFails(backupSystemTable, backupId); 146 147 // Cover the backup with another backup 148 String coverId = createAndUpdateBackup(cst + 1000, mapt + 1000); 149 150 // Now, deletion should succeed because the backup is covered by the new one 151 assertDeletionSucceeds(backupSystemTable, backupId, false); 152 assertDeletionSucceeds(backupSystemTable, coverId, true); 153 154 cleanupContinuousBackup(); 155 } 156 157 /** 158 * Main Case: continuousBackupStartTime >= maxAllowedPITRTime 159 * <p> 160 * Sub Case: fs < cst 161 */ 162 @Test 163 public void testDeletionWhenBackupBeforeCST_ShouldSucceed() throws Exception { 164 long now = System.currentTimeMillis(); 165 long cst = now - 20 * ONE_DAY_IN_MILLISECONDS; 166 configureContinuousBackup(cst); 167 168 String backupId = createAndUpdateBackup(cst - 1000, cst + 1000); 169 assertDeletionSucceeds(backupSystemTable, backupId, false); 170 171 cleanupContinuousBackup(); 172 } 173 174 /** 175 * Main Case: continuousBackupStartTime >= maxAllowedPITRTime 176 * <p> 177 * Sub Case: fs >= cst 178 */ 179 @Test 180 public void testDeletionWhenBackupAfterCST_ShouldFailUnlessCovered() throws Exception { 181 long now = System.currentTimeMillis(); 182 long cst = now - 20 * ONE_DAY_IN_MILLISECONDS; 183 configureContinuousBackup(cst); 184 185 String backupId = createAndUpdateBackup(cst + 1000, cst + 2000); 186 assertDeletionFails(backupSystemTable, backupId); 187 188 // Cover the backup with another backup 189 String coverId = createAndUpdateBackup(cst + 1000, cst + 2000); 190 191 assertDeletionSucceeds(backupSystemTable, backupId, false); 192 assertDeletionSucceeds(backupSystemTable, coverId, true); 193 194 cleanupContinuousBackup(); 195 } 196 197 @Test 198 public void testDeleteIncrementalBackup() throws Exception { 199 long now = System.currentTimeMillis(); 200 long cst = now - 20 * ONE_DAY_IN_MILLISECONDS; 201 configureContinuousBackup(cst); 202 203 String fullBackupId = fullTableBackup(Lists.newArrayList(table1)); 204 String incrementalTableBackupId = incrementalTableBackup(Lists.newArrayList(table1)); 205 assertDeletionSucceeds(backupSystemTable, incrementalTableBackupId, false); 206 207 assertDeletionSucceeds(backupSystemTable, fullBackupId, true); 208 } 209 210 @Test 211 public void testDeleteFullBackupNonContinuousTable() throws Exception { 212 conf1.setLong(CONF_CONTINUOUS_BACKUP_PITR_WINDOW_DAYS, 30); 213 backupSystemTable = new BackupSystemTable(TEST_UTIL.getConnection()); 214 215 long now = System.currentTimeMillis(); 216 String backupId = 217 createAndUpdateBackup(now - ONE_DAY_IN_MILLISECONDS, now - ONE_DAY_IN_MILLISECONDS + 1000); 218 assertDeletionSucceeds(backupSystemTable, backupId, false); 219 } 220 221 /** 222 * Creates a full backup and updates its timestamps. 223 */ 224 private String createAndUpdateBackup(long startTs, long completeTs) throws Exception { 225 String backupId = fullTableBackup(Lists.newArrayList(table1)); 226 assertTrue(checkSucceeded(backupId)); 227 228 BackupInfo backupInfo = getBackupInfoById(backupId); 229 backupInfo.setStartTs(startTs); 230 backupInfo.setCompleteTs(completeTs); 231 backupSystemTable.updateBackupInfo(backupInfo); 232 233 return backupId; 234 } 235 236 private void assertDeletionSucceeds(BackupSystemTable table, String backupId, 237 boolean isForceDelete) throws Exception { 238 int ret = deleteBackup(backupId, isForceDelete); 239 assertEquals(0, ret); 240 assertFalse(backupExists(table, backupId), "Backup should be deleted but still exists!"); 241 } 242 243 private void assertDeletionFails(BackupSystemTable table, String backupId) throws Exception { 244 int ret = deleteBackup(backupId, false); 245 assertNotEquals(0, ret); 246 assertTrue(backupExists(table, backupId), "Backup should still exist after failed deletion!"); 247 } 248 249 private boolean backupExists(BackupSystemTable table, String backupId) throws Exception { 250 return table.getBackupHistory().stream() 251 .anyMatch(backup -> backup.getBackupId().equals(backupId)); 252 } 253 254 private int deleteBackup(String backupId, boolean isForceDelete) throws Exception { 255 String[] args = buildBackupDeleteArgs(backupId, isForceDelete); 256 return ToolRunner.run(conf1, new BackupDriver(), args); 257 } 258 259 private String[] buildBackupDeleteArgs(String backupId, boolean isForceDelete) { 260 return isForceDelete 261 ? new String[] { "delete", "-l", backupId, "-fd" } 262 : new String[] { "delete", "-l", backupId }; 263 } 264 265 private BackupInfo getBackupInfoById(String backupId) throws IOException { 266 return backupSystemTable.getBackupHistory(withState(BackupInfo.BackupState.COMPLETE)).stream() 267 .filter(b -> b.getBackupId().equals(backupId)).findFirst() 268 .orElseThrow(() -> new IllegalStateException("Backup should exist: " + backupId)); 269 } 270}