View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   * http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  package org.apache.hadoop.hbase.coprocessor;
21  
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.apache.hadoop.conf.Configuration;
25  import org.apache.hadoop.fs.FileSystem;
26  import org.apache.hadoop.fs.Path;
27  import org.apache.hadoop.hbase.*;
28  import org.apache.hadoop.hbase.regionserver.HRegion;
29  import org.apache.hadoop.hbase.client.Put;
30  import org.apache.hadoop.hbase.regionserver.wal.HLog;
31  import org.apache.hadoop.hbase.regionserver.wal.HLogFactory;
32  import org.apache.hadoop.hbase.regionserver.wal.HLogSplitter;
33  import org.apache.hadoop.hbase.regionserver.wal.WALCoprocessorHost;
34  import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
35  import org.apache.hadoop.hbase.security.User;
36  import org.apache.hadoop.hbase.util.Bytes;
37  import org.apache.hadoop.hbase.util.FSUtils;
38  import org.apache.hadoop.hbase.util.EnvironmentEdge;
39  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
40  import org.junit.After;
41  import org.junit.AfterClass;
42  import org.junit.Before;
43  import org.junit.BeforeClass;
44  import org.junit.Test;
45  import org.junit.experimental.categories.Category;
46  
47  import java.io.IOException;
48  import java.security.PrivilegedExceptionAction;
49  import java.util.Arrays;
50  import java.util.List;
51  import java.util.Map;
52  
53  import static org.junit.Assert.*;
54  
55  /**
56   * Tests invocation of the
57   * {@link org.apache.hadoop.hbase.coprocessor.MasterObserver} interface hooks at
58   * all appropriate times during normal HMaster operations.
59   */
60  @Category(MediumTests.class)
61  public class TestWALObserver {
62    private static final Log LOG = LogFactory.getLog(TestWALObserver.class);
63    private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
64  
65    private static byte[] TEST_TABLE = Bytes.toBytes("observedTable");
66    private static byte[][] TEST_FAMILY = { Bytes.toBytes("fam1"),
67        Bytes.toBytes("fam2"), Bytes.toBytes("fam3"), };
68    private static byte[][] TEST_QUALIFIER = { Bytes.toBytes("q1"),
69        Bytes.toBytes("q2"), Bytes.toBytes("q3"), };
70    private static byte[][] TEST_VALUE = { Bytes.toBytes("v1"),
71        Bytes.toBytes("v2"), Bytes.toBytes("v3"), };
72    private static byte[] TEST_ROW = Bytes.toBytes("testRow");
73  
74    private Configuration conf;
75    private FileSystem fs;
76    private Path dir;
77    private Path hbaseRootDir;
78    private String logName;
79    private Path oldLogDir;
80    private Path logDir;
81  
82    @BeforeClass
83    public static void setupBeforeClass() throws Exception {
84      Configuration conf = TEST_UTIL.getConfiguration();
85      conf.set(CoprocessorHost.WAL_COPROCESSOR_CONF_KEY,
86          SampleRegionWALObserver.class.getName());
87      conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
88          SampleRegionWALObserver.class.getName());
89      conf.setBoolean("dfs.support.append", true);
90      conf.setInt("dfs.client.block.recovery.retries", 2);
91  
92      TEST_UTIL.startMiniCluster(1);
93      Path hbaseRootDir = TEST_UTIL.getDFSCluster().getFileSystem()
94          .makeQualified(new Path("/hbase"));
95      LOG.info("hbase.rootdir=" + hbaseRootDir);
96      FSUtils.setRootDir(conf, hbaseRootDir);
97    }
98  
99    @AfterClass
100   public static void teardownAfterClass() throws Exception {
101     TEST_UTIL.shutdownMiniCluster();
102   }
103 
104   @Before
105   public void setUp() throws Exception {
106     this.conf = HBaseConfiguration.create(TEST_UTIL.getConfiguration());
107     // this.cluster = TEST_UTIL.getDFSCluster();
108     this.fs = TEST_UTIL.getDFSCluster().getFileSystem();
109     this.hbaseRootDir = FSUtils.getRootDir(conf);
110     this.dir = new Path(this.hbaseRootDir, TestWALObserver.class.getName());
111     this.oldLogDir = new Path(this.hbaseRootDir,
112         HConstants.HREGION_OLDLOGDIR_NAME);
113     this.logDir = new Path(this.hbaseRootDir, HConstants.HREGION_LOGDIR_NAME);
114     this.logName = HConstants.HREGION_LOGDIR_NAME;
115 
116     if (TEST_UTIL.getDFSCluster().getFileSystem().exists(this.hbaseRootDir)) {
117       TEST_UTIL.getDFSCluster().getFileSystem().delete(this.hbaseRootDir, true);
118     }
119   }
120 
121   @After
122   public void tearDown() throws Exception {
123     TEST_UTIL.getDFSCluster().getFileSystem().delete(this.hbaseRootDir, true);
124   }
125 
126   /**
127    * Test WAL write behavior with WALObserver. The coprocessor monitors a
128    * WALEdit written to WAL, and ignore, modify, and add KeyValue's for the
129    * WALEdit.
130    */
131   @Test
132   public void testWALObserverWriteToWAL() throws Exception {
133 
134     HRegionInfo hri = createBasic3FamilyHRegionInfo(Bytes.toString(TEST_TABLE));
135     final HTableDescriptor htd = createBasic3FamilyHTD(Bytes
136         .toString(TEST_TABLE));
137 
138     Path basedir = new Path(this.hbaseRootDir, Bytes.toString(TEST_TABLE));
139     deleteDir(basedir);
140     fs.mkdirs(new Path(basedir, hri.getEncodedName()));
141 
142     HLog log = HLogFactory.createHLog(this.fs, hbaseRootDir,
143         TestWALObserver.class.getName(), this.conf);
144     SampleRegionWALObserver cp = getCoprocessor(log);
145 
146     // TEST_FAMILY[0] shall be removed from WALEdit.
147     // TEST_FAMILY[1] value shall be changed.
148     // TEST_FAMILY[2] shall be added to WALEdit, although it's not in the put.
149     cp.setTestValues(TEST_TABLE, TEST_ROW, TEST_FAMILY[0], TEST_QUALIFIER[0],
150         TEST_FAMILY[1], TEST_QUALIFIER[1], TEST_FAMILY[2], TEST_QUALIFIER[2]);
151 
152     assertFalse(cp.isPreWALWriteCalled());
153     assertFalse(cp.isPostWALWriteCalled());
154 
155     // TEST_FAMILY[2] is not in the put, however it shall be added by the tested
156     // coprocessor.
157     // Use a Put to create familyMap.
158     Put p = creatPutWith2Families(TEST_ROW);
159 
160     Map<byte[], List<Cell>> familyMap = p.getFamilyCellMap();
161     WALEdit edit = new WALEdit();
162     addFamilyMapToWALEdit(familyMap, edit);
163 
164     boolean foundFamily0 = false;
165     boolean foundFamily2 = false;
166     boolean modifiedFamily1 = false;
167 
168     List<KeyValue> kvs = edit.getKeyValues();
169 
170     for (KeyValue kv : kvs) {
171       if (Arrays.equals(kv.getFamily(), TEST_FAMILY[0])) {
172         foundFamily0 = true;
173       }
174       if (Arrays.equals(kv.getFamily(), TEST_FAMILY[2])) {
175         foundFamily2 = true;
176       }
177       if (Arrays.equals(kv.getFamily(), TEST_FAMILY[1])) {
178         if (!Arrays.equals(kv.getValue(), TEST_VALUE[1])) {
179           modifiedFamily1 = true;
180         }
181       }
182     }
183     assertTrue(foundFamily0);
184     assertFalse(foundFamily2);
185     assertFalse(modifiedFamily1);
186 
187     // it's where WAL write cp should occur.
188     long now = EnvironmentEdgeManager.currentTimeMillis();
189     log.append(hri, hri.getTableName(), edit, now, htd);
190 
191     // the edit shall have been change now by the coprocessor.
192     foundFamily0 = false;
193     foundFamily2 = false;
194     modifiedFamily1 = false;
195     for (KeyValue kv : kvs) {
196       if (Arrays.equals(kv.getFamily(), TEST_FAMILY[0])) {
197         foundFamily0 = true;
198       }
199       if (Arrays.equals(kv.getFamily(), TEST_FAMILY[2])) {
200         foundFamily2 = true;
201       }
202       if (Arrays.equals(kv.getFamily(), TEST_FAMILY[1])) {
203         if (!Arrays.equals(kv.getValue(), TEST_VALUE[1])) {
204           modifiedFamily1 = true;
205         }
206       }
207     }
208     assertFalse(foundFamily0);
209     assertTrue(foundFamily2);
210     assertTrue(modifiedFamily1);
211 
212     assertTrue(cp.isPreWALWriteCalled());
213     assertTrue(cp.isPostWALWriteCalled());
214   }
215 
216   /**
217    * Test WAL replay behavior with WALObserver.
218    */
219   @Test
220   public void testWALCoprocessorReplay() throws Exception {
221     // WAL replay is handled at HRegion::replayRecoveredEdits(), which is
222     // ultimately called by HRegion::initialize()
223     TableName tableName = TableName.valueOf("testWALCoprocessorReplay");
224     final HTableDescriptor htd = getBasic3FamilyHTableDescriptor(tableName);
225     // final HRegionInfo hri =
226     // createBasic3FamilyHRegionInfo(Bytes.toString(tableName));
227     // final HRegionInfo hri1 =
228     // createBasic3FamilyHRegionInfo(Bytes.toString(tableName));
229     final HRegionInfo hri = new HRegionInfo(tableName, null, null);
230 
231     final Path basedir =
232         FSUtils.getTableDir(this.hbaseRootDir, tableName);
233     deleteDir(basedir);
234     fs.mkdirs(new Path(basedir, hri.getEncodedName()));
235 
236     final Configuration newConf = HBaseConfiguration.create(this.conf);
237 
238     // HLog wal = new HLog(this.fs, this.dir, this.oldLogDir, this.conf);
239     HLog wal = createWAL(this.conf);
240     // Put p = creatPutWith2Families(TEST_ROW);
241     WALEdit edit = new WALEdit();
242     long now = EnvironmentEdgeManager.currentTimeMillis();
243     // addFamilyMapToWALEdit(p.getFamilyMap(), edit);
244     final int countPerFamily = 1000;
245     // for (HColumnDescriptor hcd: hri.getTableDesc().getFamilies()) {
246     for (HColumnDescriptor hcd : htd.getFamilies()) {
247       // addWALEdits(tableName, hri, TEST_ROW, hcd.getName(), countPerFamily,
248       // EnvironmentEdgeManager.getDelegate(), wal);
249       addWALEdits(tableName, hri, TEST_ROW, hcd.getName(), countPerFamily,
250           EnvironmentEdgeManager.getDelegate(), wal, htd);
251     }
252     wal.append(hri, tableName, edit, now, htd);
253     // sync to fs.
254     wal.sync();
255 
256     User user = HBaseTestingUtility.getDifferentUser(newConf,
257         ".replay.wal.secondtime");
258     user.runAs(new PrivilegedExceptionAction() {
259       public Object run() throws Exception {
260         Path p = runWALSplit(newConf);
261         LOG.info("WALSplit path == " + p);
262         FileSystem newFS = FileSystem.get(newConf);
263         // Make a new wal for new region open.
264         HLog wal2 = createWAL(newConf);
265         HRegion region = HRegion.openHRegion(newConf, FileSystem.get(newConf), hbaseRootDir,
266             hri, htd, wal2, TEST_UTIL.getHBaseCluster().getRegionServer(0), null);
267         long seqid2 = region.getOpenSeqNum();
268 
269         SampleRegionWALObserver cp2 =
270           (SampleRegionWALObserver)region.getCoprocessorHost().findCoprocessor(
271               SampleRegionWALObserver.class.getName());
272         // TODO: asserting here is problematic.
273         assertNotNull(cp2);
274         assertTrue(cp2.isPreWALRestoreCalled());
275         assertTrue(cp2.isPostWALRestoreCalled());
276         region.close();
277         wal2.closeAndDelete();
278         return null;
279       }
280     });
281   }
282 
283   /**
284    * Test to see CP loaded successfully or not. There is a duplication at
285    * TestHLog, but the purpose of that one is to see whether the loaded CP will
286    * impact existing HLog tests or not.
287    */
288   @Test
289   public void testWALObserverLoaded() throws Exception {
290     HLog log = HLogFactory.createHLog(fs, hbaseRootDir,
291         TestWALObserver.class.getName(), conf);
292     assertNotNull(getCoprocessor(log));
293   }
294 
295   private SampleRegionWALObserver getCoprocessor(HLog wal) throws Exception {
296     WALCoprocessorHost host = wal.getCoprocessorHost();
297     Coprocessor c = host.findCoprocessor(SampleRegionWALObserver.class
298         .getName());
299     return (SampleRegionWALObserver) c;
300   }
301 
302   /*
303    * Creates an HRI around an HTD that has <code>tableName</code> and three
304    * column families named.
305    * 
306    * @param tableName Name of table to use when we create HTableDescriptor.
307    */
308   private HRegionInfo createBasic3FamilyHRegionInfo(final String tableName) {
309     HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(tableName));
310 
311     for (int i = 0; i < TEST_FAMILY.length; i++) {
312       HColumnDescriptor a = new HColumnDescriptor(TEST_FAMILY[i]);
313       htd.addFamily(a);
314     }
315     return new HRegionInfo(htd.getTableName(), null, null, false);
316   }
317 
318   /*
319    * @param p Directory to cleanup
320    */
321   private void deleteDir(final Path p) throws IOException {
322     if (this.fs.exists(p)) {
323       if (!this.fs.delete(p, true)) {
324         throw new IOException("Failed remove of " + p);
325       }
326     }
327   }
328 
329   private Put creatPutWith2Families(byte[] row) throws IOException {
330     Put p = new Put(row);
331     for (int i = 0; i < TEST_FAMILY.length - 1; i++) {
332       p.add(TEST_FAMILY[i], TEST_QUALIFIER[i], TEST_VALUE[i]);
333     }
334     return p;
335   }
336 
337   /**
338    * Copied from HRegion.
339    * 
340    * @param familyMap
341    *          map of family->edits
342    * @param walEdit
343    *          the destination entry to append into
344    */
345   private void addFamilyMapToWALEdit(Map<byte[], List<Cell>> familyMap,
346       WALEdit walEdit) {
347     for (List<Cell> edits : familyMap.values()) {
348       for (Cell cell : edits) {
349         // KeyValue v1 expectation. Cast for now until we go all Cell all the time. TODO.
350         walEdit.add((KeyValue)cell);
351       }
352     }
353   }
354 
355   private Path runWALSplit(final Configuration c) throws IOException {
356     List<Path> splits = HLogSplitter.split(
357       hbaseRootDir, logDir, oldLogDir, FileSystem.get(c), c);
358     // Split should generate only 1 file since there's only 1 region
359     assertEquals(1, splits.size());
360     // Make sure the file exists
361     assertTrue(fs.exists(splits.get(0)));
362     LOG.info("Split file=" + splits.get(0));
363     return splits.get(0);
364   }
365 
366   private HLog createWAL(final Configuration c) throws IOException {
367     return HLogFactory.createHLog(FileSystem.get(c), hbaseRootDir, logName, c);
368   }
369 
370   private void addWALEdits(final TableName tableName, final HRegionInfo hri,
371       final byte[] rowName, final byte[] family, final int count,
372       EnvironmentEdge ee, final HLog wal, final HTableDescriptor htd)
373       throws IOException {
374     String familyStr = Bytes.toString(family);
375     for (int j = 0; j < count; j++) {
376       byte[] qualifierBytes = Bytes.toBytes(Integer.toString(j));
377       byte[] columnBytes = Bytes.toBytes(familyStr + ":" + Integer.toString(j));
378       WALEdit edit = new WALEdit();
379       edit.add(new KeyValue(rowName, family, qualifierBytes, ee
380           .currentTimeMillis(), columnBytes));
381       wal.append(hri, tableName, edit, ee.currentTimeMillis(), htd);
382     }
383   }
384 
385   private HTableDescriptor getBasic3FamilyHTableDescriptor(
386       final TableName tableName) {
387     HTableDescriptor htd = new HTableDescriptor(tableName);
388 
389     for (int i = 0; i < TEST_FAMILY.length; i++) {
390       HColumnDescriptor a = new HColumnDescriptor(TEST_FAMILY[i]);
391       htd.addFamily(a);
392     }
393     return htd;
394   }
395 
396   private HTableDescriptor createBasic3FamilyHTD(final String tableName) {
397     HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(tableName));
398     HColumnDescriptor a = new HColumnDescriptor(Bytes.toBytes("a"));
399     htd.addFamily(a);
400     HColumnDescriptor b = new HColumnDescriptor(Bytes.toBytes("b"));
401     htd.addFamily(b);
402     HColumnDescriptor c = new HColumnDescriptor(Bytes.toBytes("c"));
403     htd.addFamily(c);
404     return htd;
405   }
406 
407 }