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