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 public static final String SNAPSHOT_CORRUPTED_FILE = "_CORRUPTED"; 139 140 private SnapshotDescriptionUtils() { 141 // private constructor for utility class 142 } 143 144 /** 145 * @param conf {@link Configuration} from which to check for the timeout 146 * @param type type of snapshot being taken 147 * @param defaultMaxWaitTime Default amount of time to wait, if none is in the configuration 148 * @return the max amount of time the master should wait for a snapshot to complete 149 */ 150 public static long getMaxMasterTimeout(Configuration conf, SnapshotDescription.Type type, 151 long defaultMaxWaitTime) { 152 String confKey; 153 switch (type) { 154 case DISABLED: 155 default: 156 confKey = MASTER_SNAPSHOT_TIMEOUT_MILLIS; 157 } 158 return Math.max(conf.getLong(confKey, defaultMaxWaitTime), 159 conf.getLong(MASTER_SNAPSHOT_TIMEOUT_MILLIS, defaultMaxWaitTime)); 160 } 161 162 /** 163 * Get the snapshot root directory. All the snapshots are kept under this directory, i.e. 164 * ${hbase.rootdir}/.snapshot 165 * @param rootDir hbase root directory 166 * @return the base directory in which all snapshots are kept 167 */ 168 public static Path getSnapshotRootDir(final Path rootDir) { 169 return new Path(rootDir, HConstants.SNAPSHOT_DIR_NAME); 170 } 171 172 /** 173 * Get the directory for a specified snapshot. This directory is a sub-directory of snapshot root 174 * directory and all the data files for a snapshot are kept under this directory. 175 * @param snapshot snapshot being taken 176 * @param rootDir hbase root directory 177 * @return the final directory for the completed snapshot 178 */ 179 public static Path getCompletedSnapshotDir(final SnapshotDescription snapshot, 180 final Path rootDir) { 181 return getCompletedSnapshotDir(snapshot.getName(), rootDir); 182 } 183 184 /** 185 * Get the directory for a completed snapshot. This directory is a sub-directory of snapshot root 186 * directory and all the data files for a snapshot are kept under this directory. 187 * @param snapshotName name of the snapshot being taken 188 * @param rootDir hbase root directory 189 * @return the final directory for the completed snapshot 190 */ 191 public static Path getCompletedSnapshotDir(final String snapshotName, final Path rootDir) { 192 return getSpecifiedSnapshotDir(getSnapshotsDir(rootDir), snapshotName); 193 } 194 195 /** 196 * Get the general working directory for snapshots - where they are built, where they are 197 * temporarily copied on export, etc. 198 * @param rootDir root directory of the HBase installation 199 * @param conf Configuration of the HBase instance 200 * @return Path to the snapshot tmp directory, relative to the passed root directory 201 */ 202 public static Path getWorkingSnapshotDir(final Path rootDir, final Configuration conf) { 203 return new Path( 204 conf.get(SNAPSHOT_WORKING_DIR, getDefaultWorkingSnapshotDir(rootDir).toString())); 205 } 206 207 /** 208 * Get the directory to build a snapshot, before it is finalized 209 * @param snapshot snapshot that will be built 210 * @param rootDir root directory of the hbase installation 211 * @param conf Configuration of the HBase instance 212 * @return {@link Path} where one can build a snapshot 213 */ 214 public static Path getWorkingSnapshotDir(SnapshotDescription snapshot, final Path rootDir, 215 Configuration conf) { 216 return getWorkingSnapshotDir(snapshot.getName(), rootDir, conf); 217 } 218 219 /** 220 * Get the directory to build a snapshot, before it is finalized 221 * @param snapshotName name of the snapshot 222 * @param rootDir root directory of the hbase installation 223 * @param conf Configuration of the HBase instance 224 * @return {@link Path} where one can build a snapshot 225 */ 226 public static Path getWorkingSnapshotDir(String snapshotName, final Path rootDir, 227 Configuration conf) { 228 return getSpecifiedSnapshotDir(getWorkingSnapshotDir(rootDir, conf), snapshotName); 229 } 230 231 /** 232 * Get the flag file path if the snapshot is corrupted 233 * @param workingDir the directory where we build the specific snapshot 234 * @return {@link Path} snapshot corrupted flag file path 235 */ 236 public static Path getCorruptedFlagFileForSnapshot(final Path workingDir) { 237 return new Path(workingDir, SNAPSHOT_CORRUPTED_FILE); 238 } 239 240 /** 241 * Get the directory within the given filepath to store the snapshot instance 242 * @param snapshotsDir directory to store snapshot directory within 243 * @param snapshotName name of the snapshot to take 244 * @return the final directory for the snapshot in the given filepath 245 */ 246 private static final Path getSpecifiedSnapshotDir(final Path snapshotsDir, String snapshotName) { 247 return new Path(snapshotsDir, snapshotName); 248 } 249 250 /** 251 * @param rootDir hbase root directory 252 * @return the directory for all completed snapshots; 253 */ 254 public static final Path getSnapshotsDir(Path rootDir) { 255 return new Path(rootDir, HConstants.SNAPSHOT_DIR_NAME); 256 } 257 258 /** 259 * Determines if the given workingDir is a subdirectory of the given "root directory" 260 * @param workingDir a directory to check 261 * @param rootDir root directory of the HBase installation 262 * @return true if the given workingDir is a subdirectory of the given root directory, false 263 * otherwise 264 */ 265 public static boolean isSubDirectoryOf(final Path workingDir, final Path rootDir) { 266 return workingDir.toString().startsWith(rootDir.toString() + Path.SEPARATOR); 267 } 268 269 /** 270 * Determines if the given workingDir is a subdirectory of the default working snapshot directory 271 * @param workingDir a directory to check 272 * @param conf configuration for the HBase cluster 273 * @return true if the given workingDir is a subdirectory of the default working directory for 274 * snapshots, false otherwise 275 * @throws IOException if we can't get the root dir 276 */ 277 public static boolean isWithinDefaultWorkingDir(final Path workingDir, Configuration conf) 278 throws IOException { 279 Path defaultWorkingDir = getDefaultWorkingSnapshotDir(CommonFSUtils.getRootDir(conf)); 280 return workingDir.equals(defaultWorkingDir) || isSubDirectoryOf(workingDir, defaultWorkingDir); 281 } 282 283 /** 284 * Get the default working directory for snapshots - where they are built, where they are 285 * temporarily copied on export, etc. 286 * @param rootDir root directory of the HBase installation 287 * @return Path to the default snapshot tmp directory, relative to the passed root directory 288 */ 289 private static Path getDefaultWorkingSnapshotDir(final Path rootDir) { 290 return new Path(getSnapshotsDir(rootDir), SNAPSHOT_TMP_DIR_NAME); 291 } 292 293 /** 294 * Convert the passed snapshot description into a 'full' snapshot description based on default 295 * parameters, if none have been supplied. This resolves any 'optional' parameters that aren't 296 * supplied to their default values. 297 * @param snapshot general snapshot descriptor 298 * @param conf Configuration to read configured snapshot defaults if snapshot is not complete 299 * @return a valid snapshot description 300 * @throws IllegalArgumentException if the {@link SnapshotDescription} is not a complete 301 * {@link SnapshotDescription}. 302 */ 303 public static SnapshotDescription validate(SnapshotDescription snapshot, Configuration conf) 304 throws IllegalArgumentException, IOException { 305 if (!snapshot.hasTable()) { 306 throw new IllegalArgumentException( 307 "Descriptor doesn't apply to a table, so we can't build it."); 308 } 309 310 SnapshotDescription.Builder builder = snapshot.toBuilder(); 311 312 // set the creation time, if one hasn't been set 313 long time = snapshot.getCreationTime(); 314 if (time == SnapshotDescriptionUtils.NO_SNAPSHOT_START_TIME_SPECIFIED) { 315 time = EnvironmentEdgeManager.currentTime(); 316 LOG.debug("Creation time not specified, setting to:" + time + " (current time:" 317 + EnvironmentEdgeManager.currentTime() + ")."); 318 builder.setCreationTime(time); 319 } 320 321 long ttl = snapshot.getTtl(); 322 // set default ttl(sec) if it is not set already or the value is out of the range 323 if ( 324 ttl == SnapshotDescriptionUtils.NO_SNAPSHOT_TTL_SPECIFIED 325 || ttl > TimeUnit.MILLISECONDS.toSeconds(Long.MAX_VALUE) 326 ) { 327 final long defaultSnapshotTtl = 328 conf.getLong(HConstants.DEFAULT_SNAPSHOT_TTL_CONFIG_KEY, HConstants.DEFAULT_SNAPSHOT_TTL); 329 if (LOG.isDebugEnabled()) { 330 LOG.debug("Snapshot current TTL value: {} resetting it to default value: {}", ttl, 331 defaultSnapshotTtl); 332 } 333 ttl = defaultSnapshotTtl; 334 } 335 builder.setTtl(ttl); 336 337 if (!snapshot.hasVersion()) { 338 builder.setVersion(SnapshotDescriptionUtils.SNAPSHOT_LAYOUT_VERSION); 339 LOG.debug("Snapshot {} VERSION not specified, setting to {}", snapshot.getName(), 340 SnapshotDescriptionUtils.SNAPSHOT_LAYOUT_VERSION); 341 } 342 343 RpcServer.getRequestUser().ifPresent(user -> { 344 if (AccessChecker.isAuthorizationSupported(conf)) { 345 builder.setOwner(user.getShortName()); 346 LOG.debug("Set {} as owner of Snapshot", user.getShortName()); 347 } 348 }); 349 350 snapshot = builder.build(); 351 352 // set the acl to snapshot if security feature is enabled. 353 if (isSecurityAvailable(conf)) { 354 snapshot = writeAclToSnapshotDescription(snapshot, conf); 355 } 356 return snapshot; 357 } 358 359 /** 360 * Write the snapshot description into the working directory of a snapshot 361 * @param snapshot description of the snapshot being taken 362 * @param workingDir working directory of the snapshot 363 * @param fs {@link FileSystem} on which the snapshot should be taken 364 * @throws IOException if we can't reach the filesystem and the file cannot be cleaned up on 365 * failure 366 */ 367 public static void writeSnapshotInfo(SnapshotDescription snapshot, Path workingDir, FileSystem fs) 368 throws IOException { 369 FsPermission perms = 370 CommonFSUtils.getFilePermissions(fs, fs.getConf(), HConstants.DATA_FILE_UMASK_KEY); 371 Path snapshotInfo = new Path(workingDir, SnapshotDescriptionUtils.SNAPSHOTINFO_FILE); 372 try (FSDataOutputStream out = CommonFSUtils.create(fs, snapshotInfo, perms, true)) { 373 snapshot.writeTo(out); 374 } catch (IOException e) { 375 // if we get an exception, try to remove the snapshot info 376 if (!fs.delete(snapshotInfo, false)) { 377 String msg = "Couldn't delete snapshot info file: " + snapshotInfo; 378 LOG.error(msg); 379 throw new IOException(msg); 380 } 381 } 382 } 383 384 /** 385 * Read in the {@link SnapshotDescription} stored for the snapshot in the passed directory 386 * @param fs filesystem where the snapshot was taken 387 * @param snapshotDir directory where the snapshot was stored 388 * @return the stored snapshot description 389 * @throws CorruptedSnapshotException if the snapshot cannot be read 390 */ 391 public static SnapshotDescription readSnapshotInfo(FileSystem fs, Path snapshotDir) 392 throws CorruptedSnapshotException { 393 Path snapshotInfo = new Path(snapshotDir, SNAPSHOTINFO_FILE); 394 try (FSDataInputStream in = fs.open(snapshotInfo)) { 395 return SnapshotDescription.parseFrom(in); 396 } catch (IOException e) { 397 throw new CorruptedSnapshotException("Couldn't read snapshot info from:" + snapshotInfo, e); 398 } 399 } 400 401 /** 402 * Commits the snapshot process by moving the working snapshot to the finalized filepath 403 * @param snapshotDir The file path of the completed snapshots 404 * @param workingDir The file path of the in progress snapshots 405 * @param fs The file system of the completed snapshots 406 * @param workingDirFs The file system of the in progress snapshots 407 * @param conf Configuration 408 * @throws SnapshotCreationException if the snapshot could not be moved 409 * @throws IOException the filesystem could not be reached 410 */ 411 public static void completeSnapshot(Path snapshotDir, Path workingDir, FileSystem fs, 412 FileSystem workingDirFs, final Configuration conf) 413 throws SnapshotCreationException, IOException { 414 LOG.debug( 415 "Sentinel is done, just moving the snapshot from " + workingDir + " to " + snapshotDir); 416 // If the working and completed snapshot directory are on the same file system, attempt 417 // to rename the working snapshot directory to the completed location. If that fails, 418 // or the file systems differ, attempt to copy the directory over, throwing an exception 419 // if this fails 420 URI workingURI = workingDirFs.getUri(); 421 URI rootURI = fs.getUri(); 422 423 if ( 424 (shouldSkipRenameSnapshotDirectories(workingURI, rootURI) 425 || !fs.rename(workingDir, snapshotDir)) 426 && !FileUtil.copy(workingDirFs, workingDir, fs, snapshotDir, true, true, conf) 427 ) { 428 throw new SnapshotCreationException("Failed to copy working directory(" + workingDir 429 + ") to completed directory(" + snapshotDir + ")."); 430 } 431 } 432 433 static boolean shouldSkipRenameSnapshotDirectories(URI workingURI, URI rootURI) { 434 // check scheme, e.g. file, hdfs 435 if (workingURI.getScheme() == null && rootURI.getScheme() != null) { 436 return true; 437 } 438 if (workingURI.getScheme() != null && !workingURI.getScheme().equals(rootURI.getScheme())) { 439 return true; 440 } 441 442 // check Authority, e.g. localhost:port 443 if (workingURI.getAuthority() == null && rootURI.getAuthority() != null) { 444 return true; 445 } 446 if ( 447 workingURI.getAuthority() != null && !workingURI.getAuthority().equals(rootURI.getAuthority()) 448 ) { 449 return true; 450 } 451 452 // check UGI/userInfo 453 if (workingURI.getUserInfo() == null && rootURI.getUserInfo() != null) { 454 return true; 455 } 456 if ( 457 workingURI.getUserInfo() != null && !workingURI.getUserInfo().equals(rootURI.getUserInfo()) 458 ) { 459 return true; 460 } 461 return false; 462 } 463 464 /** 465 * Check if the user is this table snapshot's owner 466 * @param snapshot the table snapshot description 467 * @param user the user 468 * @return true if the user is the owner of the snapshot, false otherwise or the snapshot owner 469 * field is not present. 470 */ 471 public static boolean isSnapshotOwner(org.apache.hadoop.hbase.client.SnapshotDescription snapshot, 472 User user) { 473 if (user == null) return false; 474 return user.getShortName().equals(snapshot.getOwner()); 475 } 476 477 public static boolean isSecurityAvailable(Configuration conf) throws IOException { 478 try (Connection conn = ConnectionFactory.createConnection(conf); 479 Admin admin = conn.getAdmin()) { 480 return admin.tableExists(PermissionStorage.ACL_TABLE_NAME); 481 } 482 } 483 484 private static SnapshotDescription writeAclToSnapshotDescription(SnapshotDescription snapshot, 485 Configuration conf) throws IOException { 486 ListMultimap<String, UserPermission> perms = 487 User.runAsLoginUser(new PrivilegedExceptionAction<ListMultimap<String, UserPermission>>() { 488 @Override 489 public ListMultimap<String, UserPermission> run() throws Exception { 490 return PermissionStorage.getTablePermissions(conf, 491 TableName.valueOf(snapshot.getTable())); 492 } 493 }); 494 return snapshot.toBuilder() 495 .setUsersAndPermissions(ShadedAccessControlUtil.toUserTablePermissions(perms)).build(); 496 } 497 498 /** 499 * Method to check whether TTL has expired for specified snapshot creation time and snapshot ttl. 500 * NOTE: For backward compatibility (after the patch deployment on HMaster), any snapshot with ttl 501 * 0 is to be considered as snapshot to keep FOREVER. Default ttl value specified by 502 * {@link HConstants#DEFAULT_SNAPSHOT_TTL} 503 * @return true if ttl has expired, or, false, otherwise 504 */ 505 public static boolean isExpiredSnapshot(long snapshotTtl, long snapshotCreatedTime, 506 long currentTime) { 507 return snapshotCreatedTime > 0 && snapshotTtl > HConstants.DEFAULT_SNAPSHOT_TTL 508 && snapshotTtl < TimeUnit.MILLISECONDS.toSeconds(Long.MAX_VALUE) 509 && (snapshotCreatedTime + TimeUnit.SECONDS.toMillis(snapshotTtl)) < currentTime; 510 } 511}