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 java.io.IOException;
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026import java.util.Map.Entry;
027import java.util.Objects;
028import org.apache.hadoop.conf.Configuration;
029import org.apache.hadoop.fs.FileStatus;
030import org.apache.hadoop.fs.FileSystem;
031import org.apache.hadoop.fs.LocatedFileStatus;
032import org.apache.hadoop.fs.Path;
033import org.apache.hadoop.fs.RemoteIterator;
034import org.apache.hadoop.hbase.HBaseConfiguration;
035import org.apache.hadoop.hbase.HBaseTestingUtil;
036import org.apache.hadoop.hbase.HConstants;
037import org.apache.hadoop.hbase.NamespaceDescriptor;
038import org.apache.hadoop.hbase.TableName;
039import org.apache.hadoop.hbase.backup.BackupInfo.BackupPhase;
040import org.apache.hadoop.hbase.backup.BackupInfo.BackupState;
041import org.apache.hadoop.hbase.backup.impl.BackupAdminImpl;
042import org.apache.hadoop.hbase.backup.impl.BackupManager;
043import org.apache.hadoop.hbase.backup.impl.BackupSystemTable;
044import org.apache.hadoop.hbase.backup.impl.FullTableBackupClient;
045import org.apache.hadoop.hbase.backup.impl.IncrementalBackupManager;
046import org.apache.hadoop.hbase.backup.impl.IncrementalTableBackupClient;
047import org.apache.hadoop.hbase.backup.util.BackupUtils;
048import org.apache.hadoop.hbase.client.Admin;
049import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
050import org.apache.hadoop.hbase.client.Connection;
051import org.apache.hadoop.hbase.client.ConnectionFactory;
052import org.apache.hadoop.hbase.client.Durability;
053import org.apache.hadoop.hbase.client.Put;
054import org.apache.hadoop.hbase.client.Table;
055import org.apache.hadoop.hbase.client.TableDescriptor;
056import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
057import org.apache.hadoop.hbase.master.cleaner.LogCleaner;
058import org.apache.hadoop.hbase.master.cleaner.TimeToLiveLogCleaner;
059import org.apache.hadoop.hbase.regionserver.LogRoller;
060import org.apache.hadoop.hbase.security.HadoopSecurityEnabledUserProviderForTesting;
061import org.apache.hadoop.hbase.security.UserProvider;
062import org.apache.hadoop.hbase.security.access.SecureTestUtil;
063import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils;
064import org.apache.hadoop.hbase.util.Bytes;
065import org.apache.hadoop.hbase.util.CommonFSUtils;
066import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
067import org.apache.hadoop.hbase.wal.AbstractFSWALProvider;
068import org.apache.hadoop.hbase.wal.WALFactory;
069import org.junit.jupiter.api.AfterAll;
070import org.junit.jupiter.api.BeforeAll;
071import org.junit.jupiter.api.BeforeEach;
072import org.slf4j.Logger;
073import org.slf4j.LoggerFactory;
074
075/**
076 * This class is only a base for other integration-level backup tests. Do not add tests here.
077 * TestBackupSmallTests is where tests that don't require bring machines up/down should go All other
078 * tests should have their own classes and extend this one
079 */
080public class TestBackupBase {
081  private static final Logger LOG = LoggerFactory.getLogger(TestBackupBase.class);
082
083  protected static HBaseTestingUtil TEST_UTIL;
084  protected static HBaseTestingUtil TEST_UTIL2;
085  protected static Configuration conf1;
086  protected static Configuration conf2;
087
088  protected static TableName table1 = TableName.valueOf("table1");
089  protected static TableDescriptor table1Desc;
090  protected static TableName table2 = TableName.valueOf("table2");
091  protected static TableName table3 = TableName.valueOf("table3");
092  protected static TableName table4 = TableName.valueOf("table4");
093
094  protected static TableName table1_restore = TableName.valueOf("default:table1");
095  protected static TableName table2_restore = TableName.valueOf("ns2:table2");
096  protected static TableName table3_restore = TableName.valueOf("ns3:table3_restore");
097
098  protected static final int NB_ROWS_IN_BATCH = 99;
099  protected static final byte[] qualName = Bytes.toBytes("q1");
100  protected static final byte[] famName = Bytes.toBytes("f");
101
102  protected static String BACKUP_ROOT_DIR;
103  protected static String BACKUP_REMOTE_ROOT_DIR;
104  protected static String provider = "defaultProvider";
105  protected static boolean secure = false;
106
107  protected static boolean autoRestoreOnFailure;
108  protected static boolean useSecondCluster;
109
110  static class IncrementalTableBackupClientForTest extends IncrementalTableBackupClient {
111    public IncrementalTableBackupClientForTest() {
112    }
113
114    public IncrementalTableBackupClientForTest(Connection conn, String backupId,
115      BackupRequest request) throws IOException {
116      super(conn, backupId, request);
117    }
118
119    @BeforeEach
120    public void ensurePreviousBackupTestsAreCleanedUp() throws Exception {
121      // Every operation here may not be necessary for any given test,
122      // some often being no-ops. the goal is to help ensure atomicity
123      // of that tests that implement TestBackupBase
124      try (BackupAdmin backupAdmin = getBackupAdmin()) {
125        backupManager.finishBackupSession();
126        backupAdmin.listBackupSets().forEach(backupSet -> {
127          try {
128            backupAdmin.deleteBackupSet(backupSet.getName());
129          } catch (IOException ignored) {
130          }
131        });
132      } catch (Exception ignored) {
133      }
134      Arrays.stream(TEST_UTIL.getAdmin().listTableNames())
135        .filter(tableName -> !tableName.isSystemTable()).forEach(tableName -> {
136          try {
137            TEST_UTIL.truncateTable(tableName);
138          } catch (IOException ignored) {
139          }
140        });
141      TEST_UTIL.getMiniHBaseCluster().getRegionServerThreads().forEach(rst -> {
142        try {
143          LogRoller walRoller = rst.getRegionServer().getWalRoller();
144          walRoller.requestRollAll();
145          walRoller.waitUntilWalRollFinished();
146        } catch (Exception ignored) {
147        }
148      });
149    }
150
151    @Override
152    public void execute() throws IOException {
153      // case INCREMENTAL_COPY:
154      try {
155        // case PREPARE_INCREMENTAL:
156        failStageIf(Stage.stage_0);
157        beginBackup(backupManager, backupInfo);
158
159        failStageIf(Stage.stage_1);
160        backupInfo.setPhase(BackupPhase.PREPARE_INCREMENTAL);
161        LOG.debug("For incremental backup, current table set is "
162          + backupManager.getIncrementalBackupTableSet());
163        newTimestamps = ((IncrementalBackupManager) backupManager).getIncrBackupLogFileMap();
164        // copy out the table and region info files for each table
165        BackupUtils.copyTableRegionInfo(conn, backupInfo, conf);
166        // convert WAL to HFiles and copy them to .tmp under BACKUP_ROOT
167        convertWALsToHFiles();
168        incrementalCopyHFiles(new String[] { getBulkOutputDir().toString() },
169          backupInfo.getBackupRootDir());
170        failStageIf(Stage.stage_2);
171
172        // case INCR_BACKUP_COMPLETE:
173        // set overall backup status: complete. Here we make sure to complete the backup.
174        // After this checkpoint, even if entering cancel process, will let the backup finished
175        // Set the previousTimestampMap which is before this current log roll to the manifest.
176        Map<TableName, Map<String, Long>> previousTimestampMap =
177          backupManager.readLogTimestampMap();
178        backupInfo.setIncrTimestampMap(previousTimestampMap);
179
180        // The table list in backupInfo is good for both full backup and incremental backup.
181        // For incremental backup, it contains the incremental backup table set.
182        backupManager.writeRegionServerLogTimestamp(backupInfo.getTables(), newTimestamps);
183        failStageIf(Stage.stage_3);
184
185        Map<TableName, Map<String, Long>> newTableSetTimestampMap =
186          backupManager.readLogTimestampMap();
187
188        handleBulkLoad(backupInfo.getTableNames());
189        failStageIf(Stage.stage_4);
190
191        // backup complete
192        backupInfo.setTableSetTimestampMap(newTableSetTimestampMap);
193        completeBackup(conn, backupInfo, BackupType.INCREMENTAL, conf);
194
195      } catch (Exception e) {
196        failBackup(conn, backupInfo, backupManager, e, "Unexpected Exception : ",
197          BackupType.INCREMENTAL, conf);
198        throw new IOException(e);
199      }
200    }
201  }
202
203  static class FullTableBackupClientForTest extends FullTableBackupClient {
204    public FullTableBackupClientForTest() {
205    }
206
207    public FullTableBackupClientForTest(Connection conn, String backupId, BackupRequest request)
208      throws IOException {
209      super(conn, backupId, request);
210    }
211
212    @Override
213    public void execute() throws IOException {
214      // Get the stage ID to fail on
215      try (Admin admin = conn.getAdmin()) {
216        // Begin BACKUP
217        beginBackup(backupManager, backupInfo);
218        failStageIf(Stage.stage_0);
219        // do snapshot for full table backup
220        failStageIf(Stage.stage_1);
221        // We roll log here before we do the snapshot. It is possible there is duplicate data
222        // in the log that is already in the snapshot. But if we do it after the snapshot, we
223        // could have data loss.
224        // A better approach is to do the roll log on each RS in the same global procedure as
225        // the snapshot.
226        LOG.info("Execute roll log procedure for full backup ...");
227
228        BackupUtils.logRoll(conn, backupInfo.getBackupRootDir(), conf);
229        failStageIf(Stage.stage_2);
230        newTimestamps = backupManager.readRegionServerLastLogRollResult();
231
232        // SNAPSHOT_TABLES:
233        backupInfo.setPhase(BackupPhase.SNAPSHOT);
234        for (TableName tableName : tableList) {
235          String snapshotName = "snapshot_" + Long.toString(EnvironmentEdgeManager.currentTime())
236            + "_" + tableName.getNamespaceAsString() + "_" + tableName.getQualifierAsString();
237
238          snapshotTable(admin, tableName, snapshotName);
239          backupInfo.setSnapshotName(tableName, snapshotName);
240        }
241        failStageIf(Stage.stage_3);
242        // SNAPSHOT_COPY:
243        // do snapshot copy
244        LOG.debug("snapshot copy for " + backupId);
245        snapshotCopy(backupInfo);
246        // Updates incremental backup table set
247        backupManager.addIncrementalBackupTableSet(backupInfo.getTables());
248
249        // BACKUP_COMPLETE:
250        // set overall backup status: complete. Here we make sure to complete the backup.
251        // After this checkpoint, even if entering cancel process, will let the backup finished
252        backupInfo.setState(BackupState.COMPLETE);
253        // The table list in backupInfo is good for both full backup and incremental backup.
254        // For incremental backup, it contains the incremental backup table set.
255        backupManager.writeRegionServerLogTimestamp(backupInfo.getTables(), newTimestamps);
256
257        Map<TableName, Map<String, Long>> newTableSetTimestampMap =
258          backupManager.readLogTimestampMap();
259
260        failStageIf(Stage.stage_4);
261        // backup complete
262        backupInfo.setTableSetTimestampMap(newTableSetTimestampMap);
263        completeBackup(conn, backupInfo, BackupType.FULL, conf);
264
265      } catch (Exception e) {
266
267        if (autoRestoreOnFailure) {
268          failBackup(conn, backupInfo, backupManager, e, "Unexpected BackupException : ",
269            BackupType.FULL, conf);
270        }
271        throw new IOException(e);
272      }
273    }
274  }
275
276  public static void setUpHelper() throws Exception {
277    BACKUP_ROOT_DIR = Path.SEPARATOR + "backupUT";
278    BACKUP_REMOTE_ROOT_DIR = Path.SEPARATOR + "backupUT";
279
280    if (secure) {
281      // set the always on security provider
282      UserProvider.setUserProviderForTesting(TEST_UTIL.getConfiguration(),
283        HadoopSecurityEnabledUserProviderForTesting.class);
284      // setup configuration
285      SecureTestUtil.enableSecurity(TEST_UTIL.getConfiguration());
286    }
287    conf1.setBoolean(BackupRestoreConstants.BACKUP_ENABLE_KEY, true);
288    BackupManager.decorateMasterConfiguration(conf1);
289    BackupManager.decorateRegionServerConfiguration(conf1);
290    conf1.set(HConstants.ZOOKEEPER_ZNODE_PARENT, "/1");
291    // Set TTL for old WALs to 1 sec to enforce fast cleaning of an archived
292    // WAL files
293    conf1.setLong(TimeToLiveLogCleaner.TTL_CONF_KEY, 1000);
294    conf1.setLong(LogCleaner.OLD_WALS_CLEANER_THREAD_TIMEOUT_MSEC, 1000);
295
296    // Set MultiWAL (with 2 default WAL files per RS)
297    conf1.set(WALFactory.WAL_PROVIDER, provider);
298    TEST_UTIL.startMiniCluster();
299    conf1 = TEST_UTIL.getConfiguration();
300    TEST_UTIL.startMiniMapReduceCluster();
301
302    if (useSecondCluster) {
303      conf2 = HBaseConfiguration.create(conf1);
304      conf2.set(HConstants.ZOOKEEPER_ZNODE_PARENT, "/2");
305      TEST_UTIL2 = new HBaseTestingUtil(conf2);
306      TEST_UTIL2.setZkCluster(TEST_UTIL.getZkCluster());
307      TEST_UTIL2.startMiniDFSCluster(3);
308      String root2 = TEST_UTIL2.getConfiguration().get("fs.defaultFS");
309      Path p = new Path(new Path(root2), "/tmp/wal");
310      CommonFSUtils.setWALRootDir(TEST_UTIL2.getConfiguration(), p);
311      TEST_UTIL2.startMiniCluster();
312    }
313
314    BACKUP_ROOT_DIR =
315      new Path(new Path(TEST_UTIL.getConfiguration().get("fs.defaultFS")), BACKUP_ROOT_DIR)
316        .toString();
317    LOG.info("ROOTDIR " + BACKUP_ROOT_DIR);
318    if (useSecondCluster) {
319      BACKUP_REMOTE_ROOT_DIR = new Path(
320        new Path(TEST_UTIL2.getConfiguration().get("fs.defaultFS")) + BACKUP_REMOTE_ROOT_DIR)
321        .toString();
322      LOG.info("REMOTE ROOTDIR " + BACKUP_REMOTE_ROOT_DIR);
323    }
324    createTables();
325    populateFromMasterConfig(TEST_UTIL.getHBaseCluster().getMaster().getConfiguration(), conf1);
326  }
327
328  /**
329   * Setup Cluster with appropriate configurations before running tests.
330   * @throws Exception if starting the mini cluster or setting up the tables fails
331   */
332  @BeforeAll
333  public static void setUp() throws Exception {
334    TEST_UTIL = new HBaseTestingUtil();
335    conf1 = TEST_UTIL.getConfiguration();
336    autoRestoreOnFailure = true;
337    useSecondCluster = false;
338    setUpHelper();
339  }
340
341  private static void populateFromMasterConfig(Configuration masterConf, Configuration conf) {
342    Iterator<Entry<String, String>> it = masterConf.iterator();
343    while (it.hasNext()) {
344      Entry<String, String> e = it.next();
345      conf.set(e.getKey(), e.getValue());
346    }
347  }
348
349  @AfterAll
350  public static void tearDown() throws Exception {
351    try {
352      SnapshotTestingUtils.deleteAllSnapshots(TEST_UTIL.getAdmin());
353    } catch (Exception e) {
354    }
355    SnapshotTestingUtils.deleteArchiveDirectory(TEST_UTIL);
356    if (useSecondCluster) {
357      TEST_UTIL2.shutdownMiniCluster();
358    }
359    TEST_UTIL.shutdownMiniCluster();
360    TEST_UTIL.shutdownMiniMapReduceCluster();
361    autoRestoreOnFailure = true;
362    useSecondCluster = false;
363  }
364
365  Table insertIntoTable(Connection conn, TableName table, byte[] family, int id, int numRows)
366    throws IOException {
367    Table t = conn.getTable(table);
368    Put p1;
369    for (int i = 0; i < numRows; i++) {
370      p1 = new Put(Bytes.toBytes("row-" + table + "-" + id + "-" + i));
371      p1.addColumn(family, qualName, Bytes.toBytes("val" + i));
372      t.put(p1);
373    }
374    return t;
375  }
376
377  protected BackupRequest createBackupRequest(BackupType type, List<TableName> tables,
378    String path) {
379    return createBackupRequest(type, tables, path, false);
380  }
381
382  protected BackupRequest createBackupRequest(BackupType type, List<TableName> tables, String path,
383    boolean noChecksumVerify) {
384    BackupRequest.Builder builder = new BackupRequest.Builder();
385    BackupRequest request = builder.withBackupType(type).withTableList(tables)
386      .withTargetRootDir(path).withNoChecksumVerify(noChecksumVerify).build();
387    return request;
388  }
389
390  protected String backupTables(BackupType type, List<TableName> tables, String path)
391    throws IOException {
392    Connection conn = null;
393    BackupAdmin badmin = null;
394    String backupId;
395    try {
396      conn = ConnectionFactory.createConnection(conf1);
397      badmin = new BackupAdminImpl(conn);
398      BackupRequest request = createBackupRequest(type, new ArrayList<>(tables), path);
399      backupId = badmin.backupTables(request);
400    } finally {
401      if (badmin != null) {
402        badmin.close();
403      }
404      if (conn != null) {
405        conn.close();
406      }
407    }
408    return backupId;
409  }
410
411  protected String fullTableBackup(List<TableName> tables) throws IOException {
412    return backupTables(BackupType.FULL, tables, BACKUP_ROOT_DIR);
413  }
414
415  protected String incrementalTableBackup(List<TableName> tables) throws IOException {
416    return backupTables(BackupType.INCREMENTAL, tables, BACKUP_ROOT_DIR);
417  }
418
419  protected static void loadTable(Table table) throws Exception {
420    Put p; // 100 + 1 row to t1_syncup
421    for (int i = 0; i < NB_ROWS_IN_BATCH; i++) {
422      p = new Put(Bytes.toBytes("row" + i));
423      p.setDurability(Durability.SKIP_WAL);
424      p.addColumn(famName, qualName, Bytes.toBytes("val" + i));
425      table.put(p);
426    }
427  }
428
429  protected static void createTables() throws Exception {
430    long tid = EnvironmentEdgeManager.currentTime();
431    table1 = TableName.valueOf("test-" + tid);
432    Admin ha = TEST_UTIL.getAdmin();
433
434    // Create namespaces
435    ha.createNamespace(NamespaceDescriptor.create("ns1").build());
436    ha.createNamespace(NamespaceDescriptor.create("ns2").build());
437    ha.createNamespace(NamespaceDescriptor.create("ns3").build());
438    ha.createNamespace(NamespaceDescriptor.create("ns4").build());
439
440    TableDescriptor desc = TableDescriptorBuilder.newBuilder(table1)
441      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(famName)).build();
442    ha.createTable(desc);
443    table1Desc = desc;
444    Connection conn = ConnectionFactory.createConnection(conf1);
445    Table table = conn.getTable(table1);
446    loadTable(table);
447    table.close();
448    table2 = TableName.valueOf("ns2:test-" + tid + 1);
449    desc = TableDescriptorBuilder.newBuilder(table2)
450      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(famName)).build();
451    ha.createTable(desc);
452    table = conn.getTable(table2);
453    loadTable(table);
454    table.close();
455    table3 = TableName.valueOf("ns3:test-" + tid + 2);
456    table = TEST_UTIL.createTable(table3, famName);
457    table.close();
458    table4 = TableName.valueOf("ns4:test-" + tid + 3);
459    table = TEST_UTIL.createTable(table4, famName);
460    table.close();
461    ha.close();
462    conn.close();
463  }
464
465  protected boolean checkSucceeded(String backupId) throws IOException {
466    BackupInfo status = getBackupInfo(backupId);
467
468    if (status == null) {
469      return false;
470    }
471
472    return status.getState() == BackupState.COMPLETE;
473  }
474
475  protected boolean checkFailed(String backupId) throws IOException {
476    BackupInfo status = getBackupInfo(backupId);
477
478    if (status == null) {
479      return false;
480    }
481
482    return status.getState() == BackupState.FAILED;
483  }
484
485  private BackupInfo getBackupInfo(String backupId) throws IOException {
486    try (BackupSystemTable table = new BackupSystemTable(TEST_UTIL.getConnection())) {
487      BackupInfo status = table.readBackupInfo(backupId);
488      return status;
489    }
490  }
491
492  protected static BackupAdmin getBackupAdmin() throws IOException {
493    return new BackupAdminImpl(TEST_UTIL.getConnection());
494  }
495
496  /**
497   * Helper method
498   */
499  protected List<TableName> toList(String... args) {
500    List<TableName> ret = new ArrayList<>();
501    for (int i = 0; i < args.length; i++) {
502      ret.add(TableName.valueOf(args[i]));
503    }
504    return ret;
505  }
506
507  protected List<FileStatus> getListOfWALFiles(Configuration c) throws IOException {
508    Path logRoot = new Path(CommonFSUtils.getWALRootDir(c), HConstants.HREGION_LOGDIR_NAME);
509    FileSystem fs = logRoot.getFileSystem(c);
510    RemoteIterator<LocatedFileStatus> it = fs.listFiles(logRoot, true);
511    List<FileStatus> logFiles = new ArrayList<FileStatus>();
512    while (it.hasNext()) {
513      LocatedFileStatus lfs = it.next();
514      if (lfs.isFile() && !AbstractFSWALProvider.isMetaFile(lfs.getPath())) {
515        logFiles.add(lfs);
516        LOG.info(Objects.toString(lfs));
517      }
518    }
519    return logFiles;
520  }
521
522  protected void dumpBackupDir() throws IOException {
523    // Dump Backup Dir
524    FileSystem fs = FileSystem.get(conf1);
525    RemoteIterator<LocatedFileStatus> it = fs.listFiles(new Path(BACKUP_ROOT_DIR), true);
526    while (it.hasNext()) {
527      LOG.debug(Objects.toString(it.next().getPath()));
528    }
529  }
530}