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