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 java.io.IOException; 021import java.util.ArrayList; 022import java.util.Collections; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026import java.util.Map.Entry; 027import java.util.TreeMap; 028import org.apache.hadoop.conf.Configuration; 029import org.apache.hadoop.fs.FSDataInputStream; 030import org.apache.hadoop.fs.FSDataOutputStream; 031import org.apache.hadoop.fs.FileStatus; 032import org.apache.hadoop.fs.FileSystem; 033import org.apache.hadoop.fs.Path; 034import org.apache.hadoop.hbase.ServerName; 035import org.apache.hadoop.hbase.TableName; 036import org.apache.hadoop.hbase.backup.BackupInfo; 037import org.apache.hadoop.hbase.backup.BackupType; 038import org.apache.hadoop.hbase.backup.HBackupFileSystem; 039import org.apache.hadoop.hbase.backup.util.BackupUtils; 040import org.apache.yetus.audience.InterfaceAudience; 041import org.slf4j.Logger; 042import org.slf4j.LoggerFactory; 043 044import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 045import org.apache.hadoop.hbase.shaded.protobuf.generated.BackupProtos; 046import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; 047 048/** 049 * Backup manifest contains all the meta data of a backup image. The manifest info will be bundled 050 * as manifest file together with data. So that each backup image will contain all the info needed 051 * for restore. BackupManifest is a storage container for BackupImage. It is responsible for 052 * storing/reading backup image data and has some additional utility methods. 053 */ 054@InterfaceAudience.Private 055public class BackupManifest { 056 private static final Logger LOG = LoggerFactory.getLogger(BackupManifest.class); 057 058 // manifest file name 059 public static final String MANIFEST_FILE_NAME = ".backup.manifest"; 060 061 /** 062 * Backup image, the dependency graph is made up by series of backup images BackupImage contains 063 * all the relevant information to restore the backup and is used during restore operation 064 */ 065 public static class BackupImage implements Comparable<BackupImage> { 066 static class Builder { 067 BackupImage image; 068 069 Builder() { 070 image = new BackupImage(); 071 } 072 073 Builder withBackupId(String backupId) { 074 image.setBackupId(backupId); 075 return this; 076 } 077 078 Builder withType(BackupType type) { 079 image.setType(type); 080 return this; 081 } 082 083 Builder withRootDir(String rootDir) { 084 image.setRootDir(rootDir); 085 return this; 086 } 087 088 Builder withTableList(List<TableName> tableList) { 089 image.setTableList(tableList); 090 return this; 091 } 092 093 Builder withStartTime(long startTime) { 094 image.setStartTs(startTime); 095 return this; 096 } 097 098 Builder withCompleteTime(long completeTime) { 099 image.setCompleteTs(completeTime); 100 return this; 101 } 102 103 BackupImage build() { 104 return image; 105 } 106 107 } 108 109 private String backupId; 110 private BackupType type; 111 private String rootDir; 112 private List<TableName> tableList; 113 private long startTs; 114 private long completeTs; 115 private ArrayList<BackupImage> ancestors; 116 private Map<TableName, Map<String, Long>> incrTimeRanges; 117 118 static Builder newBuilder() { 119 return new Builder(); 120 } 121 122 public BackupImage() { 123 super(); 124 } 125 126 private BackupImage(String backupId, BackupType type, String rootDir, List<TableName> tableList, 127 long startTs, long completeTs) { 128 this.backupId = backupId; 129 this.type = type; 130 this.rootDir = rootDir; 131 this.tableList = tableList; 132 this.startTs = startTs; 133 this.completeTs = completeTs; 134 } 135 136 static BackupImage fromProto(BackupProtos.BackupImage im) { 137 String backupId = im.getBackupId(); 138 String rootDir = im.getBackupRootDir(); 139 long startTs = im.getStartTs(); 140 long completeTs = im.getCompleteTs(); 141 List<HBaseProtos.TableName> tableListList = im.getTableListList(); 142 List<TableName> tableList = new ArrayList<>(); 143 for (HBaseProtos.TableName tn : tableListList) { 144 tableList.add(ProtobufUtil.toTableName(tn)); 145 } 146 147 List<BackupProtos.BackupImage> ancestorList = im.getAncestorsList(); 148 149 BackupType type = im.getBackupType() == BackupProtos.BackupType.FULL 150 ? BackupType.FULL 151 : BackupType.INCREMENTAL; 152 153 BackupImage image = new BackupImage(backupId, type, rootDir, tableList, startTs, completeTs); 154 for (BackupProtos.BackupImage img : ancestorList) { 155 image.addAncestor(fromProto(img)); 156 } 157 image.setIncrTimeRanges(loadIncrementalTimestampMap(im)); 158 return image; 159 } 160 161 BackupProtos.BackupImage toProto() { 162 BackupProtos.BackupImage.Builder builder = BackupProtos.BackupImage.newBuilder(); 163 builder.setBackupId(backupId); 164 builder.setCompleteTs(completeTs); 165 builder.setStartTs(startTs); 166 builder.setBackupRootDir(rootDir); 167 if (type == BackupType.FULL) { 168 builder.setBackupType(BackupProtos.BackupType.FULL); 169 } else { 170 builder.setBackupType(BackupProtos.BackupType.INCREMENTAL); 171 } 172 173 for (TableName name : tableList) { 174 builder.addTableList(ProtobufUtil.toProtoTableName(name)); 175 } 176 177 if (ancestors != null) { 178 for (BackupImage im : ancestors) { 179 builder.addAncestors(im.toProto()); 180 } 181 } 182 183 setIncrementalTimestampMap(builder); 184 return builder.build(); 185 } 186 187 private static Map<TableName, Map<String, Long>> 188 loadIncrementalTimestampMap(BackupProtos.BackupImage proto) { 189 List<BackupProtos.TableServerTimestamp> list = proto.getTstMapList(); 190 191 Map<TableName, Map<String, Long>> incrTimeRanges = new HashMap<>(); 192 193 if (list == null || list.size() == 0) { 194 return incrTimeRanges; 195 } 196 197 for (BackupProtos.TableServerTimestamp tst : list) { 198 TableName tn = ProtobufUtil.toTableName(tst.getTableName()); 199 Map<String, Long> map = incrTimeRanges.get(tn); 200 if (map == null) { 201 map = new HashMap<>(); 202 incrTimeRanges.put(tn, map); 203 } 204 List<BackupProtos.ServerTimestamp> listSt = tst.getServerTimestampList(); 205 for (BackupProtos.ServerTimestamp stm : listSt) { 206 ServerName sn = ProtobufUtil.toServerName(stm.getServerName()); 207 map.put(sn.getHostname() + ":" + sn.getPort(), stm.getTimestamp()); 208 } 209 } 210 return incrTimeRanges; 211 } 212 213 private void setIncrementalTimestampMap(BackupProtos.BackupImage.Builder builder) { 214 if (this.incrTimeRanges == null) { 215 return; 216 } 217 for (Entry<TableName, Map<String, Long>> entry : this.incrTimeRanges.entrySet()) { 218 TableName key = entry.getKey(); 219 Map<String, Long> value = entry.getValue(); 220 BackupProtos.TableServerTimestamp.Builder tstBuilder = 221 BackupProtos.TableServerTimestamp.newBuilder(); 222 tstBuilder.setTableName(ProtobufUtil.toProtoTableName(key)); 223 224 for (Map.Entry<String, Long> entry2 : value.entrySet()) { 225 String s = entry2.getKey(); 226 BackupProtos.ServerTimestamp.Builder stBuilder = 227 BackupProtos.ServerTimestamp.newBuilder(); 228 HBaseProtos.ServerName.Builder snBuilder = HBaseProtos.ServerName.newBuilder(); 229 ServerName sn = ServerName.parseServerName(s); 230 snBuilder.setHostName(sn.getHostname()); 231 snBuilder.setPort(sn.getPort()); 232 stBuilder.setServerName(snBuilder.build()); 233 stBuilder.setTimestamp(entry2.getValue()); 234 tstBuilder.addServerTimestamp(stBuilder.build()); 235 } 236 builder.addTstMap(tstBuilder.build()); 237 } 238 } 239 240 public String getBackupId() { 241 return backupId; 242 } 243 244 private void setBackupId(String backupId) { 245 this.backupId = backupId; 246 } 247 248 public BackupType getType() { 249 return type; 250 } 251 252 private void setType(BackupType type) { 253 this.type = type; 254 } 255 256 public String getRootDir() { 257 return rootDir; 258 } 259 260 private void setRootDir(String rootDir) { 261 this.rootDir = rootDir; 262 } 263 264 public List<TableName> getTableNames() { 265 return tableList; 266 } 267 268 private void setTableList(List<TableName> tableList) { 269 this.tableList = tableList; 270 } 271 272 public long getStartTs() { 273 return startTs; 274 } 275 276 private void setStartTs(long startTs) { 277 this.startTs = startTs; 278 } 279 280 public long getCompleteTs() { 281 return completeTs; 282 } 283 284 private void setCompleteTs(long completeTs) { 285 this.completeTs = completeTs; 286 } 287 288 public ArrayList<BackupImage> getAncestors() { 289 if (this.ancestors == null) { 290 this.ancestors = new ArrayList<>(); 291 } 292 return this.ancestors; 293 } 294 295 public void removeAncestors(List<String> backupIds) { 296 List<BackupImage> toRemove = new ArrayList<>(); 297 for (BackupImage im : this.ancestors) { 298 if (backupIds.contains(im.getBackupId())) { 299 toRemove.add(im); 300 } 301 } 302 this.ancestors.removeAll(toRemove); 303 } 304 305 private void addAncestor(BackupImage backupImage) { 306 this.getAncestors().add(backupImage); 307 } 308 309 public boolean hasAncestor(String token) { 310 for (BackupImage image : this.getAncestors()) { 311 if (image.getBackupId().equals(token)) { 312 return true; 313 } 314 } 315 return false; 316 } 317 318 public boolean hasTable(TableName table) { 319 return tableList.contains(table); 320 } 321 322 @Override 323 public int compareTo(BackupImage other) { 324 String thisBackupId = this.getBackupId(); 325 String otherBackupId = other.getBackupId(); 326 int index1 = thisBackupId.lastIndexOf("_"); 327 int index2 = otherBackupId.lastIndexOf("_"); 328 String name1 = thisBackupId.substring(0, index1); 329 String name2 = otherBackupId.substring(0, index2); 330 if (name1.equals(name2)) { 331 Long thisTS = Long.valueOf(thisBackupId.substring(index1 + 1)); 332 Long otherTS = Long.valueOf(otherBackupId.substring(index2 + 1)); 333 return thisTS.compareTo(otherTS); 334 } else { 335 return name1.compareTo(name2); 336 } 337 } 338 339 @Override 340 public boolean equals(Object obj) { 341 if (obj instanceof BackupImage) { 342 return this.compareTo((BackupImage) obj) == 0; 343 } 344 return false; 345 } 346 347 @Override 348 public int hashCode() { 349 int hash = 33 * this.getBackupId().hashCode() + type.hashCode(); 350 hash = 33 * hash + rootDir.hashCode(); 351 hash = 33 * hash + Long.valueOf(startTs).hashCode(); 352 hash = 33 * hash + Long.valueOf(completeTs).hashCode(); 353 for (TableName table : tableList) { 354 hash = 33 * hash + table.hashCode(); 355 } 356 return hash; 357 } 358 359 public Map<TableName, Map<String, Long>> getIncrTimeRanges() { 360 return incrTimeRanges; 361 } 362 363 private void setIncrTimeRanges(Map<TableName, Map<String, Long>> incrTimeRanges) { 364 this.incrTimeRanges = incrTimeRanges; 365 } 366 } 367 368 // backup image directory 369 private BackupImage backupImage; 370 371 /** 372 * Construct manifest for a ongoing backup. 373 * @param backup The ongoing backup info 374 */ 375 public BackupManifest(BackupInfo backup) { 376 BackupImage.Builder builder = BackupImage.newBuilder(); 377 this.backupImage = builder.withBackupId(backup.getBackupId()).withType(backup.getType()) 378 .withRootDir(backup.getBackupRootDir()).withTableList(backup.getTableNames()) 379 .withStartTime(backup.getStartTs()).withCompleteTime(backup.getCompleteTs()).build(); 380 } 381 382 /** 383 * Construct a table level manifest for a backup of the named table. 384 * @param backup The ongoing backup session info 385 */ 386 public BackupManifest(BackupInfo backup, TableName table) { 387 List<TableName> tables = new ArrayList<TableName>(); 388 tables.add(table); 389 BackupImage.Builder builder = BackupImage.newBuilder(); 390 this.backupImage = builder.withBackupId(backup.getBackupId()).withType(backup.getType()) 391 .withRootDir(backup.getBackupRootDir()).withTableList(tables) 392 .withStartTime(backup.getStartTs()).withCompleteTime(backup.getCompleteTs()).build(); 393 } 394 395 /** 396 * Construct manifest from a backup directory. 397 * @param conf configuration 398 * @param backupPath backup path 399 * @throws IOException if constructing the manifest from the backup directory fails 400 */ 401 public BackupManifest(Configuration conf, Path backupPath) throws IOException { 402 this(backupPath.getFileSystem(conf), backupPath); 403 } 404 405 /** 406 * Construct manifest from a backup directory. 407 * @param fs the FileSystem 408 * @param backupPath backup path 409 * @throws BackupException exception 410 */ 411 public BackupManifest(FileSystem fs, Path backupPath) throws BackupException { 412 if (LOG.isDebugEnabled()) { 413 LOG.debug("Loading manifest from: " + backupPath.toString()); 414 } 415 // The input backupDir may not exactly be the backup table dir. 416 // It could be the backup log dir where there is also a manifest file stored. 417 // This variable's purpose is to keep the correct and original location so 418 // that we can store/persist it. 419 try { 420 FileStatus[] subFiles = BackupUtils.listStatus(fs, backupPath, null); 421 if (subFiles == null) { 422 String errorMsg = backupPath.toString() + " does not exist"; 423 LOG.error(errorMsg); 424 throw new IOException(errorMsg); 425 } 426 for (FileStatus subFile : subFiles) { 427 if (subFile.getPath().getName().equals(MANIFEST_FILE_NAME)) { 428 // load and set manifest field from file content 429 long len = subFile.getLen(); 430 byte[] pbBytes = new byte[(int) len]; 431 try (FSDataInputStream in = fs.open(subFile.getPath())) { 432 in.readFully(pbBytes); 433 } catch (IOException e) { 434 throw new BackupException(e.getMessage()); 435 } 436 BackupProtos.BackupImage proto = null; 437 try { 438 proto = BackupProtos.BackupImage.parseFrom(pbBytes); 439 } catch (Exception e) { 440 throw new BackupException(e); 441 } 442 this.backupImage = BackupImage.fromProto(proto); 443 LOG.debug("Loaded manifest instance from manifest file: " 444 + BackupUtils.getPath(subFile.getPath())); 445 return; 446 } 447 } 448 String errorMsg = "No manifest file found in: " + backupPath.toString(); 449 throw new IOException(errorMsg); 450 } catch (IOException e) { 451 throw new BackupException(e.getMessage()); 452 } 453 } 454 455 public BackupType getType() { 456 return backupImage.getType(); 457 } 458 459 /** 460 * Get the table set of this image. 461 * @return The table set list 462 */ 463 public List<TableName> getTableList() { 464 return backupImage.getTableNames(); 465 } 466 467 /** 468 * TODO: fix it. Persist the manifest file. 469 * @throws BackupException if an error occurred while storing the manifest file. 470 */ 471 public void store(Configuration conf) throws BackupException { 472 byte[] data = backupImage.toProto().toByteArray(); 473 // write the file, overwrite if already exist 474 Path manifestFilePath = 475 new Path(HBackupFileSystem.getBackupPath(backupImage.getRootDir(), backupImage.getBackupId()), 476 MANIFEST_FILE_NAME); 477 try (FSDataOutputStream out = 478 manifestFilePath.getFileSystem(conf).create(manifestFilePath, true)) { 479 out.write(data); 480 } catch (IOException e) { 481 throw new BackupException(e.getMessage()); 482 } 483 484 LOG.info("Manifest file stored to " + manifestFilePath); 485 } 486 487 /** 488 * Get this backup image. 489 * @return the backup image. 490 */ 491 public BackupImage getBackupImage() { 492 return backupImage; 493 } 494 495 /** 496 * Add dependent backup image for this backup. 497 * @param image The direct dependent backup image 498 */ 499 public void addDependentImage(BackupImage image) { 500 this.backupImage.addAncestor(image); 501 } 502 503 /** 504 * Set the incremental timestamp map directly. 505 * @param incrTimestampMap timestamp map 506 */ 507 public void setIncrTimestampMap(Map<TableName, Map<String, Long>> incrTimestampMap) { 508 this.backupImage.setIncrTimeRanges(incrTimestampMap); 509 } 510 511 public Map<TableName, Map<String, Long>> getIncrTimestampMap() { 512 return backupImage.getIncrTimeRanges(); 513 } 514 515 /** 516 * Get the image list of this backup for restore in time order. 517 * @param reverse If true, then output in reverse order, otherwise in time order from old to new 518 * @return the backup image list for restore in time order 519 */ 520 public ArrayList<BackupImage> getRestoreDependentList(boolean reverse) { 521 TreeMap<Long, BackupImage> restoreImages = new TreeMap<>(); 522 restoreImages.put(backupImage.startTs, backupImage); 523 for (BackupImage image : backupImage.getAncestors()) { 524 restoreImages.put(Long.valueOf(image.startTs), image); 525 } 526 return new ArrayList<>( 527 reverse ? restoreImages.descendingMap().values() : restoreImages.values()); 528 } 529 530 /** 531 * Get the dependent image list for a specific table of this backup in time order from old to new 532 * if want to restore to this backup image level. 533 * @param table table 534 * @return the backup image list for a table in time order 535 */ 536 public ArrayList<BackupImage> getDependentListByTable(TableName table) { 537 ArrayList<BackupImage> tableImageList = new ArrayList<>(); 538 ArrayList<BackupImage> imageList = getRestoreDependentList(true); 539 for (BackupImage image : imageList) { 540 if (image.hasTable(table)) { 541 tableImageList.add(image); 542 if (image.getType() == BackupType.FULL) { 543 break; 544 } 545 } 546 } 547 Collections.reverse(tableImageList); 548 return tableImageList; 549 } 550 551 /** 552 * Get the full dependent image list in the whole dependency scope for a specific table of this 553 * backup in time order from old to new. 554 * @param table table 555 * @return the full backup image list for a table in time order in the whole scope of the 556 * dependency of this image 557 */ 558 public ArrayList<BackupImage> getAllDependentListByTable(TableName table) { 559 ArrayList<BackupImage> tableImageList = new ArrayList<>(); 560 ArrayList<BackupImage> imageList = getRestoreDependentList(false); 561 for (BackupImage image : imageList) { 562 if (image.hasTable(table)) { 563 tableImageList.add(image); 564 } 565 } 566 return tableImageList; 567 } 568 569 /** 570 * Check whether backup image1 could cover backup image2 or not. 571 * @param image1 backup image 1 572 * @param image2 backup image 2 573 * @return true if image1 can cover image2, otherwise false 574 */ 575 public static boolean canCoverImage(BackupImage image1, BackupImage image2) { 576 // image1 can cover image2 only when the following conditions are satisfied: 577 // - image1 must not be an incremental image; 578 // - image1 must be taken after image2 has been taken; 579 // - table set of image1 must cover the table set of image2. 580 if (image1.getType() == BackupType.INCREMENTAL) { 581 return false; 582 } 583 if (image1.getStartTs() < image2.getStartTs()) { 584 return false; 585 } 586 List<TableName> image1TableList = image1.getTableNames(); 587 List<TableName> image2TableList = image2.getTableNames(); 588 boolean found; 589 for (int i = 0; i < image2TableList.size(); i++) { 590 found = false; 591 for (int j = 0; j < image1TableList.size(); j++) { 592 if (image2TableList.get(i).equals(image1TableList.get(j))) { 593 found = true; 594 break; 595 } 596 } 597 if (!found) { 598 return false; 599 } 600 } 601 602 LOG.debug("Backup image " + image1.getBackupId() + " can cover " + image2.getBackupId()); 603 return true; 604 } 605 606 /** 607 * Check whether backup image set could cover a backup image or not. 608 * @param fullImages The backup image set 609 * @param image The target backup image 610 * @return true if fullImages can cover image, otherwise false 611 */ 612 public static boolean canCoverImage(ArrayList<BackupImage> fullImages, BackupImage image) { 613 // fullImages can cover image only when the following conditions are satisfied: 614 // - each image of fullImages must not be an incremental image; 615 // - each image of fullImages must be taken after image has been taken; 616 // - sum table set of fullImages must cover the table set of image. 617 for (BackupImage image1 : fullImages) { 618 if (image1.getType() == BackupType.INCREMENTAL) { 619 return false; 620 } 621 if (image1.getStartTs() < image.getStartTs()) { 622 return false; 623 } 624 } 625 626 ArrayList<String> image1TableList = new ArrayList<>(); 627 for (BackupImage image1 : fullImages) { 628 List<TableName> tableList = image1.getTableNames(); 629 for (TableName table : tableList) { 630 image1TableList.add(table.getNameAsString()); 631 } 632 } 633 ArrayList<String> image2TableList = new ArrayList<>(); 634 List<TableName> tableList = image.getTableNames(); 635 for (TableName table : tableList) { 636 image2TableList.add(table.getNameAsString()); 637 } 638 639 for (int i = 0; i < image2TableList.size(); i++) { 640 if (image1TableList.contains(image2TableList.get(i)) == false) { 641 return false; 642 } 643 } 644 645 LOG.debug("Full image set can cover image " + image.getBackupId()); 646 return true; 647 } 648 649 public BackupInfo toBackupInfo() { 650 BackupInfo info = new BackupInfo(); 651 info.setType(backupImage.getType()); 652 List<TableName> list = backupImage.getTableNames(); 653 TableName[] tables = new TableName[list.size()]; 654 info.addTables(list.toArray(tables)); 655 info.setBackupId(backupImage.getBackupId()); 656 info.setStartTs(backupImage.getStartTs()); 657 info.setBackupRootDir(backupImage.getRootDir()); 658 if (backupImage.getType() == BackupType.INCREMENTAL) { 659 info.setHLogTargetDir( 660 BackupUtils.getLogBackupDir(backupImage.getRootDir(), backupImage.getBackupId())); 661 } 662 return info; 663 } 664}