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 static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertNull;
24  
25  import java.io.IOException;
26  import java.util.Collections;
27  import java.util.List;
28  import java.util.NavigableSet;
29  import java.util.concurrent.CountDownLatch;
30  
31  import org.apache.hadoop.conf.Configuration;
32  import org.apache.hadoop.fs.FileSystem;
33  import org.apache.hadoop.fs.Path;
34  import org.apache.hadoop.hbase.Cell;
35  import org.apache.hadoop.hbase.Coprocessor;
36  import org.apache.hadoop.hbase.HBaseTestingUtility;
37  import org.apache.hadoop.hbase.HColumnDescriptor;
38  import org.apache.hadoop.hbase.HConstants;
39  import org.apache.hadoop.hbase.HRegionInfo;
40  import org.apache.hadoop.hbase.HTableDescriptor;
41  import org.apache.hadoop.hbase.TableName;
42  import org.apache.hadoop.hbase.client.Admin;
43  import org.apache.hadoop.hbase.client.Get;
44  import org.apache.hadoop.hbase.client.HTable;
45  import org.apache.hadoop.hbase.client.IsolationLevel;
46  import org.apache.hadoop.hbase.client.Put;
47  import org.apache.hadoop.hbase.client.Result;
48  import org.apache.hadoop.hbase.client.Scan;
49  import org.apache.hadoop.hbase.client.Table;
50  import org.apache.hadoop.hbase.filter.FilterBase;
51  import org.apache.hadoop.hbase.regionserver.HRegion;
52  import org.apache.hadoop.hbase.regionserver.HRegionServer;
53  import org.apache.hadoop.hbase.regionserver.HStore;
54  import org.apache.hadoop.hbase.regionserver.InternalScanner;
55  import org.apache.hadoop.hbase.regionserver.KeyValueScanner;
56  import org.apache.hadoop.hbase.regionserver.Region;
57  import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost;
58  import org.apache.hadoop.hbase.regionserver.RegionServerServices;
59  import org.apache.hadoop.hbase.regionserver.ScanType;
60  import org.apache.hadoop.hbase.regionserver.Store;
61  import org.apache.hadoop.hbase.regionserver.StoreScanner;
62  import org.apache.hadoop.hbase.regionserver.compactions.CompactionContext;
63  import org.apache.hadoop.hbase.regionserver.throttle.ThroughputController;
64  import org.apache.hadoop.hbase.security.User;
65  import org.apache.hadoop.hbase.testclassification.MediumTests;
66  import org.apache.hadoop.hbase.util.Bytes;
67  import org.apache.hadoop.hbase.wal.WAL;
68  import org.junit.Test;
69  import org.junit.experimental.categories.Category;
70  
71  @Category(MediumTests.class)
72  public class TestRegionObserverScannerOpenHook {
73    private static HBaseTestingUtility UTIL = new HBaseTestingUtility();
74    static final Path DIR = UTIL.getDataTestDir();
75  
76    public static class NoDataFilter extends FilterBase {
77  
78      @Override
79      public ReturnCode filterKeyValue(Cell ignored) throws IOException {
80        return ReturnCode.SKIP;
81      }
82  
83      @Override
84      public boolean filterAllRemaining() throws IOException {
85        return true;
86      }
87  
88      @Override
89      public boolean filterRow() throws IOException {
90        return true;
91      }
92    }
93  
94    /**
95     * Do the same logic as the {@link BaseRegionObserver}. Needed since {@link BaseRegionObserver} is
96     * an abstract class.
97     */
98    public static class EmptyRegionObsever extends BaseRegionObserver {
99    }
100 
101   /**
102    * Don't return any data from a scan by creating a custom {@link StoreScanner}.
103    */
104   public static class NoDataFromScan extends BaseRegionObserver {
105     @Override
106     public KeyValueScanner preStoreScannerOpen(ObserverContext<RegionCoprocessorEnvironment> c,
107         Store store, Scan scan, NavigableSet<byte[]> targetCols, KeyValueScanner s)
108         throws IOException {
109       scan.setFilter(new NoDataFilter());
110       return new StoreScanner(store, store.getScanInfo(), scan, targetCols,
111         ((HStore)store).getHRegion().getReadpoint(IsolationLevel.READ_COMMITTED));
112     }
113   }
114 
115   /**
116    * Don't allow any data in a flush by creating a custom {@link StoreScanner}.
117    */
118   public static class NoDataFromFlush extends BaseRegionObserver {
119     @Override
120     public InternalScanner preFlushScannerOpen(ObserverContext<RegionCoprocessorEnvironment> c,
121         Store store, KeyValueScanner memstoreScanner, InternalScanner s) throws IOException {
122       Scan scan = new Scan();
123       scan.setFilter(new NoDataFilter());
124       return new StoreScanner(store, store.getScanInfo(), scan,
125           Collections.singletonList(memstoreScanner), ScanType.COMPACT_RETAIN_DELETES,
126           store.getSmallestReadPoint(), HConstants.OLDEST_TIMESTAMP);
127     }
128   }
129 
130   /**
131    * Don't allow any data to be written out in the compaction by creating a custom
132    * {@link StoreScanner}.
133    */
134   public static class NoDataFromCompaction extends BaseRegionObserver {
135     @Override
136     public InternalScanner preCompactScannerOpen(ObserverContext<RegionCoprocessorEnvironment> c,
137         Store store, List<? extends KeyValueScanner> scanners, ScanType scanType,
138         long earliestPutTs, InternalScanner s) throws IOException {
139       Scan scan = new Scan();
140       scan.setFilter(new NoDataFilter());
141       return new StoreScanner(store, store.getScanInfo(), scan, scanners,
142           ScanType.COMPACT_RETAIN_DELETES, store.getSmallestReadPoint(),
143           HConstants.OLDEST_TIMESTAMP);
144     }
145   }
146 
147   Region initHRegion(byte[] tableName, String callingMethod, Configuration conf,
148       byte[]... families) throws IOException {
149     HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(tableName));
150     for (byte[] family : families) {
151       htd.addFamily(new HColumnDescriptor(family));
152     }
153     HRegionInfo info = new HRegionInfo(htd.getTableName(), null, null, false);
154     Path path = new Path(DIR + callingMethod);
155     HRegion r = HRegion.createHRegion(info, path, conf, htd);
156     // this following piece is a hack. currently a coprocessorHost
157     // is secretly loaded at OpenRegionHandler. we don't really
158     // start a region server here, so just manually create cphost
159     // and set it to region.
160     RegionCoprocessorHost host = new RegionCoprocessorHost(r, null, conf);
161     r.setCoprocessorHost(host);
162     return r;
163   }
164 
165   @Test
166   public void testRegionObserverScanTimeStacking() throws Exception {
167     byte[] ROW = Bytes.toBytes("testRow");
168     byte[] TABLE = Bytes.toBytes(getClass().getName());
169     byte[] A = Bytes.toBytes("A");
170     byte[][] FAMILIES = new byte[][] { A };
171 
172     // Use new HTU to not overlap with the DFS cluster started in #CompactionStacking
173     Configuration conf = new HBaseTestingUtility().getConfiguration();
174     Region region = initHRegion(TABLE, getClass().getName(), conf, FAMILIES);
175     RegionCoprocessorHost h = region.getCoprocessorHost();
176     h.load(NoDataFromScan.class, Coprocessor.PRIORITY_HIGHEST, conf);
177     h.load(EmptyRegionObsever.class, Coprocessor.PRIORITY_USER, conf);
178 
179     Put put = new Put(ROW);
180     put.add(A, A, A);
181     region.put(put);
182 
183     Get get = new Get(ROW);
184     Result r = region.get(get);
185     assertNull(
186       "Got an unexpected number of rows - no data should be returned with the NoDataFromScan coprocessor. Found: "
187           + r, r.listCells());
188   }
189 
190   @Test
191   public void testRegionObserverFlushTimeStacking() throws Exception {
192     byte[] ROW = Bytes.toBytes("testRow");
193     byte[] TABLE = Bytes.toBytes(getClass().getName());
194     byte[] A = Bytes.toBytes("A");
195     byte[][] FAMILIES = new byte[][] { A };
196 
197     // Use new HTU to not overlap with the DFS cluster started in #CompactionStacking
198     Configuration conf = new HBaseTestingUtility().getConfiguration();
199     Region region = initHRegion(TABLE, getClass().getName(), conf, FAMILIES);
200     RegionCoprocessorHost h = region.getCoprocessorHost();
201     h.load(NoDataFromFlush.class, Coprocessor.PRIORITY_HIGHEST, conf);
202     h.load(EmptyRegionObsever.class, Coprocessor.PRIORITY_USER, conf);
203 
204     // put a row and flush it to disk
205     Put put = new Put(ROW);
206     put.add(A, A, A);
207     region.put(put);
208     region.flush(true);
209     Get get = new Get(ROW);
210     Result r = region.get(get);
211     assertNull(
212       "Got an unexpected number of rows - no data should be returned with the NoDataFromScan coprocessor. Found: "
213           + r, r.listCells());
214   }
215 
216   /*
217    * Custom HRegion which uses CountDownLatch to signal the completion of compaction
218    */
219   public static class CompactionCompletionNotifyingRegion extends HRegion {
220     private static volatile CountDownLatch compactionStateChangeLatch = null;
221 
222     @SuppressWarnings("deprecation")
223     public CompactionCompletionNotifyingRegion(Path tableDir, WAL log,
224         FileSystem fs, Configuration confParam, HRegionInfo info,
225         HTableDescriptor htd, RegionServerServices rsServices) {
226       super(tableDir, log, fs, confParam, info, htd, rsServices);
227     }
228 
229     public CountDownLatch getCompactionStateChangeLatch() {
230       if (compactionStateChangeLatch == null) compactionStateChangeLatch = new CountDownLatch(1);
231       return compactionStateChangeLatch;
232     }
233 
234     @Override
235     public boolean compact(CompactionContext compaction, Store store,
236         ThroughputController throughputController) throws IOException {
237       boolean ret = super.compact(compaction, store, throughputController);
238       if (ret) compactionStateChangeLatch.countDown();
239       return ret;
240     }
241 
242     @Override
243     public boolean compact(CompactionContext compaction, Store store,
244         ThroughputController throughputController, User user) throws IOException {
245       boolean ret = super.compact(compaction, store, throughputController, user);
246       if (ret) compactionStateChangeLatch.countDown();
247       return ret;
248     }
249   }
250 
251   /**
252    * Unfortunately, the easiest way to test this is to spin up a mini-cluster since we want to do
253    * the usual compaction mechanism on the region, rather than going through the backdoor to the
254    * region
255    */
256   @Test
257   public void testRegionObserverCompactionTimeStacking() throws Exception {
258     // setup a mini cluster so we can do a real compaction on a region
259     Configuration conf = UTIL.getConfiguration();
260     conf.setClass(HConstants.REGION_IMPL, CompactionCompletionNotifyingRegion.class, HRegion.class);
261     conf.setInt("hbase.hstore.compaction.min", 2);
262     UTIL.startMiniCluster();
263     String tableName = "testRegionObserverCompactionTimeStacking";
264     byte[] ROW = Bytes.toBytes("testRow");
265     byte[] A = Bytes.toBytes("A");
266     HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(tableName));
267     desc.addFamily(new HColumnDescriptor(A));
268     desc.addCoprocessor(EmptyRegionObsever.class.getName(), null, Coprocessor.PRIORITY_USER, null);
269     desc.addCoprocessor(NoDataFromCompaction.class.getName(), null, Coprocessor.PRIORITY_HIGHEST,
270       null);
271 
272     Admin admin = UTIL.getHBaseAdmin();
273     admin.createTable(desc);
274 
275     Table table = new HTable(conf, desc.getTableName());
276 
277     // put a row and flush it to disk
278     Put put = new Put(ROW);
279     put.add(A, A, A);
280     table.put(put);
281 
282     HRegionServer rs = UTIL.getRSForFirstRegionInTable(desc.getTableName());
283     List<Region> regions = rs.getOnlineRegions(desc.getTableName());
284     assertEquals("More than 1 region serving test table with 1 row", 1, regions.size());
285     Region region = regions.get(0);
286     admin.flushRegion(region.getRegionInfo().getRegionName());
287     CountDownLatch latch = ((CompactionCompletionNotifyingRegion)region)
288         .getCompactionStateChangeLatch();
289 
290     // put another row and flush that too
291     put = new Put(Bytes.toBytes("anotherrow"));
292     put.add(A, A, A);
293     table.put(put);
294     admin.flushRegion(region.getRegionInfo().getRegionName());
295 
296     // run a compaction, which normally would should get rid of the data
297     // wait for the compaction checker to complete
298     latch.await();
299     // check both rows to ensure that they aren't there
300     Get get = new Get(ROW);
301     Result r = table.get(get);
302     assertNull(
303       "Got an unexpected number of rows - no data should be returned with the NoDataFromScan coprocessor. Found: "
304           + r, r.listCells());
305 
306     get = new Get(Bytes.toBytes("anotherrow"));
307     r = table.get(get);
308     assertNull(
309       "Got an unexpected number of rows - no data should be returned with the NoDataFromScan coprocessor Found: "
310           + r, r.listCells());
311 
312     table.close();
313     UTIL.shutdownMiniCluster();
314   }
315 }