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.net.URI; 022import java.security.PrivilegedExceptionAction; 023import java.util.Collections; 024import java.util.concurrent.TimeUnit; 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.fs.FSDataInputStream; 027import org.apache.hadoop.fs.FSDataOutputStream; 028import org.apache.hadoop.fs.FileSystem; 029import org.apache.hadoop.fs.FileUtil; 030import org.apache.hadoop.fs.Path; 031import org.apache.hadoop.fs.permission.FsPermission; 032import org.apache.hadoop.hbase.HConstants; 033import org.apache.hadoop.hbase.TableName; 034import org.apache.hadoop.hbase.client.Admin; 035import org.apache.hadoop.hbase.client.Connection; 036import org.apache.hadoop.hbase.client.ConnectionFactory; 037import org.apache.hadoop.hbase.ipc.RpcServer; 038import org.apache.hadoop.hbase.security.User; 039import org.apache.hadoop.hbase.security.access.AccessChecker; 040import org.apache.hadoop.hbase.security.access.PermissionStorage; 041import org.apache.hadoop.hbase.security.access.ShadedAccessControlUtil; 042import org.apache.hadoop.hbase.security.access.UserPermission; 043import org.apache.hadoop.hbase.util.CommonFSUtils; 044import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 045import org.apache.hadoop.hbase.util.FSUtils; 046import org.apache.yetus.audience.InterfaceAudience; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049 050import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap; 051 052import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription; 053 054/** 055 * Utility class to help manage {@link SnapshotDescription SnapshotDesriptions}. 056 * <p> 057 * Snapshots are laid out on disk like this: 058 * 059 * <pre> 060 * /hbase/.snapshots 061 * /.tmp <---- working directory 062 * /[snapshot name] <----- completed snapshot 063 * </pre> 064 * 065 * A completed snapshot named 'completed' then looks like (multiple regions, servers, files, etc. 066 * signified by '...' on the same directory depth). 067 * 068 * <pre> 069 * /hbase/.snapshots/completed 070 * .snapshotinfo <--- Description of the snapshot 071 * .tableinfo <--- Copy of the tableinfo 072 * /.logs 073 * /[server_name] 074 * /... [log files] 075 * ... 076 * /[region name] <---- All the region's information 077 * .regioninfo <---- Copy of the HRegionInfo 078 * /[column family name] 079 * /[hfile name] <--- name of the hfile in the real region 080 * ... 081 * ... 082 * ... 083 * </pre> 084 * 085 * Utility methods in this class are useful for getting the correct locations for different parts of 086 * the snapshot, as well as moving completed snapshots into place (see {@link #completeSnapshot}, 087 * and writing the {@link SnapshotDescription} to the working snapshot directory. 088 */ 089@InterfaceAudience.Private 090public final class SnapshotDescriptionUtils { 091 092 /** 093 * Filter that only accepts completed snapshot directories 094 */ 095 public static class CompletedSnaphotDirectoriesFilter extends FSUtils.BlackListDirFilter { 096 097 /** 098 * */ 099 public CompletedSnaphotDirectoriesFilter(FileSystem fs) { 100 super(fs, Collections.singletonList(SNAPSHOT_TMP_DIR_NAME)); 101 } 102 } 103 104 private static final Logger LOG = LoggerFactory.getLogger(SnapshotDescriptionUtils.class); 105 /** 106 * Version of the fs layout for a snapshot. Future snapshots may have different file layouts, 107 * which we may need to read in differently. 108 */ 109 public static final int SNAPSHOT_LAYOUT_VERSION = SnapshotManifestV2.DESCRIPTOR_VERSION; 110 111 // snapshot directory constants 112 /** 113 * The file contains the snapshot basic information and it is under the directory of a snapshot. 114 */ 115 public static final String SNAPSHOTINFO_FILE = ".snapshotinfo"; 116 117 /** Temporary directory under the snapshot directory to store in-progress snapshots */ 118 public static final String SNAPSHOT_TMP_DIR_NAME = ".tmp"; 119 120 /** 121 * The configuration property that determines the filepath of the snapshot base working directory 122 */ 123 public static final String SNAPSHOT_WORKING_DIR = "hbase.snapshot.working.dir"; 124 125 // snapshot operation values 126 /** Default value if no start time is specified */ 127 public static final long NO_SNAPSHOT_START_TIME_SPECIFIED = 0; 128 129 // Default value if no ttl is specified for Snapshot 130 private static final long NO_SNAPSHOT_TTL_SPECIFIED = 0; 131 132 public static final String MASTER_SNAPSHOT_TIMEOUT_MILLIS = 133 "hbase.snapshot.master.timeout.millis"; 134 135 /** By default, wait 300 seconds for a snapshot to complete */ 136 public static final long DEFAULT_MAX_WAIT_TIME = 60000 * 5; 137 138 /** 139 * By default, check to see if the snapshot is complete (ms) 140 * @deprecated Use {@link #DEFAULT_MAX_WAIT_TIME} instead. 141 */ 142 @Deprecated 143 public static final int SNAPSHOT_TIMEOUT_MILLIS_DEFAULT = 60000 * 5; 144 145 /** 146 * Conf key for # of ms elapsed before injecting a snapshot timeout error when waiting for 147 * completion. 148 * @deprecated Use {@link #MASTER_SNAPSHOT_TIMEOUT_MILLIS} instead. 149 */ 150 @Deprecated 151 public static final String SNAPSHOT_TIMEOUT_MILLIS_KEY = "hbase.snapshot.master.timeoutMillis"; 152 153 public static final String SNAPSHOT_CORRUPTED_FILE = "_CORRUPTED"; 154 155 private SnapshotDescriptionUtils() { 156 // private constructor for utility class 157 } 158 159 /** 160 * @param conf {@link Configuration} from which to check for the timeout 161 * @param type type of snapshot being taken 162 * @param defaultMaxWaitTime Default amount of time to wait, if none is in the configuration 163 * @return the max amount of time the master should wait for a snapshot to complete 164 */ 165 public static long getMaxMasterTimeout(Configuration conf, SnapshotDescription.Type type, 166 long defaultMaxWaitTime) { 167 String confKey; 168 switch (type) { 169 case DISABLED: 170 default: 171 confKey = MASTER_SNAPSHOT_TIMEOUT_MILLIS; 172 } 173 return Math.max(conf.getLong(confKey, defaultMaxWaitTime), 174 conf.getLong(SNAPSHOT_TIMEOUT_MILLIS_KEY, defaultMaxWaitTime)); 175 } 176 177 /** 178 * Get the snapshot root directory. All the snapshots are kept under this directory, i.e. 179 * ${hbase.rootdir}/.snapshot 180 * @param rootDir hbase root directory 181 * @return the base directory in which all snapshots are kept 182 */ 183 public static Path getSnapshotRootDir(final Path rootDir) { 184 return new Path(rootDir, HConstants.SNAPSHOT_DIR_NAME); 185 } 186 187 /** 188 * Get the directory for a specified snapshot. This directory is a sub-directory of snapshot root 189 * directory and all the data files for a snapshot are kept under this directory. 190 * @param snapshot snapshot being taken 191 * @param rootDir hbase root directory 192 * @return the final directory for the completed snapshot 193 */ 194 public static Path getCompletedSnapshotDir(final SnapshotDescription snapshot, 195 final Path rootDir) { 196 return getCompletedSnapshotDir(snapshot.getName(), rootDir); 197 } 198 199 /** 200 * Get the directory for a completed snapshot. This directory is a sub-directory of snapshot root 201 * directory and all the data files for a snapshot are kept under this directory. 202 * @param snapshotName name of the snapshot being taken 203 * @param rootDir hbase root directory 204 * @return the final directory for the completed snapshot 205 */ 206 public static Path getCompletedSnapshotDir(final String snapshotName, final Path rootDir) { 207 return getSpecifiedSnapshotDir(getSnapshotsDir(rootDir), snapshotName); 208 } 209 210 /** 211 * Get the general working directory for snapshots - where they are built, where they are 212 * temporarily copied on export, etc. 213 * @param rootDir root directory of the HBase installation 214 * @param conf Configuration of the HBase instance 215 * @return Path to the snapshot tmp directory, relative to the passed root directory 216 */ 217 public static Path getWorkingSnapshotDir(final Path rootDir, final Configuration conf) { 218 return new Path( 219 conf.get(SNAPSHOT_WORKING_DIR, getDefaultWorkingSnapshotDir(rootDir).toString())); 220 } 221 222 /** 223 * Get the directory to build a snapshot, before it is finalized 224 * @param snapshot snapshot that will be built 225 * @param rootDir root directory of the hbase installation 226 * @param conf Configuration of the HBase instance 227 * @return {@link Path} where one can build a snapshot 228 */ 229 public static Path getWorkingSnapshotDir(SnapshotDescription snapshot, final Path rootDir, 230 Configuration conf) { 231 return getWorkingSnapshotDir(snapshot.getName(), rootDir, conf); 232 } 233 234 /** 235 * Get the directory to build a snapshot, before it is finalized 236 * @param snapshotName name of the snapshot 237 * @param rootDir root directory of the hbase installation 238 * @param conf Configuration of the HBase instance 239 * @return {@link Path} where one can build a snapshot 240 */ 241 public static Path getWorkingSnapshotDir(String snapshotName, final Path rootDir, 242 Configuration conf) { 243 return getSpecifiedSnapshotDir(getWorkingSnapshotDir(rootDir, conf), snapshotName); 244 } 245 246 /** 247 * Get the flag file path if the snapshot is corrupted 248 * @param workingDir the directory where we build the specific snapshot 249 * @return {@link Path} snapshot corrupted flag file path 250 */ 251 public static Path getCorruptedFlagFileForSnapshot(final Path workingDir) { 252 return new Path(workingDir, SNAPSHOT_CORRUPTED_FILE); 253 } 254 255 /** 256 * Get the directory within the given filepath to store the snapshot instance 257 * @param snapshotsDir directory to store snapshot directory within 258 * @param snapshotName name of the snapshot to take 259 * @return the final directory for the snapshot in the given filepath 260 */ 261 private static final Path getSpecifiedSnapshotDir(final Path snapshotsDir, String snapshotName) { 262 return new Path(snapshotsDir, snapshotName); 263 } 264 265 /** 266 * @param rootDir hbase root directory 267 * @return the directory for all completed snapshots; 268 */ 269 public static final Path getSnapshotsDir(Path rootDir) { 270 return new Path(rootDir, HConstants.SNAPSHOT_DIR_NAME); 271 } 272 273 /** 274 * Determines if the given workingDir is a subdirectory of the given "root directory" 275 * @param workingDir a directory to check 276 * @param rootDir root directory of the HBase installation 277 * @return true if the given workingDir is a subdirectory of the given root directory, false 278 * otherwise 279 */ 280 public static boolean isSubDirectoryOf(final Path workingDir, final Path rootDir) { 281 return workingDir.toString().startsWith(rootDir.toString() + Path.SEPARATOR); 282 } 283 284 /** 285 * Determines if the given workingDir is a subdirectory of the default working snapshot directory 286 * @param workingDir a directory to check 287 * @param conf configuration for the HBase cluster 288 * @return true if the given workingDir is a subdirectory of the default working directory for 289 * snapshots, false otherwise 290 * @throws IOException if we can't get the root dir 291 */ 292 public static boolean isWithinDefaultWorkingDir(final Path workingDir, Configuration conf) 293 throws IOException { 294 Path defaultWorkingDir = getDefaultWorkingSnapshotDir(CommonFSUtils.getRootDir(conf)); 295 return workingDir.equals(defaultWorkingDir) || isSubDirectoryOf(workingDir, defaultWorkingDir); 296 } 297 298 /** 299 * Get the default working directory for snapshots - where they are built, where they are 300 * temporarily copied on export, etc. 301 * @param rootDir root directory of the HBase installation 302 * @return Path to the default snapshot tmp directory, relative to the passed root directory 303 */ 304 private static Path getDefaultWorkingSnapshotDir(final Path rootDir) { 305 return new Path(getSnapshotsDir(rootDir), SNAPSHOT_TMP_DIR_NAME); 306 } 307 308 /** 309 * Convert the passed snapshot description into a 'full' snapshot description based on default 310 * parameters, if none have been supplied. This resolves any 'optional' parameters that aren't 311 * supplied to their default values. 312 * @param snapshot general snapshot descriptor 313 * @param conf Configuration to read configured snapshot defaults if snapshot is not complete 314 * @return a valid snapshot description 315 * @throws IllegalArgumentException if the {@link SnapshotDescription} is not a complete 316 * {@link SnapshotDescription}. 317 */ 318 public static SnapshotDescription validate(SnapshotDescription snapshot, Configuration conf) 319 throws IllegalArgumentException, IOException { 320 if (!snapshot.hasTable()) { 321 throw new IllegalArgumentException( 322 "Descriptor doesn't apply to a table, so we can't build it."); 323 } 324 325 SnapshotDescription.Builder builder = snapshot.toBuilder(); 326 327 // set the creation time, if one hasn't been set 328 long time = snapshot.getCreationTime(); 329 if (time == SnapshotDescriptionUtils.NO_SNAPSHOT_START_TIME_SPECIFIED) { 330 time = EnvironmentEdgeManager.currentTime(); 331 LOG.debug("Creation time not specified, setting to:" + time + " (current time:" 332 + EnvironmentEdgeManager.currentTime() + ")."); 333 builder.setCreationTime(time); 334 } 335 336 long ttl = snapshot.getTtl(); 337 // set default ttl(sec) if it is not set already or the value is out of the range 338 if ( 339 ttl == SnapshotDescriptionUtils.NO_SNAPSHOT_TTL_SPECIFIED 340 || ttl > TimeUnit.MILLISECONDS.toSeconds(Long.MAX_VALUE) 341 ) { 342 final long defaultSnapshotTtl = 343 conf.getLong(HConstants.DEFAULT_SNAPSHOT_TTL_CONFIG_KEY, HConstants.DEFAULT_SNAPSHOT_TTL); 344 if (LOG.isDebugEnabled()) { 345 LOG.debug("Snapshot current TTL value: {} resetting it to default value: {}", ttl, 346 defaultSnapshotTtl); 347 } 348 ttl = defaultSnapshotTtl; 349 } 350 builder.setTtl(ttl); 351 352 if (!snapshot.hasVersion()) { 353 builder.setVersion(SnapshotDescriptionUtils.SNAPSHOT_LAYOUT_VERSION); 354 LOG.debug("Snapshot {} VERSION not specified, setting to {}", snapshot.getName(), 355 SnapshotDescriptionUtils.SNAPSHOT_LAYOUT_VERSION); 356 } 357 358 RpcServer.getRequestUser().ifPresent(user -> { 359 if (AccessChecker.isAuthorizationSupported(conf)) { 360 builder.setOwner(user.getShortName()); 361 LOG.debug("Set {} as owner of Snapshot", user.getShortName()); 362 } 363 }); 364 365 snapshot = builder.build(); 366 367 // set the acl to snapshot if security feature is enabled. 368 if (isSecurityAvailable(conf)) { 369 snapshot = writeAclToSnapshotDescription(snapshot, conf); 370 } 371 return snapshot; 372 } 373 374 /** 375 * Write the snapshot description into the working directory of a snapshot 376 * @param snapshot description of the snapshot being taken 377 * @param workingDir working directory of the snapshot 378 * @param fs {@link FileSystem} on which the snapshot should be taken 379 * @throws IOException if we can't reach the filesystem and the file cannot be cleaned up on 380 * failure 381 */ 382 public static void writeSnapshotInfo(SnapshotDescription snapshot, Path workingDir, FileSystem fs) 383 throws IOException { 384 FsPermission perms = 385 CommonFSUtils.getFilePermissions(fs, fs.getConf(), HConstants.DATA_FILE_UMASK_KEY); 386 Path snapshotInfo = new Path(workingDir, SnapshotDescriptionUtils.SNAPSHOTINFO_FILE); 387 try (FSDataOutputStream out = CommonFSUtils.create(fs, snapshotInfo, perms, true)) { 388 snapshot.writeTo(out); 389 } catch (IOException e) { 390 // if we get an exception, try to remove the snapshot info 391 if (!fs.delete(snapshotInfo, false)) { 392 String msg = "Couldn't delete snapshot info file: " + snapshotInfo; 393 LOG.error(msg); 394 throw new IOException(msg); 395 } 396 } 397 } 398 399 /** 400 * Read in the {@link org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription} 401 * stored for the snapshot in the passed directory 402 * @param fs filesystem where the snapshot was taken 403 * @param snapshotDir directory where the snapshot was stored 404 * @return the stored snapshot description 405 * @throws CorruptedSnapshotException if the snapshot cannot be read 406 */ 407 public static SnapshotDescription readSnapshotInfo(FileSystem fs, Path snapshotDir) 408 throws CorruptedSnapshotException { 409 Path snapshotInfo = new Path(snapshotDir, SNAPSHOTINFO_FILE); 410 try (FSDataInputStream in = fs.open(snapshotInfo)) { 411 return SnapshotDescription.parseFrom(in); 412 } catch (IOException e) { 413 throw new CorruptedSnapshotException("Couldn't read snapshot info from:" + snapshotInfo, e); 414 } 415 } 416 417 /** 418 * Commits the snapshot process by moving the working snapshot to the finalized filepath 419 * @param snapshotDir The file path of the completed snapshots 420 * @param workingDir The file path of the in progress snapshots 421 * @param fs The file system of the completed snapshots 422 * @param workingDirFs The file system of the in progress snapshots 423 * @param conf Configuration 424 * @throws SnapshotCreationException if the snapshot could not be moved 425 * @throws IOException the filesystem could not be reached 426 */ 427 public static void completeSnapshot(Path snapshotDir, Path workingDir, FileSystem fs, 428 FileSystem workingDirFs, final Configuration conf) 429 throws SnapshotCreationException, IOException { 430 LOG.debug( 431 "Sentinel is done, just moving the snapshot from " + workingDir + " to " + snapshotDir); 432 // If the working and completed snapshot directory are on the same file system, attempt 433 // to rename the working snapshot directory to the completed location. If that fails, 434 // or the file systems differ, attempt to copy the directory over, throwing an exception 435 // if this fails 436 URI workingURI = workingDirFs.getUri(); 437 URI rootURI = fs.getUri(); 438 439 if ( 440 (shouldSkipRenameSnapshotDirectories(workingURI, rootURI) 441 || !fs.rename(workingDir, snapshotDir)) 442 && !FileUtil.copy(workingDirFs, workingDir, fs, snapshotDir, true, true, conf) 443 ) { 444 throw new SnapshotCreationException("Failed to copy working directory(" + workingDir 445 + ") to completed directory(" + snapshotDir + ")."); 446 } 447 } 448 449 static boolean shouldSkipRenameSnapshotDirectories(URI workingURI, URI rootURI) { 450 // check scheme, e.g. file, hdfs 451 if (workingURI.getScheme() == null && rootURI.getScheme() != null) { 452 return true; 453 } 454 if (workingURI.getScheme() != null && !workingURI.getScheme().equals(rootURI.getScheme())) { 455 return true; 456 } 457 458 // check Authority, e.g. localhost:port 459 if (workingURI.getAuthority() == null && rootURI.getAuthority() != null) { 460 return true; 461 } 462 if ( 463 workingURI.getAuthority() != null && !workingURI.getAuthority().equals(rootURI.getAuthority()) 464 ) { 465 return true; 466 } 467 468 // check UGI/userInfo 469 if (workingURI.getUserInfo() == null && rootURI.getUserInfo() != null) { 470 return true; 471 } 472 if ( 473 workingURI.getUserInfo() != null && !workingURI.getUserInfo().equals(rootURI.getUserInfo()) 474 ) { 475 return true; 476 } 477 return false; 478 } 479 480 /** 481 * Check if the user is this table snapshot's owner 482 * @param snapshot the table snapshot description 483 * @param user the user 484 * @return true if the user is the owner of the snapshot, false otherwise or the snapshot owner 485 * field is not present. 486 */ 487 public static boolean isSnapshotOwner(org.apache.hadoop.hbase.client.SnapshotDescription snapshot, 488 User user) { 489 if (user == null) return false; 490 return user.getShortName().equals(snapshot.getOwner()); 491 } 492 493 public static boolean isSecurityAvailable(Configuration conf) throws IOException { 494 try (Connection conn = ConnectionFactory.createConnection(conf); 495 Admin admin = conn.getAdmin()) { 496 return admin.tableExists(PermissionStorage.ACL_TABLE_NAME); 497 } 498 } 499 500 private static SnapshotDescription writeAclToSnapshotDescription(SnapshotDescription snapshot, 501 Configuration conf) throws IOException { 502 ListMultimap<String, UserPermission> perms = 503 User.runAsLoginUser(new PrivilegedExceptionAction<ListMultimap<String, UserPermission>>() { 504 @Override 505 public ListMultimap<String, UserPermission> run() throws Exception { 506 return PermissionStorage.getTablePermissions(conf, 507 TableName.valueOf(snapshot.getTable())); 508 } 509 }); 510 return snapshot.toBuilder() 511 .setUsersAndPermissions(ShadedAccessControlUtil.toUserTablePermissions(perms)).build(); 512 } 513 514 /** 515 * Method to check whether TTL has expired for specified snapshot creation time and snapshot ttl. 516 * NOTE: For backward compatibility (after the patch deployment on HMaster), any snapshot with ttl 517 * 0 is to be considered as snapshot to keep FOREVER. Default ttl value specified by 518 * {@link HConstants#DEFAULT_SNAPSHOT_TTL} 519 * @return true if ttl has expired, or, false, otherwise 520 */ 521 public static boolean isExpiredSnapshot(long snapshotTtl, long snapshotCreatedTime, 522 long currentTime) { 523 return snapshotCreatedTime > 0 && snapshotTtl > HConstants.DEFAULT_SNAPSHOT_TTL 524 && snapshotTtl < TimeUnit.MILLISECONDS.toSeconds(Long.MAX_VALUE) 525 && (snapshotCreatedTime + TimeUnit.SECONDS.toMillis(snapshotTtl)) < currentTime; 526 } 527}