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;
019
020import static org.junit.Assert.assertArrayEquals;
021import static org.junit.Assert.assertEquals;
022import static org.junit.Assert.assertFalse;
023import static org.junit.Assert.assertNotEquals;
024import static org.junit.Assert.assertNotNull;
025import static org.junit.Assert.assertTrue;
026
027import java.io.IOException;
028import java.util.ArrayList;
029import java.util.List;
030import java.util.NavigableMap;
031import org.apache.hadoop.hbase.TimestampTestBase.FlushCache;
032import org.apache.hadoop.hbase.client.Admin;
033import org.apache.hadoop.hbase.client.Get;
034import org.apache.hadoop.hbase.client.Put;
035import org.apache.hadoop.hbase.client.Result;
036import org.apache.hadoop.hbase.client.ResultScanner;
037import org.apache.hadoop.hbase.client.Scan;
038import org.apache.hadoop.hbase.client.Table;
039import org.apache.hadoop.hbase.testclassification.MediumTests;
040import org.apache.hadoop.hbase.testclassification.MiscTests;
041import org.apache.hadoop.hbase.util.Bytes;
042import org.apache.hadoop.hbase.util.Pair;
043import org.junit.AfterClass;
044import org.junit.Before;
045import org.junit.BeforeClass;
046import org.junit.ClassRule;
047import org.junit.Rule;
048import org.junit.Test;
049import org.junit.experimental.categories.Category;
050import org.junit.rules.TestName;
051import org.slf4j.Logger;
052import org.slf4j.LoggerFactory;
053
054import org.apache.hbase.thirdparty.com.google.common.collect.Iterables;
055
056/**
057 * Port of old TestScanMultipleVersions, TestTimestamp and TestGetRowVersions from old testing
058 * framework to {@link HBaseTestingUtility}.
059 */
060@Category({ MiscTests.class, MediumTests.class })
061public class TestMultiVersions {
062
063  @ClassRule
064  public static final HBaseClassTestRule CLASS_RULE =
065    HBaseClassTestRule.forClass(TestMultiVersions.class);
066
067  private static final Logger LOG = LoggerFactory.getLogger(TestMultiVersions.class);
068  private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
069  private Admin admin;
070
071  private static final int NUM_SLAVES = 3;
072
073  @Rule
074  public TestName name = new TestName();
075
076  @BeforeClass
077  public static void setUpBeforeClass() throws Exception {
078    UTIL.startMiniCluster(NUM_SLAVES);
079  }
080
081  @AfterClass
082  public static void tearDownAfterClass() throws Exception {
083    UTIL.shutdownMiniCluster();
084  }
085
086  @Before
087  public void before() throws MasterNotRunningException, ZooKeeperConnectionException, IOException {
088    this.admin = UTIL.getAdmin();
089  }
090
091  /**
092   * Tests user specifiable time stamps putting, getting and scanning. Also tests same in presence
093   * of deletes. Test cores are written so can be run against an HRegion and against an HTable: i.e.
094   * both local and remote.
095   * <p>
096   * Port of old TestTimestamp test to here so can better utilize the spun up cluster running more
097   * than a single test per spin up. Keep old tests' crazyness.
098   */
099  @Test
100  public void testTimestamps() throws Exception {
101    HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(name.getMethodName()));
102    HColumnDescriptor hcd = new HColumnDescriptor(TimestampTestBase.FAMILY_NAME);
103    hcd.setMaxVersions(3);
104    desc.addFamily(hcd);
105    this.admin.createTable(desc);
106    Table table = UTIL.getConnection().getTable(desc.getTableName());
107    // TODO: Remove these deprecated classes or pull them in here if this is
108    // only test using them.
109    TimestampTestBase.doTestDelete(table, new FlushCache() {
110      @Override
111      public void flushcache() throws IOException {
112        UTIL.getHBaseCluster().flushcache();
113      }
114    });
115
116    // Perhaps drop and readd the table between tests so the former does
117    // not pollute this latter? Or put into separate tests.
118    TimestampTestBase.doTestTimestampScanning(table, new FlushCache() {
119      @Override
120      public void flushcache() throws IOException {
121        UTIL.getMiniHBaseCluster().flushcache();
122      }
123    });
124
125    table.close();
126  }
127
128  /**
129   * Verifies versions across a cluster restart.
130   * <p/>
131   * Port of old TestGetRowVersions test to here so can better utilize the spun up cluster running
132   * more than a single test per spin up. Keep old tests' crazyness.
133   */
134  @Test
135  public void testGetRowVersions() throws Exception {
136    final byte[] contents = Bytes.toBytes("contents");
137    final byte[] row = Bytes.toBytes("row");
138    final byte[] value1 = Bytes.toBytes("value1");
139    final byte[] value2 = Bytes.toBytes("value2");
140    final long timestamp1 = 100L;
141    final long timestamp2 = 200L;
142    final HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(name.getMethodName()));
143    HColumnDescriptor hcd = new HColumnDescriptor(contents);
144    hcd.setMaxVersions(3);
145    desc.addFamily(hcd);
146    this.admin.createTable(desc);
147    Put put = new Put(row, timestamp1);
148    put.addColumn(contents, contents, value1);
149    Table table = UTIL.getConnection().getTable(desc.getTableName());
150    table.put(put);
151    // Shut down and restart the HBase cluster
152    table.close();
153    UTIL.shutdownMiniHBaseCluster();
154    LOG.debug("HBase cluster shut down -- restarting");
155    StartMiniClusterOption option =
156      StartMiniClusterOption.builder().numRegionServers(NUM_SLAVES).build();
157    UTIL.startMiniHBaseCluster(option);
158    // Make a new connection.
159    table = UTIL.getConnection().getTable(desc.getTableName());
160    // Overwrite previous value
161    put = new Put(row, timestamp2);
162    put.addColumn(contents, contents, value2);
163    table.put(put);
164    // Now verify that getRow(row, column, latest) works
165    Get get = new Get(row);
166    // Should get one version by default
167    Result r = table.get(get);
168    assertNotNull(r);
169    assertFalse(r.isEmpty());
170    assertEquals(1, r.size());
171    byte[] value = r.getValue(contents, contents);
172    assertNotEquals(0, value.length);
173    assertTrue(Bytes.equals(value, value2));
174    // Now check getRow with multiple versions
175    get = new Get(row);
176    get.setMaxVersions();
177    r = table.get(get);
178    assertEquals(2, r.size());
179    value = r.getValue(contents, contents);
180    assertNotEquals(0, value.length);
181    assertArrayEquals(value, value2);
182    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> map = r.getMap();
183    NavigableMap<byte[], NavigableMap<Long, byte[]>> familyMap = map.get(contents);
184    NavigableMap<Long, byte[]> versionMap = familyMap.get(contents);
185    assertEquals(2, versionMap.size());
186    assertArrayEquals(value1, versionMap.get(timestamp1));
187    assertArrayEquals(value2, versionMap.get(timestamp2));
188    table.close();
189  }
190
191  /**
192   * Port of old TestScanMultipleVersions test here so can better utilize the spun up cluster
193   * running more than just a single test. Keep old tests crazyness.
194   * <p>
195   * Tests five cases of scans and timestamps. n
196   */
197  @Test
198  public void testScanMultipleVersions() throws Exception {
199    final TableName tableName = TableName.valueOf(name.getMethodName());
200    final HTableDescriptor desc = new HTableDescriptor(tableName);
201    desc.addFamily(new HColumnDescriptor(HConstants.CATALOG_FAMILY));
202    final byte[][] rows = new byte[][] { Bytes.toBytes("row_0200"), Bytes.toBytes("row_0800") };
203    final byte[][] splitRows = new byte[][] { Bytes.toBytes("row_0500") };
204    final long[] timestamp = new long[] { 100L, 1000L };
205    this.admin.createTable(desc, splitRows);
206    Table table = UTIL.getConnection().getTable(tableName);
207    // Assert we got the region layout wanted.
208    Pair<byte[][], byte[][]> keys =
209      UTIL.getConnection().getRegionLocator(tableName).getStartEndKeys();
210    assertEquals(2, keys.getFirst().length);
211    byte[][] startKeys = keys.getFirst();
212    byte[][] endKeys = keys.getSecond();
213
214    for (int i = 0; i < startKeys.length; i++) {
215      if (i == 0) {
216        assertArrayEquals(HConstants.EMPTY_START_ROW, startKeys[i]);
217        assertArrayEquals(endKeys[i], splitRows[0]);
218      } else if (i == 1) {
219        assertArrayEquals(splitRows[0], startKeys[i]);
220        assertArrayEquals(endKeys[i], HConstants.EMPTY_END_ROW);
221      }
222    }
223    // Insert data
224    List<Put> puts = new ArrayList<>();
225    for (int i = 0; i < startKeys.length; i++) {
226      for (int j = 0; j < timestamp.length; j++) {
227        Put put = new Put(rows[i], timestamp[j]);
228        put.addColumn(HConstants.CATALOG_FAMILY, null, timestamp[j], Bytes.toBytes(timestamp[j]));
229        puts.add(put);
230      }
231    }
232    table.put(puts);
233    // There are 5 cases we have to test. Each is described below.
234    for (int i = 0; i < rows.length; i++) {
235      for (int j = 0; j < timestamp.length; j++) {
236        Get get = new Get(rows[i]);
237        get.addFamily(HConstants.CATALOG_FAMILY);
238        get.setTimestamp(timestamp[j]);
239        Result result = table.get(get);
240        int cellCount = result.rawCells().length;
241        assertEquals(1, cellCount);
242      }
243    }
244
245    // Case 1: scan with LATEST_TIMESTAMP. Should get two rows
246    int count;
247    Scan scan = new Scan();
248    scan.addFamily(HConstants.CATALOG_FAMILY);
249    try (ResultScanner s = table.getScanner(scan)) {
250      count = Iterables.size(s);
251    }
252    assertEquals("Number of rows should be 2", 2, count);
253
254    // Case 2: Scan with a timestamp greater than most recent timestamp
255    // (in this case > 1000 and < LATEST_TIMESTAMP. Should get 2 rows.
256    scan = new Scan();
257    scan.setTimeRange(1000L, Long.MAX_VALUE);
258    scan.addFamily(HConstants.CATALOG_FAMILY);
259    try (ResultScanner s = table.getScanner(scan)) {
260      count = Iterables.size(s);
261    }
262    assertEquals("Number of rows should be 2", 2, count);
263
264    // Case 3: scan with timestamp equal to most recent timestamp
265    // (in this case == 1000. Should get 2 rows.
266    scan = new Scan();
267    scan.setTimestamp(1000L);
268    scan.addFamily(HConstants.CATALOG_FAMILY);
269    try (ResultScanner s = table.getScanner(scan)) {
270      count = Iterables.size(s);
271    }
272    assertEquals("Number of rows should be 2", 2, count);
273
274    // Case 4: scan with timestamp greater than first timestamp but less than
275    // second timestamp (100 < timestamp < 1000). Should get 2 rows.
276    scan = new Scan();
277    scan.setTimeRange(100L, 1000L);
278    scan.addFamily(HConstants.CATALOG_FAMILY);
279    try (ResultScanner s = table.getScanner(scan)) {
280      count = Iterables.size(s);
281    }
282    assertEquals("Number of rows should be 2", 2, count);
283
284    // Case 5: scan with timestamp equal to first timestamp (100)
285    // Should get 2 rows.
286    scan = new Scan();
287    scan.setTimestamp(100L);
288    scan.addFamily(HConstants.CATALOG_FAMILY);
289    try (ResultScanner s = table.getScanner(scan)) {
290      count = Iterables.size(s);
291    }
292    assertEquals("Number of rows should be 2", 2, count);
293  }
294
295}