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