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.backup;
019
020import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.CONF_CONTINUOUS_BACKUP_WAL_DIR;
021import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.LONG_OPTION_PITR_BACKUP_PATH;
022import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.LONG_OPTION_TO_DATETIME;
023import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_PITR_BACKUP_PATH;
024import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_PITR_BACKUP_PATH_DESC;
025import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_TO_DATETIME;
026import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_TO_DATETIME_DESC;
027
028import java.net.URI;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.fs.FileSystem;
031import org.apache.hadoop.fs.Path;
032import org.apache.hadoop.hbase.HBaseConfiguration;
033import org.apache.hadoop.hbase.TableName;
034import org.apache.hadoop.hbase.backup.impl.BackupAdminImpl;
035import org.apache.hadoop.hbase.backup.util.BackupUtils;
036import org.apache.hadoop.hbase.client.Connection;
037import org.apache.hadoop.hbase.client.ConnectionFactory;
038import org.apache.hadoop.hbase.util.CommonFSUtils;
039import org.apache.hadoop.util.ToolRunner;
040import org.apache.yetus.audience.InterfaceAudience;
041
042import org.apache.hbase.thirdparty.com.google.common.base.Strings;
043
044/**
045 * Command-line entry point for restore operation
046 */
047@InterfaceAudience.Private
048public class PointInTimeRestoreDriver extends AbstractRestoreDriver {
049  private static final String USAGE_STRING = """
050      Usage: hbase pitr [options]
051        <backup_path>   Backup Path to use for Point in Time Restore
052        table(s)        Comma-separated list of tables to restore
053      """;
054
055  @Override
056  protected int executeRestore(boolean check, TableName[] fromTables, TableName[] toTables,
057    boolean isOverwrite) {
058    String walBackupDir = getConf().get(CONF_CONTINUOUS_BACKUP_WAL_DIR);
059    if (Strings.isNullOrEmpty(walBackupDir)) {
060      System.err.printf(
061        "Point-in-Time Restore requires the WAL backup directory (%s) to replay logs after full and incremental backups. "
062          + "Set this property if you need Point-in-Time Restore. Otherwise, use the normal restore process with the appropriate backup ID.%n",
063        CONF_CONTINUOUS_BACKUP_WAL_DIR);
064      return -1;
065    }
066
067    String[] remainArgs = cmd.getArgs();
068    if (remainArgs.length != 0) {
069      printToolUsage();
070      return -1;
071    }
072
073    String backupRootDir = cmd.getOptionValue(OPTION_PITR_BACKUP_PATH);
074
075    try (final Connection conn = ConnectionFactory.createConnection(conf);
076      BackupAdmin client = new BackupAdminImpl(conn)) {
077      // Get the replication checkpoint (last known safe point for Continuous Backup)
078      long replicationCheckpoint = BackupUtils.getReplicationCheckpoint(conn);
079      long endTime = replicationCheckpoint;
080
081      if (cmd.hasOption(OPTION_TO_DATETIME)) {
082        String time = cmd.getOptionValue(OPTION_TO_DATETIME);
083        try {
084          endTime = Long.parseLong(time);
085          // Convert seconds to milliseconds if input is in seconds
086          if (endTime < 10_000_000_000L) {
087            endTime *= 1000;
088          }
089        } catch (NumberFormatException e) {
090          System.out.println("ERROR: Invalid timestamp format for --to-datetime: " + time);
091          printToolUsage();
092          return -5;
093        }
094      }
095
096      // Ensure the requested restore time does not exceed the replication checkpoint
097      if (endTime > replicationCheckpoint) {
098        LOG.error(
099          "ERROR: Requested restore time ({}) exceeds the last known safe replication checkpoint ({}). "
100            + "Please choose a time before this checkpoint to ensure data consistency.",
101          endTime, replicationCheckpoint);
102        return -5;
103      }
104
105      // TODO: Currently hardcoding keepOriginalSplits=false and restoreRootDir via tmp dir.
106      // These should come from user input (same issue exists in normal restore).
107      // Expose them as configurable options in future.
108      PointInTimeRestoreRequest pointInTimeRestoreRequest =
109        new PointInTimeRestoreRequest.Builder().withBackupRootDir(backupRootDir).withCheck(check)
110          .withFromTables(fromTables).withToTables(toTables).withOverwrite(isOverwrite)
111          .withToDateTime(endTime).withKeepOriginalSplits(false).withRestoreRootDir(
112            BackupUtils.getTmpRestoreOutputDir(FileSystem.get(conf), conf).toString())
113          .build();
114
115      client.pointInTimeRestore(pointInTimeRestoreRequest);
116    } catch (Exception e) {
117      LOG.error("Error while running restore backup", e);
118      return -5;
119    }
120    return 0;
121  }
122
123  @Override
124  protected void addOptions() {
125    super.addOptions();
126    addOptWithArg(OPTION_TO_DATETIME, LONG_OPTION_TO_DATETIME, OPTION_TO_DATETIME_DESC);
127    addOptWithArg(OPTION_PITR_BACKUP_PATH, LONG_OPTION_PITR_BACKUP_PATH,
128      OPTION_PITR_BACKUP_PATH_DESC);
129  }
130
131  public static void main(String[] args) throws Exception {
132    Configuration conf = HBaseConfiguration.create();
133    Path rootDir = CommonFSUtils.getRootDir(conf);
134    URI defaultFs = rootDir.getFileSystem(conf).getUri();
135    CommonFSUtils.setFsDefault(conf, new Path(defaultFs));
136    int ret = ToolRunner.run(conf, new PointInTimeRestoreDriver(), args);
137    System.exit(ret);
138  }
139
140  @Override
141  protected String getUsageString() {
142    return USAGE_STRING;
143  }
144}