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.impl; 019 020import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.JOB_NAME_CONF_KEY; 021 022import java.io.IOException; 023import java.net.URI; 024import java.net.URISyntaxException; 025import java.util.ArrayList; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030import org.apache.commons.io.FilenameUtils; 031import org.apache.commons.lang3.StringUtils; 032import org.apache.hadoop.fs.FileSystem; 033import org.apache.hadoop.fs.LocatedFileStatus; 034import org.apache.hadoop.fs.Path; 035import org.apache.hadoop.fs.RemoteIterator; 036import org.apache.hadoop.hbase.TableName; 037import org.apache.hadoop.hbase.backup.BackupCopyJob; 038import org.apache.hadoop.hbase.backup.BackupInfo; 039import org.apache.hadoop.hbase.backup.BackupInfo.BackupPhase; 040import org.apache.hadoop.hbase.backup.BackupRequest; 041import org.apache.hadoop.hbase.backup.BackupRestoreFactory; 042import org.apache.hadoop.hbase.backup.BackupType; 043import org.apache.hadoop.hbase.backup.HBackupFileSystem; 044import org.apache.hadoop.hbase.backup.mapreduce.MapReduceBackupCopyJob; 045import org.apache.hadoop.hbase.backup.mapreduce.MapReduceHFileSplitterJob; 046import org.apache.hadoop.hbase.backup.util.BackupUtils; 047import org.apache.hadoop.hbase.client.Admin; 048import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 049import org.apache.hadoop.hbase.client.Connection; 050import org.apache.hadoop.hbase.io.hfile.HFile; 051import org.apache.hadoop.hbase.mapreduce.WALPlayer; 052import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils; 053import org.apache.hadoop.hbase.snapshot.SnapshotManifest; 054import org.apache.hadoop.hbase.snapshot.SnapshotRegionLocator; 055import org.apache.hadoop.hbase.util.CommonFSUtils; 056import org.apache.hadoop.hbase.util.HFileArchiveUtil; 057import org.apache.hadoop.hbase.wal.AbstractFSWALProvider; 058import org.apache.hadoop.util.Tool; 059import org.apache.yetus.audience.InterfaceAudience; 060import org.slf4j.Logger; 061import org.slf4j.LoggerFactory; 062 063import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 064 065import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos; 066 067/** 068 * Incremental backup implementation. See the {@link #execute() execute} method. 069 */ 070@InterfaceAudience.Private 071public class IncrementalTableBackupClient extends TableBackupClient { 072 private static final Logger LOG = LoggerFactory.getLogger(IncrementalTableBackupClient.class); 073 074 protected IncrementalTableBackupClient() { 075 } 076 077 public IncrementalTableBackupClient(final Connection conn, final String backupId, 078 BackupRequest request) throws IOException { 079 super(conn, backupId, request); 080 } 081 082 protected List<String> filterMissingFiles(List<String> incrBackupFileList) throws IOException { 083 List<String> list = new ArrayList<>(); 084 for (String file : incrBackupFileList) { 085 Path p = new Path(file); 086 if (fs.exists(p) || isActiveWalPath(p)) { 087 list.add(file); 088 } else { 089 LOG.warn("Can't find file: " + file); 090 } 091 } 092 return list; 093 } 094 095 /** 096 * Check if a given path is belongs to active WAL directory 097 * @param p path 098 * @return true, if yes 099 */ 100 protected boolean isActiveWalPath(Path p) { 101 return !AbstractFSWALProvider.isArchivedLogFile(p); 102 } 103 104 protected static int getIndex(TableName tbl, List<TableName> sTableList) { 105 if (sTableList == null) { 106 return 0; 107 } 108 109 for (int i = 0; i < sTableList.size(); i++) { 110 if (tbl.equals(sTableList.get(i))) { 111 return i; 112 } 113 } 114 return -1; 115 } 116 117 /** 118 * Reads bulk load records from backup table, iterates through the records and forms the paths for 119 * bulk loaded hfiles. Copies the bulk loaded hfiles to backup destination. This method does NOT 120 * clean up the entries in the bulk load system table. Those entries should not be cleaned until 121 * the backup is marked as complete. 122 * @param tablesToBackup list of tables to be backed up 123 */ 124 protected List<BulkLoad> handleBulkLoad(List<TableName> tablesToBackup) throws IOException { 125 List<String> activeFiles = new ArrayList<>(); 126 List<String> archiveFiles = new ArrayList<>(); 127 List<BulkLoad> bulkLoads = backupManager.readBulkloadRows(tablesToBackup); 128 FileSystem tgtFs; 129 try { 130 tgtFs = FileSystem.get(new URI(backupInfo.getBackupRootDir()), conf); 131 } catch (URISyntaxException use) { 132 throw new IOException("Unable to get FileSystem", use); 133 } 134 Path rootdir = CommonFSUtils.getRootDir(conf); 135 Path tgtRoot = new Path(new Path(backupInfo.getBackupRootDir()), backupId); 136 137 for (BulkLoad bulkLoad : bulkLoads) { 138 TableName srcTable = bulkLoad.getTableName(); 139 String regionName = bulkLoad.getRegion(); 140 String fam = bulkLoad.getColumnFamily(); 141 String filename = FilenameUtils.getName(bulkLoad.getHfilePath()); 142 143 if (!tablesToBackup.contains(srcTable)) { 144 LOG.debug("Skipping {} since it is not in tablesToBackup", srcTable); 145 continue; 146 } 147 Path tblDir = CommonFSUtils.getTableDir(rootdir, srcTable); 148 Path p = new Path(tblDir, regionName + Path.SEPARATOR + fam + Path.SEPARATOR + filename); 149 150 String srcTableQualifier = srcTable.getQualifierAsString(); 151 String srcTableNs = srcTable.getNamespaceAsString(); 152 Path tgtFam = new Path(tgtRoot, srcTableNs + Path.SEPARATOR + srcTableQualifier 153 + Path.SEPARATOR + regionName + Path.SEPARATOR + fam); 154 if (!tgtFs.mkdirs(tgtFam)) { 155 throw new IOException("couldn't create " + tgtFam); 156 } 157 Path tgt = new Path(tgtFam, filename); 158 159 Path archiveDir = HFileArchiveUtil.getStoreArchivePath(conf, srcTable, regionName, fam); 160 Path archive = new Path(archiveDir, filename); 161 162 if (fs.exists(p)) { 163 if (LOG.isTraceEnabled()) { 164 LOG.trace("found bulk hfile {} in {} for {}", bulkLoad.getHfilePath(), p.getParent(), 165 srcTableQualifier); 166 LOG.trace("copying {} to {}", p, tgt); 167 } 168 activeFiles.add(p.toString()); 169 } else if (fs.exists(archive)) { 170 LOG.debug("copying archive {} to {}", archive, tgt); 171 archiveFiles.add(archive.toString()); 172 } 173 mergeSplitBulkloads(activeFiles, archiveFiles, srcTable); 174 incrementalCopyBulkloadHFiles(tgtFs, srcTable); 175 } 176 return bulkLoads; 177 } 178 179 private void mergeSplitBulkloads(List<String> activeFiles, List<String> archiveFiles, 180 TableName tn) throws IOException { 181 int attempt = 1; 182 183 while (!activeFiles.isEmpty()) { 184 LOG.info("MergeSplit {} active bulk loaded files. Attempt={}", activeFiles.size(), attempt++); 185 // Active file can be archived during copy operation, 186 // we need to handle this properly 187 try { 188 mergeSplitBulkloads(activeFiles, tn); 189 break; 190 } catch (IOException e) { 191 int numActiveFiles = activeFiles.size(); 192 updateFileLists(activeFiles, archiveFiles); 193 if (activeFiles.size() < numActiveFiles) { 194 continue; 195 } 196 197 throw e; 198 } 199 } 200 201 if (!archiveFiles.isEmpty()) { 202 mergeSplitBulkloads(archiveFiles, tn); 203 } 204 } 205 206 private void mergeSplitBulkloads(List<String> files, TableName tn) throws IOException { 207 MapReduceHFileSplitterJob player = new MapReduceHFileSplitterJob(); 208 conf.set(MapReduceHFileSplitterJob.BULK_OUTPUT_CONF_KEY, 209 getBulkOutputDirForTable(tn).toString()); 210 player.setConf(conf); 211 212 String inputDirs = StringUtils.join(files, ","); 213 String[] args = { inputDirs, tn.getNameWithNamespaceInclAsString() }; 214 215 int result; 216 217 try { 218 result = player.run(args); 219 } catch (Exception e) { 220 LOG.error("Failed to run MapReduceHFileSplitterJob", e); 221 throw new IOException(e); 222 } 223 224 if (result != 0) { 225 throw new IOException( 226 "Failed to run MapReduceHFileSplitterJob with invalid result: " + result); 227 } 228 } 229 230 private void updateFileLists(List<String> activeFiles, List<String> archiveFiles) 231 throws IOException { 232 List<String> newlyArchived = new ArrayList<>(); 233 234 for (String spath : activeFiles) { 235 if (!fs.exists(new Path(spath))) { 236 newlyArchived.add(spath); 237 } 238 } 239 240 if (newlyArchived.size() > 0) { 241 activeFiles.removeAll(newlyArchived); 242 archiveFiles.addAll(newlyArchived); 243 } 244 245 LOG.debug(newlyArchived.size() + " files have been archived."); 246 } 247 248 /** 249 * @throws IOException If the execution of the backup fails 250 * @throws ColumnFamilyMismatchException If the column families of the current table do not match 251 * the column families for the last full backup. In which 252 * case, a full backup should be taken 253 */ 254 @Override 255 public void execute() throws IOException, ColumnFamilyMismatchException { 256 try { 257 Map<TableName, String> tablesToFullBackupIds = getFullBackupIds(); 258 verifyCfCompatibility(backupInfo.getTables(), tablesToFullBackupIds); 259 260 // case PREPARE_INCREMENTAL: 261 beginBackup(backupManager, backupInfo); 262 backupInfo.setPhase(BackupPhase.PREPARE_INCREMENTAL); 263 LOG.debug("For incremental backup, current table set is " 264 + backupManager.getIncrementalBackupTableSet()); 265 newTimestamps = ((IncrementalBackupManager) backupManager).getIncrBackupLogFileMap(); 266 } catch (Exception e) { 267 // fail the overall backup and return 268 failBackup(conn, backupInfo, backupManager, e, "Unexpected Exception : ", 269 BackupType.INCREMENTAL, conf); 270 throw new IOException(e); 271 } 272 273 // case INCREMENTAL_COPY: 274 try { 275 // copy out the table and region info files for each table 276 BackupUtils.copyTableRegionInfo(conn, backupInfo, conf); 277 setupRegionLocator(); 278 // convert WAL to HFiles and copy them to .tmp under BACKUP_ROOT 279 convertWALsToHFiles(); 280 incrementalCopyHFiles(new String[] { getBulkOutputDir().toString() }, 281 backupInfo.getBackupRootDir()); 282 } catch (Exception e) { 283 String msg = "Unexpected exception in incremental-backup: incremental copy " + backupId; 284 // fail the overall backup and return 285 failBackup(conn, backupInfo, backupManager, e, msg, BackupType.INCREMENTAL, conf); 286 throw new IOException(e); 287 } 288 // case INCR_BACKUP_COMPLETE: 289 // set overall backup status: complete. Here we make sure to complete the backup. 290 // After this checkpoint, even if entering cancel process, will let the backup finished 291 try { 292 // Set the previousTimestampMap which is before this current log roll to the manifest. 293 Map<TableName, Map<String, Long>> previousTimestampMap = backupManager.readLogTimestampMap(); 294 backupInfo.setIncrTimestampMap(previousTimestampMap); 295 296 // The table list in backupInfo is good for both full backup and incremental backup. 297 // For incremental backup, it contains the incremental backup table set. 298 backupManager.writeRegionServerLogTimestamp(backupInfo.getTables(), newTimestamps); 299 300 Map<TableName, Map<String, Long>> newTableSetTimestampMap = 301 backupManager.readLogTimestampMap(); 302 303 backupInfo.setTableSetTimestampMap(newTableSetTimestampMap); 304 Long newStartCode = 305 BackupUtils.getMinValue(BackupUtils.getRSLogTimestampMins(newTableSetTimestampMap)); 306 backupManager.writeBackupStartCode(newStartCode); 307 308 List<BulkLoad> bulkLoads = handleBulkLoad(backupInfo.getTableNames()); 309 310 // backup complete 311 completeBackup(conn, backupInfo, BackupType.INCREMENTAL, conf); 312 313 List<byte[]> bulkLoadedRows = Lists.transform(bulkLoads, BulkLoad::getRowKey); 314 backupManager.deleteBulkLoadedRows(bulkLoadedRows); 315 } catch (IOException e) { 316 failBackup(conn, backupInfo, backupManager, e, "Unexpected Exception : ", 317 BackupType.INCREMENTAL, conf); 318 throw new IOException(e); 319 } 320 } 321 322 protected void incrementalCopyHFiles(String[] files, String backupDest) throws IOException { 323 try { 324 LOG.debug("Incremental copy HFiles is starting. dest=" + backupDest); 325 // set overall backup phase: incremental_copy 326 backupInfo.setPhase(BackupPhase.INCREMENTAL_COPY); 327 // get incremental backup file list and prepare parms for DistCp 328 String[] strArr = new String[files.length + 1]; 329 System.arraycopy(files, 0, strArr, 0, files.length); 330 strArr[strArr.length - 1] = backupDest; 331 332 String jobname = "Incremental_Backup-HFileCopy-" + backupInfo.getBackupId(); 333 if (LOG.isDebugEnabled()) { 334 LOG.debug("Setting incremental copy HFiles job name to : " + jobname); 335 } 336 conf.set(JOB_NAME_CONF_KEY, jobname); 337 338 BackupCopyJob copyService = BackupRestoreFactory.getBackupCopyJob(conf); 339 int res = copyService.copy(backupInfo, backupManager, conf, BackupType.INCREMENTAL, strArr); 340 if (res != 0) { 341 LOG.error("Copy incremental HFile files failed with return code: " + res + "."); 342 throw new IOException( 343 "Failed copy from " + StringUtils.join(files, ',') + " to " + backupDest); 344 } 345 LOG.debug("Incremental copy HFiles from " + StringUtils.join(files, ',') + " to " + backupDest 346 + " finished."); 347 } finally { 348 deleteBulkLoadDirectory(); 349 } 350 } 351 352 protected void deleteBulkLoadDirectory() throws IOException { 353 // delete original bulk load directory on method exit 354 Path path = getBulkOutputDir(); 355 FileSystem fs = FileSystem.get(path.toUri(), conf); 356 boolean result = fs.delete(path, true); 357 if (!result) { 358 LOG.warn("Could not delete " + path); 359 } 360 } 361 362 protected void convertWALsToHFiles() throws IOException { 363 // get incremental backup file list and prepare parameters for DistCp 364 List<String> incrBackupFileList = backupInfo.getIncrBackupFileList(); 365 // Get list of tables in incremental backup set 366 Set<TableName> tableSet = backupManager.getIncrementalBackupTableSet(); 367 // filter missing files out (they have been copied by previous backups) 368 incrBackupFileList = filterMissingFiles(incrBackupFileList); 369 List<String> tableList = new ArrayList<String>(); 370 for (TableName table : tableSet) { 371 // Check if table exists 372 if (tableExists(table, conn)) { 373 tableList.add(table.getNameAsString()); 374 } else { 375 LOG.warn("Table " + table + " does not exists. Skipping in WAL converter"); 376 } 377 } 378 walToHFiles(incrBackupFileList, tableList); 379 380 } 381 382 protected boolean tableExists(TableName table, Connection conn) throws IOException { 383 try (Admin admin = conn.getAdmin()) { 384 return admin.tableExists(table); 385 } 386 } 387 388 protected void walToHFiles(List<String> dirPaths, List<String> tableList) throws IOException { 389 Tool player = new WALPlayer(); 390 391 // Player reads all files in arbitrary directory structure and creates 392 // a Map task for each file. We use ';' as separator 393 // because WAL file names contains ',' 394 String dirs = StringUtils.join(dirPaths, ';'); 395 String jobname = "Incremental_Backup-" + backupId; 396 397 Path bulkOutputPath = getBulkOutputDir(); 398 conf.set(WALPlayer.BULK_OUTPUT_CONF_KEY, bulkOutputPath.toString()); 399 conf.set(WALPlayer.INPUT_FILES_SEPARATOR_KEY, ";"); 400 conf.setBoolean(WALPlayer.MULTI_TABLES_SUPPORT, true); 401 conf.set(JOB_NAME_CONF_KEY, jobname); 402 String[] playerArgs = { dirs, StringUtils.join(tableList, ",") }; 403 404 try { 405 player.setConf(conf); 406 int result = player.run(playerArgs); 407 if (result != 0) { 408 throw new IOException("WAL Player failed"); 409 } 410 conf.unset(WALPlayer.INPUT_FILES_SEPARATOR_KEY); 411 conf.unset(JOB_NAME_CONF_KEY); 412 } catch (IOException e) { 413 throw e; 414 } catch (Exception ee) { 415 throw new IOException("Can not convert from directory " + dirs 416 + " (check Hadoop, HBase and WALPlayer M/R job logs) ", ee); 417 } 418 } 419 420 private void incrementalCopyBulkloadHFiles(FileSystem tgtFs, TableName tn) throws IOException { 421 Path bulkOutDir = getBulkOutputDirForTable(tn); 422 423 if (tgtFs.exists(bulkOutDir)) { 424 conf.setInt(MapReduceBackupCopyJob.NUMBER_OF_LEVELS_TO_PRESERVE_KEY, 2); 425 Path tgtPath = getTargetDirForTable(tn); 426 try { 427 RemoteIterator<LocatedFileStatus> locatedFiles = tgtFs.listFiles(bulkOutDir, true); 428 List<String> files = new ArrayList<>(); 429 while (locatedFiles.hasNext()) { 430 LocatedFileStatus file = locatedFiles.next(); 431 if (file.isFile() && HFile.isHFileFormat(tgtFs, file.getPath())) { 432 files.add(file.getPath().toString()); 433 } 434 } 435 incrementalCopyHFiles(files.toArray(files.toArray(new String[0])), tgtPath.toString()); 436 } finally { 437 conf.unset(MapReduceBackupCopyJob.NUMBER_OF_LEVELS_TO_PRESERVE_KEY); 438 } 439 } 440 } 441 442 protected Path getBulkOutputDirForTable(TableName table) { 443 Path tablePath = getBulkOutputDir(); 444 tablePath = new Path(tablePath, table.getNamespaceAsString()); 445 tablePath = new Path(tablePath, table.getQualifierAsString()); 446 return new Path(tablePath, "data"); 447 } 448 449 protected Path getBulkOutputDir() { 450 String backupId = backupInfo.getBackupId(); 451 Path path = new Path(backupInfo.getBackupRootDir()); 452 path = new Path(path, ".tmp"); 453 path = new Path(path, backupId); 454 return path; 455 } 456 457 private Path getTargetDirForTable(TableName table) { 458 Path path = new Path(backupInfo.getBackupRootDir() + Path.SEPARATOR + backupInfo.getBackupId()); 459 path = new Path(path, table.getNamespaceAsString()); 460 path = new Path(path, table.getNameAsString()); 461 return path; 462 } 463 464 private void setupRegionLocator() throws IOException { 465 Map<TableName, String> fullBackupIds = getFullBackupIds(); 466 try (BackupAdminImpl backupAdmin = new BackupAdminImpl(conn)) { 467 468 for (TableName tableName : backupInfo.getTables()) { 469 String fullBackupId = fullBackupIds.get(tableName); 470 BackupInfo fullBackupInfo = backupAdmin.getBackupInfo(fullBackupId); 471 String snapshotName = fullBackupInfo.getSnapshotName(tableName); 472 Path root = HBackupFileSystem.getTableBackupPath(tableName, 473 new Path(fullBackupInfo.getBackupRootDir()), fullBackupId); 474 String manifestDir = 475 SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, root).toString(); 476 SnapshotRegionLocator.setSnapshotManifestDir(conf, manifestDir, tableName); 477 } 478 } 479 } 480 481 private Map<TableName, String> getFullBackupIds() throws IOException { 482 // Ancestors are stored from newest to oldest, so we can iterate backwards 483 // in order to populate our backupId map with the most recent full backup 484 // for a given table 485 List<BackupManifest.BackupImage> images = getAncestors(backupInfo); 486 Map<TableName, String> results = new HashMap<>(); 487 for (int i = images.size() - 1; i >= 0; i--) { 488 BackupManifest.BackupImage image = images.get(i); 489 if (image.getType() != BackupType.FULL) { 490 continue; 491 } 492 493 for (TableName tn : image.getTableNames()) { 494 results.put(tn, image.getBackupId()); 495 } 496 } 497 return results; 498 } 499 500 /** 501 * Verifies that the current table descriptor CFs matches the descriptor CFs of the last full 502 * backup for the tables. This ensures CF compatibility across incremental backups. If a mismatch 503 * is detected, a full table backup should be taken, rather than an incremental one 504 */ 505 private void verifyCfCompatibility(Set<TableName> tables, 506 Map<TableName, String> tablesToFullBackupId) throws IOException, ColumnFamilyMismatchException { 507 ColumnFamilyMismatchException.ColumnFamilyMismatchExceptionBuilder exBuilder = 508 ColumnFamilyMismatchException.newBuilder(); 509 try (Admin admin = conn.getAdmin(); BackupAdminImpl backupAdmin = new BackupAdminImpl(conn)) { 510 for (TableName tn : tables) { 511 String backupId = tablesToFullBackupId.get(tn); 512 BackupInfo fullBackupInfo = backupAdmin.getBackupInfo(backupId); 513 514 ColumnFamilyDescriptor[] currentCfs = admin.getDescriptor(tn).getColumnFamilies(); 515 String snapshotName = fullBackupInfo.getSnapshotName(tn); 516 Path root = HBackupFileSystem.getTableBackupPath(tn, 517 new Path(fullBackupInfo.getBackupRootDir()), fullBackupInfo.getBackupId()); 518 Path manifestDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, root); 519 520 FileSystem fs; 521 try { 522 fs = FileSystem.get(new URI(fullBackupInfo.getBackupRootDir()), conf); 523 } catch (URISyntaxException e) { 524 throw new IOException("Unable to get fs for backup " + fullBackupInfo.getBackupId(), e); 525 } 526 527 SnapshotProtos.SnapshotDescription snapshotDescription = 528 SnapshotDescriptionUtils.readSnapshotInfo(fs, manifestDir); 529 SnapshotManifest manifest = 530 SnapshotManifest.open(conf, fs, manifestDir, snapshotDescription); 531 532 ColumnFamilyDescriptor[] backupCfs = manifest.getTableDescriptor().getColumnFamilies(); 533 if (!areCfsCompatible(currentCfs, backupCfs)) { 534 exBuilder.addMismatchedTable(tn, currentCfs, backupCfs); 535 } 536 } 537 } 538 539 ColumnFamilyMismatchException ex = exBuilder.build(); 540 if (!ex.getMismatchedTables().isEmpty()) { 541 throw ex; 542 } 543 } 544 545 private static boolean areCfsCompatible(ColumnFamilyDescriptor[] currentCfs, 546 ColumnFamilyDescriptor[] backupCfs) { 547 if (currentCfs.length != backupCfs.length) { 548 return false; 549 } 550 551 for (int i = 0; i < backupCfs.length; i++) { 552 String currentCf = currentCfs[i].getNameAsString(); 553 String backupCf = backupCfs[i].getNameAsString(); 554 555 if (!currentCf.equals(backupCf)) { 556 return false; 557 } 558 } 559 560 return true; 561 } 562}