1   /**
2    * Copyright 2007 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.regionserver.wal;
21  
22  import java.io.OutputStream;
23  import java.lang.reflect.Method;
24  import java.util.ArrayList;
25  import java.util.List;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.commons.logging.impl.Log4JLogger;
30  import org.apache.hadoop.hbase.HBaseClusterTestCase;
31  import org.apache.hadoop.hbase.HColumnDescriptor;
32  import org.apache.hadoop.hbase.HConstants;
33  import org.apache.hadoop.hbase.HTableDescriptor;
34  import org.apache.hadoop.hbase.regionserver.HRegionServer;
35  import org.apache.hadoop.hbase.regionserver.HRegion;
36  import org.apache.hadoop.hbase.client.HBaseAdmin;
37  import org.apache.hadoop.hbase.client.HTable;
38  import org.apache.hadoop.hbase.client.Put;
39  import org.apache.hadoop.hbase.util.Bytes;
40  import org.apache.hadoop.hbase.util.FSUtils;
41  import org.apache.hadoop.hdfs.DFSClient;
42  import org.apache.hadoop.hdfs.MiniDFSCluster.DataNodeProperties;
43  import org.apache.hadoop.hdfs.protocol.DatanodeInfo;
44  import org.apache.hadoop.hdfs.server.datanode.DataNode;
45  import org.apache.hadoop.hdfs.server.namenode.FSNamesystem;
46  import org.apache.hadoop.hdfs.server.namenode.LeaseManager;
47  import org.apache.log4j.Level;
48  
49  /**
50   * Test log deletion as logs are rolled.
51   */
52  public class TestLogRolling extends HBaseClusterTestCase {
53    private static final Log LOG = LogFactory.getLog(TestLogRolling.class);
54    private HRegionServer server;
55    private HLog log;
56    private String tableName;
57    private byte[] value;
58  
59   // verbose logging on classes that are touched in these tests
60   {
61     ((Log4JLogger)DataNode.LOG).getLogger().setLevel(Level.ALL);
62     ((Log4JLogger)LeaseManager.LOG).getLogger().setLevel(Level.ALL);
63     ((Log4JLogger)FSNamesystem.LOG).getLogger().setLevel(Level.ALL);
64     ((Log4JLogger)DFSClient.LOG).getLogger().setLevel(Level.ALL);
65     ((Log4JLogger)HRegionServer.LOG).getLogger().setLevel(Level.ALL);
66     ((Log4JLogger)HRegion.LOG).getLogger().setLevel(Level.ALL);
67     ((Log4JLogger)HLog.LOG).getLogger().setLevel(Level.ALL);
68   }
69  
70    /**
71     * constructor
72     * @throws Exception
73     */
74    public TestLogRolling() throws Exception {
75      // start one regionserver and a minidfs.
76      super();
77      try {
78        this.server = null;
79        this.log = null;
80        this.tableName = null;
81        this.value = null;
82  
83        String className = this.getClass().getName();
84        StringBuilder v = new StringBuilder(className);
85        while (v.length() < 1000) {
86          v.append(className);
87        }
88        value = Bytes.toBytes(v.toString());
89  
90      } catch (Exception e) {
91        LOG.fatal("error in constructor", e);
92        throw e;
93      }
94    }
95  
96    // Need to override this setup so we can edit the config before it gets sent
97   // to the HDFS & HBase cluster startup.
98    @Override
99    protected void setUp() throws Exception {
100     /**** configuration for testLogRolling ****/
101     // Force a region split after every 768KB
102     conf.setLong("hbase.hregion.max.filesize", 768L * 1024L);
103 
104     // We roll the log after every 32 writes
105     conf.setInt("hbase.regionserver.maxlogentries", 32);
106 
107     // For less frequently updated regions flush after every 2 flushes
108     conf.setInt("hbase.hregion.memstore.optionalflushcount", 2);
109 
110     // We flush the cache after every 8192 bytes
111     conf.setInt("hbase.hregion.memstore.flush.size", 8192);
112 
113     // Increase the amount of time between client retries
114     conf.setLong("hbase.client.pause", 15 * 1000);
115 
116     // Reduce thread wake frequency so that other threads can get
117     // a chance to run.
118     conf.setInt(HConstants.THREAD_WAKE_FREQUENCY, 2 * 1000);
119 
120    /**** configuration for testLogRollOnDatanodeDeath ****/
121    // make sure log.hflush() calls syncFs() to open a pipeline
122    conf.setBoolean("dfs.support.append", true);
123    // lower the namenode & datanode heartbeat so the namenode 
124    // quickly detects datanode failures
125    conf.setInt("heartbeat.recheck.interval", 5000);
126    conf.setInt("dfs.heartbeat.interval", 1);
127    // the namenode might still try to choose the recently-dead datanode 
128    // for a pipeline, so try to a new pipeline multiple times
129    conf.setInt("dfs.client.block.write.retries", 30);
130    
131    super.setUp();
132   }
133 
134   private void startAndWriteData() throws Exception {
135     // When the META table can be opened, the region servers are running
136     new HTable(conf, HConstants.META_TABLE_NAME);
137     this.server = cluster.getRegionServerThreads().get(0).getRegionServer();
138     this.log = server.getLog();
139 
140     // Create the test table and open it
141     HTableDescriptor desc = new HTableDescriptor(tableName);
142     desc.addFamily(new HColumnDescriptor(HConstants.CATALOG_FAMILY));
143     HBaseAdmin admin = new HBaseAdmin(conf);
144     admin.createTable(desc);
145     HTable table = new HTable(conf, tableName);
146     for (int i = 1; i <= 256; i++) {    // 256 writes should cause 8 log rolls
147       Put put = new Put(Bytes.toBytes("row" + String.format("%1$04d", i)));
148       put.add(HConstants.CATALOG_FAMILY, null, value);
149       table.put(put);
150       if (i % 32 == 0) {
151         // After every 32 writes sleep to let the log roller run
152         try {
153           Thread.sleep(2000);
154         } catch (InterruptedException e) {
155           // continue
156         }
157       }
158     }
159   }
160 
161   /**
162    * Tests that logs are deleted
163    *
164    * @throws Exception
165    */
166   public void testLogRolling() throws Exception {
167     this.tableName = getName();
168     try {
169       startAndWriteData();
170       LOG.info("after writing there are " + log.getNumLogFiles() + " log files");
171 
172       // flush all regions
173 
174       List<HRegion> regions =
175         new ArrayList<HRegion>(server.getOnlineRegions());
176       for (HRegion r: regions) {
177         r.flushcache();
178       }
179 
180       // Now roll the log
181       log.rollWriter();
182 
183       int count = log.getNumLogFiles();
184       LOG.info("after flushing all regions and rolling logs there are " +
185           log.getNumLogFiles() + " log files");
186       assertTrue(("actual count: " + count), count <= 2);
187     } catch (Exception e) {
188       LOG.fatal("unexpected exception", e);
189       throw e;
190     }
191   }
192 
193   void writeData(HTable table, int rownum) throws Exception {
194     Put put = new Put(Bytes.toBytes("row" + String.format("%1$04d", rownum)));
195     put.add(HConstants.CATALOG_FAMILY, null, value);
196     table.put(put);
197 
198     // sleep to let the log roller run (if it needs to)
199     try {
200       Thread.sleep(2000);
201     } catch (InterruptedException e) {
202       // continue
203     }
204   }
205   
206   /**
207    * Tests that logs are rolled upon detecting datanode death
208    * Requires an HDFS jar with HDFS-826 & syncFs() support (HDFS-200)
209    * 
210    * @throws Exception
211    */
212   public void testLogRollOnDatanodeDeath() throws Exception {
213     assertTrue("This test requires HLog file replication.", 
214         fs.getDefaultReplication() > 1);
215     
216     // When the META table can be opened, the region servers are running
217     new HTable(conf, HConstants.META_TABLE_NAME);
218     this.server = cluster.getRegionServer(0);
219     this.log = server.getLog();
220     
221     assertTrue("Need HDFS-826 for this test", log.canGetCurReplicas());
222     // don't run this test without append support (HDFS-200 & HDFS-142)
223     assertTrue("Need append support for this test", FSUtils.isAppendSupported(conf));
224 
225     // add up the datanode count, to ensure proper replication when we kill 1
226     dfsCluster.startDataNodes(conf, 1, true, null, null);
227     dfsCluster.waitActive();
228     assertTrue(dfsCluster.getDataNodes().size() >= 
229                fs.getDefaultReplication() + 1);
230 
231     // Create the test table and open it
232     String tableName = getName();
233     HTableDescriptor desc = new HTableDescriptor(tableName);
234     desc.addFamily(new HColumnDescriptor(HConstants.CATALOG_FAMILY));
235     HBaseAdmin admin = new HBaseAdmin(conf);
236     admin.createTable(desc);
237     HTable table = new HTable(conf, tableName);
238     table.setAutoFlush(true);
239 
240     long curTime = System.currentTimeMillis();
241     long oldFilenum = log.getFilenum();
242     assertTrue("Log should have a timestamp older than now", 
243              curTime > oldFilenum && oldFilenum != -1);
244 
245     // normal write
246     writeData(table, 1);
247     assertTrue("The log shouldn't have rolled yet", 
248               oldFilenum == log.getFilenum());
249 
250     // kill a datanode in the pipeline to force a log roll on the next sync()
251     OutputStream stm = log.getOutputStream();
252     Method getPipeline = null;
253     for (Method m : stm.getClass().getDeclaredMethods()) {
254       if(m.getName().endsWith("getPipeline")) {
255         getPipeline = m;
256         getPipeline.setAccessible(true);
257         break;
258       }
259     }
260     assertTrue("Need DFSOutputStream.getPipeline() for this test", 
261                 getPipeline != null);
262     Object repl = getPipeline.invoke(stm, new Object []{} /*NO_ARGS*/);
263     DatanodeInfo[] pipeline = (DatanodeInfo[]) repl;
264     assertTrue(pipeline.length == fs.getDefaultReplication());
265     DataNodeProperties dnprop = dfsCluster.stopDataNode(pipeline[0].getName());
266     assertTrue(dnprop != null);
267 
268     // this write should succeed, but trigger a log roll
269     writeData(table, 2);
270     long newFilenum = log.getFilenum();
271     assertTrue("Missing datanode should've triggered a log roll", 
272               newFilenum > oldFilenum && newFilenum > curTime);
273     
274     // write some more log data (this should use a new hdfs_out)
275     writeData(table, 3);
276     assertTrue("The log should not roll again.", 
277               log.getFilenum() == newFilenum);
278     assertTrue("New log file should have the default replication",
279               log.getLogReplication() == fs.getDefaultReplication());
280   }
281 }