|
23 | 23 | import java.net.URI; |
24 | 24 | import java.net.URISyntaxException; |
25 | 25 | import java.util.ArrayList; |
| 26 | +import java.util.HashMap; |
26 | 27 | import java.util.List; |
27 | 28 | import java.util.Map; |
28 | 29 | import java.util.Set; |
|
32 | 33 | import org.apache.hadoop.fs.Path; |
33 | 34 | import org.apache.hadoop.hbase.TableName; |
34 | 35 | import org.apache.hadoop.hbase.backup.BackupCopyJob; |
| 36 | +import org.apache.hadoop.hbase.backup.BackupInfo; |
35 | 37 | import org.apache.hadoop.hbase.backup.BackupInfo.BackupPhase; |
36 | 38 | import org.apache.hadoop.hbase.backup.BackupRequest; |
37 | 39 | import org.apache.hadoop.hbase.backup.BackupRestoreFactory; |
38 | 40 | import org.apache.hadoop.hbase.backup.BackupType; |
| 41 | +import org.apache.hadoop.hbase.backup.HBackupFileSystem; |
39 | 42 | import org.apache.hadoop.hbase.backup.mapreduce.MapReduceBackupCopyJob; |
40 | 43 | import org.apache.hadoop.hbase.backup.util.BackupUtils; |
41 | 44 | import org.apache.hadoop.hbase.client.Admin; |
| 45 | +import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; |
42 | 46 | import org.apache.hadoop.hbase.client.Connection; |
43 | 47 | import org.apache.hadoop.hbase.mapreduce.WALPlayer; |
| 48 | +import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils; |
| 49 | +import org.apache.hadoop.hbase.snapshot.SnapshotManifest; |
44 | 50 | import org.apache.hadoop.hbase.util.Bytes; |
45 | 51 | import org.apache.hadoop.hbase.util.CommonFSUtils; |
46 | 52 | import org.apache.hadoop.hbase.util.HFileArchiveUtil; |
|
51 | 57 | import org.slf4j.Logger; |
52 | 58 | import org.slf4j.LoggerFactory; |
53 | 59 |
|
| 60 | +import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos; |
| 61 | + |
54 | 62 | /** |
55 | 63 | * Incremental backup implementation. See the {@link #execute() execute} method. |
56 | 64 | */ |
@@ -260,8 +268,11 @@ private void updateFileLists(List<String> activeFiles, List<String> archiveFiles |
260 | 268 | } |
261 | 269 |
|
262 | 270 | @Override |
263 | | - public void execute() throws IOException { |
| 271 | + public void execute() throws IOException, ColumnFamilyMismatchException { |
264 | 272 | try { |
| 273 | + Map<TableName, String> tablesToFullBackupIds = getFullBackupIds(); |
| 274 | + verifyCfCompatibility(backupInfo.getTables(), tablesToFullBackupIds); |
| 275 | + |
265 | 276 | // case PREPARE_INCREMENTAL: |
266 | 277 | beginBackup(backupManager, backupInfo); |
267 | 278 | backupInfo.setPhase(BackupPhase.PREPARE_INCREMENTAL); |
@@ -434,4 +445,86 @@ protected Path getBulkOutputDir() { |
434 | 445 | path = new Path(path, backupId); |
435 | 446 | return path; |
436 | 447 | } |
| 448 | + |
| 449 | + private Map<TableName, String> getFullBackupIds() throws IOException { |
| 450 | + // Ancestors are stored from newest to oldest, so we can iterate backwards |
| 451 | + // in order to populate our backupId map with the most recent full backup |
| 452 | + // for a given table |
| 453 | + List<BackupManifest.BackupImage> images = getAncestors(backupInfo); |
| 454 | + Map<TableName, String> results = new HashMap<>(); |
| 455 | + for (int i = images.size() - 1; i >= 0; i--) { |
| 456 | + BackupManifest.BackupImage image = images.get(i); |
| 457 | + if (image.getType() != BackupType.FULL) { |
| 458 | + continue; |
| 459 | + } |
| 460 | + |
| 461 | + for (TableName tn : image.getTableNames()) { |
| 462 | + results.put(tn, image.getBackupId()); |
| 463 | + } |
| 464 | + } |
| 465 | + return results; |
| 466 | + } |
| 467 | + |
| 468 | + /** |
| 469 | + * Verifies that the current table descriptor CFs matches the descriptor CFs of the last full |
| 470 | + * backup for the tables. This ensures CF compatibility across incremental backups. If a mismatch |
| 471 | + * is detected, a full table backup should be taken, rather than an incremental one |
| 472 | + */ |
| 473 | + private void verifyCfCompatibility(Set<TableName> tables, |
| 474 | + Map<TableName, String> tablesToFullBackupId) throws IOException, ColumnFamilyMismatchException { |
| 475 | + ColumnFamilyMismatchException.ColumnFamilyMismatchExceptionBuilder exBuilder = |
| 476 | + ColumnFamilyMismatchException.newBuilder(); |
| 477 | + try (Admin admin = conn.getAdmin(); BackupAdminImpl backupAdmin = new BackupAdminImpl(conn)) { |
| 478 | + for (TableName tn : tables) { |
| 479 | + String backupId = tablesToFullBackupId.get(tn); |
| 480 | + BackupInfo fullBackupInfo = backupAdmin.getBackupInfo(backupId); |
| 481 | + |
| 482 | + ColumnFamilyDescriptor[] currentCfs = admin.getDescriptor(tn).getColumnFamilies(); |
| 483 | + String snapshotName = fullBackupInfo.getSnapshotName(tn); |
| 484 | + Path root = HBackupFileSystem.getTableBackupPath(tn, |
| 485 | + new Path(fullBackupInfo.getBackupRootDir()), fullBackupInfo.getBackupId()); |
| 486 | + Path manifestDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, root); |
| 487 | + |
| 488 | + FileSystem fs; |
| 489 | + try { |
| 490 | + fs = FileSystem.get(new URI(fullBackupInfo.getBackupRootDir()), conf); |
| 491 | + } catch (URISyntaxException e) { |
| 492 | + throw new IOException("Unable to get fs", e); |
| 493 | + } |
| 494 | + |
| 495 | + SnapshotProtos.SnapshotDescription snapshotDescription = |
| 496 | + SnapshotDescriptionUtils.readSnapshotInfo(fs, manifestDir); |
| 497 | + SnapshotManifest manifest = |
| 498 | + SnapshotManifest.open(conf, fs, manifestDir, snapshotDescription); |
| 499 | + |
| 500 | + ColumnFamilyDescriptor[] backupCfs = manifest.getTableDescriptor().getColumnFamilies(); |
| 501 | + if (!areCfsCompatible(currentCfs, backupCfs)) { |
| 502 | + exBuilder.addMismatchedTable(tn, currentCfs, backupCfs); |
| 503 | + } |
| 504 | + } |
| 505 | + } |
| 506 | + |
| 507 | + ColumnFamilyMismatchException ex = exBuilder.build(); |
| 508 | + if (!ex.getMismatchedTables().isEmpty()) { |
| 509 | + throw ex; |
| 510 | + } |
| 511 | + } |
| 512 | + |
| 513 | + private static boolean areCfsCompatible(ColumnFamilyDescriptor[] currentCfs, |
| 514 | + ColumnFamilyDescriptor[] backupCfs) { |
| 515 | + if (currentCfs.length != backupCfs.length) { |
| 516 | + return false; |
| 517 | + } |
| 518 | + |
| 519 | + for (int i = 0; i < backupCfs.length; i++) { |
| 520 | + String currentCf = currentCfs[i].getNameAsString(); |
| 521 | + String backupCf = backupCfs[i].getNameAsString(); |
| 522 | + |
| 523 | + if (!currentCf.equals(backupCf)) { |
| 524 | + return false; |
| 525 | + } |
| 526 | + } |
| 527 | + |
| 528 | + return true; |
| 529 | + } |
437 | 530 | } |
0 commit comments