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