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