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.rest;
21  
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertNotNull;
24  import static org.junit.Assert.assertNull;
25  import static org.junit.Assert.assertTrue;
26  
27  import java.io.ByteArrayInputStream;
28  import java.io.IOException;
29  import java.io.StringWriter;
30  import java.util.ArrayList;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Random;
34  
35  import javax.xml.bind.JAXBContext;
36  import javax.xml.bind.JAXBException;
37  import javax.xml.bind.Marshaller;
38  import javax.xml.bind.Unmarshaller;
39  
40  import org.apache.commons.httpclient.Header;
41  import org.apache.hadoop.conf.Configuration;
42  import org.apache.hadoop.hbase.HBaseTestingUtility;
43  import org.apache.hadoop.hbase.HColumnDescriptor;
44  import org.apache.hadoop.hbase.HTableDescriptor;
45  import org.apache.hadoop.hbase.KeyValue;
46  import org.apache.hadoop.hbase.TableName;
47  import org.apache.hadoop.hbase.client.Admin;
48  import org.apache.hadoop.hbase.client.Connection;
49  import org.apache.hadoop.hbase.client.ConnectionFactory;
50  import org.apache.hadoop.hbase.client.Durability;
51  import org.apache.hadoop.hbase.client.Put;
52  import org.apache.hadoop.hbase.client.Table;
53  import org.apache.hadoop.hbase.rest.client.Client;
54  import org.apache.hadoop.hbase.rest.client.Cluster;
55  import org.apache.hadoop.hbase.rest.client.Response;
56  import org.apache.hadoop.hbase.rest.model.CellModel;
57  import org.apache.hadoop.hbase.rest.model.CellSetModel;
58  import org.apache.hadoop.hbase.rest.model.RowModel;
59  import org.apache.hadoop.hbase.rest.model.ScannerModel;
60  import org.apache.hadoop.hbase.testclassification.MediumTests;
61  import org.apache.hadoop.hbase.util.Bytes;
62  import org.junit.AfterClass;
63  import org.junit.BeforeClass;
64  import org.junit.Test;
65  import org.junit.experimental.categories.Category;
66  
67  @Category(MediumTests.class)
68  public class TestScannerResource {
69  
70    private static final TableName TABLE = TableName.valueOf("TestScannerResource");
71    private static final TableName TABLE_TO_BE_DISABLED = TableName.valueOf("ScannerResourceDisable");
72    private static final String NONEXISTENT_TABLE = "ThisTableDoesNotExist";
73    private static final String CFA = "a";
74    private static final String CFB = "b";
75    private static final String COLUMN_1 = CFA + ":1";
76    private static final String COLUMN_2 = CFB + ":2";
77  
78    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
79    private static final HBaseRESTTestingUtility REST_TEST_UTIL = 
80      new HBaseRESTTestingUtility();
81    private static Client client;
82    private static JAXBContext context;
83    private static Marshaller marshaller;
84    private static Unmarshaller unmarshaller;
85    private static int expectedRows1;
86    private static int expectedRows2;
87    private static Configuration conf;
88  
89    static int insertData(Configuration conf, TableName tableName, String column, double prob)
90        throws IOException {
91      Random rng = new Random();
92      byte[] k = new byte[3];
93      byte [][] famAndQf = KeyValue.parseColumn(Bytes.toBytes(column));
94      List<Put> puts = new ArrayList<>();
95      for (byte b1 = 'a'; b1 < 'z'; b1++) {
96        for (byte b2 = 'a'; b2 < 'z'; b2++) {
97          for (byte b3 = 'a'; b3 < 'z'; b3++) {
98            if (rng.nextDouble() < prob) {
99              k[0] = b1;
100             k[1] = b2;
101             k[2] = b3;
102             Put put = new Put(k);
103             put.setDurability(Durability.SKIP_WAL);
104             put.add(famAndQf[0], famAndQf[1], k);
105             puts.add(put);
106           }
107         }
108       }
109     }
110     try (Connection conn = ConnectionFactory.createConnection(conf);
111         Table table = conn.getTable(tableName)) {
112       table.put(puts);
113     }
114     return puts.size();
115   }
116 
117   static int countCellSet(CellSetModel model) {
118     int count = 0;
119     Iterator<RowModel> rows = model.getRows().iterator();
120     while (rows.hasNext()) {
121       RowModel row = rows.next();
122       Iterator<CellModel> cells = row.getCells().iterator();
123       while (cells.hasNext()) {
124         cells.next();
125         count++;
126       }
127     }
128     return count;
129   }
130 
131   private static int fullTableScan(ScannerModel model) throws IOException {
132     model.setBatch(100);
133     Response response = client.put("/" + TABLE + "/scanner",
134       Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
135     assertEquals(201, response.getCode());
136     String scannerURI = response.getLocation();
137     assertNotNull(scannerURI);
138     int count = 0;
139     while (true) {
140       response = client.get(scannerURI, Constants.MIMETYPE_PROTOBUF);
141       assertTrue(response.getCode() == 200 || response.getCode() == 204);
142       if (response.getCode() == 200) {
143         assertEquals(Constants.MIMETYPE_PROTOBUF, response.getHeader("content-type"));
144         CellSetModel cellSet = new CellSetModel();
145         cellSet.getObjectFromMessage(response.getBody());
146         Iterator<RowModel> rows = cellSet.getRows().iterator();
147         while (rows.hasNext()) {
148           RowModel row = rows.next();
149           Iterator<CellModel> cells = row.getCells().iterator();
150           while (cells.hasNext()) {
151             cells.next();
152             count++;
153           }
154         }
155       } else {
156         break;
157       }
158     }
159     // delete the scanner
160     response = client.delete(scannerURI);
161     assertEquals(200, response.getCode());
162     return count;
163   }
164 
165   @BeforeClass
166   public static void setUpBeforeClass() throws Exception {
167     conf = TEST_UTIL.getConfiguration();
168     TEST_UTIL.startMiniCluster();
169     REST_TEST_UTIL.startServletContainer(conf);
170     client = new Client(new Cluster().add("localhost",
171       REST_TEST_UTIL.getServletPort()));
172     context = JAXBContext.newInstance(
173       CellModel.class,
174       CellSetModel.class,
175       RowModel.class,
176       ScannerModel.class);
177     marshaller = context.createMarshaller();
178     unmarshaller = context.createUnmarshaller();
179     Admin admin = TEST_UTIL.getHBaseAdmin();
180     if (admin.tableExists(TABLE)) {
181       return;
182     }
183     HTableDescriptor htd = new HTableDescriptor(TABLE);
184     htd.addFamily(new HColumnDescriptor(CFA));
185     htd.addFamily(new HColumnDescriptor(CFB));
186     admin.createTable(htd);
187     expectedRows1 = insertData(TEST_UTIL.getConfiguration(), TABLE, COLUMN_1, 1.0);
188     expectedRows2 = insertData(TEST_UTIL.getConfiguration(), TABLE, COLUMN_2, 0.5);
189 
190     htd = new HTableDescriptor(TABLE_TO_BE_DISABLED);
191     htd.addFamily(new HColumnDescriptor(CFA));
192     htd.addFamily(new HColumnDescriptor(CFB));
193     admin.createTable(htd);
194   }
195 
196   @AfterClass
197   public static void tearDownAfterClass() throws Exception {
198     REST_TEST_UTIL.shutdownServletContainer();
199     TEST_UTIL.shutdownMiniCluster();
200   }
201 
202   @Test
203   public void testSimpleScannerXML() throws IOException, JAXBException {
204     final int BATCH_SIZE = 5;
205     // new scanner
206     ScannerModel model = new ScannerModel();
207     model.setBatch(BATCH_SIZE);
208     model.addColumn(Bytes.toBytes(COLUMN_1));
209     StringWriter writer = new StringWriter();
210     marshaller.marshal(model, writer);
211     byte[] body = Bytes.toBytes(writer.toString());
212 
213     // test put operation is forbidden in read-only mode
214     conf.set("hbase.rest.readonly", "true");
215     Response response = client.put("/" + TABLE + "/scanner",
216       Constants.MIMETYPE_XML, body);
217     assertEquals(403, response.getCode());
218     String scannerURI = response.getLocation();
219     assertNull(scannerURI);
220 
221     // recall previous put operation with read-only off
222     conf.set("hbase.rest.readonly", "false");
223     response = client.put("/" + TABLE + "/scanner", Constants.MIMETYPE_XML,
224       body);
225     assertEquals(201, response.getCode());
226     scannerURI = response.getLocation();
227     assertNotNull(scannerURI);
228 
229     // get a cell set
230     response = client.get(scannerURI, Constants.MIMETYPE_XML);
231     assertEquals(200, response.getCode());
232     assertEquals(Constants.MIMETYPE_XML, response.getHeader("content-type"));
233     CellSetModel cellSet = (CellSetModel)
234       unmarshaller.unmarshal(new ByteArrayInputStream(response.getBody()));
235     // confirm batch size conformance
236     assertEquals(BATCH_SIZE, countCellSet(cellSet));
237 
238     // test delete scanner operation is forbidden in read-only mode
239     conf.set("hbase.rest.readonly", "true");
240     response = client.delete(scannerURI);
241     assertEquals(403, response.getCode());
242 
243     // recall previous delete scanner operation with read-only off
244     conf.set("hbase.rest.readonly", "false");
245     response = client.delete(scannerURI);
246     assertEquals(200, response.getCode());
247   }
248 
249   @Test
250   public void testSimpleScannerPB() throws IOException {
251     final int BATCH_SIZE = 10;
252     // new scanner
253     ScannerModel model = new ScannerModel();
254     model.setBatch(BATCH_SIZE);
255     model.addColumn(Bytes.toBytes(COLUMN_1));
256 
257     // test put operation is forbidden in read-only mode
258     conf.set("hbase.rest.readonly", "true");
259     Response response = client.put("/" + TABLE + "/scanner",
260       Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
261     assertEquals(403, response.getCode());
262     String scannerURI = response.getLocation();
263     assertNull(scannerURI);
264 
265     // recall previous put operation with read-only off
266     conf.set("hbase.rest.readonly", "false");
267     response = client.put("/" + TABLE + "/scanner",
268       Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
269     assertEquals(201, response.getCode());
270     scannerURI = response.getLocation();
271     assertNotNull(scannerURI);
272 
273     // get a cell set
274     response = client.get(scannerURI, Constants.MIMETYPE_PROTOBUF);
275     assertEquals(200, response.getCode());
276     assertEquals(Constants.MIMETYPE_PROTOBUF, response.getHeader("content-type"));
277     CellSetModel cellSet = new CellSetModel();
278     cellSet.getObjectFromMessage(response.getBody());
279     // confirm batch size conformance
280     assertEquals(BATCH_SIZE, countCellSet(cellSet));
281 
282     // test delete scanner operation is forbidden in read-only mode
283     conf.set("hbase.rest.readonly", "true");
284     response = client.delete(scannerURI);
285     assertEquals(403, response.getCode());
286 
287     // recall previous delete scanner operation with read-only off
288     conf.set("hbase.rest.readonly", "false");
289     response = client.delete(scannerURI);
290     assertEquals(200, response.getCode());
291   }
292 
293   @Test
294   public void testSimpleScannerBinary() throws IOException {
295     // new scanner
296     ScannerModel model = new ScannerModel();
297     model.setBatch(1);
298     model.addColumn(Bytes.toBytes(COLUMN_1));
299 
300     // test put operation is forbidden in read-only mode
301     conf.set("hbase.rest.readonly", "true");
302     Response response = client.put("/" + TABLE + "/scanner",
303       Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
304     assertEquals(403, response.getCode());
305     String scannerURI = response.getLocation();
306     assertNull(scannerURI);
307 
308     // recall previous put operation with read-only off
309     conf.set("hbase.rest.readonly", "false");
310     response = client.put("/" + TABLE + "/scanner",
311       Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
312     assertEquals(201, response.getCode());
313     scannerURI = response.getLocation();
314     assertNotNull(scannerURI);
315 
316     // get a cell
317     response = client.get(scannerURI, Constants.MIMETYPE_BINARY);
318     assertEquals(200, response.getCode());
319     assertEquals(Constants.MIMETYPE_BINARY, response.getHeader("content-type"));
320     // verify that data was returned
321     assertTrue(response.getBody().length > 0);
322     // verify that the expected X-headers are present
323     boolean foundRowHeader = false, foundColumnHeader = false,
324       foundTimestampHeader = false;
325     for (Header header: response.getHeaders()) {
326       if (header.getName().equals("X-Row")) {
327         foundRowHeader = true;
328       } else if (header.getName().equals("X-Column")) {
329         foundColumnHeader = true;
330       } else if (header.getName().equals("X-Timestamp")) {
331         foundTimestampHeader = true;
332       }
333     }
334     assertTrue(foundRowHeader);
335     assertTrue(foundColumnHeader);
336     assertTrue(foundTimestampHeader);
337 
338     // test delete scanner operation is forbidden in read-only mode
339     conf.set("hbase.rest.readonly", "true");
340     response = client.delete(scannerURI);
341     assertEquals(403, response.getCode());
342 
343     // recall previous delete scanner operation with read-only off
344     conf.set("hbase.rest.readonly", "false");
345     response = client.delete(scannerURI);
346     assertEquals(200, response.getCode());
347   }
348 
349   @Test
350   public void testFullTableScan() throws IOException {
351     ScannerModel model = new ScannerModel();
352     model.addColumn(Bytes.toBytes(COLUMN_1));
353     assertEquals(expectedRows1, fullTableScan(model));
354 
355     model = new ScannerModel();
356     model.addColumn(Bytes.toBytes(COLUMN_2));
357     assertEquals(expectedRows2, fullTableScan(model));
358   }
359 
360   @Test
361   public void testTableDoesNotExist() throws IOException, JAXBException {
362     ScannerModel model = new ScannerModel();
363     StringWriter writer = new StringWriter();
364     marshaller.marshal(model, writer);
365     byte[] body = Bytes.toBytes(writer.toString());
366     Response response = client.put("/" + NONEXISTENT_TABLE +
367       "/scanner", Constants.MIMETYPE_XML, body);
368     String scannerURI = response.getLocation();
369     assertNotNull(scannerURI);
370     response = client.get(scannerURI, Constants.MIMETYPE_XML);
371     assertEquals(404, response.getCode());
372   }
373 
374   // performs table scan during which the underlying table is disabled
375   // assert that we get 410 (Gone)
376   @Test
377   public void testTableScanWithTableDisable() throws IOException {
378     ScannerModel model = new ScannerModel();
379     model.addColumn(Bytes.toBytes(COLUMN_1));
380     model.setCaching(1);
381     Response response = client.put("/" + TABLE_TO_BE_DISABLED + "/scanner",
382       Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
383     assertEquals(201, response.getCode());
384     String scannerURI = response.getLocation();
385     assertNotNull(scannerURI);
386     TEST_UTIL.getHBaseAdmin().disableTable(TABLE_TO_BE_DISABLED);
387       response = client.get(scannerURI, Constants.MIMETYPE_PROTOBUF);
388     assertTrue("got " + response.getCode(), response.getCode() == 410);
389   }
390 
391 }
392