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.snapshot; 019 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.OutputStream; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.LinkedList; 029import java.util.List; 030import java.util.Map; 031import java.util.Map.Entry; 032import java.util.Set; 033import java.util.TreeMap; 034import java.util.concurrent.ThreadPoolExecutor; 035import java.util.stream.Collectors; 036import org.apache.hadoop.conf.Configuration; 037import org.apache.hadoop.fs.FileStatus; 038import org.apache.hadoop.fs.FileSystem; 039import org.apache.hadoop.fs.Path; 040import org.apache.hadoop.hbase.MetaTableAccessor; 041import org.apache.hadoop.hbase.TableName; 042import org.apache.hadoop.hbase.backup.HFileArchiver; 043import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 044import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 045import org.apache.hadoop.hbase.client.Connection; 046import org.apache.hadoop.hbase.client.ConnectionFactory; 047import org.apache.hadoop.hbase.client.RegionInfo; 048import org.apache.hadoop.hbase.client.RegionInfoBuilder; 049import org.apache.hadoop.hbase.client.TableDescriptor; 050import org.apache.hadoop.hbase.errorhandling.ForeignExceptionDispatcher; 051import org.apache.hadoop.hbase.io.HFileLink; 052import org.apache.hadoop.hbase.io.Reference; 053import org.apache.hadoop.hbase.mob.MobUtils; 054import org.apache.hadoop.hbase.monitoring.MonitoredTask; 055import org.apache.hadoop.hbase.monitoring.TaskMonitor; 056import org.apache.hadoop.hbase.regionserver.HRegion; 057import org.apache.hadoop.hbase.regionserver.HRegionFileSystem; 058import org.apache.hadoop.hbase.regionserver.StoreContext; 059import org.apache.hadoop.hbase.regionserver.StoreFileInfo; 060import org.apache.hadoop.hbase.regionserver.StoreUtils; 061import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker; 062import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory; 063import org.apache.hadoop.hbase.security.access.AccessControlClient; 064import org.apache.hadoop.hbase.security.access.Permission; 065import org.apache.hadoop.hbase.security.access.ShadedAccessControlUtil; 066import org.apache.hadoop.hbase.security.access.TablePermission; 067import org.apache.hadoop.hbase.snapshot.RestoreSnapshotHelper.RestoreMetaChanges; 068import org.apache.hadoop.hbase.util.Bytes; 069import org.apache.hadoop.hbase.util.CommonFSUtils; 070import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 071import org.apache.hadoop.hbase.util.FSUtils; 072import org.apache.hadoop.hbase.util.ModifyRegionUtils; 073import org.apache.hadoop.hbase.util.Pair; 074import org.apache.hadoop.io.IOUtils; 075import org.apache.yetus.audience.InterfaceAudience; 076import org.slf4j.Logger; 077import org.slf4j.LoggerFactory; 078 079import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap; 080 081import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 082import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription; 083import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotRegionManifest; 084 085/** 086 * Helper to Restore/Clone a Snapshot 087 * <p> 088 * The helper assumes that a table is already created, and by calling restore() the content present 089 * in the snapshot will be restored as the new content of the table. 090 * <p> 091 * Clone from Snapshot: If the target table is empty, the restore operation is just a "clone 092 * operation", where the only operations are: 093 * <ul> 094 * <li>for each region in the snapshot create a new region (note that the region will have a 095 * different name, since the encoding contains the table name) 096 * <li>for each file in the region create a new HFileLink to point to the original file. 097 * <li>restore the logs, if any 098 * </ul> 099 * <p> 100 * Restore from Snapshot: 101 * <ul> 102 * <li>for each region in the table verify which are available in the snapshot and which are not 103 * <ul> 104 * <li>if the region is not present in the snapshot, remove it. 105 * <li>if the region is present in the snapshot 106 * <ul> 107 * <li>for each file in the table region verify which are available in the snapshot 108 * <ul> 109 * <li>if the hfile is not present in the snapshot, remove it 110 * <li>if the hfile is present, keep it (nothing to do) 111 * </ul> 112 * <li>for each file in the snapshot region but not in the table 113 * <ul> 114 * <li>create a new HFileLink that point to the original file 115 * </ul> 116 * </ul> 117 * </ul> 118 * <li>for each region in the snapshot not present in the current table state 119 * <ul> 120 * <li>create a new region and for each file in the region create a new HFileLink (This is the same 121 * as the clone operation) 122 * </ul> 123 * <li>restore the logs, if any 124 * </ul> 125 */ 126@InterfaceAudience.Private 127public class RestoreSnapshotHelper { 128 private static final Logger LOG = LoggerFactory.getLogger(RestoreSnapshotHelper.class); 129 130 private final Map<byte[], byte[]> regionsMap = new TreeMap<>(Bytes.BYTES_COMPARATOR); 131 132 private final Map<String, Pair<String, String>> parentsMap = new HashMap<>(); 133 134 private final ForeignExceptionDispatcher monitor; 135 private final MonitoredTask status; 136 137 private final SnapshotManifest snapshotManifest; 138 private final SnapshotDescription snapshotDesc; 139 private final TableName snapshotTable; 140 141 private final TableDescriptor tableDesc; 142 private final Path rootDir; 143 private final Path tableDir; 144 145 private final Configuration conf; 146 private final FileSystem fs; 147 private final boolean createBackRefs; 148 149 public RestoreSnapshotHelper(final Configuration conf, final FileSystem fs, 150 final SnapshotManifest manifest, final TableDescriptor tableDescriptor, final Path rootDir, 151 final ForeignExceptionDispatcher monitor, final MonitoredTask status) { 152 this(conf, fs, manifest, tableDescriptor, rootDir, monitor, status, true); 153 } 154 155 public RestoreSnapshotHelper(final Configuration conf, final FileSystem fs, 156 final SnapshotManifest manifest, final TableDescriptor tableDescriptor, final Path rootDir, 157 final ForeignExceptionDispatcher monitor, final MonitoredTask status, 158 final boolean createBackRefs) { 159 this.fs = fs; 160 this.conf = conf; 161 this.snapshotManifest = manifest; 162 this.snapshotDesc = manifest.getSnapshotDescription(); 163 this.snapshotTable = TableName.valueOf(snapshotDesc.getTable()); 164 this.tableDesc = tableDescriptor; 165 this.rootDir = rootDir; 166 this.tableDir = CommonFSUtils.getTableDir(rootDir, tableDesc.getTableName()); 167 this.monitor = monitor; 168 this.status = status; 169 this.createBackRefs = createBackRefs; 170 } 171 172 /** 173 * Restore the on-disk table to a specified snapshot state. 174 * @return the set of regions touched by the restore operation 175 */ 176 public RestoreMetaChanges restoreHdfsRegions() throws IOException { 177 ThreadPoolExecutor exec = SnapshotManifest.createExecutor(conf, "RestoreSnapshot"); 178 try { 179 return restoreHdfsRegions(exec); 180 } finally { 181 exec.shutdown(); 182 } 183 } 184 185 private RestoreMetaChanges restoreHdfsRegions(final ThreadPoolExecutor exec) throws IOException { 186 LOG.info("starting restore table regions using snapshot=" + snapshotDesc); 187 188 Map<String, SnapshotRegionManifest> regionManifests = snapshotManifest.getRegionManifestsMap(); 189 if (regionManifests == null) { 190 LOG.warn("Nothing to restore. Snapshot " + snapshotDesc + " looks empty"); 191 return null; 192 } 193 194 RestoreMetaChanges metaChanges = new RestoreMetaChanges(tableDesc, parentsMap); 195 196 // Take a copy of the manifest.keySet() since we are going to modify 197 // this instance, by removing the regions already present in the restore dir. 198 Set<String> regionNames = new HashSet<>(regionManifests.keySet()); 199 200 List<RegionInfo> tableRegions = getTableRegions(); 201 202 RegionInfo mobRegion = 203 MobUtils.getMobRegionInfo(snapshotManifest.getTableDescriptor().getTableName()); 204 if (tableRegions != null) { 205 // restore the mob region in case 206 if (regionNames.contains(mobRegion.getEncodedName())) { 207 monitor.rethrowException(); 208 status.setStatus("Restoring mob region..."); 209 List<RegionInfo> mobRegions = new ArrayList<>(1); 210 mobRegions.add(mobRegion); 211 restoreHdfsMobRegions(exec, regionManifests, mobRegions); 212 regionNames.remove(mobRegion.getEncodedName()); 213 status.setStatus("Finished restoring mob region."); 214 } 215 } 216 if (regionNames.contains(mobRegion.getEncodedName())) { 217 // add the mob region 218 monitor.rethrowException(); 219 status.setStatus("Cloning mob region..."); 220 cloneHdfsMobRegion(regionManifests, mobRegion); 221 regionNames.remove(mobRegion.getEncodedName()); 222 status.setStatus("Finished cloning mob region."); 223 } 224 225 // Identify which region are still available and which not. 226 // NOTE: we rely upon the region name as: "table name, start key, end key" 227 if (tableRegions != null) { 228 monitor.rethrowException(); 229 for (RegionInfo regionInfo : tableRegions) { 230 String regionName = regionInfo.getEncodedName(); 231 if (regionNames.contains(regionName)) { 232 LOG.info("region to restore: " + regionName); 233 regionNames.remove(regionName); 234 metaChanges.addRegionToRestore( 235 ProtobufUtil.toRegionInfo(regionManifests.get(regionName).getRegionInfo())); 236 } else { 237 LOG.info("region to remove: " + regionName); 238 metaChanges.addRegionToRemove(regionInfo); 239 } 240 } 241 } 242 243 // Regions to Add: present in the snapshot but not in the current table 244 List<RegionInfo> regionsToAdd = new ArrayList<>(regionNames.size()); 245 if (regionNames.size() > 0) { 246 monitor.rethrowException(); 247 for (String regionName : regionNames) { 248 LOG.info("region to add: " + regionName); 249 regionsToAdd 250 .add(ProtobufUtil.toRegionInfo(regionManifests.get(regionName).getRegionInfo())); 251 } 252 } 253 254 // Create new regions cloning from the snapshot 255 // HBASE-19980: We need to call cloneHdfsRegions() before restoreHdfsRegions() because 256 // regionsMap is constructed in cloneHdfsRegions() and it can be used in restoreHdfsRegions(). 257 monitor.rethrowException(); 258 status.setStatus("Cloning regions..."); 259 RegionInfo[] clonedRegions = cloneHdfsRegions(exec, regionManifests, regionsToAdd); 260 metaChanges.setNewRegions(clonedRegions); 261 status.setStatus("Finished cloning regions."); 262 263 // Restore regions using the snapshot data 264 monitor.rethrowException(); 265 status.setStatus("Restoring table regions..."); 266 restoreHdfsRegions(exec, regionManifests, metaChanges.getRegionsToRestore()); 267 status.setStatus("Finished restoring all table regions."); 268 269 // Remove regions from the current table 270 monitor.rethrowException(); 271 status.setStatus("Starting to delete excess regions from table"); 272 removeHdfsRegions(exec, metaChanges.getRegionsToRemove()); 273 status.setStatus("Finished deleting excess regions from table."); 274 275 LOG.info("finishing restore table regions using snapshot=" + snapshotDesc); 276 277 return metaChanges; 278 } 279 280 /** 281 * Describe the set of operations needed to update hbase:meta after restore. 282 */ 283 public static class RestoreMetaChanges { 284 private final Map<String, Pair<String, String>> parentsMap; 285 private final TableDescriptor htd; 286 287 private List<RegionInfo> regionsToRestore = null; 288 private List<RegionInfo> regionsToRemove = null; 289 private List<RegionInfo> regionsToAdd = null; 290 291 public RestoreMetaChanges(TableDescriptor htd, Map<String, Pair<String, String>> parentsMap) { 292 this.parentsMap = parentsMap; 293 this.htd = htd; 294 } 295 296 public TableDescriptor getTableDescriptor() { 297 return htd; 298 } 299 300 /** 301 * Returns the map of parent-children_pair. 302 * @return the map 303 */ 304 public Map<String, Pair<String, String>> getParentToChildrenPairMap() { 305 return this.parentsMap; 306 } 307 308 /** Returns true if there're new regions */ 309 public boolean hasRegionsToAdd() { 310 return this.regionsToAdd != null && this.regionsToAdd.size() > 0; 311 } 312 313 /** 314 * Returns the list of new regions added during the on-disk restore. The caller is responsible 315 * to add the regions to META. e.g MetaTableAccessor.addRegionsToMeta(...) 316 * @return the list of regions to add to META 317 */ 318 public List<RegionInfo> getRegionsToAdd() { 319 return this.regionsToAdd; 320 } 321 322 /** Returns true if there're regions to restore */ 323 public boolean hasRegionsToRestore() { 324 return this.regionsToRestore != null && this.regionsToRestore.size() > 0; 325 } 326 327 /** 328 * Returns the list of 'restored regions' during the on-disk restore. The caller is responsible 329 * to add the regions to hbase:meta if not present. 330 * @return the list of regions restored 331 */ 332 public List<RegionInfo> getRegionsToRestore() { 333 return this.regionsToRestore; 334 } 335 336 /** Returns true if there're regions to remove */ 337 public boolean hasRegionsToRemove() { 338 return this.regionsToRemove != null && this.regionsToRemove.size() > 0; 339 } 340 341 /** 342 * Returns the list of regions removed during the on-disk restore. The caller is responsible to 343 * remove the regions from META. e.g. MetaTableAccessor.deleteRegions(...) 344 * @return the list of regions to remove from META 345 */ 346 public List<RegionInfo> getRegionsToRemove() { 347 return this.regionsToRemove; 348 } 349 350 void setNewRegions(final RegionInfo[] hris) { 351 if (hris != null) { 352 regionsToAdd = Arrays.asList(hris); 353 } else { 354 regionsToAdd = null; 355 } 356 } 357 358 void addRegionToRemove(final RegionInfo hri) { 359 if (regionsToRemove == null) { 360 regionsToRemove = new LinkedList<>(); 361 } 362 regionsToRemove.add(hri); 363 } 364 365 void addRegionToRestore(final RegionInfo hri) { 366 if (regionsToRestore == null) { 367 regionsToRestore = new LinkedList<>(); 368 } 369 regionsToRestore.add(hri); 370 } 371 372 public void updateMetaParentRegions(Connection connection, final List<RegionInfo> regionInfos) 373 throws IOException { 374 if (regionInfos == null || parentsMap.isEmpty()) return; 375 376 // Extract region names and offlined regions 377 Map<String, RegionInfo> regionsByName = new HashMap<>(regionInfos.size()); 378 List<RegionInfo> parentRegions = new LinkedList<>(); 379 for (RegionInfo regionInfo : regionInfos) { 380 if (regionInfo.isSplitParent()) { 381 parentRegions.add(regionInfo); 382 } else { 383 regionsByName.put(regionInfo.getEncodedName(), regionInfo); 384 } 385 } 386 387 // Update Offline parents 388 for (RegionInfo regionInfo : parentRegions) { 389 Pair<String, String> daughters = parentsMap.get(regionInfo.getEncodedName()); 390 if (daughters == null) { 391 // The snapshot contains an unreferenced region. 392 // It will be removed by the CatalogJanitor. 393 LOG.warn("Skip update of unreferenced offline parent: " + regionInfo); 394 continue; 395 } 396 397 // One side of the split is already compacted 398 if (daughters.getSecond() == null) { 399 daughters.setSecond(daughters.getFirst()); 400 } 401 402 LOG.debug("Update splits parent " + regionInfo.getEncodedName() + " -> " + daughters); 403 MetaTableAccessor.addSplitsToParent(connection, regionInfo, 404 regionsByName.get(daughters.getFirst()), regionsByName.get(daughters.getSecond())); 405 } 406 } 407 } 408 409 /** 410 * Remove specified regions from the file-system, using the archiver. 411 */ 412 private void removeHdfsRegions(final ThreadPoolExecutor exec, final List<RegionInfo> regions) 413 throws IOException { 414 if (regions == null || regions.isEmpty()) return; 415 ModifyRegionUtils.editRegions(exec, regions, new ModifyRegionUtils.RegionEditTask() { 416 @Override 417 public void editRegion(final RegionInfo hri) throws IOException { 418 HFileArchiver.archiveRegion(conf, fs, hri, rootDir, tableDir); 419 } 420 }); 421 } 422 423 /** 424 * Restore specified regions by restoring content to the snapshot state. 425 */ 426 private void restoreHdfsRegions(final ThreadPoolExecutor exec, 427 final Map<String, SnapshotRegionManifest> regionManifests, final List<RegionInfo> regions) 428 throws IOException { 429 if (regions == null || regions.isEmpty()) return; 430 ModifyRegionUtils.editRegions(exec, regions, new ModifyRegionUtils.RegionEditTask() { 431 @Override 432 public void editRegion(final RegionInfo hri) throws IOException { 433 restoreRegion(hri, regionManifests.get(hri.getEncodedName())); 434 } 435 }); 436 } 437 438 /** 439 * Restore specified mob regions by restoring content to the snapshot state. 440 */ 441 private void restoreHdfsMobRegions(final ThreadPoolExecutor exec, 442 final Map<String, SnapshotRegionManifest> regionManifests, final List<RegionInfo> regions) 443 throws IOException { 444 if (regions == null || regions.isEmpty()) return; 445 ModifyRegionUtils.editRegions(exec, regions, new ModifyRegionUtils.RegionEditTask() { 446 @Override 447 public void editRegion(final RegionInfo hri) throws IOException { 448 restoreMobRegion(hri, regionManifests.get(hri.getEncodedName())); 449 } 450 }); 451 } 452 453 private Map<String, List<SnapshotRegionManifest.StoreFile>> 454 getRegionHFileReferences(final SnapshotRegionManifest manifest) { 455 Map<String, List<SnapshotRegionManifest.StoreFile>> familyMap = 456 new HashMap<>(manifest.getFamilyFilesCount()); 457 for (SnapshotRegionManifest.FamilyFiles familyFiles : manifest.getFamilyFilesList()) { 458 familyMap.put(familyFiles.getFamilyName().toStringUtf8(), 459 new ArrayList<>(familyFiles.getStoreFilesList())); 460 } 461 return familyMap; 462 } 463 464 /** 465 * Restore region by removing files not in the snapshot and adding the missing ones from the 466 * snapshot. 467 */ 468 private void restoreRegion(final RegionInfo regionInfo, 469 final SnapshotRegionManifest regionManifest) throws IOException { 470 restoreRegion(regionInfo, regionManifest, new Path(tableDir, regionInfo.getEncodedName()), 471 tableDir); 472 } 473 474 /** 475 * Restore mob region by removing files not in the snapshot and adding the missing ones from the 476 * snapshot. 477 */ 478 private void restoreMobRegion(final RegionInfo regionInfo, 479 final SnapshotRegionManifest regionManifest) throws IOException { 480 if (regionManifest == null) { 481 return; 482 } 483 restoreRegion(regionInfo, regionManifest, 484 MobUtils.getMobRegionPath(conf, tableDesc.getTableName()), 485 MobUtils.getMobTableDir(conf, tableDesc.getTableName())); 486 } 487 488 /** 489 * Restore region by removing files not in the snapshot and adding the missing ones from the 490 * snapshot. 491 */ 492 private void restoreRegion(final RegionInfo regionInfo, 493 final SnapshotRegionManifest regionManifest, Path regionDir, Path tableDir) throws IOException { 494 Map<String, List<SnapshotRegionManifest.StoreFile>> snapshotFiles = 495 getRegionHFileReferences(regionManifest); 496 497 String tableName = tableDesc.getTableName().getNameAsString(); 498 final String snapshotName = snapshotDesc.getName(); 499 500 HRegionFileSystem regionFS = (fs.exists(regionDir)) 501 ? HRegionFileSystem.openRegionFromFileSystem(conf, fs, tableDir, regionInfo, false) 502 : HRegionFileSystem.createRegionOnFileSystem(conf, fs, tableDir, regionInfo); 503 504 // Restore families present in the table 505 for (Path familyDir : FSUtils.getFamilyDirs(fs, regionDir)) { 506 byte[] family = Bytes.toBytes(familyDir.getName()); 507 ColumnFamilyDescriptor familyDescriptor = ColumnFamilyDescriptorBuilder.of(family); 508 StoreFileTracker tracker = StoreFileTrackerFactory.create(conf, true, 509 StoreContext.getBuilder().withColumnFamilyDescriptor(familyDescriptor) 510 .withFamilyStoreDirectoryPath(familyDir).withRegionFileSystem(regionFS).build()); 511 List<StoreFileInfo> storeFileInfos = tracker.load(); 512 List<String> familyFiles = storeFileInfos.stream() 513 .map(storeFileInfo -> storeFileInfo.getPath().getName()).collect(Collectors.toList()); 514 List<SnapshotRegionManifest.StoreFile> snapshotFamilyFiles = 515 snapshotFiles.remove(familyDir.getName()); 516 List<StoreFileInfo> filesToTrack = new ArrayList<>(); 517 if (snapshotFamilyFiles != null) { 518 List<SnapshotRegionManifest.StoreFile> hfilesToAdd = new ArrayList<>(); 519 for (SnapshotRegionManifest.StoreFile storeFile : snapshotFamilyFiles) { 520 if (familyFiles.contains(storeFile.getName())) { 521 // HFile already present 522 familyFiles.remove(storeFile.getName()); 523 // no need to restore already present files, but we need to add those to tracker 524 filesToTrack 525 .add(tracker.getStoreFileInfo(new Path(familyDir, storeFile.getName()), true)); 526 } else { 527 // HFile missing 528 hfilesToAdd.add(storeFile); 529 } 530 } 531 532 // Remove hfiles not present in the snapshot 533 for (String hfileName : familyFiles) { 534 for (StoreFileInfo storeFileInfo : storeFileInfos) { 535 if (hfileName.equals(storeFileInfo.getPath().getName())) { 536 tracker.removeStoreFiles( 537 StoreUtils.toHStoreFile(Collections.singletonList(storeFileInfo), null, null)); 538 LOG.trace("Removing HFile=" + hfileName + " not present in snapshot=" + snapshotName 539 + " from region=" + regionInfo.getEncodedName() + " table=" + tableName); 540 } 541 } 542 } 543 544 // Restore Missing files 545 for (SnapshotRegionManifest.StoreFile storeFile : hfilesToAdd) { 546 LOG.debug("Restoring missing HFileLink " + storeFile.getName() + " of snapshot=" 547 + snapshotName + " to region=" + regionInfo.getEncodedName() + " table=" + tableName); 548 StoreFileInfo storeFileInfo = 549 restoreStoreFile(familyDir, regionInfo, storeFile, createBackRefs, tracker); 550 // mark the reference file to be added to tracker 551 filesToTrack.add(storeFileInfo); 552 } 553 } else { 554 // Family doesn't exists in the snapshot 555 LOG.trace("Removing family=" + Bytes.toString(family) + " in snapshot=" + snapshotName 556 + " from region=" + regionInfo.getEncodedName() + " table=" + tableName); 557 LOG.debug("Removing family=" + Bytes.toString(family) + " in snapshot=" + snapshotName 558 + " from region=" + regionInfo.getEncodedName() + " table=" + tableName); 559 HFileArchiver.archiveFamilyByFamilyDir(fs, conf, regionInfo, familyDir, family); 560 fs.delete(familyDir, true); 561 } 562 563 // simply reset list of tracked files with the matching files 564 // and the extra one present in the snapshot 565 tracker.set(filesToTrack); 566 } 567 568 // Add families not present in the table 569 for (Map.Entry<String, List<SnapshotRegionManifest.StoreFile>> familyEntry : snapshotFiles 570 .entrySet()) { 571 Path familyDir = new Path(regionDir, familyEntry.getKey()); 572 StoreFileTracker tracker = 573 StoreFileTrackerFactory.create(conf, true, StoreContext.getBuilder() 574 .withFamilyStoreDirectoryPath(familyDir).withRegionFileSystem(regionFS).build()); 575 List<StoreFileInfo> files = new ArrayList<>(); 576 if (!fs.mkdirs(familyDir)) { 577 throw new IOException("Unable to create familyDir=" + familyDir); 578 } 579 580 for (SnapshotRegionManifest.StoreFile storeFile : familyEntry.getValue()) { 581 LOG.trace("Adding HFileLink (Not present in the table) " + storeFile.getName() 582 + " of snapshot " + snapshotName + " to table=" + tableName); 583 StoreFileInfo storeFileInfo = 584 restoreStoreFile(familyDir, regionInfo, storeFile, createBackRefs, tracker); 585 files.add(storeFileInfo); 586 } 587 tracker.set(files); 588 } 589 } 590 591 /** 592 * Clone specified regions. For each region create a new region and create a HFileLink for each 593 * hfile. 594 */ 595 private RegionInfo[] cloneHdfsRegions(final ThreadPoolExecutor exec, 596 final Map<String, SnapshotRegionManifest> regionManifests, final List<RegionInfo> regions) 597 throws IOException { 598 if (regions == null || regions.isEmpty()) return null; 599 600 final Map<String, RegionInfo> snapshotRegions = new HashMap<>(regions.size()); 601 final String snapshotName = snapshotDesc.getName(); 602 603 // clone region info (change embedded tableName with the new one) 604 RegionInfo[] clonedRegionsInfo = new RegionInfo[regions.size()]; 605 for (int i = 0; i < clonedRegionsInfo.length; ++i) { 606 // clone the region info from the snapshot region info 607 RegionInfo snapshotRegionInfo = regions.get(i); 608 clonedRegionsInfo[i] = cloneRegionInfo(snapshotRegionInfo); 609 610 // add the region name mapping between snapshot and cloned 611 String snapshotRegionName = snapshotRegionInfo.getEncodedName(); 612 String clonedRegionName = clonedRegionsInfo[i].getEncodedName(); 613 regionsMap.put(Bytes.toBytes(snapshotRegionName), Bytes.toBytes(clonedRegionName)); 614 LOG.info("clone region=" + snapshotRegionName + " as " + clonedRegionName + " in snapshot " 615 + snapshotName); 616 617 // Add mapping between cloned region name and snapshot region info 618 snapshotRegions.put(clonedRegionName, snapshotRegionInfo); 619 } 620 621 // create the regions on disk 622 ModifyRegionUtils.createRegions(exec, conf, rootDir, tableDesc, clonedRegionsInfo, 623 new ModifyRegionUtils.RegionFillTask() { 624 @Override 625 public void fillRegion(final HRegion region) throws IOException { 626 RegionInfo snapshotHri = snapshotRegions.get(region.getRegionInfo().getEncodedName()); 627 cloneRegion(region, snapshotHri, regionManifests.get(snapshotHri.getEncodedName())); 628 } 629 }); 630 631 return clonedRegionsInfo; 632 } 633 634 /** 635 * Clone the mob region. For the region create a new region and create a HFileLink for each hfile. 636 */ 637 private void cloneHdfsMobRegion(final Map<String, SnapshotRegionManifest> regionManifests, 638 final RegionInfo region) throws IOException { 639 // clone region info (change embedded tableName with the new one) 640 Path clonedRegionPath = MobUtils.getMobRegionPath(rootDir, tableDesc.getTableName()); 641 cloneRegion(MobUtils.getMobRegionInfo(tableDesc.getTableName()), clonedRegionPath, region, 642 regionManifests.get(region.getEncodedName())); 643 } 644 645 /** 646 * Clone region directory content from the snapshot info. Each region is encoded with the table 647 * name, so the cloned region will have a different region name. Instead of copying the hfiles a 648 * HFileLink is created. 649 * @param regionDir {@link Path} cloned dir 650 */ 651 private void cloneRegion(final RegionInfo newRegionInfo, final Path regionDir, 652 final RegionInfo snapshotRegionInfo, final SnapshotRegionManifest manifest) throws IOException { 653 final String tableName = tableDesc.getTableName().getNameAsString(); 654 final String snapshotName = snapshotDesc.getName(); 655 for (SnapshotRegionManifest.FamilyFiles familyFiles : manifest.getFamilyFilesList()) { 656 Path familyDir = new Path(regionDir, familyFiles.getFamilyName().toStringUtf8()); 657 List<StoreFileInfo> clonedFiles = new ArrayList<>(); 658 HRegionFileSystem regionFS = (fs.exists(regionDir)) 659 ? HRegionFileSystem.openRegionFromFileSystem(conf, fs, tableDir, newRegionInfo, false) 660 : HRegionFileSystem.createRegionOnFileSystem(conf, fs, tableDir, newRegionInfo); 661 662 Configuration sftConf = StoreUtils.createStoreConfiguration(conf, tableDesc, 663 tableDesc.getColumnFamily(familyFiles.getFamilyName().toByteArray())); 664 StoreFileTracker tracker = 665 StoreFileTrackerFactory 666 .create(sftConf, true, 667 StoreContext.getBuilder() 668 .withFamilyStoreDirectoryPath( 669 new Path(regionDir, familyFiles.getFamilyName().toStringUtf8())) 670 .withRegionFileSystem(regionFS) 671 .withColumnFamilyDescriptor( 672 ColumnFamilyDescriptorBuilder.of(familyFiles.getFamilyName().toByteArray())) 673 .build()); 674 tracker.load(); 675 for (SnapshotRegionManifest.StoreFile storeFile : familyFiles.getStoreFilesList()) { 676 LOG.info("Adding HFileLink " + storeFile.getName() + " from cloned region " + "in snapshot " 677 + snapshotName + " to table=" + tableName); 678 if (MobUtils.isMobRegionInfo(newRegionInfo)) { 679 String mobFileName = 680 HFileLink.createHFileLinkName(snapshotRegionInfo, storeFile.getName()); 681 Path mobPath = new Path(familyDir, mobFileName); 682 if (fs.exists(mobPath)) { 683 fs.delete(mobPath, true); 684 } 685 StoreFileInfo storeFileInfo = 686 restoreStoreFile(familyDir, snapshotRegionInfo, storeFile, createBackRefs, tracker); 687 clonedFiles.add(storeFileInfo); 688 } else { 689 StoreFileInfo storeFileInfo = 690 restoreStoreFile(familyDir, snapshotRegionInfo, storeFile, createBackRefs, tracker); 691 clonedFiles.add(storeFileInfo); 692 } 693 } 694 tracker.add(clonedFiles); 695 } 696 697 } 698 699 /** 700 * Clone region directory content from the snapshot info. Each region is encoded with the table 701 * name, so the cloned region will have a different region name. Instead of copying the hfiles a 702 * HFileLink is created. 703 * @param region {@link HRegion} cloned 704 */ 705 private void cloneRegion(final HRegion region, final RegionInfo snapshotRegionInfo, 706 final SnapshotRegionManifest manifest) throws IOException { 707 cloneRegion(region.getRegionInfo(), new Path(tableDir, region.getRegionInfo().getEncodedName()), 708 snapshotRegionInfo, manifest); 709 } 710 711 /** 712 * Create a new {@link HFileLink} to reference the store file. 713 * <p> 714 * The store file in the snapshot can be a simple hfile, an HFileLink or a reference. 715 * <ul> 716 * <li>hfile: abc -> table=region-abc 717 * <li>reference: abc.1234 -> table=region-abc.1234 718 * <li>hfilelink: table=region-hfile -> table=region-hfile 719 * </ul> 720 * @param familyDir destination directory for the store file 721 * @param regionInfo destination region info for the table 722 * @param createBackRef - Whether back reference should be created. Defaults to true. 723 * @param storeFile store file name (can be a Reference, HFileLink or simple HFile) 724 */ 725 private StoreFileInfo restoreStoreFile(final Path familyDir, final RegionInfo regionInfo, 726 final SnapshotRegionManifest.StoreFile storeFile, final boolean createBackRef, 727 final StoreFileTracker tracker) throws IOException { 728 String hfileName = storeFile.getName(); 729 StoreFileInfo info = null; 730 if (HFileLink.isHFileLink(hfileName) || StoreFileInfo.isMobFileLink(hfileName)) { 731 HFileLink hfileLink = tracker.createFromHFileLink(hfileName, createBackRef); 732 info = new StoreFileInfo(conf, fs, new Path(familyDir, hfileName), hfileLink); 733 return info; 734 } else if (StoreFileInfo.isReference(hfileName)) { 735 return restoreReferenceFile(familyDir, regionInfo, storeFile, tracker); 736 } else { 737 HFileLink hfileLink = tracker.createAndCommitHFileLink(regionInfo.getTable(), 738 regionInfo.getEncodedName(), hfileName, createBackRef); 739 return new StoreFileInfo(conf, fs, new Path(familyDir, HFileLink 740 .createHFileLinkName(regionInfo.getTable(), regionInfo.getEncodedName(), hfileName)), 741 hfileLink); 742 } 743 } 744 745 /** 746 * Create a new {@link Reference} as copy of the source one. 747 * <p> 748 * <blockquote> 749 * 750 * <pre> 751 * The source table looks like: 752 * 1234/abc (original file) 753 * 5678/abc.1234 (reference file) 754 * 755 * After the clone operation looks like: 756 * wxyz/table=1234-abc 757 * stuv/table=1234-abc.wxyz 758 * 759 * NOTE that the region name in the clone changes (md5 of regioninfo) 760 * and the reference should reflect that change. 761 * </pre> 762 * 763 * </blockquote> 764 * @param familyDir destination directory for the store file 765 * @param regionInfo destination region info for the table 766 * @param storeFile reference file name 767 */ 768 private StoreFileInfo restoreReferenceFile(final Path familyDir, final RegionInfo regionInfo, 769 final SnapshotRegionManifest.StoreFile storeFile, final StoreFileTracker tracker) 770 throws IOException { 771 String hfileName = storeFile.getName(); 772 StoreFileInfo storeFileInfo = null; 773 774 // Extract the referred information (hfile name and parent region) 775 Path refPath = 776 StoreFileInfo 777 .getReferredToFile( 778 new Path( 779 new Path( 780 new Path(new Path(snapshotTable.getNamespaceAsString(), 781 snapshotTable.getQualifierAsString()), regionInfo.getEncodedName()), 782 familyDir.getName()), 783 hfileName)); 784 String snapshotRegionName = refPath.getParent().getParent().getName(); 785 String fileName = refPath.getName(); 786 787 // The new reference should have the cloned region name as parent, if it is a clone. 788 String clonedRegionName = Bytes.toString(regionsMap.get(Bytes.toBytes(snapshotRegionName))); 789 if (clonedRegionName == null) clonedRegionName = snapshotRegionName; 790 791 // The output file should be a reference link table=snapshotRegion-fileName.clonedRegionName 792 Path linkPath = null; 793 String refLink = fileName; 794 if (!HFileLink.isHFileLink(fileName)) { 795 refLink = HFileLink.createHFileLinkName(snapshotTable, snapshotRegionName, fileName); 796 linkPath = new Path(familyDir, 797 HFileLink.createHFileLinkName(snapshotTable, regionInfo.getEncodedName(), hfileName)); 798 } 799 800 Path outPath = new Path(familyDir, refLink + '.' + clonedRegionName); 801 802 // Create the new reference 803 if (storeFile.hasReference()) { 804 Reference reference = Reference.convert(storeFile.getReference()); 805 tracker.createAndCommitReference(reference, outPath); 806 storeFileInfo = new StoreFileInfo(conf, fs, outPath, reference); 807 } else { 808 InputStream in; 809 if (linkPath != null) { 810 HFileLink hfileLink = HFileLink.buildFromHFileLinkPattern(conf, linkPath); 811 storeFileInfo = new StoreFileInfo(conf, fs, outPath, hfileLink); 812 tracker.add(Collections.singletonList(storeFileInfo)); 813 in = hfileLink.open(fs); 814 } else { 815 linkPath = new Path(new Path( 816 HRegion.getRegionDir(snapshotManifest.getSnapshotDir(), regionInfo.getEncodedName()), 817 familyDir.getName()), hfileName); 818 in = fs.open(linkPath); 819 } 820 OutputStream out = fs.create(outPath); 821 IOUtils.copyBytes(in, out, conf); 822 } 823 824 // Add the daughter region to the map 825 String regionName = Bytes.toString(regionsMap.get(regionInfo.getEncodedNameAsBytes())); 826 if (regionName == null) { 827 regionName = regionInfo.getEncodedName(); 828 } 829 LOG.debug("Restore reference " + regionName + " to " + clonedRegionName); 830 synchronized (parentsMap) { 831 Pair<String, String> daughters = parentsMap.get(clonedRegionName); 832 if (daughters == null) { 833 // In case one side of the split is already compacted, regionName is put as both first and 834 // second of Pair 835 daughters = new Pair<>(regionName, regionName); 836 parentsMap.put(clonedRegionName, daughters); 837 } else if (!regionName.equals(daughters.getFirst())) { 838 daughters.setSecond(regionName); 839 } 840 } 841 return storeFileInfo; 842 } 843 844 /** 845 * Create a new {@link RegionInfo} from the snapshot region info. Keep the same startKey, endKey, 846 * regionId and split information but change the table name. 847 * @param snapshotRegionInfo Info for region to clone. 848 * @return the new HRegion instance 849 */ 850 public RegionInfo cloneRegionInfo(final RegionInfo snapshotRegionInfo) { 851 return cloneRegionInfo(tableDesc.getTableName(), snapshotRegionInfo); 852 } 853 854 public static RegionInfo cloneRegionInfo(TableName tableName, RegionInfo snapshotRegionInfo) { 855 return RegionInfoBuilder.newBuilder(tableName).setStartKey(snapshotRegionInfo.getStartKey()) 856 .setEndKey(snapshotRegionInfo.getEndKey()).setSplit(snapshotRegionInfo.isSplit()) 857 .setRegionId(snapshotRegionInfo.getRegionId()).setOffline(snapshotRegionInfo.isOffline()) 858 .build(); 859 } 860 861 /** Returns the set of the regions contained in the table */ 862 private List<RegionInfo> getTableRegions() throws IOException { 863 LOG.debug("get table regions: " + tableDir); 864 FileStatus[] regionDirs = 865 CommonFSUtils.listStatus(fs, tableDir, new FSUtils.RegionDirFilter(fs)); 866 if (regionDirs == null) { 867 return null; 868 } 869 870 List<RegionInfo> regions = new ArrayList<>(regionDirs.length); 871 for (int i = 0; i < regionDirs.length; ++i) { 872 RegionInfo hri = HRegionFileSystem.loadRegionInfoFileContent(fs, regionDirs[i].getPath()); 873 regions.add(hri); 874 } 875 LOG.debug("found " + regions.size() + " regions for table=" 876 + tableDesc.getTableName().getNameAsString()); 877 return regions; 878 } 879 880 /** 881 * Copy the snapshot files for a snapshot scanner, discards meta changes. 882 */ 883 public static RestoreMetaChanges copySnapshotForScanner(Configuration conf, FileSystem fs, 884 Path rootDir, Path restoreDir, String snapshotName) throws IOException { 885 // ensure that restore dir is not under root dir 886 if (!restoreDir.getFileSystem(conf).getUri().equals(rootDir.getFileSystem(conf).getUri())) { 887 throw new IllegalArgumentException( 888 "Filesystems for restore directory and HBase root " + "directory should be the same"); 889 } 890 if (restoreDir.toUri().getPath().startsWith(rootDir.toUri().getPath() + "/")) { 891 throw new IllegalArgumentException("Restore directory cannot be a sub directory of HBase " 892 + "root directory. RootDir: " + rootDir + ", restoreDir: " + restoreDir); 893 } 894 895 Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir); 896 SnapshotDescription snapshotDesc = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir); 897 // check if the snapshot is expired. 898 boolean isExpired = SnapshotDescriptionUtils.isExpiredSnapshot(snapshotDesc.getTtl(), 899 snapshotDesc.getCreationTime(), EnvironmentEdgeManager.currentTime()); 900 if (isExpired) { 901 throw new SnapshotTTLExpiredException(ProtobufUtil.createSnapshotDesc(snapshotDesc)); 902 } 903 SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, snapshotDesc); 904 905 MonitoredTask status = TaskMonitor.get() 906 .createStatus("Restoring snapshot '" + snapshotName + "' to directory " + restoreDir); 907 ForeignExceptionDispatcher monitor = new ForeignExceptionDispatcher(); 908 909 // we send createBackRefs=false so that restored hfiles do not create back reference links 910 // in the base hbase root dir. 911 RestoreSnapshotHelper helper = new RestoreSnapshotHelper(conf, fs, manifest, 912 manifest.getTableDescriptor(), restoreDir, monitor, status, false); 913 RestoreMetaChanges metaChanges = helper.restoreHdfsRegions(); // TODO: parallelize. 914 915 if (LOG.isDebugEnabled()) { 916 LOG.debug("Restored table dir:" + restoreDir); 917 CommonFSUtils.logFileSystemState(fs, restoreDir, LOG); 918 } 919 return metaChanges; 920 } 921 922 public static void restoreSnapshotAcl(SnapshotDescription snapshot, TableName newTableName, 923 Configuration conf) throws IOException { 924 if (snapshot.hasUsersAndPermissions() && snapshot.getUsersAndPermissions() != null) { 925 LOG.info("Restore snapshot acl to table. snapshot: " + snapshot + ", table: " + newTableName); 926 ListMultimap<String, Permission> perms = 927 ShadedAccessControlUtil.toUserTablePermissions(snapshot.getUsersAndPermissions()); 928 try (Connection conn = ConnectionFactory.createConnection(conf)) { 929 for (Entry<String, Permission> e : perms.entries()) { 930 String user = e.getKey(); 931 TablePermission tablePerm = (TablePermission) e.getValue(); 932 AccessControlClient.grant(conn, newTableName, user, tablePerm.getFamily(), 933 tablePerm.getQualifier(), tablePerm.getActions()); 934 } 935 } catch (Throwable e) { 936 throw new IOException("Grant acl into newly creatd table failed. snapshot: " + snapshot 937 + ", table: " + newTableName, e); 938 } 939 } 940 } 941}