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