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.BackupInfo.withRoot; 021import static org.apache.hadoop.hbase.backup.BackupInfo.withState; 022import static org.apache.hadoop.hbase.backup.BackupInfo.withType; 023 024import edu.umd.cs.findbugs.annotations.Nullable; 025import java.io.Closeable; 026import java.io.IOException; 027import java.io.InterruptedIOException; 028import java.nio.charset.StandardCharsets; 029import java.util.ArrayList; 030import java.util.Arrays; 031import java.util.Collection; 032import java.util.Collections; 033import java.util.HashMap; 034import java.util.HashSet; 035import java.util.Iterator; 036import java.util.List; 037import java.util.Map; 038import java.util.Map.Entry; 039import java.util.Objects; 040import java.util.Set; 041import java.util.TreeMap; 042import java.util.TreeSet; 043import java.util.function.Predicate; 044import java.util.stream.Collectors; 045import java.util.stream.Stream; 046import org.apache.commons.lang3.ArrayUtils; 047import org.apache.commons.lang3.StringUtils; 048import org.apache.hadoop.conf.Configuration; 049import org.apache.hadoop.fs.Path; 050import org.apache.hadoop.hbase.Cell; 051import org.apache.hadoop.hbase.CellUtil; 052import org.apache.hadoop.hbase.HBaseConfiguration; 053import org.apache.hadoop.hbase.NamespaceDescriptor; 054import org.apache.hadoop.hbase.NamespaceExistException; 055import org.apache.hadoop.hbase.ServerName; 056import org.apache.hadoop.hbase.TableExistsException; 057import org.apache.hadoop.hbase.TableName; 058import org.apache.hadoop.hbase.TableNotDisabledException; 059import org.apache.hadoop.hbase.backup.BackupInfo; 060import org.apache.hadoop.hbase.backup.BackupInfo.BackupState; 061import org.apache.hadoop.hbase.backup.BackupRestoreConstants; 062import org.apache.hadoop.hbase.backup.BackupType; 063import org.apache.hadoop.hbase.client.Admin; 064import org.apache.hadoop.hbase.client.BufferedMutator; 065import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 066import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 067import org.apache.hadoop.hbase.client.Connection; 068import org.apache.hadoop.hbase.client.Delete; 069import org.apache.hadoop.hbase.client.Get; 070import org.apache.hadoop.hbase.client.Put; 071import org.apache.hadoop.hbase.client.Result; 072import org.apache.hadoop.hbase.client.ResultScanner; 073import org.apache.hadoop.hbase.client.Scan; 074import org.apache.hadoop.hbase.client.SnapshotDescription; 075import org.apache.hadoop.hbase.client.Table; 076import org.apache.hadoop.hbase.client.TableDescriptor; 077import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 078import org.apache.hadoop.hbase.util.Bytes; 079import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 080import org.apache.yetus.audience.InterfaceAudience; 081import org.slf4j.Logger; 082import org.slf4j.LoggerFactory; 083 084import org.apache.hbase.thirdparty.com.google.common.base.Preconditions; 085import org.apache.hbase.thirdparty.com.google.common.base.Splitter; 086import org.apache.hbase.thirdparty.com.google.common.collect.Iterators; 087 088import org.apache.hadoop.hbase.shaded.protobuf.generated.BackupProtos; 089import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; 090 091/** 092 * This class provides API to access backup system table<br> 093 * Backup system table schema:<br> 094 * <p> 095 * <ul> 096 * <li>1. Backup sessions rowkey= "session:"+backupId; value =serialized BackupInfo</li> 097 * <li>2. Backup start code rowkey = "startcode:"+backupRoot; value = startcode</li> 098 * <li>3. Incremental backup set rowkey="incrbackupset:"+backupRoot; table="meta:"+tablename of 099 * include table; value=empty</li> 100 * <li>4. Table-RS-timestamp map rowkey="trslm:"+backupRoot+table_name; value = map[RS-> last WAL 101 * timestamp]</li> 102 * <li>5. RS - WAL ts map rowkey="rslogts:"+backupRoot +server; value = last WAL timestamp</li> 103 * <li>6. WALs recorded rowkey="wals:"+WAL unique file name; value = backupId and full WAL file 104 * name</li> 105 * </ul> 106 * </p> 107 */ 108@InterfaceAudience.Private 109public final class BackupSystemTable implements Closeable { 110 111 private static final Logger LOG = LoggerFactory.getLogger(BackupSystemTable.class); 112 113 static class WALItem { 114 String backupId; 115 String walFile; 116 String backupRoot; 117 118 WALItem(String backupId, String walFile, String backupRoot) { 119 this.backupId = backupId; 120 this.walFile = walFile; 121 this.backupRoot = backupRoot; 122 } 123 124 public String getBackupId() { 125 return backupId; 126 } 127 128 public String getWalFile() { 129 return walFile; 130 } 131 132 public String getBackupRoot() { 133 return backupRoot; 134 } 135 136 @Override 137 public String toString() { 138 return Path.SEPARATOR + backupRoot + Path.SEPARATOR + backupId + Path.SEPARATOR + walFile; 139 } 140 } 141 142 /** 143 * Backup system table (main) name 144 */ 145 private TableName tableName; 146 147 /** 148 * Backup System table name for bulk loaded files. We keep all bulk loaded file references in a 149 * separate table because we have to isolate general backup operations: create, merge etc from 150 * activity of RegionObserver, which controls process of a bulk loading 151 * {@link org.apache.hadoop.hbase.backup.BackupObserver} 152 */ 153 private TableName bulkLoadTableName; 154 155 /** 156 * Stores backup sessions (contexts) 157 */ 158 final static byte[] SESSIONS_FAMILY = Bytes.toBytes("session"); 159 /** 160 * Stores other meta 161 */ 162 final static byte[] META_FAMILY = Bytes.toBytes("meta"); 163 final static byte[] BULK_LOAD_FAMILY = Bytes.toBytes("bulk"); 164 /** 165 * Connection to HBase cluster, shared among all instances 166 */ 167 private final Connection connection; 168 169 private final static String BACKUP_INFO_PREFIX = "session:"; 170 private final static String START_CODE_ROW = "startcode:"; 171 private final static byte[] ACTIVE_SESSION_ROW = Bytes.toBytes("activesession:"); 172 private final static byte[] ACTIVE_SESSION_COL = Bytes.toBytes("c"); 173 174 private final static byte[] ACTIVE_SESSION_YES = Bytes.toBytes("yes"); 175 private final static byte[] ACTIVE_SESSION_NO = Bytes.toBytes("no"); 176 177 private final static String INCR_BACKUP_SET = "incrbackupset:"; 178 private final static String CONTINUOUS_BACKUP_SET = "continuousbackupset"; 179 /** 180 * Row key identifier for storing the last replicated WAL timestamp in the backup system table for 181 * continuous backup. 182 */ 183 private static final String CONTINUOUS_BACKUP_REPLICATION_TIMESTAMP_ROW = 184 "continuous_backup_last_replicated"; 185 private final static String TABLE_RS_LOG_MAP_PREFIX = "trslm:"; 186 private final static String RS_LOG_TS_PREFIX = "rslogts:"; 187 188 private final static String BULK_LOAD_PREFIX = "bulk:"; 189 private final static byte[] BULK_LOAD_PREFIX_BYTES = Bytes.toBytes(BULK_LOAD_PREFIX); 190 private final static byte[] DELETE_OP_ROW = Bytes.toBytes("delete_op_row"); 191 private final static byte[] MERGE_OP_ROW = Bytes.toBytes("merge_op_row"); 192 193 final static byte[] TBL_COL = Bytes.toBytes("tbl"); 194 final static byte[] FAM_COL = Bytes.toBytes("fam"); 195 final static byte[] PATH_COL = Bytes.toBytes("path"); 196 197 private final static String SET_KEY_PREFIX = "backupset:"; 198 199 // separator between BULK_LOAD_PREFIX and ordinals 200 private final static String BLK_LD_DELIM = ":"; 201 private final static byte[] EMPTY_VALUE = new byte[] {}; 202 203 // Safe delimiter in a string 204 private final static String NULL = "\u0000"; 205 206 public BackupSystemTable(Connection conn) throws IOException { 207 this.connection = conn; 208 Configuration conf = this.connection.getConfiguration(); 209 tableName = BackupSystemTable.getTableName(conf); 210 bulkLoadTableName = BackupSystemTable.getTableNameForBulkLoadedData(conf); 211 checkSystemTable(); 212 } 213 214 private void checkSystemTable() throws IOException { 215 try (Admin admin = connection.getAdmin()) { 216 verifyNamespaceExists(admin); 217 Configuration conf = connection.getConfiguration(); 218 if (!admin.tableExists(tableName)) { 219 TableDescriptor backupHTD = BackupSystemTable.getSystemTableDescriptor(conf); 220 createSystemTable(admin, backupHTD); 221 } 222 ensureTableEnabled(admin, tableName); 223 if (!admin.tableExists(bulkLoadTableName)) { 224 TableDescriptor blHTD = BackupSystemTable.getSystemTableForBulkLoadedDataDescriptor(conf); 225 createSystemTable(admin, blHTD); 226 } 227 ensureTableEnabled(admin, bulkLoadTableName); 228 waitForSystemTable(admin, tableName); 229 waitForSystemTable(admin, bulkLoadTableName); 230 } 231 } 232 233 private void createSystemTable(Admin admin, TableDescriptor descriptor) throws IOException { 234 try { 235 admin.createTable(descriptor); 236 } catch (TableExistsException e) { 237 // swallow because this class is initialized in concurrent environments (i.e. bulkloads), 238 // so may be subject to race conditions where one caller succeeds in creating the 239 // table and others fail because it now exists 240 LOG.debug("Table {} already exists, ignoring", descriptor.getTableName(), e); 241 } 242 } 243 244 private void verifyNamespaceExists(Admin admin) throws IOException { 245 String namespaceName = tableName.getNamespaceAsString(); 246 NamespaceDescriptor ns = NamespaceDescriptor.create(namespaceName).build(); 247 NamespaceDescriptor[] list = admin.listNamespaceDescriptors(); 248 boolean exists = false; 249 for (NamespaceDescriptor nsd : list) { 250 if (nsd.getName().equals(ns.getName())) { 251 exists = true; 252 break; 253 } 254 } 255 if (!exists) { 256 try { 257 admin.createNamespace(ns); 258 } catch (NamespaceExistException e) { 259 // swallow because this class is initialized in concurrent environments (i.e. bulkloads), 260 // so may be subject to race conditions where one caller succeeds in creating the 261 // namespace and others fail because it now exists 262 LOG.debug("Namespace {} already exists, ignoring", ns.getName(), e); 263 } 264 } 265 } 266 267 private void waitForSystemTable(Admin admin, TableName tableName) throws IOException { 268 // Return fast if the table is available and avoid a log message 269 if (admin.tableExists(tableName) && admin.isTableAvailable(tableName)) { 270 return; 271 } 272 long TIMEOUT = 60000; 273 long startTime = EnvironmentEdgeManager.currentTime(); 274 LOG.debug("Backup table {} is not present and available, waiting for it to become so", 275 tableName); 276 while (!admin.tableExists(tableName) || !admin.isTableAvailable(tableName)) { 277 try { 278 Thread.sleep(100); 279 } catch (InterruptedException e) { 280 throw (IOException) new InterruptedIOException().initCause(e); 281 } 282 if (EnvironmentEdgeManager.currentTime() - startTime > TIMEOUT) { 283 throw new IOException( 284 "Failed to create backup system table " + tableName + " after " + TIMEOUT + "ms"); 285 } 286 } 287 LOG.debug("Backup table {} exists and available", tableName); 288 } 289 290 @Override 291 public void close() { 292 // do nothing 293 } 294 295 /** 296 * Updates status (state) of a backup session in backup system table table 297 * @param info backup info 298 * @throws IOException exception 299 */ 300 public void updateBackupInfo(BackupInfo info) throws IOException { 301 if (LOG.isTraceEnabled()) { 302 LOG.trace("update backup status in backup system table for: " + info.getBackupId() 303 + " set status=" + info.getState()); 304 } 305 try (Table table = connection.getTable(tableName)) { 306 Put put = createPutForBackupInfo(info); 307 table.put(put); 308 } 309 } 310 311 /* 312 * @param backupId the backup Id 313 * @return Map of rows to path of bulk loaded hfile 314 */ 315 Map<byte[], String> readBulkLoadedFiles(String backupId) throws IOException { 316 Scan scan = BackupSystemTable.createScanForBulkLoadedFiles(backupId); 317 try (Table table = connection.getTable(bulkLoadTableName); 318 ResultScanner scanner = table.getScanner(scan)) { 319 Result res = null; 320 Map<byte[], String> map = new TreeMap<>(Bytes.BYTES_COMPARATOR); 321 while ((res = scanner.next()) != null) { 322 res.advance(); 323 byte[] row = CellUtil.cloneRow(res.listCells().get(0)); 324 for (Cell cell : res.listCells()) { 325 if ( 326 CellUtil.compareQualifiers(cell, BackupSystemTable.PATH_COL, 0, 327 BackupSystemTable.PATH_COL.length) == 0 328 ) { 329 map.put(row, Bytes.toString(CellUtil.cloneValue(cell))); 330 } 331 } 332 } 333 return map; 334 } 335 } 336 337 /** 338 * Deletes backup status from backup system table table 339 * @param backupId backup id 340 * @throws IOException exception 341 */ 342 public void deleteBackupInfo(String backupId) throws IOException { 343 if (LOG.isTraceEnabled()) { 344 LOG.trace("delete backup status in backup system table for " + backupId); 345 } 346 try (Table table = connection.getTable(tableName)) { 347 Delete del = createDeleteForBackupInfo(backupId); 348 table.delete(del); 349 } 350 } 351 352 /** 353 * Registers a bulk load. 354 * @param tableName table name 355 * @param region the region receiving hfile 356 * @param cfToHfilePath column family and associated hfiles 357 */ 358 public void registerBulkLoad(TableName tableName, byte[] region, 359 Map<byte[], List<Path>> cfToHfilePath) throws IOException { 360 if (LOG.isDebugEnabled()) { 361 LOG.debug("Writing bulk load descriptor to backup {} with {} entries", tableName, 362 cfToHfilePath.size()); 363 } 364 try (BufferedMutator bufferedMutator = connection.getBufferedMutator(bulkLoadTableName)) { 365 List<Put> puts = BackupSystemTable.createPutForBulkLoad(tableName, region, cfToHfilePath); 366 bufferedMutator.mutate(puts); 367 LOG.debug("Written {} rows for bulk load of table {}", puts.size(), tableName); 368 } 369 } 370 371 /** 372 * Removes entries from the table that tracks all bulk loaded hfiles. 373 * @param rows the row keys of the entries to be deleted 374 */ 375 public void deleteBulkLoadedRows(List<byte[]> rows) throws IOException { 376 try (BufferedMutator bufferedMutator = connection.getBufferedMutator(bulkLoadTableName)) { 377 List<Delete> deletes = new ArrayList<>(); 378 for (byte[] row : rows) { 379 Delete del = new Delete(row); 380 deletes.add(del); 381 LOG.debug("Deleting bulk load entry with key: {}", Bytes.toString(row)); 382 } 383 bufferedMutator.mutate(deletes); 384 LOG.debug("Deleted {} bulk load entries.", rows.size()); 385 } 386 } 387 388 /** 389 * Reads the rows from backup table recording bulk loaded hfiles 390 */ 391 public List<BulkLoad> readBulkloadRows() throws IOException { 392 Scan scan = BackupSystemTable.createScanForOrigBulkLoadedFiles(null); 393 return processBulkLoadRowScan(scan, Long.MAX_VALUE); 394 } 395 396 /** 397 * Reads the rows from backup table recording bulk loaded hfiles 398 * @param tableList list of table names 399 */ 400 public List<BulkLoad> readBulkloadRows(Collection<TableName> tableList) throws IOException { 401 return readBulkloadRows(tableList, Long.MAX_VALUE); 402 } 403 404 /** 405 * Reads the rows from backup table recording bulk loaded hfiles 406 * @param tableList list of table names 407 * @param endTimestamp upper bound timestamp for bulkload entries retrieval 408 */ 409 public List<BulkLoad> readBulkloadRows(Collection<TableName> tableList, long endTimestamp) 410 throws IOException { 411 List<BulkLoad> result = new ArrayList<>(); 412 for (TableName table : tableList) { 413 Scan scan = BackupSystemTable.createScanForOrigBulkLoadedFiles(table); 414 result.addAll(processBulkLoadRowScan(scan, endTimestamp)); 415 } 416 return result; 417 } 418 419 private List<BulkLoad> processBulkLoadRowScan(Scan scan, long endTimestamp) throws IOException { 420 List<BulkLoad> result = new ArrayList<>(); 421 try (Table bulkLoadTable = connection.getTable(bulkLoadTableName); 422 ResultScanner scanner = bulkLoadTable.getScanner(scan)) { 423 Result res; 424 while ((res = scanner.next()) != null) { 425 res.advance(); 426 TableName table = null; 427 String fam = null; 428 String path = null; 429 String region = null; 430 byte[] row = null; 431 long timestamp = 0L; 432 for (Cell cell : res.listCells()) { 433 row = CellUtil.cloneRow(cell); 434 timestamp = cell.getTimestamp(); 435 String rowStr = Bytes.toString(row); 436 region = BackupSystemTable.getRegionNameFromOrigBulkLoadRow(rowStr); 437 if ( 438 CellUtil.compareQualifiers(cell, BackupSystemTable.TBL_COL, 0, 439 BackupSystemTable.TBL_COL.length) == 0 440 ) { 441 table = TableName.valueOf(CellUtil.cloneValue(cell)); 442 } else if ( 443 CellUtil.compareQualifiers(cell, BackupSystemTable.FAM_COL, 0, 444 BackupSystemTable.FAM_COL.length) == 0 445 ) { 446 fam = Bytes.toString(CellUtil.cloneValue(cell)); 447 } else if ( 448 CellUtil.compareQualifiers(cell, BackupSystemTable.PATH_COL, 0, 449 BackupSystemTable.PATH_COL.length) == 0 450 ) { 451 path = Bytes.toString(CellUtil.cloneValue(cell)); 452 } 453 } 454 LOG.debug("Found orig path {} for family {} of table {} and region {} with timestamp {}", 455 path, fam, table, region, timestamp); 456 if (timestamp <= endTimestamp) { 457 result.add(new BulkLoad(table, region, fam, path, row, timestamp)); 458 } 459 } 460 } 461 return result; 462 } 463 464 /** 465 * Reads backup status object (instance of backup info) from backup system table table 466 * @param backupId backup id 467 * @return Current status of backup session or null 468 */ 469 public BackupInfo readBackupInfo(String backupId) throws IOException { 470 if (LOG.isTraceEnabled()) { 471 LOG.trace("read backup status from backup system table for: " + backupId); 472 } 473 474 try (Table table = connection.getTable(tableName)) { 475 Get get = createGetForBackupInfo(backupId); 476 Result res = table.get(get); 477 if (res.isEmpty()) { 478 return null; 479 } 480 return resultToBackupInfo(res); 481 } 482 } 483 484 /** 485 * Exclusive operations are: create, delete, merge 486 * @throws IOException if a table operation fails or an active backup exclusive operation is 487 * already underway 488 */ 489 public void startBackupExclusiveOperation() throws IOException { 490 LOG.debug("Start new backup exclusive operation"); 491 492 try (Table table = connection.getTable(tableName)) { 493 Put put = createPutForStartBackupSession(); 494 // First try to put if row does not exist 495 if ( 496 !table.checkAndMutate(ACTIVE_SESSION_ROW, SESSIONS_FAMILY).qualifier(ACTIVE_SESSION_COL) 497 .ifNotExists().thenPut(put) 498 ) { 499 // Row exists, try to put if value == ACTIVE_SESSION_NO 500 if ( 501 !table.checkAndMutate(ACTIVE_SESSION_ROW, SESSIONS_FAMILY).qualifier(ACTIVE_SESSION_COL) 502 .ifEquals(ACTIVE_SESSION_NO).thenPut(put) 503 ) { 504 throw new ExclusiveOperationException(); 505 } 506 } 507 } 508 } 509 510 private Put createPutForStartBackupSession() { 511 Put put = new Put(ACTIVE_SESSION_ROW); 512 put.addColumn(SESSIONS_FAMILY, ACTIVE_SESSION_COL, ACTIVE_SESSION_YES); 513 return put; 514 } 515 516 public void finishBackupExclusiveOperation() throws IOException { 517 LOG.debug("Finish backup exclusive operation"); 518 519 try (Table table = connection.getTable(tableName)) { 520 Put put = createPutForStopBackupSession(); 521 if ( 522 !table.checkAndMutate(ACTIVE_SESSION_ROW, SESSIONS_FAMILY).qualifier(ACTIVE_SESSION_COL) 523 .ifEquals(ACTIVE_SESSION_YES).thenPut(put) 524 ) { 525 throw new IOException("There is no active backup exclusive operation"); 526 } 527 } 528 } 529 530 private Put createPutForStopBackupSession() { 531 Put put = new Put(ACTIVE_SESSION_ROW); 532 put.addColumn(SESSIONS_FAMILY, ACTIVE_SESSION_COL, ACTIVE_SESSION_NO); 533 return put; 534 } 535 536 /** 537 * Get the Region Servers log information after the last log roll from backup system table. 538 * @param backupRoot root directory path to backup 539 * @return RS log info 540 * @throws IOException exception 541 */ 542 public HashMap<String, Long> readRegionServerLastLogRollResult(String backupRoot) 543 throws IOException { 544 LOG.trace("read region server last roll log result to backup system table"); 545 546 Scan scan = createScanForReadRegionServerLastLogRollResult(backupRoot); 547 548 try (Table table = connection.getTable(tableName); 549 ResultScanner scanner = table.getScanner(scan)) { 550 Result res; 551 HashMap<String, Long> rsTimestampMap = new HashMap<>(); 552 while ((res = scanner.next()) != null) { 553 res.advance(); 554 Cell cell = res.current(); 555 byte[] row = CellUtil.cloneRow(cell); 556 String server = getServerNameForReadRegionServerLastLogRollResult(row); 557 byte[] data = CellUtil.cloneValue(cell); 558 rsTimestampMap.put(server, Bytes.toLong(data)); 559 } 560 return rsTimestampMap; 561 } 562 } 563 564 /** 565 * Writes Region Server last roll log result (timestamp) to backup system table table 566 * @param server Region Server name 567 * @param ts last log timestamp 568 * @param backupRoot root directory path to backup 569 * @throws IOException exception 570 */ 571 public void writeRegionServerLastLogRollResult(String server, Long ts, String backupRoot) 572 throws IOException { 573 LOG.trace("write region server last roll log result to backup system table"); 574 575 try (Table table = connection.getTable(tableName)) { 576 Put put = createPutForRegionServerLastLogRollResult(server, ts, backupRoot); 577 table.put(put); 578 } 579 } 580 581 /** 582 * Retrieve all table names that are part of any known completed backup 583 */ 584 public Set<TableName> getTablesIncludedInBackups() throws IOException { 585 // Incremental backups have the same tables as the preceding full backups 586 List<BackupInfo> infos = 587 getBackupHistory(withState(BackupState.COMPLETE), withType(BackupType.FULL)); 588 return infos.stream().flatMap(info -> info.getTableNames().stream()) 589 .collect(Collectors.toSet()); 590 } 591 592 /** 593 * Goes through all backup history corresponding to the provided root folder, and collects all 594 * backup info mentioning each of the provided tables. 595 * @param set the tables for which to collect the {@code BackupInfo} 596 * @param backupRoot backup destination path to retrieve backup history for 597 * @return a map containing (a subset of) the provided {@code TableName}s, mapped to a list of at 598 * least one {@code BackupInfo} 599 * @throws IOException if getting the backup history fails 600 */ 601 public Map<TableName, List<BackupInfo>> getBackupHistoryForTableSet(Set<TableName> set, 602 String backupRoot) throws IOException { 603 List<BackupInfo> history = getBackupHistory(withRoot(backupRoot)); 604 Map<TableName, List<BackupInfo>> tableHistoryMap = new HashMap<>(); 605 for (BackupInfo info : history) { 606 List<TableName> tables = info.getTableNames(); 607 for (TableName tableName : tables) { 608 if (set.contains(tableName)) { 609 List<BackupInfo> list = 610 tableHistoryMap.computeIfAbsent(tableName, k -> new ArrayList<>()); 611 list.add(info); 612 } 613 } 614 } 615 return tableHistoryMap; 616 } 617 618 /** 619 * Get all backup information passing the given filters, ordered by descending backupId. I.e. from 620 * newest to oldest. 621 */ 622 public List<BackupInfo> getBackupHistory(BackupInfo.Filter... toInclude) throws IOException { 623 return getBackupHistory(Order.NEW_TO_OLD, Integer.MAX_VALUE, toInclude); 624 } 625 626 /** 627 * Retrieves the first n entries of the sorted, filtered list of backup infos. 628 * @param order desired ordering of the results. 629 * @param n number of entries to return 630 */ 631 public List<BackupInfo> getBackupHistory(Order order, int n, BackupInfo.Filter... toInclude) 632 throws IOException { 633 Preconditions.checkArgument(n >= 0, "n should be >= 0"); 634 LOG.trace("get backup infos from backup system table"); 635 636 if (n == 0) { 637 return Collections.emptyList(); 638 } 639 640 Predicate<BackupInfo> combinedPredicate = Stream.of(toInclude) 641 .map(filter -> (Predicate<BackupInfo>) filter).reduce(Predicate::and).orElse(x -> true); 642 643 Scan scan = createScanForBackupHistory(order); 644 List<BackupInfo> list = new ArrayList<>(); 645 646 try (Table table = connection.getTable(tableName); 647 ResultScanner scanner = table.getScanner(scan)) { 648 Result res; 649 while ((res = scanner.next()) != null) { 650 res.advance(); 651 BackupInfo context = cellToBackupInfo(res.current()); 652 if (combinedPredicate.test(context)) { 653 list.add(context); 654 if (list.size() == n) { 655 break; 656 } 657 } 658 } 659 return list; 660 } 661 } 662 663 /** 664 * Write the current timestamps for each regionserver to backup system table after a successful 665 * full or incremental backup. The saved timestamp is of the last log file that was backed up 666 * already. 667 * @param tables tables 668 * @param newTimestamps timestamps 669 * @param backupRoot root directory path to backup 670 * @throws IOException exception 671 */ 672 public void writeRegionServerLogTimestamp(Set<TableName> tables, Map<String, Long> newTimestamps, 673 String backupRoot) throws IOException { 674 if (LOG.isTraceEnabled()) { 675 LOG.trace("write RS log time stamps to backup system table for tables [" 676 + StringUtils.join(tables, ",") + "]"); 677 } 678 List<Put> puts = new ArrayList<>(); 679 for (TableName table : tables) { 680 byte[] smapData = toTableServerTimestampProto(table, newTimestamps).toByteArray(); 681 Put put = createPutForWriteRegionServerLogTimestamp(table, smapData, backupRoot); 682 puts.add(put); 683 } 684 try (BufferedMutator bufferedMutator = connection.getBufferedMutator(tableName)) { 685 bufferedMutator.mutate(puts); 686 } 687 } 688 689 /** 690 * Read the timestamp for each region server log after the last successful backup. Each table has 691 * its own set of the timestamps. The info is stored for each table as a concatenated string of 692 * rs->timestapmp 693 * @param backupRoot root directory path to backup 694 * @return the timestamp for each region server. key: tableName value: 695 * RegionServer,PreviousTimeStamp 696 * @throws IOException exception 697 */ 698 public Map<TableName, Map<String, Long>> readLogTimestampMap(String backupRoot) 699 throws IOException { 700 if (LOG.isTraceEnabled()) { 701 LOG.trace("read RS log ts from backup system table for root=" + backupRoot); 702 } 703 704 Map<TableName, Map<String, Long>> tableTimestampMap = new HashMap<>(); 705 706 Scan scan = createScanForReadLogTimestampMap(backupRoot); 707 try (Table table = connection.getTable(tableName); 708 ResultScanner scanner = table.getScanner(scan)) { 709 Result res; 710 while ((res = scanner.next()) != null) { 711 res.advance(); 712 Cell cell = res.current(); 713 byte[] row = CellUtil.cloneRow(cell); 714 String tabName = getTableNameForReadLogTimestampMap(row); 715 TableName tn = TableName.valueOf(tabName); 716 byte[] data = CellUtil.cloneValue(cell); 717 if (data == null) { 718 throw new IOException("Data of last backup data from backup system table " 719 + "is empty. Create a backup first."); 720 } 721 if (data != null && data.length > 0) { 722 HashMap<String, Long> lastBackup = 723 fromTableServerTimestampProto(BackupProtos.TableServerTimestamp.parseFrom(data)); 724 tableTimestampMap.put(tn, lastBackup); 725 } 726 } 727 return tableTimestampMap; 728 } 729 } 730 731 private BackupProtos.TableServerTimestamp toTableServerTimestampProto(TableName table, 732 Map<String, Long> map) { 733 BackupProtos.TableServerTimestamp.Builder tstBuilder = 734 BackupProtos.TableServerTimestamp.newBuilder(); 735 tstBuilder 736 .setTableName(org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil.toProtoTableName(table)); 737 738 for (Entry<String, Long> entry : map.entrySet()) { 739 BackupProtos.ServerTimestamp.Builder builder = BackupProtos.ServerTimestamp.newBuilder(); 740 HBaseProtos.ServerName.Builder snBuilder = HBaseProtos.ServerName.newBuilder(); 741 ServerName sn = ServerName.parseServerName(entry.getKey()); 742 snBuilder.setHostName(sn.getHostname()); 743 snBuilder.setPort(sn.getPort()); 744 builder.setServerName(snBuilder.build()); 745 builder.setTimestamp(entry.getValue()); 746 tstBuilder.addServerTimestamp(builder.build()); 747 } 748 749 return tstBuilder.build(); 750 } 751 752 private HashMap<String, Long> 753 fromTableServerTimestampProto(BackupProtos.TableServerTimestamp proto) { 754 755 HashMap<String, Long> map = new HashMap<>(); 756 List<BackupProtos.ServerTimestamp> list = proto.getServerTimestampList(); 757 for (BackupProtos.ServerTimestamp st : list) { 758 ServerName sn = 759 org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil.toServerName(st.getServerName()); 760 map.put(sn.getHostname() + ":" + sn.getPort(), st.getTimestamp()); 761 } 762 return map; 763 } 764 765 /** 766 * Return the current tables covered by incremental backup. 767 * @param backupRoot root directory path to backup 768 * @return set of tableNames 769 * @throws IOException exception 770 */ 771 public Set<TableName> getIncrementalBackupTableSet(String backupRoot) throws IOException { 772 LOG.trace("get incremental backup table set from backup system table"); 773 774 TreeSet<TableName> set = new TreeSet<>(); 775 776 try (Table table = connection.getTable(tableName)) { 777 Get get = createGetForIncrBackupTableSet(backupRoot); 778 Result res = table.get(get); 779 if (res.isEmpty()) { 780 return set; 781 } 782 List<Cell> cells = res.listCells(); 783 for (Cell cell : cells) { 784 // qualifier = table name - we use table names as qualifiers 785 set.add(TableName.valueOf(CellUtil.cloneQualifier(cell))); 786 } 787 return set; 788 } 789 } 790 791 /** 792 * Retrieves the current set of tables covered by continuous backup along with the timestamp 793 * indicating when continuous backup started for each table. 794 * @return a map where the key is the table name and the value is the timestamp representing the 795 * start time of continuous backup for that table. 796 * @throws IOException if an I/O error occurs while accessing the backup system table. 797 */ 798 public Map<TableName, Long> getContinuousBackupTableSet() throws IOException { 799 LOG.trace("Retrieving continuous backup table set from the backup system table."); 800 Map<TableName, Long> tableMap = new TreeMap<>(); 801 802 try (Table systemTable = connection.getTable(tableName)) { 803 Get getOperation = createGetForContinuousBackupTableSet(); 804 Result result = systemTable.get(getOperation); 805 806 if (result.isEmpty()) { 807 return tableMap; 808 } 809 810 // Extract table names and timestamps from the result cells 811 List<Cell> cells = result.listCells(); 812 for (Cell cell : cells) { 813 TableName tableName = TableName.valueOf(CellUtil.cloneQualifier(cell)); 814 long timestamp = Bytes.toLong(CellUtil.cloneValue(cell)); 815 tableMap.put(tableName, timestamp); 816 } 817 } 818 819 return tableMap; 820 } 821 822 /** 823 * Add tables to global incremental backup set 824 * @param tables set of tables 825 * @param backupRoot root directory path to backup 826 * @throws IOException exception 827 */ 828 public void addIncrementalBackupTableSet(Set<TableName> tables, String backupRoot) 829 throws IOException { 830 if (LOG.isTraceEnabled()) { 831 LOG.trace("Add incremental backup table set to backup system table. ROOT=" + backupRoot 832 + " tables [" + StringUtils.join(tables, " ") + "]"); 833 } 834 if (LOG.isDebugEnabled()) { 835 tables.forEach(table -> LOG.debug(Objects.toString(table))); 836 } 837 try (Table table = connection.getTable(tableName)) { 838 Put put = createPutForIncrBackupTableSet(tables, backupRoot); 839 table.put(put); 840 } 841 } 842 843 /** 844 * Add tables to the global continuous backup set. Only updates tables that are not already in the 845 * continuous backup set. 846 * @param tables set of tables to add 847 * @param startTimestamp timestamp indicating when continuous backup started 848 * @throws IOException if an error occurs while updating the backup system table 849 */ 850 public void addContinuousBackupTableSet(Set<TableName> tables, long startTimestamp) 851 throws IOException { 852 if (LOG.isTraceEnabled()) { 853 LOG.trace("Add continuous backup table set to backup system table. tables [" 854 + StringUtils.join(tables, " ") + "]"); 855 } 856 if (LOG.isDebugEnabled()) { 857 tables.forEach(table -> LOG.debug(Objects.toString(table))); 858 } 859 860 // Get existing continuous backup tables 861 Map<TableName, Long> existingTables = getContinuousBackupTableSet(); 862 863 try (Table table = connection.getTable(tableName)) { 864 Put put = createPutForContinuousBackupTableSet(tables, existingTables, startTimestamp); 865 if (!put.isEmpty()) { 866 table.put(put); 867 } 868 } 869 } 870 871 /** 872 * Updates the system table with the new start timestamps for continuous backup tables. 873 * @param tablesToUpdate The set of tables that need their start timestamps updated. 874 * @param newStartTimestamp The new start timestamp to be set. 875 */ 876 public void updateContinuousBackupTableSet(Set<TableName> tablesToUpdate, long newStartTimestamp) 877 throws IOException { 878 if (tablesToUpdate == null || tablesToUpdate.isEmpty()) { 879 LOG.warn("No tables provided for updating start timestamps."); 880 return; 881 } 882 883 try (Table table = connection.getTable(tableName)) { 884 Put put = new Put(rowkey(CONTINUOUS_BACKUP_SET)); 885 886 for (TableName tableName : tablesToUpdate) { 887 put.addColumn(BackupSystemTable.META_FAMILY, Bytes.toBytes(tableName.getNameAsString()), 888 Bytes.toBytes(newStartTimestamp)); 889 } 890 891 table.put(put); 892 LOG.info("Successfully updated start timestamps for {} tables in the backup system table.", 893 tablesToUpdate.size()); 894 } 895 } 896 897 /** 898 * Removes tables from the global continuous backup set. Only removes entries that currently exist 899 * in the backup system table. 900 * @param tables set of tables to remove 901 * @throws IOException if an error occurs while updating the backup system table 902 */ 903 public void removeContinuousBackupTableSet(Set<TableName> tables) throws IOException { 904 if (LOG.isTraceEnabled()) { 905 LOG.trace("Remove continuous backup table set from backup system table. tables [" 906 + StringUtils.join(tables, " ") + "]"); 907 } 908 if (LOG.isDebugEnabled()) { 909 tables.forEach(table -> LOG.debug("Removing: " + table)); 910 } 911 912 Map<TableName, Long> existingTables = getContinuousBackupTableSet(); 913 Set<TableName> toRemove = 914 tables.stream().filter(existingTables::containsKey).collect(Collectors.toSet()); 915 916 if (toRemove.isEmpty()) { 917 LOG.debug("No matching tables found to remove from continuous backup set."); 918 return; 919 } 920 921 try (Table table = connection.getTable(tableName)) { 922 Delete delete = createDeleteForContinuousBackupTableSet(toRemove); 923 table.delete(delete); 924 } 925 } 926 927 /** 928 * Updates the latest replicated WAL timestamp for a region server in the backup system table. 929 * This is used to track the replication checkpoint for continuous backup and PITR (Point-in-Time 930 * Restore). 931 * @param serverName the server for which the latest WAL timestamp is being recorded 932 * @param timestamp the timestamp (in milliseconds) of the last WAL entry replicated 933 * @throws IOException if an error occurs while writing to the backup system table 934 */ 935 public void updateBackupCheckpointTimestamp(ServerName serverName, long timestamp) 936 throws IOException { 937 938 HBaseProtos.ServerName.Builder serverProto = 939 HBaseProtos.ServerName.newBuilder().setHostName(serverName.getHostname()) 940 .setPort(serverName.getPort()).setStartCode(serverName.getStartCode()); 941 942 try (Table table = connection.getTable(tableName)) { 943 Put put = createPutForBackupCheckpoint(serverProto.build().toByteArray(), timestamp); 944 if (!put.isEmpty()) { 945 table.put(put); 946 } 947 } 948 } 949 950 /** 951 * Retrieves the latest replicated WAL timestamps for all region servers from the backup system 952 * table. This is used to track the replication checkpoint state for continuous backup and PITR 953 * (Point-in-Time Restore). 954 * @return a map where the key is {@link ServerName} and the value is the latest replicated WAL 955 * timestamp in milliseconds 956 * @throws IOException if an error occurs while reading from the backup system table 957 */ 958 public Map<ServerName, Long> getBackupCheckpointTimestamps() throws IOException { 959 LOG.trace("Fetching latest backup checkpoint timestamps for all region servers."); 960 961 Map<ServerName, Long> checkpointMap = new HashMap<>(); 962 963 byte[] rowKey = rowkey(CONTINUOUS_BACKUP_REPLICATION_TIMESTAMP_ROW); 964 Get get = new Get(rowKey); 965 get.addFamily(BackupSystemTable.META_FAMILY); 966 967 try (Table table = connection.getTable(tableName)) { 968 Result result = table.get(get); 969 970 if (result.isEmpty()) { 971 LOG.debug("No checkpoint timestamps found in backup system table."); 972 return checkpointMap; 973 } 974 975 List<Cell> cells = result.listCells(); 976 for (Cell cell : cells) { 977 try { 978 HBaseProtos.ServerName protoServer = 979 HBaseProtos.ServerName.parseFrom(CellUtil.cloneQualifier(cell)); 980 ServerName serverName = ServerName.valueOf(protoServer.getHostName(), 981 protoServer.getPort(), protoServer.getStartCode()); 982 983 long timestamp = Bytes.toLong(CellUtil.cloneValue(cell)); 984 checkpointMap.put(serverName, timestamp); 985 } catch (IllegalArgumentException e) { 986 LOG.warn("Failed to parse server name or timestamp from cell: {}", cell, e); 987 } 988 } 989 } 990 991 return checkpointMap; 992 } 993 994 /** 995 * Constructs a {@link Put} operation to update the last replicated WAL timestamp for a given 996 * server in the backup system table. 997 * @param serverNameBytes the serialized server name as bytes 998 * @param timestamp the WAL entry timestamp to store 999 * @return a {@link Put} object ready to be written to the system table 1000 */ 1001 private Put createPutForBackupCheckpoint(byte[] serverNameBytes, long timestamp) { 1002 Put put = new Put(rowkey(CONTINUOUS_BACKUP_REPLICATION_TIMESTAMP_ROW)); 1003 put.addColumn(BackupSystemTable.META_FAMILY, serverNameBytes, Bytes.toBytes(timestamp)); 1004 return put; 1005 } 1006 1007 /** 1008 * Deletes incremental backup set for a backup destination 1009 * @param backupRoot backup root 1010 */ 1011 public void deleteIncrementalBackupTableSet(String backupRoot) throws IOException { 1012 if (LOG.isTraceEnabled()) { 1013 LOG.trace("Delete incremental backup table set to backup system table. ROOT=" + backupRoot); 1014 } 1015 try (Table table = connection.getTable(tableName)) { 1016 Delete delete = createDeleteForIncrBackupTableSet(backupRoot); 1017 table.delete(delete); 1018 } 1019 } 1020 1021 /** 1022 * Checks if we have at least one backup session in backup system table This API is used by 1023 * BackupLogCleaner 1024 * @return true, if at least one session exists in backup system table 1025 * @throws IOException exception 1026 */ 1027 public boolean hasBackupSessions() throws IOException { 1028 LOG.trace("Has backup sessions from backup system table"); 1029 1030 Scan scan = createScanForBackupHistory(Order.OLD_TO_NEW); 1031 scan.setCaching(1); 1032 try (Table table = connection.getTable(tableName); 1033 ResultScanner scanner = table.getScanner(scan)) { 1034 return scanner.next() != null; 1035 } 1036 } 1037 1038 /** 1039 * BACKUP SETS 1040 */ 1041 1042 /** 1043 * Get backup set list 1044 * @return backup set list 1045 * @throws IOException if a table or scanner operation fails 1046 */ 1047 public List<String> listBackupSets() throws IOException { 1048 LOG.trace("Backup set list"); 1049 1050 List<String> list = new ArrayList<>(); 1051 try (Table table = connection.getTable(tableName)) { 1052 Scan scan = createScanForBackupSetList(); 1053 scan.readVersions(1); 1054 try (ResultScanner scanner = table.getScanner(scan)) { 1055 Result res; 1056 while ((res = scanner.next()) != null) { 1057 res.advance(); 1058 list.add(cellKeyToBackupSetName(res.current())); 1059 } 1060 return list; 1061 } 1062 } 1063 } 1064 1065 /** 1066 * Get backup set description (list of tables) 1067 * @param name set's name 1068 * @return list of tables in a backup set 1069 * @throws IOException if a table operation fails 1070 */ 1071 public List<TableName> describeBackupSet(String name) throws IOException { 1072 if (LOG.isTraceEnabled()) { 1073 LOG.trace(" Backup set describe: " + name); 1074 } 1075 try (Table table = connection.getTable(tableName)) { 1076 Get get = createGetForBackupSet(name); 1077 Result res = table.get(get); 1078 if (res.isEmpty()) { 1079 return null; 1080 } 1081 res.advance(); 1082 String[] tables = cellValueToBackupSet(res.current()); 1083 return Arrays.asList(tables).stream().map(item -> TableName.valueOf(item)) 1084 .collect(Collectors.toList()); 1085 } 1086 } 1087 1088 /** 1089 * Add backup set (list of tables) 1090 * @param name set name 1091 * @param newTables list of tables, comma-separated 1092 * @throws IOException if a table operation fails 1093 */ 1094 public void addToBackupSet(String name, String[] newTables) throws IOException { 1095 if (LOG.isTraceEnabled()) { 1096 LOG.trace("Backup set add: " + name + " tables [" + StringUtils.join(newTables, " ") + "]"); 1097 } 1098 String[] union = null; 1099 try (Table table = connection.getTable(tableName)) { 1100 Get get = createGetForBackupSet(name); 1101 Result res = table.get(get); 1102 if (res.isEmpty()) { 1103 union = newTables; 1104 } else { 1105 res.advance(); 1106 String[] tables = cellValueToBackupSet(res.current()); 1107 union = merge(tables, newTables); 1108 } 1109 Put put = createPutForBackupSet(name, union); 1110 table.put(put); 1111 } 1112 } 1113 1114 /** 1115 * Remove tables from backup set (list of tables) 1116 * @param name set name 1117 * @param toRemove list of tables 1118 * @throws IOException if a table operation or deleting the backup set fails 1119 */ 1120 public void removeFromBackupSet(String name, String[] toRemove) throws IOException { 1121 if (LOG.isTraceEnabled()) { 1122 LOG.trace( 1123 " Backup set remove from : " + name + " tables [" + StringUtils.join(toRemove, " ") + "]"); 1124 } 1125 String[] disjoint; 1126 String[] tables; 1127 try (Table table = connection.getTable(tableName)) { 1128 Get get = createGetForBackupSet(name); 1129 Result res = table.get(get); 1130 if (res.isEmpty()) { 1131 LOG.warn("Backup set '" + name + "' not found."); 1132 return; 1133 } else { 1134 res.advance(); 1135 tables = cellValueToBackupSet(res.current()); 1136 disjoint = disjoin(tables, toRemove); 1137 } 1138 if (disjoint.length > 0 && disjoint.length != tables.length) { 1139 Put put = createPutForBackupSet(name, disjoint); 1140 table.put(put); 1141 } else if (disjoint.length == tables.length) { 1142 LOG.warn("Backup set '" + name + "' does not contain tables [" 1143 + StringUtils.join(toRemove, " ") + "]"); 1144 } else { // disjoint.length == 0 and tables.length >0 1145 // Delete backup set 1146 LOG.info("Backup set '" + name + "' is empty. Deleting."); 1147 deleteBackupSet(name); 1148 } 1149 } 1150 } 1151 1152 private String[] merge(String[] existingTables, String[] newTables) { 1153 Set<String> tables = new HashSet<>(Arrays.asList(existingTables)); 1154 tables.addAll(Arrays.asList(newTables)); 1155 return tables.toArray(new String[0]); 1156 } 1157 1158 private String[] disjoin(String[] existingTables, String[] toRemove) { 1159 Set<String> tables = new HashSet<>(Arrays.asList(existingTables)); 1160 Arrays.asList(toRemove).forEach(table -> tables.remove(table)); 1161 return tables.toArray(new String[0]); 1162 } 1163 1164 /** 1165 * Delete backup set 1166 * @param name set's name 1167 * @throws IOException if getting or deleting the table fails 1168 */ 1169 public void deleteBackupSet(String name) throws IOException { 1170 if (LOG.isTraceEnabled()) { 1171 LOG.trace(" Backup set delete: " + name); 1172 } 1173 try (Table table = connection.getTable(tableName)) { 1174 Delete del = createDeleteForBackupSet(name); 1175 table.delete(del); 1176 } 1177 } 1178 1179 /** 1180 * Get backup system table descriptor 1181 * @return table's descriptor 1182 */ 1183 public static TableDescriptor getSystemTableDescriptor(Configuration conf) { 1184 TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(getTableName(conf)); 1185 1186 ColumnFamilyDescriptorBuilder colBuilder = 1187 ColumnFamilyDescriptorBuilder.newBuilder(SESSIONS_FAMILY); 1188 1189 colBuilder.setMaxVersions(1); 1190 Configuration config = HBaseConfiguration.create(); 1191 int ttl = config.getInt(BackupRestoreConstants.BACKUP_SYSTEM_TTL_KEY, 1192 BackupRestoreConstants.BACKUP_SYSTEM_TTL_DEFAULT); 1193 colBuilder.setTimeToLive(ttl); 1194 1195 ColumnFamilyDescriptor colSessionsDesc = colBuilder.build(); 1196 builder.setColumnFamily(colSessionsDesc); 1197 1198 colBuilder = ColumnFamilyDescriptorBuilder.newBuilder(META_FAMILY); 1199 colBuilder.setTimeToLive(ttl); 1200 builder.setColumnFamily(colBuilder.build()); 1201 return builder.build(); 1202 } 1203 1204 public static TableName getTableName(Configuration conf) { 1205 String name = conf.get(BackupRestoreConstants.BACKUP_SYSTEM_TABLE_NAME_KEY, 1206 BackupRestoreConstants.BACKUP_SYSTEM_TABLE_NAME_DEFAULT); 1207 return TableName.valueOf(name); 1208 } 1209 1210 public static String getTableNameAsString(Configuration conf) { 1211 return getTableName(conf).getNameAsString(); 1212 } 1213 1214 public static String getSnapshotName(Configuration conf) { 1215 return "snapshot_" + getTableNameAsString(conf).replace(":", "_"); 1216 } 1217 1218 /** 1219 * Get backup system table descriptor 1220 * @return table's descriptor 1221 */ 1222 public static TableDescriptor getSystemTableForBulkLoadedDataDescriptor(Configuration conf) { 1223 TableDescriptorBuilder builder = 1224 TableDescriptorBuilder.newBuilder(getTableNameForBulkLoadedData(conf)); 1225 1226 ColumnFamilyDescriptorBuilder colBuilder = 1227 ColumnFamilyDescriptorBuilder.newBuilder(SESSIONS_FAMILY); 1228 colBuilder.setMaxVersions(1); 1229 Configuration config = HBaseConfiguration.create(); 1230 int ttl = config.getInt(BackupRestoreConstants.BACKUP_SYSTEM_TTL_KEY, 1231 BackupRestoreConstants.BACKUP_SYSTEM_TTL_DEFAULT); 1232 colBuilder.setTimeToLive(ttl); 1233 ColumnFamilyDescriptor colSessionsDesc = colBuilder.build(); 1234 builder.setColumnFamily(colSessionsDesc); 1235 colBuilder = ColumnFamilyDescriptorBuilder.newBuilder(META_FAMILY); 1236 colBuilder.setTimeToLive(ttl); 1237 builder.setColumnFamily(colBuilder.build()); 1238 return builder.build(); 1239 } 1240 1241 public static TableName getTableNameForBulkLoadedData(Configuration conf) { 1242 String name = conf.get(BackupRestoreConstants.BACKUP_SYSTEM_TABLE_NAME_KEY, 1243 BackupRestoreConstants.BACKUP_SYSTEM_TABLE_NAME_DEFAULT) + "_bulk"; 1244 return TableName.valueOf(name); 1245 } 1246 1247 /** 1248 * Creates Put operation for a given backup info object 1249 * @param context backup info 1250 * @return put operation 1251 * @throws IOException exception 1252 */ 1253 private Put createPutForBackupInfo(BackupInfo context) throws IOException { 1254 Put put = new Put(rowkey(BACKUP_INFO_PREFIX, context.getBackupId())); 1255 put.addColumn(BackupSystemTable.SESSIONS_FAMILY, Bytes.toBytes("context"), 1256 context.toByteArray()); 1257 return put; 1258 } 1259 1260 /** 1261 * Creates Get operation for a given backup id 1262 * @param backupId backup's ID 1263 * @return get operation 1264 * @throws IOException exception 1265 */ 1266 private Get createGetForBackupInfo(String backupId) throws IOException { 1267 Get get = new Get(rowkey(BACKUP_INFO_PREFIX, backupId)); 1268 get.addFamily(BackupSystemTable.SESSIONS_FAMILY); 1269 get.readVersions(1); 1270 return get; 1271 } 1272 1273 /** 1274 * Creates Delete operation for a given backup id 1275 * @param backupId backup's ID 1276 * @return delete operation 1277 */ 1278 private Delete createDeleteForBackupInfo(String backupId) { 1279 Delete del = new Delete(rowkey(BACKUP_INFO_PREFIX, backupId)); 1280 del.addFamily(BackupSystemTable.SESSIONS_FAMILY); 1281 return del; 1282 } 1283 1284 /** 1285 * Converts Result to BackupInfo 1286 * @param res HBase result 1287 * @return backup info instance 1288 * @throws IOException exception 1289 */ 1290 private BackupInfo resultToBackupInfo(Result res) throws IOException { 1291 res.advance(); 1292 Cell cell = res.current(); 1293 return cellToBackupInfo(cell); 1294 } 1295 1296 /** 1297 * Creates Get to retrieve incremental backup table set from backup system table 1298 * @return get operation 1299 * @throws IOException exception 1300 */ 1301 private Get createGetForIncrBackupTableSet(String backupRoot) throws IOException { 1302 Get get = new Get(rowkey(INCR_BACKUP_SET, backupRoot)); 1303 get.addFamily(BackupSystemTable.META_FAMILY); 1304 get.readVersions(1); 1305 return get; 1306 } 1307 1308 /** 1309 * Creates a Get operation to retrieve the continuous backup table set from the backup system 1310 * table. 1311 * @return a Get operation for retrieving the table set 1312 */ 1313 private Get createGetForContinuousBackupTableSet() throws IOException { 1314 Get get = new Get(rowkey(CONTINUOUS_BACKUP_SET)); 1315 get.addFamily(BackupSystemTable.META_FAMILY); 1316 get.readVersions(1); 1317 return get; 1318 } 1319 1320 /** 1321 * Creates Put to store incremental backup table set 1322 * @param tables tables 1323 * @return put operation 1324 */ 1325 private Put createPutForIncrBackupTableSet(Set<TableName> tables, String backupRoot) { 1326 Put put = new Put(rowkey(INCR_BACKUP_SET, backupRoot)); 1327 for (TableName table : tables) { 1328 put.addColumn(BackupSystemTable.META_FAMILY, Bytes.toBytes(table.getNameAsString()), 1329 EMPTY_VALUE); 1330 } 1331 return put; 1332 } 1333 1334 /** 1335 * Creates a Put operation to store the continuous backup table set. Only includes tables that are 1336 * not already in the set. 1337 * @param tables tables to add 1338 * @param existingTables tables that already have continuous backup enabled 1339 * @param startTimestamp timestamp indicating when continuous backup started 1340 * @return put operation 1341 */ 1342 private Put createPutForContinuousBackupTableSet(Set<TableName> tables, 1343 Map<TableName, Long> existingTables, long startTimestamp) { 1344 Put put = new Put(rowkey(CONTINUOUS_BACKUP_SET)); 1345 1346 for (TableName table : tables) { 1347 if (!existingTables.containsKey(table)) { 1348 put.addColumn(BackupSystemTable.META_FAMILY, Bytes.toBytes(table.getNameAsString()), 1349 Bytes.toBytes(startTimestamp)); 1350 } 1351 } 1352 1353 return put; 1354 } 1355 1356 /** 1357 * Creates Delete for incremental backup table set 1358 * @param backupRoot backup root 1359 * @return delete operation 1360 */ 1361 private Delete createDeleteForIncrBackupTableSet(String backupRoot) { 1362 Delete delete = new Delete(rowkey(INCR_BACKUP_SET, backupRoot)); 1363 delete.addFamily(BackupSystemTable.META_FAMILY); 1364 return delete; 1365 } 1366 1367 /** 1368 * Creates Delete for continuous backup table set 1369 * @param tables tables to remove 1370 * @return delete operation 1371 */ 1372 private Delete createDeleteForContinuousBackupTableSet(Set<TableName> tables) { 1373 Delete delete = new Delete(rowkey(CONTINUOUS_BACKUP_SET)); 1374 for (TableName tableName : tables) { 1375 delete.addColumn(META_FAMILY, Bytes.toBytes(tableName.getNameAsString())); 1376 } 1377 return delete; 1378 } 1379 1380 /** 1381 * Creates Scan operation to load backup history 1382 * @param order order of the scan results 1383 * @return scan operation 1384 */ 1385 private Scan createScanForBackupHistory(Order order) { 1386 Scan scan = new Scan(); 1387 byte[] startRow = Bytes.toBytes(BACKUP_INFO_PREFIX); 1388 if (order == Order.NEW_TO_OLD) { 1389 byte[] stopRow = Arrays.copyOf(startRow, startRow.length); 1390 stopRow[stopRow.length - 1] = (byte) (stopRow[stopRow.length - 1] + 1); 1391 scan.setReversed(true); 1392 scan.withStartRow(stopRow, false); 1393 scan.withStopRow(startRow); 1394 } else if (order == Order.OLD_TO_NEW) { 1395 scan.setStartStopRowForPrefixScan(startRow); 1396 } else { 1397 throw new IllegalArgumentException("Unsupported order: " + order); 1398 } 1399 scan.addFamily(BackupSystemTable.SESSIONS_FAMILY); 1400 scan.readVersions(1); 1401 return scan; 1402 } 1403 1404 /** 1405 * Converts cell to backup info instance. 1406 * @param current current cell 1407 * @return backup backup info instance 1408 * @throws IOException exception 1409 */ 1410 private BackupInfo cellToBackupInfo(Cell current) throws IOException { 1411 byte[] data = CellUtil.cloneValue(current); 1412 return BackupInfo.fromByteArray(data); 1413 } 1414 1415 /** 1416 * Creates Put to write RS last roll log timestamp map 1417 * @param table table 1418 * @param smap map, containing RS:ts 1419 * @return put operation 1420 */ 1421 private Put createPutForWriteRegionServerLogTimestamp(TableName table, byte[] smap, 1422 String backupRoot) { 1423 Put put = new Put(rowkey(TABLE_RS_LOG_MAP_PREFIX, backupRoot, NULL, table.getNameAsString())); 1424 put.addColumn(BackupSystemTable.META_FAMILY, Bytes.toBytes("log-roll-map"), smap); 1425 return put; 1426 } 1427 1428 /** 1429 * Creates Scan to load table-> { RS -> ts} map of maps 1430 * @return scan operation 1431 */ 1432 private Scan createScanForReadLogTimestampMap(String backupRoot) { 1433 Scan scan = new Scan(); 1434 scan.setStartStopRowForPrefixScan(rowkey(TABLE_RS_LOG_MAP_PREFIX, backupRoot, NULL)); 1435 scan.addFamily(BackupSystemTable.META_FAMILY); 1436 1437 return scan; 1438 } 1439 1440 /** 1441 * Get table name from rowkey 1442 * @param cloneRow rowkey 1443 * @return table name 1444 */ 1445 private String getTableNameForReadLogTimestampMap(byte[] cloneRow) { 1446 String s = Bytes.toString(cloneRow); 1447 int index = s.lastIndexOf(NULL); 1448 return s.substring(index + 1); 1449 } 1450 1451 /** 1452 * Creates Put to store RS last log result 1453 * @param server server name 1454 * @param timestamp log roll result (timestamp) 1455 * @return put operation 1456 */ 1457 private Put createPutForRegionServerLastLogRollResult(String server, Long timestamp, 1458 String backupRoot) { 1459 Put put = new Put(rowkey(RS_LOG_TS_PREFIX, backupRoot, NULL, server)); 1460 put.addColumn(BackupSystemTable.META_FAMILY, Bytes.toBytes("rs-log-ts"), 1461 Bytes.toBytes(timestamp)); 1462 return put; 1463 } 1464 1465 /** 1466 * Creates Scan operation to load last RS log roll results 1467 * @return scan operation 1468 */ 1469 private Scan createScanForReadRegionServerLastLogRollResult(String backupRoot) { 1470 Scan scan = new Scan(); 1471 scan.setStartStopRowForPrefixScan(rowkey(RS_LOG_TS_PREFIX, backupRoot, NULL)); 1472 scan.addFamily(BackupSystemTable.META_FAMILY); 1473 scan.readVersions(1); 1474 1475 return scan; 1476 } 1477 1478 /** 1479 * Get server's name from rowkey 1480 * @param row rowkey 1481 * @return server's name 1482 */ 1483 private String getServerNameForReadRegionServerLastLogRollResult(byte[] row) { 1484 String s = Bytes.toString(row); 1485 int index = s.lastIndexOf(NULL); 1486 return s.substring(index + 1); 1487 } 1488 1489 /** 1490 * Creates Put's for bulk loads. 1491 */ 1492 private static List<Put> createPutForBulkLoad(TableName table, byte[] region, 1493 Map<byte[], List<Path>> columnFamilyToHFilePaths) { 1494 List<Put> puts = new ArrayList<>(); 1495 for (Map.Entry<byte[], List<Path>> entry : columnFamilyToHFilePaths.entrySet()) { 1496 for (Path path : entry.getValue()) { 1497 String file = path.toString(); 1498 int lastSlash = file.lastIndexOf("/"); 1499 String filename = file.substring(lastSlash + 1); 1500 Put put = new Put(rowkey(BULK_LOAD_PREFIX, table.toString(), BLK_LD_DELIM, 1501 Bytes.toString(region), BLK_LD_DELIM, filename)); 1502 put.addColumn(BackupSystemTable.META_FAMILY, TBL_COL, table.getName()); 1503 put.addColumn(BackupSystemTable.META_FAMILY, FAM_COL, entry.getKey()); 1504 put.addColumn(BackupSystemTable.META_FAMILY, PATH_COL, Bytes.toBytes(file)); 1505 puts.add(put); 1506 LOG.debug("Done writing bulk path {} for {} {}", file, table, Bytes.toString(region)); 1507 } 1508 } 1509 return puts; 1510 } 1511 1512 public static void snapshot(Connection conn) throws IOException { 1513 try (Admin admin = conn.getAdmin()) { 1514 Configuration conf = conn.getConfiguration(); 1515 admin.snapshot(BackupSystemTable.getSnapshotName(conf), BackupSystemTable.getTableName(conf)); 1516 } 1517 } 1518 1519 public static void restoreFromSnapshot(Connection conn) throws IOException { 1520 Configuration conf = conn.getConfiguration(); 1521 LOG.debug("Restoring " + BackupSystemTable.getTableNameAsString(conf) + " from snapshot"); 1522 try (Admin admin = conn.getAdmin()) { 1523 String snapshotName = BackupSystemTable.getSnapshotName(conf); 1524 if (snapshotExists(admin, snapshotName)) { 1525 admin.restoreBackupSystemTable(snapshotName); 1526 LOG.debug("Done restoring backup system table"); 1527 } else { 1528 // Snapshot does not exists, i.e completeBackup failed after 1529 // deleting backup system table snapshot 1530 // In this case we log WARN and proceed 1531 LOG.warn( 1532 "Could not restore backup system table. Snapshot " + snapshotName + " does not exists."); 1533 } 1534 } 1535 } 1536 1537 private static boolean snapshotExists(Admin admin, String snapshotName) throws IOException { 1538 List<SnapshotDescription> list = admin.listSnapshots(); 1539 for (SnapshotDescription desc : list) { 1540 if (desc.getName().equals(snapshotName)) { 1541 return true; 1542 } 1543 } 1544 return false; 1545 } 1546 1547 public static boolean snapshotExists(Connection conn) throws IOException { 1548 return snapshotExists(conn.getAdmin(), getSnapshotName(conn.getConfiguration())); 1549 } 1550 1551 public static void deleteSnapshot(Connection conn) throws IOException { 1552 Configuration conf = conn.getConfiguration(); 1553 LOG.debug("Deleting " + BackupSystemTable.getSnapshotName(conf) + " from the system"); 1554 try (Admin admin = conn.getAdmin()) { 1555 String snapshotName = BackupSystemTable.getSnapshotName(conf); 1556 if (snapshotExists(admin, snapshotName)) { 1557 admin.deleteSnapshot(snapshotName); 1558 LOG.debug("Done deleting backup system table snapshot"); 1559 } else { 1560 LOG.error("Snapshot " + snapshotName + " does not exists"); 1561 } 1562 } 1563 } 1564 1565 private Put createPutForDeleteOperation(String[] backupIdList) { 1566 byte[] value = Bytes.toBytes(StringUtils.join(backupIdList, ",")); 1567 Put put = new Put(DELETE_OP_ROW); 1568 put.addColumn(META_FAMILY, FAM_COL, value); 1569 return put; 1570 } 1571 1572 private Delete createDeleteForBackupDeleteOperation() { 1573 Delete delete = new Delete(DELETE_OP_ROW); 1574 delete.addFamily(META_FAMILY); 1575 return delete; 1576 } 1577 1578 private Get createGetForDeleteOperation() { 1579 Get get = new Get(DELETE_OP_ROW); 1580 get.addFamily(META_FAMILY); 1581 return get; 1582 } 1583 1584 public void startDeleteOperation(String[] backupIdList) throws IOException { 1585 if (LOG.isTraceEnabled()) { 1586 LOG.trace("Start delete operation for backups: " + StringUtils.join(backupIdList)); 1587 } 1588 Put put = createPutForDeleteOperation(backupIdList); 1589 try (Table table = connection.getTable(tableName)) { 1590 table.put(put); 1591 } 1592 } 1593 1594 public void finishDeleteOperation() throws IOException { 1595 LOG.trace("Finsih delete operation for backup ids"); 1596 1597 Delete delete = createDeleteForBackupDeleteOperation(); 1598 try (Table table = connection.getTable(tableName)) { 1599 table.delete(delete); 1600 } 1601 } 1602 1603 public String[] getListOfBackupIdsFromDeleteOperation() throws IOException { 1604 LOG.trace("Get delete operation for backup ids"); 1605 1606 Get get = createGetForDeleteOperation(); 1607 try (Table table = connection.getTable(tableName)) { 1608 Result res = table.get(get); 1609 if (res.isEmpty()) { 1610 return null; 1611 } 1612 Cell cell = res.listCells().get(0); 1613 byte[] val = CellUtil.cloneValue(cell); 1614 if (val.length == 0) { 1615 return null; 1616 } 1617 return Splitter.on(',').splitToStream(new String(val, StandardCharsets.UTF_8)) 1618 .toArray(String[]::new); 1619 } 1620 } 1621 1622 private Put createPutForMergeOperation(String[] backupIdList) { 1623 byte[] value = Bytes.toBytes(StringUtils.join(backupIdList, ",")); 1624 Put put = new Put(MERGE_OP_ROW); 1625 put.addColumn(META_FAMILY, FAM_COL, value); 1626 return put; 1627 } 1628 1629 public boolean isMergeInProgress() throws IOException { 1630 Get get = new Get(MERGE_OP_ROW); 1631 try (Table table = connection.getTable(tableName)) { 1632 Result res = table.get(get); 1633 return !res.isEmpty(); 1634 } 1635 } 1636 1637 private Put createPutForUpdateTablesForMerge(List<TableName> tables) { 1638 byte[] value = Bytes.toBytes(StringUtils.join(tables, ",")); 1639 Put put = new Put(MERGE_OP_ROW); 1640 put.addColumn(META_FAMILY, PATH_COL, value); 1641 return put; 1642 } 1643 1644 private Delete createDeleteForBackupMergeOperation() { 1645 Delete delete = new Delete(MERGE_OP_ROW); 1646 delete.addFamily(META_FAMILY); 1647 return delete; 1648 } 1649 1650 private Get createGetForMergeOperation() { 1651 Get get = new Get(MERGE_OP_ROW); 1652 get.addFamily(META_FAMILY); 1653 return get; 1654 } 1655 1656 public void startMergeOperation(String[] backupIdList) throws IOException { 1657 if (LOG.isTraceEnabled()) { 1658 LOG.trace("Start merge operation for backups: " + StringUtils.join(backupIdList)); 1659 } 1660 Put put = createPutForMergeOperation(backupIdList); 1661 try (Table table = connection.getTable(tableName)) { 1662 table.put(put); 1663 } 1664 } 1665 1666 public void updateProcessedTablesForMerge(List<TableName> tables) throws IOException { 1667 if (LOG.isTraceEnabled()) { 1668 LOG.trace("Update tables for merge : " + StringUtils.join(tables, ",")); 1669 } 1670 Put put = createPutForUpdateTablesForMerge(tables); 1671 try (Table table = connection.getTable(tableName)) { 1672 table.put(put); 1673 } 1674 } 1675 1676 public void finishMergeOperation() throws IOException { 1677 LOG.trace("Finish merge operation for backup ids"); 1678 1679 Delete delete = createDeleteForBackupMergeOperation(); 1680 try (Table table = connection.getTable(tableName)) { 1681 table.delete(delete); 1682 } 1683 } 1684 1685 public String[] getListOfBackupIdsFromMergeOperation() throws IOException { 1686 LOG.trace("Get backup ids for merge operation"); 1687 1688 Get get = createGetForMergeOperation(); 1689 try (Table table = connection.getTable(tableName)) { 1690 Result res = table.get(get); 1691 if (res.isEmpty()) { 1692 return null; 1693 } 1694 Cell cell = res.listCells().get(0); 1695 byte[] val = CellUtil.cloneValue(cell); 1696 if (val.length == 0) { 1697 return null; 1698 } 1699 return Splitter.on(',').splitToStream(new String(val, StandardCharsets.UTF_8)) 1700 .toArray(String[]::new); 1701 } 1702 } 1703 1704 /** 1705 * Creates a scan to read all registered bulk loads for the given table, or for all tables if 1706 * {@code table} is {@code null}. 1707 */ 1708 static Scan createScanForOrigBulkLoadedFiles(@Nullable TableName table) { 1709 Scan scan = new Scan(); 1710 byte[] startRow = table == null 1711 ? BULK_LOAD_PREFIX_BYTES 1712 : rowkey(BULK_LOAD_PREFIX, table.toString(), BLK_LD_DELIM); 1713 byte[] stopRow = Arrays.copyOf(startRow, startRow.length); 1714 stopRow[stopRow.length - 1] = (byte) (stopRow[stopRow.length - 1] + 1); 1715 scan.withStartRow(startRow); 1716 scan.withStopRow(stopRow); 1717 scan.addFamily(BackupSystemTable.META_FAMILY); 1718 scan.readVersions(1); 1719 return scan; 1720 } 1721 1722 static String getTableNameFromOrigBulkLoadRow(String rowStr) { 1723 // format is bulk : namespace : table : region : file 1724 return Iterators.get(Splitter.onPattern(BLK_LD_DELIM).split(rowStr).iterator(), 1); 1725 } 1726 1727 static String getRegionNameFromOrigBulkLoadRow(String rowStr) { 1728 // format is bulk : namespace : table : region : file 1729 List<String> parts = Splitter.onPattern(BLK_LD_DELIM).splitToList(rowStr); 1730 Iterator<String> i = parts.iterator(); 1731 int idx = 3; 1732 if (parts.size() == 4) { 1733 // the table is in default namespace 1734 idx = 2; 1735 } 1736 String region = Iterators.get(i, idx); 1737 LOG.debug("bulk row string " + rowStr + " region " + region); 1738 return region; 1739 } 1740 1741 /* 1742 * Used to query bulk loaded hfiles which have been copied by incremental backup 1743 * @param backupId the backup Id. It can be null when querying for all tables 1744 * @return the Scan object 1745 * @deprecated This method is broken if a backupId is specified - see HBASE-28715 1746 */ 1747 static Scan createScanForBulkLoadedFiles(String backupId) { 1748 Scan scan = new Scan(); 1749 byte[] startRow = 1750 backupId == null ? BULK_LOAD_PREFIX_BYTES : rowkey(BULK_LOAD_PREFIX, backupId + BLK_LD_DELIM); 1751 byte[] stopRow = Arrays.copyOf(startRow, startRow.length); 1752 stopRow[stopRow.length - 1] = (byte) (stopRow[stopRow.length - 1] + 1); 1753 scan.withStartRow(startRow); 1754 scan.withStopRow(stopRow); 1755 scan.addFamily(BackupSystemTable.META_FAMILY); 1756 scan.readVersions(1); 1757 return scan; 1758 } 1759 1760 /** 1761 * Creates Scan operation to load backup set list 1762 * @return scan operation 1763 */ 1764 private Scan createScanForBackupSetList() { 1765 Scan scan = new Scan(); 1766 byte[] startRow = Bytes.toBytes(SET_KEY_PREFIX); 1767 byte[] stopRow = Arrays.copyOf(startRow, startRow.length); 1768 stopRow[stopRow.length - 1] = (byte) (stopRow[stopRow.length - 1] + 1); 1769 scan.withStartRow(startRow); 1770 scan.withStopRow(stopRow); 1771 scan.addFamily(BackupSystemTable.META_FAMILY); 1772 return scan; 1773 } 1774 1775 /** 1776 * Creates Get operation to load backup set content 1777 * @return get operation 1778 */ 1779 private Get createGetForBackupSet(String name) { 1780 Get get = new Get(rowkey(SET_KEY_PREFIX, name)); 1781 get.addFamily(BackupSystemTable.META_FAMILY); 1782 return get; 1783 } 1784 1785 /** 1786 * Creates Delete operation to delete backup set content 1787 * @param name backup set's name 1788 * @return delete operation 1789 */ 1790 private Delete createDeleteForBackupSet(String name) { 1791 Delete del = new Delete(rowkey(SET_KEY_PREFIX, name)); 1792 del.addFamily(BackupSystemTable.META_FAMILY); 1793 return del; 1794 } 1795 1796 /** 1797 * Creates Put operation to update backup set content 1798 * @param name backup set's name 1799 * @param tables list of tables 1800 * @return put operation 1801 */ 1802 private Put createPutForBackupSet(String name, String[] tables) { 1803 Put put = new Put(rowkey(SET_KEY_PREFIX, name)); 1804 byte[] value = convertToByteArray(tables); 1805 put.addColumn(BackupSystemTable.META_FAMILY, Bytes.toBytes("tables"), value); 1806 return put; 1807 } 1808 1809 private byte[] convertToByteArray(String[] tables) { 1810 return Bytes.toBytes(StringUtils.join(tables, ",")); 1811 } 1812 1813 /** 1814 * Converts cell to backup set list. 1815 * @param current current cell 1816 * @return backup set as array of table names 1817 */ 1818 private String[] cellValueToBackupSet(Cell current) { 1819 byte[] data = CellUtil.cloneValue(current); 1820 if (!ArrayUtils.isEmpty(data)) { 1821 return Bytes.toString(data).split(","); 1822 } 1823 return new String[0]; 1824 } 1825 1826 /** 1827 * Converts cell key to backup set name. 1828 * @param current current cell 1829 * @return backup set name 1830 */ 1831 private String cellKeyToBackupSetName(Cell current) { 1832 byte[] data = CellUtil.cloneRow(current); 1833 return Bytes.toString(data).substring(SET_KEY_PREFIX.length()); 1834 } 1835 1836 private static byte[] rowkey(String s, String... other) { 1837 StringBuilder sb = new StringBuilder(s); 1838 for (String ss : other) { 1839 sb.append(ss); 1840 } 1841 return Bytes.toBytes(sb.toString()); 1842 } 1843 1844 private static void ensureTableEnabled(Admin admin, TableName tableName) throws IOException { 1845 if (!admin.isTableEnabled(tableName)) { 1846 try { 1847 admin.enableTable(tableName); 1848 } catch (TableNotDisabledException ignored) { 1849 LOG.info("Table {} is not disabled, ignoring enable request", tableName); 1850 } 1851 } 1852 } 1853 1854 public enum Order { 1855 /** 1856 * Old backups first, most recents last. I.e. sorted by ascending backupId. 1857 */ 1858 OLD_TO_NEW, 1859 /** 1860 * New backups first, oldest last. I.e. sorted by descending backupId. 1861 */ 1862 NEW_TO_OLD 1863 } 1864}