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.util; 019 020import java.io.FileNotFoundException; 021import java.io.IOException; 022import java.net.URLDecoder; 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.Comparator; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029import java.util.Map.Entry; 030import java.util.TreeSet; 031import java.util.function.Predicate; 032import java.util.stream.Stream; 033import org.apache.hadoop.conf.Configuration; 034import org.apache.hadoop.fs.FSDataOutputStream; 035import org.apache.hadoop.fs.FileStatus; 036import org.apache.hadoop.fs.FileSystem; 037import org.apache.hadoop.fs.LocatedFileStatus; 038import org.apache.hadoop.fs.Path; 039import org.apache.hadoop.fs.PathFilter; 040import org.apache.hadoop.fs.RemoteIterator; 041import org.apache.hadoop.fs.permission.FsPermission; 042import org.apache.hadoop.hbase.HConstants; 043import org.apache.hadoop.hbase.MetaTableAccessor; 044import org.apache.hadoop.hbase.ServerName; 045import org.apache.hadoop.hbase.TableName; 046import org.apache.hadoop.hbase.backup.BackupInfo; 047import org.apache.hadoop.hbase.backup.BackupRestoreConstants; 048import org.apache.hadoop.hbase.backup.HBackupFileSystem; 049import org.apache.hadoop.hbase.backup.RestoreRequest; 050import org.apache.hadoop.hbase.backup.impl.BackupManifest; 051import org.apache.hadoop.hbase.backup.impl.BackupManifest.BackupImage; 052import org.apache.hadoop.hbase.backup.impl.BackupSystemTable; 053import org.apache.hadoop.hbase.backup.master.LogRollMasterProcedureManager; 054import org.apache.hadoop.hbase.client.Admin; 055import org.apache.hadoop.hbase.client.Connection; 056import org.apache.hadoop.hbase.client.RegionInfo; 057import org.apache.hadoop.hbase.client.TableDescriptor; 058import org.apache.hadoop.hbase.master.region.MasterRegionFactory; 059import org.apache.hadoop.hbase.tool.BulkLoadHFiles; 060import org.apache.hadoop.hbase.util.CommonFSUtils; 061import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 062import org.apache.hadoop.hbase.util.FSTableDescriptors; 063import org.apache.hadoop.hbase.util.FSUtils; 064import org.apache.hadoop.hbase.wal.AbstractFSWALProvider; 065import org.apache.yetus.audience.InterfaceAudience; 066import org.slf4j.Logger; 067import org.slf4j.LoggerFactory; 068 069import org.apache.hbase.thirdparty.com.google.common.base.Splitter; 070import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap; 071import org.apache.hbase.thirdparty.com.google.common.collect.Iterables; 072import org.apache.hbase.thirdparty.com.google.common.collect.Iterators; 073 074/** 075 * A collection for methods used by multiple classes to backup HBase tables. 076 */ 077@InterfaceAudience.Private 078public final class BackupUtils { 079 private static final Logger LOG = LoggerFactory.getLogger(BackupUtils.class); 080 public static final String LOGNAME_SEPARATOR = "."; 081 public static final int MILLISEC_IN_HOUR = 3600000; 082 083 private BackupUtils() { 084 throw new AssertionError("Instantiating utility class..."); 085 } 086 087 /** 088 * Loop through the RS log timestamp map for the tables, for each RS, find the min timestamp value 089 * for the RS among the tables. 090 * @param rsLogTimestampMap timestamp map 091 * @return the min timestamp of each RS 092 */ 093 public static Map<String, Long> 094 getRSLogTimestampMins(Map<TableName, Map<String, Long>> rsLogTimestampMap) { 095 if (rsLogTimestampMap == null || rsLogTimestampMap.isEmpty()) { 096 return null; 097 } 098 099 HashMap<String, Long> rsLogTimestampMins = new HashMap<>(); 100 HashMap<String, HashMap<TableName, Long>> rsLogTimestampMapByRS = new HashMap<>(); 101 102 for (Entry<TableName, Map<String, Long>> tableEntry : rsLogTimestampMap.entrySet()) { 103 TableName table = tableEntry.getKey(); 104 Map<String, Long> rsLogTimestamp = tableEntry.getValue(); 105 for (Entry<String, Long> rsEntry : rsLogTimestamp.entrySet()) { 106 String rs = rsEntry.getKey(); 107 Long ts = rsEntry.getValue(); 108 rsLogTimestampMapByRS.putIfAbsent(rs, new HashMap<>()); 109 rsLogTimestampMapByRS.get(rs).put(table, ts); 110 } 111 } 112 113 for (Entry<String, HashMap<TableName, Long>> entry : rsLogTimestampMapByRS.entrySet()) { 114 String rs = entry.getKey(); 115 rsLogTimestampMins.put(rs, BackupUtils.getMinValue(entry.getValue())); 116 } 117 118 return rsLogTimestampMins; 119 } 120 121 /** 122 * copy out Table RegionInfo into incremental backup image need to consider move this logic into 123 * HBackupFileSystem 124 * @param conn connection 125 * @param backupInfo backup info 126 * @param conf configuration 127 * @throws IOException exception 128 */ 129 public static void copyTableRegionInfo(Connection conn, BackupInfo backupInfo, Configuration conf) 130 throws IOException { 131 Path rootDir = CommonFSUtils.getRootDir(conf); 132 FileSystem fs = rootDir.getFileSystem(conf); 133 134 // for each table in the table set, copy out the table info and region 135 // info files in the correct directory structure 136 try (Admin admin = conn.getAdmin()) { 137 for (TableName table : backupInfo.getTables()) { 138 if (!admin.tableExists(table)) { 139 LOG.warn("Table " + table + " does not exists, skipping it."); 140 continue; 141 } 142 TableDescriptor orig = FSTableDescriptors.getTableDescriptorFromFs(fs, rootDir, table); 143 144 // write a copy of descriptor to the target directory 145 Path target = new Path(backupInfo.getTableBackupDir(table)); 146 FileSystem targetFs = target.getFileSystem(conf); 147 try (FSTableDescriptors descriptors = 148 new FSTableDescriptors(targetFs, CommonFSUtils.getRootDir(conf))) { 149 descriptors.createTableDescriptorForTableDirectory(target, orig, false); 150 } 151 LOG.debug("Attempting to copy table info for:" + table + " target: " + target 152 + " descriptor: " + orig); 153 LOG.debug("Finished copying tableinfo."); 154 List<RegionInfo> regions = MetaTableAccessor.getTableRegions(conn, table); 155 // For each region, write the region info to disk 156 LOG.debug("Starting to write region info for table " + table); 157 for (RegionInfo regionInfo : regions) { 158 Path regionDir = FSUtils 159 .getRegionDirFromTableDir(new Path(backupInfo.getTableBackupDir(table)), regionInfo); 160 regionDir = new Path(backupInfo.getTableBackupDir(table), regionDir.getName()); 161 writeRegioninfoOnFilesystem(conf, targetFs, regionDir, regionInfo); 162 } 163 LOG.debug("Finished writing region info for table " + table); 164 } 165 } 166 } 167 168 /** 169 * Write the .regioninfo file on-disk. 170 */ 171 public static void writeRegioninfoOnFilesystem(final Configuration conf, final FileSystem fs, 172 final Path regionInfoDir, RegionInfo regionInfo) throws IOException { 173 final byte[] content = RegionInfo.toDelimitedByteArray(regionInfo); 174 Path regionInfoFile = new Path(regionInfoDir, "." + HConstants.REGIONINFO_QUALIFIER_STR); 175 // First check to get the permissions 176 FsPermission perms = CommonFSUtils.getFilePermissions(fs, conf, HConstants.DATA_FILE_UMASK_KEY); 177 // Write the RegionInfo file content 178 FSDataOutputStream out = FSUtils.create(conf, fs, regionInfoFile, perms, null); 179 try { 180 out.write(content); 181 } finally { 182 out.close(); 183 } 184 } 185 186 /** 187 * Parses hostname:port from WAL file path 188 * @param p path to WAL file 189 * @return hostname:port 190 */ 191 public static String parseHostNameFromLogFile(Path p) { 192 try { 193 if (AbstractFSWALProvider.isArchivedLogFile(p)) { 194 return BackupUtils.parseHostFromOldLog(p); 195 } else { 196 ServerName sname = AbstractFSWALProvider.getServerNameFromWALDirectoryName(p); 197 if (sname != null) { 198 return sname.getAddress().toString(); 199 } else { 200 LOG.error("Skip log file (can't parse): " + p); 201 return null; 202 } 203 } 204 } catch (Exception e) { 205 LOG.error("Skip log file (can't parse): " + p, e); 206 return null; 207 } 208 } 209 210 /** 211 * Returns WAL file name 212 * @param walFileName WAL file name 213 * @return WAL file name 214 */ 215 public static String getUniqueWALFileNamePart(String walFileName) { 216 return getUniqueWALFileNamePart(new Path(walFileName)); 217 } 218 219 /** 220 * Returns WAL file name 221 * @param p WAL file path 222 * @return WAL file name 223 */ 224 public static String getUniqueWALFileNamePart(Path p) { 225 return p.getName(); 226 } 227 228 /** 229 * Get the total length of files under the given directory recursively. 230 * @param fs The hadoop file system 231 * @param dir The target directory 232 * @return the total length of files 233 * @throws IOException exception 234 */ 235 public static long getFilesLength(FileSystem fs, Path dir) throws IOException { 236 long totalLength = 0; 237 FileStatus[] files = CommonFSUtils.listStatus(fs, dir); 238 if (files != null) { 239 for (FileStatus fileStatus : files) { 240 if (fileStatus.isDirectory()) { 241 totalLength += getFilesLength(fs, fileStatus.getPath()); 242 } else { 243 totalLength += fileStatus.getLen(); 244 } 245 } 246 } 247 return totalLength; 248 } 249 250 /** 251 * Get list of all old WAL files (WALs and archive) 252 * @param c configuration 253 * @param hostTimestampMap {host,timestamp} map 254 * @return list of WAL files 255 * @throws IOException exception 256 */ 257 public static List<String> getWALFilesOlderThan(final Configuration c, 258 final HashMap<String, Long> hostTimestampMap) throws IOException { 259 Path walRootDir = CommonFSUtils.getWALRootDir(c); 260 Path logDir = new Path(walRootDir, HConstants.HREGION_LOGDIR_NAME); 261 Path oldLogDir = new Path(walRootDir, HConstants.HREGION_OLDLOGDIR_NAME); 262 List<String> logFiles = new ArrayList<>(); 263 264 PathFilter filter = p -> { 265 try { 266 if (AbstractFSWALProvider.isMetaFile(p)) { 267 return false; 268 } 269 String host = parseHostNameFromLogFile(p); 270 if (host == null) { 271 return false; 272 } 273 Long oldTimestamp = hostTimestampMap.get(host); 274 Long currentLogTS = BackupUtils.getCreationTime(p); 275 return currentLogTS <= oldTimestamp; 276 } catch (Exception e) { 277 LOG.warn("Can not parse" + p, e); 278 return false; 279 } 280 }; 281 FileSystem walFs = CommonFSUtils.getWALFileSystem(c); 282 logFiles = BackupUtils.getFiles(walFs, logDir, logFiles, filter); 283 logFiles = BackupUtils.getFiles(walFs, oldLogDir, logFiles, filter); 284 return logFiles; 285 } 286 287 public static TableName[] parseTableNames(String tables) { 288 if (tables == null) { 289 return null; 290 } 291 return Splitter.on(BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND).splitToStream(tables) 292 .map(TableName::valueOf).toArray(TableName[]::new); 293 } 294 295 /** 296 * Check whether the backup path exist 297 * @param backupStr backup 298 * @param conf configuration 299 * @return Yes if path exists 300 * @throws IOException exception 301 */ 302 public static boolean checkPathExist(String backupStr, Configuration conf) throws IOException { 303 boolean isExist = false; 304 Path backupPath = new Path(backupStr); 305 FileSystem fileSys = backupPath.getFileSystem(conf); 306 String targetFsScheme = fileSys.getUri().getScheme(); 307 if (LOG.isTraceEnabled()) { 308 LOG.trace("Schema of given url: " + backupStr + " is: " + targetFsScheme); 309 } 310 if (fileSys.exists(backupPath)) { 311 isExist = true; 312 } 313 return isExist; 314 } 315 316 /** 317 * Check target path first, confirm it doesn't exist before backup 318 * @param backupRootPath backup destination path 319 * @param conf configuration 320 * @throws IOException exception 321 */ 322 public static void checkTargetDir(String backupRootPath, Configuration conf) throws IOException { 323 boolean targetExists; 324 try { 325 targetExists = checkPathExist(backupRootPath, conf); 326 } catch (IOException e) { 327 String expMsg = e.getMessage(); 328 String newMsg = null; 329 if (expMsg.contains("No FileSystem for scheme")) { 330 newMsg = 331 "Unsupported filesystem scheme found in the backup target url. Error Message: " + expMsg; 332 LOG.error(newMsg); 333 throw new IOException(newMsg); 334 } else { 335 throw e; 336 } 337 } 338 339 if (targetExists) { 340 LOG.info("Using existing backup root dir: " + backupRootPath); 341 } else { 342 LOG.info("Backup root dir " + backupRootPath + " does not exist. Will be created."); 343 } 344 } 345 346 /** 347 * Get the min value for all the Values a map. 348 * @param map map 349 * @return the min value 350 */ 351 public static <T> Long getMinValue(Map<T, Long> map) { 352 Long minTimestamp = null; 353 if (map != null) { 354 ArrayList<Long> timestampList = new ArrayList<>(map.values()); 355 Collections.sort(timestampList); 356 // The min among all the RS log timestamps will be kept in backup system table table. 357 minTimestamp = timestampList.get(0); 358 } 359 return minTimestamp; 360 } 361 362 /** 363 * Parses host name:port from archived WAL path 364 * @param p path 365 * @return host name 366 */ 367 public static String parseHostFromOldLog(Path p) { 368 // Skip master wals 369 if (p.getName().endsWith(MasterRegionFactory.ARCHIVED_WAL_SUFFIX)) { 370 return null; 371 } 372 try { 373 String urlDecodedName = URLDecoder.decode(p.getName(), "UTF8"); 374 Iterable<String> nameSplitsOnComma = Splitter.on(",").split(urlDecodedName); 375 String host = Iterables.get(nameSplitsOnComma, 0); 376 String port = Iterables.get(nameSplitsOnComma, 1); 377 return host + ":" + port; 378 } catch (Exception e) { 379 LOG.warn("Skip log file (can't parse): {}", p); 380 return null; 381 } 382 } 383 384 /** 385 * Given the log file, parse the timestamp from the file name. The timestamp is the last number. 386 * @param p a path to the log file 387 * @return the timestamp 388 * @throws IOException exception 389 */ 390 public static Long getCreationTime(Path p) throws IOException { 391 int idx = p.getName().lastIndexOf(LOGNAME_SEPARATOR); 392 if (idx < 0) { 393 throw new IOException("Cannot parse timestamp from path " + p); 394 } 395 String ts = p.getName().substring(idx + 1); 396 return Long.parseLong(ts); 397 } 398 399 public static List<String> getFiles(FileSystem fs, Path rootDir, List<String> files, 400 PathFilter filter) throws IOException { 401 RemoteIterator<LocatedFileStatus> it = fs.listFiles(rootDir, true); 402 403 while (it.hasNext()) { 404 LocatedFileStatus lfs = it.next(); 405 if (lfs.isDirectory()) { 406 continue; 407 } 408 // apply filter 409 if (filter.accept(lfs.getPath())) { 410 files.add(lfs.getPath().toString()); 411 } 412 } 413 return files; 414 } 415 416 public static void cleanupBackupData(BackupInfo context, Configuration conf) throws IOException { 417 cleanupHLogDir(context, conf); 418 cleanupTargetDir(context, conf); 419 } 420 421 /** 422 * Clean up directories which are generated when DistCp copying hlogs 423 * @param backupInfo backup info 424 * @param conf configuration 425 * @throws IOException exception 426 */ 427 private static void cleanupHLogDir(BackupInfo backupInfo, Configuration conf) throws IOException { 428 String logDir = backupInfo.getHLogTargetDir(); 429 if (logDir == null) { 430 LOG.warn("No log directory specified for " + backupInfo.getBackupId()); 431 return; 432 } 433 434 Path rootPath = new Path(logDir).getParent(); 435 FileSystem fs = FileSystem.get(rootPath.toUri(), conf); 436 FileStatus[] files = listStatus(fs, rootPath, null); 437 if (files == null) { 438 return; 439 } 440 for (FileStatus file : files) { 441 LOG.debug("Delete log files: " + file.getPath().getName()); 442 fs.delete(file.getPath(), true); 443 } 444 } 445 446 private static void cleanupTargetDir(BackupInfo backupInfo, Configuration conf) { 447 try { 448 // clean up the data at target directory 449 LOG.debug("Trying to cleanup up target dir : " + backupInfo.getBackupId()); 450 String targetDir = backupInfo.getBackupRootDir(); 451 if (targetDir == null) { 452 LOG.warn("No target directory specified for " + backupInfo.getBackupId()); 453 return; 454 } 455 456 FileSystem outputFs = FileSystem.get(new Path(backupInfo.getBackupRootDir()).toUri(), conf); 457 458 for (TableName table : backupInfo.getTables()) { 459 Path targetDirPath = new Path( 460 getTableBackupDir(backupInfo.getBackupRootDir(), backupInfo.getBackupId(), table)); 461 if (outputFs.delete(targetDirPath, true)) { 462 LOG.info("Cleaning up backup data at " + targetDirPath.toString() + " done."); 463 } else { 464 LOG.info("No data has been found in " + targetDirPath.toString() + "."); 465 } 466 467 Path tableDir = targetDirPath.getParent(); 468 FileStatus[] backups = listStatus(outputFs, tableDir, null); 469 if (backups == null || backups.length == 0) { 470 outputFs.delete(tableDir, true); 471 LOG.debug(tableDir.toString() + " is empty, remove it."); 472 } 473 } 474 outputFs.delete(new Path(targetDir, backupInfo.getBackupId()), true); 475 } catch (IOException e1) { 476 LOG.error("Cleaning up backup data of " + backupInfo.getBackupId() + " at " 477 + backupInfo.getBackupRootDir() + " failed due to " + e1.getMessage() + "."); 478 } 479 } 480 481 /** 482 * Given the backup root dir, backup id and the table name, return the backup image location, 483 * which is also where the backup manifest file is. return value look like: 484 * "hdfs://backup.hbase.org:9000/user/biadmin/backup1/backup_1396650096738/default/t1_dn/" 485 * @param backupRootDir backup root directory 486 * @param backupId backup id 487 * @param tableName table name 488 * @return backupPath String for the particular table 489 */ 490 public static String getTableBackupDir(String backupRootDir, String backupId, 491 TableName tableName) { 492 return backupRootDir + Path.SEPARATOR + backupId + Path.SEPARATOR 493 + tableName.getNamespaceAsString() + Path.SEPARATOR + tableName.getQualifierAsString() 494 + Path.SEPARATOR; 495 } 496 497 /** 498 * Calls fs.listStatus() and treats FileNotFoundException as non-fatal This accommodates 499 * differences between hadoop versions, where hadoop 1 does not throw a FileNotFoundException, and 500 * return an empty FileStatus[] while Hadoop 2 will throw FileNotFoundException. 501 * @param fs file system 502 * @param dir directory 503 * @param filter path filter 504 * @return null if dir is empty or doesn't exist, otherwise FileStatus array 505 */ 506 public static FileStatus[] listStatus(final FileSystem fs, final Path dir, 507 final PathFilter filter) throws IOException { 508 FileStatus[] status = null; 509 try { 510 status = filter == null ? fs.listStatus(dir) : fs.listStatus(dir, filter); 511 } catch (FileNotFoundException fnfe) { 512 // if directory doesn't exist, return null 513 if (LOG.isTraceEnabled()) { 514 LOG.trace(dir + " doesn't exist"); 515 } 516 } 517 518 if (status == null || status.length < 1) { 519 return null; 520 } 521 522 return status; 523 } 524 525 /** 526 * Return the 'path' component of a Path. In Hadoop, Path is a URI. This method returns the 'path' 527 * component of a Path's URI: e.g. If a Path is 528 * <code>hdfs://example.org:9000/hbase_trunk/TestTable/compaction.dir</code>, this method returns 529 * <code>/hbase_trunk/TestTable/compaction.dir</code>. This method is useful if you want to print 530 * out a Path without qualifying Filesystem instance. 531 * @param p file system Path whose 'path' component we are to return. 532 * @return Path portion of the Filesystem 533 */ 534 public static String getPath(Path p) { 535 return p.toUri().getPath(); 536 } 537 538 /** 539 * Given the backup root dir and the backup id, return the log file location for an incremental 540 * backup. 541 * @param backupRootDir backup root directory 542 * @param backupId backup id 543 * @return logBackupDir: ".../user/biadmin/backup1/WALs/backup_1396650096738" 544 */ 545 public static String getLogBackupDir(String backupRootDir, String backupId) { 546 return backupRootDir + Path.SEPARATOR + backupId + Path.SEPARATOR 547 + HConstants.HREGION_LOGDIR_NAME; 548 } 549 550 private static List<BackupInfo> getHistory(Configuration conf, Path backupRootPath) 551 throws IOException { 552 // Get all (n) history from backup root destination 553 554 FileSystem fs = FileSystem.get(backupRootPath.toUri(), conf); 555 RemoteIterator<LocatedFileStatus> it = fs.listLocatedStatus(backupRootPath); 556 557 List<BackupInfo> infos = new ArrayList<>(); 558 while (it.hasNext()) { 559 LocatedFileStatus lfs = it.next(); 560 561 if (!lfs.isDirectory()) { 562 continue; 563 } 564 565 String backupId = lfs.getPath().getName(); 566 try { 567 BackupInfo info = loadBackupInfo(backupRootPath, backupId, fs); 568 infos.add(info); 569 } catch (IOException e) { 570 LOG.error("Can not load backup info from: " + lfs.getPath(), e); 571 } 572 } 573 // Sort 574 Collections.sort(infos, new Comparator<BackupInfo>() { 575 @Override 576 public int compare(BackupInfo o1, BackupInfo o2) { 577 long ts1 = getTimestamp(o1.getBackupId()); 578 long ts2 = getTimestamp(o2.getBackupId()); 579 580 if (ts1 == ts2) { 581 return 0; 582 } 583 584 return ts1 < ts2 ? 1 : -1; 585 } 586 587 private long getTimestamp(String backupId) { 588 return Long.parseLong(Iterators.get(Splitter.on('_').split(backupId).iterator(), 1)); 589 } 590 }); 591 return infos; 592 } 593 594 public static List<BackupInfo> getHistory(Configuration conf, int n, Path backupRootPath, 595 BackupInfo.Filter... filters) throws IOException { 596 List<BackupInfo> infos = getHistory(conf, backupRootPath); 597 598 Predicate<BackupInfo> combinedPredicate = Stream.of(filters) 599 .map(filter -> (Predicate<BackupInfo>) filter).reduce(Predicate::and).orElse(x -> true); 600 601 return infos.stream().filter(combinedPredicate).limit(n).toList(); 602 } 603 604 public static BackupInfo loadBackupInfo(Path backupRootPath, String backupId, FileSystem fs) 605 throws IOException { 606 Path backupPath = new Path(backupRootPath, backupId); 607 608 RemoteIterator<LocatedFileStatus> it = fs.listFiles(backupPath, true); 609 while (it.hasNext()) { 610 LocatedFileStatus lfs = it.next(); 611 if (lfs.getPath().getName().equals(BackupManifest.MANIFEST_FILE_NAME)) { 612 // Load BackupManifest 613 BackupManifest manifest = new BackupManifest(fs, lfs.getPath().getParent()); 614 BackupInfo info = manifest.toBackupInfo(); 615 return info; 616 } 617 } 618 return null; 619 } 620 621 /** 622 * Create restore request. 623 * @param backupRootDir backup root dir 624 * @param backupId backup id 625 * @param check check only 626 * @param fromTables table list from 627 * @param toTables table list to 628 * @param isOverwrite overwrite data 629 * @return request obkect 630 */ 631 public static RestoreRequest createRestoreRequest(String backupRootDir, String backupId, 632 boolean check, TableName[] fromTables, TableName[] toTables, boolean isOverwrite) { 633 return createRestoreRequest(backupRootDir, backupId, check, fromTables, toTables, isOverwrite, 634 false); 635 } 636 637 public static RestoreRequest createRestoreRequest(String backupRootDir, String backupId, 638 boolean check, TableName[] fromTables, TableName[] toTables, boolean isOverwrite, 639 boolean isKeepOriginalSplits) { 640 RestoreRequest.Builder builder = new RestoreRequest.Builder(); 641 RestoreRequest request = builder.withBackupRootDir(backupRootDir).withBackupId(backupId) 642 .withCheck(check).withFromTables(fromTables).withToTables(toTables).withOverwrite(isOverwrite) 643 .withKeepOriginalSplits(isKeepOriginalSplits).build(); 644 return request; 645 } 646 647 public static boolean validate(List<TableName> tables, BackupManifest backupManifest, 648 Configuration conf) throws IOException { 649 boolean isValid = true; 650 651 for (TableName table : tables) { 652 TreeSet<BackupImage> imageSet = new TreeSet<>(); 653 654 ArrayList<BackupImage> depList = backupManifest.getDependentListByTable(table); 655 if (depList != null && !depList.isEmpty()) { 656 imageSet.addAll(depList); 657 } 658 659 LOG.info("Dependent image(s) from old to new:"); 660 for (BackupImage image : imageSet) { 661 String imageDir = 662 HBackupFileSystem.getTableBackupDir(image.getRootDir(), image.getBackupId(), table); 663 if (!BackupUtils.checkPathExist(imageDir, conf)) { 664 LOG.error("ERROR: backup image does not exist: " + imageDir); 665 isValid = false; 666 break; 667 } 668 LOG.info("Backup image: " + image.getBackupId() + " for '" + table + "' is available"); 669 } 670 } 671 return isValid; 672 } 673 674 public static Path getBulkOutputDir(Path restoreRootDir, String tableName, Configuration conf, 675 boolean deleteOnExit) throws IOException { 676 FileSystem fs = restoreRootDir.getFileSystem(conf); 677 Path path = new Path(restoreRootDir, 678 "bulk_output-" + tableName + "-" + EnvironmentEdgeManager.currentTime()); 679 if (deleteOnExit) { 680 fs.deleteOnExit(path); 681 } 682 return path; 683 } 684 685 public static Path getBulkOutputDir(Path restoreRootDir, String tableName, Configuration conf) 686 throws IOException { 687 return getBulkOutputDir(restoreRootDir, tableName, conf, true); 688 } 689 690 public static Path getBulkOutputDir(String tableName, Configuration conf, boolean deleteOnExit) 691 throws IOException { 692 FileSystem fs = FileSystem.get(conf); 693 return getBulkOutputDir(getTmpRestoreOutputDir(fs, conf), tableName, conf, deleteOnExit); 694 } 695 696 /** 697 * Build temporary output path 698 * @param fs filesystem for default output dir 699 * @param conf configuration 700 * @return output path 701 */ 702 public static Path getTmpRestoreOutputDir(FileSystem fs, Configuration conf) { 703 String tmp = 704 conf.get(HConstants.TEMPORARY_FS_DIRECTORY_KEY, fs.getHomeDirectory() + "/hbase-staging"); 705 return new Path(tmp); 706 } 707 708 public static String getFileNameCompatibleString(TableName table) { 709 return table.getNamespaceAsString() + "-" + table.getQualifierAsString(); 710 } 711 712 public static boolean failed(int result) { 713 return result != 0; 714 } 715 716 public static boolean succeeded(int result) { 717 return result == 0; 718 } 719 720 public static BulkLoadHFiles createLoader(Configuration config) { 721 // set configuration for restore: 722 // LoadIncrementalHFile needs more time 723 // <name>hbase.rpc.timeout</name> <value>600000</value> 724 // calculates 725 Configuration conf = new Configuration(config); 726 conf.setInt(HConstants.HBASE_RPC_TIMEOUT_KEY, MILLISEC_IN_HOUR); 727 728 // By default, it is 32 and loader will fail if # of files in any region exceed this 729 // limit. Bad for snapshot restore. 730 conf.setInt(BulkLoadHFiles.MAX_FILES_PER_REGION_PER_FAMILY, Integer.MAX_VALUE); 731 conf.set(BulkLoadHFiles.IGNORE_UNMATCHED_CF_CONF_KEY, "yes"); 732 return BulkLoadHFiles.create(conf); 733 } 734 735 public static String findMostRecentBackupId(String[] backupIds) { 736 long recentTimestamp = Long.MIN_VALUE; 737 for (String backupId : backupIds) { 738 long ts = Long.parseLong(Iterators.get(Splitter.on('_').split(backupId).iterator(), 1)); 739 if (ts > recentTimestamp) { 740 recentTimestamp = ts; 741 } 742 } 743 return BackupRestoreConstants.BACKUPID_PREFIX + recentTimestamp; 744 } 745 746 /** 747 * roll WAL writer for all region servers and record the newest log roll result 748 */ 749 public static void logRoll(Connection conn, String backupRootDir, Configuration conf) 750 throws IOException { 751 boolean legacy = conf.getBoolean("hbase.backup.logroll.legacy.used", false); 752 if (legacy) { 753 logRollV1(conn, backupRootDir); 754 } else { 755 logRollV2(conn, backupRootDir); 756 } 757 } 758 759 private static void logRollV1(Connection conn, String backupRootDir) throws IOException { 760 try (Admin admin = conn.getAdmin()) { 761 admin.execProcedure(LogRollMasterProcedureManager.ROLLLOG_PROCEDURE_SIGNATURE, 762 LogRollMasterProcedureManager.ROLLLOG_PROCEDURE_NAME, 763 ImmutableMap.of("backupRoot", backupRootDir)); 764 } 765 } 766 767 private static void logRollV2(Connection conn, String backupRootDir) throws IOException { 768 BackupSystemTable backupSystemTable = new BackupSystemTable(conn); 769 HashMap<String, Long> lastLogRollResult = 770 backupSystemTable.readRegionServerLastLogRollResult(backupRootDir); 771 try (Admin admin = conn.getAdmin()) { 772 Map<ServerName, Long> newLogRollResult = admin.rollAllWALWriters(); 773 774 for (Map.Entry<ServerName, Long> entry : newLogRollResult.entrySet()) { 775 ServerName serverName = entry.getKey(); 776 long newHighestWALFilenum = entry.getValue(); 777 778 String address = serverName.getAddress().toString(); 779 Long lastHighestWALFilenum = lastLogRollResult.get(address); 780 if (lastHighestWALFilenum != null && lastHighestWALFilenum > newHighestWALFilenum) { 781 LOG.warn("Won't update last roll log result for server {}: current = {}, new = {}", 782 serverName, lastHighestWALFilenum, newHighestWALFilenum); 783 } else { 784 backupSystemTable.writeRegionServerLastLogRollResult(address, newHighestWALFilenum, 785 backupRootDir); 786 if (LOG.isDebugEnabled()) { 787 LOG.debug("updated last roll log result for {} from {} to {}", serverName, 788 lastHighestWALFilenum, newHighestWALFilenum); 789 } 790 } 791 } 792 } 793 } 794}