1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase;
19  
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertFalse;
23  import static org.junit.Assert.assertNotNull;
24  import static org.junit.Assert.assertTrue;
25  
26  import java.io.IOException;
27  import java.util.Map;
28  import java.util.NavigableMap;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.conf.Configuration;
33  import org.apache.hadoop.hbase.HBaseTestCase.FlushCache;
34  import org.apache.hadoop.hbase.HBaseTestCase.HTableIncommon;
35  import org.apache.hadoop.hbase.HBaseTestCase.Incommon;
36  import org.apache.hadoop.hbase.client.Get;
37  import org.apache.hadoop.hbase.client.HBaseAdmin;
38  import org.apache.hadoop.hbase.client.HTable;
39  import org.apache.hadoop.hbase.client.Put;
40  import org.apache.hadoop.hbase.client.Result;
41  import org.apache.hadoop.hbase.client.ResultScanner;
42  import org.apache.hadoop.hbase.client.Scan;
43  import org.apache.hadoop.hbase.util.Bytes;
44  import org.junit.After;
45  import org.junit.AfterClass;
46  import org.junit.Before;
47  import org.junit.BeforeClass;
48  import org.junit.Test;
49  import org.junit.experimental.categories.Category;
50  
51  /**
52   * Port of old TestScanMultipleVersions, TestTimestamp and TestGetRowVersions
53   * from old testing framework to {@link HBaseTestingUtility}.
54   */
55  @Category(MediumTests.class)
56  public class TestMultiVersions {
57    private static final Log LOG = LogFactory.getLog(TestMultiVersions.class);
58    private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
59    private HBaseAdmin admin;
60  
61    @BeforeClass
62    public static void setUpBeforeClass() throws Exception {
63      UTIL.startMiniCluster();
64    }
65  
66    @AfterClass
67    public static void tearDownAfterClass() throws Exception {
68      UTIL.shutdownMiniCluster();
69    }
70  
71    @Before
72    public void before()
73    throws MasterNotRunningException, ZooKeeperConnectionException {
74      this.admin = new HBaseAdmin(UTIL.getConfiguration());
75    }
76  
77    @After
78    public void after() throws IOException {
79      this.admin.close();
80    }
81  
82    /**
83    * Tests user specifiable time stamps putting, getting and scanning.  Also
84     * tests same in presence of deletes.  Test cores are written so can be
85     * run against an HRegion and against an HTable: i.e. both local and remote.
86     * 
87     * <p>Port of old TestTimestamp test to here so can better utilize the spun
88     * up cluster running more than a single test per spin up.  Keep old tests'
89     * crazyness.
90     */
91    @Test
92    public void testTimestamps() throws Exception {
93      HTableDescriptor desc = new HTableDescriptor("testTimestamps");
94      desc.addFamily(new HColumnDescriptor(TimestampTestBase.FAMILY_NAME));
95      this.admin.createTable(desc);
96      HTable table = new HTable(UTIL.getConfiguration(), desc.getName());
97      // TODO: Remove these deprecated classes or pull them in here if this is
98      // only test using them.
99      Incommon incommon = new HTableIncommon(table);
100     TimestampTestBase.doTestDelete(incommon, new FlushCache() {
101       public void flushcache() throws IOException {
102         UTIL.getHBaseCluster().flushcache();
103       }
104      });
105 
106     // Perhaps drop and readd the table between tests so the former does
107     // not pollute this latter?  Or put into separate tests.
108     TimestampTestBase.doTestTimestampScanning(incommon, new FlushCache() {
109       public void flushcache() throws IOException {
110         UTIL.getMiniHBaseCluster().flushcache();
111       }
112     });
113 
114     table.close();
115   }
116 
117   /**
118    * Verifies versions across a cluster restart.
119    * Port of old TestGetRowVersions test to here so can better utilize the spun
120    * up cluster running more than a single test per spin up.  Keep old tests'
121    * crazyness.
122    */
123   @Test
124   public void testGetRowVersions() throws Exception {
125     final String tableName = "testGetRowVersions";
126     final byte [] contents = Bytes.toBytes("contents");
127     final byte [] row = Bytes.toBytes("row");
128     final byte [] value1 = Bytes.toBytes("value1");
129     final byte [] value2 = Bytes.toBytes("value2");
130     final long timestamp1 = 100L;
131     final long timestamp2 = 200L;
132     final HTableDescriptor desc = new HTableDescriptor(tableName);
133     desc.addFamily(new HColumnDescriptor(contents));
134     this.admin.createTable(desc);
135     Put put = new Put(row, timestamp1, null);
136     put.add(contents, contents, value1);
137     HTable table = new HTable(UTIL.getConfiguration(), tableName);
138     table.put(put);
139     // Shut down and restart the HBase cluster
140     table.close();
141     UTIL.shutdownMiniHBaseCluster();
142     LOG.debug("HBase cluster shut down -- restarting");
143     UTIL.startMiniHBaseCluster(1, 1);
144     // Make a new connection.  Use new Configuration instance because old one
145     // is tied to an HConnection that has since gone stale.
146     table = new HTable(new Configuration(UTIL.getConfiguration()), tableName);
147     // Overwrite previous value
148     put = new Put(row, timestamp2, null);
149     put.add(contents, contents, value2);
150     table.put(put);
151     // Now verify that getRow(row, column, latest) works
152     Get get = new Get(row);
153     // Should get one version by default
154     Result r = table.get(get);
155     assertNotNull(r);
156     assertFalse(r.isEmpty());
157     assertTrue(r.size() == 1);
158     byte [] value = r.getValue(contents, contents);
159     assertTrue(value.length != 0);
160     assertTrue(Bytes.equals(value, value2));
161     // Now check getRow with multiple versions
162     get = new Get(row);
163     get.setMaxVersions();
164     r = table.get(get);
165     assertTrue(r.size() == 2);
166     value = r.getValue(contents, contents);
167     assertTrue(value.length != 0);
168     assertTrue(Bytes.equals(value, value2));
169     NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> map =
170       r.getMap();
171     NavigableMap<byte[], NavigableMap<Long, byte[]>> familyMap =
172       map.get(contents);
173     NavigableMap<Long, byte[]> versionMap = familyMap.get(contents);
174     assertTrue(versionMap.size() == 2);
175     assertTrue(Bytes.equals(value1, versionMap.get(timestamp1)));
176     assertTrue(Bytes.equals(value2, versionMap.get(timestamp2)));
177     table.close();
178   }
179 
180   /**
181    * Port of old TestScanMultipleVersions test here so can better utilize the
182    * spun up cluster running more than just a single test.  Keep old tests
183    * crazyness.
184    * 
185    * <p>Tests five cases of scans and timestamps.
186    * @throws Exception
187    */
188   @Test
189   public void testScanMultipleVersions() throws Exception {
190     final byte [] tableName = Bytes.toBytes("testScanMultipleVersions");
191     final HTableDescriptor desc = new HTableDescriptor(tableName);
192     desc.addFamily(new HColumnDescriptor(HConstants.CATALOG_FAMILY));
193     final byte [][] rows = new byte[][] {
194       Bytes.toBytes("row_0200"),
195       Bytes.toBytes("row_0800")
196     };
197     final byte [][] splitRows = new byte[][] {Bytes.toBytes("row_0500")};
198     final long [] timestamp = new long[] {100L, 1000L};
199     this.admin.createTable(desc, splitRows);
200     HTable table = new HTable(UTIL.getConfiguration(), tableName);
201     // Assert we got the region layout wanted.
202     NavigableMap<HRegionInfo, ServerName> locations = table.getRegionLocations();
203     assertEquals(2, locations.size());
204     int index = 0;
205     for (Map.Entry<HRegionInfo, ServerName> e: locations.entrySet()) {
206       HRegionInfo hri = e.getKey();
207       if (index == 0) {
208         assertTrue(Bytes.equals(HConstants.EMPTY_START_ROW, hri.getStartKey()));
209         assertTrue(Bytes.equals(hri.getEndKey(), splitRows[0]));
210       } else if (index == 1) {
211         assertTrue(Bytes.equals(splitRows[0], hri.getStartKey()));
212         assertTrue(Bytes.equals(hri.getEndKey(), HConstants.EMPTY_END_ROW));
213       }
214       index++;
215     }
216     // Insert data
217     for (int i = 0; i < locations.size(); i++) {
218       for (int j = 0; j < timestamp.length; j++) {
219         Put put = new Put(rows[i], timestamp[j], null);
220         put.add(HConstants.CATALOG_FAMILY, null, timestamp[j],
221             Bytes.toBytes(timestamp[j]));
222         table.put(put);
223       }
224     }
225     // There are 5 cases we have to test. Each is described below.
226     for (int i = 0; i < rows.length; i++) {
227       for (int j = 0; j < timestamp.length; j++) {
228         Get get = new Get(rows[i]);
229         get.addFamily(HConstants.CATALOG_FAMILY);
230         get.setTimeStamp(timestamp[j]);
231         Result result = table.get(get);
232         int cellCount = 0;
233         for(@SuppressWarnings("unused")KeyValue kv : result.list()) {
234           cellCount++;
235         }
236         assertTrue(cellCount == 1);
237       }
238       table.close();
239     }
240 
241     // Case 1: scan with LATEST_TIMESTAMP. Should get two rows
242     int count = 0;
243     Scan scan = new Scan();
244     scan.addFamily(HConstants.CATALOG_FAMILY);
245     ResultScanner s = table.getScanner(scan);
246     try {
247       for (Result rr = null; (rr = s.next()) != null;) {
248         System.out.println(rr.toString());
249         count += 1;
250       }
251       assertEquals("Number of rows should be 2", 2, count);
252     } finally {
253       s.close();
254     }
255 
256     // Case 2: Scan with a timestamp greater than most recent timestamp
257     // (in this case > 1000 and < LATEST_TIMESTAMP. Should get 2 rows.
258 
259     count = 0;
260     scan = new Scan();
261     scan.setTimeRange(1000L, Long.MAX_VALUE);
262     scan.addFamily(HConstants.CATALOG_FAMILY);
263 
264     s = table.getScanner(scan);
265     try {
266       while (s.next() != null) {
267         count += 1;
268       }
269       assertEquals("Number of rows should be 2", 2, count);
270     } finally {
271       s.close();
272     }
273 
274     // Case 3: scan with timestamp equal to most recent timestamp
275     // (in this case == 1000. Should get 2 rows.
276 
277     count = 0;
278     scan = new Scan();
279     scan.setTimeStamp(1000L);
280     scan.addFamily(HConstants.CATALOG_FAMILY);
281 
282     s = table.getScanner(scan);
283     try {
284       while (s.next() != null) {
285         count += 1;
286       }
287       assertEquals("Number of rows should be 2", 2, count);
288     } finally {
289       s.close();
290     }
291 
292     // Case 4: scan with timestamp greater than first timestamp but less than
293     // second timestamp (100 < timestamp < 1000). Should get 2 rows.
294 
295     count = 0;
296     scan = new Scan();
297     scan.setTimeRange(100L, 1000L);
298     scan.addFamily(HConstants.CATALOG_FAMILY);
299 
300     s = table.getScanner(scan);
301     try {
302       while (s.next() != null) {
303         count += 1;
304       }
305       assertEquals("Number of rows should be 2", 2, count);
306     } finally {
307       s.close();
308     }
309 
310     // Case 5: scan with timestamp equal to first timestamp (100)
311     // Should get 2 rows.
312 
313     count = 0;
314     scan = new Scan();
315     scan.setTimeStamp(100L);
316     scan.addFamily(HConstants.CATALOG_FAMILY);
317 
318     s = table.getScanner(scan);
319     try {
320       while (s.next() != null) {
321         count += 1;
322       }
323       assertEquals("Number of rows should be 2", 2, count);
324     } finally {
325       s.close();
326     }
327   }
328 
329   @org.junit.Rule
330   public org.apache.hadoop.hbase.ResourceCheckerJUnitRule cu =
331     new org.apache.hadoop.hbase.ResourceCheckerJUnitRule();
332 }
333