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