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.client;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023import static org.junit.Assert.fail;
024
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.List;
028import java.util.regex.Pattern;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.fs.FileSystem;
031import org.apache.hadoop.fs.Path;
032import org.apache.hadoop.hbase.HBaseClassTestRule;
033import org.apache.hadoop.hbase.HBaseTestingUtility;
034import org.apache.hadoop.hbase.HConstants;
035import org.apache.hadoop.hbase.HTableDescriptor;
036import org.apache.hadoop.hbase.TableName;
037import org.apache.hadoop.hbase.TableNameTestRule;
038import org.apache.hadoop.hbase.TableNotFoundException;
039import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
040import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy;
041import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
042import org.apache.hadoop.hbase.snapshot.SnapshotCreationException;
043import org.apache.hadoop.hbase.snapshot.SnapshotDoesNotExistException;
044import org.apache.hadoop.hbase.snapshot.SnapshotManifestV1;
045import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils;
046import org.apache.hadoop.hbase.testclassification.ClientTests;
047import org.apache.hadoop.hbase.testclassification.LargeTests;
048import org.apache.hadoop.hbase.util.Bytes;
049import org.apache.hadoop.hbase.util.CommonFSUtils;
050import org.junit.After;
051import org.junit.AfterClass;
052import org.junit.Before;
053import org.junit.BeforeClass;
054import org.junit.ClassRule;
055import org.junit.Rule;
056import org.junit.Test;
057import org.junit.experimental.categories.Category;
058import org.junit.runner.RunWith;
059import org.junit.runners.Parameterized;
060import org.junit.runners.Parameterized.Parameter;
061import org.junit.runners.Parameterized.Parameters;
062import org.slf4j.Logger;
063import org.slf4j.LoggerFactory;
064
065import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
066
067import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
068
069/**
070 * Test create/using/deleting snapshots from the client
071 * <p>
072 * This is an end-to-end test for the snapshot utility
073 */
074@RunWith(Parameterized.class)
075@Category({ LargeTests.class, ClientTests.class })
076public class TestSnapshotFromClient {
077
078  @ClassRule
079  public static final HBaseClassTestRule CLASS_RULE =
080    HBaseClassTestRule.forClass(TestSnapshotFromClient.class);
081
082  private static final Logger LOG = LoggerFactory.getLogger(TestSnapshotFromClient.class);
083
084  protected static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
085  protected static final int NUM_RS = 2;
086  protected static final String STRING_TABLE_NAME = "test";
087  protected static final byte[] TEST_FAM = Bytes.toBytes("fam");
088  protected static final TableName TABLE_NAME = TableName.valueOf(STRING_TABLE_NAME);
089  private static final Pattern MATCH_ALL = Pattern.compile(".*");
090
091  @Rule
092  public TableNameTestRule name = new TableNameTestRule();
093
094  @Parameter
095  public StoreFileTrackerFactory.Trackers trackerImpl;
096
097  @Parameters(name = "{index}: tracker={0}")
098  public static List<Object[]> params() {
099    return Arrays.asList(new Object[] { StoreFileTrackerFactory.Trackers.DEFAULT },
100      new Object[] { StoreFileTrackerFactory.Trackers.FILE });
101  }
102
103  /**
104   * Setup the config for the cluster
105   * @throws Exception on failure
106   */
107  @BeforeClass
108  public static void setupCluster() throws Exception {
109    setupConf(UTIL.getConfiguration());
110    UTIL.startMiniCluster(NUM_RS);
111  }
112
113  protected static void setupConf(Configuration conf) {
114    // disable the ui
115    conf.setInt("hbase.regionsever.info.port", -1);
116    // change the flush size to a small amount, regulating number of store files
117    conf.setInt("hbase.hregion.memstore.flush.size", 25000);
118    // so make sure we get a compaction when doing a load, but keep around some
119    // files in the store
120    conf.setInt("hbase.hstore.compaction.min", 10);
121    conf.setInt("hbase.hstore.compactionThreshold", 10);
122    // block writes if we get to 12 store files
123    conf.setInt("hbase.hstore.blockingStoreFiles", 12);
124    // Enable snapshot
125    conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
126    conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
127      ConstantSizeRegionSplitPolicy.class.getName());
128  }
129
130  @Before
131  public void setup() throws Exception {
132    createTable();
133  }
134
135  protected void createTable() throws Exception {
136    TableDescriptor htd =
137      TableDescriptorBuilder.newBuilder(TABLE_NAME).setRegionReplication(getNumReplicas())
138        .setValue(StoreFileTrackerFactory.TRACKER_IMPL, trackerImpl.name()).build();
139    UTIL.createTable(htd, new byte[][] { TEST_FAM }, null);
140  }
141
142  protected int getNumReplicas() {
143    return 1;
144  }
145
146  @After
147  public void tearDown() throws Exception {
148    UTIL.deleteTable(TABLE_NAME);
149    SnapshotTestingUtils.deleteAllSnapshots(UTIL.getAdmin());
150    SnapshotTestingUtils.deleteArchiveDirectory(UTIL);
151  }
152
153  @AfterClass
154  public static void cleanupTest() throws Exception {
155    try {
156      UTIL.shutdownMiniCluster();
157    } catch (Exception e) {
158      LOG.warn("failure shutting down cluster", e);
159    }
160  }
161
162  /**
163   * Test snapshotting not allowed hbase:meta and -ROOT-
164   */
165  @Test
166  public void testMetaTablesSnapshot() throws Exception {
167    Admin admin = UTIL.getAdmin();
168    byte[] snapshotName = Bytes.toBytes("metaSnapshot");
169
170    try {
171      admin.snapshot(snapshotName, TableName.META_TABLE_NAME);
172      fail("taking a snapshot of hbase:meta should not be allowed");
173    } catch (IllegalArgumentException e) {
174      // expected
175    }
176  }
177
178  /**
179   * Test HBaseAdmin#deleteSnapshots(String) which deletes snapshots whose names match the parameter
180   */
181  @Test
182  public void testSnapshotDeletionWithRegex() throws Exception {
183    Admin admin = UTIL.getAdmin();
184    // make sure we don't fail on listing snapshots
185    SnapshotTestingUtils.assertNoSnapshots(admin);
186
187    // put some stuff in the table
188    Table table = UTIL.getConnection().getTable(TABLE_NAME);
189    UTIL.loadTable(table, TEST_FAM);
190    table.close();
191
192    byte[] snapshot1 = Bytes.toBytes("TableSnapshot1");
193    admin.snapshot(snapshot1, TABLE_NAME);
194    LOG.debug("Snapshot1 completed.");
195
196    byte[] snapshot2 = Bytes.toBytes("TableSnapshot2");
197    admin.snapshot(snapshot2, TABLE_NAME);
198    LOG.debug("Snapshot2 completed.");
199
200    String snapshot3 = "3rdTableSnapshot";
201    admin.snapshot(Bytes.toBytes(snapshot3), TABLE_NAME);
202    LOG.debug(snapshot3 + " completed.");
203
204    // delete the first two snapshots
205    admin.deleteSnapshots(Pattern.compile("TableSnapshot.*"));
206    List<SnapshotDescription> snapshots = admin.listSnapshots();
207    assertEquals(1, snapshots.size());
208    assertEquals(snapshot3, snapshots.get(0).getName());
209
210    admin.deleteSnapshot(snapshot3);
211    admin.close();
212  }
213
214  /**
215   * Test snapshotting a table that is offline
216   */
217  @Test
218  public void testOfflineTableSnapshot() throws Exception {
219    Admin admin = UTIL.getAdmin();
220    // make sure we don't fail on listing snapshots
221    SnapshotTestingUtils.assertNoSnapshots(admin);
222
223    // put some stuff in the table
224    Table table = UTIL.getConnection().getTable(TABLE_NAME);
225    UTIL.loadTable(table, TEST_FAM, false);
226
227    LOG.debug("FS state before disable:");
228    CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(),
229      CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG);
230    // XXX if this is flakey, might want to consider using the async version and looping as
231    // disableTable can succeed and still timeout.
232    admin.disableTable(TABLE_NAME);
233
234    LOG.debug("FS state before snapshot:");
235    CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(),
236      CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG);
237
238    // take a snapshot of the disabled table
239    final String SNAPSHOT_NAME = "offlineTableSnapshot";
240    byte[] snapshot = Bytes.toBytes(SNAPSHOT_NAME);
241
242    admin.snapshot(new SnapshotDescription(SNAPSHOT_NAME, TABLE_NAME, SnapshotType.DISABLED, null,
243      -1, SnapshotManifestV1.DESCRIPTOR_VERSION, null));
244    LOG.debug("Snapshot completed.");
245
246    // make sure we have the snapshot
247    List<SnapshotDescription> snapshots =
248      SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshot, TABLE_NAME);
249
250    // make sure its a valid snapshot
251    FileSystem fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem();
252    Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
253    LOG.debug("FS state after snapshot:");
254    CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(),
255      CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG);
256    SnapshotTestingUtils.confirmSnapshotValid(
257      ProtobufUtil.createHBaseProtosSnapshotDesc(snapshots.get(0)), TABLE_NAME, TEST_FAM, rootDir,
258      admin, fs);
259
260    admin.deleteSnapshot(snapshot);
261    snapshots = admin.listSnapshots();
262    SnapshotTestingUtils.assertNoSnapshots(admin);
263  }
264
265  @Test
266  public void testSnapshotFailsOnNonExistantTable() throws Exception {
267    Admin admin = UTIL.getAdmin();
268    // make sure we don't fail on listing snapshots
269    SnapshotTestingUtils.assertNoSnapshots(admin);
270    String tableName = "_not_a_table";
271
272    // make sure the table doesn't exist
273    boolean fail = false;
274    do {
275      try {
276        admin.getTableDescriptor(TableName.valueOf(tableName));
277        fail = true;
278        LOG.error("Table:" + tableName + " already exists, checking a new name");
279        tableName = tableName + "!";
280      } catch (TableNotFoundException e) {
281        fail = false;
282      }
283    } while (fail);
284
285    // snapshot the non-existant table
286    try {
287      admin.snapshot("fail", TableName.valueOf(tableName));
288      fail("Snapshot succeeded even though there is not table.");
289    } catch (SnapshotCreationException e) {
290      LOG.info("Correctly failed to snapshot a non-existant table:" + e.getMessage());
291    }
292  }
293
294  @Test
295  public void testOfflineTableSnapshotWithEmptyRegions() throws Exception {
296    // test with an empty table with one region
297
298    Admin admin = UTIL.getAdmin();
299    // make sure we don't fail on listing snapshots
300    SnapshotTestingUtils.assertNoSnapshots(admin);
301
302    LOG.debug("FS state before disable:");
303    CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(),
304      CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG);
305    admin.disableTable(TABLE_NAME);
306
307    LOG.debug("FS state before snapshot:");
308    CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(),
309      CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG);
310
311    // take a snapshot of the disabled table
312    byte[] snapshot = Bytes.toBytes("testOfflineTableSnapshotWithEmptyRegions");
313    admin.snapshot(snapshot, TABLE_NAME);
314    LOG.debug("Snapshot completed.");
315
316    // make sure we have the snapshot
317    List<SnapshotDescription> snapshots =
318      SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshot, TABLE_NAME);
319
320    // make sure its a valid snapshot
321    FileSystem fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem();
322    Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
323    LOG.debug("FS state after snapshot:");
324    CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(),
325      CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG);
326
327    List<byte[]> emptyCfs = Lists.newArrayList(TEST_FAM); // no file in the region
328    List<byte[]> nonEmptyCfs = Lists.newArrayList();
329    SnapshotTestingUtils.confirmSnapshotValid(
330      ProtobufUtil.createHBaseProtosSnapshotDesc(snapshots.get(0)), TABLE_NAME, nonEmptyCfs,
331      emptyCfs, rootDir, admin, fs);
332
333    admin.deleteSnapshot(snapshot);
334    snapshots = admin.listSnapshots();
335    SnapshotTestingUtils.assertNoSnapshots(admin);
336  }
337
338  @Test
339  public void testListTableSnapshots() throws Exception {
340    Admin admin = null;
341    final TableName tableName = name.getTableName();
342    try {
343      admin = UTIL.getAdmin();
344
345      HTableDescriptor htd = new HTableDescriptor(tableName);
346      UTIL.createTable(htd, new byte[][] { TEST_FAM }, UTIL.getConfiguration());
347
348      String table1Snapshot1 = "Table1Snapshot1";
349      admin.snapshot(table1Snapshot1, TABLE_NAME);
350      LOG.debug("Snapshot1 completed.");
351
352      String table1Snapshot2 = "Table1Snapshot2";
353      admin.snapshot(table1Snapshot2, TABLE_NAME);
354      LOG.debug("Snapshot2 completed.");
355
356      String table2Snapshot1 = "Table2Snapshot1";
357      admin.snapshot(Bytes.toBytes(table2Snapshot1), tableName);
358      LOG.debug(table2Snapshot1 + " completed.");
359
360      List<SnapshotDescription> listTableSnapshots =
361        admin.listTableSnapshots(Pattern.compile("test.*"), MATCH_ALL);
362      List<String> listTableSnapshotNames = new ArrayList<>();
363      assertEquals(3, listTableSnapshots.size());
364      for (SnapshotDescription s : listTableSnapshots) {
365        listTableSnapshotNames.add(s.getName());
366      }
367      assertTrue(listTableSnapshotNames.contains(table1Snapshot1));
368      assertTrue(listTableSnapshotNames.contains(table1Snapshot2));
369      assertTrue(listTableSnapshotNames.contains(table2Snapshot1));
370    } finally {
371      if (admin != null) {
372        try {
373          admin.deleteSnapshots(Pattern.compile("Table.*"));
374        } catch (SnapshotDoesNotExistException ignore) {
375        }
376        if (admin.tableExists(tableName)) {
377          UTIL.deleteTable(tableName);
378        }
379        admin.close();
380      }
381    }
382  }
383
384  @Test
385  public void testListTableSnapshotsWithRegex() throws Exception {
386    Admin admin = null;
387    try {
388      admin = UTIL.getAdmin();
389
390      String table1Snapshot1 = "Table1Snapshot1";
391      admin.snapshot(table1Snapshot1, TABLE_NAME);
392      LOG.debug("Snapshot1 completed.");
393
394      String table1Snapshot2 = "Table1Snapshot2";
395      admin.snapshot(table1Snapshot2, TABLE_NAME);
396      LOG.debug("Snapshot2 completed.");
397
398      String table2Snapshot1 = "Table2Snapshot1";
399      admin.snapshot(Bytes.toBytes(table2Snapshot1), TABLE_NAME);
400      LOG.debug(table2Snapshot1 + " completed.");
401
402      List<SnapshotDescription> listTableSnapshots =
403        admin.listTableSnapshots(Pattern.compile("test.*"), Pattern.compile("Table1.*"));
404      List<String> listTableSnapshotNames = new ArrayList<>();
405      assertEquals(2, listTableSnapshots.size());
406      for (SnapshotDescription s : listTableSnapshots) {
407        listTableSnapshotNames.add(s.getName());
408      }
409      assertTrue(listTableSnapshotNames.contains(table1Snapshot1));
410      assertTrue(listTableSnapshotNames.contains(table1Snapshot2));
411      assertFalse(listTableSnapshotNames.contains(table2Snapshot1));
412    } finally {
413      if (admin != null) {
414        try {
415          admin.deleteSnapshots(Pattern.compile("Table.*"));
416        } catch (SnapshotDoesNotExistException ignore) {
417        }
418        admin.close();
419      }
420    }
421  }
422
423  @Test
424  public void testDeleteTableSnapshots() throws Exception {
425    Admin admin = null;
426    final TableName tableName = name.getTableName();
427    try {
428      admin = UTIL.getAdmin();
429
430      HTableDescriptor htd = new HTableDescriptor(tableName);
431      UTIL.createTable(htd, new byte[][] { TEST_FAM }, UTIL.getConfiguration());
432
433      String table1Snapshot1 = "Table1Snapshot1";
434      admin.snapshot(table1Snapshot1, TABLE_NAME);
435      LOG.debug("Snapshot1 completed.");
436
437      String table1Snapshot2 = "Table1Snapshot2";
438      admin.snapshot(table1Snapshot2, TABLE_NAME);
439      LOG.debug("Snapshot2 completed.");
440
441      String table2Snapshot1 = "Table2Snapshot1";
442      admin.snapshot(Bytes.toBytes(table2Snapshot1), tableName);
443      LOG.debug(table2Snapshot1 + " completed.");
444
445      Pattern tableNamePattern = Pattern.compile("test.*");
446      admin.deleteTableSnapshots(tableNamePattern, MATCH_ALL);
447      assertEquals(0, admin.listTableSnapshots(tableNamePattern, MATCH_ALL).size());
448    } finally {
449      if (admin != null) {
450        if (admin.tableExists(tableName)) {
451          UTIL.deleteTable(tableName);
452        }
453        admin.close();
454      }
455    }
456  }
457
458  @Test
459  public void testDeleteTableSnapshotsWithRegex() throws Exception {
460    Admin admin = null;
461    Pattern tableNamePattern = Pattern.compile("test.*");
462    try {
463      admin = UTIL.getAdmin();
464
465      String table1Snapshot1 = "Table1Snapshot1";
466      admin.snapshot(table1Snapshot1, TABLE_NAME);
467      LOG.debug("Snapshot1 completed.");
468
469      String table1Snapshot2 = "Table1Snapshot2";
470      admin.snapshot(table1Snapshot2, TABLE_NAME);
471      LOG.debug("Snapshot2 completed.");
472
473      String table2Snapshot1 = "Table2Snapshot1";
474      admin.snapshot(Bytes.toBytes(table2Snapshot1), TABLE_NAME);
475      LOG.debug(table2Snapshot1 + " completed.");
476
477      admin.deleteTableSnapshots(tableNamePattern, Pattern.compile("Table1.*"));
478      assertEquals(1, admin.listTableSnapshots(tableNamePattern, MATCH_ALL).size());
479    } finally {
480      if (admin != null) {
481        try {
482          admin.deleteTableSnapshots(tableNamePattern, MATCH_ALL);
483        } catch (SnapshotDoesNotExistException ignore) {
484        }
485        admin.close();
486      }
487    }
488  }
489}