View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.snapshot;
19  
20  import static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.assertFalse;
22  import static org.junit.Assert.assertTrue;
23  
24  import java.io.IOException;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Collection;
28  import java.util.List;
29  import java.util.Set;
30  import java.util.HashSet;
31  import java.util.TreeSet;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.apache.hadoop.fs.FileStatus;
36  import org.apache.hadoop.fs.FileSystem;
37  import org.apache.hadoop.fs.Path;
38  import org.apache.hadoop.fs.PathFilter;
39  import org.apache.hadoop.hbase.HBaseTestingUtility;
40  import org.apache.hadoop.hbase.HColumnDescriptor;
41  import org.apache.hadoop.hbase.HConstants;
42  import org.apache.hadoop.hbase.HRegionInfo;
43  import org.apache.hadoop.hbase.HTableDescriptor;
44  import org.apache.hadoop.hbase.TableName;
45  import org.apache.hadoop.hbase.TableNotEnabledException;
46  import org.apache.hadoop.hbase.client.Durability;
47  import org.apache.hadoop.hbase.client.HBaseAdmin;
48  import org.apache.hadoop.hbase.client.HTable;
49  import org.apache.hadoop.hbase.client.Put;
50  import org.apache.hadoop.hbase.master.HMaster;
51  import org.apache.hadoop.hbase.master.MasterFileSystem;
52  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
53  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
54  import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.IsSnapshotDoneRequest;
55  import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.IsSnapshotDoneResponse;
56  import org.apache.hadoop.hbase.regionserver.HRegion;
57  import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
58  import org.apache.hadoop.hbase.regionserver.HRegionServer;
59  import org.apache.hadoop.hbase.util.Bytes;
60  import org.apache.hadoop.hbase.util.FSTableDescriptors;
61  import org.apache.hadoop.hbase.util.FSUtils;
62  import org.apache.hadoop.hbase.util.FSVisitor;
63  import org.apache.hadoop.hbase.util.MD5Hash;
64  import org.junit.Assert;
65  
66  import com.google.protobuf.ServiceException;
67  
68  /**
69   * Utilities class for snapshots
70   */
71  public class SnapshotTestingUtils {
72  
73    private static final Log LOG = LogFactory.getLog(SnapshotTestingUtils.class);
74  
75    /**
76     * Assert that we don't have any snapshots lists
77     *
78     * @throws IOException
79     *           if the admin operation fails
80     */
81    public static void assertNoSnapshots(HBaseAdmin admin) throws IOException {
82      assertEquals("Have some previous snapshots", 0, admin.listSnapshots()
83          .size());
84    }
85  
86    /**
87     * Make sure that there is only one snapshot returned from the master and its
88     * name and table match the passed in parameters.
89     */
90    public static List<SnapshotDescription> assertExistsMatchingSnapshot(
91        HBaseAdmin admin, String snapshotName, TableName tableName)
92        throws IOException {
93      // list the snapshot
94      List<SnapshotDescription> snapshots = admin.listSnapshots();
95  
96      List<SnapshotDescription> returnedSnapshots = new ArrayList<SnapshotDescription>();
97      for (SnapshotDescription sd : snapshots) {
98        if (snapshotName.equals(sd.getName()) &&
99            tableName.equals(TableName.valueOf(sd.getTable()))) {
100         returnedSnapshots.add(sd);
101       }
102     }
103 
104     Assert.assertTrue("No matching snapshots found.", returnedSnapshots.size()>0);
105     return returnedSnapshots;
106   }
107 
108   /**
109    * Make sure that there is only one snapshot returned from the master
110    */
111   public static void assertOneSnapshotThatMatches(HBaseAdmin admin,
112       SnapshotDescription snapshot) throws IOException {
113     assertOneSnapshotThatMatches(admin, snapshot.getName(),
114         TableName.valueOf(snapshot.getTable()));
115   }
116 
117   /**
118    * Make sure that there is only one snapshot returned from the master and its
119    * name and table match the passed in parameters.
120    */
121   public static List<SnapshotDescription> assertOneSnapshotThatMatches(
122       HBaseAdmin admin, String snapshotName, TableName tableName)
123       throws IOException {
124     // list the snapshot
125     List<SnapshotDescription> snapshots = admin.listSnapshots();
126 
127     assertEquals("Should only have 1 snapshot", 1, snapshots.size());
128     assertEquals(snapshotName, snapshots.get(0).getName());
129     assertEquals(tableName, TableName.valueOf(snapshots.get(0).getTable()));
130 
131     return snapshots;
132   }
133 
134   /**
135    * Make sure that there is only one snapshot returned from the master and its
136    * name and table match the passed in parameters.
137    */
138   public static List<SnapshotDescription> assertOneSnapshotThatMatches(
139       HBaseAdmin admin, byte[] snapshot, TableName tableName) throws IOException {
140     return assertOneSnapshotThatMatches(admin, Bytes.toString(snapshot),
141         tableName);
142   }
143 
144   /**
145    * Confirm that the snapshot contains references to all the files that should
146    * be in the snapshot.
147    */
148   public static void confirmSnapshotValid(
149       SnapshotDescription snapshotDescriptor, TableName tableName,
150       byte[] testFamily, Path rootDir, HBaseAdmin admin, FileSystem fs,
151       boolean requireLogs, Path logsDir, Set<String> snapshotServers)
152       throws IOException {
153     ArrayList nonEmptyTestFamilies = new ArrayList(1);
154     nonEmptyTestFamilies.add(testFamily);
155     confirmSnapshotValid(snapshotDescriptor, tableName,
156       nonEmptyTestFamilies, null, rootDir, admin, fs, requireLogs,
157       logsDir, snapshotServers);
158   }
159 
160   /**
161    * Confirm that the snapshot has no references files but only metadata.
162    */
163   public static void confirmEmptySnapshotValid(
164       SnapshotDescription snapshotDescriptor, TableName tableName,
165       byte[] testFamily, Path rootDir, HBaseAdmin admin, FileSystem fs,
166       boolean requireLogs, Path logsDir, Set<String> snapshotServers)
167       throws IOException {
168     ArrayList emptyTestFamilies = new ArrayList(1);
169     emptyTestFamilies.add(testFamily);
170     confirmSnapshotValid(snapshotDescriptor, tableName,
171       null, emptyTestFamilies, rootDir, admin, fs, requireLogs,
172       logsDir, snapshotServers);
173   }
174 
175   /**
176    * Confirm that the snapshot contains references to all the files that should
177    * be in the snapshot. This method also perform some redundant check like
178    * the existence of the snapshotinfo or the regioninfo which are done always
179    * by the MasterSnapshotVerifier, at the end of the snapshot operation.
180    */
181   public static void confirmSnapshotValid(
182       SnapshotDescription snapshotDescriptor, TableName tableName,
183       List<byte[]> nonEmptyTestFamilies, List<byte[]> emptyTestFamilies,
184       Path rootDir, HBaseAdmin admin, FileSystem fs, boolean requireLogs,
185       Path logsDir, Set<String> snapshotServers) throws IOException {
186     // check snapshot dir
187     Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(
188         snapshotDescriptor, rootDir);
189     assertTrue(fs.exists(snapshotDir));
190 
191     // check snapshot info
192     Path snapshotinfo = new Path(snapshotDir, SnapshotDescriptionUtils.SNAPSHOTINFO_FILE);
193     assertTrue(fs.exists(snapshotinfo));
194 
195     // check the logs dir
196     if (requireLogs) {
197       TakeSnapshotUtils.verifyAllLogsGotReferenced(fs, logsDir,
198           snapshotServers, snapshotDescriptor, new Path(snapshotDir,
199               HConstants.HREGION_LOGDIR_NAME));
200     }
201 
202     // check the table info
203     HTableDescriptor desc = FSTableDescriptors.getTableDescriptorFromFs(fs, rootDir, tableName);
204     HTableDescriptor snapshotDesc = FSTableDescriptors.getTableDescriptorFromFs(fs, snapshotDir);
205     assertEquals(desc, snapshotDesc);
206 
207     // Extract regions and families with store files
208     final Set<String> snapshotRegions = new HashSet<String>();
209     final Set<byte[]> snapshotFamilies = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
210     FSVisitor.visitTableStoreFiles(fs, snapshotDir, new FSVisitor.StoreFileVisitor() {
211       public void storeFile(final String region, final String family, final String hfileName)
212           throws IOException {
213         snapshotRegions.add(region);
214         snapshotFamilies.add(Bytes.toBytes(family));
215       }
216     });
217 
218     // Verify that there are store files in the specified families
219     if (nonEmptyTestFamilies != null) {
220       for (final byte[] familyName: nonEmptyTestFamilies) {
221         assertTrue(snapshotFamilies.contains(familyName));
222       }
223     }
224 
225     // Verify that there are no store files in the specified families
226     if (emptyTestFamilies != null) {
227       for (final byte[] familyName: emptyTestFamilies) {
228         assertFalse(snapshotFamilies.contains(familyName));
229       }
230     }
231 
232     // Avoid checking regions if the request is for an empty snapshot
233     if ((nonEmptyTestFamilies == null || nonEmptyTestFamilies.size() == 0) &&
234         (emptyTestFamilies != null && emptyTestFamilies.size() > 0)) {
235       assertEquals(0, snapshotRegions.size());
236       return;
237     }
238 
239     // check the region snapshot for all the regions
240     List<HRegionInfo> regions = admin.getTableRegions(tableName);
241     assertEquals(regions.size(), snapshotRegions.size());
242 
243     // Verify Regions
244     for (HRegionInfo info : regions) {
245       String regionName = info.getEncodedName();
246       assertTrue(snapshotRegions.contains(regionName));
247 
248       Path regionDir = new Path(snapshotDir, regionName);
249       HRegionInfo snapshotRegionInfo = HRegionFileSystem.loadRegionInfoFileContent(fs, regionDir);
250       assertEquals(info, snapshotRegionInfo);
251     }
252   }
253 
254   /**
255    * Helper method for testing async snapshot operations. Just waits for the
256    * given snapshot to complete on the server by repeatedly checking the master.
257    *
258    * @param master: the master running the snapshot
259    * @param snapshot: the snapshot to check
260    * @param sleep: amount to sleep between checks to see if the snapshot is done
261    * @throws ServiceException if the snapshot fails
262    */
263   public static void waitForSnapshotToComplete(HMaster master,
264       SnapshotDescription snapshot, long sleep) throws ServiceException {
265     final IsSnapshotDoneRequest request = IsSnapshotDoneRequest.newBuilder()
266         .setSnapshot(snapshot).build();
267     IsSnapshotDoneResponse done = IsSnapshotDoneResponse.newBuilder()
268         .buildPartial();
269     while (!done.getDone()) {
270       done = master.isSnapshotDone(null, request);
271       try {
272         Thread.sleep(sleep);
273       } catch (InterruptedException e) {
274         throw new ServiceException(e);
275       }
276     }
277   }
278 
279   public static void cleanupSnapshot(HBaseAdmin admin, byte[] tableName)
280       throws IOException {
281     SnapshotTestingUtils.cleanupSnapshot(admin, Bytes.toString(tableName));
282   }
283 
284   public static void cleanupSnapshot(HBaseAdmin admin, String snapshotName)
285       throws IOException {
286     // delete the taken snapshot
287     admin.deleteSnapshot(snapshotName);
288     assertNoSnapshots(admin);
289   }
290 
291   /**
292    * Expect the snapshot to throw an error when checking if the snapshot is
293    * complete
294    *
295    * @param master master to check
296    * @param snapshot the {@link SnapshotDescription} request to pass to the master
297    * @param clazz expected exception from the master
298    */
299   public static void expectSnapshotDoneException(HMaster master,
300       IsSnapshotDoneRequest snapshot,
301       Class<? extends HBaseSnapshotException> clazz) {
302     try {
303       master.isSnapshotDone(null, snapshot);
304       Assert.fail("didn't fail to lookup a snapshot");
305     } catch (ServiceException se) {
306       try {
307         throw ProtobufUtil.getRemoteException(se);
308       } catch (HBaseSnapshotException e) {
309         assertEquals("Threw wrong snapshot exception!", clazz, e.getClass());
310       } catch (Throwable t) {
311         Assert.fail("Threw an unexpected exception:" + t);
312       }
313     }
314   }
315 
316   /**
317    * List all the HFiles in the given table
318    *
319    * @param fs: FileSystem where the table lives
320    * @param tableDir directory of the table
321    * @return array of the current HFiles in the table (could be a zero-length array)
322    * @throws IOException on unexecpted error reading the FS
323    */
324   public static Path[] listHFiles(final FileSystem fs, final Path tableDir)
325       throws IOException {
326     final ArrayList<Path> hfiles = new ArrayList<Path>();
327     FSVisitor.visitTableStoreFiles(fs, tableDir, new FSVisitor.StoreFileVisitor() {
328       public void storeFile(final String region, final String family, final String hfileName)
329           throws IOException {
330         hfiles.add(new Path(tableDir, new Path(region, new Path(family, hfileName))));
331       }
332     });
333     return hfiles.toArray(new Path[hfiles.size()]);
334   }
335 
336   /**
337    * Take a snapshot of the specified table and verify that the given family is
338    * not empty. Note that this will leave the table disabled
339    * in the case of an offline snapshot.
340    */
341   public static void createSnapshotAndValidate(HBaseAdmin admin,
342       TableName tableName, String familyName, String snapshotNameString,
343       Path rootDir, FileSystem fs, boolean onlineSnapshot)
344       throws Exception {
345     ArrayList<byte[]> nonEmptyFamilyNames = new ArrayList<byte[]>(1);
346     nonEmptyFamilyNames.add(Bytes.toBytes(familyName));
347     createSnapshotAndValidate(admin, tableName, nonEmptyFamilyNames, /* emptyFamilyNames= */ null,
348                               snapshotNameString, rootDir, fs, onlineSnapshot);
349   }
350 
351   /**
352    * Take a snapshot of the specified table and verify the given families.
353    * Note that this will leave the table disabled in the case of an offline snapshot.
354    */
355   public static void createSnapshotAndValidate(HBaseAdmin admin,
356       TableName tableName, List<byte[]> nonEmptyFamilyNames, List<byte[]> emptyFamilyNames,
357       String snapshotNameString, Path rootDir, FileSystem fs, boolean onlineSnapshot)
358         throws Exception {
359     if (!onlineSnapshot) {
360       try {
361         admin.disableTable(tableName);
362       } catch (TableNotEnabledException tne) {
363         LOG.info("In attempting to disable " + tableName + " it turns out that the this table is " +
364             "already disabled.");
365       }
366     }
367     admin.snapshot(snapshotNameString, tableName);
368 
369     List<SnapshotDescription> snapshots = SnapshotTestingUtils.assertExistsMatchingSnapshot(admin,
370       snapshotNameString, tableName);
371     if (snapshots == null || snapshots.size() != 1) {
372       Assert.fail("Incorrect number of snapshots for table " + tableName);
373     }
374 
375     SnapshotTestingUtils.confirmSnapshotValid(snapshots.get(0), tableName, nonEmptyFamilyNames,
376       emptyFamilyNames, rootDir, admin, fs, false,
377       new Path(rootDir, HConstants.HREGION_LOGDIR_NAME), null);
378   }
379 
380   // ==========================================================================
381   //  Table Helpers
382   // ==========================================================================
383   public static void waitForTableToBeOnline(final HBaseTestingUtility util,
384                                             final TableName tableName)
385       throws IOException, InterruptedException {
386     HRegionServer rs = util.getRSForFirstRegionInTable(tableName);
387     List<HRegion> onlineRegions = rs.getOnlineRegions(tableName);
388     for (HRegion region : onlineRegions) {
389       region.waitForFlushesAndCompactions();
390     }
391     util.getHBaseAdmin().isTableAvailable(tableName);
392   }
393 
394   public static void createTable(final HBaseTestingUtility util, final TableName tableName,
395       final byte[]... families) throws IOException, InterruptedException {
396     HTableDescriptor htd = new HTableDescriptor(tableName);
397     for (byte[] family: families) {
398       HColumnDescriptor hcd = new HColumnDescriptor(family);
399       htd.addFamily(hcd);
400     }
401     byte[][] splitKeys = new byte[14][];
402     byte[] hex = Bytes.toBytes("123456789abcde");
403     for (int i = 0; i < splitKeys.length; ++i) {
404       splitKeys[i] = new byte[] { hex[i] };
405     }
406     util.getHBaseAdmin().createTable(htd, splitKeys);
407     waitForTableToBeOnline(util, tableName);
408     assertEquals(15, util.getHBaseAdmin().getTableRegions(tableName).size());
409   }
410 
411   public static void loadData(final HBaseTestingUtility util, final TableName tableName, int rows,
412       byte[]... families) throws IOException, InterruptedException {
413     loadData(util, new HTable(util.getConfiguration(), tableName), rows, families);
414   }
415 
416   public static void loadData(final HBaseTestingUtility util, final HTable table, int rows,
417       byte[]... families) throws IOException, InterruptedException {
418     table.setAutoFlush(false, true);
419 
420     // Ensure one row per region
421     assertTrue(rows >= 16);
422     for (byte k0: Bytes.toBytes("0123456789abcdef")) {
423       byte[] k = new byte[] { k0 };
424       byte[] value = Bytes.add(Bytes.toBytes(System.currentTimeMillis()), k);
425       byte[] key = Bytes.add(k, Bytes.toBytes(MD5Hash.getMD5AsHex(value)));
426       putData(table, families, key, value);
427       rows--;
428     }
429 
430     // Add other extra rows. more rows, more files
431     while (rows-- > 0) {
432       byte[] value = Bytes.add(Bytes.toBytes(System.currentTimeMillis()), Bytes.toBytes(rows));
433       byte[] key = Bytes.toBytes(MD5Hash.getMD5AsHex(value));
434       putData(table, families, key, value);
435     }
436     table.flushCommits();
437 
438     waitForTableToBeOnline(util, table.getName());
439   }
440 
441   private static void putData(final HTable table, final byte[][] families,
442       final byte[] key, final byte[] value) throws IOException {
443     byte[] q = Bytes.toBytes("q");
444     Put put = new Put(key);
445     put.setDurability(Durability.SKIP_WAL);
446     for (byte[] family: families) {
447       put.add(family, q, value);
448     }
449     table.put(put);
450   }
451 
452   public static void deleteAllSnapshots(final HBaseAdmin admin)
453       throws IOException {
454     // Delete all the snapshots
455     for (SnapshotDescription snapshot: admin.listSnapshots()) {
456       admin.deleteSnapshot(snapshot.getName());
457     }
458     SnapshotTestingUtils.assertNoSnapshots(admin);
459   }
460 
461   public static void deleteArchiveDirectory(final HBaseTestingUtility util)
462       throws IOException {
463     // Ensure the archiver to be empty
464     MasterFileSystem mfs = util.getMiniHBaseCluster().getMaster().getMasterFileSystem();
465     Path archiveDir = new Path(mfs.getRootDir(), HConstants.HFILE_ARCHIVE_DIRECTORY);
466     mfs.getFileSystem().delete(archiveDir, true);
467   }
468 
469   public static void verifyRowCount(final HBaseTestingUtility util, final TableName tableName,
470       long expectedRows) throws IOException {
471     HTable table = new HTable(util.getConfiguration(), tableName);
472     try {
473       assertEquals(expectedRows, util.countRows(table));
474     } finally {
475       table.close();
476     }
477   }
478 }