1   /**
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.client;
21  
22  import static org.junit.Assert.*;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.List;
29  import java.util.Set;
30  
31  import junit.framework.Assert;
32  import junit.framework.AssertionFailedError;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.hadoop.conf.Configuration;
37  import org.apache.hadoop.fs.FileSystem;
38  import org.apache.hadoop.fs.FileUtil;
39  import org.apache.hadoop.fs.FsShell;
40  import org.apache.hadoop.fs.Path;
41  import org.apache.hadoop.hbase.HBaseTestingUtility;
42  import org.apache.hadoop.hbase.HColumnDescriptor;
43  import org.apache.hadoop.hbase.HConstants;
44  import org.apache.hadoop.hbase.HRegionInfo;
45  import org.apache.hadoop.hbase.HTableDescriptor;
46  import org.apache.hadoop.hbase.catalog.CatalogTracker;
47  import org.apache.hadoop.hbase.catalog.MetaMigrationRemovingHTD;
48  import org.apache.hadoop.hbase.catalog.MetaReader;
49  import org.apache.hadoop.hbase.migration.HRegionInfo090x;
50  import org.apache.hadoop.hbase.util.Bytes;
51  import org.apache.hadoop.hbase.util.Writables;
52  import org.junit.AfterClass;
53  import org.junit.BeforeClass;
54  import org.junit.Test;
55  
56  /**
57   * Test migration that removes HTableDescriptor from HRegionInfo moving the
58   * meta version from no version to {@link MetaReader#META_VERSION}.
59   */
60  public class TestMetaMigrationRemovingHTD {
61    static final Log LOG = LogFactory.getLog(TestMetaMigrationRemovingHTD.class);
62    private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
63    private final static String TESTTABLE = "TestTable";
64    private final static int ROWCOUNT = 100;
65  
66    @BeforeClass
67    public static void setUpBeforeClass() throws Exception {
68      // Start up our mini cluster on top of an 0.90 root.dir that has data from
69      // a 0.90 hbase run -- it has a table with 100 rows in it  -- and see if
70      // we can migrate from 0.90.
71      TEST_UTIL.startMiniZKCluster();
72      TEST_UTIL.startMiniDFSCluster(1);
73      Path testdir = TEST_UTIL.getDataTestDir("TestMetaMigrationRemovingHTD");
74      // Untar our test dir.
75      File untar = untar(new File(testdir.toString()));
76      // Now copy the untar up into hdfs so when we start hbase, we'll run from it.
77      Configuration conf = TEST_UTIL.getConfiguration();
78      FsShell shell = new FsShell(conf);
79      FileSystem fs = FileSystem.get(conf);
80      // Minihbase roots itself in user home directory up in minidfs.
81      Path homedir = fs.getHomeDirectory();
82      doFsCommand(shell,
83        new String [] {"-put", untar.toURI().toString(), homedir.toString()});
84      // See whats in minihdfs.
85      doFsCommand(shell, new String [] {"-lsr", "/"});
86      TEST_UTIL.startMiniHBaseCluster(1, 1);
87      // Assert we are running against the copied-up filesystem.  The copied-up
88      // rootdir should have had a table named 'TestTable' in it.  Assert it
89      // present.
90      HTable t = new HTable(TEST_UTIL.getConfiguration(), TESTTABLE);
91      ResultScanner scanner = t.getScanner(new Scan());
92      int count = 0;
93      while (scanner.next() != null) {
94        count++;
95      }
96      // Assert that we find all 100 rows that are in the data we loaded.  If
97      // so then we must have migrated it from 0.90 to 0.92.
98      Assert.assertEquals(ROWCOUNT, count);
99    }
100 
101   private static File untar(final File testdir) throws IOException {
102     // Find the src data under src/test/data
103     final String datafile = "hbase-4388-root.dir";
104     String srcTarFile =
105       System.getProperty("project.build.testSourceDirectory", "src/test") +
106       File.separator + "data" + File.separator + datafile + ".tgz";
107     File homedir = new File(testdir.toString());
108     File tgtUntarDir = new File(homedir, datafile);
109     if (tgtUntarDir.exists()) {
110       if (!FileUtil.fullyDelete(tgtUntarDir)) {
111         throw new IOException("Failed delete of " + tgtUntarDir.toString());
112       }
113     }
114     LOG.info("Untarring " + srcTarFile + " into " + homedir.toString());
115     FileUtil.unTar(new File(srcTarFile), homedir);
116     Assert.assertTrue(tgtUntarDir.exists());
117     return tgtUntarDir;
118   }
119 
120   private static void doFsCommand(final FsShell shell, final String [] args)
121   throws Exception {
122     // Run the 'put' command.
123     int errcode = shell.run(args);
124     if (errcode != 0) throw new IOException("Failed put; errcode=" + errcode);
125   }
126 
127   /**
128    * @throws java.lang.Exception
129    */
130   @AfterClass
131   public static void tearDownAfterClass() throws Exception {
132     TEST_UTIL.shutdownMiniCluster();
133   }
134 
135   @Test
136   public void testMetaUpdatedFlagInROOT() throws Exception {
137     boolean metaUpdated = MetaMigrationRemovingHTD.
138       isMetaHRIUpdated(TEST_UTIL.getMiniHBaseCluster().getMaster());
139     assertEquals(true, metaUpdated);
140   }
141 
142   @Test
143   public void testMetaMigration() throws Exception {
144     LOG.info("Starting testMetaWithLegacyHRI");
145     final byte [] FAMILY = Bytes.toBytes("family");
146     HTableDescriptor htd = new HTableDescriptor("testMetaMigration");
147     HColumnDescriptor hcd = new HColumnDescriptor(FAMILY);
148       htd.addFamily(hcd);
149     Configuration conf = TEST_UTIL.getConfiguration();
150     createMultiRegionsWithLegacyHRI(conf, htd, FAMILY,
151         new byte[][]{
152             HConstants.EMPTY_START_ROW,
153             Bytes.toBytes("region_a"),
154             Bytes.toBytes("region_b")});
155     CatalogTracker ct =
156       TEST_UTIL.getMiniHBaseCluster().getMaster().getCatalogTracker();
157     // Erase the current version of root meta for this test.
158     undoVersionInMeta();
159     MetaReader.fullScanMetaAndPrint(ct);
160     LOG.info("Meta Print completed.testUpdatesOnMetaWithLegacyHRI");
161 
162     Set<HTableDescriptor> htds =
163       MetaMigrationRemovingHTD.updateMetaWithNewRegionInfo(
164         TEST_UTIL.getHBaseCluster().getMaster());
165     MetaReader.fullScanMetaAndPrint(ct);
166     // Should be one entry only and it should be for the table we just added.
167     assertEquals(1, htds.size());
168     assertTrue(htds.contains(htd));
169     // Assert that the flag in ROOT is updated to reflect the correct status
170     boolean metaUpdated =
171       MetaMigrationRemovingHTD.isMetaHRIUpdated(
172         TEST_UTIL.getMiniHBaseCluster().getMaster());
173     assertEquals(true, metaUpdated);
174   }
175 
176   /**
177    * This test assumes a master crash/failure during the meta migration process
178    * and attempts to continue the meta migration process when a new master takes over.
179    * When a master dies during the meta migration we will have some rows of
180    * META.CatalogFamily updated with new HRI, (i.e HRI with out HTD) and some
181    * still hanging with legacy HRI. (i.e HRI with HTD). When the backup master/ or
182    * fresh start of master attempts the migration it will encouter some rows of META
183    * already updated with new HRI and some still legacy. This test will simulate this
184    * scenario and validates that the migration process can safely skip the updated
185    * rows and migrate any pending rows at startup.
186    * @throws Exception
187    */
188   @Test
189   public void testMasterCrashDuringMetaMigration() throws Exception {
190     final byte[] FAMILY = Bytes.toBytes("family");
191     HTableDescriptor htd = new HTableDescriptor("testMasterCrashDuringMetaMigration");
192     HColumnDescriptor hcd = new HColumnDescriptor(FAMILY);
193       htd.addFamily(hcd);
194     Configuration conf = TEST_UTIL.getConfiguration();
195     // Create 10 New regions.
196     createMultiRegionsWithNewHRI(conf, htd, FAMILY, 10);
197     // Create 10 Legacy regions.
198     createMultiRegionsWithLegacyHRI(conf, htd, FAMILY, 10);
199     CatalogTracker ct =
200       TEST_UTIL.getMiniHBaseCluster().getMaster().getCatalogTracker();
201     // Erase the current version of root meta for this test.
202     undoVersionInMeta();
203     MetaMigrationRemovingHTD.updateRootWithMetaMigrationStatus(ct);
204     //MetaReader.fullScanMetaAndPrint(ct);
205     LOG.info("Meta Print completed.testUpdatesOnMetaWithLegacyHRI");
206 
207     Set<HTableDescriptor> htds =
208       MetaMigrationRemovingHTD.updateMetaWithNewRegionInfo(
209         TEST_UTIL.getHBaseCluster().getMaster());
210     assertEquals(1, htds.size());
211     assertTrue(htds.contains(htd));
212     // Assert that the flag in ROOT is updated to reflect the correct status
213     boolean metaUpdated = MetaMigrationRemovingHTD.
214       isMetaHRIUpdated(TEST_UTIL.getMiniHBaseCluster().getMaster());
215     assertEquals(true, metaUpdated);
216     LOG.info("END testMetaWithLegacyHRI");
217   }
218 
219   private void undoVersionInMeta() throws IOException {
220     Delete d = new Delete(HRegionInfo.ROOT_REGIONINFO.getRegionName());
221     // Erase the current version of root meta for this test.
222     d.deleteColumn(HConstants.CATALOG_FAMILY, HConstants.META_VERSION_QUALIFIER);
223     HTable rootTable =
224       new HTable(TEST_UTIL.getConfiguration(), HConstants.ROOT_TABLE_NAME);
225     try {
226       rootTable.delete(d);
227     } finally {
228       rootTable.close();
229     }
230   }
231 
232   public static void assertEquals(int expected, int actual) {
233     if (expected != actual) {
234       throw new AssertionFailedError("expected:<" +
235       expected + "> but was:<" +
236       actual + ">");
237     }
238   }
239 
240   public static void assertEquals(boolean expected, boolean actual) {
241     if (expected != actual) {
242       throw new AssertionFailedError("expected:<" +
243       expected + "> but was:<" +
244       actual + ">");
245     }
246   }
247 
248 
249   /**
250    * @param c
251    * @param htd
252    * @param family
253    * @param numRegions
254    * @return
255    * @throws IOException
256    * @deprecated Just for testing migration of meta from 0.90 to 0.92... will be
257    * removed thereafter
258    */
259   public int createMultiRegionsWithLegacyHRI(final Configuration c,
260       final HTableDescriptor htd, final byte [] family, int numRegions)
261   throws IOException {
262     if (numRegions < 3) throw new IOException("Must create at least 3 regions");
263     byte [] startKey = Bytes.toBytes("aaaaa");
264     byte [] endKey = Bytes.toBytes("zzzzz");
265     byte [][] splitKeys = Bytes.split(startKey, endKey, numRegions - 3);
266     byte [][] regionStartKeys = new byte[splitKeys.length+1][];
267     for (int i=0;i<splitKeys.length;i++) {
268       regionStartKeys[i+1] = splitKeys[i];
269     }
270     regionStartKeys[0] = HConstants.EMPTY_BYTE_ARRAY;
271     return createMultiRegionsWithLegacyHRI(c, htd, family, regionStartKeys);
272   }
273 
274   /**
275    * @param c
276    * @param htd
277    * @param columnFamily
278    * @param startKeys
279    * @return
280    * @throws IOException
281    * @deprecated Just for testing migration of meta from 0.90 to 0.92... will be
282    * removed thereafter
283    */
284   public int createMultiRegionsWithLegacyHRI(final Configuration c,
285       final HTableDescriptor htd, final byte[] columnFamily, byte [][] startKeys)
286   throws IOException {
287     Arrays.sort(startKeys, Bytes.BYTES_COMPARATOR);
288     HTable meta = new HTable(c, HConstants.META_TABLE_NAME);
289     if(!htd.hasFamily(columnFamily)) {
290       HColumnDescriptor hcd = new HColumnDescriptor(columnFamily);
291       htd.addFamily(hcd);
292     }
293     List<HRegionInfo090x> newRegions
294         = new ArrayList<HRegionInfo090x>(startKeys.length);
295     int count = 0;
296     for (int i = 0; i < startKeys.length; i++) {
297       int j = (i + 1) % startKeys.length;
298       HRegionInfo090x hri = new HRegionInfo090x(htd,
299         startKeys[i], startKeys[j]);
300       Put put = new Put(hri.getRegionName());
301       put.setWriteToWAL(false);
302       put.add(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER,
303         Writables.getBytes(hri));
304       meta.put(put);
305       LOG.info("createMultiRegions: PUT inserted " + hri.toString());
306 
307       newRegions.add(hri);
308       count++;
309     }
310     return count;
311   }
312 
313   int createMultiRegionsWithNewHRI(final Configuration c,
314       final HTableDescriptor htd, final byte [] family, int numRegions)
315   throws IOException {
316     if (numRegions < 3) throw new IOException("Must create at least 3 regions");
317     byte [] startKey = Bytes.toBytes("aaaaa");
318     byte [] endKey = Bytes.toBytes("zzzzz");
319     byte [][] splitKeys = Bytes.split(startKey, endKey, numRegions - 3);
320     byte [][] regionStartKeys = new byte[splitKeys.length+1][];
321     for (int i=0;i<splitKeys.length;i++) {
322       regionStartKeys[i+1] = splitKeys[i];
323     }
324     regionStartKeys[0] = HConstants.EMPTY_BYTE_ARRAY;
325     return createMultiRegionsWithNewHRI(c, htd, family, regionStartKeys);
326   }
327 
328   int createMultiRegionsWithNewHRI(final Configuration c, final HTableDescriptor htd,
329       final byte[] columnFamily, byte [][] startKeys)
330   throws IOException {
331     Arrays.sort(startKeys, Bytes.BYTES_COMPARATOR);
332     HTable meta = new HTable(c, HConstants.META_TABLE_NAME);
333     if(!htd.hasFamily(columnFamily)) {
334       HColumnDescriptor hcd = new HColumnDescriptor(columnFamily);
335       htd.addFamily(hcd);
336     }
337     List<HRegionInfo> newRegions
338         = new ArrayList<HRegionInfo>(startKeys.length);
339     int count = 0;
340     for (int i = 0; i < startKeys.length; i++) {
341       int j = (i + 1) % startKeys.length;
342       HRegionInfo hri = new HRegionInfo(htd.getName(),
343         startKeys[i], startKeys[j]);
344       Put put = new Put(hri.getRegionName());
345       put.setWriteToWAL(false);
346       put.add(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER,
347         Writables.getBytes(hri));
348       meta.put(put);
349       LOG.info("createMultiRegions: PUT inserted " + hri.toString());
350 
351       newRegions.add(hri);
352       count++;
353     }
354     return count;
355   }
356 }