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.master;
21  
22  
23  import java.io.IOException;
24  import java.util.Collection;
25  import java.util.concurrent.atomic.AtomicBoolean;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.conf.Configuration;
30  import org.apache.hadoop.hbase.HConstants;
31  import org.apache.hadoop.hbase.HBaseTestingUtility;
32  import org.apache.hadoop.hbase.HServerInfo;
33  import org.apache.hadoop.hbase.HMsg;
34  import org.apache.hadoop.hbase.MiniHBaseCluster;
35  import org.apache.hadoop.hbase.HRegionInfo;
36  import org.apache.hadoop.hbase.client.HTable;
37  import org.apache.hadoop.hbase.client.Put;
38  import org.apache.hadoop.hbase.client.Result;
39  import org.apache.hadoop.hbase.client.ResultScanner;
40  import org.apache.hadoop.hbase.client.Scan;
41  import org.apache.hadoop.hbase.master.HMaster;
42  import org.apache.hadoop.hbase.master.ProcessRegionClose;
43  import org.apache.hadoop.hbase.master.ProcessRegionOpen;
44  import org.apache.hadoop.hbase.master.RegionServerOperation;
45  import org.apache.hadoop.hbase.master.RegionServerOperationListener;
46  import org.apache.hadoop.hbase.regionserver.HRegion;
47  import org.apache.hadoop.hbase.regionserver.HRegionServer;
48  import org.apache.hadoop.hbase.util.Bytes;
49  import org.apache.hadoop.hbase.util.Threads;
50  import org.apache.hadoop.hbase.util.Writables;
51  import org.junit.AfterClass;
52  import org.junit.Assert;
53  import org.junit.Before;
54  import org.junit.BeforeClass;
55  import org.junit.Test;
56  
57  public class TestZKBasedReopenRegion {
58    private static final Log LOG = LogFactory.getLog(TestZKBasedReopenRegion.class);
59    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
60    private static final String TABLENAME = "master_transitions";
61    private static final byte [][] FAMILIES = new byte [][] {Bytes.toBytes("a"),
62      Bytes.toBytes("b"), Bytes.toBytes("c")};
63  
64    @BeforeClass public static void beforeAllTests() throws Exception {
65      Configuration c = TEST_UTIL.getConfiguration();
66      c.setBoolean("dfs.support.append", true);
67      c.setInt("hbase.regionserver.info.port", 0);
68      c.setInt("hbase.master.meta.thread.rescanfrequency", 5*1000);
69      TEST_UTIL.startMiniCluster(2);
70      TEST_UTIL.createTable(Bytes.toBytes(TABLENAME), FAMILIES);
71      HTable t = new HTable(TEST_UTIL.getConfiguration(), TABLENAME);
72      int countOfRegions = TEST_UTIL.createMultiRegions(t, getTestFamily());
73      waitUntilAllRegionsAssigned(countOfRegions);
74      addToEachStartKey(countOfRegions);
75    }
76  
77    @AfterClass public static void afterAllTests() throws IOException {
78      TEST_UTIL.shutdownMiniCluster();
79    }
80  
81    @Before public void setup() throws IOException {
82      if (TEST_UTIL.getHBaseCluster().getLiveRegionServerThreads().size() < 2) {
83        // Need at least two servers.
84        LOG.info("Started new server=" +
85          TEST_UTIL.getHBaseCluster().startRegionServer());
86        
87      }
88    }
89  
90    @Test (timeout=300000) public void testOpenRegion()
91    throws Exception {
92      MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
93      LOG.info("Number of region servers = " + cluster.getLiveRegionServerThreads().size());
94  
95      int rsIdx = 0;
96      HRegionServer regionServer = TEST_UTIL.getHBaseCluster().getRegionServer(rsIdx);
97      Collection<HRegion> regions = regionServer.getOnlineRegions();
98      HRegion region = regions.iterator().next();
99      LOG.debug("Asking RS to close region " + region.getRegionNameAsString());
100 
101     AtomicBoolean closeEventProcessed = new AtomicBoolean(false);
102     AtomicBoolean reopenEventProcessed = new AtomicBoolean(false);
103     RegionServerOperationListener listener = 
104       new ReopenRegionEventListener(region.getRegionNameAsString(), 
105                                     closeEventProcessed,
106                                     reopenEventProcessed);
107     HMaster master = TEST_UTIL.getHBaseCluster().getMaster();
108     master.getRegionServerOperationQueue().registerRegionServerOperationListener(listener);
109     HMsg closeRegionMsg = new HMsg(HMsg.Type.MSG_REGION_CLOSE, 
110                                    region.getRegionInfo(),
111                                    Bytes.toBytes("Forcing close in test")
112                                   );
113     TEST_UTIL.getHBaseCluster().addMessageToSendRegionServer(rsIdx, closeRegionMsg);
114     
115     synchronized(closeEventProcessed) {
116       closeEventProcessed.wait(3*60*1000);
117     }
118     if(!closeEventProcessed.get()) {
119       throw new Exception("Timed out, close event not called on master.");
120     }
121 
122     synchronized(reopenEventProcessed) {
123       reopenEventProcessed.wait(3*60*1000);
124     }
125     if(!reopenEventProcessed.get()) {
126       throw new Exception("Timed out, open event not called on master after region close.");
127     }    
128     
129     LOG.info("Done with test, RS informed master successfully.");
130   }
131   
132   public static class ReopenRegionEventListener implements RegionServerOperationListener {
133     
134     private static final Log LOG = LogFactory.getLog(ReopenRegionEventListener.class);
135     String regionToClose;
136     AtomicBoolean closeEventProcessed;
137     AtomicBoolean reopenEventProcessed;
138 
139     public ReopenRegionEventListener(String regionToClose, 
140                                      AtomicBoolean closeEventProcessed,
141                                      AtomicBoolean reopenEventProcessed) {
142       this.regionToClose = regionToClose;
143       this.closeEventProcessed = closeEventProcessed;
144       this.reopenEventProcessed = reopenEventProcessed;
145     }
146 
147     @Override
148     public boolean process(HServerInfo serverInfo, HMsg incomingMsg) {
149       return true;
150     }
151 
152     @Override
153     public boolean process(RegionServerOperation op) throws IOException {
154       return true;
155     }
156 
157     @Override
158     public void processed(RegionServerOperation op) {
159       LOG.debug("Master processing object: " + op.getClass().getCanonicalName());
160       if(op instanceof ProcessRegionClose) {
161         ProcessRegionClose regionCloseOp = (ProcessRegionClose)op;
162         String region = regionCloseOp.getRegionInfo().getRegionNameAsString();
163         LOG.debug("Finished closing region " + region + ", expected to close region " + regionToClose);
164         if(regionToClose.equals(region)) {
165           closeEventProcessed.set(true);
166         }
167         synchronized(closeEventProcessed) {
168           closeEventProcessed.notifyAll();
169         }
170       }
171       // Wait for open event AFTER we have closed the region
172       if(closeEventProcessed.get()) {
173         if(op instanceof ProcessRegionOpen) {
174           ProcessRegionOpen regionOpenOp = (ProcessRegionOpen)op;
175           String region = regionOpenOp.getRegionInfo().getRegionNameAsString();
176           LOG.debug("Finished closing region " + region + ", expected to close region " + regionToClose);
177           if(regionToClose.equals(region)) {
178             reopenEventProcessed.set(true);
179           }
180           synchronized(reopenEventProcessed) {
181             reopenEventProcessed.notifyAll();
182           }
183         }        
184       }
185       
186     }
187     
188   }
189   
190 
191   private static void waitUntilAllRegionsAssigned(final int countOfRegions)
192   throws IOException {
193     HTable meta = new HTable(TEST_UTIL.getConfiguration(),
194       HConstants.META_TABLE_NAME);
195     while (true) {
196       int rows = 0;
197       Scan scan = new Scan();
198       scan.addColumn(HConstants.CATALOG_FAMILY, HConstants.SERVER_QUALIFIER);
199       ResultScanner s = meta.getScanner(scan);
200       for (Result r = null; (r = s.next()) != null;) {
201         byte [] b =
202           r.getValue(HConstants.CATALOG_FAMILY, HConstants.SERVER_QUALIFIER);
203         if (b == null || b.length <= 0) break;
204         rows++;
205       }
206       s.close();
207       // If I get to here and all rows have a Server, then all have been assigned.
208       if (rows == countOfRegions) break;
209       LOG.info("Found=" + rows);
210       Threads.sleep(1000); 
211     }
212   }
213 
214   /*
215    * Add to each of the regions in .META. a value.  Key is the startrow of the
216    * region (except its 'aaa' for first region).  Actual value is the row name.
217    * @param expected
218    * @return
219    * @throws IOException
220    */
221   private static int addToEachStartKey(final int expected) throws IOException {
222     HTable t = new HTable(TEST_UTIL.getConfiguration(), TABLENAME);
223     HTable meta = new HTable(TEST_UTIL.getConfiguration(),
224         HConstants.META_TABLE_NAME);
225     int rows = 0;
226     Scan scan = new Scan();
227     scan.addColumn(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
228     ResultScanner s = meta.getScanner(scan);
229     for (Result r = null; (r = s.next()) != null;) {
230       byte [] b =
231         r.getValue(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
232       if (b == null || b.length <= 0) break;
233       HRegionInfo hri = Writables.getHRegionInfo(b);
234       // If start key, add 'aaa'.
235       byte [] row = getStartKey(hri);
236       Put p = new Put(row);
237       p.add(getTestFamily(), getTestQualifier(), row);
238       t.put(p);
239       rows++;
240     }
241     s.close();
242     Assert.assertEquals(expected, rows);
243     return rows;
244   }
245 
246   private static byte [] getStartKey(final HRegionInfo hri) {
247     return Bytes.equals(HConstants.EMPTY_START_ROW, hri.getStartKey())?
248         Bytes.toBytes("aaa"): hri.getStartKey();
249   }
250 
251   private static byte [] getTestFamily() {
252     return FAMILIES[0];
253   }
254 
255   private static byte [] getTestQualifier() {
256     return getTestFamily();
257   }
258   
259   public static void main(String args[]) throws Exception {
260     TestZKBasedReopenRegion.beforeAllTests();
261     
262     TestZKBasedReopenRegion test = new TestZKBasedReopenRegion();
263     test.setup();
264     test.testOpenRegion();
265     
266     TestZKBasedReopenRegion.afterAllTests();
267   }
268 }