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.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertNotNull;
022import static org.junit.jupiter.api.Assertions.assertTrue;
023import static org.junit.jupiter.api.Assertions.fail;
024
025import java.io.ByteArrayInputStream;
026import java.io.ByteArrayOutputStream;
027import java.io.DataInputStream;
028import java.io.DataOutputStream;
029import java.io.IOException;
030import java.util.List;
031import java.util.Map;
032import java.util.NavigableSet;
033import java.util.Set;
034import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
035import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
036import org.apache.hadoop.hbase.client.Get;
037import org.apache.hadoop.hbase.client.RegionInfo;
038import org.apache.hadoop.hbase.client.RegionInfoBuilder;
039import org.apache.hadoop.hbase.client.Scan;
040import org.apache.hadoop.hbase.client.TableDescriptor;
041import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
042import org.apache.hadoop.hbase.filter.BinaryComparator;
043import org.apache.hadoop.hbase.filter.Filter;
044import org.apache.hadoop.hbase.filter.PrefixFilter;
045import org.apache.hadoop.hbase.filter.RowFilter;
046import org.apache.hadoop.hbase.io.TimeRange;
047import org.apache.hadoop.hbase.testclassification.MiscTests;
048import org.apache.hadoop.hbase.testclassification.SmallTests;
049import org.apache.hadoop.hbase.util.ByteBufferUtils;
050import org.apache.hadoop.hbase.util.Bytes;
051import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
052import org.apache.hadoop.io.DataInputBuffer;
053import org.junit.jupiter.api.Tag;
054import org.junit.jupiter.api.Test;
055
056import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
057import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos;
058
059/**
060 * Test HBase Writables serializations
061 */
062@Tag(MiscTests.TAG)
063@Tag(SmallTests.TAG)
064public class TestSerialization {
065
066  @Test
067  public void testKeyValue() throws Exception {
068    final String name = "testKeyValue2";
069    byte[] row = Bytes.toBytes(name);
070    byte[] fam = Bytes.toBytes("fam");
071    byte[] qf = Bytes.toBytes("qf");
072    long ts = EnvironmentEdgeManager.currentTime();
073    byte[] val = Bytes.toBytes("val");
074    KeyValue kv = new KeyValue(row, fam, qf, ts, val);
075    ByteArrayOutputStream baos = new ByteArrayOutputStream();
076    DataOutputStream dos = new DataOutputStream(baos);
077    KeyValueUtil.write(kv, dos);
078    dos.close();
079    byte[] mb = baos.toByteArray();
080    ByteArrayInputStream bais = new ByteArrayInputStream(mb);
081    DataInputStream dis = new DataInputStream(bais);
082    KeyValue deserializedKv = KeyValueUtil.create(dis);
083    assertTrue(Bytes.equals(kv.getBuffer(), deserializedKv.getBuffer()));
084    assertEquals(kv.getOffset(), deserializedKv.getOffset());
085    assertEquals(kv.getLength(), deserializedKv.getLength());
086  }
087
088  @Test
089  public void testCreateKeyValueInvalidNegativeLength() {
090
091    KeyValue kv_0 = new KeyValue(Bytes.toBytes("myRow"), Bytes.toBytes("myCF"), // 51 bytes
092      Bytes.toBytes("myQualifier"), 12345L, Bytes.toBytes("my12345"));
093
094    KeyValue kv_1 = new KeyValue(Bytes.toBytes("myRow"), Bytes.toBytes("myCF"), // 49 bytes
095      Bytes.toBytes("myQualifier"), 12345L, Bytes.toBytes("my123"));
096
097    ByteArrayOutputStream baos = new ByteArrayOutputStream();
098    DataOutputStream dos = new DataOutputStream(baos);
099
100    long l = 0;
101    try {
102      ByteBufferUtils.putInt(dos, kv_0.getSerializedSize(false));
103      l = (long) kv_0.write(dos, false) + Bytes.SIZEOF_INT;
104      ByteBufferUtils.putInt(dos, kv_1.getSerializedSize(false));
105      l += (long) kv_1.write(dos, false) + Bytes.SIZEOF_INT;
106      assertEquals(100L, l);
107    } catch (IOException e) {
108      fail("Unexpected IOException" + e.getMessage());
109    }
110
111    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
112    DataInputStream dis = new DataInputStream(bais);
113
114    try {
115      KeyValueUtil.create(dis);
116      assertTrue(kv_0.equals(kv_1));
117    } catch (Exception e) {
118      fail("Unexpected Exception" + e.getMessage());
119    }
120
121    // length -1
122    try {
123      // even if we have a good kv now in dis we will just pass length with -1 for simplicity
124      KeyValueUtil.create(-1, dis);
125      fail("Expected corrupt stream");
126    } catch (Exception e) {
127      assertEquals("Failed read -1 bytes, stream corrupt?", e.getMessage());
128    }
129
130  }
131
132  @Test
133  public void testCompareFilter() throws Exception {
134    Filter f =
135      new RowFilter(CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes("testRowOne-2")));
136    byte[] bytes = f.toByteArray();
137    Filter ff = RowFilter.parseFrom(bytes);
138    assertNotNull(ff);
139  }
140
141  @Test
142  public void testTableDescriptor() throws Exception {
143    final String name = "testTableDescriptor";
144    TableDescriptor htd = createTableDescriptor(name);
145    byte[] mb = TableDescriptorBuilder.toByteArray(htd);
146    TableDescriptor deserializedHtd = TableDescriptorBuilder.parseFrom(mb);
147    assertEquals(htd.getTableName(), deserializedHtd.getTableName());
148  }
149
150  /**
151   * Test RegionInfo serialization
152   */
153  @Test
154  public void testRegionInfo() throws Exception {
155    RegionInfo hri = createRandomRegion("testRegionInfo");
156
157    // test toByteArray()
158    byte[] hrib = RegionInfo.toByteArray(hri);
159    RegionInfo deserializedHri = RegionInfo.parseFrom(hrib);
160    assertEquals(hri.getEncodedName(), deserializedHri.getEncodedName());
161    assertEquals(hri, deserializedHri);
162
163    // test toDelimitedByteArray()
164    hrib = RegionInfo.toDelimitedByteArray(hri);
165    DataInputBuffer buf = new DataInputBuffer();
166    try {
167      buf.reset(hrib, hrib.length);
168      deserializedHri = RegionInfo.parseFrom(buf);
169      assertEquals(hri.getEncodedName(), deserializedHri.getEncodedName());
170      assertEquals(hri, deserializedHri);
171    } finally {
172      buf.close();
173    }
174  }
175
176  @Test
177  public void testRegionInfos() throws Exception {
178    RegionInfo hri = createRandomRegion("testRegionInfos");
179    byte[] triple = RegionInfo.toDelimitedByteArray(hri, hri, hri);
180    List<RegionInfo> regions = RegionInfo.parseDelimitedFrom(triple, 0, triple.length);
181    assertTrue(regions.size() == 3);
182    assertTrue(regions.get(0).equals(regions.get(1)));
183    assertTrue(regions.get(0).equals(regions.get(2)));
184  }
185
186  private RegionInfo createRandomRegion(final String name) {
187    TableDescriptorBuilder tableDescriptorBuilder =
188      TableDescriptorBuilder.newBuilder(TableName.valueOf(name));
189    String[] families = new String[] { "info", "anchor" };
190    for (int i = 0; i < families.length; i++) {
191      ColumnFamilyDescriptor columnFamilyDescriptor =
192        ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(families[i])).build();
193      tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
194    }
195    TableDescriptor tableDescriptor = tableDescriptorBuilder.build();
196    return RegionInfoBuilder.newBuilder(tableDescriptor.getTableName()).build();
197  }
198
199  @Test
200  public void testGet() throws Exception {
201    byte[] row = Bytes.toBytes("row");
202    byte[] fam = Bytes.toBytes("fam");
203    byte[] qf1 = Bytes.toBytes("qf1");
204    long ts = EnvironmentEdgeManager.currentTime();
205    int maxVersions = 2;
206
207    Get get = new Get(row);
208    get.addColumn(fam, qf1);
209    get.setTimeRange(ts, ts + 1);
210    get.readVersions(maxVersions);
211
212    ClientProtos.Get getProto = ProtobufUtil.toGet(get);
213    Get desGet = ProtobufUtil.toGet(getProto);
214
215    assertTrue(Bytes.equals(get.getRow(), desGet.getRow()));
216    Set<byte[]> set = null;
217    Set<byte[]> desSet = null;
218
219    for (Map.Entry<byte[], NavigableSet<byte[]>> entry : get.getFamilyMap().entrySet()) {
220      assertTrue(desGet.getFamilyMap().containsKey(entry.getKey()));
221      set = entry.getValue();
222      desSet = desGet.getFamilyMap().get(entry.getKey());
223      for (byte[] qualifier : set) {
224        assertTrue(desSet.contains(qualifier));
225      }
226    }
227
228    assertEquals(get.getMaxVersions(), desGet.getMaxVersions());
229    TimeRange tr = get.getTimeRange();
230    TimeRange desTr = desGet.getTimeRange();
231    assertEquals(tr.getMax(), desTr.getMax());
232    assertEquals(tr.getMin(), desTr.getMin());
233  }
234
235  @Test
236  public void testScan() throws Exception {
237
238    byte[] startRow = Bytes.toBytes("startRow");
239    byte[] stopRow = Bytes.toBytes("stopRow");
240    byte[] fam = Bytes.toBytes("fam");
241    byte[] qf1 = Bytes.toBytes("qf1");
242    long ts = EnvironmentEdgeManager.currentTime();
243    int maxVersions = 2;
244
245    Scan scan = new Scan().withStartRow(startRow).withStopRow(stopRow);
246    scan.addColumn(fam, qf1);
247    scan.setTimeRange(ts, ts + 1);
248    scan.readVersions(maxVersions);
249
250    ClientProtos.Scan scanProto = ProtobufUtil.toScan(scan);
251    Scan desScan = ProtobufUtil.toScan(scanProto);
252
253    assertTrue(Bytes.equals(scan.getStartRow(), desScan.getStartRow()));
254    assertTrue(Bytes.equals(scan.getStopRow(), desScan.getStopRow()));
255    assertEquals(scan.getCacheBlocks(), desScan.getCacheBlocks());
256    Set<byte[]> set = null;
257    Set<byte[]> desSet = null;
258
259    for (Map.Entry<byte[], NavigableSet<byte[]>> entry : scan.getFamilyMap().entrySet()) {
260      assertTrue(desScan.getFamilyMap().containsKey(entry.getKey()));
261      set = entry.getValue();
262      desSet = desScan.getFamilyMap().get(entry.getKey());
263      for (byte[] column : set) {
264        assertTrue(desSet.contains(column));
265      }
266
267      // Test filters are serialized properly.
268      scan = new Scan().withStartRow(startRow);
269      final String name = "testScan";
270      byte[] prefix = Bytes.toBytes(name);
271      scan.setFilter(new PrefixFilter(prefix));
272      scanProto = ProtobufUtil.toScan(scan);
273      desScan = ProtobufUtil.toScan(scanProto);
274      Filter f = desScan.getFilter();
275      assertTrue(f instanceof PrefixFilter);
276    }
277
278    assertEquals(scan.getMaxVersions(), desScan.getMaxVersions());
279    TimeRange tr = scan.getTimeRange();
280    TimeRange desTr = desScan.getTimeRange();
281    assertEquals(tr.getMax(), desTr.getMax());
282    assertEquals(tr.getMin(), desTr.getMin());
283  }
284
285  protected static final int MAXVERSIONS = 3;
286  protected final static byte[] fam1 = Bytes.toBytes("colfamily1");
287  protected final static byte[] fam2 = Bytes.toBytes("colfamily2");
288  protected final static byte[] fam3 = Bytes.toBytes("colfamily3");
289  protected static final byte[][] COLUMNS = { fam1, fam2, fam3 };
290
291  /**
292   * Create a table of name <code>name</code> with {@link #COLUMNS} for families.
293   * @param name Name to give table.
294   * @return Column descriptor.
295   */
296  protected TableDescriptor createTableDescriptor(final String name) {
297    return createTableDescriptor(name, MAXVERSIONS);
298  }
299
300  /**
301   * Create a table of name <code>name</code> with {@link #COLUMNS} for families.
302   * @param name     Name to give table.
303   * @param versions How many versions to allow per column.
304   * @return Column descriptor.
305   */
306  protected TableDescriptor createTableDescriptor(final String name, final int versions) {
307    TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(TableName.valueOf(name));
308    builder
309      .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(fam1).setMaxVersions(versions)
310        .setBlockCacheEnabled(false).build())
311      .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(fam2).setMaxVersions(versions)
312        .setBlockCacheEnabled(false).build())
313      .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(fam3).setMaxVersions(versions)
314        .setBlockCacheEnabled(false).build());
315    return builder.build();
316  }
317}