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.fail;
023
024import java.io.IOException;
025import java.util.HashSet;
026import java.util.List;
027import java.util.Set;
028
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.fs.Path;
031import org.apache.hadoop.hbase.HBaseClassTestRule;
032import org.apache.hadoop.hbase.HBaseTestingUtility;
033import org.apache.hadoop.hbase.HColumnDescriptor;
034import org.apache.hadoop.hbase.HConstants;
035import org.apache.hadoop.hbase.HTableDescriptor;
036import org.apache.hadoop.hbase.TableName;
037import org.apache.hadoop.hbase.master.MasterFileSystem;
038import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
039import org.apache.hadoop.hbase.regionserver.NoSuchColumnFamilyException;
040import org.apache.hadoop.hbase.snapshot.CorruptedSnapshotException;
041import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils;
042import org.apache.hadoop.hbase.testclassification.ClientTests;
043import org.apache.hadoop.hbase.testclassification.LargeTests;
044import org.apache.hadoop.hbase.util.Bytes;
045import org.apache.hadoop.hbase.util.FSUtils;
046import org.junit.After;
047import org.junit.AfterClass;
048import org.junit.Before;
049import org.junit.BeforeClass;
050import org.junit.ClassRule;
051import org.junit.Rule;
052import org.junit.Test;
053import org.junit.experimental.categories.Category;
054import org.junit.rules.TestName;
055
056/**
057 * Test restore snapshots from the client
058 */
059@Category({LargeTests.class, ClientTests.class})
060public class TestRestoreSnapshotFromClient {
061
062  @ClassRule
063  public static final HBaseClassTestRule CLASS_RULE =
064      HBaseClassTestRule.forClass(TestRestoreSnapshotFromClient.class);
065
066  protected final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
067
068  protected final byte[] FAMILY = Bytes.toBytes("cf");
069  protected final byte[] TEST_FAMILY2 = Bytes.toBytes("cf2");
070
071  protected TableName tableName;
072  protected byte[] emptySnapshot;
073  protected byte[] snapshotName0;
074  protected byte[] snapshotName1;
075  protected byte[] snapshotName2;
076  protected int snapshot0Rows;
077  protected int snapshot1Rows;
078  protected Admin admin;
079
080  @Rule
081  public TestName name = new TestName();
082
083  @BeforeClass
084  public static void setupCluster() throws Exception {
085    setupConf(TEST_UTIL.getConfiguration());
086    TEST_UTIL.startMiniCluster(3);
087  }
088
089  protected static void setupConf(Configuration conf) {
090    TEST_UTIL.getConfiguration().setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
091    TEST_UTIL.getConfiguration().setInt("hbase.hstore.compactionThreshold", 10);
092    TEST_UTIL.getConfiguration().setInt("hbase.regionserver.msginterval", 100);
093    TEST_UTIL.getConfiguration().setInt("hbase.client.pause", 250);
094    TEST_UTIL.getConfiguration().setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 6);
095    TEST_UTIL.getConfiguration().setBoolean(
096        "hbase.master.enabletable.roundrobin", true);
097  }
098
099  @AfterClass
100  public static void tearDownAfterClass() throws Exception {
101    TEST_UTIL.shutdownMiniCluster();
102  }
103
104  /**
105   * Initialize the tests with a table filled with some data
106   * and two snapshots (snapshotName0, snapshotName1) of different states.
107   * The tableName, snapshotNames and the number of rows in the snapshot are initialized.
108   */
109  @Before
110  public void setup() throws Exception {
111    this.admin = TEST_UTIL.getAdmin();
112
113    long tid = System.currentTimeMillis();
114    tableName = TableName.valueOf(name.getMethodName() + "-" + tid);
115    emptySnapshot = Bytes.toBytes("emptySnaptb-" + tid);
116    snapshotName0 = Bytes.toBytes("snaptb0-" + tid);
117    snapshotName1 = Bytes.toBytes("snaptb1-" + tid);
118    snapshotName2 = Bytes.toBytes("snaptb2-" + tid);
119
120    // create Table and disable it
121    createTable();
122    admin.disableTable(tableName);
123
124    // take an empty snapshot
125    admin.snapshot(emptySnapshot, tableName);
126
127    // enable table and insert data
128    admin.enableTable(tableName);
129    SnapshotTestingUtils.loadData(TEST_UTIL, tableName, 500, FAMILY);
130    try (Table table = TEST_UTIL.getConnection().getTable(tableName)) {
131      snapshot0Rows = countRows(table);
132    }
133    admin.disableTable(tableName);
134
135    // take a snapshot
136    admin.snapshot(snapshotName0, tableName);
137
138    // enable table and insert more data
139    admin.enableTable(tableName);
140    SnapshotTestingUtils.loadData(TEST_UTIL, tableName, 500, FAMILY);
141    try (Table table = TEST_UTIL.getConnection().getTable(tableName)) {
142      snapshot1Rows = countRows(table);
143    }
144  }
145
146  protected void createTable() throws Exception {
147    SnapshotTestingUtils.createTable(TEST_UTIL, tableName, getNumReplicas(), FAMILY);
148  }
149
150  @After
151  public void tearDown() throws Exception {
152    TEST_UTIL.deleteTable(tableName);
153    SnapshotTestingUtils.deleteAllSnapshots(TEST_UTIL.getAdmin());
154    SnapshotTestingUtils.deleteArchiveDirectory(TEST_UTIL);
155  }
156
157  @Test
158  public void testRestoreSnapshot() throws IOException {
159    verifyRowCount(TEST_UTIL, tableName, snapshot1Rows);
160    admin.disableTable(tableName);
161    admin.snapshot(snapshotName1, tableName);
162    // Restore from snapshot-0
163    admin.restoreSnapshot(snapshotName0);
164    admin.enableTable(tableName);
165    verifyRowCount(TEST_UTIL, tableName, snapshot0Rows);
166    SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
167
168    // Restore from emptySnapshot
169    admin.disableTable(tableName);
170    admin.restoreSnapshot(emptySnapshot);
171    admin.enableTable(tableName);
172    verifyRowCount(TEST_UTIL, tableName, 0);
173    SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
174
175    // Restore from snapshot-1
176    admin.disableTable(tableName);
177    admin.restoreSnapshot(snapshotName1);
178    admin.enableTable(tableName);
179    verifyRowCount(TEST_UTIL, tableName, snapshot1Rows);
180    SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
181
182    // Restore from snapshot-1
183    TEST_UTIL.deleteTable(tableName);
184    admin.restoreSnapshot(snapshotName1);
185    verifyRowCount(TEST_UTIL, tableName, snapshot1Rows);
186    SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
187  }
188
189  protected int getNumReplicas() {
190    return 1;
191  }
192
193  protected HColumnDescriptor getTestRestoreSchemaChangeHCD() {
194    return new HColumnDescriptor(TEST_FAMILY2);
195  }
196
197  @Test
198  public void testRestoreSchemaChange() throws Exception {
199    Table table = TEST_UTIL.getConnection().getTable(tableName);
200
201    // Add one column family and put some data in it
202    admin.disableTable(tableName);
203    admin.addColumnFamily(tableName, getTestRestoreSchemaChangeHCD());
204    admin.enableTable(tableName);
205    assertEquals(2, table.getTableDescriptor().getFamilies().size());
206    HTableDescriptor htd = admin.getTableDescriptor(tableName);
207    assertEquals(2, htd.getFamilies().size());
208    SnapshotTestingUtils.loadData(TEST_UTIL, tableName, 500, TEST_FAMILY2);
209    long snapshot2Rows = snapshot1Rows + 500L;
210    assertEquals(snapshot2Rows, countRows(table));
211    assertEquals(500, countRows(table, TEST_FAMILY2));
212    Set<String> fsFamilies = getFamiliesFromFS(tableName);
213    assertEquals(2, fsFamilies.size());
214
215    // Take a snapshot
216    admin.disableTable(tableName);
217    admin.snapshot(snapshotName2, tableName);
218
219    // Restore the snapshot (without the cf)
220    admin.restoreSnapshot(snapshotName0);
221    admin.enableTable(tableName);
222    assertEquals(1, table.getTableDescriptor().getFamilies().size());
223    try {
224      countRows(table, TEST_FAMILY2);
225      fail("family '" + Bytes.toString(TEST_FAMILY2) + "' should not exists");
226    } catch (NoSuchColumnFamilyException e) {
227      // expected
228    }
229    assertEquals(snapshot0Rows, countRows(table));
230    htd = admin.getTableDescriptor(tableName);
231    assertEquals(1, htd.getFamilies().size());
232    fsFamilies = getFamiliesFromFS(tableName);
233    assertEquals(1, fsFamilies.size());
234
235    // Restore back the snapshot (with the cf)
236    admin.disableTable(tableName);
237    admin.restoreSnapshot(snapshotName2);
238    admin.enableTable(tableName);
239    htd = admin.getTableDescriptor(tableName);
240    assertEquals(2, htd.getFamilies().size());
241    assertEquals(2, table.getTableDescriptor().getFamilies().size());
242    assertEquals(500, countRows(table, TEST_FAMILY2));
243    assertEquals(snapshot2Rows, countRows(table));
244    fsFamilies = getFamiliesFromFS(tableName);
245    assertEquals(2, fsFamilies.size());
246    table.close();
247  }
248
249  @Test
250  public void testCloneSnapshotOfCloned() throws IOException, InterruptedException {
251    TableName clonedTableName = TableName.valueOf(name.getMethodName() + "-" + System.currentTimeMillis());
252    admin.cloneSnapshot(snapshotName0, clonedTableName);
253    verifyRowCount(TEST_UTIL, clonedTableName, snapshot0Rows);
254    SnapshotTestingUtils.verifyReplicasCameOnline(clonedTableName, admin, getNumReplicas());
255    admin.disableTable(clonedTableName);
256    admin.snapshot(snapshotName2, clonedTableName);
257    TEST_UTIL.deleteTable(clonedTableName);
258    waitCleanerRun();
259
260    admin.cloneSnapshot(snapshotName2, clonedTableName);
261    verifyRowCount(TEST_UTIL, clonedTableName, snapshot0Rows);
262    SnapshotTestingUtils.verifyReplicasCameOnline(clonedTableName, admin, getNumReplicas());
263    TEST_UTIL.deleteTable(clonedTableName);
264  }
265
266  @Test
267  public void testCloneAndRestoreSnapshot() throws IOException, InterruptedException {
268    TEST_UTIL.deleteTable(tableName);
269    waitCleanerRun();
270
271    admin.cloneSnapshot(snapshotName0, tableName);
272    verifyRowCount(TEST_UTIL, tableName, snapshot0Rows);
273    SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
274    waitCleanerRun();
275
276    admin.disableTable(tableName);
277    admin.restoreSnapshot(snapshotName0);
278    admin.enableTable(tableName);
279    verifyRowCount(TEST_UTIL, tableName, snapshot0Rows);
280    SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
281  }
282
283  @Test
284  public void testCorruptedSnapshot() throws IOException, InterruptedException {
285    SnapshotTestingUtils.corruptSnapshot(TEST_UTIL, Bytes.toString(snapshotName0));
286    TableName cloneName = TableName.valueOf(name.getMethodName() + "-" + System.currentTimeMillis());
287    try {
288      admin.cloneSnapshot(snapshotName0, cloneName);
289      fail("Expected CorruptedSnapshotException, got succeeded cloneSnapshot()");
290    } catch (CorruptedSnapshotException e) {
291      // Got the expected corruption exception.
292      // check for no references of the cloned table.
293      assertFalse(admin.tableExists(cloneName));
294    } catch (Exception e) {
295      fail("Expected CorruptedSnapshotException got: " + e);
296    }
297  }
298
299  @Test
300  public void testRestoreSnapshotAfterSplittingRegions() throws IOException, InterruptedException {
301    List<RegionInfo> regionInfos = admin.getRegions(tableName);
302    RegionReplicaUtil.removeNonDefaultRegions(regionInfos);
303
304    // Split the first region
305    splitRegion(regionInfos.get(0));
306
307    // Take a snapshot
308    admin.snapshot(snapshotName1, tableName);
309
310    // Restore the snapshot
311    admin.disableTable(tableName);
312    admin.restoreSnapshot(snapshotName1);
313    admin.enableTable(tableName);
314
315    verifyRowCount(TEST_UTIL, tableName, snapshot1Rows);
316  }
317
318  // ==========================================================================
319  //  Helpers
320  // ==========================================================================
321  private void waitCleanerRun() throws InterruptedException {
322    TEST_UTIL.getMiniHBaseCluster().getMaster().getHFileCleaner().choreForTesting();
323  }
324
325  private Set<String> getFamiliesFromFS(final TableName tableName) throws IOException {
326    MasterFileSystem mfs = TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterFileSystem();
327    Set<String> families = new HashSet<>();
328    Path tableDir = FSUtils.getTableDir(mfs.getRootDir(), tableName);
329    for (Path regionDir: FSUtils.getRegionDirs(mfs.getFileSystem(), tableDir)) {
330      for (Path familyDir: FSUtils.getFamilyDirs(mfs.getFileSystem(), regionDir)) {
331        families.add(familyDir.getName());
332      }
333    }
334    return families;
335  }
336
337  protected void verifyRowCount(final HBaseTestingUtility util, final TableName tableName,
338      long expectedRows) throws IOException {
339    SnapshotTestingUtils.verifyRowCount(util, tableName, expectedRows);
340  }
341
342  protected int countRows(final Table table, final byte[]... families) throws IOException {
343    return TEST_UTIL.countRows(table, families);
344  }
345
346  protected void splitRegion(final RegionInfo regionInfo) throws IOException {
347    byte[][] splitPoints = Bytes.split(regionInfo.getStartKey(), regionInfo.getEndKey(), 1);
348    admin.split(regionInfo.getTable(), splitPoints[1]);
349  }
350}