001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase;
019
020import static org.apache.hadoop.hbase.util.FutureUtils.addListener;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collections;
026import java.util.List;
027import java.util.Map;
028import java.util.NavigableMap;
029import java.util.Optional;
030import java.util.SortedMap;
031import java.util.concurrent.CompletableFuture;
032import java.util.regex.Matcher;
033import java.util.regex.Pattern;
034import java.util.stream.Collectors;
035import org.apache.hadoop.hbase.MetaTableAccessor.CollectingVisitor;
036import org.apache.hadoop.hbase.MetaTableAccessor.QueryType;
037import org.apache.hadoop.hbase.MetaTableAccessor.Visitor;
038import org.apache.hadoop.hbase.client.AdvancedScanResultConsumer;
039import org.apache.hadoop.hbase.client.AsyncTable;
040import org.apache.hadoop.hbase.client.Consistency;
041import org.apache.hadoop.hbase.client.Get;
042import org.apache.hadoop.hbase.client.RegionInfo;
043import org.apache.hadoop.hbase.client.RegionReplicaUtil;
044import org.apache.hadoop.hbase.client.Result;
045import org.apache.hadoop.hbase.client.Scan;
046import org.apache.hadoop.hbase.client.Scan.ReadType;
047import org.apache.hadoop.hbase.client.TableState;
048import org.apache.hadoop.hbase.exceptions.DeserializationException;
049import org.apache.hadoop.hbase.util.Bytes;
050import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
051import org.apache.hadoop.hbase.util.Pair;
052import org.apache.yetus.audience.InterfaceAudience;
053import org.slf4j.Logger;
054import org.slf4j.LoggerFactory;
055
056/**
057 * The asynchronous meta table accessor. Used to read/write region and assignment information store
058 * in <code>hbase:meta</code>.
059 * @since 2.0.0
060 */
061@InterfaceAudience.Private
062public class AsyncMetaTableAccessor {
063
064  private static final Logger LOG = LoggerFactory.getLogger(AsyncMetaTableAccessor.class);
065
066
067  /** The delimiter for meta columns for replicaIds &gt; 0 */
068  private static final char META_REPLICA_ID_DELIMITER = '_';
069
070  /** A regex for parsing server columns from meta. See above javadoc for meta layout */
071  private static final Pattern SERVER_COLUMN_PATTERN = Pattern
072      .compile("^server(_[0-9a-fA-F]{4})?$");
073
074  public static CompletableFuture<Boolean> tableExists(AsyncTable<?> metaTable,
075      TableName tableName) {
076    return getTableState(metaTable, tableName).thenApply(Optional::isPresent);
077  }
078
079  public static CompletableFuture<Optional<TableState>> getTableState(AsyncTable<?> metaTable,
080      TableName tableName) {
081    CompletableFuture<Optional<TableState>> future = new CompletableFuture<>();
082    Get get = new Get(tableName.getName()).addColumn(getTableFamily(), getStateColumn());
083    long time = EnvironmentEdgeManager.currentTime();
084    try {
085      get.setTimeRange(0, time);
086      addListener(metaTable.get(get), (result, error) -> {
087        if (error != null) {
088          future.completeExceptionally(error);
089          return;
090        }
091        try {
092          future.complete(getTableState(result));
093        } catch (IOException e) {
094          future.completeExceptionally(e);
095        }
096      });
097    } catch (IOException ioe) {
098      future.completeExceptionally(ioe);
099    }
100    return future;
101  }
102
103  /**
104   * Returns the HRegionLocation from meta for the given region
105   * @param metaTable
106   * @param regionName region we're looking for
107   * @return HRegionLocation for the given region
108   */
109  public static CompletableFuture<Optional<HRegionLocation>> getRegionLocation(
110      AsyncTable<?> metaTable, byte[] regionName) {
111    CompletableFuture<Optional<HRegionLocation>> future = new CompletableFuture<>();
112    try {
113      RegionInfo parsedRegionInfo = MetaTableAccessor.parseRegionInfoFromRegionName(regionName);
114      addListener(metaTable.get(new Get(MetaTableAccessor.getMetaKeyForRegion(parsedRegionInfo))
115        .addFamily(HConstants.CATALOG_FAMILY)), (r, err) -> {
116          if (err != null) {
117            future.completeExceptionally(err);
118            return;
119          }
120          future.complete(getRegionLocations(r)
121            .map(locations -> locations.getRegionLocation(parsedRegionInfo.getReplicaId())));
122        });
123    } catch (IOException parseEx) {
124      LOG.warn("Failed to parse the passed region name: " + Bytes.toStringBinary(regionName));
125      future.completeExceptionally(parseEx);
126    }
127    return future;
128  }
129
130  /**
131   * Returns the HRegionLocation from meta for the given encoded region name
132   * @param metaTable
133   * @param encodedRegionName region we're looking for
134   * @return HRegionLocation for the given region
135   */
136  public static CompletableFuture<Optional<HRegionLocation>> getRegionLocationWithEncodedName(
137      AsyncTable<?> metaTable, byte[] encodedRegionName) {
138    CompletableFuture<Optional<HRegionLocation>> future = new CompletableFuture<>();
139    addListener(
140      metaTable
141        .scanAll(new Scan().setReadType(ReadType.PREAD).addFamily(HConstants.CATALOG_FAMILY)),
142      (results, err) -> {
143        if (err != null) {
144          future.completeExceptionally(err);
145          return;
146        }
147        String encodedRegionNameStr = Bytes.toString(encodedRegionName);
148        results.stream().filter(result -> !result.isEmpty())
149          .filter(result -> MetaTableAccessor.getRegionInfo(result) != null).forEach(result -> {
150            getRegionLocations(result).ifPresent(locations -> {
151              for (HRegionLocation location : locations.getRegionLocations()) {
152                if (location != null &&
153                  encodedRegionNameStr.equals(location.getRegion().getEncodedName())) {
154                  future.complete(Optional.of(location));
155                  return;
156                }
157              }
158            });
159          });
160        future.complete(Optional.empty());
161      });
162    return future;
163  }
164
165  private static Optional<TableState> getTableState(Result r) throws IOException {
166    Cell cell = r.getColumnLatestCell(getTableFamily(), getStateColumn());
167    if (cell == null) return Optional.empty();
168    try {
169      return Optional.of(TableState.parseFrom(
170        TableName.valueOf(r.getRow()),
171        Arrays.copyOfRange(cell.getValueArray(), cell.getValueOffset(), cell.getValueOffset()
172            + cell.getValueLength())));
173    } catch (DeserializationException e) {
174      throw new IOException("Failed to parse table state from result: " + r, e);
175    }
176  }
177
178  /**
179   * Used to get all region locations for the specific table.
180   * @param metaTable
181   * @param tableName table we're looking for, can be null for getting all regions
182   * @return the list of region locations. The return value will be wrapped by a
183   *         {@link CompletableFuture}.
184   */
185  public static CompletableFuture<List<HRegionLocation>> getTableHRegionLocations(
186      AsyncTable<AdvancedScanResultConsumer> metaTable, TableName tableName) {
187    CompletableFuture<List<HRegionLocation>> future = new CompletableFuture<>();
188    addListener(getTableRegionsAndLocations(metaTable, tableName, true), (locations, err) -> {
189      if (err != null) {
190        future.completeExceptionally(err);
191      } else if (locations == null || locations.isEmpty()) {
192        future.complete(Collections.emptyList());
193      } else {
194        List<HRegionLocation> regionLocations =
195          locations.stream().map(loc -> new HRegionLocation(loc.getFirst(), loc.getSecond()))
196            .collect(Collectors.toList());
197        future.complete(regionLocations);
198      }
199    });
200    return future;
201  }
202
203  /**
204   * Used to get table regions' info and server.
205   * @param metaTable
206   * @param tableName table we're looking for, can be null for getting all regions
207   * @param excludeOfflinedSplitParents don't return split parents
208   * @return the list of regioninfos and server. The return value will be wrapped by a
209   *         {@link CompletableFuture}.
210   */
211  private static CompletableFuture<List<Pair<RegionInfo, ServerName>>> getTableRegionsAndLocations(
212      final AsyncTable<AdvancedScanResultConsumer> metaTable,
213      final TableName tableName, final boolean excludeOfflinedSplitParents) {
214    CompletableFuture<List<Pair<RegionInfo, ServerName>>> future = new CompletableFuture<>();
215    if (TableName.META_TABLE_NAME.equals(tableName)) {
216      future.completeExceptionally(new IOException(
217          "This method can't be used to locate meta regions;" + " use MetaTableLocator instead"));
218    }
219
220    // Make a version of CollectingVisitor that collects RegionInfo and ServerAddress
221    CollectingVisitor<Pair<RegionInfo, ServerName>> visitor =
222      new CollectingVisitor<Pair<RegionInfo, ServerName>>() {
223      private RegionLocations current = null;
224
225      @Override
226      public boolean visit(Result r) throws IOException {
227        Optional<RegionLocations> currentRegionLocations = getRegionLocations(r);
228        current = currentRegionLocations.orElse(null);
229        if (current == null || current.getRegionLocation().getRegion() == null) {
230          LOG.warn("No serialized RegionInfo in " + r);
231          return true;
232        }
233        RegionInfo hri = current.getRegionLocation().getRegion();
234        if (excludeOfflinedSplitParents && hri.isSplitParent()) return true;
235        // Else call super and add this Result to the collection.
236        return super.visit(r);
237      }
238
239      @Override
240      void add(Result r) {
241        if (current == null) {
242          return;
243        }
244        for (HRegionLocation loc : current.getRegionLocations()) {
245          if (loc != null) {
246            this.results.add(new Pair<RegionInfo, ServerName>(loc.getRegion(), loc
247                .getServerName()));
248          }
249        }
250      }
251    };
252
253    addListener(scanMeta(metaTable, tableName, QueryType.REGION, visitor), (v, error) -> {
254      if (error != null) {
255        future.completeExceptionally(error);
256        return;
257      }
258      future.complete(visitor.getResults());
259    });
260    return future;
261  }
262
263  /**
264   * Performs a scan of META table for given table.
265   * @param metaTable
266   * @param tableName table withing we scan
267   * @param type scanned part of meta
268   * @param visitor Visitor invoked against each row
269   */
270  private static CompletableFuture<Void> scanMeta(AsyncTable<AdvancedScanResultConsumer> metaTable,
271      TableName tableName, QueryType type, final Visitor visitor) {
272    return scanMeta(metaTable, getTableStartRowForMeta(tableName, type),
273      getTableStopRowForMeta(tableName, type), type, Integer.MAX_VALUE, visitor);
274  }
275
276  /**
277   * Performs a scan of META table for given table.
278   * @param metaTable
279   * @param startRow Where to start the scan
280   * @param stopRow Where to stop the scan
281   * @param type scanned part of meta
282   * @param maxRows maximum rows to return
283   * @param visitor Visitor invoked against each row
284   */
285  private static CompletableFuture<Void> scanMeta(AsyncTable<AdvancedScanResultConsumer> metaTable,
286      byte[] startRow, byte[] stopRow, QueryType type, int maxRows, final Visitor visitor) {
287    int rowUpperLimit = maxRows > 0 ? maxRows : Integer.MAX_VALUE;
288    Scan scan = getMetaScan(metaTable, rowUpperLimit);
289    for (byte[] family : type.getFamilies()) {
290      scan.addFamily(family);
291    }
292    if (startRow != null) {
293      scan.withStartRow(startRow);
294    }
295    if (stopRow != null) {
296      scan.withStopRow(stopRow);
297    }
298
299    if (LOG.isDebugEnabled()) {
300      LOG.debug("Scanning META" + " starting at row=" + Bytes.toStringBinary(scan.getStartRow())
301          + " stopping at row=" + Bytes.toStringBinary(scan.getStopRow()) + " for max="
302          + rowUpperLimit + " with caching=" + scan.getCaching());
303    }
304
305    CompletableFuture<Void> future = new CompletableFuture<Void>();
306    metaTable.scan(scan, new MetaTableScanResultConsumer(rowUpperLimit, visitor, future));
307    return future;
308  }
309
310  private static final class MetaTableScanResultConsumer implements AdvancedScanResultConsumer {
311
312    private int currentRowCount;
313
314    private final int rowUpperLimit;
315
316    private final Visitor visitor;
317
318    private final CompletableFuture<Void> future;
319
320    MetaTableScanResultConsumer(int rowUpperLimit, Visitor visitor,
321        CompletableFuture<Void> future) {
322      this.rowUpperLimit = rowUpperLimit;
323      this.visitor = visitor;
324      this.future = future;
325      this.currentRowCount = 0;
326    }
327
328    @Override
329    public void onError(Throwable error) {
330      future.completeExceptionally(error);
331    }
332
333    @Override
334    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "NP_NONNULL_PARAM_VIOLATION",
335      justification = "https://github.com/findbugsproject/findbugs/issues/79")
336    public void onComplete() {
337      future.complete(null);
338    }
339
340    @Override
341    public void onNext(Result[] results, ScanController controller) {
342      boolean terminateScan = false;
343      for (Result result : results) {
344        try {
345          if (!visitor.visit(result)) {
346            terminateScan = true;
347            break;
348          }
349        } catch (Exception e) {
350          future.completeExceptionally(e);
351          terminateScan = true;
352          break;
353        }
354        if (++currentRowCount >= rowUpperLimit) {
355          terminateScan = true;
356          break;
357        }
358      }
359      if (terminateScan) {
360        controller.terminate();
361      }
362    }
363  }
364
365  private static Scan getMetaScan(AsyncTable<?> metaTable, int rowUpperLimit) {
366    Scan scan = new Scan();
367    int scannerCaching = metaTable.getConfiguration().getInt(HConstants.HBASE_META_SCANNER_CACHING,
368      HConstants.DEFAULT_HBASE_META_SCANNER_CACHING);
369    if (metaTable.getConfiguration().getBoolean(HConstants.USE_META_REPLICAS,
370      HConstants.DEFAULT_USE_META_REPLICAS)) {
371      scan.setConsistency(Consistency.TIMELINE);
372    }
373    if (rowUpperLimit <= scannerCaching) {
374      scan.setLimit(rowUpperLimit);
375    }
376    int rows = Math.min(rowUpperLimit, scannerCaching);
377    scan.setCaching(rows);
378    return scan;
379  }
380
381  /**
382   * Returns an HRegionLocationList extracted from the result.
383   * @return an HRegionLocationList containing all locations for the region range or null if we
384   *         can't deserialize the result.
385   */
386  private static Optional<RegionLocations> getRegionLocations(final Result r) {
387    if (r == null) return Optional.empty();
388    Optional<RegionInfo> regionInfo = getHRegionInfo(r, getRegionInfoColumn());
389    if (!regionInfo.isPresent()) return Optional.empty();
390
391    List<HRegionLocation> locations = new ArrayList<HRegionLocation>(1);
392    NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyMap = r.getNoVersionMap();
393
394    locations.add(getRegionLocation(r, regionInfo.get(), 0));
395
396    NavigableMap<byte[], byte[]> infoMap = familyMap.get(getCatalogFamily());
397    if (infoMap == null) return Optional.of(new RegionLocations(locations));
398
399    // iterate until all serverName columns are seen
400    int replicaId = 0;
401    byte[] serverColumn = getServerColumn(replicaId);
402    SortedMap<byte[], byte[]> serverMap = null;
403    serverMap = infoMap.tailMap(serverColumn, false);
404
405    if (serverMap.isEmpty()) return Optional.of(new RegionLocations(locations));
406
407    for (Map.Entry<byte[], byte[]> entry : serverMap.entrySet()) {
408      replicaId = parseReplicaIdFromServerColumn(entry.getKey());
409      if (replicaId < 0) {
410        break;
411      }
412      HRegionLocation location = getRegionLocation(r, regionInfo.get(), replicaId);
413      // In case the region replica is newly created, it's location might be null. We usually do not
414      // have HRL's in RegionLocations object with null ServerName. They are handled as null HRLs.
415      if (location == null || location.getServerName() == null) {
416        locations.add(null);
417      } else {
418        locations.add(location);
419      }
420    }
421
422    return Optional.of(new RegionLocations(locations));
423  }
424
425  /**
426   * Returns the HRegionLocation parsed from the given meta row Result
427   * for the given regionInfo and replicaId. The regionInfo can be the default region info
428   * for the replica.
429   * @param r the meta row result
430   * @param regionInfo RegionInfo for default replica
431   * @param replicaId the replicaId for the HRegionLocation
432   * @return HRegionLocation parsed from the given meta row Result for the given replicaId
433   */
434  private static HRegionLocation getRegionLocation(final Result r, final RegionInfo regionInfo,
435      final int replicaId) {
436    Optional<ServerName> serverName = getServerName(r, replicaId);
437    long seqNum = getSeqNumDuringOpen(r, replicaId);
438    RegionInfo replicaInfo = RegionReplicaUtil.getRegionInfoForReplica(regionInfo, replicaId);
439    return new HRegionLocation(replicaInfo, serverName.orElse(null), seqNum);
440  }
441
442  /**
443   * Returns a {@link ServerName} from catalog table {@link Result}.
444   * @param r Result to pull from
445   * @return A ServerName instance.
446   */
447  private static Optional<ServerName> getServerName(final Result r, final int replicaId) {
448    byte[] serverColumn = getServerColumn(replicaId);
449    Cell cell = r.getColumnLatestCell(getCatalogFamily(), serverColumn);
450    if (cell == null || cell.getValueLength() == 0) return Optional.empty();
451    String hostAndPort = Bytes.toString(cell.getValueArray(), cell.getValueOffset(),
452      cell.getValueLength());
453    byte[] startcodeColumn = getStartCodeColumn(replicaId);
454    cell = r.getColumnLatestCell(getCatalogFamily(), startcodeColumn);
455    if (cell == null || cell.getValueLength() == 0) return Optional.empty();
456    try {
457      return Optional.of(ServerName.valueOf(hostAndPort,
458        Bytes.toLong(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength())));
459    } catch (IllegalArgumentException e) {
460      LOG.error("Ignoring invalid region for server " + hostAndPort + "; cell=" + cell, e);
461      return Optional.empty();
462    }
463  }
464
465  /**
466   * The latest seqnum that the server writing to meta observed when opening the region.
467   * E.g. the seqNum when the result of {@link #getServerName(Result, int)} was written.
468   * @param r Result to pull the seqNum from
469   * @return SeqNum, or HConstants.NO_SEQNUM if there's no value written.
470   */
471  private static long getSeqNumDuringOpen(final Result r, final int replicaId) {
472    Cell cell = r.getColumnLatestCell(getCatalogFamily(), getSeqNumColumn(replicaId));
473    if (cell == null || cell.getValueLength() == 0) return HConstants.NO_SEQNUM;
474    return Bytes.toLong(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
475  }
476
477  /**
478   * @param tableName table we're working with
479   * @return start row for scanning META according to query type
480   */
481  private static byte[] getTableStartRowForMeta(TableName tableName, QueryType type) {
482    if (tableName == null) {
483      return null;
484    }
485    switch (type) {
486      case REGION:
487      case REPLICATION: {
488        byte[] startRow = new byte[tableName.getName().length + 2];
489        System.arraycopy(tableName.getName(), 0, startRow, 0, tableName.getName().length);
490        startRow[startRow.length - 2] = HConstants.DELIMITER;
491        startRow[startRow.length - 1] = HConstants.DELIMITER;
492        return startRow;
493      }
494      case ALL:
495      case TABLE:
496      default: {
497        return tableName.getName();
498      }
499    }
500  }
501
502  /**
503   * @param tableName table we're working with
504   * @return stop row for scanning META according to query type
505   */
506  private static byte[] getTableStopRowForMeta(TableName tableName, QueryType type) {
507    if (tableName == null) {
508      return null;
509    }
510    final byte[] stopRow;
511    switch (type) {
512      case REGION:
513      case REPLICATION: {
514        stopRow = new byte[tableName.getName().length + 3];
515        System.arraycopy(tableName.getName(), 0, stopRow, 0, tableName.getName().length);
516        stopRow[stopRow.length - 3] = ' ';
517        stopRow[stopRow.length - 2] = HConstants.DELIMITER;
518        stopRow[stopRow.length - 1] = HConstants.DELIMITER;
519        break;
520      }
521      case ALL:
522      case TABLE:
523      default: {
524        stopRow = new byte[tableName.getName().length + 1];
525        System.arraycopy(tableName.getName(), 0, stopRow, 0, tableName.getName().length);
526        stopRow[stopRow.length - 1] = ' ';
527        break;
528      }
529    }
530    return stopRow;
531  }
532
533  /**
534   * Returns the RegionInfo object from the column {@link HConstants#CATALOG_FAMILY} and
535   * <code>qualifier</code> of the catalog table result.
536   * @param r a Result object from the catalog table scan
537   * @param qualifier Column family qualifier
538   * @return An RegionInfo instance.
539   */
540  private static Optional<RegionInfo> getHRegionInfo(final Result r, byte[] qualifier) {
541    Cell cell = r.getColumnLatestCell(getCatalogFamily(), qualifier);
542    if (cell == null) return Optional.empty();
543    return Optional.ofNullable(RegionInfo.parseFromOrNull(cell.getValueArray(),
544      cell.getValueOffset(), cell.getValueLength()));
545  }
546
547  /**
548   * Returns the column family used for meta columns.
549   * @return HConstants.CATALOG_FAMILY.
550   */
551  private static byte[] getCatalogFamily() {
552    return HConstants.CATALOG_FAMILY;
553  }
554
555  /**
556   * Returns the column family used for table columns.
557   * @return HConstants.TABLE_FAMILY.
558   */
559  private static byte[] getTableFamily() {
560    return HConstants.TABLE_FAMILY;
561  }
562
563  /**
564   * Returns the column qualifier for serialized region info
565   * @return HConstants.REGIONINFO_QUALIFIER
566   */
567  private static byte[] getRegionInfoColumn() {
568    return HConstants.REGIONINFO_QUALIFIER;
569  }
570
571  /**
572   * Returns the column qualifier for serialized table state
573   * @return HConstants.TABLE_STATE_QUALIFIER
574   */
575  private static byte[] getStateColumn() {
576    return HConstants.TABLE_STATE_QUALIFIER;
577  }
578
579  /**
580   * Returns the column qualifier for server column for replicaId
581   * @param replicaId the replicaId of the region
582   * @return a byte[] for server column qualifier
583   */
584  private static byte[] getServerColumn(int replicaId) {
585    return replicaId == 0
586      ? HConstants.SERVER_QUALIFIER
587      : Bytes.toBytes(HConstants.SERVER_QUALIFIER_STR + META_REPLICA_ID_DELIMITER
588      + String.format(RegionInfo.REPLICA_ID_FORMAT, replicaId));
589  }
590
591  /**
592   * Returns the column qualifier for server start code column for replicaId
593   * @param replicaId the replicaId of the region
594   * @return a byte[] for server start code column qualifier
595   */
596  private static byte[] getStartCodeColumn(int replicaId) {
597    return replicaId == 0
598      ? HConstants.STARTCODE_QUALIFIER
599      : Bytes.toBytes(HConstants.STARTCODE_QUALIFIER_STR + META_REPLICA_ID_DELIMITER
600      + String.format(RegionInfo.REPLICA_ID_FORMAT, replicaId));
601  }
602
603  /**
604   * Returns the column qualifier for seqNum column for replicaId
605   * @param replicaId the replicaId of the region
606   * @return a byte[] for seqNum column qualifier
607   */
608  private static byte[] getSeqNumColumn(int replicaId) {
609    return replicaId == 0
610      ? HConstants.SEQNUM_QUALIFIER
611      : Bytes.toBytes(HConstants.SEQNUM_QUALIFIER_STR + META_REPLICA_ID_DELIMITER
612      + String.format(RegionInfo.REPLICA_ID_FORMAT, replicaId));
613  }
614
615  /**
616   * Parses the replicaId from the server column qualifier. See top of the class javadoc
617   * for the actual meta layout
618   * @param serverColumn the column qualifier
619   * @return an int for the replicaId
620   */
621  private static int parseReplicaIdFromServerColumn(byte[] serverColumn) {
622    String serverStr = Bytes.toString(serverColumn);
623
624    Matcher matcher = SERVER_COLUMN_PATTERN.matcher(serverStr);
625    if (matcher.matches() && matcher.groupCount() > 0) {
626      String group = matcher.group(1);
627      if (group != null && group.length() > 0) {
628        return Integer.parseInt(group.substring(1), 16);
629      } else {
630        return 0;
631      }
632    }
633    return -1;
634  }
635}