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.assertTrue;
22  
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collection;
27  import java.util.List;
28  import java.util.Set;
29  import java.util.TreeSet;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.hadoop.fs.FileStatus;
34  import org.apache.hadoop.fs.FileSystem;
35  import org.apache.hadoop.fs.Path;
36  import org.apache.hadoop.fs.PathFilter;
37  import org.apache.hadoop.hbase.HBaseTestingUtility;
38  import org.apache.hadoop.hbase.HColumnDescriptor;
39  import org.apache.hadoop.hbase.HConstants;
40  import org.apache.hadoop.hbase.HRegionInfo;
41  import org.apache.hadoop.hbase.HTableDescriptor;
42  import org.apache.hadoop.hbase.client.Durability;
43  import org.apache.hadoop.hbase.client.HBaseAdmin;
44  import org.apache.hadoop.hbase.client.HTable;
45  import org.apache.hadoop.hbase.client.Put;
46  import org.apache.hadoop.hbase.master.HMaster;
47  import org.apache.hadoop.hbase.master.MasterFileSystem;
48  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
49  import org.apache.hadoop.hbase.regionserver.HRegion;
50  import org.apache.hadoop.hbase.regionserver.HRegionServer;
51  import org.apache.hadoop.hbase.snapshot.HBaseSnapshotException;
52  import org.apache.hadoop.hbase.snapshot.HSnapshotDescription;
53  import org.apache.hadoop.hbase.snapshot.TakeSnapshotUtils;
54  import org.apache.hadoop.hbase.util.Bytes;
55  import org.apache.hadoop.hbase.util.FSTableDescriptors;
56  import org.apache.hadoop.hbase.util.FSUtils;
57  import org.apache.hadoop.hbase.util.MD5Hash;
58  import org.junit.Assert;
59  
60  /**
61   * Utilities class for snapshots
62   */
63  public class SnapshotTestingUtils {
64  
65    private static final Log LOG = LogFactory.getLog(SnapshotTestingUtils.class);
66  
67    /**
68     * Assert that we don't have any snapshots lists
69     * @throws IOException if the admin operation fails
70     */
71    public static void assertNoSnapshots(HBaseAdmin admin) throws IOException {
72      assertEquals("Have some previous snapshots", 0, admin.listSnapshots().size());
73    }
74  
75    /**
76     * Make sure that there is only one snapshot returned from the master and its name and table match
77     * the passed in parameters.
78     */
79    public static void assertOneSnapshotThatMatches(HBaseAdmin admin, HSnapshotDescription snapshot)
80        throws IOException {
81      assertOneSnapshotThatMatches(admin, snapshot.getName(), snapshot.getTable());
82    }
83  
84    /**
85     * Make sure that there is only one snapshot returned from the master and its name and table match
86     * the passed in parameters.
87     */
88    public static void assertOneSnapshotThatMatches(HBaseAdmin admin, SnapshotDescription snapshot)
89        throws IOException {
90      assertOneSnapshotThatMatches(admin, snapshot.getName(), snapshot.getTable());
91    }
92  
93    /**
94     * Make sure that there is only one snapshot returned from the master and its name and table match
95     * the passed in parameters.
96     */
97    public static List<SnapshotDescription> assertOneSnapshotThatMatches(HBaseAdmin admin,
98        String snapshotName, String tableName) throws IOException {
99      // list the snapshot
100     List<SnapshotDescription> snapshots = admin.listSnapshots();
101 
102     assertEquals("Should only have 1 snapshot", 1, snapshots.size());
103     assertEquals(snapshotName, snapshots.get(0).getName());
104     assertEquals(tableName, snapshots.get(0).getTable());
105 
106     return snapshots;
107   }
108 
109   /**
110    * Make sure that there is only one snapshot returned from the master and its name and table match
111    * the passed in parameters.
112    */
113   public static List<SnapshotDescription> assertOneSnapshotThatMatches(HBaseAdmin admin,
114       byte[] snapshot, byte[] tableName) throws IOException {
115     return assertOneSnapshotThatMatches(admin, Bytes.toString(snapshot), Bytes.toString(tableName));
116   }
117 
118   /**
119    * Confirm that the snapshot contains references to all the files that should be in the snapshot
120    */
121   public static void confirmSnapshotValid(SnapshotDescription snapshotDescriptor,
122       byte[] tableName, byte[] testFamily, Path rootDir, HBaseAdmin admin, FileSystem fs,
123       boolean requireLogs, Path logsDir, Set<String> snapshotServers) throws IOException {
124     Path snapshotDir = SnapshotDescriptionUtils
125         .getCompletedSnapshotDir(snapshotDescriptor, rootDir);
126     assertTrue(fs.exists(snapshotDir));
127     Path snapshotinfo = new Path(snapshotDir, SnapshotDescriptionUtils.SNAPSHOTINFO_FILE);
128     assertTrue(fs.exists(snapshotinfo));
129     // check the logs dir
130     if (requireLogs) {
131       TakeSnapshotUtils.verifyAllLogsGotReferenced(fs, logsDir, snapshotServers,
132         snapshotDescriptor, new Path(snapshotDir, HConstants.HREGION_LOGDIR_NAME));
133     }
134     // check the table info
135     HTableDescriptor desc = FSTableDescriptors.getTableDescriptor(fs, rootDir, tableName);
136     HTableDescriptor snapshotDesc = FSTableDescriptors.getTableDescriptor(fs, snapshotDir);
137     assertEquals(desc, snapshotDesc);
138 
139     // check the region snapshot for all the regions
140     List<HRegionInfo> regions = admin.getTableRegions(tableName);
141     for (HRegionInfo info : regions) {
142       String regionName = info.getEncodedName();
143       Path regionDir = new Path(snapshotDir, regionName);
144       HRegionInfo snapshotRegionInfo = HRegion.loadDotRegionInfoFileContent(fs, regionDir);
145       assertEquals(info, snapshotRegionInfo);
146       // check to make sure we have the family
147       Path familyDir = new Path(regionDir, Bytes.toString(testFamily));
148       assertTrue("Expected to find: " + familyDir + ", but it doesn't exist", fs.exists(familyDir));
149       // make sure we have some files references
150       assertTrue(fs.listStatus(familyDir).length > 0);
151     }
152   }
153 
154   /**
155    * Helper method for testing async snapshot operations. Just waits for the given snapshot to
156    * complete on the server by repeatedly checking the master.
157    * @param master running the snapshot
158    * @param snapshot to check
159    * @param sleep amount to sleep between checks to see if the snapshot is done
160    * @throws IOException if the snapshot fails
161    */
162   public static void waitForSnapshotToComplete(HMaster master, HSnapshotDescription snapshot,
163       long sleep) throws IOException {
164     boolean done = false;
165     while (!done) {
166       done = master.isSnapshotDone(snapshot);
167       try {
168         Thread.sleep(sleep);
169       } catch (InterruptedException e) {
170         throw new IOException(e);
171       }
172     }
173   }
174 
175   public static void cleanupSnapshot(HBaseAdmin admin, byte[] tableName) throws IOException {
176     SnapshotTestingUtils.cleanupSnapshot(admin, Bytes.toString(tableName));
177   }
178 
179   public static void cleanupSnapshot(HBaseAdmin admin, String snapshotName) throws IOException {
180     // delete the taken snapshot
181     admin.deleteSnapshot(snapshotName);
182     assertNoSnapshots(admin);
183   }
184 
185   /**
186    * Expect the snapshot to throw an error when checking if the snapshot is complete
187    * @param master master to check
188    * @param snapshot the {@link HSnapshotDescription} request to pass to the master
189    * @param clazz expected exception from the master
190    */
191   public static void expectSnapshotDoneException(HMaster master, HSnapshotDescription snapshot,
192       Class<? extends HBaseSnapshotException> clazz) {
193     try {
194       boolean res = master.isSnapshotDone(snapshot);
195       Assert.fail("didn't fail to lookup a snapshot: res=" + res);
196     } catch (HBaseSnapshotException e) {
197       assertEquals("Threw wrong snapshot exception!", clazz, e.getClass());
198     } catch (Throwable t) {
199       Assert.fail("Threw an unexpected exception:" + t);
200     }
201   }
202 
203   /**
204    * List all the HFiles in the given table
205    * @param fs FileSystem where the table lives
206    * @param tableDir directory of the table
207    * @return array of the current HFiles in the table (could be a zero-length array)
208    * @throws IOException on unexecpted error reading the FS
209    */
210   public static FileStatus[] listHFiles(final FileSystem fs, Path tableDir) throws IOException {
211     // setup the filters we will need based on the filesystem
212     PathFilter regionFilter = new FSUtils.RegionDirFilter(fs);
213     PathFilter familyFilter = new FSUtils.FamilyDirFilter(fs);
214     final PathFilter fileFilter = new PathFilter() {
215       @Override
216       public boolean accept(Path file) {
217         try {
218           return fs.isFile(file);
219         } catch (IOException e) {
220           return false;
221         }
222       }
223     };
224 
225     FileStatus[] regionDirs = FSUtils.listStatus(fs, tableDir, regionFilter);
226     // if no regions, then we are done
227     if (regionDirs == null || regionDirs.length == 0) return new FileStatus[0];
228 
229     // go through each of the regions, and add al the hfiles under each family
230     List<FileStatus> regionFiles = new ArrayList<FileStatus>(regionDirs.length);
231     for (FileStatus regionDir : regionDirs) {
232       FileStatus[] fams = FSUtils.listStatus(fs, regionDir.getPath(), familyFilter);
233       // if no families, then we are done again
234       if (fams == null || fams.length == 0) continue;
235       // add all the hfiles under the family
236       regionFiles.addAll(SnapshotTestingUtils.getHFilesInRegion(fams, fs, fileFilter));
237     }
238     FileStatus[] files = new FileStatus[regionFiles.size()];
239     regionFiles.toArray(files);
240     return files;
241   }
242 
243   /**
244    * Get all the hfiles in the region, under the passed set of families
245    * @param families all the family directories under the region
246    * @param fs filesystem where the families live
247    * @param fileFilter filter to only include files
248    * @return collection of all the hfiles under all the passed in families (non-null)
249    * @throws IOException on unexecpted error reading the FS
250    */
251   public static Collection<FileStatus> getHFilesInRegion(FileStatus[] families, FileSystem fs,
252       PathFilter fileFilter) throws IOException {
253     Set<FileStatus> files = new TreeSet<FileStatus>();
254     for (FileStatus family : families) {
255       // get all the hfiles in the family
256       FileStatus[] hfiles = FSUtils.listStatus(fs, family.getPath(), fileFilter);
257       // if no hfiles, then we are done with this family
258       if (hfiles == null || hfiles.length == 0) continue;
259       files.addAll(Arrays.asList(hfiles));
260     }
261     return files;
262   }
263 
264   // ==========================================================================
265   //  Table Helpers
266   // ==========================================================================
267   public static void waitForTableToBeOnline(final HBaseTestingUtility util, final byte[] tableName)
268       throws IOException, InterruptedException {
269     HRegionServer rs = util.getRSForFirstRegionInTable(tableName);
270     List<HRegion> onlineRegions = rs.getOnlineRegions(tableName);
271     for (HRegion region : onlineRegions) {
272       region.waitForFlushesAndCompactions();
273     }
274     util.getHBaseAdmin().isTableAvailable(tableName);
275   }
276 
277   public static void createTable(final HBaseTestingUtility util, final byte[] tableName,
278       final byte[]... families) throws IOException, InterruptedException {
279     HTableDescriptor htd = new HTableDescriptor(tableName);
280     for (byte[] family: families) {
281       HColumnDescriptor hcd = new HColumnDescriptor(family);
282       htd.addFamily(hcd);
283     }
284     byte[][] splitKeys = new byte[14][];
285     byte[] hex = Bytes.toBytes("123456789abcde");
286     for (int i = 0; i < splitKeys.length; ++i) {
287       splitKeys[i] = new byte[] { hex[i] };
288     }
289     util.getHBaseAdmin().createTable(htd, splitKeys);
290     waitForTableToBeOnline(util, tableName);
291     assertEquals(15, util.getHBaseAdmin().getTableRegions(tableName).size());
292   }
293 
294   public static void loadData(final HBaseTestingUtility util, final byte[] tableName, int rows,
295       byte[]... families) throws IOException, InterruptedException {
296     loadData(util, new HTable(util.getConfiguration(), tableName), rows, families);
297   }
298 
299   public static void loadData(final HBaseTestingUtility util, final HTable table, int rows,
300       byte[]... families) throws IOException, InterruptedException {
301     table.setAutoFlush(false);
302 
303     // Ensure one row per region
304     assertTrue(rows >= 16);
305     for (byte k0: Bytes.toBytes("0123456789abcdef")) {
306       byte[] k = new byte[] { k0 };
307       byte[] value = Bytes.add(Bytes.toBytes(System.currentTimeMillis()), k);
308       byte[] key = Bytes.add(k, Bytes.toBytes(MD5Hash.getMD5AsHex(value)));
309       putData(table, families, key, value);
310       rows--;
311     }
312 
313     // Add other extra rows. more rows, more files
314     while (rows-- > 0) {
315       byte[] value = Bytes.add(Bytes.toBytes(System.currentTimeMillis()), Bytes.toBytes(rows));
316       byte[] key = Bytes.toBytes(MD5Hash.getMD5AsHex(value));
317       putData(table, families, key, value);
318     }
319     table.flushCommits();
320 
321     waitForTableToBeOnline(util, table.getTableName());
322   }
323 
324   private static void putData(final HTable table, final byte[][] families,
325       final byte[] key, final byte[] value) throws IOException {
326     byte[] q = Bytes.toBytes("q");
327     Put put = new Put(key);
328     put.setDurability(Durability.SKIP_WAL);
329     for (byte[] family: families) {
330       put.add(family, q, value);
331     }
332     table.put(put);
333   }
334 
335   public static void deleteAllSnapshots(final HBaseAdmin admin)
336       throws IOException {
337     // Delete all the snapshots
338     for (SnapshotDescription snapshot: admin.listSnapshots()) {
339       admin.deleteSnapshot(snapshot.getName());
340     }
341     SnapshotTestingUtils.assertNoSnapshots(admin);
342   }
343 
344   public static void deleteArchiveDirectory(final HBaseTestingUtility util)
345       throws IOException {
346     // Ensure the archiver to be empty
347     MasterFileSystem mfs = util.getMiniHBaseCluster().getMaster().getMasterFileSystem();
348     Path archiveDir = new Path(mfs.getRootDir(), HConstants.HFILE_ARCHIVE_DIRECTORY);
349     mfs.getFileSystem().delete(archiveDir, true);
350   }
351 
352   public static void verifyRowCount(final HBaseTestingUtility util, final byte[] tableName,
353       long expectedRows) throws IOException {
354     HTable table = new HTable(util.getConfiguration(), tableName);
355     try {
356       assertEquals(expectedRows, util.countRows(table));
357     } finally {
358       table.close();
359     }
360   }
361 }