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 java.io.IOException; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Iterator; 024import java.util.List; 025import java.util.Map; 026import java.util.Map.Entry; 027import java.util.Objects; 028import org.apache.hadoop.conf.Configuration; 029import org.apache.hadoop.fs.FileStatus; 030import org.apache.hadoop.fs.FileSystem; 031import org.apache.hadoop.fs.LocatedFileStatus; 032import org.apache.hadoop.fs.Path; 033import org.apache.hadoop.fs.RemoteIterator; 034import org.apache.hadoop.hbase.HBaseConfiguration; 035import org.apache.hadoop.hbase.HBaseTestingUtil; 036import org.apache.hadoop.hbase.HConstants; 037import org.apache.hadoop.hbase.NamespaceDescriptor; 038import org.apache.hadoop.hbase.TableName; 039import org.apache.hadoop.hbase.backup.BackupInfo.BackupPhase; 040import org.apache.hadoop.hbase.backup.BackupInfo.BackupState; 041import org.apache.hadoop.hbase.backup.impl.BackupAdminImpl; 042import org.apache.hadoop.hbase.backup.impl.BackupManager; 043import org.apache.hadoop.hbase.backup.impl.BackupSystemTable; 044import org.apache.hadoop.hbase.backup.impl.FullTableBackupClient; 045import org.apache.hadoop.hbase.backup.impl.IncrementalBackupManager; 046import org.apache.hadoop.hbase.backup.impl.IncrementalTableBackupClient; 047import org.apache.hadoop.hbase.backup.util.BackupUtils; 048import org.apache.hadoop.hbase.client.Admin; 049import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 050import org.apache.hadoop.hbase.client.Connection; 051import org.apache.hadoop.hbase.client.ConnectionFactory; 052import org.apache.hadoop.hbase.client.Durability; 053import org.apache.hadoop.hbase.client.Put; 054import org.apache.hadoop.hbase.client.Table; 055import org.apache.hadoop.hbase.client.TableDescriptor; 056import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 057import org.apache.hadoop.hbase.master.cleaner.LogCleaner; 058import org.apache.hadoop.hbase.master.cleaner.TimeToLiveLogCleaner; 059import org.apache.hadoop.hbase.regionserver.LogRoller; 060import org.apache.hadoop.hbase.security.HadoopSecurityEnabledUserProviderForTesting; 061import org.apache.hadoop.hbase.security.UserProvider; 062import org.apache.hadoop.hbase.security.access.SecureTestUtil; 063import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils; 064import org.apache.hadoop.hbase.util.Bytes; 065import org.apache.hadoop.hbase.util.CommonFSUtils; 066import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 067import org.apache.hadoop.hbase.wal.AbstractFSWALProvider; 068import org.apache.hadoop.hbase.wal.WALFactory; 069import org.junit.jupiter.api.AfterAll; 070import org.junit.jupiter.api.BeforeAll; 071import org.junit.jupiter.api.BeforeEach; 072import org.slf4j.Logger; 073import org.slf4j.LoggerFactory; 074 075/** 076 * This class is only a base for other integration-level backup tests. Do not add tests here. 077 * TestBackupSmallTests is where tests that don't require bring machines up/down should go All other 078 * tests should have their own classes and extend this one 079 */ 080public class TestBackupBase { 081 private static final Logger LOG = LoggerFactory.getLogger(TestBackupBase.class); 082 083 protected static HBaseTestingUtil TEST_UTIL; 084 protected static HBaseTestingUtil TEST_UTIL2; 085 protected static Configuration conf1; 086 protected static Configuration conf2; 087 088 protected static TableName table1 = TableName.valueOf("table1"); 089 protected static TableDescriptor table1Desc; 090 protected static TableName table2 = TableName.valueOf("table2"); 091 protected static TableName table3 = TableName.valueOf("table3"); 092 protected static TableName table4 = TableName.valueOf("table4"); 093 094 protected static TableName table1_restore = TableName.valueOf("default:table1"); 095 protected static TableName table2_restore = TableName.valueOf("ns2:table2"); 096 protected static TableName table3_restore = TableName.valueOf("ns3:table3_restore"); 097 098 protected static final int NB_ROWS_IN_BATCH = 99; 099 protected static final byte[] qualName = Bytes.toBytes("q1"); 100 protected static final byte[] famName = Bytes.toBytes("f"); 101 102 protected static String BACKUP_ROOT_DIR; 103 protected static String BACKUP_REMOTE_ROOT_DIR; 104 protected static String provider = "defaultProvider"; 105 protected static boolean secure = false; 106 107 protected static boolean autoRestoreOnFailure; 108 protected static boolean useSecondCluster; 109 110 static class IncrementalTableBackupClientForTest extends IncrementalTableBackupClient { 111 public IncrementalTableBackupClientForTest() { 112 } 113 114 public IncrementalTableBackupClientForTest(Connection conn, String backupId, 115 BackupRequest request) throws IOException { 116 super(conn, backupId, request); 117 } 118 119 @BeforeEach 120 public void ensurePreviousBackupTestsAreCleanedUp() throws Exception { 121 // Every operation here may not be necessary for any given test, 122 // some often being no-ops. the goal is to help ensure atomicity 123 // of that tests that implement TestBackupBase 124 try (BackupAdmin backupAdmin = getBackupAdmin()) { 125 backupManager.finishBackupSession(); 126 backupAdmin.listBackupSets().forEach(backupSet -> { 127 try { 128 backupAdmin.deleteBackupSet(backupSet.getName()); 129 } catch (IOException ignored) { 130 } 131 }); 132 } catch (Exception ignored) { 133 } 134 Arrays.stream(TEST_UTIL.getAdmin().listTableNames()) 135 .filter(tableName -> !tableName.isSystemTable()).forEach(tableName -> { 136 try { 137 TEST_UTIL.truncateTable(tableName); 138 } catch (IOException ignored) { 139 } 140 }); 141 TEST_UTIL.getMiniHBaseCluster().getRegionServerThreads().forEach(rst -> { 142 try { 143 LogRoller walRoller = rst.getRegionServer().getWalRoller(); 144 walRoller.requestRollAll(); 145 walRoller.waitUntilWalRollFinished(); 146 } catch (Exception ignored) { 147 } 148 }); 149 } 150 151 @Override 152 public void execute() throws IOException { 153 // case INCREMENTAL_COPY: 154 try { 155 // case PREPARE_INCREMENTAL: 156 failStageIf(Stage.stage_0); 157 beginBackup(backupManager, backupInfo); 158 159 failStageIf(Stage.stage_1); 160 backupInfo.setPhase(BackupPhase.PREPARE_INCREMENTAL); 161 LOG.debug("For incremental backup, current table set is " 162 + backupManager.getIncrementalBackupTableSet()); 163 newTimestamps = ((IncrementalBackupManager) backupManager).getIncrBackupLogFileMap(); 164 // copy out the table and region info files for each table 165 BackupUtils.copyTableRegionInfo(conn, backupInfo, conf); 166 // convert WAL to HFiles and copy them to .tmp under BACKUP_ROOT 167 convertWALsToHFiles(); 168 incrementalCopyHFiles(new String[] { getBulkOutputDir().toString() }, 169 backupInfo.getBackupRootDir()); 170 failStageIf(Stage.stage_2); 171 172 // case INCR_BACKUP_COMPLETE: 173 // set overall backup status: complete. Here we make sure to complete the backup. 174 // After this checkpoint, even if entering cancel process, will let the backup finished 175 // Set the previousTimestampMap which is before this current log roll to the manifest. 176 Map<TableName, Map<String, Long>> previousTimestampMap = 177 backupManager.readLogTimestampMap(); 178 backupInfo.setIncrTimestampMap(previousTimestampMap); 179 180 // The table list in backupInfo is good for both full backup and incremental backup. 181 // For incremental backup, it contains the incremental backup table set. 182 backupManager.writeRegionServerLogTimestamp(backupInfo.getTables(), newTimestamps); 183 failStageIf(Stage.stage_3); 184 185 Map<TableName, Map<String, Long>> newTableSetTimestampMap = 186 backupManager.readLogTimestampMap(); 187 188 handleBulkLoad(backupInfo.getTableNames()); 189 failStageIf(Stage.stage_4); 190 191 // backup complete 192 backupInfo.setTableSetTimestampMap(newTableSetTimestampMap); 193 completeBackup(conn, backupInfo, BackupType.INCREMENTAL, conf); 194 195 } catch (Exception e) { 196 failBackup(conn, backupInfo, backupManager, e, "Unexpected Exception : ", 197 BackupType.INCREMENTAL, conf); 198 throw new IOException(e); 199 } 200 } 201 } 202 203 static class FullTableBackupClientForTest extends FullTableBackupClient { 204 public FullTableBackupClientForTest() { 205 } 206 207 public FullTableBackupClientForTest(Connection conn, String backupId, BackupRequest request) 208 throws IOException { 209 super(conn, backupId, request); 210 } 211 212 @Override 213 public void execute() throws IOException { 214 // Get the stage ID to fail on 215 try (Admin admin = conn.getAdmin()) { 216 // Begin BACKUP 217 beginBackup(backupManager, backupInfo); 218 failStageIf(Stage.stage_0); 219 // do snapshot for full table backup 220 failStageIf(Stage.stage_1); 221 // We roll log here before we do the snapshot. It is possible there is duplicate data 222 // in the log that is already in the snapshot. But if we do it after the snapshot, we 223 // could have data loss. 224 // A better approach is to do the roll log on each RS in the same global procedure as 225 // the snapshot. 226 LOG.info("Execute roll log procedure for full backup ..."); 227 228 BackupUtils.logRoll(conn, backupInfo.getBackupRootDir(), conf); 229 failStageIf(Stage.stage_2); 230 newTimestamps = backupManager.readRegionServerLastLogRollResult(); 231 232 // SNAPSHOT_TABLES: 233 backupInfo.setPhase(BackupPhase.SNAPSHOT); 234 for (TableName tableName : tableList) { 235 String snapshotName = "snapshot_" + Long.toString(EnvironmentEdgeManager.currentTime()) 236 + "_" + tableName.getNamespaceAsString() + "_" + tableName.getQualifierAsString(); 237 238 snapshotTable(admin, tableName, snapshotName); 239 backupInfo.setSnapshotName(tableName, snapshotName); 240 } 241 failStageIf(Stage.stage_3); 242 // SNAPSHOT_COPY: 243 // do snapshot copy 244 LOG.debug("snapshot copy for " + backupId); 245 snapshotCopy(backupInfo); 246 // Updates incremental backup table set 247 backupManager.addIncrementalBackupTableSet(backupInfo.getTables()); 248 249 // BACKUP_COMPLETE: 250 // set overall backup status: complete. Here we make sure to complete the backup. 251 // After this checkpoint, even if entering cancel process, will let the backup finished 252 backupInfo.setState(BackupState.COMPLETE); 253 // The table list in backupInfo is good for both full backup and incremental backup. 254 // For incremental backup, it contains the incremental backup table set. 255 backupManager.writeRegionServerLogTimestamp(backupInfo.getTables(), newTimestamps); 256 257 Map<TableName, Map<String, Long>> newTableSetTimestampMap = 258 backupManager.readLogTimestampMap(); 259 260 failStageIf(Stage.stage_4); 261 // backup complete 262 backupInfo.setTableSetTimestampMap(newTableSetTimestampMap); 263 completeBackup(conn, backupInfo, BackupType.FULL, conf); 264 265 } catch (Exception e) { 266 267 if (autoRestoreOnFailure) { 268 failBackup(conn, backupInfo, backupManager, e, "Unexpected BackupException : ", 269 BackupType.FULL, conf); 270 } 271 throw new IOException(e); 272 } 273 } 274 } 275 276 public static void setUpHelper() throws Exception { 277 BACKUP_ROOT_DIR = Path.SEPARATOR + "backupUT"; 278 BACKUP_REMOTE_ROOT_DIR = Path.SEPARATOR + "backupUT"; 279 280 if (secure) { 281 // set the always on security provider 282 UserProvider.setUserProviderForTesting(TEST_UTIL.getConfiguration(), 283 HadoopSecurityEnabledUserProviderForTesting.class); 284 // setup configuration 285 SecureTestUtil.enableSecurity(TEST_UTIL.getConfiguration()); 286 } 287 conf1.setBoolean(BackupRestoreConstants.BACKUP_ENABLE_KEY, true); 288 BackupManager.decorateMasterConfiguration(conf1); 289 BackupManager.decorateRegionServerConfiguration(conf1); 290 conf1.set(HConstants.ZOOKEEPER_ZNODE_PARENT, "/1"); 291 // Set TTL for old WALs to 1 sec to enforce fast cleaning of an archived 292 // WAL files 293 conf1.setLong(TimeToLiveLogCleaner.TTL_CONF_KEY, 1000); 294 conf1.setLong(LogCleaner.OLD_WALS_CLEANER_THREAD_TIMEOUT_MSEC, 1000); 295 296 // Set MultiWAL (with 2 default WAL files per RS) 297 conf1.set(WALFactory.WAL_PROVIDER, provider); 298 TEST_UTIL.startMiniCluster(); 299 conf1 = TEST_UTIL.getConfiguration(); 300 TEST_UTIL.startMiniMapReduceCluster(); 301 302 if (useSecondCluster) { 303 conf2 = HBaseConfiguration.create(conf1); 304 conf2.set(HConstants.ZOOKEEPER_ZNODE_PARENT, "/2"); 305 TEST_UTIL2 = new HBaseTestingUtil(conf2); 306 TEST_UTIL2.setZkCluster(TEST_UTIL.getZkCluster()); 307 TEST_UTIL2.startMiniDFSCluster(3); 308 String root2 = TEST_UTIL2.getConfiguration().get("fs.defaultFS"); 309 Path p = new Path(new Path(root2), "/tmp/wal"); 310 CommonFSUtils.setWALRootDir(TEST_UTIL2.getConfiguration(), p); 311 TEST_UTIL2.startMiniCluster(); 312 } 313 314 BACKUP_ROOT_DIR = 315 new Path(new Path(TEST_UTIL.getConfiguration().get("fs.defaultFS")), BACKUP_ROOT_DIR) 316 .toString(); 317 LOG.info("ROOTDIR " + BACKUP_ROOT_DIR); 318 if (useSecondCluster) { 319 BACKUP_REMOTE_ROOT_DIR = new Path( 320 new Path(TEST_UTIL2.getConfiguration().get("fs.defaultFS")) + BACKUP_REMOTE_ROOT_DIR) 321 .toString(); 322 LOG.info("REMOTE ROOTDIR " + BACKUP_REMOTE_ROOT_DIR); 323 } 324 createTables(); 325 populateFromMasterConfig(TEST_UTIL.getHBaseCluster().getMaster().getConfiguration(), conf1); 326 } 327 328 /** 329 * Setup Cluster with appropriate configurations before running tests. 330 * @throws Exception if starting the mini cluster or setting up the tables fails 331 */ 332 @BeforeAll 333 public static void setUp() throws Exception { 334 TEST_UTIL = new HBaseTestingUtil(); 335 conf1 = TEST_UTIL.getConfiguration(); 336 autoRestoreOnFailure = true; 337 useSecondCluster = false; 338 setUpHelper(); 339 } 340 341 private static void populateFromMasterConfig(Configuration masterConf, Configuration conf) { 342 Iterator<Entry<String, String>> it = masterConf.iterator(); 343 while (it.hasNext()) { 344 Entry<String, String> e = it.next(); 345 conf.set(e.getKey(), e.getValue()); 346 } 347 } 348 349 @AfterAll 350 public static void tearDown() throws Exception { 351 try { 352 SnapshotTestingUtils.deleteAllSnapshots(TEST_UTIL.getAdmin()); 353 } catch (Exception e) { 354 } 355 SnapshotTestingUtils.deleteArchiveDirectory(TEST_UTIL); 356 if (useSecondCluster) { 357 TEST_UTIL2.shutdownMiniCluster(); 358 } 359 TEST_UTIL.shutdownMiniCluster(); 360 TEST_UTIL.shutdownMiniMapReduceCluster(); 361 autoRestoreOnFailure = true; 362 useSecondCluster = false; 363 } 364 365 Table insertIntoTable(Connection conn, TableName table, byte[] family, int id, int numRows) 366 throws IOException { 367 Table t = conn.getTable(table); 368 Put p1; 369 for (int i = 0; i < numRows; i++) { 370 p1 = new Put(Bytes.toBytes("row-" + table + "-" + id + "-" + i)); 371 p1.addColumn(family, qualName, Bytes.toBytes("val" + i)); 372 t.put(p1); 373 } 374 return t; 375 } 376 377 protected BackupRequest createBackupRequest(BackupType type, List<TableName> tables, 378 String path) { 379 return createBackupRequest(type, tables, path, false); 380 } 381 382 protected BackupRequest createBackupRequest(BackupType type, List<TableName> tables, String path, 383 boolean noChecksumVerify) { 384 BackupRequest.Builder builder = new BackupRequest.Builder(); 385 BackupRequest request = builder.withBackupType(type).withTableList(tables) 386 .withTargetRootDir(path).withNoChecksumVerify(noChecksumVerify).build(); 387 return request; 388 } 389 390 protected String backupTables(BackupType type, List<TableName> tables, String path) 391 throws IOException { 392 Connection conn = null; 393 BackupAdmin badmin = null; 394 String backupId; 395 try { 396 conn = ConnectionFactory.createConnection(conf1); 397 badmin = new BackupAdminImpl(conn); 398 BackupRequest request = createBackupRequest(type, new ArrayList<>(tables), path); 399 backupId = badmin.backupTables(request); 400 } finally { 401 if (badmin != null) { 402 badmin.close(); 403 } 404 if (conn != null) { 405 conn.close(); 406 } 407 } 408 return backupId; 409 } 410 411 protected String fullTableBackup(List<TableName> tables) throws IOException { 412 return backupTables(BackupType.FULL, tables, BACKUP_ROOT_DIR); 413 } 414 415 protected String incrementalTableBackup(List<TableName> tables) throws IOException { 416 return backupTables(BackupType.INCREMENTAL, tables, BACKUP_ROOT_DIR); 417 } 418 419 protected static void loadTable(Table table) throws Exception { 420 Put p; // 100 + 1 row to t1_syncup 421 for (int i = 0; i < NB_ROWS_IN_BATCH; i++) { 422 p = new Put(Bytes.toBytes("row" + i)); 423 p.setDurability(Durability.SKIP_WAL); 424 p.addColumn(famName, qualName, Bytes.toBytes("val" + i)); 425 table.put(p); 426 } 427 } 428 429 protected static void createTables() throws Exception { 430 long tid = EnvironmentEdgeManager.currentTime(); 431 table1 = TableName.valueOf("test-" + tid); 432 Admin ha = TEST_UTIL.getAdmin(); 433 434 // Create namespaces 435 ha.createNamespace(NamespaceDescriptor.create("ns1").build()); 436 ha.createNamespace(NamespaceDescriptor.create("ns2").build()); 437 ha.createNamespace(NamespaceDescriptor.create("ns3").build()); 438 ha.createNamespace(NamespaceDescriptor.create("ns4").build()); 439 440 TableDescriptor desc = TableDescriptorBuilder.newBuilder(table1) 441 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(famName)).build(); 442 ha.createTable(desc); 443 table1Desc = desc; 444 Connection conn = ConnectionFactory.createConnection(conf1); 445 Table table = conn.getTable(table1); 446 loadTable(table); 447 table.close(); 448 table2 = TableName.valueOf("ns2:test-" + tid + 1); 449 desc = TableDescriptorBuilder.newBuilder(table2) 450 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(famName)).build(); 451 ha.createTable(desc); 452 table = conn.getTable(table2); 453 loadTable(table); 454 table.close(); 455 table3 = TableName.valueOf("ns3:test-" + tid + 2); 456 table = TEST_UTIL.createTable(table3, famName); 457 table.close(); 458 table4 = TableName.valueOf("ns4:test-" + tid + 3); 459 table = TEST_UTIL.createTable(table4, famName); 460 table.close(); 461 ha.close(); 462 conn.close(); 463 } 464 465 protected boolean checkSucceeded(String backupId) throws IOException { 466 BackupInfo status = getBackupInfo(backupId); 467 468 if (status == null) { 469 return false; 470 } 471 472 return status.getState() == BackupState.COMPLETE; 473 } 474 475 protected boolean checkFailed(String backupId) throws IOException { 476 BackupInfo status = getBackupInfo(backupId); 477 478 if (status == null) { 479 return false; 480 } 481 482 return status.getState() == BackupState.FAILED; 483 } 484 485 private BackupInfo getBackupInfo(String backupId) throws IOException { 486 try (BackupSystemTable table = new BackupSystemTable(TEST_UTIL.getConnection())) { 487 BackupInfo status = table.readBackupInfo(backupId); 488 return status; 489 } 490 } 491 492 protected static BackupAdmin getBackupAdmin() throws IOException { 493 return new BackupAdminImpl(TEST_UTIL.getConnection()); 494 } 495 496 /** 497 * Helper method 498 */ 499 protected List<TableName> toList(String... args) { 500 List<TableName> ret = new ArrayList<>(); 501 for (int i = 0; i < args.length; i++) { 502 ret.add(TableName.valueOf(args[i])); 503 } 504 return ret; 505 } 506 507 protected List<FileStatus> getListOfWALFiles(Configuration c) throws IOException { 508 Path logRoot = new Path(CommonFSUtils.getWALRootDir(c), HConstants.HREGION_LOGDIR_NAME); 509 FileSystem fs = logRoot.getFileSystem(c); 510 RemoteIterator<LocatedFileStatus> it = fs.listFiles(logRoot, true); 511 List<FileStatus> logFiles = new ArrayList<FileStatus>(); 512 while (it.hasNext()) { 513 LocatedFileStatus lfs = it.next(); 514 if (lfs.isFile() && !AbstractFSWALProvider.isMetaFile(lfs.getPath())) { 515 logFiles.add(lfs); 516 LOG.info(Objects.toString(lfs)); 517 } 518 } 519 return logFiles; 520 } 521 522 protected void dumpBackupDir() throws IOException { 523 // Dump Backup Dir 524 FileSystem fs = FileSystem.get(conf1); 525 RemoteIterator<LocatedFileStatus> it = fs.listFiles(new Path(BACKUP_ROOT_DIR), true); 526 while (it.hasNext()) { 527 LOG.debug(Objects.toString(it.next().getPath())); 528 } 529 } 530}