From 3f62d2bdcd31b885e1dc0b5173185d8451597ed2 Mon Sep 17 00:00:00 2001 From: brandboat Date: Tue, 5 Sep 2017 00:00:19 +0800 Subject: [PATCH] HBASE-17980 Any HRegionInfo we give out should be immutable --- .../java/org/apache/hadoop/hbase/HRegionInfo.java | 121 +- .../java/org/apache/hadoop/hbase/client/Admin.java | 27 + .../org/apache/hadoop/hbase/client/HBaseAdmin.java | 37 +- .../hadoop/hbase/client/ImmutableHRegionInfo.java | 49 + .../org/apache/hadoop/hbase/client/RegionInfo.java | 205 ++++ .../hadoop/hbase/client/RegionInfoBuilder.java | 1252 ++++++++++++++++++++ .../hadoop/hbase/client/RegionReplicaUtil.java | 32 + .../hbase/client/UnmodifyableHRegionInfo.java | 51 - .../hadoop/hbase/shaded/protobuf/ProtobufUtil.java | 68 ++ .../hbase/client/TestImmutableHRegionInfo.java | 61 + .../org/apache/hadoop/hbase/MetaMockingUtil.java | 13 - .../hbase/regionserver/TestRegionInfoBuilder.java | 407 +++++++ 12 files changed, 2246 insertions(+), 77 deletions(-) create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/client/ImmutableHRegionInfo.java create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionInfo.java create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionInfoBuilder.java delete mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/client/UnmodifyableHRegionInfo.java create mode 100644 hbase-client/src/test/java/org/apache/hadoop/hbase/client/TestImmutableHRegionInfo.java create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionInfoBuilder.java diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/HRegionInfo.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/HRegionInfo.java index d470ffa..2331db2 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/HRegionInfo.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/HRegionInfo.java @@ -29,6 +29,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.client.RegionInfo; import org.apache.hadoop.hbase.client.RegionReplicaUtil; import org.apache.hadoop.hbase.KeyValue.KVComparator; import org.apache.hadoop.hbase.exceptions.DeserializationException; @@ -36,7 +37,6 @@ import org.apache.hadoop.hbase.master.RegionState; import org.apache.hadoop.hbase.shaded.com.google.protobuf.UnsafeByteOperations; import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; -import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.RegionInfo; import org.apache.hadoop.hbase.util.ByteArrayHashKey; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.HashKey; @@ -74,9 +74,12 @@ import org.apache.hadoop.util.StringUtils; * correspond to multiple HRegionInfo's. These HRI's share the same fields however except the * replicaId field. If the replicaId is not set, it defaults to 0, which is compatible with the * previous behavior of a range corresponding to 1 region. + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0. + * use {@link RegionInfoBuilder} to build {@link RegionInfo}. */ +@Deprecated @InterfaceAudience.Public -public class HRegionInfo implements Comparable { +public class HRegionInfo implements RegionInfo, Comparable { private static final Log LOG = LogFactory.getLog(HRegionInfo.class); @@ -142,7 +145,10 @@ public class HRegionInfo implements Comparable { /** * @param regionName * @return the encodedName + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#encodeRegionName(byte[])}. */ + @Deprecated public static String encodeRegionName(final byte [] regionName) { String encodedName; if (hasEncodedName(regionName)) { @@ -168,6 +174,11 @@ public class HRegionInfo implements Comparable { return prettyPrint(this.getEncodedName()); } + /** + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#getShortNameToLog(RegionInfo...)}. + */ + @Deprecated public static String getShortNameToLog(HRegionInfo...hris) { return getShortNameToLog(Arrays.asList(hris)); } @@ -175,7 +186,10 @@ public class HRegionInfo implements Comparable { /** * @return Return a String of short, printable names for hris * (usually encoded name) for us logging. + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#getShortNameToLog(List}<{@link RegionInfo}>)}. */ + @Deprecated public static String getShortNameToLog(final List hris) { return hris.stream().map(hri -> hri.getShortNameToLog()). collect(Collectors.toList()).toString(); @@ -186,7 +200,10 @@ public class HRegionInfo implements Comparable { * @param encodedRegionName The encoded regionname. * @return hbase:meta if passed 1028785192 else returns * encodedRegionName + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#prettyPrint(String)}. */ + @Deprecated public static String prettyPrint(final String encodedRegionName) { if (encodedRegionName.equals("1028785192")) { return encodedRegionName + "/hbase:meta"; @@ -232,7 +249,6 @@ public class HRegionInfo implements Comparable { this.hashCode = result; } - /** * Private constructor used constructing HRegionInfo for the * first meta regions @@ -354,8 +370,8 @@ public class HRegionInfo implements Comparable { this.startKey = other.getStartKey(); this.hashCode = other.hashCode(); this.encodedName = other.getEncodedName(); - this.tableName = other.tableName; - this.replicaId = other.replicaId; + this.tableName = other.getTable(); + this.replicaId = other.getReplicaId(); } public HRegionInfo(HRegionInfo other, int replicaId) { @@ -372,7 +388,10 @@ public class HRegionInfo implements Comparable { * @param newFormat should we create the region name in the new format * (such that it contains its encoded name?). * @return Region name made of passed tableName, startKey and id + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#createRegionName(TableName, byte[], long, boolean)}. */ + @Deprecated public static byte [] createRegionName(final TableName tableName, final byte [] startKey, final long regionid, boolean newFormat) { return createRegionName(tableName, startKey, Long.toString(regionid), newFormat); @@ -386,7 +405,10 @@ public class HRegionInfo implements Comparable { * @param newFormat should we create the region name in the new format * (such that it contains its encoded name?). * @return Region name made of passed tableName, startKey and id + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#createRegionName(TableName, byte[], String, boolean)}. */ + @Deprecated public static byte [] createRegionName(final TableName tableName, final byte [] startKey, final String id, boolean newFormat) { return createRegionName(tableName, startKey, Bytes.toBytes(id), newFormat); @@ -401,7 +423,10 @@ public class HRegionInfo implements Comparable { * @param newFormat should we create the region name in the new format * (such that it contains its encoded name?). * @return Region name made of passed tableName, startKey, id and replicaId + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#createRegionName(TableName, byte[], long, int, boolean)}. */ + @Deprecated public static byte [] createRegionName(final TableName tableName, final byte [] startKey, final long regionid, int replicaId, boolean newFormat) { return createRegionName(tableName, startKey, Bytes.toBytes(Long.toString(regionid)), @@ -416,7 +441,10 @@ public class HRegionInfo implements Comparable { * @param newFormat should we create the region name in the new format * (such that it contains its encoded name?). * @return Region name made of passed tableName, startKey and id + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#createRegionName(TableName, byte[], byte[], boolean)}. */ + @Deprecated public static byte [] createRegionName(final TableName tableName, final byte [] startKey, final byte [] id, boolean newFormat) { return createRegionName(tableName, startKey, id, DEFAULT_REPLICA_ID, newFormat); @@ -429,7 +457,10 @@ public class HRegionInfo implements Comparable { * @param replicaId * @param newFormat should we create the region name in the new format * @return Region name made of passed tableName, startKey, id and replicaId + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#createRegionName(TableName, byte[], byte[], int, boolean)}. */ + @Deprecated public static byte [] createRegionName(final TableName tableName, final byte [] startKey, final byte [] id, final int replicaId, boolean newFormat) { int len = tableName.getName().length + 2 + id.length + @@ -496,7 +527,10 @@ public class HRegionInfo implements Comparable { * Gets the table name from the specified region name. * @param regionName to extract the table name from * @return Table name + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#getTable(byte[])}. */ + @Deprecated public static TableName getTable(final byte [] regionName) { int offset = -1; for (int i = 0; i < regionName.length; i++) { @@ -514,7 +548,10 @@ public class HRegionInfo implements Comparable { * Gets the start key from the specified region name. * @param regionName * @return Start key. + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#getStartKey(byte[])}. */ + @Deprecated public static byte[] getStartKey(final byte[] regionName) throws IOException { return parseRegionName(regionName)[1]; } @@ -524,7 +561,10 @@ public class HRegionInfo implements Comparable { * @param regionName * @return Array of byte[] containing tableName, startKey and id * @throws IOException + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#parseRegionName(byte[])}. */ + @Deprecated public static byte [][] parseRegionName(final byte [] regionName) throws IOException { // Region name is of the format: @@ -595,6 +635,15 @@ public class HRegionInfo implements Comparable { return elements; } + /** + * + * @param regionName + * @return + * @throws IOException + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#isEncodedRegionName(byte[])}. + */ + @Deprecated public static boolean isEncodedRegionName(byte[] regionName) throws IOException { try { HRegionInfo.parseRegionName(regionName); @@ -883,7 +932,7 @@ public class HRegionInfo implements Comparable { * * @return the converted RegionInfo */ - RegionInfo convert() { + HBaseProtos.RegionInfo convert() { return convert(this); } @@ -892,10 +941,13 @@ public class HRegionInfo implements Comparable { * * @param info the HRegionInfo to convert * @return the converted RegionInfo + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil#toProtoRegionInfo(org.apache.hadoop.hbase.client.RegionInfo})}. */ - public static RegionInfo convert(final HRegionInfo info) { + @Deprecated + public static HBaseProtos.RegionInfo convert(final HRegionInfo info) { if (info == null) return null; - RegionInfo.Builder builder = RegionInfo.newBuilder(); + HBaseProtos.RegionInfo.Builder builder = HBaseProtos.RegionInfo.newBuilder(); builder.setTableName(ProtobufUtil.toProtoTableName(info.getTable())); builder.setRegionId(info.getRegionId()); if (info.getStartKey() != null) { @@ -914,9 +966,12 @@ public class HRegionInfo implements Comparable { * Convert a RegionInfo to a HRegionInfo * * @param proto the RegionInfo to convert - * @return the converted HRegionInfho + * @return the converted HRegionInfo + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil#toRegionInfo(HBaseProtos.RegionInfo})}. */ - public static HRegionInfo convert(final RegionInfo proto) { + @Deprecated + public static HRegionInfo convert(final HBaseProtos.RegionInfo proto) { if (proto == null) return null; TableName tableName = ProtobufUtil.toTableName(proto.getTableName()); @@ -951,7 +1006,10 @@ public class HRegionInfo implements Comparable { /** * @return This instance serialized as protobuf w/ a magic pb prefix. * @see #parseFrom(byte[]) + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#toByteArray(RegionInfo)}. */ + @Deprecated public byte [] toByteArray() { byte [] bytes = convert().toByteArray(); return ProtobufUtil.prependPBMagic(bytes); @@ -961,7 +1019,10 @@ public class HRegionInfo implements Comparable { * @return A deserialized {@link HRegionInfo} * or null if we failed deserialize or passed bytes null * @see #toByteArray() + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#parseFromOrNull(byte[])}. */ + @Deprecated public static HRegionInfo parseFromOrNull(final byte [] bytes) { if (bytes == null) return null; return parseFromOrNull(bytes, 0, bytes.length); @@ -971,7 +1032,10 @@ public class HRegionInfo implements Comparable { * @return A deserialized {@link HRegionInfo} or null * if we failed deserialize or passed bytes null * @see #toByteArray() + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#parseFromOrNull(byte[], int, int)}. */ + @Deprecated public static HRegionInfo parseFromOrNull(final byte [] bytes, int offset, int len) { if (bytes == null || len <= 0) return null; try { @@ -986,6 +1050,8 @@ public class HRegionInfo implements Comparable { * @return A deserialized {@link HRegionInfo} * @throws DeserializationException * @see #toByteArray() + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#parseFrom(byte[])}. */ public static HRegionInfo parseFrom(final byte [] bytes) throws DeserializationException { if (bytes == null) return null; @@ -999,7 +1065,10 @@ public class HRegionInfo implements Comparable { * @return A deserialized {@link HRegionInfo} * @throws DeserializationException * @see #toByteArray() + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#parseFrom(byte[], int, int)}. */ + @Deprecated public static HRegionInfo parseFrom(final byte [] bytes, int offset, int len) throws DeserializationException { if (ProtobufUtil.isPBMagicPrefix(bytes, offset, len)) { @@ -1023,7 +1092,10 @@ public class HRegionInfo implements Comparable { * @return This instance serialized as a delimited protobuf w/ a magic pb prefix. * @throws IOException * @see #toByteArray() + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#toDelimitedByteArray(RegionInfo)}. */ + @Deprecated public byte [] toDelimitedByteArray() throws IOException { return ProtobufUtil.toDelimitedByteArray(convert()); } @@ -1034,7 +1106,10 @@ public class HRegionInfo implements Comparable { * @param state * @param conf * @return descriptive string + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#getDescriptiveNameFromRegionStateForDisplay(RegionState, Configuration)}. */ + @Deprecated public static String getDescriptiveNameFromRegionStateForDisplay(RegionState state, Configuration conf) { if (conf.getBoolean(DISPLAY_KEYS_KEY, true)) return state.toDescriptiveString(); @@ -1049,7 +1124,10 @@ public class HRegionInfo implements Comparable { * @param hri * @param conf * @return the endkey + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#getEndKeyForDisplay(RegionInfo, Configuration)}. */ + @Deprecated public static byte[] getEndKeyForDisplay(HRegionInfo hri, Configuration conf) { boolean displayKey = conf.getBoolean(DISPLAY_KEYS_KEY, true); if (displayKey) return hri.getEndKey(); @@ -1061,7 +1139,10 @@ public class HRegionInfo implements Comparable { * @param hri * @param conf * @return the startkey + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#getStartKeyForDisplay(RegionInfo, Configuration)}. */ + @Deprecated public static byte[] getStartKeyForDisplay(HRegionInfo hri, Configuration conf) { boolean displayKey = conf.getBoolean(DISPLAY_KEYS_KEY, true); if (displayKey) return hri.getStartKey(); @@ -1073,7 +1154,10 @@ public class HRegionInfo implements Comparable { * @param hri * @param conf * @return region name as String + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#getRegionNameAsStringForDisplay(RegionInfo, Configuration)}. */ + @Deprecated public static String getRegionNameAsStringForDisplay(HRegionInfo hri, Configuration conf) { return Bytes.toStringBinary(getRegionNameForDisplay(hri, conf)); } @@ -1083,7 +1167,10 @@ public class HRegionInfo implements Comparable { * @param hri * @param conf * @return region name bytes + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#getRegionNameForDisplay(RegionInfo, Configuration)}. */ + @Deprecated public static byte[] getRegionNameForDisplay(HRegionInfo hri, Configuration conf) { boolean displayKey = conf.getBoolean(DISPLAY_KEYS_KEY, true); if (displayKey || hri.getTable().equals(TableName.META_TABLE_NAME)) { @@ -1132,7 +1219,10 @@ public class HRegionInfo implements Comparable { * @param in * @return An instance of HRegionInfo. * @throws IOException + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#parseFrom(DataInputStream)}. */ + @Deprecated public static HRegionInfo parseFrom(final DataInputStream in) throws IOException { // I need to be able to move back in the stream if this is not a pb serialization so I can // do the Writable decoding instead. @@ -1161,7 +1251,10 @@ public class HRegionInfo implements Comparable { * @return This instance serialized as a delimited protobuf w/ a magic pb prefix. * @throws IOException * @see #toByteArray() + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#toDelimitedByteArray(RegionInfo...)}. */ + @Deprecated public static byte[] toDelimitedByteArray(HRegionInfo... infos) throws IOException { byte[][] bytes = new byte[infos.length][]; int size = 0; @@ -1186,7 +1279,10 @@ public class HRegionInfo implements Comparable { * @param offset the start offset into the byte[] buffer * @param length how far we should read into the byte[] buffer * @return All the hregioninfos that are in the byte array. Keeps reading till we hit the end. + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#parseDelimitedFrom(byte[], int, int)}. */ + @Deprecated public static List parseDelimitedFrom(final byte[] bytes, final int offset, final int length) throws IOException { if (bytes == null) { @@ -1211,7 +1307,10 @@ public class HRegionInfo implements Comparable { * @param regionA * @param regionB * @return true if two regions are adjacent + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link org.apache.hadoop.hbase.client.RegionInfoBuilder#areAdjacent(RegionInfo, RegionInfo)}. */ + @Deprecated public static boolean areAdjacent(HRegionInfo regionA, HRegionInfo regionB) { if (regionA == null || regionB == null) { throw new IllegalArgumentException( @@ -1228,4 +1327,4 @@ public class HRegionInfo implements Comparable { } return false; } -} +} \ No newline at end of file diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java index b19c107..6b32dd6 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java @@ -801,10 +801,23 @@ public interface Admin extends Abortable, Closeable { /** * Get all the online regions on a region server. + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * (HBASE-17980). + * Use {@link #getRegions(final ServerName sn)}. */ + @Deprecated List getOnlineRegions(final ServerName sn) throws IOException; /** + * Get all the online regions on a region server. + * + * @param sn Server name. + * @return List of {@link RegionInfo} + * @throws java.io.IOException + */ + List getRegions(final ServerName sn) throws IOException; + + /** * Flush a table. Synchronous operation. * * @param tableName table to flush @@ -1383,10 +1396,24 @@ public interface Admin extends Abortable, Closeable { * @param tableName the name of the table * @return List of {@link HRegionInfo}. * @throws IOException + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * (HBASE-17980). + * Use {@link #getRegions(TableName)}. */ + @Deprecated List getTableRegions(final TableName tableName) throws IOException; + /** + * Get the regions of a given table. + * + * @param tableName the name of the table + * @return List of {@link RegionInfo}. + * @throws IOException + */ + List getRegions(final TableName tableName) + throws IOException; + @Override void close() throws IOException; diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java index c699676..106065d 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java @@ -397,6 +397,16 @@ public class HBaseAdmin implements Admin { }); } + @Override + public List getRegions(final ServerName sn) throws IOException { + return getOnlineRegions(sn).stream().collect(Collectors.toList()); + } + + @Override + public List getRegions(final TableName tableName) throws IOException { + return getTableRegions(tableName).stream().collect(Collectors.toList()); + } + private static class AbortProcedureFuture extends ProcedureFuture { private boolean isAbortInProgress; @@ -1143,12 +1153,24 @@ public class HBaseAdmin implements Admin { unassign(hri.getRegionName(), true); } + /** + * + * @param sn + * @return List of {@link HRegionInfo}. + * @throws IOException + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link #getRegions(ServerName)}. + */ + @Deprecated @Override public List getOnlineRegions(final ServerName sn) throws IOException { AdminService.BlockingInterface admin = this.connection.getAdmin(sn); // TODO: There is no timeout on this controller. Set one! HBaseRpcController controller = rpcControllerFactory.newController(); - return ProtobufUtil.getOnlineRegions(controller, admin); + List onlineRegions = ProtobufUtil.getOnlineRegions(controller, admin); + return onlineRegions == null ? null : onlineRegions.stream() + .map(hri -> new ImmutableHRegionInfo(hri)) + .collect(Collectors.toList()); } @Override @@ -2353,6 +2375,15 @@ public class HBaseAdmin implements Admin { } } + /** + * + * @param tableName + * @return List of {@link HRegionInfo}. + * @throws IOException + * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0 + * Use {@link #getRegions(TableName)}. + */ + @Deprecated @Override public List getTableRegions(final TableName tableName) throws IOException { @@ -2369,7 +2400,9 @@ public class HBaseAdmin implements Admin { } finally { zookeeper.close(); } - return regions; + return regions == null ? null : regions.stream() + .map(hri -> new ImmutableHRegionInfo(hri)) + .collect(Collectors.toList()); } @Override diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ImmutableHRegionInfo.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ImmutableHRegionInfo.java new file mode 100644 index 0000000..0111e52 --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ImmutableHRegionInfo.java @@ -0,0 +1,49 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.client; + +import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.classification.InterfaceAudience; + +/** + * Read-only Region info. + */ +@Deprecated // deprecated for hbase 2.0, remove for hbase 3.0. see HRegionInfo. +@InterfaceAudience.Private +public class ImmutableHRegionInfo extends HRegionInfo { + + /* + * Creates an immutable copy of an HRegionInfo. + * + * @param other + */ + public ImmutableHRegionInfo(HRegionInfo other) { + super(other); + } + + @Override + public void setSplit(boolean split) { + throw new UnsupportedOperationException("HRegionInfo is read-only"); + } + + @Override + public void setOffline(boolean offline) { + throw new UnsupportedOperationException("HRegionInfo is read-only"); + } +} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionInfo.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionInfo.java new file mode 100644 index 0000000..330860b --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionInfo.java @@ -0,0 +1,205 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.client; + +import java.util.Comparator; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.util.Bytes; + +/** + * Information about a region. A region is a range of keys in the whole keyspace + * of a table, an identifier (a timestamp) for differentiating between subset + * ranges (after region split) and a replicaId for differentiating the instance + * for the same range and some status information about the region. + * + * The region has a unique name which consists of the following fields: + *
    + *
  • tableName : The name of the table
  • + *
  • startKey : The startKey for the region.
  • + *
  • regionId : A timestamp when the region is created.
  • + *
  • replicaId : An id starting from 0 to differentiate replicas of the + * same region range but hosted in separated servers. The same region range can + * be hosted in multiple locations.
  • + *
  • encodedName : An MD5 encoded string for the region name.
  • + *
+ * + *
Other than the fields in the region name, region info contains: + *
    + *
  • endKey : the endKey for the region (exclusive)
  • + *
  • split : Whether the region is split
  • + *
  • offline : Whether the region is offline
  • + *
+ * + */ +@InterfaceAudience.Public +public interface RegionInfo { + + @InterfaceAudience.Private + Comparator COMPARATOR + = (RegionInfo lhs, RegionInfo rhs) -> { + if (rhs == null) { + return 1; + } + + // Are regions of same table? + int result = lhs.getTable().compareTo(rhs.getTable()); + if (result != 0) { + return result; + } + + // Compare start keys. + result = Bytes.compareTo(lhs.getStartKey(), rhs.getStartKey()); + if (result != 0) { + return result; + } + + // Compare end keys. + result = Bytes.compareTo(lhs.getEndKey(), rhs.getEndKey()); + + if (result != 0) { + if (lhs.getStartKey().length != 0 + && lhs.getEndKey().length == 0) { + return 1; // this is last region + } + if (rhs.getStartKey().length != 0 + && rhs.getEndKey().length == 0) { + return -1; // o is the last region + } + return result; + } + + // regionId is usually milli timestamp -- this defines older stamps + // to be "smaller" than newer stamps in sort order. + if (lhs.getRegionId() > rhs.getRegionId()) { + return 1; + } else if (lhs.getRegionId() < rhs.getRegionId()) { + return -1; + } + + int replicaDiff = lhs.getReplicaId() - rhs.getReplicaId(); + if (replicaDiff != 0) return replicaDiff; + + if (lhs.isOffline() == rhs.isOffline()) + return 0; + if (lhs.isOffline() == true) return -1; + + return 1; + }; + + /** + * @return Return a short, printable name for this region + * (usually encoded name) for us logging. + */ + String getShortNameToLog(); + + /** + * @return the regionId. + */ + long getRegionId(); + + /** + * @return the regionName as an array of bytes. + * @see #getRegionNameAsString() + */ + byte [] getRegionName(); + + /** + * @return Region name as a String for use in logging, etc. + */ + String getRegionNameAsString(); + + /** + * @return the encoded region name. + */ + String getEncodedName(); + + /** + * @return the encoded region name as an array of bytes. + */ + byte [] getEncodedNameAsBytes(); + + /** + * @return the startKey. + */ + byte [] getStartKey(); + + /** + * @return the endKey. + */ + byte [] getEndKey(); + + /** + * @return current table name of the region + */ + TableName getTable(); + + /** + * @return returns region replica id + */ + int getReplicaId(); + + /** + * @return True if has been split and has daughters. + */ + boolean isSplit(); + + /** + * @return True if this region is offline. + */ + boolean isOffline(); + + /** + * @return True if this is a split parent region. + */ + boolean isSplitParent(); + + /** + * @return true if this region is from hbase:meta. + */ + boolean isMetaTable(); + + /** + * @return true if this region is from a system table. + */ + boolean isSystemTable(); + + /** + * @return true if this region is a meta region. + */ + boolean isMetaRegion(); + + /** + * @param rangeStartKey + * @param rangeEndKey + * @return true if the given inclusive range of rows is fully contained + * by this region. For example, if the region is foo,a,g and this is + * passed ["b","c"] or ["a","c"] it will return true, but if this is passed + * ["b","z"] it will return false. + * @throws IllegalArgumentException if the range passed is invalid (ie. end < start) + */ + boolean containsRange(byte[] rangeStartKey, byte[] rangeEndKey); + + /** + * @param row + * @return true if the given row falls in this region. + */ + boolean containsRow(byte[] row); + +} \ No newline at end of file diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionInfoBuilder.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionInfoBuilder.java new file mode 100644 index 0000000..e3cae16 --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionInfoBuilder.java @@ -0,0 +1,1252 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.client; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.KeyValue; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.exceptions.DeserializationException; +import org.apache.hadoop.hbase.master.RegionState; +import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; +import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; +import org.apache.hadoop.hbase.util.ByteArrayHashKey; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.HashKey; +import org.apache.hadoop.hbase.util.JenkinsHash; +import org.apache.hadoop.hbase.util.MD5Hash; +import org.apache.hadoop.io.DataInputBuffer; +import org.apache.hadoop.util.StringUtils; + +@InterfaceAudience.Private +public class RegionInfoBuilder { + + private static final Log LOG = LogFactory.getLog(RegionInfoBuilder.class); + + /** + * Separator used to demarcate the encodedName in a region name + * in the new format. See description on new format above. + */ + private static final int ENC_SEPARATOR = '.'; + public static final int MD5_HEX_LENGTH = 32; + + /** A non-capture group so that this can be embedded. */ + public static final String ENCODED_REGION_NAME_REGEX = "(?:[a-f0-9]+)"; + + /** + * to keep appended int's sorted in string format. Only allows 2 bytes + * to be sorted for replicaId. + */ + public static final String REPLICA_ID_FORMAT = "%04X"; + + public static final byte REPLICA_ID_DELIMITER = (byte)'_'; + + private static final int MAX_REPLICA_ID = 0xFFFF; + public static final int DEFAULT_REPLICA_ID = 0; + public static final String INVALID_REGION_NAME_FORMAT_MESSAGE = "Invalid regionName format"; + //TODO: Move NO_HASH to HStoreFile which is really the only place it is used. + public static final String NO_HASH = null; + + final static String DISPLAY_KEYS_KEY = "hbase.display.keys"; + public final static byte[] HIDDEN_END_KEY = Bytes.toBytes("hidden-end-key"); + public final static byte[] HIDDEN_START_KEY = Bytes.toBytes("hidden-start-key"); + + /** RegionInfo for first meta region */ + // TODO: How come Meta regions still do not have encoded region names? Fix. + public static final RegionInfo FIRST_META_REGIONINFO = + new MutableRegionInfo(1L, TableName.META_TABLE_NAME); + + private MutableRegionInfo content = null; + private boolean isMetaRegion = false; + + public static RegionInfoBuilder newBuilder(TableName tableName) { + return newBuilder(tableName, false); + } + + public static RegionInfoBuilder newBuilder(TableName tableName, boolean isMetaRegion) { + return new RegionInfoBuilder(tableName, isMetaRegion); + } + + public static RegionInfoBuilder newBuilder(RegionInfo regionInfo) { + return new RegionInfoBuilder(regionInfo); + } + + private RegionInfoBuilder(TableName tableName, boolean isMetaRegion) { + this.content = new MutableRegionInfo(tableName); + this.isMetaRegion = isMetaRegion; + } + + private RegionInfoBuilder(RegionInfo regionInfo) { + this.content = new MutableRegionInfo(regionInfo); + } + + public RegionInfoBuilder setStartKey(byte[] startKey) { + content.setStartKey(startKey); + return this; + } + + public RegionInfoBuilder setEndKey(byte[] endKey) { + content.setEndKey(endKey); + return this; + } + + public RegionInfoBuilder setRegionId(long regionId) { + content.setRegionId(regionId); + return this; + } + + public RegionInfoBuilder setReplicaId(int replicaId) { + content.setReplicaId(replicaId); + return this; + } + + public RegionInfoBuilder setSplit(boolean isSplit) { + content.setSplit(isSplit); + return this; + } + + public RegionInfoBuilder setOffline(boolean isOffline) { + content.setOffline(isOffline); + return this; + } + + public RegionInfo build() { + return new MutableRegionInfo(content, isMetaRegion); + } + + /** + * Does region name contain its encoded name? + * @param regionName region name + * @return boolean indicating if this a new format region + * name which contains its encoded name. + */ + private static boolean hasEncodedName(final byte[] regionName) { + // check if region name ends in ENC_SEPARATOR + if ((regionName.length >= 1) + && (regionName[regionName.length - 1] == ENC_SEPARATOR)) { + // region name is new format. it contains the encoded name. + return true; + } + return false; + } + + /** + * @param regionName + * @return the encodedName + */ + public static String encodeRegionName(final byte [] regionName) { + String encodedName; + if (hasEncodedName(regionName)) { + // region is in new format: + // ,,/encodedName/ + encodedName = Bytes.toString(regionName, + regionName.length - MD5_HEX_LENGTH - 1, + MD5_HEX_LENGTH); + } else { + // old format region name. First hbase:meta region also + // use this format.EncodedName is the JenkinsHash value. + HashKey key = new ByteArrayHashKey(regionName, 0, regionName.length); + int hashVal = Math.abs(JenkinsHash.getInstance().hash(key, 0)); + encodedName = String.valueOf(hashVal); + } + return encodedName; + } + + public static String getShortNameToLog(RegionInfo...hris) { + return getShortNameToLog(Arrays.asList(hris)); + } + + /** + * @param ris + * @return Return a String of short, printable names for hris + * (usually encoded name) for us logging. + */ + public static String getShortNameToLog(final List ris) { + return ris.stream().map(ri -> ri.getShortNameToLog()). + collect(Collectors.toList()).toString(); + } + + /** + * Use logging. + * @param encodedRegionName The encoded regionname. + * @return hbase:meta if passed 1028785192 else returns + * encodedRegionName + */ + public static String prettyPrint(final String encodedRegionName) { + if (encodedRegionName.equals("1028785192")) { + return encodedRegionName + "/hbase:meta"; + } + return encodedRegionName; + } + + /** + * Make a region name of passed parameters. + * @param tableName + * @param startKey Can be null + * @param regionid Region id (Usually timestamp from when region was created). + * @param newFormat should we create the region name in the new format + * (such that it contains its encoded name?). + * @return Region name made of passed tableName, startKey and id + */ + public static byte [] createRegionName(final TableName tableName, + final byte [] startKey, final long regionid, boolean newFormat) { + return createRegionName(tableName, startKey, Long.toString(regionid), newFormat); + } + + /** + * Make a region name of passed parameters. + * @param tableName + * @param startKey Can be null + * @param id Region id (Usually timestamp from when region was created). + * @param newFormat should we create the region name in the new format + * (such that it contains its encoded name?). + * @return Region name made of passed tableName, startKey and id + */ + public static byte [] createRegionName(final TableName tableName, + final byte [] startKey, final String id, boolean newFormat) { + return createRegionName(tableName, startKey, Bytes.toBytes(id), newFormat); + } + + /** + * Make a region name of passed parameters. + * @param tableName + * @param startKey Can be null + * @param regionid Region id (Usually timestamp from when region was created). + * @param replicaId + * @param newFormat should we create the region name in the new format + * (such that it contains its encoded name?). + * @return Region name made of passed tableName, startKey, id and replicaId + */ + public static byte [] createRegionName(final TableName tableName, + final byte [] startKey, final long regionid, int replicaId, boolean newFormat) { + return createRegionName(tableName, startKey, Bytes.toBytes(Long.toString(regionid)), + replicaId, newFormat); + } + + /** + * Make a region name of passed parameters. + * @param tableName + * @param startKey Can be null + * @param id Region id (Usually timestamp from when region was created). + * @param newFormat should we create the region name in the new format + * (such that it contains its encoded name?). + * @return Region name made of passed tableName, startKey and id + */ + public static byte [] createRegionName(final TableName tableName, + final byte [] startKey, final byte [] id, boolean newFormat) { + return createRegionName(tableName, startKey, id, DEFAULT_REPLICA_ID, newFormat); + } + /** + * Make a region name of passed parameters. + * @param tableName + * @param startKey Can be null + * @param id Region id (Usually timestamp from when region was created). + * @param replicaId + * @param newFormat should we create the region name in the new format + * @return Region name made of passed tableName, startKey, id and replicaId + */ + public static byte [] createRegionName(final TableName tableName, + final byte [] startKey, final byte [] id, final int replicaId, boolean newFormat) { + int len = tableName.getName().length + 2 + id.length + + (startKey == null? 0: startKey.length); + if (newFormat) { + len += MD5_HEX_LENGTH + 2; + } + byte[] replicaIdBytes = null; + // Special casing: replicaId is only appended if replicaId is greater than + // 0. This is because all regions in meta would have to be migrated to the new + // name otherwise + if (replicaId > 0) { + // use string representation for replica id + replicaIdBytes = Bytes.toBytes(String.format(REPLICA_ID_FORMAT, replicaId)); + len += 1 + replicaIdBytes.length; + } + + byte [] b = new byte [len]; + + int offset = tableName.getName().length; + System.arraycopy(tableName.getName(), 0, b, 0, offset); + b[offset++] = HConstants.DELIMITER; + if (startKey != null && startKey.length > 0) { + System.arraycopy(startKey, 0, b, offset, startKey.length); + offset += startKey.length; + } + b[offset++] = HConstants.DELIMITER; + System.arraycopy(id, 0, b, offset, id.length); + offset += id.length; + + if (replicaIdBytes != null) { + b[offset++] = REPLICA_ID_DELIMITER; + System.arraycopy(replicaIdBytes, 0, b, offset, replicaIdBytes.length); + offset += replicaIdBytes.length; + } + + if (newFormat) { + // + // Encoded name should be built into the region name. + // + // Use the region name thus far (namely, ,,_) + // to compute a MD5 hash to be used as the encoded name, and append + // it to the byte buffer. + // + String md5Hash = MD5Hash.getMD5AsHex(b, 0, offset); + byte [] md5HashBytes = Bytes.toBytes(md5Hash); + + if (md5HashBytes.length != MD5_HEX_LENGTH) { + LOG.error("MD5-hash length mismatch: Expected=" + MD5_HEX_LENGTH + + "; Got=" + md5HashBytes.length); + } + + // now append the bytes '..' to the end + b[offset++] = ENC_SEPARATOR; + System.arraycopy(md5HashBytes, 0, b, offset, MD5_HEX_LENGTH); + offset += MD5_HEX_LENGTH; + b[offset++] = ENC_SEPARATOR; + } + + return b; + } + + /** + * Gets the table name from the specified region name. + * @param regionName to extract the table name from + * @return Table name + */ + public static TableName getTable(final byte [] regionName) { + int offset = -1; + for (int i = 0; i < regionName.length; i++) { + if (regionName[i] == HConstants.DELIMITER) { + offset = i; + break; + } + } + byte[] buff = new byte[offset]; + System.arraycopy(regionName, 0, buff, 0, offset); + return TableName.valueOf(buff); + } + + /** + * Gets the start key from the specified region name. + * @param regionName + * @return Start key. + * @throws java.io.IOException + */ + public static byte[] getStartKey(final byte[] regionName) throws IOException { + return parseRegionName(regionName)[1]; + } + + /** + * Separate elements of a regionName. + * @param regionName + * @return Array of byte[] containing tableName, startKey and id + * @throws IOException + */ + public static byte [][] parseRegionName(final byte [] regionName) + throws IOException { + // Region name is of the format: + // tablename,startkey,regionIdTimestamp[_replicaId][.encodedName.] + // startkey can contain the delimiter (',') so we parse from the start and end + + // parse from start + int offset = -1; + for (int i = 0; i < regionName.length; i++) { + if (regionName[i] == HConstants.DELIMITER) { + offset = i; + break; + } + } + if (offset == -1) { + throw new IOException(INVALID_REGION_NAME_FORMAT_MESSAGE + + ": " + Bytes.toStringBinary(regionName)); + } + byte[] tableName = new byte[offset]; + System.arraycopy(regionName, 0, tableName, 0, offset); + offset = -1; + + int endOffset = regionName.length; + // check whether regionName contains encodedName + if (regionName.length > MD5_HEX_LENGTH + 2 + && regionName[regionName.length-1] == ENC_SEPARATOR + && regionName[regionName.length-MD5_HEX_LENGTH-2] == ENC_SEPARATOR) { + endOffset = endOffset - MD5_HEX_LENGTH - 2; + } + + // parse from end + byte[] replicaId = null; + int idEndOffset = endOffset; + for (int i = endOffset - 1; i > 0; i--) { + if (regionName[i] == REPLICA_ID_DELIMITER) { //replicaId may or may not be present + replicaId = new byte[endOffset - i - 1]; + System.arraycopy(regionName, i + 1, replicaId, 0, + endOffset - i - 1); + idEndOffset = i; + // do not break, continue to search for id + } + if (regionName[i] == HConstants.DELIMITER) { + offset = i; + break; + } + } + if (offset == -1) { + throw new IOException(INVALID_REGION_NAME_FORMAT_MESSAGE + + ": " + Bytes.toStringBinary(regionName)); + } + byte [] startKey = HConstants.EMPTY_BYTE_ARRAY; + if(offset != tableName.length + 1) { + startKey = new byte[offset - tableName.length - 1]; + System.arraycopy(regionName, tableName.length + 1, startKey, 0, + offset - tableName.length - 1); + } + byte [] id = new byte[idEndOffset - offset - 1]; + System.arraycopy(regionName, offset + 1, id, 0, + idEndOffset - offset - 1); + byte [][] elements = new byte[replicaId == null ? 3 : 4][]; + elements[0] = tableName; + elements[1] = startKey; + elements[2] = id; + if (replicaId != null) { + elements[3] = replicaId; + } + + return elements; + } + + public static boolean isEncodedRegionName(byte[] regionName) throws IOException { + try { + parseRegionName(regionName); + return false; + } catch (IOException e) { + if (StringUtils.stringifyException(e) + .contains(INVALID_REGION_NAME_FORMAT_MESSAGE)) { + return true; + } + throw e; + } + } + + /** + * @param bytes + * @return A deserialized {@link RegionInfo} + * or null if we failed deserialize or passed bytes null + * @see #toByteArray() + */ + public static RegionInfo parseFromOrNull(final byte [] bytes) { + if (bytes == null) return null; + return parseFromOrNull(bytes, 0, bytes.length); + } + + /** + * @param bytes + * @param offset + * @param len + * @return A deserialized {@link RegionInfo} or null + * if we failed deserialize or passed bytes null + * @see #toByteArray() + */ + public static RegionInfo parseFromOrNull(final byte [] bytes, int offset, int len) { + if (bytes == null || len <= 0) return null; + try { + return parseFrom(bytes, offset, len); + } catch (DeserializationException e) { + return null; + } + } + + /** + * @param bytes A pb RegionInfo serialized with a pb magic prefix. + * @return A deserialized {@link RegionInfo} + * @throws DeserializationException + * @see #toByteArray() + */ + public static RegionInfo parseFrom(final byte [] bytes) throws DeserializationException { + if (bytes == null) return null; + return parseFrom(bytes, 0, bytes.length); + } + + /** + * @param bytes A pb RegionInfo serialized with a pb magic prefix. + * @param offset starting point in the byte array + * @param len length to read on the byte array + * @return A deserialized {@link RegionInfo} + * @throws DeserializationException + * @see #toByteArray() + */ + public static RegionInfo parseFrom(final byte [] bytes, int offset, int len) + throws DeserializationException { + if (ProtobufUtil.isPBMagicPrefix(bytes, offset, len)) { + int pblen = ProtobufUtil.lengthOfPBMagic(); + try { + HBaseProtos.RegionInfo.Builder builder = HBaseProtos.RegionInfo.newBuilder(); + ProtobufUtil.mergeFrom(builder, bytes, pblen + offset, len - pblen); + HBaseProtos.RegionInfo ri = builder.build(); + return ProtobufUtil.toRegionInfo(ri); + } catch (IOException e) { + throw new DeserializationException(e); + } + } else { + throw new DeserializationException("PB encoded RegionInfo expected"); + } + } + + /** + * Get the descriptive name as {@link RegionState} does it but with hidden + * startkey optionally + * @param state + * @param conf + * @return descriptive string + */ + public static String getDescriptiveNameFromRegionStateForDisplay(RegionState state, + Configuration conf) { + if (conf.getBoolean(DISPLAY_KEYS_KEY, true)) return state.toDescriptiveString(); + String descriptiveStringFromState = state.toDescriptiveString(); + int idx = descriptiveStringFromState.lastIndexOf(" state="); + String regionName = getRegionNameAsStringForDisplay( + RegionInfoBuilder.newBuilder(state.getRegion()).build(), conf); + return regionName + descriptiveStringFromState.substring(idx); + } + + /** + * Get the end key for display. Optionally hide the real end key. + * @param ri + * @param conf + * @return the endkey + */ + public static byte[] getEndKeyForDisplay(RegionInfo ri, Configuration conf) { + boolean displayKey = conf.getBoolean(DISPLAY_KEYS_KEY, true); + if (displayKey) return ri.getEndKey(); + return HIDDEN_END_KEY; + } + + /** + * Get the start key for display. Optionally hide the real start key. + * @param ri + * @param conf + * @return the startkey + */ + public static byte[] getStartKeyForDisplay(RegionInfo ri, Configuration conf) { + boolean displayKey = conf.getBoolean(DISPLAY_KEYS_KEY, true); + if (displayKey) return ri.getStartKey(); + return HIDDEN_START_KEY; + } + + /** + * Get the region name for display. Optionally hide the start key. + * @param ri + * @param conf + * @return region name as String + */ + public static String getRegionNameAsStringForDisplay(RegionInfo ri, Configuration conf) { + return Bytes.toStringBinary(getRegionNameForDisplay(ri, conf)); + } + + /** + * Get the region name for display. Optionally hide the start key. + * @param ri + * @param conf + * @return region name bytes + */ + public static byte[] getRegionNameForDisplay(RegionInfo ri, Configuration conf) { + boolean displayKey = conf.getBoolean(DISPLAY_KEYS_KEY, true); + if (displayKey || ri.getTable().equals(TableName.META_TABLE_NAME)) { + return ri.getRegionName(); + } else { + // create a modified regionname with the startkey replaced but preserving + // the other parts including the encodedname. + try { + byte[][]regionNameParts = parseRegionName(ri.getRegionName()); + regionNameParts[1] = HIDDEN_START_KEY; //replace the real startkey + int len = 0; + // get the total length + for (byte[] b : regionNameParts) { + len += b.length; + } + byte[] encodedRegionName = + Bytes.toBytes(encodeRegionName(ri.getRegionName())); + len += encodedRegionName.length; + //allocate some extra bytes for the delimiters and the last '.' + byte[] modifiedName = new byte[len + regionNameParts.length + 1]; + int lengthSoFar = 0; + int loopCount = 0; + for (byte[] b : regionNameParts) { + System.arraycopy(b, 0, modifiedName, lengthSoFar, b.length); + lengthSoFar += b.length; + if (loopCount++ == 2) modifiedName[lengthSoFar++] = REPLICA_ID_DELIMITER; + else modifiedName[lengthSoFar++] = HConstants.DELIMITER; + } + // replace the last comma with '.' + modifiedName[lengthSoFar - 1] = ENC_SEPARATOR; + System.arraycopy(encodedRegionName, 0, modifiedName, lengthSoFar, + encodedRegionName.length); + lengthSoFar += encodedRegionName.length; + modifiedName[lengthSoFar] = ENC_SEPARATOR; + return modifiedName; + } catch (IOException e) { + //LOG.warn("Encountered exception " + e); + throw new RuntimeException(e); + } + } + } + + /** + * Parses an RegionInfo instance from the passed in stream. + * Presumes the RegionInfo was serialized to the stream with + * {@link #toDelimitedByteArray()}. + * @param in + * @return An instance of RegionInfo. + * @throws IOException + */ + public static RegionInfo parseFrom(final DataInputStream in) throws IOException { + // I need to be able to move back in the stream if this is not a pb + // serialization so I can do the Writable decoding instead. + int pblen = ProtobufUtil.lengthOfPBMagic(); + byte [] pbuf = new byte[pblen]; + if (in.markSupported()) { //read it with mark() + in.mark(pblen); + } + + //assumption: if Writable serialization, it should be longer than pblen. + int read = in.read(pbuf); + if (read != pblen) throw new IOException("read=" + read + ", wanted=" + pblen); + if (ProtobufUtil.isPBMagicPrefix(pbuf)) { + return ProtobufUtil.toRegionInfo(HBaseProtos.RegionInfo.parseDelimitedFrom(in)); + } else { + throw new IOException("PB encoded RegionInfo expected"); + } + } + + /** + * Serializes given RegionInfo's as a byte array. Use this instead of {@link #toByteArray()} when + * writing to a stream and you want to use the pb mergeDelimitedFrom (w/o the delimiter, pb reads + * to EOF which may not be what you want). {@link #parseDelimitedFrom(byte[], int, int)} can + * be used to read back the instances. + * @param infos RegionInfo objects to serialize + * @return This instance serialized as a delimited protobuf w/ a magic pb prefix. + * @throws IOException + * @see #toByteArray() + */ + public static byte[] toDelimitedByteArray(RegionInfo... infos) throws IOException { + byte[][] bytes = new byte[infos.length][]; + int size = 0; + for (int i = 0; i < infos.length; i++) { + bytes[i] = toDelimitedByteArray(infos[i]); + size += bytes[i].length; + } + + byte[] result = new byte[size]; + int offset = 0; + for (byte[] b : bytes) { + System.arraycopy(b, 0, result, offset, b.length); + offset += b.length; + } + return result; + } + + /** + * Parses all the RegionInfo instances from the passed in stream until EOF. Presumes the + * RegionInfo's were serialized to the stream with {@link #toDelimitedByteArray()} + * @param bytes serialized bytes + * @param offset the start offset into the byte[] buffer + * @param length how far we should read into the byte[] buffer + * @return All the RegionInfos that are in the byte array. Keeps reading till we hit the end. + * @throws java.io.IOException + */ + public static List parseDelimitedFrom(final byte[] bytes, final int offset, + final int length) throws IOException { + if (bytes == null) { + throw new IllegalArgumentException("Can't build an object with empty bytes array"); + } + DataInputBuffer in = new DataInputBuffer(); + List ris = new ArrayList<>(); + try { + in.reset(bytes, offset, length); + while (in.available() > 0) { + RegionInfo ri = parseFrom(in); + ris.add(ri); + } + } finally { + in.close(); + } + return ris; + } + + /** + * Check whether two regions are adjacent + * @param regionA + * @param regionB + * @return true if two regions are adjacent + */ + public static boolean areAdjacent(RegionInfo regionA, RegionInfo regionB) { + if (regionA == null || regionB == null) { + throw new IllegalArgumentException( + "Can't check whether adjacent for null region"); + } + RegionInfo a = regionA; + RegionInfo b = regionB; + if (Bytes.compareTo(a.getStartKey(), b.getStartKey()) > 0) { + a = regionB; + b = regionA; + } + if (Bytes.compareTo(a.getEndKey(), b.getStartKey()) == 0) { + return true; + } + return false; + } + + /** + * @return This instance serialized as protobuf w/ a magic pb prefix. + * @see #parseFrom(byte[]) + */ + public static byte [] toByteArray(RegionInfo ri) { + byte [] bytes = ProtobufUtil.toProtoRegionInfo(ri).toByteArray(); + return ProtobufUtil.prependPBMagic(bytes); + } + + /** + * Use this instead of {@link #toByteArray()} when writing to a stream and you want to use + * the pb mergeDelimitedFrom (w/o the delimiter, pb reads to EOF which may not be what you want). + * @return This instance serialized as a delimited protobuf w/ a magic pb prefix. + * @throws IOException + * @see #toByteArray() + */ + public static byte [] toDelimitedByteArray(RegionInfo ri) throws IOException { + return ProtobufUtil.toDelimitedByteArray( + ProtobufUtil.toProtoRegionInfo(ri)); + } + + @InterfaceAudience.Private + public static class MutableRegionInfo + implements RegionInfo, Comparable { + + /** + * The new format for a region name contains its encodedName at the end. + * The encoded name also serves as the directory name for the region + * in the filesystem. + * + * New region name format: + * <tablename>,,<startkey>,<regionIdTimestamp>.<encodedName>. + * where, + * <encodedName> is a hex version of the MD5 hash of + * <tablename>,<startkey>,<regionIdTimestamp> + * + * The old region name format: + * <tablename>,<startkey>,<regionIdTimestamp> + * For region names in the old format, the encoded name is a 32-bit + * JenkinsHash integer value (in its decimal notation, string form). + *

+ * **NOTE** + * + * The first hbase:meta region, and regions created by an older + * version of HBase (0.20 or prior) will continue to use the + * old region name format. + */ + + // This flag is in the parent of a split while the parent is still referenced + // by daughter regions. We USED to set this flag when we disabled a table + // but now table state is kept up in zookeeper as of 0.90.0 HBase. + private boolean offLine = false; + private boolean split = false; + private long regionId = -1; + private int replicaId = DEFAULT_REPLICA_ID; + private transient byte [] regionName = HConstants.EMPTY_BYTE_ARRAY; + private byte [] startKey = HConstants.EMPTY_BYTE_ARRAY; + private byte [] endKey = HConstants.EMPTY_BYTE_ARRAY; + private int hashCode = -1; + private String encodedName = null; + private byte [] encodedNameAsBytes = null; + // Current TableName + private TableName tableName = null; + + private void setHashCode() { + int result = Arrays.hashCode(this.regionName); + result ^= this.regionId; + result ^= Arrays.hashCode(this.startKey); + result ^= Arrays.hashCode(this.endKey); + result ^= Boolean.valueOf(this.offLine).hashCode(); + result ^= Arrays.hashCode(this.tableName.getName()); + result ^= this.replicaId; + this.hashCode = result; + } + + /** + * Private constructor used constructing MutableRegionInfo for the + * first meta regions + */ + private MutableRegionInfo(long regionId, TableName tableName) { + this(regionId, tableName, DEFAULT_REPLICA_ID); + } + + public MutableRegionInfo(long regionId, TableName tableName, int replicaId) { + super(); + this.regionId = regionId; + this.tableName = tableName; + this.replicaId = replicaId; + // Note: First Meta region replicas names are in old format + this.regionName = createRegionName(tableName, null, regionId, replicaId, false); + setHashCode(); + } + + public MutableRegionInfo(final TableName tableName) { + this(tableName, null, null); + } + + /** + * Construct MutableRegionInfo with explicit parameters + * + * @param tableName the table name + * @param startKey first key in region + * @param endKey end of key range + * @throws IllegalArgumentException + */ + public MutableRegionInfo(final TableName tableName, final byte[] startKey, final byte[] endKey) + throws IllegalArgumentException { + this(tableName, startKey, endKey, false); + } + + /** + * Construct MutableRegionInfo with explicit parameters + * + * @param tableName the table descriptor + * @param startKey first key in region + * @param endKey end of key range + * @param split true if this region has split and we have daughter regions + * regions that may or may not hold references to this region. + * @throws IllegalArgumentException + */ + public MutableRegionInfo(final TableName tableName, final byte[] startKey, final byte[] endKey, + final boolean split) + throws IllegalArgumentException { + this(tableName, startKey, endKey, split, System.currentTimeMillis()); + } + + /** + * Construct MutableRegionInfo with explicit parameters + * + * @param tableName the table descriptor + * @param startKey first key in region + * @param endKey end of key range + * @param split true if this region has split and we have daughter regions + * regions that may or may not hold references to this region. + * @param regionid Region id to use. + * @throws IllegalArgumentException + */ + public MutableRegionInfo(final TableName tableName, final byte[] startKey, + final byte[] endKey, final boolean split, final long regionid) + throws IllegalArgumentException { + this(tableName, startKey, endKey, split, regionid, DEFAULT_REPLICA_ID); + } + + /** + * Construct MutableRegionInfo with explicit parameters + * + * @param tableName the table descriptor + * @param startKey first key in region + * @param endKey end of key range + * @param split true if this region has split and we have daughter regions + * regions that may or may not hold references to this region. + * @param regionid Region id to use. + * @param replicaId the replicaId to use + * @throws IllegalArgumentException + */ + public MutableRegionInfo(final TableName tableName, final byte[] startKey, + final byte[] endKey, final boolean split, final long regionid, + final int replicaId) + throws IllegalArgumentException { + super(); + if (tableName == null) { + throw new IllegalArgumentException("TableName cannot be null"); + } + this.tableName = tableName; + this.offLine = false; + this.regionId = regionid; + this.replicaId = replicaId; + if (this.replicaId > MAX_REPLICA_ID) { + throw new IllegalArgumentException("ReplicaId cannot be greater than" + MAX_REPLICA_ID); + } + + this.regionName = createRegionName(this.tableName, startKey, regionId, replicaId, true); + + this.split = split; + this.endKey = endKey == null? HConstants.EMPTY_END_ROW: endKey.clone(); + this.startKey = startKey == null? + HConstants.EMPTY_START_ROW: startKey.clone(); + this.tableName = tableName; + setHashCode(); + } + + /** + * Construct MutableRegionInfo. + * Only for RegionInfoBuilder to use. + * @param other + */ + public MutableRegionInfo(MutableRegionInfo other, boolean isMetaRegion) { + super(); + if (other.getTable() == null) { + throw new IllegalArgumentException("TableName cannot be null"); + } + this.tableName = other.getTable(); + this.offLine = other.isOffline(); + this.regionId = other.getRegionId(); + this.replicaId = other.getReplicaId(); + if (this.replicaId > MAX_REPLICA_ID) { + throw new IllegalArgumentException("ReplicaId cannot be greater than" + MAX_REPLICA_ID); + } + + if(isMetaRegion) { + // Note: First Meta region replicas names are in old format + this.regionName = createRegionName( + other.getTable(), null, other.getRegionId(), + other.getReplicaId(), false); + } else { + this.regionName = createRegionName( + other.getTable(), other.getStartKey(), other.getRegionId(), + other.getReplicaId(), true); + } + + this.split = other.isSplit(); + this.endKey = other.getEndKey() == null? HConstants.EMPTY_END_ROW: other.getEndKey().clone(); + this.startKey = other.getStartKey() == null? + HConstants.EMPTY_START_ROW: other.getStartKey().clone(); + this.tableName = other.getTable(); + setHashCode(); + } + + /** + * Construct a copy of RegionInfo as MutableRegionInfo. + * Only for RegionInfoBuilder to use. + * @param regionInfo + */ + public MutableRegionInfo(RegionInfo regionInfo) { + super(); + this.endKey = regionInfo.getEndKey(); + this.offLine = regionInfo.isOffline(); + this.regionId = regionInfo.getRegionId(); + this.regionName = regionInfo.getRegionName(); + this.split = regionInfo.isSplit(); + this.startKey = regionInfo.getStartKey(); + this.hashCode = regionInfo.hashCode(); + this.encodedName = regionInfo.getEncodedName(); + this.tableName = regionInfo.getTable(); + this.replicaId = regionInfo.getReplicaId(); + } + + /** + * @return Return a short, printable name for this region + * (usually encoded name) for us logging. + */ + @Override + public String getShortNameToLog() { + return prettyPrint(this.getEncodedName()); + } + + /** @return the regionId */ + @Override + public long getRegionId(){ + return regionId; + } + + /** + * set region id. + * @param regionId + * @return MutableRegionInfo + */ + public MutableRegionInfo setRegionId(long regionId) { + this.regionId = regionId; + return this; + } + + /** + * @return the regionName as an array of bytes. + * @see #getRegionNameAsString() + */ + @Override + public byte [] getRegionName(){ + return regionName; + } + + /** + * set region name. + * @param regionName + * @return MutableRegionInfo + */ + public MutableRegionInfo setRegionName(byte[] regionName) { + this.regionName = regionName; + return this; + } + + /** + * @return Region name as a String for use in logging, etc. + */ + @Override + public String getRegionNameAsString() { + if (hasEncodedName(this.regionName)) { + // new format region names already have their encoded name. + return Bytes.toStringBinary(this.regionName); + } + + // old format. regionNameStr doesn't have the region name. + // + // + return Bytes.toStringBinary(this.regionName) + "." + this.getEncodedName(); + } + + /** @return the encoded region name */ + @Override + public synchronized String getEncodedName() { + if (this.encodedName == null) { + this.encodedName = encodeRegionName(this.regionName); + } + return this.encodedName; + } + + @Override + public synchronized byte [] getEncodedNameAsBytes() { + if (this.encodedNameAsBytes == null) { + this.encodedNameAsBytes = Bytes.toBytes(getEncodedName()); + } + return this.encodedNameAsBytes; + } + + /** @return the startKey */ + @Override + public byte [] getStartKey(){ + return startKey; + } + + /** + * @param startKey + * @return MutableRegionInfo + */ + public MutableRegionInfo setStartKey(byte[] startKey) { + this.startKey = startKey; + return this; + } + + /** @return the endKey */ + @Override + public byte [] getEndKey(){ + return endKey; + } + + /** + * @param endKey + * @return MutableRegionInfo + */ + public MutableRegionInfo setEndKey(byte[] endKey) { + this.endKey = endKey; + return this; + } + + /** + * Get current table name of the region + * @return TableName + */ + @Override + public TableName getTable() { + // This method name should be getTableName but there was already a method getTableName + // that returned a byte array. It is unfortunate given everywhere else, getTableName returns + // a TableName instance. + if (tableName == null || tableName.getName().length == 0) { + tableName = RegionInfoBuilder.getTable(getRegionName()); + } + return this.tableName; + } + + /** + * Returns true if the given inclusive range of rows is fully contained + * by this region. For example, if the region is foo,a,g and this is + * passed ["b","c"] or ["a","c"] it will return true, but if this is passed + * ["b","z"] it will return false. + * @throws IllegalArgumentException if the range passed is invalid (ie. end < start) + */ + @Override + public boolean containsRange(byte[] rangeStartKey, byte[] rangeEndKey) { + if (Bytes.compareTo(rangeStartKey, rangeEndKey) > 0) { + throw new IllegalArgumentException( + "Invalid range: " + Bytes.toStringBinary(rangeStartKey) + + " > " + Bytes.toStringBinary(rangeEndKey)); + } + + boolean firstKeyInRange = Bytes.compareTo(rangeStartKey, startKey) >= 0; + boolean lastKeyInRange = + Bytes.compareTo(rangeEndKey, endKey) < 0 || + Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY); + return firstKeyInRange && lastKeyInRange; + } + + /** + * Return true if the given row falls in this region. + */ + @Override + public boolean containsRow(byte[] row) { + return Bytes.compareTo(row, startKey) >= 0 && + (Bytes.compareTo(row, endKey) < 0 || + Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY)); + } + + /** + * @return true if this region is from hbase:meta + */ + @Override + public boolean isMetaTable() { + return isMetaRegion(); + } + + /** @return true if this region is a meta region */ + @Override + public boolean isMetaRegion() { + return tableName.equals(FIRST_META_REGIONINFO.getTable()); + } + + /** + * @return true if this region is from a system table + */ + @Override + public boolean isSystemTable() { + return tableName.isSystemTable(); + } + + /** + * @return True if has been split and has daughters. + */ + @Override + public boolean isSplit() { + return this.split; + } + + /** + * @param split set split status + * @return MutableRegionInfo + */ + public MutableRegionInfo setSplit(boolean split) { + this.split = split; + return this; + } + + /** + * @return True if this region is offline. + */ + @Override + public boolean isOffline() { + return this.offLine; + } + + /** + * The parent of a region split is offline while split daughters hold + * references to the parent. Offlined regions are closed. + * @param offLine Set online/offline status. + * @return MutableRegionInfo + */ + public MutableRegionInfo setOffline(boolean offLine) { + this.offLine = offLine; + return this; + } + + /** + * @return True if this is a split parent region. + */ + @Override + public boolean isSplitParent() { + if (!isSplit()) return false; + if (!isOffline()) { + LOG.warn("Region is split but NOT offline: " + getRegionNameAsString()); + } + return true; + } + + /** + * Returns the region replica id + * @return returns region replica id + */ + @Override + public int getReplicaId() { + return replicaId; + } + + public MutableRegionInfo setReplicaId(int replicaId) { + this.replicaId = replicaId; + return this; + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "{ENCODED => " + getEncodedName() + ", " + + HConstants.NAME + " => '" + Bytes.toStringBinary(this.regionName) + + "', STARTKEY => '" + + Bytes.toStringBinary(this.startKey) + "', ENDKEY => '" + + Bytes.toStringBinary(this.endKey) + "'" + + (isOffline()? ", OFFLINE => true": "") + + (isSplit()? ", SPLIT => true": "") + + ((replicaId > 0)? ", REPLICA_ID => " + replicaId : "") + "}"; + } + + /** + * @param o + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + if (!(o instanceof MutableRegionInfo)) { + return false; + } + return this.compareTo((MutableRegionInfo)o) == 0; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return this.hashCode; + } + + @Override + public int compareTo(MutableRegionInfo other) { + return RegionInfo.COMPARATOR.compare(this, other); + } + + /** + * @return Comparator to use comparing {@link KeyValue}s. + * @deprecated Use Region#getCellComparator(). deprecated for hbase 2.0, remove for hbase 3.0 + */ + @Deprecated + public KeyValue.KVComparator getComparator() { + return isMetaRegion()? + KeyValue.META_COMPARATOR: KeyValue.COMPARATOR; + } + + } +} \ No newline at end of file diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionReplicaUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionReplicaUtil.java index c2dcbc0..feb21a3 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionReplicaUtil.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionReplicaUtil.java @@ -57,6 +57,7 @@ public class RegionReplicaUtil { * @return an HRegionInfo object corresponding to the same range (table, start and * end key), but for the given replicaId. */ + @Deprecated // Deprecated for HBase-2.0.0, use #getRegionInfoForReplica public static HRegionInfo getRegionInfoForReplica(HRegionInfo regionInfo, int replicaId) { if (regionInfo.getReplicaId() == replicaId) { return regionInfo; @@ -73,6 +74,37 @@ public class RegionReplicaUtil { } /** + * Returns the RegionInfo for the given replicaId. + * RegionInfo's correspond to a range of a table, but more than one + * "instance" of the same range can be deployed which are differentiated by + * the replicaId. + * @param regionInfo + * @param replicaId the replicaId to use + * @return an RegionInfo object corresponding to the same range (table, start and + * end key), but for the given replicaId. + */ + public static RegionInfo getRegionInfoForReplica(RegionInfo regionInfo, int replicaId) { + if (regionInfo.getReplicaId() == replicaId) { + return regionInfo; + } + RegionInfoBuilder replicaInfo; + if (regionInfo.isMetaRegion()) { + replicaInfo = RegionInfoBuilder.newBuilder(regionInfo.getTable(), true) + .setRegionId(regionInfo.getRegionId()) + .setReplicaId(replicaId); + } else { + replicaInfo = RegionInfoBuilder.newBuilder(regionInfo.getTable()) + .setStartKey(regionInfo.getStartKey()) + .setEndKey(regionInfo.getEndKey()) + .setSplit(regionInfo.isSplit()) + .setRegionId(regionInfo.getRegionId()) + .setReplicaId(replicaId); + } + replicaInfo.setOffline(regionInfo.isOffline()); + return replicaInfo.build(); + } + + /** * Returns the HRegionInfo for the default replicaId (0). HRegionInfo's correspond to * a range of a table, but more than one "instance" of the same range can be * deployed which are differentiated by the replicaId. diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/UnmodifyableHRegionInfo.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/UnmodifyableHRegionInfo.java deleted file mode 100644 index 742acee..0000000 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/UnmodifyableHRegionInfo.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.hadoop.hbase.client; - -import org.apache.hadoop.hbase.HRegionInfo; -import org.apache.hadoop.hbase.classification.InterfaceAudience; - -@InterfaceAudience.Public -class UnmodifyableHRegionInfo extends HRegionInfo { - /* - * Creates an unmodifyable copy of an HRegionInfo - * - * @param info - */ - UnmodifyableHRegionInfo(HRegionInfo info) { - super(info); - } - - /** - * @param split set split status - */ - @Override - public void setSplit(boolean split) { - throw new UnsupportedOperationException("HRegionInfo is read-only"); - } - - /** - * @param offLine set online - offline status - */ - @Override - public void setOffline(boolean offLine) { - throw new UnsupportedOperationException("HRegionInfo is read-only"); - } -} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java index e97b78d..23512b2 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java @@ -79,7 +79,9 @@ import org.apache.hadoop.hbase.client.Increment; import org.apache.hadoop.hbase.client.Mutation; import org.apache.hadoop.hbase.client.PackagePrivateFieldAccessor; import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.RegionInfoBuilder; import org.apache.hadoop.hbase.client.RegionLoadStats; +import org.apache.hadoop.hbase.client.RegionReplicaUtil; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.client.SnapshotDescription; @@ -3411,4 +3413,70 @@ public final class ProtobufUtil { return lock; } + + /** + * Convert a RegionInfo to a Proto RegionInfo + * + * @param info the RegionInfo to convert + * @return the converted Proto RegionInfo + */ + public static RegionInfo toProtoRegionInfo(final org.apache.hadoop.hbase.client.RegionInfo info) { + if (info == null) return null; + RegionInfo.Builder builder = RegionInfo.newBuilder(); + builder.setTableName(org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil.toProtoTableName(info.getTable())); + builder.setRegionId(info.getRegionId()); + if (info.getStartKey() != null) { + builder.setStartKey(UnsafeByteOperations.unsafeWrap(info.getStartKey())); + } + if (info.getEndKey() != null) { + builder.setEndKey(UnsafeByteOperations.unsafeWrap(info.getEndKey())); + } + builder.setOffline(info.isOffline()); + builder.setSplit(info.isSplit()); + builder.setReplicaId(info.getReplicaId()); + return builder.build(); + } + + /** + * Convert HBaseProto.RegionInfo to a RegionInfo + * + * @param proto the RegionInfo to convert + * @return the converted RegionInfo + */ + public static org.apache.hadoop.hbase.client.RegionInfo toRegionInfo(final RegionInfo proto) { + if (proto == null) return null; + TableName tableName = + org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil.toTableName(proto.getTableName()); + if (tableName.equals(TableName.META_TABLE_NAME)) { + return RegionInfoBuilder.newBuilder( + RegionReplicaUtil.getRegionInfoForReplica( + RegionInfoBuilder.FIRST_META_REGIONINFO, + proto.getReplicaId())) + .build(); + } + long regionId = proto.getRegionId(); + int replicaId = proto.hasReplicaId() ? proto.getReplicaId() : RegionInfoBuilder.DEFAULT_REPLICA_ID; + byte[] startKey = null; + byte[] endKey = null; + if (proto.hasStartKey()) { + startKey = proto.getStartKey().toByteArray(); + } + if (proto.hasEndKey()) { + endKey = proto.getEndKey().toByteArray(); + } + boolean split = false; + if (proto.hasSplit()) { + split = proto.getSplit(); + } + RegionInfoBuilder rib = RegionInfoBuilder.newBuilder(tableName) + .setStartKey(startKey) + .setEndKey(endKey) + .setRegionId(regionId) + .setReplicaId(replicaId) + .setSplit(split); + if (proto.hasOffline()) { + rib.setOffline(proto.getOffline()); + } + return rib.build(); + } } diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/client/TestImmutableHRegionInfo.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/client/TestImmutableHRegionInfo.java new file mode 100644 index 0000000..4644641 --- /dev/null +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/client/TestImmutableHRegionInfo.java @@ -0,0 +1,61 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.client; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.testclassification.ClientTests; +import org.apache.hadoop.hbase.testclassification.SmallTests; +import static org.junit.Assert.fail; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.TestName; + +/** + * Test ImmutableHRegionInfo + */ +@Category({ClientTests.class, SmallTests.class}) +public class TestImmutableHRegionInfo { + + @Rule + public TestName name = new TestName(); + + private final List> TEST_FUNCTIONS = Arrays.asList( + hri -> hri.setOffline(true), + hri -> hri.setSplit(true) + ); + + @Test + public void testImmutable() { + HRegionInfo hri = new HRegionInfo(TableName.valueOf(name.getMethodName())); + ImmutableHRegionInfo immutableHri = new ImmutableHRegionInfo(hri); + + TEST_FUNCTIONS.forEach(f -> { + try { + f.accept(immutableHri); + fail("ImmutableHRegionInfo can't be modified !!!"); + } catch(UnsupportedOperationException e) { + } + }); + } + +} diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/MetaMockingUtil.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/MetaMockingUtil.java index 9a1515b..a89237e 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/MetaMockingUtil.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/MetaMockingUtil.java @@ -105,17 +105,4 @@ public class MetaMockingUtil { return Result.create(kvs); } - /** - * @param sn ServerName to use making startcode and server in meta - * @param hri Region to serialize into HRegionInfo - * @return A mocked up Result that fakes a Get on a row in the hbase:meta table. - * @throws IOException - */ - public static Result getMetaTableRowResultAsSplitRegion(final HRegionInfo hri, - final ServerName sn) throws IOException { - hri.setOffline(true); - hri.setSplit(true); - return getMetaTableRowResult(hri, sn); - } - } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionInfoBuilder.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionInfoBuilder.java new file mode 100644 index 0000000..f738dca --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionInfoBuilder.java @@ -0,0 +1,407 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.regionserver; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileStatus; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.RegionInfo; +import org.apache.hadoop.hbase.client.RegionInfoBuilder; +import org.apache.hadoop.hbase.client.TableDescriptor; +import org.apache.hadoop.hbase.client.TableDescriptorBuilder; +import org.apache.hadoop.hbase.exceptions.DeserializationException; +import org.apache.hadoop.hbase.master.RegionState; +import org.apache.hadoop.hbase.shaded.com.google.protobuf.UnsafeByteOperations; +import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; +import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; +import org.apache.hadoop.hbase.testclassification.RegionServerTests; +import org.apache.hadoop.hbase.testclassification.SmallTests; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.FSTableDescriptors; +import org.apache.hadoop.hbase.util.MD5Hash; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.TestName; + +@Category({RegionServerTests.class, SmallTests.class}) +public class TestRegionInfoBuilder { + @Rule + public TestName name = new TestName(); + + @Test + public void testBuilder() { + TableName tn = TableName.valueOf("test"); + RegionInfoBuilder builder = RegionInfoBuilder.newBuilder(tn); + byte[] startKey = Bytes.toBytes("a"); + builder.setStartKey(startKey); + byte[] endKey = Bytes.toBytes("z"); + builder.setEndKey(endKey); + int regionId = 1; + builder.setRegionId(1); + int replicaId = 2; + builder.setReplicaId(replicaId); + boolean offline = true; + builder.setOffline(offline); + boolean isSplit = true; + builder.setSplit(isSplit); + RegionInfo ri = builder.build(); + + assertEquals(tn, ri.getTable()); + assertArrayEquals(startKey, ri.getStartKey()); + assertArrayEquals(endKey, ri.getEndKey()); + assertEquals(regionId, ri.getRegionId()); + assertEquals(replicaId, ri.getReplicaId()); + assertEquals(offline, ri.isOffline()); + assertEquals(isSplit, ri.isSplit()); + } + + @Test + public void testPb() throws DeserializationException { + RegionInfo ri = RegionInfoBuilder.FIRST_META_REGIONINFO; + byte [] bytes = RegionInfoBuilder.toByteArray(ri); + RegionInfo pbri = RegionInfoBuilder.parseFrom(bytes); + assertTrue(ri.equals(pbri)); + } + + @Test + public void testReadAndWriteRegionInfoFile() throws IOException, InterruptedException { + HBaseTestingUtility htu = new HBaseTestingUtility(); + RegionInfo ri = RegionInfoBuilder.FIRST_META_REGIONINFO; + Path basedir = htu.getDataTestDir(); + // Create a region. That'll write the .regioninfo file. + FSTableDescriptors fsTableDescriptors = new FSTableDescriptors(htu.getConfiguration()); + HRegion r = HBaseTestingUtility.createRegionAndWAL(convert(ri), basedir, htu.getConfiguration(), + fsTableDescriptors.get(TableName.META_TABLE_NAME)); + // Get modtime on the file. + long modtime = getModTime(r); + HBaseTestingUtility.closeRegionAndWAL(r); + Thread.sleep(1001); + r = HRegion.openHRegion(basedir, convert(ri), fsTableDescriptors.get(TableName.META_TABLE_NAME), + null, htu.getConfiguration()); + // Ensure the file is not written for a second time. + long modtime2 = getModTime(r); + assertEquals(modtime, modtime2); + // Now load the file. + RegionInfo deserializedRi = HRegionFileSystem.loadRegionInfoFileContent( + r.getRegionFileSystem().getFileSystem(), r.getRegionFileSystem().getRegionDir()); + deserializedRi = RegionInfoBuilder.newBuilder(deserializedRi).build(); + assertTrue(ri.equals(deserializedRi)); + HBaseTestingUtility.closeRegionAndWAL(r); + } + + long getModTime(final HRegion r) throws IOException { + FileStatus[] statuses = r.getRegionFileSystem().getFileSystem().listStatus( + new Path(r.getRegionFileSystem().getRegionDir(), HRegionFileSystem.REGION_INFO_FILE)); + assertTrue(statuses != null && statuses.length == 1); + return statuses[0].getModificationTime(); + } + + @Test + public void testCreateRegionInfoName() throws Exception { + final String tableName = name.getMethodName(); + final TableName tn = TableName.valueOf(tableName); + String startKey = "startkey"; + final byte[] sk = Bytes.toBytes(startKey); + String id = "id"; + + // old format region name + byte [] name = RegionInfoBuilder.createRegionName(tn, sk, id, false); + String nameStr = Bytes.toString(name); + assertEquals(tableName + "," + startKey + "," + id, nameStr); + + + // new format region name. + String md5HashInHex = MD5Hash.getMD5AsHex(name); + assertEquals(RegionInfoBuilder.MD5_HEX_LENGTH, md5HashInHex.length()); + name = RegionInfoBuilder.createRegionName(tn, sk, id, true); + nameStr = Bytes.toString(name); + assertEquals(tableName + "," + startKey + "," + + id + "." + md5HashInHex + ".", + nameStr); + } + + @Test + public void testContainsRange() { + TableDescriptor tableDesc = TableDescriptorBuilder.newBuilder( + TableName.valueOf(name.getMethodName())).build(); + RegionInfo ri = RegionInfoBuilder.newBuilder(tableDesc.getTableName()) + .setStartKey(Bytes.toBytes("a")) + .setEndKey(Bytes.toBytes("g")).build(); + // Single row range at start of region + assertTrue(ri.containsRange(Bytes.toBytes("a"), Bytes.toBytes("a"))); + // Fully contained range + assertTrue(ri.containsRange(Bytes.toBytes("b"), Bytes.toBytes("c"))); + // Range overlapping start of region + assertTrue(ri.containsRange(Bytes.toBytes("a"), Bytes.toBytes("c"))); + // Fully contained single-row range + assertTrue(ri.containsRange(Bytes.toBytes("c"), Bytes.toBytes("c"))); + // Range that overlaps end key and hence doesn't fit + assertFalse(ri.containsRange(Bytes.toBytes("a"), Bytes.toBytes("g"))); + // Single row range on end key + assertFalse(ri.containsRange(Bytes.toBytes("g"), Bytes.toBytes("g"))); + // Single row range entirely outside + assertFalse(ri.containsRange(Bytes.toBytes("z"), Bytes.toBytes("z"))); + + // Degenerate range + try { + ri.containsRange(Bytes.toBytes("z"), Bytes.toBytes("a")); + fail("Invalid range did not throw IAE"); + } catch (IllegalArgumentException iae) { + } + } + + @Test + public void testLastRegionCompare() { + TableDescriptor tableDesc = TableDescriptorBuilder + .newBuilder(TableName.valueOf(name.getMethodName())).build(); + RegionInfo rip = RegionInfoBuilder.newBuilder(tableDesc.getTableName()) + .setStartKey(Bytes.toBytes("a")) + .setEndKey(new byte[0]).build(); + RegionInfo ric = RegionInfoBuilder.newBuilder(tableDesc.getTableName()) + .setStartKey(Bytes.toBytes("a")) + .setEndKey(Bytes.toBytes("b")).build(); + assertTrue(RegionInfo.COMPARATOR.compare(rip, ric) > 0); + } + + @Test + public void testMetaTables() { + assertTrue(RegionInfoBuilder.FIRST_META_REGIONINFO.isMetaTable()); + } + + @Test + public void testComparator() { + final TableName tableName = TableName.valueOf(name.getMethodName()); + byte[] empty = new byte[0]; + RegionInfo older = RegionInfoBuilder.newBuilder(tableName) + .setStartKey(empty) + .setEndKey(empty) + .setSplit(false) + .setRegionId(0L).build(); + RegionInfo newer = RegionInfoBuilder.newBuilder(tableName) + .setStartKey(empty) + .setEndKey(empty) + .setSplit(false) + .setRegionId(1L).build(); + assertTrue(RegionInfo.COMPARATOR.compare(older, newer) < 0); + assertTrue(RegionInfo.COMPARATOR.compare(newer, older) > 0); + assertTrue(RegionInfo.COMPARATOR.compare(older, older) == 0); + assertTrue(RegionInfo.COMPARATOR.compare(newer, newer) == 0); + } + + @Test + public void testRegionNameForRegionReplicas() throws Exception { + String tableName = name.getMethodName(); + final TableName tn = TableName.valueOf(tableName); + String startKey = "startkey"; + final byte[] sk = Bytes.toBytes(startKey); + String id = "id"; + + // assert with only the region name without encoding + + // primary, replicaId = 0 + byte [] name = RegionInfoBuilder.createRegionName(tn, sk, Bytes.toBytes(id), 0, false); + String nameStr = Bytes.toString(name); + assertEquals(tableName + "," + startKey + "," + id, nameStr); + + // replicaId = 1 + name = RegionInfoBuilder.createRegionName(tn, sk, Bytes.toBytes(id), 1, false); + nameStr = Bytes.toString(name); + assertEquals(tableName + "," + startKey + "," + id + "_" + + String.format(RegionInfoBuilder.REPLICA_ID_FORMAT, 1), nameStr); + + // replicaId = max + name = RegionInfoBuilder.createRegionName(tn, sk, Bytes.toBytes(id), 0xFFFF, false); + nameStr = Bytes.toString(name); + assertEquals(tableName + "," + startKey + "," + id + "_" + + String.format(RegionInfoBuilder.REPLICA_ID_FORMAT, 0xFFFF), nameStr); + } + + @Test + public void testParseName() throws IOException { + final TableName tableName = TableName.valueOf(name.getMethodName()); + byte[] startKey = Bytes.toBytes("startKey"); + long regionId = System.currentTimeMillis(); + int replicaId = 42; + + // test without replicaId + byte[] regionName = RegionInfoBuilder.createRegionName(tableName, startKey, regionId, false); + + byte[][] fields = RegionInfoBuilder.parseRegionName(regionName); + assertArrayEquals(Bytes.toString(fields[0]),tableName.getName(), fields[0]); + assertArrayEquals(Bytes.toString(fields[1]),startKey, fields[1]); + assertArrayEquals(Bytes.toString(fields[2]), Bytes.toBytes(Long.toString(regionId)),fields[2]); + assertEquals(3, fields.length); + + // test with replicaId + regionName = RegionInfoBuilder.createRegionName(tableName, startKey, regionId, + replicaId, false); + + fields = RegionInfoBuilder.parseRegionName(regionName); + assertArrayEquals(Bytes.toString(fields[0]),tableName.getName(), fields[0]); + assertArrayEquals(Bytes.toString(fields[1]),startKey, fields[1]); + assertArrayEquals(Bytes.toString(fields[2]), Bytes.toBytes(Long.toString(regionId)),fields[2]); + assertArrayEquals(Bytes.toString(fields[3]), Bytes.toBytes( + String.format(RegionInfoBuilder.REPLICA_ID_FORMAT, replicaId)), fields[3]); + } + + @Test + public void testConvert() { + final TableName tableName = TableName.valueOf("ns1:" + name.getMethodName()); + byte[] startKey = Bytes.toBytes("startKey"); + byte[] endKey = Bytes.toBytes("endKey"); + boolean split = false; + long regionId = System.currentTimeMillis(); + int replicaId = 42; + + + RegionInfo ri = RegionInfoBuilder.newBuilder(tableName) + .setStartKey(startKey) + .setEndKey(endKey) + .setSplit(split) + .setRegionId(regionId) + .setReplicaId(replicaId).build(); + + // convert two times, compare + RegionInfo convertedRi = ProtobufUtil.toRegionInfo( + ProtobufUtil.toProtoRegionInfo(ri)); + + assertEquals(ri, convertedRi); + + // test convert RegionInfo without replicaId + HBaseProtos.RegionInfo info = HBaseProtos.RegionInfo.newBuilder() + .setTableName(HBaseProtos.TableName.newBuilder() + .setQualifier(UnsafeByteOperations.unsafeWrap(tableName.getQualifier())) + .setNamespace(UnsafeByteOperations.unsafeWrap(tableName.getNamespace())) + .build()) + .setStartKey(UnsafeByteOperations.unsafeWrap(startKey)) + .setEndKey(UnsafeByteOperations.unsafeWrap(endKey)) + .setSplit(split) + .setRegionId(regionId) + .build(); + + convertedRi = ProtobufUtil.toRegionInfo(info); + RegionInfo expectedRi = RegionInfoBuilder.newBuilder(tableName) + .setStartKey(startKey) + .setEndKey(endKey) + .setSplit(split) + .setRegionId(regionId) + .setReplicaId(0).build(); + + assertEquals(expectedRi, convertedRi); + } + + @Test + public void testRegionDetailsForDisplay() throws IOException { + byte[] startKey = new byte[] {0x01, 0x01, 0x02, 0x03}; + byte[] endKey = new byte[] {0x01, 0x01, 0x02, 0x04}; + Configuration conf = new Configuration(); + conf.setBoolean("hbase.display.keys", false); + RegionInfo ri = RegionInfoBuilder.newBuilder(TableName.valueOf(name.getMethodName())) + .setStartKey(startKey) + .setEndKey(endKey).build(); + checkEquality(ri, conf); + // check HRIs with non-default replicaId + ri = RegionInfoBuilder.newBuilder(TableName.valueOf(name.getMethodName())) + .setStartKey(startKey) + .setEndKey(endKey) + .setSplit(false) + .setRegionId(System.currentTimeMillis()) + .setReplicaId(1).build(); + checkEquality(ri, conf); + Assert.assertArrayEquals(RegionInfoBuilder.HIDDEN_END_KEY, + RegionInfoBuilder.getEndKeyForDisplay(ri, conf)); + Assert.assertArrayEquals(RegionInfoBuilder.HIDDEN_START_KEY, + RegionInfoBuilder.getStartKeyForDisplay(ri, conf)); + + RegionState state = new RegionState(convert(ri), RegionState.State.OPEN); + String descriptiveNameForDisplay = + RegionInfoBuilder.getDescriptiveNameFromRegionStateForDisplay(state, conf); + checkDescriptiveNameEquality(descriptiveNameForDisplay,state.toDescriptiveString(), startKey); + + conf.setBoolean("hbase.display.keys", true); + Assert.assertArrayEquals(endKey, RegionInfoBuilder.getEndKeyForDisplay(ri, conf)); + Assert.assertArrayEquals(startKey, RegionInfoBuilder.getStartKeyForDisplay(ri, conf)); + Assert.assertEquals(state.toDescriptiveString(), + RegionInfoBuilder.getDescriptiveNameFromRegionStateForDisplay(state, conf)); + } + + private void checkDescriptiveNameEquality(String descriptiveNameForDisplay, String origDesc, + byte[] startKey) { + // except for the "hidden-start-key" substring everything else should exactly match + String firstPart = descriptiveNameForDisplay.substring(0, + descriptiveNameForDisplay.indexOf(new String(RegionInfoBuilder.HIDDEN_START_KEY))); + String secondPart = descriptiveNameForDisplay.substring( + descriptiveNameForDisplay.indexOf(new String(RegionInfoBuilder.HIDDEN_START_KEY)) + + RegionInfoBuilder.HIDDEN_START_KEY.length); + String firstPartOrig = origDesc.substring(0, + origDesc.indexOf(Bytes.toStringBinary(startKey))); + String secondPartOrig = origDesc.substring( + origDesc.indexOf(Bytes.toStringBinary(startKey)) + + Bytes.toStringBinary(startKey).length()); + assert(firstPart.equals(firstPartOrig)); + assert(secondPart.equals(secondPartOrig)); + } + + private void checkEquality(RegionInfo ri, Configuration conf) throws IOException { + byte[] modifiedRegionName = RegionInfoBuilder.getRegionNameForDisplay(ri, conf); + byte[][] modifiedRegionNameParts = RegionInfoBuilder.parseRegionName(modifiedRegionName); + byte[][] regionNameParts = RegionInfoBuilder.parseRegionName(ri.getRegionName()); + + //same number of parts + assert(modifiedRegionNameParts.length == regionNameParts.length); + System.out.println("modifiedRegionNameParts: " + modifiedRegionNameParts.length); + System.out.println("part num: " + regionNameParts.length); + System.out.println("YY: " + new String(ri.getRegionName())); + System.out.println("YY: " + new String(regionNameParts[1])); + for (int i = 0; i < regionNameParts.length; i++) { + // all parts should match except for [1] where in the modified one, + // we should have "hidden_start_key" + if (i != 1) { + Assert.assertArrayEquals(regionNameParts[i], modifiedRegionNameParts[i]); + } else { + System.out.println("abv:" + regionNameParts[i].length); + System.out.println("abv:" + modifiedRegionNameParts[i][0]); + Assert.assertNotEquals(regionNameParts[i][0], modifiedRegionNameParts[i][0]); + Assert.assertArrayEquals(modifiedRegionNameParts[1], + RegionInfoBuilder.getStartKeyForDisplay(ri, conf)); + } + } + } + + private HRegionInfo convert(RegionInfo ri) { + HRegionInfo hri = new HRegionInfo( + ri.getTable(), ri.getStartKey(), ri.getEndKey(), ri.isSplit(), ri.getRegionId()); + hri.setOffline(ri.isOffline()); + return hri; + } +} + -- 2.7.4