001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.rest.client;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertNotNull;
023import static org.junit.jupiter.api.Assertions.assertNull;
024import static org.junit.jupiter.api.Assertions.assertTrue;
025
026import java.io.IOException;
027import java.util.ArrayList;
028import java.util.Collections;
029import java.util.Iterator;
030import java.util.List;
031import org.apache.hadoop.hbase.Cell;
032import org.apache.hadoop.hbase.CellUtil;
033import org.apache.hadoop.hbase.HBaseTestingUtil;
034import org.apache.hadoop.hbase.TableName;
035import org.apache.hadoop.hbase.client.Admin;
036import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
037import org.apache.hadoop.hbase.client.Delete;
038import org.apache.hadoop.hbase.client.Get;
039import org.apache.hadoop.hbase.client.Put;
040import org.apache.hadoop.hbase.client.Result;
041import org.apache.hadoop.hbase.client.ResultScanner;
042import org.apache.hadoop.hbase.client.Scan;
043import org.apache.hadoop.hbase.client.Table;
044import org.apache.hadoop.hbase.client.TableDescriptor;
045import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
046import org.apache.hadoop.hbase.rest.HBaseRESTTestingUtility;
047import org.apache.hadoop.hbase.rest.RESTServlet;
048import org.apache.hadoop.hbase.testclassification.LargeTests;
049import org.apache.hadoop.hbase.testclassification.RestTests;
050import org.apache.hadoop.hbase.util.Bytes;
051import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
052import org.apache.http.Header;
053import org.apache.http.message.BasicHeader;
054import org.junit.jupiter.api.AfterAll;
055import org.junit.jupiter.api.AfterEach;
056import org.junit.jupiter.api.BeforeAll;
057import org.junit.jupiter.api.BeforeEach;
058import org.junit.jupiter.api.Tag;
059import org.junit.jupiter.api.Test;
060
061@Tag(RestTests.TAG)
062@Tag(LargeTests.TAG)
063public class TestRemoteTable {
064
065  // Verify that invalid URL characters and arbitrary bytes are escaped when
066  // constructing REST URLs per HBASE-7621. RemoteHTable should support row keys
067  // and qualifiers containing any byte for all table operations.
068  private static final String INVALID_URL_CHARS_1 =
069    "|\"\\^{}\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000B\u000C";
070
071  // HColumnDescriptor prevents certain characters in column names. The following
072  // are examples of characters are allowed in column names but are not valid in
073  // URLs.
074  private static final String INVALID_URL_CHARS_2 = "|^{}\u0242";
075
076  // Besides alphanumeric these characters can also be present in table names.
077  private static final String VALID_TABLE_NAME_CHARS = "_-.";
078
079  private static final TableName TABLE =
080    TableName.valueOf("TestRemoteTable" + VALID_TABLE_NAME_CHARS);
081
082  private static final byte[] ROW_1 = Bytes.toBytes("testrow1" + INVALID_URL_CHARS_1);
083  private static final byte[] ROW_2 = Bytes.toBytes("testrow2" + INVALID_URL_CHARS_1);
084  private static final byte[] ROW_3 = Bytes.toBytes("testrow3" + INVALID_URL_CHARS_1);
085  private static final byte[] ROW_4 = Bytes.toBytes("testrow4" + INVALID_URL_CHARS_1);
086
087  private static final byte[] COLUMN_1 = Bytes.toBytes("a" + INVALID_URL_CHARS_2);
088  private static final byte[] COLUMN_2 = Bytes.toBytes("b" + INVALID_URL_CHARS_2);
089  private static final byte[] COLUMN_3 = Bytes.toBytes("c" + INVALID_URL_CHARS_2);
090
091  private static final byte[] QUALIFIER_1 = Bytes.toBytes("1" + INVALID_URL_CHARS_1);
092  private static final byte[] QUALIFIER_2 = Bytes.toBytes("2" + INVALID_URL_CHARS_1);
093  private static final byte[] VALUE_1 = Bytes.toBytes("testvalue1");
094  private static final byte[] VALUE_2 = Bytes.toBytes("testvalue2");
095
096  private static final long ONE_HOUR = 60 * 60 * 1000;
097  private static final long TS_2 = EnvironmentEdgeManager.currentTime();
098  private static final long TS_1 = TS_2 - ONE_HOUR;
099
100  private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
101  private static final HBaseRESTTestingUtility REST_TEST_UTIL = new HBaseRESTTestingUtility();
102  private RemoteHTable remoteTable;
103
104  @BeforeAll
105  public static void setUpBeforeClass() throws Exception {
106    TEST_UTIL.startMiniCluster();
107    REST_TEST_UTIL.startServletContainer(TEST_UTIL.getConfiguration());
108  }
109
110  @BeforeEach
111  public void before() throws Exception {
112    Admin admin = TEST_UTIL.getAdmin();
113    if (admin.tableExists(TABLE)) {
114      if (admin.isTableEnabled(TABLE)) {
115        admin.disableTable(TABLE);
116      }
117
118      admin.deleteTable(TABLE);
119    }
120
121    TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(TABLE)
122      .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(COLUMN_1).setMaxVersions(3).build())
123      .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(COLUMN_2).setMaxVersions(3).build())
124      .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(COLUMN_3).setMaxVersions(3).build())
125      .build();
126    admin.createTable(tableDescriptor);
127    try (Table table = TEST_UTIL.getConnection().getTable(TABLE)) {
128      Put put = new Put(ROW_1);
129      put.addColumn(COLUMN_1, QUALIFIER_1, TS_2, VALUE_1);
130      table.put(put);
131      put = new Put(ROW_2);
132      put.addColumn(COLUMN_1, QUALIFIER_1, TS_1, VALUE_1);
133      put.addColumn(COLUMN_1, QUALIFIER_1, TS_2, VALUE_2);
134      put.addColumn(COLUMN_2, QUALIFIER_2, TS_2, VALUE_2);
135      table.put(put);
136    }
137    remoteTable =
138      new RemoteHTable(new Client(new Cluster().add("localhost", REST_TEST_UTIL.getServletPort())),
139        TEST_UTIL.getConfiguration(), TABLE.toBytes());
140  }
141
142  @AfterEach
143  public void after() throws Exception {
144    remoteTable.close();
145  }
146
147  @AfterAll
148  public static void tearDownAfterClass() throws Exception {
149    REST_TEST_UTIL.shutdownServletContainer();
150    TEST_UTIL.shutdownMiniCluster();
151  }
152
153  @Test
154  public void testGetTableDescriptor() throws IOException {
155    try (Table table = TEST_UTIL.getConnection().getTable(TABLE)) {
156      TableDescriptor local = table.getDescriptor();
157      assertEquals(remoteTable.getDescriptor(), local);
158    }
159  }
160
161  @Test
162  public void testGet() throws IOException {
163    // Requires UriCompliance.Violation.SUSPICIOUS_PATH_CHARACTERS
164    // Otherwise fails with "400: Suspicious Path Character"
165    // In this test, the request path resolves to
166    // "/TestRemoteTable_-./testrow1%7C%22%5C%5E%7B%7D%01%02%03%04%05%06%07%08%09%0B%0C/"
167    // and is considered suspicious by the Jetty 12.
168    // Basically ROW_1 contains invalid URL characters here.
169    Get get = new Get(ROW_1);
170    Result result = remoteTable.get(get);
171    byte[] value1 = result.getValue(COLUMN_1, QUALIFIER_1);
172    byte[] value2 = result.getValue(COLUMN_2, QUALIFIER_2);
173    assertNotNull(value1);
174    assertTrue(Bytes.equals(VALUE_1, value1));
175    assertNull(value2);
176
177    get = new Get(ROW_1);
178    get.addFamily(COLUMN_3);
179    result = remoteTable.get(get);
180    value1 = result.getValue(COLUMN_1, QUALIFIER_1);
181    value2 = result.getValue(COLUMN_2, QUALIFIER_2);
182    assertNull(value1);
183    assertNull(value2);
184
185    get = new Get(ROW_1);
186    get.addColumn(COLUMN_1, QUALIFIER_1);
187    get.addColumn(COLUMN_2, QUALIFIER_2);
188    result = remoteTable.get(get);
189    value1 = result.getValue(COLUMN_1, QUALIFIER_1);
190    value2 = result.getValue(COLUMN_2, QUALIFIER_2);
191    assertNotNull(value1);
192    assertTrue(Bytes.equals(VALUE_1, value1));
193    assertNull(value2);
194
195    get = new Get(ROW_2);
196    result = remoteTable.get(get);
197    value1 = result.getValue(COLUMN_1, QUALIFIER_1);
198    value2 = result.getValue(COLUMN_2, QUALIFIER_2);
199    assertNotNull(value1);
200    assertTrue(Bytes.equals(VALUE_2, value1)); // @TS_2
201    assertNotNull(value2);
202    assertTrue(Bytes.equals(VALUE_2, value2));
203
204    get = new Get(ROW_2);
205    get.addFamily(COLUMN_1);
206    result = remoteTable.get(get);
207    value1 = result.getValue(COLUMN_1, QUALIFIER_1);
208    value2 = result.getValue(COLUMN_2, QUALIFIER_2);
209    assertNotNull(value1);
210    assertTrue(Bytes.equals(VALUE_2, value1)); // @TS_2
211    assertNull(value2);
212
213    get = new Get(ROW_2);
214    get.addColumn(COLUMN_1, QUALIFIER_1);
215    get.addColumn(COLUMN_2, QUALIFIER_2);
216    result = remoteTable.get(get);
217    value1 = result.getValue(COLUMN_1, QUALIFIER_1);
218    value2 = result.getValue(COLUMN_2, QUALIFIER_2);
219    assertNotNull(value1);
220    assertTrue(Bytes.equals(VALUE_2, value1)); // @TS_2
221    assertNotNull(value2);
222    assertTrue(Bytes.equals(VALUE_2, value2));
223
224    // test timestamp
225    get = new Get(ROW_2);
226    get.addFamily(COLUMN_1);
227    get.addFamily(COLUMN_2);
228    get.setTimestamp(TS_1);
229    result = remoteTable.get(get);
230    value1 = result.getValue(COLUMN_1, QUALIFIER_1);
231    value2 = result.getValue(COLUMN_2, QUALIFIER_2);
232    assertNotNull(value1);
233    assertTrue(Bytes.equals(VALUE_1, value1)); // @TS_1
234    assertNull(value2);
235
236    // test timerange
237    get = new Get(ROW_2);
238    get.addFamily(COLUMN_1);
239    get.addFamily(COLUMN_2);
240    get.setTimeRange(0, TS_1 + 1);
241    result = remoteTable.get(get);
242    value1 = result.getValue(COLUMN_1, QUALIFIER_1);
243    value2 = result.getValue(COLUMN_2, QUALIFIER_2);
244    assertNotNull(value1);
245    assertTrue(Bytes.equals(VALUE_1, value1)); // @TS_1
246    assertNull(value2);
247
248    // test maxVersions
249    get = new Get(ROW_2);
250    get.addFamily(COLUMN_1);
251    get.readVersions(2);
252    result = remoteTable.get(get);
253    int count = 0;
254    for (Cell kv : result.listCells()) {
255      if (CellUtil.matchingFamily(kv, COLUMN_1) && TS_1 == kv.getTimestamp()) {
256        assertTrue(CellUtil.matchingValue(kv, VALUE_1)); // @TS_1
257        count++;
258      }
259      if (CellUtil.matchingFamily(kv, COLUMN_1) && TS_2 == kv.getTimestamp()) {
260        assertTrue(CellUtil.matchingValue(kv, VALUE_2)); // @TS_2
261        count++;
262      }
263    }
264    assertEquals(2, count);
265  }
266
267  @Test
268  public void testMultiGet() throws Exception {
269    // In case of multi gets, the request path resolves to
270    // "/TestRemoteTable_-./multiget/?row=testrow1%7C%22%5C%5E&row=testrow2%7C%22%5C%5E%&v=3"
271    // and hence is not considered suspicious by the Jetty 12.
272    ArrayList<Get> gets = new ArrayList<>(2);
273    gets.add(new Get(ROW_1));
274    gets.add(new Get(ROW_2));
275    Result[] results = remoteTable.get(gets);
276    assertNotNull(results);
277    assertEquals(2, results.length);
278    assertEquals(1, results[0].size());
279    assertEquals(2, results[1].size());
280
281    // Test Versions
282    gets = new ArrayList<>(2);
283    Get g = new Get(ROW_1);
284    g.readVersions(3);
285    gets.add(g);
286    gets.add(new Get(ROW_2));
287    results = remoteTable.get(gets);
288    assertNotNull(results);
289    assertEquals(2, results.length);
290    assertEquals(1, results[0].size());
291    assertEquals(3, results[1].size());
292
293    // 404
294    gets = new ArrayList<>(1);
295    gets.add(new Get(Bytes.toBytes("RESALLYREALLYNOTTHERE")));
296    results = remoteTable.get(gets);
297    assertNotNull(results);
298    assertEquals(0, results.length);
299
300    gets = new ArrayList<>(3);
301    gets.add(new Get(Bytes.toBytes("RESALLYREALLYNOTTHERE")));
302    gets.add(new Get(ROW_1));
303    gets.add(new Get(ROW_2));
304    results = remoteTable.get(gets);
305    assertNotNull(results);
306    assertEquals(2, results.length);
307  }
308
309  @Test
310  public void testPut() throws IOException {
311    // Requires UriCompliance.Violation.SUSPICIOUS_PATH_CHARACTERS
312    // Otherwise fails with "400: Suspicious Path Character"
313    Put put = new Put(ROW_3);
314    put.addColumn(COLUMN_1, QUALIFIER_1, VALUE_1);
315    remoteTable.put(put);
316
317    Get get = new Get(ROW_3);
318    get.addFamily(COLUMN_1);
319    Result result = remoteTable.get(get);
320    byte[] value = result.getValue(COLUMN_1, QUALIFIER_1);
321    assertNotNull(value);
322    assertTrue(Bytes.equals(VALUE_1, value));
323
324    // multiput
325    List<Put> puts = new ArrayList<>(3);
326    put = new Put(ROW_3);
327    put.addColumn(COLUMN_2, QUALIFIER_2, VALUE_2);
328    puts.add(put);
329    put = new Put(ROW_4);
330    put.addColumn(COLUMN_1, QUALIFIER_1, VALUE_1);
331    puts.add(put);
332    put = new Put(ROW_4);
333    put.addColumn(COLUMN_2, QUALIFIER_2, VALUE_2);
334    puts.add(put);
335    remoteTable.put(puts);
336
337    get = new Get(ROW_3);
338    get.addFamily(COLUMN_2);
339    result = remoteTable.get(get);
340    value = result.getValue(COLUMN_2, QUALIFIER_2);
341    assertNotNull(value);
342    assertTrue(Bytes.equals(VALUE_2, value));
343    get = new Get(ROW_4);
344    result = remoteTable.get(get);
345    value = result.getValue(COLUMN_1, QUALIFIER_1);
346    assertNotNull(value);
347    assertTrue(Bytes.equals(VALUE_1, value));
348    value = result.getValue(COLUMN_2, QUALIFIER_2);
349    assertNotNull(value);
350    assertTrue(Bytes.equals(VALUE_2, value));
351
352    assertTrue(Bytes.equals(Bytes.toBytes("TestRemoteTable" + VALID_TABLE_NAME_CHARS),
353      remoteTable.getTableName()));
354  }
355
356  @Test
357  public void testDelete() throws IOException {
358    // Requires UriCompliance.Violation.SUSPICIOUS_PATH_CHARACTERS for put,
359    // otherwise fails with "400: Suspicious Path Character"
360    // This example is considered suspicious by the Jetty 12 due to reasons same as shown in
361    // testGet()
362
363    // Also, requires UriCompliance.Violation.AMBIGUOUS_EMPTY_SEGMENT
364    // Otherwise fails with "400: Ambiguous URI empty segment"
365    Put put = new Put(ROW_3);
366    put.addColumn(COLUMN_1, QUALIFIER_1, VALUE_1);
367    put.addColumn(COLUMN_2, QUALIFIER_2, VALUE_2);
368    put.addColumn(COLUMN_3, QUALIFIER_1, VALUE_1);
369    put.addColumn(COLUMN_3, QUALIFIER_2, VALUE_2);
370    remoteTable.put(put);
371
372    Get get = new Get(ROW_3);
373    get.addFamily(COLUMN_1);
374    get.addFamily(COLUMN_2);
375    get.addFamily(COLUMN_3);
376    Result result = remoteTable.get(get);
377    byte[] value1 = result.getValue(COLUMN_1, QUALIFIER_1);
378    byte[] value2 = result.getValue(COLUMN_2, QUALIFIER_2);
379    byte[] value3 = result.getValue(COLUMN_3, QUALIFIER_1);
380    byte[] value4 = result.getValue(COLUMN_3, QUALIFIER_2);
381    assertNotNull(value1);
382    assertTrue(Bytes.equals(VALUE_1, value1));
383    assertNotNull(value2);
384    assertTrue(Bytes.equals(VALUE_2, value2));
385    assertNotNull(value3);
386    assertTrue(Bytes.equals(VALUE_1, value3));
387    assertNotNull(value4);
388    assertTrue(Bytes.equals(VALUE_2, value4));
389
390    Delete delete = new Delete(ROW_3);
391    delete.addColumn(COLUMN_2, QUALIFIER_2);
392    remoteTable.delete(delete);
393
394    get = new Get(ROW_3);
395    get.addFamily(COLUMN_1);
396    get.addFamily(COLUMN_2);
397    result = remoteTable.get(get);
398    value1 = result.getValue(COLUMN_1, QUALIFIER_1);
399    value2 = result.getValue(COLUMN_2, QUALIFIER_2);
400    assertNotNull(value1);
401    assertTrue(Bytes.equals(VALUE_1, value1));
402    assertNull(value2);
403
404    // This leads to path which resolves to
405    // "/TestRemoteTable_-./testrow3%7C%22%5C%5E%7B%7D%01%02%03%04%05%06%07%08%09%0B%0C//1"
406    // causing "400: Ambiguous URI empty segment" error with Jetty 12.
407    delete = new Delete(ROW_3);
408    delete.setTimestamp(1L);
409    remoteTable.delete(delete);
410
411    get = new Get(ROW_3);
412    get.addFamily(COLUMN_1);
413    get.addFamily(COLUMN_2);
414    result = remoteTable.get(get);
415    value1 = result.getValue(COLUMN_1, QUALIFIER_1);
416    value2 = result.getValue(COLUMN_2, QUALIFIER_2);
417    assertNotNull(value1);
418    assertTrue(Bytes.equals(VALUE_1, value1));
419    assertNull(value2);
420
421    // Delete column family from row
422    delete = new Delete(ROW_3);
423    delete.addFamily(COLUMN_3);
424    remoteTable.delete(delete);
425
426    get = new Get(ROW_3);
427    get.addFamily(COLUMN_3);
428    result = remoteTable.get(get);
429    value3 = result.getValue(COLUMN_3, QUALIFIER_1);
430    value4 = result.getValue(COLUMN_3, QUALIFIER_2);
431    assertNull(value3);
432    assertNull(value4);
433
434    delete = new Delete(ROW_3);
435    remoteTable.delete(delete);
436
437    get = new Get(ROW_3);
438    get.addFamily(COLUMN_1);
439    get.addFamily(COLUMN_2);
440    result = remoteTable.get(get);
441    value1 = result.getValue(COLUMN_1, QUALIFIER_1);
442    value2 = result.getValue(COLUMN_2, QUALIFIER_2);
443    assertNull(value1);
444    assertNull(value2);
445  }
446
447  /**
448   * Test RemoteHTable.Scanner
449   */
450  @Test
451  public void testScanner() throws IOException {
452    List<Put> puts = new ArrayList<>(4);
453    Put put = new Put(ROW_1);
454    put.addColumn(COLUMN_1, QUALIFIER_1, VALUE_1);
455    puts.add(put);
456    put = new Put(ROW_2);
457    put.addColumn(COLUMN_1, QUALIFIER_1, VALUE_1);
458    puts.add(put);
459    put = new Put(ROW_3);
460    put.addColumn(COLUMN_1, QUALIFIER_1, VALUE_1);
461    puts.add(put);
462    put = new Put(ROW_4);
463    put.addColumn(COLUMN_1, QUALIFIER_1, VALUE_1);
464    puts.add(put);
465    remoteTable.put(puts);
466
467    ResultScanner scanner = remoteTable.getScanner(new Scan());
468
469    Result[] results = scanner.next(1);
470    assertNotNull(results);
471    assertEquals(1, results.length);
472    assertTrue(Bytes.equals(ROW_1, results[0].getRow()));
473
474    Result result = scanner.next();
475    assertNotNull(result);
476    assertTrue(Bytes.equals(ROW_2, result.getRow()));
477
478    results = scanner.next(2);
479    assertNotNull(results);
480    assertEquals(2, results.length);
481    assertTrue(Bytes.equals(ROW_3, results[0].getRow()));
482    assertTrue(Bytes.equals(ROW_4, results[1].getRow()));
483
484    results = scanner.next(1);
485    assertNull(results);
486    scanner.close();
487
488    scanner = remoteTable.getScanner(COLUMN_1);
489    results = scanner.next(4);
490    assertNotNull(results);
491    assertEquals(4, results.length);
492    assertTrue(Bytes.equals(ROW_1, results[0].getRow()));
493    assertTrue(Bytes.equals(ROW_2, results[1].getRow()));
494    assertTrue(Bytes.equals(ROW_3, results[2].getRow()));
495    assertTrue(Bytes.equals(ROW_4, results[3].getRow()));
496
497    scanner.close();
498
499    scanner = remoteTable.getScanner(COLUMN_1, QUALIFIER_1);
500    results = scanner.next(4);
501    assertNotNull(results);
502    assertEquals(4, results.length);
503    assertTrue(Bytes.equals(ROW_1, results[0].getRow()));
504    assertTrue(Bytes.equals(ROW_2, results[1].getRow()));
505    assertTrue(Bytes.equals(ROW_3, results[2].getRow()));
506    assertTrue(Bytes.equals(ROW_4, results[3].getRow()));
507    scanner.close();
508    assertTrue(remoteTable.isAutoFlush());
509  }
510
511  @Test
512  public void testCheckAndDelete() throws IOException {
513    // Requires UriCompliance.Violation.SUSPICIOUS_PATH_CHARACTERS
514    // Otherwise fails with "400: Suspicious Path Character"
515    // This example is considered suspicious by the Jetty 12 due to reasons same as shown in
516    // testGet()
517    Get get = new Get(ROW_1);
518    Result result = remoteTable.get(get);
519    byte[] value1 = result.getValue(COLUMN_1, QUALIFIER_1);
520    byte[] value2 = result.getValue(COLUMN_2, QUALIFIER_2);
521    assertNotNull(value1);
522    assertTrue(Bytes.equals(VALUE_1, value1));
523    assertNull(value2);
524    assertTrue(remoteTable.exists(get));
525    assertEquals(1, remoteTable.exists(Collections.singletonList(get)).length);
526    Delete delete = new Delete(ROW_1);
527
528    remoteTable.checkAndMutate(ROW_1, COLUMN_1).qualifier(QUALIFIER_1).ifEquals(VALUE_1)
529      .thenDelete(delete);
530    assertFalse(remoteTable.exists(get));
531
532    Put put = new Put(ROW_1);
533    put.addColumn(COLUMN_1, QUALIFIER_1, VALUE_1);
534    remoteTable.put(put);
535
536    assertTrue(remoteTable.checkAndMutate(ROW_1, COLUMN_1).qualifier(QUALIFIER_1).ifEquals(VALUE_1)
537      .thenPut(put));
538    assertFalse(remoteTable.checkAndMutate(ROW_1, COLUMN_1).qualifier(QUALIFIER_1).ifEquals(VALUE_2)
539      .thenPut(put));
540  }
541
542  /**
543   * Test RemoteHable.Scanner.iterator method
544   */
545  @Test
546  public void testIteratorScaner() throws IOException {
547    List<Put> puts = new ArrayList<>(4);
548    Put put = new Put(ROW_1);
549    put.addColumn(COLUMN_1, QUALIFIER_1, VALUE_1);
550    puts.add(put);
551    put = new Put(ROW_2);
552    put.addColumn(COLUMN_1, QUALIFIER_1, VALUE_1);
553    puts.add(put);
554    put = new Put(ROW_3);
555    put.addColumn(COLUMN_1, QUALIFIER_1, VALUE_1);
556    puts.add(put);
557    put = new Put(ROW_4);
558    put.addColumn(COLUMN_1, QUALIFIER_1, VALUE_1);
559    puts.add(put);
560    remoteTable.put(puts);
561
562    ResultScanner scanner = remoteTable.getScanner(new Scan());
563    Iterator<Result> iterator = scanner.iterator();
564    assertTrue(iterator.hasNext());
565    int counter = 0;
566    while (iterator.hasNext()) {
567      iterator.next();
568      counter++;
569    }
570    assertEquals(4, counter);
571  }
572
573  /**
574   * Test a some methods of class Response.
575   */
576  @Test
577  public void testResponse() {
578    Response response = new Response(200);
579    assertEquals(200, response.getCode());
580    Header[] headers = new Header[2];
581    headers[0] = new BasicHeader("header1", "value1");
582    headers[1] = new BasicHeader("header2", "value2");
583    response = new Response(200, headers);
584    assertEquals("value1", response.getHeader("header1"));
585    assertFalse(response.hasBody());
586    response.setCode(404);
587    assertEquals(404, response.getCode());
588    headers = new Header[2];
589    headers[0] = new BasicHeader("header1", "value1.1");
590    headers[1] = new BasicHeader("header2", "value2");
591    response.setHeaders(headers);
592    assertEquals("value1.1", response.getHeader("header1"));
593    response.setBody(Bytes.toBytes("body"));
594    assertTrue(response.hasBody());
595  }
596
597  /**
598   * Tests scanner with limitation limit the number of rows each scanner scan fetch at life time The
599   * number of rows returned should be equal to the limit
600   */
601  @Test
602  public void testLimitedScan() throws Exception {
603    int numTrials = 100;
604    int limit = 60;
605
606    // Truncate the test table for inserting test scenarios rows keys
607    TEST_UTIL.getAdmin().disableTable(TABLE);
608    TEST_UTIL.getAdmin().truncateTable(TABLE, false);
609    String row = "testrow";
610
611    try (Table table = TEST_UTIL.getConnection().getTable(TABLE)) {
612      List<Put> puts = new ArrayList<>();
613      Put put = null;
614      for (int i = 1; i <= numTrials; i++) {
615        put = new Put(Bytes.toBytes(row + i));
616        put.addColumn(COLUMN_1, QUALIFIER_1, TS_2, Bytes.toBytes("testvalue" + i));
617        puts.add(put);
618      }
619      table.put(puts);
620    }
621
622    remoteTable =
623      new RemoteHTable(new Client(new Cluster().add("localhost", REST_TEST_UTIL.getServletPort())),
624        TEST_UTIL.getConfiguration(), TABLE.toBytes());
625
626    Scan scan = new Scan();
627    scan.setLimit(limit);
628    ResultScanner scanner = remoteTable.getScanner(scan);
629    Iterator<Result> resultIterator = scanner.iterator();
630    int counter = 0;
631    while (resultIterator.hasNext()) {
632      resultIterator.next();
633      counter++;
634    }
635    assertEquals(limit, counter);
636  }
637
638  /**
639   * Tests keeping a HBase scanner alive for long periods of time. Each call to next() should reset
640   * the ConnectionCache timeout for the scanner's connection.
641   * @throws Exception if starting the servlet container or disabling or truncating the table fails
642   */
643  @Test
644  public void testLongLivedScan() throws Exception {
645    int numTrials = 6;
646    int trialPause = 1000;
647    int cleanUpInterval = 100;
648
649    // Shutdown the Rest Servlet container
650    REST_TEST_UTIL.shutdownServletContainer();
651
652    // Set the ConnectionCache timeout to trigger halfway through the trials
653    TEST_UTIL.getConfiguration().setLong(RESTServlet.MAX_IDLETIME, (numTrials / 2) * trialPause);
654    TEST_UTIL.getConfiguration().setLong(RESTServlet.CLEANUP_INTERVAL, cleanUpInterval);
655
656    // Start the Rest Servlet container
657    REST_TEST_UTIL.startServletContainer(TEST_UTIL.getConfiguration());
658
659    // Truncate the test table for inserting test scenarios rows keys
660    TEST_UTIL.getAdmin().disableTable(TABLE);
661    TEST_UTIL.getAdmin().truncateTable(TABLE, false);
662
663    remoteTable =
664      new RemoteHTable(new Client(new Cluster().add("localhost", REST_TEST_UTIL.getServletPort())),
665        TEST_UTIL.getConfiguration(), TABLE.toBytes());
666
667    String row = "testrow";
668
669    try (Table table = TEST_UTIL.getConnection().getTable(TABLE)) {
670      List<Put> puts = new ArrayList<Put>();
671      Put put = null;
672      for (int i = 1; i <= numTrials; i++) {
673        put = new Put(Bytes.toBytes(row + i));
674        put.addColumn(COLUMN_1, QUALIFIER_1, TS_2, Bytes.toBytes("testvalue" + i));
675        puts.add(put);
676      }
677      table.put(puts);
678    }
679
680    Scan scan = new Scan();
681    scan.setCaching(1);
682    scan.setBatch(1);
683
684    ResultScanner scanner = remoteTable.getScanner(scan);
685    Result result = null;
686    // get scanner and rows
687    for (int i = 1; i <= numTrials; i++) {
688      // Make sure that the Scanner doesn't throw an exception after the ConnectionCache timeout
689      result = scanner.next();
690      assertEquals(row + i, Bytes.toString(result.getRow()));
691      Thread.sleep(trialPause);
692    }
693  }
694
695  @Test
696  public void testScanWithInlcudeStartStopRow() throws Exception {
697    int numTrials = 6;
698
699    // Truncate the test table for inserting test scenarios rows keys
700    TEST_UTIL.getAdmin().disableTable(TABLE);
701    TEST_UTIL.getAdmin().truncateTable(TABLE, false);
702    String row = "testrow";
703
704    try (Table table = TEST_UTIL.getConnection().getTable(TABLE)) {
705      List<Put> puts = new ArrayList<>();
706      Put put = null;
707      for (int i = 1; i <= numTrials; i++) {
708        put = new Put(Bytes.toBytes(row + i));
709        put.addColumn(COLUMN_1, QUALIFIER_1, TS_2, Bytes.toBytes("testvalue" + i));
710        puts.add(put);
711      }
712      table.put(puts);
713    }
714
715    remoteTable =
716      new RemoteHTable(new Client(new Cluster().add("localhost", REST_TEST_UTIL.getServletPort())),
717        TEST_UTIL.getConfiguration(), TABLE.toBytes());
718
719    Scan scan =
720      new Scan().withStartRow(Bytes.toBytes(row + "1")).withStopRow(Bytes.toBytes(row + "5"));
721
722    ResultScanner scanner = remoteTable.getScanner(scan);
723    Iterator<Result> resultIterator = scanner.iterator();
724    int counter = 0;
725    while (resultIterator.hasNext()) {
726      byte[] row1 = resultIterator.next().getRow();
727      System.out.println(Bytes.toString(row1));
728      counter++;
729    }
730    assertEquals(4, counter);
731
732    // test with include start row false
733    scan = new Scan().withStartRow(Bytes.toBytes(row + "1"), false)
734      .withStopRow(Bytes.toBytes(row + "5"));
735    scanner = remoteTable.getScanner(scan);
736    resultIterator = scanner.iterator();
737    counter = 0;
738    while (resultIterator.hasNext()) {
739      byte[] row1 = resultIterator.next().getRow();
740      System.out.println(Bytes.toString(row1));
741      counter++;
742    }
743    assertEquals(3, counter);
744
745    // test with include start row false and stop row true
746    scan = new Scan().withStartRow(Bytes.toBytes(row + "1"), false)
747      .withStopRow(Bytes.toBytes(row + "5"), true);
748    scanner = remoteTable.getScanner(scan);
749    resultIterator = scanner.iterator();
750    counter = 0;
751    while (resultIterator.hasNext()) {
752      byte[] row1 = resultIterator.next().getRow();
753      System.out.println(Bytes.toString(row1));
754      counter++;
755    }
756    assertEquals(4, counter);
757
758    // test with include start row true and stop row true
759    scan = new Scan().withStartRow(Bytes.toBytes(row + "1"), true)
760      .withStopRow(Bytes.toBytes(row + "5"), true);
761    scanner = remoteTable.getScanner(scan);
762    resultIterator = scanner.iterator();
763    counter = 0;
764    while (resultIterator.hasNext()) {
765      byte[] row1 = resultIterator.next().getRow();
766      System.out.println(Bytes.toString(row1));
767      counter++;
768    }
769    assertEquals(5, counter);
770  }
771}