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.client;
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.assertTrue;
025import static org.junit.Assert.fail;
026
027import java.io.IOException;
028import org.apache.hadoop.hbase.HBaseClassTestRule;
029import org.apache.hadoop.hbase.HConstants;
030import org.apache.hadoop.hbase.TableName;
031import org.apache.hadoop.hbase.TableNameTestRule;
032import org.apache.hadoop.hbase.exceptions.DeserializationException;
033import org.apache.hadoop.hbase.testclassification.ClientTests;
034import org.apache.hadoop.hbase.testclassification.SmallTests;
035import org.apache.hadoop.hbase.util.Bytes;
036import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
037import org.apache.hadoop.hbase.util.MD5Hash;
038import org.junit.ClassRule;
039import org.junit.Rule;
040import org.junit.Test;
041import org.junit.experimental.categories.Category;
042
043import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
044
045import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
046import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
047
048@Category({ ClientTests.class, SmallTests.class })
049public class TestRegionInfoBuilder {
050
051  @ClassRule
052  public static final HBaseClassTestRule CLASS_RULE =
053    HBaseClassTestRule.forClass(TestRegionInfoBuilder.class);
054
055  @Rule
056  public TableNameTestRule name = new TableNameTestRule();
057
058  @Test
059  public void testBuilder() {
060    TableName tn = TableName.valueOf("test");
061    RegionInfoBuilder builder = RegionInfoBuilder.newBuilder(tn);
062    byte[] startKey = Bytes.toBytes("a");
063    builder.setStartKey(startKey);
064    byte[] endKey = Bytes.toBytes("z");
065    builder.setEndKey(endKey);
066    int regionId = 1;
067    builder.setRegionId(1);
068    int replicaId = 2;
069    builder.setReplicaId(replicaId);
070    boolean offline = true;
071    builder.setOffline(offline);
072    boolean isSplit = true;
073    builder.setSplit(isSplit);
074    RegionInfo ri = builder.build();
075
076    assertEquals(tn, ri.getTable());
077    assertArrayEquals(startKey, ri.getStartKey());
078    assertArrayEquals(endKey, ri.getEndKey());
079    assertEquals(regionId, ri.getRegionId());
080    assertEquals(replicaId, ri.getReplicaId());
081    assertEquals(offline, ri.isOffline());
082    assertEquals(isSplit, ri.isSplit());
083  }
084
085  @Test
086  public void testPb() throws DeserializationException {
087    RegionInfo ri = RegionInfoBuilder.FIRST_META_REGIONINFO;
088    byte[] bytes = RegionInfo.toByteArray(ri);
089    RegionInfo pbri = RegionInfo.parseFrom(bytes);
090    assertTrue(RegionInfo.COMPARATOR.compare(ri, pbri) == 0);
091  }
092
093  @Test
094  public void testCreateRegionInfoName() throws Exception {
095    final TableName tn = name.getTableName();
096    String startKey = "startkey";
097    final byte[] sk = Bytes.toBytes(startKey);
098    String id = "id";
099
100    // old format region name
101    byte[] name = RegionInfo.createRegionName(tn, sk, id, false);
102    String nameStr = Bytes.toString(name);
103    assertEquals(tn + "," + startKey + "," + id, nameStr);
104
105    // new format region name.
106    String md5HashInHex = MD5Hash.getMD5AsHex(name);
107    assertEquals(RegionInfo.MD5_HEX_LENGTH, md5HashInHex.length());
108    name = RegionInfo.createRegionName(tn, sk, id, true);
109    nameStr = Bytes.toString(name);
110    assertEquals(tn + "," + startKey + "," + id + "." + md5HashInHex + ".", nameStr);
111  }
112
113  @Test
114  public void testContainsRange() {
115    TableDescriptor tableDesc = TableDescriptorBuilder.newBuilder(name.getTableName()).build();
116    RegionInfo ri = RegionInfoBuilder.newBuilder(tableDesc.getTableName())
117      .setStartKey(Bytes.toBytes("a")).setEndKey(Bytes.toBytes("g")).build();
118    // Single row range at start of region
119    assertTrue(ri.containsRange(Bytes.toBytes("a"), Bytes.toBytes("a")));
120    // Fully contained range
121    assertTrue(ri.containsRange(Bytes.toBytes("b"), Bytes.toBytes("c")));
122    // Range overlapping start of region
123    assertTrue(ri.containsRange(Bytes.toBytes("a"), Bytes.toBytes("c")));
124    // Fully contained single-row range
125    assertTrue(ri.containsRange(Bytes.toBytes("c"), Bytes.toBytes("c")));
126    // Range that overlaps end key and hence doesn't fit
127    assertFalse(ri.containsRange(Bytes.toBytes("a"), Bytes.toBytes("g")));
128    // Single row range on end key
129    assertFalse(ri.containsRange(Bytes.toBytes("g"), Bytes.toBytes("g")));
130    // Single row range entirely outside
131    assertFalse(ri.containsRange(Bytes.toBytes("z"), Bytes.toBytes("z")));
132
133    // Degenerate range
134    try {
135      ri.containsRange(Bytes.toBytes("z"), Bytes.toBytes("a"));
136      fail("Invalid range did not throw IAE");
137    } catch (IllegalArgumentException iae) {
138    }
139  }
140
141  @Test
142  public void testContainsRangeForMetaTable() {
143    TableDescriptor tableDesc =
144      TableDescriptorBuilder.newBuilder(TableName.META_TABLE_NAME).build();
145    RegionInfo hri = RegionInfoBuilder.newBuilder(tableDesc.getTableName()).build();
146    byte[] startRow = HConstants.EMPTY_START_ROW;
147    byte[] row1 = Bytes.toBytes("a,a,0");
148    byte[] row2 = Bytes.toBytes("aaaaa,,1");
149    byte[] row3 = Bytes.toBytes("aaaaa,\u0000\u0000,2");
150    byte[] row4 = Bytes.toBytes("aaaaa,\u0001,3");
151    byte[] row5 = Bytes.toBytes("aaaaa,a,4");
152    byte[] row6 = Bytes.toBytes("aaaaa,\u1000,5");
153
154    // Single row range at start of region
155    assertTrue(hri.containsRange(startRow, startRow));
156    // Fully contained range
157    assertTrue(hri.containsRange(row1, row2));
158    assertTrue(hri.containsRange(row2, row3));
159    assertTrue(hri.containsRange(row3, row4));
160    assertTrue(hri.containsRange(row4, row5));
161    assertTrue(hri.containsRange(row5, row6));
162    // Range overlapping start of region
163    assertTrue(hri.containsRange(startRow, row2));
164    // Fully contained single-row range
165    assertTrue(hri.containsRange(row1, row1));
166    // Degenerate range
167    try {
168      hri.containsRange(row3, row2);
169      fail("Invalid range did not throw IAE");
170    } catch (IllegalArgumentException iae) {
171    }
172  }
173
174  @Test
175  public void testLastRegionCompare() {
176    TableDescriptor tableDesc = TableDescriptorBuilder.newBuilder(name.getTableName()).build();
177    RegionInfo rip = RegionInfoBuilder.newBuilder(tableDesc.getTableName())
178      .setStartKey(Bytes.toBytes("a")).setEndKey(new byte[0]).build();
179    RegionInfo ric = RegionInfoBuilder.newBuilder(tableDesc.getTableName())
180      .setStartKey(Bytes.toBytes("a")).setEndKey(Bytes.toBytes("b")).build();
181    assertTrue(RegionInfo.COMPARATOR.compare(rip, ric) > 0);
182  }
183
184  @Test
185  public void testMetaTables() {
186    assertTrue(RegionInfoBuilder.FIRST_META_REGIONINFO.isMetaRegion());
187  }
188
189  @Test
190  public void testComparator() {
191    final TableName tableName = name.getTableName();
192    byte[] empty = new byte[0];
193    RegionInfo older = RegionInfoBuilder.newBuilder(tableName).setStartKey(empty).setEndKey(empty)
194      .setSplit(false).setRegionId(0L).build();
195    RegionInfo newer = RegionInfoBuilder.newBuilder(tableName).setStartKey(empty).setEndKey(empty)
196      .setSplit(false).setRegionId(1L).build();
197    assertTrue(RegionInfo.COMPARATOR.compare(older, newer) < 0);
198    assertTrue(RegionInfo.COMPARATOR.compare(newer, older) > 0);
199    assertTrue(RegionInfo.COMPARATOR.compare(older, older) == 0);
200    assertTrue(RegionInfo.COMPARATOR.compare(newer, newer) == 0);
201  }
202
203  @Test
204  public void testRegionNameForRegionReplicas() throws Exception {
205    final TableName tn = name.getTableName();
206    String startKey = "startkey";
207    final byte[] sk = Bytes.toBytes(startKey);
208    String id = "id";
209
210    // assert with only the region name without encoding
211
212    // primary, replicaId = 0
213    byte[] name = RegionInfo.createRegionName(tn, sk, Bytes.toBytes(id), 0, false);
214    String nameStr = Bytes.toString(name);
215    assertEquals(tn + "," + startKey + "," + id, nameStr);
216
217    // replicaId = 1
218    name = RegionInfo.createRegionName(tn, sk, Bytes.toBytes(id), 1, false);
219    nameStr = Bytes.toString(name);
220    assertEquals(
221      tn + "," + startKey + "," + id + "_" + String.format(RegionInfo.REPLICA_ID_FORMAT, 1),
222      nameStr);
223
224    // replicaId = max
225    name = RegionInfo.createRegionName(tn, sk, Bytes.toBytes(id), 0xFFFF, false);
226    nameStr = Bytes.toString(name);
227    assertEquals(
228      tn + "," + startKey + "," + id + "_" + String.format(RegionInfo.REPLICA_ID_FORMAT, 0xFFFF),
229      nameStr);
230  }
231
232  @Test
233  public void testParseName() throws IOException {
234    final TableName tableName = name.getTableName();
235    byte[] startKey = Bytes.toBytes("startKey");
236    long regionId = EnvironmentEdgeManager.currentTime();
237    int replicaId = 42;
238
239    // test without replicaId
240    byte[] regionName = RegionInfo.createRegionName(tableName, startKey, regionId, false);
241
242    byte[][] fields = RegionInfo.parseRegionName(regionName);
243    assertArrayEquals(Bytes.toString(fields[0]), tableName.getName(), fields[0]);
244    assertArrayEquals(Bytes.toString(fields[1]), startKey, fields[1]);
245    assertArrayEquals(Bytes.toString(fields[2]), Bytes.toBytes(Long.toString(regionId)), fields[2]);
246    assertEquals(3, fields.length);
247
248    // test with replicaId
249    regionName = RegionInfo.createRegionName(tableName, startKey, regionId, replicaId, false);
250
251    fields = RegionInfo.parseRegionName(regionName);
252    assertArrayEquals(Bytes.toString(fields[0]), tableName.getName(), fields[0]);
253    assertArrayEquals(Bytes.toString(fields[1]), startKey, fields[1]);
254    assertArrayEquals(Bytes.toString(fields[2]), Bytes.toBytes(Long.toString(regionId)), fields[2]);
255    assertArrayEquals(Bytes.toString(fields[3]),
256      Bytes.toBytes(String.format(RegionInfo.REPLICA_ID_FORMAT, replicaId)), fields[3]);
257  }
258
259  @Test
260  public void testConvert() {
261    final TableName tableName =
262      TableName.valueOf("ns1:" + name.getTableName().getQualifierAsString());
263    byte[] startKey = Bytes.toBytes("startKey");
264    byte[] endKey = Bytes.toBytes("endKey");
265    boolean split = false;
266    long regionId = EnvironmentEdgeManager.currentTime();
267    int replicaId = 42;
268
269    RegionInfo ri = RegionInfoBuilder.newBuilder(tableName).setStartKey(startKey).setEndKey(endKey)
270      .setSplit(split).setRegionId(regionId).setReplicaId(replicaId).build();
271
272    // convert two times, compare
273    RegionInfo convertedRi = ProtobufUtil.toRegionInfo(ProtobufUtil.toRegionInfo(ri));
274
275    assertEquals(ri, convertedRi);
276
277    // test convert RegionInfo without replicaId
278    HBaseProtos.RegionInfo info = HBaseProtos.RegionInfo.newBuilder()
279      .setTableName(HBaseProtos.TableName.newBuilder()
280        .setQualifier(UnsafeByteOperations.unsafeWrap(tableName.getQualifier()))
281        .setNamespace(UnsafeByteOperations.unsafeWrap(tableName.getNamespace())).build())
282      .setStartKey(UnsafeByteOperations.unsafeWrap(startKey))
283      .setEndKey(UnsafeByteOperations.unsafeWrap(endKey)).setSplit(split).setRegionId(regionId)
284      .build();
285
286    convertedRi = ProtobufUtil.toRegionInfo(info);
287    RegionInfo expectedRi = RegionInfoBuilder.newBuilder(tableName).setStartKey(startKey)
288      .setEndKey(endKey).setSplit(split).setRegionId(regionId).setReplicaId(0).build();
289
290    assertEquals(expectedRi, convertedRi);
291  }
292
293  private void assertRegionNameNotEquals(RegionInfo expected, RegionInfo actual) {
294    assertNotEquals(expected.getRegionNameAsString(), actual.getRegionNameAsString());
295    assertNotEquals(expected.getEncodedName(), actual.getEncodedName());
296  }
297
298  private void assertRegionNameEquals(RegionInfo expected, RegionInfo actual) {
299    assertEquals(expected.getRegionNameAsString(), actual.getRegionNameAsString());
300    assertEquals(expected.getEncodedName(), actual.getEncodedName());
301  }
302
303  @Test
304  public void testNewBuilderWithRegionInfo() {
305    RegionInfo ri = RegionInfoBuilder.newBuilder(name.getTableName()).build();
306    assertEquals(ri, RegionInfoBuilder.newBuilder(ri).build());
307
308    // make sure that the region name and encoded name are changed, see HBASE-24500 for more
309    // details.
310    assertRegionNameNotEquals(ri,
311      RegionInfoBuilder.newBuilder(ri).setStartKey(new byte[1]).build());
312    assertRegionNameNotEquals(ri,
313      RegionInfoBuilder.newBuilder(ri).setRegionId(ri.getRegionId() + 1).build());
314    assertRegionNameNotEquals(ri, RegionInfoBuilder.newBuilder(ri).setReplicaId(1).build());
315
316    // these fields are not in region name
317    assertRegionNameEquals(ri, RegionInfoBuilder.newBuilder(ri).setEndKey(new byte[1]).build());
318    assertRegionNameEquals(ri, RegionInfoBuilder.newBuilder(ri).setSplit(true).build());
319    assertRegionNameEquals(ri, RegionInfoBuilder.newBuilder(ri).setOffline(true).build());
320  }
321}