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 * <p>
010 * http://www.apache.org/licenses/LICENSE-2.0
011 * <p>
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.client;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023
024import java.io.File;
025import java.io.IOException;
026import java.nio.file.Paths;
027import java.util.ArrayList;
028import java.util.Arrays;
029import java.util.Iterator;
030import java.util.List;
031import java.util.UUID;
032import java.util.regex.Pattern;
033import org.apache.commons.io.FileUtils;
034import org.apache.hadoop.conf.Configuration;
035import org.apache.hadoop.fs.FileSystem;
036import org.apache.hadoop.fs.Path;
037import org.apache.hadoop.hbase.HBaseClassTestRule;
038import org.apache.hadoop.hbase.HBaseTestingUtil;
039import org.apache.hadoop.hbase.HConstants;
040import org.apache.hadoop.hbase.TableName;
041import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
042import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy;
043import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
044import org.apache.hadoop.hbase.snapshot.SnapshotDoesNotExistException;
045import org.apache.hadoop.hbase.snapshot.SnapshotManifestV1;
046import org.apache.hadoop.hbase.snapshot.SnapshotManifestV2;
047import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils;
048import org.apache.hadoop.hbase.testclassification.LargeTests;
049import org.apache.hadoop.hbase.util.Bytes;
050import org.apache.hadoop.hbase.util.CommonFSUtils;
051import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
052import org.junit.After;
053import org.junit.AfterClass;
054import org.junit.Before;
055import org.junit.BeforeClass;
056import org.junit.ClassRule;
057import org.junit.Test;
058import org.junit.experimental.categories.Category;
059import org.junit.runner.RunWith;
060import org.junit.runners.Parameterized;
061import org.slf4j.Logger;
062import org.slf4j.LoggerFactory;
063
064import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
065
066import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
067
068/**
069 * This class tests that the use of a temporary snapshot directory supports snapshot functionality
070 * while the temporary directory is on a different file system than the root directory
071 * <p>
072 * This is an end-to-end test for the snapshot utility
073 */
074@Category(LargeTests.class)
075@RunWith(Parameterized.class)
076public class TestSnapshotTemporaryDirectory {
077
078  @ClassRule public static final HBaseClassTestRule CLASS_RULE =
079      HBaseClassTestRule.forClass(TestSnapshotTemporaryDirectory.class);
080
081  @Parameterized.Parameters public static Iterable<Integer> data() {
082    return Arrays
083        .asList(SnapshotManifestV1.DESCRIPTOR_VERSION, SnapshotManifestV2.DESCRIPTOR_VERSION);
084  }
085
086  @Parameterized.Parameter public int manifestVersion;
087
088  private static final Logger LOG = LoggerFactory.getLogger(TestSnapshotTemporaryDirectory.class);
089  protected static final int NUM_RS = 2;
090  protected static String TEMP_DIR =
091      Paths.get("").toAbsolutePath().toString() + Path.SEPARATOR + UUID.randomUUID().toString();
092
093  protected static Admin admin;
094  protected static final HBaseTestingUtil UTIL = new HBaseTestingUtil();
095  protected static final String STRING_TABLE_NAME = "test";
096  protected static final byte[] TEST_FAM = Bytes.toBytes("fam");
097  protected static final TableName TABLE_NAME = TableName.valueOf(STRING_TABLE_NAME);
098
099  /**
100   * Setup the config for the cluster
101   *
102   * @throws Exception on failure
103   */
104  @BeforeClass
105  public static void setupCluster() throws Exception {
106    setupConf(UTIL.getConfiguration());
107    UTIL.startMiniCluster(NUM_RS);
108    admin = UTIL.getAdmin();
109  }
110
111  private static void setupConf(Configuration conf) {
112    // disable the ui
113    conf.setInt("hbase.regionsever.info.port", -1);
114    // change the flush size to a small amount, regulating number of store files
115    conf.setInt("hbase.hregion.memstore.flush.size", 25000);
116    // so make sure we get a compaction when doing a load, but keep around some
117    // files in the store
118    conf.setInt("hbase.hstore.compaction.min", 10);
119    conf.setInt("hbase.hstore.compactionThreshold", 10);
120    // block writes if we get to 12 store files
121    conf.setInt("hbase.hstore.blockingStoreFiles", 12);
122    // Enable snapshot
123    conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
124    conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
125        ConstantSizeRegionSplitPolicy.class.getName());
126    conf.set(SnapshotDescriptionUtils.SNAPSHOT_WORKING_DIR, "file://" + new Path(TEMP_DIR, ".tmpDir").toUri());
127  }
128
129  @Before
130  public void setup() throws Exception {
131    TableDescriptor htd =
132      TableDescriptorBuilder.newBuilder(TABLE_NAME).setRegionReplication(getNumReplicas()).build();
133    UTIL.createTable(htd, new byte[][] { TEST_FAM }, UTIL.getConfiguration());
134  }
135
136  protected int getNumReplicas() {
137    return 1;
138  }
139
140  @After
141  public void tearDown() throws Exception {
142    UTIL.deleteTable(TABLE_NAME);
143    SnapshotTestingUtils.deleteAllSnapshots(UTIL.getAdmin());
144    SnapshotTestingUtils.deleteArchiveDirectory(UTIL);
145  }
146
147  @AfterClass
148  public static void cleanupTest() {
149    try {
150      UTIL.shutdownMiniCluster();
151      FileUtils.deleteDirectory(new File(TEMP_DIR));
152    } catch (Exception e) {
153      LOG.warn("failure shutting down cluster", e);
154    }
155  }
156
157  @Test
158  public void testRestoreDisabledSnapshot()
159      throws IOException, InterruptedException {
160    long tid = EnvironmentEdgeManager.currentTime();
161    TableName tableName = TableName.valueOf("testtb-" + tid);
162    String emptySnapshot = "emptySnaptb-" + tid;
163    String snapshotName0 = "snaptb0-" + tid;
164    String snapshotName1 = "snaptb1-" + tid;
165    int snapshot0Rows;
166    int snapshot1Rows;
167
168    // create Table and disable it
169    SnapshotTestingUtils.createTable(UTIL, tableName, getNumReplicas(), TEST_FAM);
170    admin.disableTable(tableName);
171
172    // take an empty snapshot
173    takeSnapshot(tableName, emptySnapshot, true);
174
175    // enable table and insert data
176    admin.enableTable(tableName);
177    SnapshotTestingUtils.loadData(UTIL, tableName, 500, TEST_FAM);
178    try (Table table = UTIL.getConnection().getTable(tableName)) {
179      snapshot0Rows = UTIL.countRows(table);
180    }
181    admin.disableTable(tableName);
182
183    // take a snapshot
184    takeSnapshot(tableName, snapshotName0, true);
185
186    // enable table and insert more data
187    admin.enableTable(tableName);
188    SnapshotTestingUtils.loadData(UTIL, tableName, 500, TEST_FAM);
189    try (Table table = UTIL.getConnection().getTable(tableName)) {
190      snapshot1Rows = UTIL.countRows(table);
191    }
192
193    SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows);
194    admin.disableTable(tableName);
195    takeSnapshot(tableName, snapshotName1, true);
196
197    // Restore from snapshot-0
198    admin.restoreSnapshot(snapshotName0);
199    admin.enableTable(tableName);
200    SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot0Rows);
201    SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
202
203    // Restore from emptySnapshot
204    admin.disableTable(tableName);
205    admin.restoreSnapshot(emptySnapshot);
206    admin.enableTable(tableName);
207    SnapshotTestingUtils.verifyRowCount(UTIL, tableName, 0);
208    SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
209
210    // Restore from snapshot-1
211    admin.disableTable(tableName);
212    admin.restoreSnapshot(snapshotName1);
213    admin.enableTable(tableName);
214    SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows);
215    SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
216
217    // Restore from snapshot-1
218    UTIL.deleteTable(tableName);
219    admin.restoreSnapshot(snapshotName1);
220    SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows);
221    SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
222  }
223
224  @Test
225  public void testRestoreEnabledSnapshot()
226      throws IOException, InterruptedException {
227    long tid = EnvironmentEdgeManager.currentTime();
228    TableName tableName = TableName.valueOf("testtb-" + tid);
229    String emptySnapshot = "emptySnaptb-" + tid;
230    String snapshotName0 = "snaptb0-" + tid;
231    String snapshotName1 = "snaptb1-" + tid;
232    int snapshot0Rows;
233    int snapshot1Rows;
234
235    // create Table
236    SnapshotTestingUtils.createTable(UTIL, tableName, getNumReplicas(), TEST_FAM);
237
238    // take an empty snapshot
239    takeSnapshot(tableName, emptySnapshot, false);
240
241    // Insert data
242    SnapshotTestingUtils.loadData(UTIL, tableName, 500, TEST_FAM);
243    try (Table table = UTIL.getConnection().getTable(tableName)) {
244      snapshot0Rows = UTIL.countRows(table);
245    }
246
247    // take a snapshot
248    takeSnapshot(tableName, snapshotName0, false);
249
250    // Insert more data
251    SnapshotTestingUtils.loadData(UTIL, tableName, 500, TEST_FAM);
252    try (Table table = UTIL.getConnection().getTable(tableName)) {
253      snapshot1Rows = UTIL.countRows(table);
254    }
255
256    SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows);
257    takeSnapshot(tableName, snapshotName1, false);
258
259    // Restore from snapshot-0
260    admin.disableTable(tableName);
261    admin.restoreSnapshot(snapshotName0);
262    admin.enableTable(tableName);
263    SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot0Rows);
264    SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
265
266    // Restore from emptySnapshot
267    admin.disableTable(tableName);
268    admin.restoreSnapshot(emptySnapshot);
269    admin.enableTable(tableName);
270    SnapshotTestingUtils.verifyRowCount(UTIL, tableName, 0);
271    SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
272
273    // Restore from snapshot-1
274    admin.disableTable(tableName);
275    admin.restoreSnapshot(snapshotName1);
276    admin.enableTable(tableName);
277    SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows);
278    SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
279
280    // Restore from snapshot-1
281    UTIL.deleteTable(tableName);
282    admin.restoreSnapshot(snapshotName1);
283    SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows);
284    SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
285  }
286
287  /**
288   * Test snapshotting a table that is offline
289   *
290   * @throws Exception if snapshot does not complete successfully
291   */
292  @Test
293  public void testOfflineTableSnapshot() throws Exception {
294    Admin admin = UTIL.getAdmin();
295    // make sure we don't fail on listing snapshots
296    SnapshotTestingUtils.assertNoSnapshots(admin);
297
298    // put some stuff in the table
299    Table table = UTIL.getConnection().getTable(TABLE_NAME);
300    UTIL.loadTable(table, TEST_FAM, false);
301
302    LOG.debug("FS state before disable:");
303    CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(),
304      CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG);
305    // XXX if this is flakey, might want to consider using the async version and looping as
306    // disableTable can succeed and still timeout.
307    admin.disableTable(TABLE_NAME);
308
309    LOG.debug("FS state before snapshot:");
310    CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(),
311      CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG);
312
313    // take a snapshot of the disabled table
314    final String SNAPSHOT_NAME = "offlineTableSnapshot";
315    String snapshot = SNAPSHOT_NAME;
316    takeSnapshot(TABLE_NAME, SNAPSHOT_NAME, true);
317    LOG.debug("Snapshot completed.");
318
319    // make sure we have the snapshot
320    List<SnapshotDescription> snapshots =
321        SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshot, TABLE_NAME);
322
323    // make sure its a valid snapshot
324    FileSystem fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem();
325    Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
326    LOG.debug("FS state after snapshot:");
327    CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(),
328      CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG);
329
330    SnapshotTestingUtils
331        .confirmSnapshotValid(ProtobufUtil.createHBaseProtosSnapshotDesc(snapshots.get(0)),
332            TABLE_NAME, TEST_FAM, rootDir, admin, fs);
333
334    admin.deleteSnapshot(snapshot);
335    SnapshotTestingUtils.assertNoSnapshots(admin);
336  }
337
338  /**
339   * Tests that snapshot has correct contents by taking snapshot, cloning it, then affirming
340   * the contents of the original and cloned table match
341   *
342   * @throws Exception if snapshot does not complete successfully
343   */
344  @Test
345  public void testSnapshotCloneContents() throws Exception {
346    // make sure we don't fail on listing snapshots
347    SnapshotTestingUtils.assertNoSnapshots(admin);
348
349    // put some stuff in the table
350    Table table = UTIL.getConnection().getTable(TABLE_NAME);
351    UTIL.loadTable(table, TEST_FAM);
352    table.close();
353
354    String snapshot1 = "TableSnapshot1";
355    takeSnapshot(TABLE_NAME, snapshot1, false);
356    LOG.debug("Snapshot1 completed.");
357
358    TableName clone = TableName.valueOf("Table1Clone");
359    admin.cloneSnapshot(snapshot1, clone, false);
360
361    Scan original = new Scan();
362    Scan cloned = new Scan();
363    ResultScanner originalScan = admin.getConnection().getTable(TABLE_NAME).getScanner(original);
364    ResultScanner clonedScan =
365        admin.getConnection().getTable(TableName.valueOf("Table1Clone")).getScanner(cloned);
366
367    Iterator<Result> i = originalScan.iterator();
368    Iterator<Result> i2 = clonedScan.iterator();
369    assertTrue(i.hasNext());
370    while (i.hasNext()) {
371      assertTrue(i2.hasNext());
372      assertEquals(Bytes.toString(i.next().getValue(TEST_FAM, new byte[] {})),
373          Bytes.toString(i2.next().getValue(TEST_FAM, new byte[] {})));
374    }
375    assertFalse(i2.hasNext());
376    admin.deleteSnapshot(snapshot1);
377    UTIL.deleteTable(clone);
378    admin.close();
379  }
380
381  @Test
382  public void testOfflineTableSnapshotWithEmptyRegion() throws Exception {
383    // test with an empty table with one region
384
385    // make sure we don't fail on listing snapshots
386    SnapshotTestingUtils.assertNoSnapshots(admin);
387
388    LOG.debug("FS state before disable:");
389    CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(),
390      CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG);
391    admin.disableTable(TABLE_NAME);
392
393    LOG.debug("FS state before snapshot:");
394    CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(),
395      CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG);
396
397    // take a snapshot of the disabled table
398    String snapshot = "testOfflineTableSnapshotWithEmptyRegion";
399    takeSnapshot(TABLE_NAME, snapshot, true);
400    LOG.debug("Snapshot completed.");
401
402    // make sure we have the snapshot
403    List<SnapshotDescription> snapshots =
404      SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshot, TABLE_NAME);
405
406    // make sure its a valid snapshot
407    FileSystem fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem();
408    Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
409    LOG.debug("FS state after snapshot:");
410    CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(),
411      CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG);
412
413    List<byte[]> emptyCfs = Lists.newArrayList(TEST_FAM); // no file in the region
414    List<byte[]> nonEmptyCfs = Lists.newArrayList();
415    SnapshotTestingUtils
416        .confirmSnapshotValid(ProtobufUtil.createHBaseProtosSnapshotDesc(snapshots.get(0)),
417            TABLE_NAME, nonEmptyCfs, emptyCfs, rootDir, admin, fs);
418
419    admin.deleteSnapshot(snapshot);
420    SnapshotTestingUtils.assertNoSnapshots(admin);
421  }
422
423  // Ensures that the snapshot is transferred to the proper completed snapshot directory
424  @Test
425  public void testEnsureTemporaryDirectoryTransfer() throws Exception {
426    Admin admin = UTIL.getAdmin();
427    TableName tableName2 = TableName.valueOf("testListTableSnapshots");
428    try {
429      TableDescriptor htd = TableDescriptorBuilder.newBuilder(tableName2).build();
430      UTIL.createTable(htd, new byte[][] { TEST_FAM }, UTIL.getConfiguration());
431
432      String table1Snapshot1 = "Table1Snapshot1";
433      takeSnapshot(TABLE_NAME, table1Snapshot1, false);
434      LOG.debug("Snapshot1 completed.");
435
436      String table1Snapshot2 = "Table1Snapshot2";
437      takeSnapshot(TABLE_NAME, table1Snapshot2, false);
438      LOG.debug("Snapshot2 completed.");
439
440      String table2Snapshot1 = "Table2Snapshot1";
441      takeSnapshot(TABLE_NAME, table2Snapshot1, false);
442      LOG.debug("Table2Snapshot1 completed.");
443
444      List<SnapshotDescription> listTableSnapshots =
445        admin.listTableSnapshots(Pattern.compile("test.*"), Pattern.compile(".*"));
446      List<String> listTableSnapshotNames = new ArrayList<String>();
447      assertEquals(3, listTableSnapshots.size());
448      for (SnapshotDescription s : listTableSnapshots) {
449        listTableSnapshotNames.add(s.getName());
450      }
451      assertTrue(listTableSnapshotNames.contains(table1Snapshot1));
452      assertTrue(listTableSnapshotNames.contains(table1Snapshot2));
453      assertTrue(listTableSnapshotNames.contains(table2Snapshot1));
454    } finally {
455      try {
456        admin.deleteSnapshots(Pattern.compile("Table.*"));
457      } catch (SnapshotDoesNotExistException ignore) {
458      }
459      if (admin.tableExists(tableName2)) {
460        UTIL.deleteTable(tableName2);
461      }
462      admin.close();
463    }
464  }
465
466  private void takeSnapshot(TableName tableName, String snapshotName, boolean disabled)
467      throws IOException {
468    SnapshotType type = disabled ? SnapshotType.DISABLED : SnapshotType.FLUSH;
469    SnapshotDescription desc = new SnapshotDescription(snapshotName, tableName, type, null, -1,
470        manifestVersion, null);
471    admin.snapshot(desc);
472  }
473}