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