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