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. For a region server that took part in the backup's log roll, the 666 * saved timestamp is the result of that roll. For a region server that did not (offline or 667 * decommissioned), it is the creation time of the newest of its log files that was backed up. 668 * @param tables tables 669 * @param newTimestamps timestamps 670 * @param backupRoot root directory path to backup 671 * @throws IOException exception 672 */ 673 public void writeRegionServerLogTimestamp(Set<TableName> tables, Map<String, Long> newTimestamps, 674 String backupRoot) throws IOException { 675 if (LOG.isTraceEnabled()) { 676 LOG.trace("write RS log time stamps to backup system table for tables [" 677 + StringUtils.join(tables, ",") + "]"); 678 } 679 List<Put> puts = new ArrayList<>(); 680 for (TableName table : tables) { 681 byte[] smapData = toTableServerTimestampProto(table, newTimestamps).toByteArray(); 682 Put put = createPutForWriteRegionServerLogTimestamp(table, smapData, backupRoot); 683 puts.add(put); 684 } 685 try (BufferedMutator bufferedMutator = connection.getBufferedMutator(tableName)) { 686 bufferedMutator.mutate(puts); 687 } 688 } 689 690 /** 691 * Read the timestamp for each region server log after the last successful backup. Each table has 692 * its own set of the timestamps. The info is stored for each table as a concatenated string of 693 * rs->timestapmp 694 * @param backupRoot root directory path to backup 695 * @return the timestamp for each region server. key: tableName value: 696 * RegionServer,PreviousTimeStamp 697 * @throws IOException exception 698 */ 699 public Map<TableName, Map<String, Long>> readLogTimestampMap(String backupRoot) 700 throws IOException { 701 if (LOG.isTraceEnabled()) { 702 LOG.trace("read RS log ts from backup system table for root=" + backupRoot); 703 } 704 705 Map<TableName, Map<String, Long>> tableTimestampMap = new HashMap<>(); 706 707 Scan scan = createScanForReadLogTimestampMap(backupRoot); 708 try (Table table = connection.getTable(tableName); 709 ResultScanner scanner = table.getScanner(scan)) { 710 Result res; 711 while ((res = scanner.next()) != null) { 712 res.advance(); 713 Cell cell = res.current(); 714 byte[] row = CellUtil.cloneRow(cell); 715 String tabName = getTableNameForReadLogTimestampMap(row); 716 TableName tn = TableName.valueOf(tabName); 717 byte[] data = CellUtil.cloneValue(cell); 718 if (data == null) { 719 throw new IOException("Data of last backup data from backup system table " 720 + "is empty. Create a backup first."); 721 } 722 if (data != null && data.length > 0) { 723 HashMap<String, Long> lastBackup = 724 fromTableServerTimestampProto(BackupProtos.TableServerTimestamp.parseFrom(data)); 725 tableTimestampMap.put(tn, lastBackup); 726 } 727 } 728 return tableTimestampMap; 729 } 730 } 731 732 private BackupProtos.TableServerTimestamp toTableServerTimestampProto(TableName table, 733 Map<String, Long> map) { 734 BackupProtos.TableServerTimestamp.Builder tstBuilder = 735 BackupProtos.TableServerTimestamp.newBuilder(); 736 tstBuilder 737 .setTableName(org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil.toProtoTableName(table)); 738 739 for (Entry<String, Long> entry : map.entrySet()) { 740 BackupProtos.ServerTimestamp.Builder builder = BackupProtos.ServerTimestamp.newBuilder(); 741 HBaseProtos.ServerName.Builder snBuilder = HBaseProtos.ServerName.newBuilder(); 742 ServerName sn = ServerName.parseServerName(entry.getKey()); 743 snBuilder.setHostName(sn.getHostname()); 744 snBuilder.setPort(sn.getPort()); 745 builder.setServerName(snBuilder.build()); 746 builder.setTimestamp(entry.getValue()); 747 tstBuilder.addServerTimestamp(builder.build()); 748 } 749 750 return tstBuilder.build(); 751 } 752 753 private HashMap<String, Long> 754 fromTableServerTimestampProto(BackupProtos.TableServerTimestamp proto) { 755 756 HashMap<String, Long> map = new HashMap<>(); 757 List<BackupProtos.ServerTimestamp> list = proto.getServerTimestampList(); 758 for (BackupProtos.ServerTimestamp st : list) { 759 ServerName sn = 760 org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil.toServerName(st.getServerName()); 761 map.put(sn.getHostname() + ":" + sn.getPort(), st.getTimestamp()); 762 } 763 return map; 764 } 765 766 /** 767 * Return the current tables covered by incremental backup. 768 * @param backupRoot root directory path to backup 769 * @return set of tableNames 770 * @throws IOException exception 771 */ 772 public Set<TableName> getIncrementalBackupTableSet(String backupRoot) throws IOException { 773 LOG.trace("get incremental backup table set from backup system table"); 774 775 TreeSet<TableName> set = new TreeSet<>(); 776 777 try (Table table = connection.getTable(tableName)) { 778 Get get = createGetForIncrBackupTableSet(backupRoot); 779 Result res = table.get(get); 780 if (res.isEmpty()) { 781 return set; 782 } 783 List<Cell> cells = res.listCells(); 784 for (Cell cell : cells) { 785 // qualifier = table name - we use table names as qualifiers 786 set.add(TableName.valueOf(CellUtil.cloneQualifier(cell))); 787 } 788 return set; 789 } 790 } 791 792 /** 793 * Retrieves the current set of tables covered by continuous backup along with the timestamp 794 * indicating when continuous backup started for each table. 795 * @return a map where the key is the table name and the value is the timestamp representing the 796 * start time of continuous backup for that table. 797 * @throws IOException if an I/O error occurs while accessing the backup system table. 798 */ 799 public Map<TableName, Long> getContinuousBackupTableSet() throws IOException { 800 LOG.trace("Retrieving continuous backup table set from the backup system table."); 801 Map<TableName, Long> tableMap = new TreeMap<>(); 802 803 try (Table systemTable = connection.getTable(tableName)) { 804 Get getOperation = createGetForContinuousBackupTableSet(); 805 Result result = systemTable.get(getOperation); 806 807 if (result.isEmpty()) { 808 return tableMap; 809 } 810 811 // Extract table names and timestamps from the result cells 812 List<Cell> cells = result.listCells(); 813 for (Cell cell : cells) { 814 TableName tableName = TableName.valueOf(CellUtil.cloneQualifier(cell)); 815 long timestamp = Bytes.toLong(CellUtil.cloneValue(cell)); 816 tableMap.put(tableName, timestamp); 817 } 818 } 819 820 return tableMap; 821 } 822 823 /** 824 * Add tables to global incremental backup set 825 * @param tables set of tables 826 * @param backupRoot root directory path to backup 827 * @throws IOException exception 828 */ 829 public void addIncrementalBackupTableSet(Set<TableName> tables, String backupRoot) 830 throws IOException { 831 if (LOG.isTraceEnabled()) { 832 LOG.trace("Add incremental backup table set to backup system table. ROOT=" + backupRoot 833 + " tables [" + StringUtils.join(tables, " ") + "]"); 834 } 835 if (LOG.isDebugEnabled()) { 836 tables.forEach(table -> LOG.debug(Objects.toString(table))); 837 } 838 try (Table table = connection.getTable(tableName)) { 839 Put put = createPutForIncrBackupTableSet(tables, backupRoot); 840 table.put(put); 841 } 842 } 843 844 /** 845 * Add tables to the global continuous backup set. Only updates tables that are not already in the 846 * continuous backup set. 847 * @param tables set of tables to add 848 * @param startTimestamp timestamp indicating when continuous backup started 849 * @throws IOException if an error occurs while updating the backup system table 850 */ 851 public void addContinuousBackupTableSet(Set<TableName> tables, long startTimestamp) 852 throws IOException { 853 if (LOG.isTraceEnabled()) { 854 LOG.trace("Add continuous backup table set to backup system table. tables [" 855 + StringUtils.join(tables, " ") + "]"); 856 } 857 if (LOG.isDebugEnabled()) { 858 tables.forEach(table -> LOG.debug(Objects.toString(table))); 859 } 860 861 // Get existing continuous backup tables 862 Map<TableName, Long> existingTables = getContinuousBackupTableSet(); 863 864 try (Table table = connection.getTable(tableName)) { 865 Put put = createPutForContinuousBackupTableSet(tables, existingTables, startTimestamp); 866 if (!put.isEmpty()) { 867 table.put(put); 868 } 869 } 870 } 871 872 /** 873 * Updates the system table with the new start timestamps for continuous backup tables. 874 * @param tablesToUpdate The set of tables that need their start timestamps updated. 875 * @param newStartTimestamp The new start timestamp to be set. 876 */ 877 public void updateContinuousBackupTableSet(Set<TableName> tablesToUpdate, long newStartTimestamp) 878 throws IOException { 879 if (tablesToUpdate == null || tablesToUpdate.isEmpty()) { 880 LOG.warn("No tables provided for updating start timestamps."); 881 return; 882 } 883 884 try (Table table = connection.getTable(tableName)) { 885 Put put = new Put(rowkey(CONTINUOUS_BACKUP_SET)); 886 887 for (TableName tableName : tablesToUpdate) { 888 put.addColumn(BackupSystemTable.META_FAMILY, Bytes.toBytes(tableName.getNameAsString()), 889 Bytes.toBytes(newStartTimestamp)); 890 } 891 892 table.put(put); 893 LOG.info("Successfully updated start timestamps for {} tables in the backup system table.", 894 tablesToUpdate.size()); 895 } 896 } 897 898 /** 899 * Removes tables from the global continuous backup set. Only removes entries that currently exist 900 * in the backup system table. 901 * @param tables set of tables to remove 902 * @throws IOException if an error occurs while updating the backup system table 903 */ 904 public void removeContinuousBackupTableSet(Set<TableName> tables) throws IOException { 905 if (LOG.isTraceEnabled()) { 906 LOG.trace("Remove continuous backup table set from backup system table. tables [" 907 + StringUtils.join(tables, " ") + "]"); 908 } 909 if (LOG.isDebugEnabled()) { 910 tables.forEach(table -> LOG.debug("Removing: " + table)); 911 } 912 913 Map<TableName, Long> existingTables = getContinuousBackupTableSet(); 914 Set<TableName> toRemove = 915 tables.stream().filter(existingTables::containsKey).collect(Collectors.toSet()); 916 917 if (toRemove.isEmpty()) { 918 LOG.debug("No matching tables found to remove from continuous backup set."); 919 return; 920 } 921 922 try (Table table = connection.getTable(tableName)) { 923 Delete delete = createDeleteForContinuousBackupTableSet(toRemove); 924 table.delete(delete); 925 } 926 } 927 928 /** 929 * Updates the latest replicated WAL timestamp for a region server in the backup system table. 930 * This is used to track the replication checkpoint for continuous backup and PITR (Point-in-Time 931 * Restore). 932 * @param serverName the server for which the latest WAL timestamp is being recorded 933 * @param timestamp the timestamp (in milliseconds) of the last WAL entry replicated 934 * @throws IOException if an error occurs while writing to the backup system table 935 */ 936 public void updateBackupCheckpointTimestamp(ServerName serverName, long timestamp) 937 throws IOException { 938 939 HBaseProtos.ServerName.Builder serverProto = 940 HBaseProtos.ServerName.newBuilder().setHostName(serverName.getHostname()) 941 .setPort(serverName.getPort()).setStartCode(serverName.getStartCode()); 942 943 try (Table table = connection.getTable(tableName)) { 944 Put put = createPutForBackupCheckpoint(serverProto.build().toByteArray(), timestamp); 945 if (!put.isEmpty()) { 946 table.put(put); 947 } 948 } 949 } 950 951 /** 952 * Retrieves the latest replicated WAL timestamps for all region servers from the backup system 953 * table. This is used to track the replication checkpoint state for continuous backup and PITR 954 * (Point-in-Time Restore). 955 * @return a map where the key is {@link ServerName} and the value is the latest replicated WAL 956 * timestamp in milliseconds 957 * @throws IOException if an error occurs while reading from the backup system table 958 */ 959 public Map<ServerName, Long> getBackupCheckpointTimestamps() throws IOException { 960 LOG.trace("Fetching latest backup checkpoint timestamps for all region servers."); 961 962 Map<ServerName, Long> checkpointMap = new HashMap<>(); 963 964 byte[] rowKey = rowkey(CONTINUOUS_BACKUP_REPLICATION_TIMESTAMP_ROW); 965 Get get = new Get(rowKey); 966 get.addFamily(BackupSystemTable.META_FAMILY); 967 968 try (Table table = connection.getTable(tableName)) { 969 Result result = table.get(get); 970 971 if (result.isEmpty()) { 972 LOG.debug("No checkpoint timestamps found in backup system table."); 973 return checkpointMap; 974 } 975 976 List<Cell> cells = result.listCells(); 977 for (Cell cell : cells) { 978 try { 979 HBaseProtos.ServerName protoServer = 980 HBaseProtos.ServerName.parseFrom(CellUtil.cloneQualifier(cell)); 981 ServerName serverName = ServerName.valueOf(protoServer.getHostName(), 982 protoServer.getPort(), protoServer.getStartCode()); 983 984 long timestamp = Bytes.toLong(CellUtil.cloneValue(cell)); 985 checkpointMap.put(serverName, timestamp); 986 } catch (IllegalArgumentException e) { 987 LOG.warn("Failed to parse server name or timestamp from cell: {}", cell, e); 988 } 989 } 990 } 991 992 return checkpointMap; 993 } 994 995 /** 996 * Constructs a {@link Put} operation to update the last replicated WAL timestamp for a given 997 * server in the backup system table. 998 * @param serverNameBytes the serialized server name as bytes 999 * @param timestamp the WAL entry timestamp to store 1000 * @return a {@link Put} object ready to be written to the system table 1001 */ 1002 private Put createPutForBackupCheckpoint(byte[] serverNameBytes, long timestamp) { 1003 Put put = new Put(rowkey(CONTINUOUS_BACKUP_REPLICATION_TIMESTAMP_ROW)); 1004 put.addColumn(BackupSystemTable.META_FAMILY, serverNameBytes, Bytes.toBytes(timestamp)); 1005 return put; 1006 } 1007 1008 /** 1009 * Deletes incremental backup set for a backup destination 1010 * @param backupRoot backup root 1011 */ 1012 public void deleteIncrementalBackupTableSet(String backupRoot) throws IOException { 1013 if (LOG.isTraceEnabled()) { 1014 LOG.trace("Delete incremental backup table set to backup system table. ROOT=" + backupRoot); 1015 } 1016 try (Table table = connection.getTable(tableName)) { 1017 Delete delete = createDeleteForIncrBackupTableSet(backupRoot); 1018 table.delete(delete); 1019 } 1020 } 1021 1022 /** 1023 * Checks if we have at least one backup session in backup system table This API is used by 1024 * BackupLogCleaner 1025 * @return true, if at least one session exists in backup system table 1026 * @throws IOException exception 1027 */ 1028 public boolean hasBackupSessions() throws IOException { 1029 LOG.trace("Has backup sessions from backup system table"); 1030 1031 Scan scan = createScanForBackupHistory(Order.OLD_TO_NEW); 1032 scan.setCaching(1); 1033 try (Table table = connection.getTable(tableName); 1034 ResultScanner scanner = table.getScanner(scan)) { 1035 return scanner.next() != null; 1036 } 1037 } 1038 1039 /** 1040 * BACKUP SETS 1041 */ 1042 1043 /** 1044 * Get backup set list 1045 * @return backup set list 1046 * @throws IOException if a table or scanner operation fails 1047 */ 1048 public List<String> listBackupSets() throws IOException { 1049 LOG.trace("Backup set list"); 1050 1051 List<String> list = new ArrayList<>(); 1052 try (Table table = connection.getTable(tableName)) { 1053 Scan scan = createScanForBackupSetList(); 1054 scan.readVersions(1); 1055 try (ResultScanner scanner = table.getScanner(scan)) { 1056 Result res; 1057 while ((res = scanner.next()) != null) { 1058 res.advance(); 1059 list.add(cellKeyToBackupSetName(res.current())); 1060 } 1061 return list; 1062 } 1063 } 1064 } 1065 1066 /** 1067 * Get backup set description (list of tables) 1068 * @param name set's name 1069 * @return list of tables in a backup set 1070 * @throws IOException if a table operation fails 1071 */ 1072 public List<TableName> describeBackupSet(String name) throws IOException { 1073 if (LOG.isTraceEnabled()) { 1074 LOG.trace(" Backup set describe: " + name); 1075 } 1076 try (Table table = connection.getTable(tableName)) { 1077 Get get = createGetForBackupSet(name); 1078 Result res = table.get(get); 1079 if (res.isEmpty()) { 1080 return null; 1081 } 1082 res.advance(); 1083 String[] tables = cellValueToBackupSet(res.current()); 1084 return Arrays.asList(tables).stream().map(item -> TableName.valueOf(item)) 1085 .collect(Collectors.toList()); 1086 } 1087 } 1088 1089 /** 1090 * Add backup set (list of tables) 1091 * @param name set name 1092 * @param newTables list of tables, comma-separated 1093 * @throws IOException if a table operation fails 1094 */ 1095 public void addToBackupSet(String name, String[] newTables) throws IOException { 1096 if (LOG.isTraceEnabled()) { 1097 LOG.trace("Backup set add: " + name + " tables [" + StringUtils.join(newTables, " ") + "]"); 1098 } 1099 String[] union = null; 1100 try (Table table = connection.getTable(tableName)) { 1101 Get get = createGetForBackupSet(name); 1102 Result res = table.get(get); 1103 if (res.isEmpty()) { 1104 union = newTables; 1105 } else { 1106 res.advance(); 1107 String[] tables = cellValueToBackupSet(res.current()); 1108 union = merge(tables, newTables); 1109 } 1110 Put put = createPutForBackupSet(name, union); 1111 table.put(put); 1112 } 1113 } 1114 1115 /** 1116 * Remove tables from backup set (list of tables) 1117 * @param name set name 1118 * @param toRemove list of tables 1119 * @throws IOException if a table operation or deleting the backup set fails 1120 */ 1121 public void removeFromBackupSet(String name, String[] toRemove) throws IOException { 1122 if (LOG.isTraceEnabled()) { 1123 LOG.trace( 1124 " Backup set remove from : " + name + " tables [" + StringUtils.join(toRemove, " ") + "]"); 1125 } 1126 String[] disjoint; 1127 String[] tables; 1128 try (Table table = connection.getTable(tableName)) { 1129 Get get = createGetForBackupSet(name); 1130 Result res = table.get(get); 1131 if (res.isEmpty()) { 1132 LOG.warn("Backup set '" + name + "' not found."); 1133 return; 1134 } else { 1135 res.advance(); 1136 tables = cellValueToBackupSet(res.current()); 1137 disjoint = disjoin(tables, toRemove); 1138 } 1139 if (disjoint.length > 0 && disjoint.length != tables.length) { 1140 Put put = createPutForBackupSet(name, disjoint); 1141 table.put(put); 1142 } else if (disjoint.length == tables.length) { 1143 LOG.warn("Backup set '" + name + "' does not contain tables [" 1144 + StringUtils.join(toRemove, " ") + "]"); 1145 } else { // disjoint.length == 0 and tables.length >0 1146 // Delete backup set 1147 LOG.info("Backup set '" + name + "' is empty. Deleting."); 1148 deleteBackupSet(name); 1149 } 1150 } 1151 } 1152 1153 private String[] merge(String[] existingTables, String[] newTables) { 1154 Set<String> tables = new HashSet<>(Arrays.asList(existingTables)); 1155 tables.addAll(Arrays.asList(newTables)); 1156 return tables.toArray(new String[0]); 1157 } 1158 1159 private String[] disjoin(String[] existingTables, String[] toRemove) { 1160 Set<String> tables = new HashSet<>(Arrays.asList(existingTables)); 1161 Arrays.asList(toRemove).forEach(table -> tables.remove(table)); 1162 return tables.toArray(new String[0]); 1163 } 1164 1165 /** 1166 * Delete backup set 1167 * @param name set's name 1168 * @throws IOException if getting or deleting the table fails 1169 */ 1170 public void deleteBackupSet(String name) throws IOException { 1171 if (LOG.isTraceEnabled()) { 1172 LOG.trace(" Backup set delete: " + name); 1173 } 1174 try (Table table = connection.getTable(tableName)) { 1175 Delete del = createDeleteForBackupSet(name); 1176 table.delete(del); 1177 } 1178 } 1179 1180 /** 1181 * Get backup system table descriptor 1182 * @return table's descriptor 1183 */ 1184 public static TableDescriptor getSystemTableDescriptor(Configuration conf) { 1185 TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(getTableName(conf)); 1186 1187 ColumnFamilyDescriptorBuilder colBuilder = 1188 ColumnFamilyDescriptorBuilder.newBuilder(SESSIONS_FAMILY); 1189 1190 colBuilder.setMaxVersions(1); 1191 Configuration config = HBaseConfiguration.create(); 1192 int ttl = config.getInt(BackupRestoreConstants.BACKUP_SYSTEM_TTL_KEY, 1193 BackupRestoreConstants.BACKUP_SYSTEM_TTL_DEFAULT); 1194 colBuilder.setTimeToLive(ttl); 1195 1196 ColumnFamilyDescriptor colSessionsDesc = colBuilder.build(); 1197 builder.setColumnFamily(colSessionsDesc); 1198 1199 colBuilder = ColumnFamilyDescriptorBuilder.newBuilder(META_FAMILY); 1200 colBuilder.setTimeToLive(ttl); 1201 builder.setColumnFamily(colBuilder.build()); 1202 return builder.build(); 1203 } 1204 1205 public static TableName getTableName(Configuration conf) { 1206 String name = conf.get(BackupRestoreConstants.BACKUP_SYSTEM_TABLE_NAME_KEY, 1207 BackupRestoreConstants.BACKUP_SYSTEM_TABLE_NAME_DEFAULT); 1208 return TableName.valueOf(name); 1209 } 1210 1211 public static String getTableNameAsString(Configuration conf) { 1212 return getTableName(conf).getNameAsString(); 1213 } 1214 1215 public static String getSnapshotName(Configuration conf) { 1216 return "snapshot_" + getTableNameAsString(conf).replace(":", "_"); 1217 } 1218 1219 /** 1220 * Get backup system table descriptor 1221 * @return table's descriptor 1222 */ 1223 public static TableDescriptor getSystemTableForBulkLoadedDataDescriptor(Configuration conf) { 1224 TableDescriptorBuilder builder = 1225 TableDescriptorBuilder.newBuilder(getTableNameForBulkLoadedData(conf)); 1226 1227 ColumnFamilyDescriptorBuilder colBuilder = 1228 ColumnFamilyDescriptorBuilder.newBuilder(SESSIONS_FAMILY); 1229 colBuilder.setMaxVersions(1); 1230 Configuration config = HBaseConfiguration.create(); 1231 int ttl = config.getInt(BackupRestoreConstants.BACKUP_SYSTEM_TTL_KEY, 1232 BackupRestoreConstants.BACKUP_SYSTEM_TTL_DEFAULT); 1233 colBuilder.setTimeToLive(ttl); 1234 ColumnFamilyDescriptor colSessionsDesc = colBuilder.build(); 1235 builder.setColumnFamily(colSessionsDesc); 1236 colBuilder = ColumnFamilyDescriptorBuilder.newBuilder(META_FAMILY); 1237 colBuilder.setTimeToLive(ttl); 1238 builder.setColumnFamily(colBuilder.build()); 1239 return builder.build(); 1240 } 1241 1242 public static TableName getTableNameForBulkLoadedData(Configuration conf) { 1243 String name = conf.get(BackupRestoreConstants.BACKUP_SYSTEM_TABLE_NAME_KEY, 1244 BackupRestoreConstants.BACKUP_SYSTEM_TABLE_NAME_DEFAULT) + "_bulk"; 1245 return TableName.valueOf(name); 1246 } 1247 1248 /** 1249 * Creates Put operation for a given backup info object 1250 * @param context backup info 1251 * @return put operation 1252 * @throws IOException exception 1253 */ 1254 private Put createPutForBackupInfo(BackupInfo context) throws IOException { 1255 Put put = new Put(rowkey(BACKUP_INFO_PREFIX, context.getBackupId())); 1256 put.addColumn(BackupSystemTable.SESSIONS_FAMILY, Bytes.toBytes("context"), 1257 context.toByteArray()); 1258 return put; 1259 } 1260 1261 /** 1262 * Creates Get operation for a given backup id 1263 * @param backupId backup's ID 1264 * @return get operation 1265 * @throws IOException exception 1266 */ 1267 private Get createGetForBackupInfo(String backupId) throws IOException { 1268 Get get = new Get(rowkey(BACKUP_INFO_PREFIX, backupId)); 1269 get.addFamily(BackupSystemTable.SESSIONS_FAMILY); 1270 get.readVersions(1); 1271 return get; 1272 } 1273 1274 /** 1275 * Creates Delete operation for a given backup id 1276 * @param backupId backup's ID 1277 * @return delete operation 1278 */ 1279 private Delete createDeleteForBackupInfo(String backupId) { 1280 Delete del = new Delete(rowkey(BACKUP_INFO_PREFIX, backupId)); 1281 del.addFamily(BackupSystemTable.SESSIONS_FAMILY); 1282 return del; 1283 } 1284 1285 /** 1286 * Converts Result to BackupInfo 1287 * @param res HBase result 1288 * @return backup info instance 1289 * @throws IOException exception 1290 */ 1291 private BackupInfo resultToBackupInfo(Result res) throws IOException { 1292 res.advance(); 1293 Cell cell = res.current(); 1294 return cellToBackupInfo(cell); 1295 } 1296 1297 /** 1298 * Creates Get to retrieve incremental backup table set from backup system table 1299 * @return get operation 1300 * @throws IOException exception 1301 */ 1302 private Get createGetForIncrBackupTableSet(String backupRoot) throws IOException { 1303 Get get = new Get(rowkey(INCR_BACKUP_SET, backupRoot)); 1304 get.addFamily(BackupSystemTable.META_FAMILY); 1305 get.readVersions(1); 1306 return get; 1307 } 1308 1309 /** 1310 * Creates a Get operation to retrieve the continuous backup table set from the backup system 1311 * table. 1312 * @return a Get operation for retrieving the table set 1313 */ 1314 private Get createGetForContinuousBackupTableSet() throws IOException { 1315 Get get = new Get(rowkey(CONTINUOUS_BACKUP_SET)); 1316 get.addFamily(BackupSystemTable.META_FAMILY); 1317 get.readVersions(1); 1318 return get; 1319 } 1320 1321 /** 1322 * Creates Put to store incremental backup table set 1323 * @param tables tables 1324 * @return put operation 1325 */ 1326 private Put createPutForIncrBackupTableSet(Set<TableName> tables, String backupRoot) { 1327 Put put = new Put(rowkey(INCR_BACKUP_SET, backupRoot)); 1328 for (TableName table : tables) { 1329 put.addColumn(BackupSystemTable.META_FAMILY, Bytes.toBytes(table.getNameAsString()), 1330 EMPTY_VALUE); 1331 } 1332 return put; 1333 } 1334 1335 /** 1336 * Creates a Put operation to store the continuous backup table set. Only includes tables that are 1337 * not already in the set. 1338 * @param tables tables to add 1339 * @param existingTables tables that already have continuous backup enabled 1340 * @param startTimestamp timestamp indicating when continuous backup started 1341 * @return put operation 1342 */ 1343 private Put createPutForContinuousBackupTableSet(Set<TableName> tables, 1344 Map<TableName, Long> existingTables, long startTimestamp) { 1345 Put put = new Put(rowkey(CONTINUOUS_BACKUP_SET)); 1346 1347 for (TableName table : tables) { 1348 if (!existingTables.containsKey(table)) { 1349 put.addColumn(BackupSystemTable.META_FAMILY, Bytes.toBytes(table.getNameAsString()), 1350 Bytes.toBytes(startTimestamp)); 1351 } 1352 } 1353 1354 return put; 1355 } 1356 1357 /** 1358 * Creates Delete for incremental backup table set 1359 * @param backupRoot backup root 1360 * @return delete operation 1361 */ 1362 private Delete createDeleteForIncrBackupTableSet(String backupRoot) { 1363 Delete delete = new Delete(rowkey(INCR_BACKUP_SET, backupRoot)); 1364 delete.addFamily(BackupSystemTable.META_FAMILY); 1365 return delete; 1366 } 1367 1368 /** 1369 * Creates Delete for continuous backup table set 1370 * @param tables tables to remove 1371 * @return delete operation 1372 */ 1373 private Delete createDeleteForContinuousBackupTableSet(Set<TableName> tables) { 1374 Delete delete = new Delete(rowkey(CONTINUOUS_BACKUP_SET)); 1375 for (TableName tableName : tables) { 1376 delete.addColumn(META_FAMILY, Bytes.toBytes(tableName.getNameAsString())); 1377 } 1378 return delete; 1379 } 1380 1381 /** 1382 * Creates Scan operation to load backup history 1383 * @param order order of the scan results 1384 * @return scan operation 1385 */ 1386 private Scan createScanForBackupHistory(Order order) { 1387 Scan scan = new Scan(); 1388 byte[] startRow = Bytes.toBytes(BACKUP_INFO_PREFIX); 1389 if (order == Order.NEW_TO_OLD) { 1390 byte[] stopRow = Arrays.copyOf(startRow, startRow.length); 1391 stopRow[stopRow.length - 1] = (byte) (stopRow[stopRow.length - 1] + 1); 1392 scan.setReversed(true); 1393 scan.withStartRow(stopRow, false); 1394 scan.withStopRow(startRow); 1395 } else if (order == Order.OLD_TO_NEW) { 1396 scan.setStartStopRowForPrefixScan(startRow); 1397 } else { 1398 throw new IllegalArgumentException("Unsupported order: " + order); 1399 } 1400 scan.addFamily(BackupSystemTable.SESSIONS_FAMILY); 1401 scan.readVersions(1); 1402 return scan; 1403 } 1404 1405 /** 1406 * Converts cell to backup info instance. 1407 * @param current current cell 1408 * @return backup backup info instance 1409 * @throws IOException exception 1410 */ 1411 private BackupInfo cellToBackupInfo(Cell current) throws IOException { 1412 byte[] data = CellUtil.cloneValue(current); 1413 return BackupInfo.fromByteArray(data); 1414 } 1415 1416 /** 1417 * Creates Put to write RS last roll log timestamp map 1418 * @param table table 1419 * @param smap map, containing RS:ts 1420 * @return put operation 1421 */ 1422 private Put createPutForWriteRegionServerLogTimestamp(TableName table, byte[] smap, 1423 String backupRoot) { 1424 Put put = new Put(rowkey(TABLE_RS_LOG_MAP_PREFIX, backupRoot, NULL, table.getNameAsString())); 1425 put.addColumn(BackupSystemTable.META_FAMILY, Bytes.toBytes("log-roll-map"), smap); 1426 return put; 1427 } 1428 1429 /** 1430 * Creates Scan to load table-> { RS -> ts} map of maps 1431 * @return scan operation 1432 */ 1433 private Scan createScanForReadLogTimestampMap(String backupRoot) { 1434 Scan scan = new Scan(); 1435 scan.setStartStopRowForPrefixScan(rowkey(TABLE_RS_LOG_MAP_PREFIX, backupRoot, NULL)); 1436 scan.addFamily(BackupSystemTable.META_FAMILY); 1437 1438 return scan; 1439 } 1440 1441 /** 1442 * Get table name from rowkey 1443 * @param cloneRow rowkey 1444 * @return table name 1445 */ 1446 private String getTableNameForReadLogTimestampMap(byte[] cloneRow) { 1447 String s = Bytes.toString(cloneRow); 1448 int index = s.lastIndexOf(NULL); 1449 return s.substring(index + 1); 1450 } 1451 1452 /** 1453 * Creates Put to store RS last log result 1454 * @param server server name 1455 * @param timestamp log roll result (timestamp) 1456 * @return put operation 1457 */ 1458 private Put createPutForRegionServerLastLogRollResult(String server, Long timestamp, 1459 String backupRoot) { 1460 Put put = new Put(rowkey(RS_LOG_TS_PREFIX, backupRoot, NULL, server)); 1461 put.addColumn(BackupSystemTable.META_FAMILY, Bytes.toBytes("rs-log-ts"), 1462 Bytes.toBytes(timestamp)); 1463 return put; 1464 } 1465 1466 /** 1467 * Creates Scan operation to load last RS log roll results 1468 * @return scan operation 1469 */ 1470 private Scan createScanForReadRegionServerLastLogRollResult(String backupRoot) { 1471 Scan scan = new Scan(); 1472 scan.setStartStopRowForPrefixScan(rowkey(RS_LOG_TS_PREFIX, backupRoot, NULL)); 1473 scan.addFamily(BackupSystemTable.META_FAMILY); 1474 scan.readVersions(1); 1475 1476 return scan; 1477 } 1478 1479 /** 1480 * Get server's name from rowkey 1481 * @param row rowkey 1482 * @return server's name 1483 */ 1484 private String getServerNameForReadRegionServerLastLogRollResult(byte[] row) { 1485 String s = Bytes.toString(row); 1486 int index = s.lastIndexOf(NULL); 1487 return s.substring(index + 1); 1488 } 1489 1490 /** 1491 * Creates Put's for bulk loads. 1492 */ 1493 private static List<Put> createPutForBulkLoad(TableName table, byte[] region, 1494 Map<byte[], List<Path>> columnFamilyToHFilePaths) { 1495 List<Put> puts = new ArrayList<>(); 1496 for (Map.Entry<byte[], List<Path>> entry : columnFamilyToHFilePaths.entrySet()) { 1497 for (Path path : entry.getValue()) { 1498 String file = path.toString(); 1499 int lastSlash = file.lastIndexOf("/"); 1500 String filename = file.substring(lastSlash + 1); 1501 Put put = new Put(rowkey(BULK_LOAD_PREFIX, table.toString(), BLK_LD_DELIM, 1502 Bytes.toString(region), BLK_LD_DELIM, filename)); 1503 put.addColumn(BackupSystemTable.META_FAMILY, TBL_COL, table.getName()); 1504 put.addColumn(BackupSystemTable.META_FAMILY, FAM_COL, entry.getKey()); 1505 put.addColumn(BackupSystemTable.META_FAMILY, PATH_COL, Bytes.toBytes(file)); 1506 puts.add(put); 1507 LOG.debug("Done writing bulk path {} for {} {}", file, table, Bytes.toString(region)); 1508 } 1509 } 1510 return puts; 1511 } 1512 1513 public static void snapshot(Connection conn) throws IOException { 1514 try (Admin admin = conn.getAdmin()) { 1515 Configuration conf = conn.getConfiguration(); 1516 admin.snapshot(BackupSystemTable.getSnapshotName(conf), BackupSystemTable.getTableName(conf)); 1517 } 1518 } 1519 1520 public static void restoreFromSnapshot(Connection conn) throws IOException { 1521 Configuration conf = conn.getConfiguration(); 1522 LOG.debug("Restoring " + BackupSystemTable.getTableNameAsString(conf) + " from snapshot"); 1523 try (Admin admin = conn.getAdmin()) { 1524 String snapshotName = BackupSystemTable.getSnapshotName(conf); 1525 if (snapshotExists(admin, snapshotName)) { 1526 admin.restoreBackupSystemTable(snapshotName); 1527 LOG.debug("Done restoring backup system table"); 1528 } else { 1529 // Snapshot does not exists, i.e completeBackup failed after 1530 // deleting backup system table snapshot 1531 // In this case we log WARN and proceed 1532 LOG.warn( 1533 "Could not restore backup system table. Snapshot " + snapshotName + " does not exists."); 1534 } 1535 } 1536 } 1537 1538 private static boolean snapshotExists(Admin admin, String snapshotName) throws IOException { 1539 List<SnapshotDescription> list = admin.listSnapshots(); 1540 for (SnapshotDescription desc : list) { 1541 if (desc.getName().equals(snapshotName)) { 1542 return true; 1543 } 1544 } 1545 return false; 1546 } 1547 1548 public static boolean snapshotExists(Connection conn) throws IOException { 1549 return snapshotExists(conn.getAdmin(), getSnapshotName(conn.getConfiguration())); 1550 } 1551 1552 public static void deleteSnapshot(Connection conn) throws IOException { 1553 Configuration conf = conn.getConfiguration(); 1554 LOG.debug("Deleting " + BackupSystemTable.getSnapshotName(conf) + " from the system"); 1555 try (Admin admin = conn.getAdmin()) { 1556 String snapshotName = BackupSystemTable.getSnapshotName(conf); 1557 if (snapshotExists(admin, snapshotName)) { 1558 admin.deleteSnapshot(snapshotName); 1559 LOG.debug("Done deleting backup system table snapshot"); 1560 } else { 1561 LOG.error("Snapshot " + snapshotName + " does not exists"); 1562 } 1563 } 1564 } 1565 1566 private Put createPutForDeleteOperation(String[] backupIdList) { 1567 byte[] value = Bytes.toBytes(StringUtils.join(backupIdList, ",")); 1568 Put put = new Put(DELETE_OP_ROW); 1569 put.addColumn(META_FAMILY, FAM_COL, value); 1570 return put; 1571 } 1572 1573 private Delete createDeleteForBackupDeleteOperation() { 1574 Delete delete = new Delete(DELETE_OP_ROW); 1575 delete.addFamily(META_FAMILY); 1576 return delete; 1577 } 1578 1579 private Get createGetForDeleteOperation() { 1580 Get get = new Get(DELETE_OP_ROW); 1581 get.addFamily(META_FAMILY); 1582 return get; 1583 } 1584 1585 public void startDeleteOperation(String[] backupIdList) throws IOException { 1586 if (LOG.isTraceEnabled()) { 1587 LOG.trace("Start delete operation for backups: " + StringUtils.join(backupIdList)); 1588 } 1589 Put put = createPutForDeleteOperation(backupIdList); 1590 try (Table table = connection.getTable(tableName)) { 1591 table.put(put); 1592 } 1593 } 1594 1595 public void finishDeleteOperation() throws IOException { 1596 LOG.trace("Finsih delete operation for backup ids"); 1597 1598 Delete delete = createDeleteForBackupDeleteOperation(); 1599 try (Table table = connection.getTable(tableName)) { 1600 table.delete(delete); 1601 } 1602 } 1603 1604 public String[] getListOfBackupIdsFromDeleteOperation() throws IOException { 1605 LOG.trace("Get delete operation for backup ids"); 1606 1607 Get get = createGetForDeleteOperation(); 1608 try (Table table = connection.getTable(tableName)) { 1609 Result res = table.get(get); 1610 if (res.isEmpty()) { 1611 return null; 1612 } 1613 Cell cell = res.listCells().get(0); 1614 byte[] val = CellUtil.cloneValue(cell); 1615 if (val.length == 0) { 1616 return null; 1617 } 1618 return Splitter.on(',').splitToStream(new String(val, StandardCharsets.UTF_8)) 1619 .toArray(String[]::new); 1620 } 1621 } 1622 1623 private Put createPutForMergeOperation(String[] backupIdList) { 1624 byte[] value = Bytes.toBytes(StringUtils.join(backupIdList, ",")); 1625 Put put = new Put(MERGE_OP_ROW); 1626 put.addColumn(META_FAMILY, FAM_COL, value); 1627 return put; 1628 } 1629 1630 public boolean isMergeInProgress() throws IOException { 1631 Get get = new Get(MERGE_OP_ROW); 1632 try (Table table = connection.getTable(tableName)) { 1633 Result res = table.get(get); 1634 return !res.isEmpty(); 1635 } 1636 } 1637 1638 private Put createPutForUpdateTablesForMerge(List<TableName> tables) { 1639 byte[] value = Bytes.toBytes(StringUtils.join(tables, ",")); 1640 Put put = new Put(MERGE_OP_ROW); 1641 put.addColumn(META_FAMILY, PATH_COL, value); 1642 return put; 1643 } 1644 1645 private Delete createDeleteForBackupMergeOperation() { 1646 Delete delete = new Delete(MERGE_OP_ROW); 1647 delete.addFamily(META_FAMILY); 1648 return delete; 1649 } 1650 1651 private Get createGetForMergeOperation() { 1652 Get get = new Get(MERGE_OP_ROW); 1653 get.addFamily(META_FAMILY); 1654 return get; 1655 } 1656 1657 public void startMergeOperation(String[] backupIdList) throws IOException { 1658 if (LOG.isTraceEnabled()) { 1659 LOG.trace("Start merge operation for backups: " + StringUtils.join(backupIdList)); 1660 } 1661 Put put = createPutForMergeOperation(backupIdList); 1662 try (Table table = connection.getTable(tableName)) { 1663 table.put(put); 1664 } 1665 } 1666 1667 public void updateProcessedTablesForMerge(List<TableName> tables) throws IOException { 1668 if (LOG.isTraceEnabled()) { 1669 LOG.trace("Update tables for merge : " + StringUtils.join(tables, ",")); 1670 } 1671 Put put = createPutForUpdateTablesForMerge(tables); 1672 try (Table table = connection.getTable(tableName)) { 1673 table.put(put); 1674 } 1675 } 1676 1677 public void finishMergeOperation() throws IOException { 1678 LOG.trace("Finish merge operation for backup ids"); 1679 1680 Delete delete = createDeleteForBackupMergeOperation(); 1681 try (Table table = connection.getTable(tableName)) { 1682 table.delete(delete); 1683 } 1684 } 1685 1686 public String[] getListOfBackupIdsFromMergeOperation() throws IOException { 1687 LOG.trace("Get backup ids for merge operation"); 1688 1689 Get get = createGetForMergeOperation(); 1690 try (Table table = connection.getTable(tableName)) { 1691 Result res = table.get(get); 1692 if (res.isEmpty()) { 1693 return null; 1694 } 1695 Cell cell = res.listCells().get(0); 1696 byte[] val = CellUtil.cloneValue(cell); 1697 if (val.length == 0) { 1698 return null; 1699 } 1700 return Splitter.on(',').splitToStream(new String(val, StandardCharsets.UTF_8)) 1701 .toArray(String[]::new); 1702 } 1703 } 1704 1705 /** 1706 * Creates a scan to read all registered bulk loads for the given table, or for all tables if 1707 * {@code table} is {@code null}. 1708 */ 1709 static Scan createScanForOrigBulkLoadedFiles(@Nullable TableName table) { 1710 Scan scan = new Scan(); 1711 byte[] startRow = table == null 1712 ? BULK_LOAD_PREFIX_BYTES 1713 : rowkey(BULK_LOAD_PREFIX, table.toString(), BLK_LD_DELIM); 1714 byte[] stopRow = Arrays.copyOf(startRow, startRow.length); 1715 stopRow[stopRow.length - 1] = (byte) (stopRow[stopRow.length - 1] + 1); 1716 scan.withStartRow(startRow); 1717 scan.withStopRow(stopRow); 1718 scan.addFamily(BackupSystemTable.META_FAMILY); 1719 scan.readVersions(1); 1720 return scan; 1721 } 1722 1723 static String getTableNameFromOrigBulkLoadRow(String rowStr) { 1724 // format is bulk : namespace : table : region : file 1725 return Iterators.get(Splitter.onPattern(BLK_LD_DELIM).split(rowStr).iterator(), 1); 1726 } 1727 1728 static String getRegionNameFromOrigBulkLoadRow(String rowStr) { 1729 // format is bulk : namespace : table : region : file 1730 List<String> parts = Splitter.onPattern(BLK_LD_DELIM).splitToList(rowStr); 1731 Iterator<String> i = parts.iterator(); 1732 int idx = 3; 1733 if (parts.size() == 4) { 1734 // the table is in default namespace 1735 idx = 2; 1736 } 1737 String region = Iterators.get(i, idx); 1738 LOG.debug("bulk row string " + rowStr + " region " + region); 1739 return region; 1740 } 1741 1742 /* 1743 * Used to query bulk loaded hfiles which have been copied by incremental backup 1744 * @param backupId the backup Id. It can be null when querying for all tables 1745 * @return the Scan object 1746 * @deprecated This method is broken if a backupId is specified - see HBASE-28715 1747 */ 1748 static Scan createScanForBulkLoadedFiles(String backupId) { 1749 Scan scan = new Scan(); 1750 byte[] startRow = 1751 backupId == null ? BULK_LOAD_PREFIX_BYTES : rowkey(BULK_LOAD_PREFIX, backupId + BLK_LD_DELIM); 1752 byte[] stopRow = Arrays.copyOf(startRow, startRow.length); 1753 stopRow[stopRow.length - 1] = (byte) (stopRow[stopRow.length - 1] + 1); 1754 scan.withStartRow(startRow); 1755 scan.withStopRow(stopRow); 1756 scan.addFamily(BackupSystemTable.META_FAMILY); 1757 scan.readVersions(1); 1758 return scan; 1759 } 1760 1761 /** 1762 * Creates Scan operation to load backup set list 1763 * @return scan operation 1764 */ 1765 private Scan createScanForBackupSetList() { 1766 Scan scan = new Scan(); 1767 byte[] startRow = Bytes.toBytes(SET_KEY_PREFIX); 1768 byte[] stopRow = Arrays.copyOf(startRow, startRow.length); 1769 stopRow[stopRow.length - 1] = (byte) (stopRow[stopRow.length - 1] + 1); 1770 scan.withStartRow(startRow); 1771 scan.withStopRow(stopRow); 1772 scan.addFamily(BackupSystemTable.META_FAMILY); 1773 return scan; 1774 } 1775 1776 /** 1777 * Creates Get operation to load backup set content 1778 * @return get operation 1779 */ 1780 private Get createGetForBackupSet(String name) { 1781 Get get = new Get(rowkey(SET_KEY_PREFIX, name)); 1782 get.addFamily(BackupSystemTable.META_FAMILY); 1783 return get; 1784 } 1785 1786 /** 1787 * Creates Delete operation to delete backup set content 1788 * @param name backup set's name 1789 * @return delete operation 1790 */ 1791 private Delete createDeleteForBackupSet(String name) { 1792 Delete del = new Delete(rowkey(SET_KEY_PREFIX, name)); 1793 del.addFamily(BackupSystemTable.META_FAMILY); 1794 return del; 1795 } 1796 1797 /** 1798 * Creates Put operation to update backup set content 1799 * @param name backup set's name 1800 * @param tables list of tables 1801 * @return put operation 1802 */ 1803 private Put createPutForBackupSet(String name, String[] tables) { 1804 Put put = new Put(rowkey(SET_KEY_PREFIX, name)); 1805 byte[] value = convertToByteArray(tables); 1806 put.addColumn(BackupSystemTable.META_FAMILY, Bytes.toBytes("tables"), value); 1807 return put; 1808 } 1809 1810 private byte[] convertToByteArray(String[] tables) { 1811 return Bytes.toBytes(StringUtils.join(tables, ",")); 1812 } 1813 1814 /** 1815 * Converts cell to backup set list. 1816 * @param current current cell 1817 * @return backup set as array of table names 1818 */ 1819 private String[] cellValueToBackupSet(Cell current) { 1820 byte[] data = CellUtil.cloneValue(current); 1821 if (!ArrayUtils.isEmpty(data)) { 1822 return Bytes.toString(data).split(","); 1823 } 1824 return new String[0]; 1825 } 1826 1827 /** 1828 * Converts cell key to backup set name. 1829 * @param current current cell 1830 * @return backup set name 1831 */ 1832 private String cellKeyToBackupSetName(Cell current) { 1833 byte[] data = CellUtil.cloneRow(current); 1834 return Bytes.toString(data).substring(SET_KEY_PREFIX.length()); 1835 } 1836 1837 private static byte[] rowkey(String s, String... other) { 1838 StringBuilder sb = new StringBuilder(s); 1839 for (String ss : other) { 1840 sb.append(ss); 1841 } 1842 return Bytes.toBytes(sb.toString()); 1843 } 1844 1845 private static void ensureTableEnabled(Admin admin, TableName tableName) throws IOException { 1846 if (!admin.isTableEnabled(tableName)) { 1847 try { 1848 admin.enableTable(tableName); 1849 } catch (TableNotDisabledException ignored) { 1850 LOG.info("Table {} is not disabled, ignoring enable request", tableName); 1851 } 1852 } 1853 } 1854 1855 public enum Order { 1856 /** 1857 * Old backups first, most recents last. I.e. sorted by ascending backupId. 1858 */ 1859 OLD_TO_NEW, 1860 /** 1861 * New backups first, oldest last. I.e. sorted by descending backupId. 1862 */ 1863 NEW_TO_OLD 1864 } 1865}