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(ProtobufUtil.toRegionInfo(regionManifests.get(regionName) 233 .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.add(ProtobufUtil.toRegionInfo(regionManifests.get(regionName) 248 .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 /** 307 * @return true if there're new regions 308 */ 309 public boolean hasRegionsToAdd() { 310 return this.regionsToAdd != null && this.regionsToAdd.size() > 0; 311 } 312 313 /** 314 * Returns the list of new regions added during the on-disk restore. 315 * The caller is responsible to add the regions to META. 316 * e.g MetaTableAccessor.addRegionsToMeta(...) 317 * @return the list of regions to add to META 318 */ 319 public List<RegionInfo> getRegionsToAdd() { 320 return this.regionsToAdd; 321 } 322 323 /** 324 * @return true if there're regions to restore 325 */ 326 public boolean hasRegionsToRestore() { 327 return this.regionsToRestore != null && this.regionsToRestore.size() > 0; 328 } 329 330 /** 331 * Returns the list of 'restored regions' during the on-disk restore. 332 * The caller is responsible to add the regions to hbase:meta if not present. 333 * @return the list of regions restored 334 */ 335 public List<RegionInfo> getRegionsToRestore() { 336 return this.regionsToRestore; 337 } 338 339 /** 340 * @return true if there're regions to remove 341 */ 342 public boolean hasRegionsToRemove() { 343 return this.regionsToRemove != null && this.regionsToRemove.size() > 0; 344 } 345 346 /** 347 * Returns the list of regions removed during the on-disk restore. 348 * The caller is responsible to remove the regions from META. 349 * e.g. MetaTableAccessor.deleteRegions(...) 350 * @return the list of regions to remove from META 351 */ 352 public List<RegionInfo> getRegionsToRemove() { 353 return this.regionsToRemove; 354 } 355 356 void setNewRegions(final RegionInfo[] hris) { 357 if (hris != null) { 358 regionsToAdd = Arrays.asList(hris); 359 } else { 360 regionsToAdd = null; 361 } 362 } 363 364 void addRegionToRemove(final RegionInfo hri) { 365 if (regionsToRemove == null) { 366 regionsToRemove = new LinkedList<>(); 367 } 368 regionsToRemove.add(hri); 369 } 370 371 void addRegionToRestore(final RegionInfo hri) { 372 if (regionsToRestore == null) { 373 regionsToRestore = new LinkedList<>(); 374 } 375 regionsToRestore.add(hri); 376 } 377 378 public void updateMetaParentRegions(Connection connection, 379 final List<RegionInfo> regionInfos) throws IOException { 380 if (regionInfos == null || parentsMap.isEmpty()) return; 381 382 // Extract region names and offlined regions 383 Map<String, RegionInfo> regionsByName = new HashMap<>(regionInfos.size()); 384 List<RegionInfo> parentRegions = new LinkedList<>(); 385 for (RegionInfo regionInfo: regionInfos) { 386 if (regionInfo.isSplitParent()) { 387 parentRegions.add(regionInfo); 388 } else { 389 regionsByName.put(regionInfo.getEncodedName(), regionInfo); 390 } 391 } 392 393 // Update Offline parents 394 for (RegionInfo regionInfo: parentRegions) { 395 Pair<String, String> daughters = parentsMap.get(regionInfo.getEncodedName()); 396 if (daughters == null) { 397 // The snapshot contains an unreferenced region. 398 // It will be removed by the CatalogJanitor. 399 LOG.warn("Skip update of unreferenced offline parent: " + regionInfo); 400 continue; 401 } 402 403 // One side of the split is already compacted 404 if (daughters.getSecond() == null) { 405 daughters.setSecond(daughters.getFirst()); 406 } 407 408 LOG.debug("Update splits parent " + regionInfo.getEncodedName() + " -> " + daughters); 409 MetaTableAccessor.addSplitsToParent(connection, regionInfo, 410 regionsByName.get(daughters.getFirst()), 411 regionsByName.get(daughters.getSecond())); 412 } 413 } 414 } 415 416 /** 417 * Remove specified regions from the file-system, using the archiver. 418 */ 419 private void removeHdfsRegions(final ThreadPoolExecutor exec, final List<RegionInfo> regions) 420 throws IOException { 421 if (regions == null || regions.isEmpty()) return; 422 ModifyRegionUtils.editRegions(exec, regions, new ModifyRegionUtils.RegionEditTask() { 423 @Override 424 public void editRegion(final RegionInfo hri) throws IOException { 425 HFileArchiver.archiveRegion(conf, fs, hri); 426 } 427 }); 428 } 429 430 /** 431 * Restore specified regions by restoring content to the snapshot state. 432 */ 433 private void restoreHdfsRegions(final ThreadPoolExecutor exec, 434 final Map<String, SnapshotRegionManifest> regionManifests, 435 final List<RegionInfo> regions) throws IOException { 436 if (regions == null || regions.isEmpty()) return; 437 ModifyRegionUtils.editRegions(exec, regions, new ModifyRegionUtils.RegionEditTask() { 438 @Override 439 public void editRegion(final RegionInfo hri) throws IOException { 440 restoreRegion(hri, regionManifests.get(hri.getEncodedName())); 441 } 442 }); 443 } 444 445 /** 446 * Restore specified mob regions by restoring content to the snapshot state. 447 */ 448 private void restoreHdfsMobRegions(final ThreadPoolExecutor exec, 449 final Map<String, SnapshotRegionManifest> regionManifests, 450 final List<RegionInfo> regions) throws IOException { 451 if (regions == null || regions.isEmpty()) return; 452 ModifyRegionUtils.editRegions(exec, regions, new ModifyRegionUtils.RegionEditTask() { 453 @Override 454 public void editRegion(final RegionInfo hri) throws IOException { 455 restoreMobRegion(hri, regionManifests.get(hri.getEncodedName())); 456 } 457 }); 458 } 459 460 private Map<String, List<SnapshotRegionManifest.StoreFile>> getRegionHFileReferences( 461 final SnapshotRegionManifest manifest) { 462 Map<String, List<SnapshotRegionManifest.StoreFile>> familyMap = 463 new HashMap<>(manifest.getFamilyFilesCount()); 464 for (SnapshotRegionManifest.FamilyFiles familyFiles: manifest.getFamilyFilesList()) { 465 familyMap.put(familyFiles.getFamilyName().toStringUtf8(), 466 new ArrayList<>(familyFiles.getStoreFilesList())); 467 } 468 return familyMap; 469 } 470 471 /** 472 * Restore region by removing files not in the snapshot 473 * and adding the missing ones from the snapshot. 474 */ 475 private void restoreRegion(final RegionInfo regionInfo, 476 final SnapshotRegionManifest regionManifest) throws IOException { 477 restoreRegion(regionInfo, regionManifest, new Path(tableDir, regionInfo.getEncodedName())); 478 } 479 480 /** 481 * Restore mob region by removing files not in the snapshot 482 * and adding the missing ones from the snapshot. 483 */ 484 private void restoreMobRegion(final RegionInfo regionInfo, 485 final SnapshotRegionManifest regionManifest) throws IOException { 486 if (regionManifest == null) { 487 return; 488 } 489 restoreRegion(regionInfo, regionManifest, 490 MobUtils.getMobRegionPath(conf, tableDesc.getTableName())); 491 } 492 493 /** 494 * Restore region by removing files not in the snapshot 495 * and adding the missing ones from the snapshot. 496 */ 497 private void restoreRegion(final RegionInfo regionInfo, 498 final SnapshotRegionManifest regionManifest, Path regionDir) throws IOException { 499 Map<String, List<SnapshotRegionManifest.StoreFile>> snapshotFiles = 500 getRegionHFileReferences(regionManifest); 501 502 String tableName = tableDesc.getTableName().getNameAsString(); 503 504 // Restore families present in the table 505 for (Path familyDir: FSUtils.getFamilyDirs(fs, regionDir)) { 506 byte[] family = Bytes.toBytes(familyDir.getName()); 507 Set<String> familyFiles = getTableRegionFamilyFiles(familyDir); 508 List<SnapshotRegionManifest.StoreFile> snapshotFamilyFiles = 509 snapshotFiles.remove(familyDir.getName()); 510 if (snapshotFamilyFiles != null) { 511 List<SnapshotRegionManifest.StoreFile> hfilesToAdd = new ArrayList<>(); 512 for (SnapshotRegionManifest.StoreFile storeFile: snapshotFamilyFiles) { 513 if (familyFiles.contains(storeFile.getName())) { 514 // HFile already present 515 familyFiles.remove(storeFile.getName()); 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 LOG.trace("Removing hfile=" + hfileName + 526 " from region=" + regionInfo.getEncodedName() + " table=" + tableName); 527 HFileArchiver.archiveStoreFile(conf, fs, regionInfo, tableDir, family, hfile); 528 } 529 530 // Restore Missing files 531 for (SnapshotRegionManifest.StoreFile storeFile: hfilesToAdd) { 532 LOG.debug("Adding HFileLink " + storeFile.getName() + 533 " to region=" + regionInfo.getEncodedName() + " table=" + tableName); 534 restoreStoreFile(familyDir, regionInfo, storeFile, createBackRefs); 535 } 536 } else { 537 // Family doesn't exists in the snapshot 538 LOG.trace("Removing family=" + Bytes.toString(family) + 539 " from region=" + regionInfo.getEncodedName() + " table=" + tableName); 540 HFileArchiver.archiveFamilyByFamilyDir(fs, conf, regionInfo, familyDir, family); 541 fs.delete(familyDir, true); 542 } 543 } 544 545 // Add families not present in the table 546 for (Map.Entry<String, List<SnapshotRegionManifest.StoreFile>> familyEntry: 547 snapshotFiles.entrySet()) { 548 Path familyDir = new Path(regionDir, familyEntry.getKey()); 549 if (!fs.mkdirs(familyDir)) { 550 throw new IOException("Unable to create familyDir=" + familyDir); 551 } 552 553 for (SnapshotRegionManifest.StoreFile storeFile: familyEntry.getValue()) { 554 LOG.trace("Adding HFileLink " + storeFile.getName() + " to table=" + tableName); 555 restoreStoreFile(familyDir, regionInfo, storeFile, createBackRefs); 556 } 557 } 558 } 559 560 /** 561 * @return The set of files in the specified family directory. 562 */ 563 private Set<String> getTableRegionFamilyFiles(final Path familyDir) throws IOException { 564 FileStatus[] hfiles = FSUtils.listStatus(fs, familyDir); 565 if (hfiles == null) return Collections.emptySet(); 566 567 Set<String> familyFiles = new HashSet<>(hfiles.length); 568 for (int i = 0; i < hfiles.length; ++i) { 569 String hfileName = hfiles[i].getPath().getName(); 570 familyFiles.add(hfileName); 571 } 572 573 return familyFiles; 574 } 575 576 /** 577 * Clone specified regions. For each region create a new region 578 * and create a HFileLink for each hfile. 579 */ 580 private RegionInfo[] cloneHdfsRegions(final ThreadPoolExecutor exec, 581 final Map<String, SnapshotRegionManifest> regionManifests, 582 final List<RegionInfo> regions) throws IOException { 583 if (regions == null || regions.isEmpty()) return null; 584 585 final Map<String, RegionInfo> snapshotRegions = new HashMap<>(regions.size()); 586 587 // clone region info (change embedded tableName with the new one) 588 RegionInfo[] clonedRegionsInfo = new RegionInfo[regions.size()]; 589 for (int i = 0; i < clonedRegionsInfo.length; ++i) { 590 // clone the region info from the snapshot region info 591 RegionInfo snapshotRegionInfo = regions.get(i); 592 clonedRegionsInfo[i] = cloneRegionInfo(snapshotRegionInfo); 593 594 // add the region name mapping between snapshot and cloned 595 String snapshotRegionName = snapshotRegionInfo.getEncodedName(); 596 String clonedRegionName = clonedRegionsInfo[i].getEncodedName(); 597 regionsMap.put(Bytes.toBytes(snapshotRegionName), Bytes.toBytes(clonedRegionName)); 598 LOG.info("clone region=" + snapshotRegionName + " as " + clonedRegionName); 599 600 // Add mapping between cloned region name and snapshot region info 601 snapshotRegions.put(clonedRegionName, snapshotRegionInfo); 602 } 603 604 // create the regions on disk 605 ModifyRegionUtils.createRegions(exec, conf, rootDir, 606 tableDesc, clonedRegionsInfo, new ModifyRegionUtils.RegionFillTask() { 607 @Override 608 public void fillRegion(final HRegion region) throws IOException { 609 RegionInfo snapshotHri = snapshotRegions.get(region.getRegionInfo().getEncodedName()); 610 cloneRegion(region, snapshotHri, regionManifests.get(snapshotHri.getEncodedName())); 611 } 612 }); 613 614 return clonedRegionsInfo; 615 } 616 617 /** 618 * Clone the mob region. For the region create a new region 619 * and create a HFileLink for each hfile. 620 */ 621 private void cloneHdfsMobRegion(final Map<String, SnapshotRegionManifest> regionManifests, 622 final RegionInfo region) throws IOException { 623 // clone region info (change embedded tableName with the new one) 624 Path clonedRegionPath = MobUtils.getMobRegionPath(rootDir, tableDesc.getTableName()); 625 cloneRegion(clonedRegionPath, region, regionManifests.get(region.getEncodedName())); 626 } 627 628 /** 629 * Clone region directory content from the snapshot info. 630 * 631 * Each region is encoded with the table name, so the cloned region will have 632 * a different region name. 633 * 634 * Instead of copying the hfiles a HFileLink is created. 635 * 636 * @param regionDir {@link Path} cloned dir 637 * @param snapshotRegionInfo 638 */ 639 private void cloneRegion(final Path regionDir, final RegionInfo snapshotRegionInfo, 640 final SnapshotRegionManifest manifest) throws IOException { 641 final String tableName = tableDesc.getTableName().getNameAsString(); 642 for (SnapshotRegionManifest.FamilyFiles familyFiles: manifest.getFamilyFilesList()) { 643 Path familyDir = new Path(regionDir, familyFiles.getFamilyName().toStringUtf8()); 644 for (SnapshotRegionManifest.StoreFile storeFile: familyFiles.getStoreFilesList()) { 645 LOG.info("Adding HFileLink " + storeFile.getName() + " to table=" + tableName); 646 restoreStoreFile(familyDir, snapshotRegionInfo, storeFile, createBackRefs); 647 } 648 } 649 } 650 651 /** 652 * Clone region directory content from the snapshot info. 653 * 654 * Each region is encoded with the table name, so the cloned region will have 655 * a different region name. 656 * 657 * Instead of copying the hfiles a HFileLink is created. 658 * 659 * @param region {@link HRegion} cloned 660 * @param snapshotRegionInfo 661 */ 662 private void cloneRegion(final HRegion region, final RegionInfo snapshotRegionInfo, 663 final SnapshotRegionManifest manifest) throws IOException { 664 cloneRegion(new Path(tableDir, region.getRegionInfo().getEncodedName()), snapshotRegionInfo, 665 manifest); 666 } 667 668 /** 669 * Create a new {@link HFileLink} to reference the store file. 670 * <p>The store file in the snapshot can be a simple hfile, an HFileLink or a reference. 671 * <ul> 672 * <li>hfile: abc -> table=region-abc 673 * <li>reference: abc.1234 -> table=region-abc.1234 674 * <li>hfilelink: table=region-hfile -> table=region-hfile 675 * </ul> 676 * @param familyDir destination directory for the store file 677 * @param regionInfo destination region info for the table 678 * @param createBackRef - Whether back reference should be created. Defaults to true. 679 * @param storeFile store file name (can be a Reference, HFileLink or simple HFile) 680 */ 681 private void restoreStoreFile(final Path familyDir, final RegionInfo regionInfo, 682 final SnapshotRegionManifest.StoreFile storeFile, final boolean createBackRef) 683 throws IOException { 684 String hfileName = storeFile.getName(); 685 if (HFileLink.isHFileLink(hfileName)) { 686 HFileLink.createFromHFileLink(conf, fs, familyDir, hfileName, createBackRef); 687 } else if (StoreFileInfo.isReference(hfileName)) { 688 restoreReferenceFile(familyDir, regionInfo, storeFile); 689 } else { 690 HFileLink.create(conf, fs, familyDir, regionInfo, hfileName, createBackRef); 691 } 692 } 693 694 /** 695 * Create a new {@link Reference} as copy of the source one. 696 * <p><blockquote><pre> 697 * The source table looks like: 698 * 1234/abc (original file) 699 * 5678/abc.1234 (reference file) 700 * 701 * After the clone operation looks like: 702 * wxyz/table=1234-abc 703 * stuv/table=1234-abc.wxyz 704 * 705 * NOTE that the region name in the clone changes (md5 of regioninfo) 706 * and the reference should reflect that change. 707 * </pre></blockquote> 708 * @param familyDir destination directory for the store file 709 * @param regionInfo destination region info for the table 710 * @param storeFile reference file name 711 */ 712 private void restoreReferenceFile(final Path familyDir, final RegionInfo regionInfo, 713 final SnapshotRegionManifest.StoreFile storeFile) throws IOException { 714 String hfileName = storeFile.getName(); 715 716 // Extract the referred information (hfile name and parent region) 717 Path refPath = 718 StoreFileInfo.getReferredToFile(new Path(new Path(new Path(new Path(snapshotTable 719 .getNamespaceAsString(), snapshotTable.getQualifierAsString()), regionInfo 720 .getEncodedName()), familyDir.getName()), hfileName)); 721 String snapshotRegionName = refPath.getParent().getParent().getName(); 722 String fileName = refPath.getName(); 723 724 // The new reference should have the cloned region name as parent, if it is a clone. 725 String clonedRegionName = Bytes.toString(regionsMap.get(Bytes.toBytes(snapshotRegionName))); 726 if (clonedRegionName == null) clonedRegionName = snapshotRegionName; 727 728 // The output file should be a reference link table=snapshotRegion-fileName.clonedRegionName 729 Path linkPath = null; 730 String refLink = fileName; 731 if (!HFileLink.isHFileLink(fileName)) { 732 refLink = HFileLink.createHFileLinkName(snapshotTable, snapshotRegionName, fileName); 733 linkPath = new Path(familyDir, 734 HFileLink.createHFileLinkName(snapshotTable, regionInfo.getEncodedName(), hfileName)); 735 } 736 737 Path outPath = new Path(familyDir, refLink + '.' + clonedRegionName); 738 739 // Create the new reference 740 if (storeFile.hasReference()) { 741 Reference reference = Reference.convert(storeFile.getReference()); 742 reference.write(fs, outPath); 743 } else { 744 InputStream in; 745 if (linkPath != null) { 746 in = HFileLink.buildFromHFileLinkPattern(conf, linkPath).open(fs); 747 } else { 748 linkPath = new Path(new Path(HRegion.getRegionDir(snapshotManifest.getSnapshotDir(), 749 regionInfo.getEncodedName()), familyDir.getName()), hfileName); 750 in = fs.open(linkPath); 751 } 752 OutputStream out = fs.create(outPath); 753 IOUtils.copyBytes(in, out, conf); 754 } 755 756 // Add the daughter region to the map 757 String regionName = Bytes.toString(regionsMap.get(regionInfo.getEncodedNameAsBytes())); 758 if (regionName == null) { 759 regionName = regionInfo.getEncodedName(); 760 } 761 LOG.debug("Restore reference " + regionName + " to " + clonedRegionName); 762 synchronized (parentsMap) { 763 Pair<String, String> daughters = parentsMap.get(clonedRegionName); 764 if (daughters == null) { 765 // In case one side of the split is already compacted, regionName is put as both first and 766 // second of Pair 767 daughters = new Pair<>(regionName, regionName); 768 parentsMap.put(clonedRegionName, daughters); 769 } else if (!regionName.equals(daughters.getFirst())) { 770 daughters.setSecond(regionName); 771 } 772 } 773 } 774 775 /** 776 * Create a new {@link RegionInfo} from the snapshot region info. 777 * Keep the same startKey, endKey, regionId and split information but change 778 * the table name. 779 * 780 * @param snapshotRegionInfo Info for region to clone. 781 * @return the new HRegion instance 782 */ 783 public RegionInfo cloneRegionInfo(final RegionInfo snapshotRegionInfo) { 784 return cloneRegionInfo(tableDesc.getTableName(), snapshotRegionInfo); 785 } 786 787 public static RegionInfo cloneRegionInfo(TableName tableName, RegionInfo snapshotRegionInfo) { 788 return RegionInfoBuilder.newBuilder(tableName) 789 .setStartKey(snapshotRegionInfo.getStartKey()) 790 .setEndKey(snapshotRegionInfo.getEndKey()) 791 .setSplit(snapshotRegionInfo.isSplit()) 792 .setRegionId(snapshotRegionInfo.getRegionId()) 793 .setOffline(snapshotRegionInfo.isOffline()) 794 .build(); 795 } 796 797 /** 798 * @return the set of the regions contained in the table 799 */ 800 private List<RegionInfo> getTableRegions() throws IOException { 801 LOG.debug("get table regions: " + tableDir); 802 FileStatus[] regionDirs = FSUtils.listStatus(fs, tableDir, new FSUtils.RegionDirFilter(fs)); 803 if (regionDirs == null) return null; 804 805 List<RegionInfo> regions = new ArrayList<>(regionDirs.length); 806 for (int i = 0; i < regionDirs.length; ++i) { 807 RegionInfo hri = HRegionFileSystem.loadRegionInfoFileContent(fs, regionDirs[i].getPath()); 808 regions.add(hri); 809 } 810 LOG.debug("found " + regions.size() + " regions for table=" + 811 tableDesc.getTableName().getNameAsString()); 812 return regions; 813 } 814 815 /** 816 * Copy the snapshot files for a snapshot scanner, discards meta changes. 817 * @param conf 818 * @param fs 819 * @param rootDir 820 * @param restoreDir 821 * @param snapshotName 822 * @throws IOException 823 */ 824 public static RestoreMetaChanges copySnapshotForScanner(Configuration conf, FileSystem fs, 825 Path rootDir, Path restoreDir, String snapshotName) throws IOException { 826 // ensure that restore dir is not under root dir 827 if (!restoreDir.getFileSystem(conf).getUri().equals(rootDir.getFileSystem(conf).getUri())) { 828 throw new IllegalArgumentException("Filesystems for restore directory and HBase root " + 829 "directory should be the same"); 830 } 831 if (restoreDir.toUri().getPath().startsWith(rootDir.toUri().getPath() +"/")) { 832 throw new IllegalArgumentException("Restore directory cannot be a sub directory of HBase " + 833 "root directory. RootDir: " + rootDir + ", restoreDir: " + restoreDir); 834 } 835 836 Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir); 837 SnapshotDescription snapshotDesc = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir); 838 SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, snapshotDesc); 839 840 MonitoredTask status = TaskMonitor.get().createStatus( 841 "Restoring snapshot '" + snapshotName + "' to directory " + restoreDir); 842 ForeignExceptionDispatcher monitor = new ForeignExceptionDispatcher(); 843 844 // we send createBackRefs=false so that restored hfiles do not create back reference links 845 // in the base hbase root dir. 846 RestoreSnapshotHelper helper = new RestoreSnapshotHelper(conf, fs, 847 manifest, manifest.getTableDescriptor(), restoreDir, monitor, status, false); 848 RestoreMetaChanges metaChanges = helper.restoreHdfsRegions(); // TODO: parallelize. 849 850 if (LOG.isDebugEnabled()) { 851 LOG.debug("Restored table dir:" + restoreDir); 852 FSUtils.logFileSystemState(fs, restoreDir, LOG); 853 } 854 return metaChanges; 855 } 856 857 public static void restoreSnapshotAcl(SnapshotDescription snapshot, TableName newTableName, 858 Configuration conf) throws IOException { 859 if (snapshot.hasUsersAndPermissions() && snapshot.getUsersAndPermissions() != null) { 860 LOG.info("Restore snapshot acl to table. snapshot: " + snapshot + ", table: " + newTableName); 861 ListMultimap<String, TablePermission> perms = 862 ShadedAccessControlUtil.toUserTablePermissions(snapshot.getUsersAndPermissions()); 863 try (Connection conn = ConnectionFactory.createConnection(conf)) { 864 for (Entry<String, TablePermission> e : perms.entries()) { 865 String user = e.getKey(); 866 TablePermission perm = e.getValue(); 867 perm.setTableName(newTableName); 868 AccessControlClient.grant(conn, perm.getTableName(), user, perm.getFamily(), 869 perm.getQualifier(), perm.getActions()); 870 } 871 } catch (Throwable e) { 872 throw new IOException("Grant acl into newly creatd table failed. snapshot: " + snapshot 873 + ", table: " + newTableName, e); 874 } 875 } 876 } 877}