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.master.assignment;
019
020import java.io.IOException;
021import java.util.Collections;
022import java.util.List;
023import java.util.SortedMap;
024import java.util.TreeMap;
025
026import org.apache.hadoop.hbase.Cell;
027import org.apache.hadoop.hbase.CellBuilderFactory;
028import org.apache.hadoop.hbase.CellBuilderType;
029import org.apache.hadoop.hbase.HConstants;
030import org.apache.hadoop.hbase.HRegionLocation;
031import org.apache.hadoop.hbase.MetaTableAccessor;
032import org.apache.hadoop.hbase.RegionLocations;
033import org.apache.hadoop.hbase.ServerName;
034import org.apache.hadoop.hbase.TableName;
035import org.apache.hadoop.hbase.client.Put;
036import org.apache.hadoop.hbase.client.RegionInfo;
037import org.apache.hadoop.hbase.client.Result;
038import org.apache.hadoop.hbase.client.Table;
039import org.apache.hadoop.hbase.client.TableDescriptor;
040import org.apache.hadoop.hbase.master.MasterFileSystem;
041import org.apache.hadoop.hbase.master.MasterServices;
042import org.apache.hadoop.hbase.master.RegionState.State;
043import org.apache.hadoop.hbase.procedure2.Procedure;
044import org.apache.hadoop.hbase.procedure2.util.StringUtils;
045import org.apache.hadoop.hbase.util.Bytes;
046import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
047import org.apache.hadoop.hbase.wal.WALSplitUtil;
048import org.apache.hadoop.hbase.zookeeper.MetaTableLocator;
049import org.apache.yetus.audience.InterfaceAudience;
050import org.apache.zookeeper.KeeperException;
051import org.slf4j.Logger;
052import org.slf4j.LoggerFactory;
053
054import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
055
056/**
057 * Store Region State to hbase:meta table.
058 */
059@InterfaceAudience.Private
060public class RegionStateStore {
061  private static final Logger LOG = LoggerFactory.getLogger(RegionStateStore.class);
062
063  /** The delimiter for meta columns for replicaIds > 0 */
064  protected static final char META_REPLICA_ID_DELIMITER = '_';
065
066  private final MasterServices master;
067
068  public RegionStateStore(final MasterServices master) {
069    this.master = master;
070  }
071
072  public interface RegionStateVisitor {
073    void visitRegionState(Result result, RegionInfo regionInfo, State state,
074      ServerName regionLocation, ServerName lastHost, long openSeqNum);
075  }
076
077  public void visitMeta(final RegionStateVisitor visitor) throws IOException {
078    MetaTableAccessor.fullScanRegions(master.getConnection(), new MetaTableAccessor.Visitor() {
079      final boolean isDebugEnabled = LOG.isDebugEnabled();
080
081      @Override
082      public boolean visit(final Result r) throws IOException {
083        if (r !=  null && !r.isEmpty()) {
084          long st = 0;
085          if (LOG.isTraceEnabled()) {
086            st = System.currentTimeMillis();
087          }
088          visitMetaEntry(visitor, r);
089          if (LOG.isTraceEnabled()) {
090            long et = System.currentTimeMillis();
091            LOG.trace("[T] LOAD META PERF " + StringUtils.humanTimeDiff(et - st));
092          }
093        } else if (isDebugEnabled) {
094          LOG.debug("NULL result from meta - ignoring but this is strange.");
095        }
096        return true;
097      }
098    });
099  }
100
101  private void visitMetaEntry(final RegionStateVisitor visitor, final Result result)
102      throws IOException {
103    final RegionLocations rl = MetaTableAccessor.getRegionLocations(result);
104    if (rl == null) return;
105
106    final HRegionLocation[] locations = rl.getRegionLocations();
107    if (locations == null) return;
108
109    for (int i = 0; i < locations.length; ++i) {
110      final HRegionLocation hrl = locations[i];
111      if (hrl == null) continue;
112
113      final RegionInfo regionInfo = hrl.getRegion();
114      if (regionInfo == null) continue;
115
116      final int replicaId = regionInfo.getReplicaId();
117      final State state = getRegionState(result, regionInfo);
118
119      final ServerName lastHost = hrl.getServerName();
120      final ServerName regionLocation = getRegionServer(result, replicaId);
121      final long openSeqNum = hrl.getSeqNum();
122
123      // TODO: move under trace, now is visible for debugging
124      LOG.info(
125        "Load hbase:meta entry region={}, regionState={}, lastHost={}, " +
126          "regionLocation={}, openSeqNum={}",
127        regionInfo.getEncodedName(), state, lastHost, regionLocation, openSeqNum);
128      visitor.visitRegionState(result, regionInfo, state, regionLocation, lastHost, openSeqNum);
129    }
130  }
131
132  public void updateRegionLocation(RegionStateNode regionStateNode)
133      throws IOException {
134    if (regionStateNode.getRegionInfo().isMetaRegion()) {
135      updateMetaLocation(regionStateNode.getRegionInfo(), regionStateNode.getRegionLocation(),
136        regionStateNode.getState());
137    } else {
138      long openSeqNum = regionStateNode.getState() == State.OPEN ? regionStateNode.getOpenSeqNum()
139        : HConstants.NO_SEQNUM;
140      updateUserRegionLocation(regionStateNode.getRegionInfo(), regionStateNode.getState(),
141        regionStateNode.getRegionLocation(), openSeqNum,
142        // The regionStateNode may have no procedure in a test scenario; allow for this.
143        regionStateNode.getProcedure() != null ? regionStateNode.getProcedure().getProcId()
144          : Procedure.NO_PROC_ID);
145    }
146  }
147
148  private void updateMetaLocation(RegionInfo regionInfo, ServerName serverName, State state)
149      throws IOException {
150    try {
151      MetaTableLocator.setMetaLocation(master.getZooKeeper(), serverName, regionInfo.getReplicaId(),
152        state);
153    } catch (KeeperException e) {
154      throw new IOException(e);
155    }
156  }
157
158  private void updateUserRegionLocation(RegionInfo regionInfo, State state,
159      ServerName regionLocation, long openSeqNum,
160       long pid) throws IOException {
161    long time = EnvironmentEdgeManager.currentTime();
162    final int replicaId = regionInfo.getReplicaId();
163    final Put put = new Put(MetaTableAccessor.getMetaKeyForRegion(regionInfo), time);
164    MetaTableAccessor.addRegionInfo(put, regionInfo);
165    final StringBuilder info =
166      new StringBuilder("pid=").append(pid).append(" updating hbase:meta row=")
167        .append(regionInfo.getEncodedName()).append(", regionState=").append(state);
168    if (openSeqNum >= 0) {
169      Preconditions.checkArgument(state == State.OPEN && regionLocation != null,
170          "Open region should be on a server");
171      MetaTableAccessor.addLocation(put, regionLocation, openSeqNum, replicaId);
172      // only update replication barrier for default replica
173      if (regionInfo.getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID &&
174        hasGlobalReplicationScope(regionInfo.getTable())) {
175        MetaTableAccessor.addReplicationBarrier(put, openSeqNum);
176        info.append(", repBarrier=").append(openSeqNum);
177      }
178      info.append(", openSeqNum=").append(openSeqNum);
179      info.append(", regionLocation=").append(regionLocation);
180    } else if (regionLocation != null) {
181      // Ideally, if no regionLocation, write null to the hbase:meta but this will confuse clients
182      // currently; they want a server to hit. TODO: Make clients wait if no location.
183      put.add(CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY)
184          .setRow(put.getRow())
185          .setFamily(HConstants.CATALOG_FAMILY)
186          .setQualifier(getServerNameColumn(replicaId))
187          .setTimestamp(put.getTimestamp())
188          .setType(Cell.Type.Put)
189          .setValue(Bytes.toBytes(regionLocation.getServerName()))
190          .build());
191      info.append(", regionLocation=").append(regionLocation);
192    }
193    put.add(CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY)
194        .setRow(put.getRow())
195        .setFamily(HConstants.CATALOG_FAMILY)
196        .setQualifier(getStateColumn(replicaId))
197        .setTimestamp(put.getTimestamp())
198        .setType(Cell.Type.Put)
199        .setValue(Bytes.toBytes(state.name()))
200        .build());
201    LOG.info(info.toString());
202    updateRegionLocation(regionInfo, state, put);
203  }
204
205  private void updateRegionLocation(RegionInfo regionInfo, State state, Put put)
206      throws IOException {
207    try (Table table = master.getConnection().getTable(TableName.META_TABLE_NAME)) {
208      table.put(put);
209    } catch (IOException e) {
210      // TODO: Revist!!!! Means that if a server is loaded, then we will abort our host!
211      // In tests we abort the Master!
212      String msg = String.format("FAILED persisting region=%s state=%s",
213        regionInfo.getShortNameToLog(), state);
214      LOG.error(msg, e);
215      master.abort(msg, e);
216      throw e;
217    }
218  }
219
220  private long getOpenSeqNumForParentRegion(RegionInfo region) throws IOException {
221    MasterFileSystem fs = master.getMasterFileSystem();
222    long maxSeqId = WALSplitUtil.getMaxRegionSequenceId(master.getConfiguration(), region,
223      fs::getFileSystem, fs::getWALFileSystem);
224    return maxSeqId > 0 ? maxSeqId + 1 : HConstants.NO_SEQNUM;
225  }
226
227  // ============================================================================================
228  //  Update Region Splitting State helpers
229  // ============================================================================================
230  public void splitRegion(RegionInfo parent, RegionInfo hriA, RegionInfo hriB,
231      ServerName serverName) throws IOException {
232    TableDescriptor htd = getTableDescriptor(parent.getTable());
233    long parentOpenSeqNum = HConstants.NO_SEQNUM;
234    if (htd.hasGlobalReplicationScope()) {
235      parentOpenSeqNum = getOpenSeqNumForParentRegion(parent);
236    }
237    MetaTableAccessor.splitRegion(master.getConnection(), parent, parentOpenSeqNum, hriA, hriB,
238      serverName, getRegionReplication(htd));
239  }
240
241  // ============================================================================================
242  //  Update Region Merging State helpers
243  // ============================================================================================
244  public void mergeRegions(RegionInfo child, RegionInfo [] parents, ServerName serverName)
245      throws IOException {
246    TableDescriptor htd = getTableDescriptor(child.getTable());
247    boolean globalScope = htd.hasGlobalReplicationScope();
248    SortedMap<RegionInfo, Long> parentSeqNums = new TreeMap<>();
249    for (RegionInfo ri: parents) {
250      parentSeqNums.put(ri, globalScope? getOpenSeqNumForParentRegion(ri): -1);
251    }
252    MetaTableAccessor.mergeRegions(master.getConnection(), child, parentSeqNums,
253        serverName, getRegionReplication(htd));
254  }
255
256  // ============================================================================================
257  //  Delete Region State helpers
258  // ============================================================================================
259  public void deleteRegion(final RegionInfo regionInfo) throws IOException {
260    deleteRegions(Collections.singletonList(regionInfo));
261  }
262
263  public void deleteRegions(final List<RegionInfo> regions) throws IOException {
264    MetaTableAccessor.deleteRegionInfos(master.getConnection(), regions);
265  }
266
267  // ==========================================================================
268  //  Table Descriptors helpers
269  // ==========================================================================
270  private boolean hasGlobalReplicationScope(TableName tableName) throws IOException {
271    return hasGlobalReplicationScope(getTableDescriptor(tableName));
272  }
273
274  private boolean hasGlobalReplicationScope(TableDescriptor htd) {
275    return htd != null ? htd.hasGlobalReplicationScope() : false;
276  }
277
278  private int getRegionReplication(TableDescriptor htd) {
279    return htd != null ? htd.getRegionReplication() : 1;
280  }
281
282  private TableDescriptor getTableDescriptor(TableName tableName) throws IOException {
283    return master.getTableDescriptors().get(tableName);
284  }
285
286  // ==========================================================================
287  //  Server Name
288  // ==========================================================================
289
290  /**
291   * Returns the {@link ServerName} from catalog table {@link Result}
292   * where the region is transitioning. It should be the same as
293   * {@link MetaTableAccessor#getServerName(Result,int)} if the server is at OPEN state.
294   * @param r Result to pull the transitioning server name from
295   * @return A ServerName instance or {@link MetaTableAccessor#getServerName(Result,int)}
296   * if necessary fields not found or empty.
297   */
298  static ServerName getRegionServer(final Result r, int replicaId) {
299    final Cell cell = r.getColumnLatestCell(HConstants.CATALOG_FAMILY,
300        getServerNameColumn(replicaId));
301    if (cell == null || cell.getValueLength() == 0) {
302      RegionLocations locations = MetaTableAccessor.getRegionLocations(r);
303      if (locations != null) {
304        HRegionLocation location = locations.getRegionLocation(replicaId);
305        if (location != null) {
306          return location.getServerName();
307        }
308      }
309      return null;
310    }
311    return ServerName.parseServerName(Bytes.toString(cell.getValueArray(),
312      cell.getValueOffset(), cell.getValueLength()));
313  }
314
315  private static byte[] getServerNameColumn(int replicaId) {
316    return replicaId == 0
317        ? HConstants.SERVERNAME_QUALIFIER
318        : Bytes.toBytes(HConstants.SERVERNAME_QUALIFIER_STR + META_REPLICA_ID_DELIMITER
319          + String.format(RegionInfo.REPLICA_ID_FORMAT, replicaId));
320  }
321
322  // ==========================================================================
323  //  Region State
324  // ==========================================================================
325
326  /**
327   * Pull the region state from a catalog table {@link Result}.
328   * @return the region state, or null if unknown.
329   */
330  public static State getRegionState(final Result r, RegionInfo regionInfo) {
331    Cell cell = r.getColumnLatestCell(HConstants.CATALOG_FAMILY,
332        getStateColumn(regionInfo.getReplicaId()));
333    if (cell == null || cell.getValueLength() == 0) {
334      return null;
335    }
336
337    String state = Bytes.toString(cell.getValueArray(), cell.getValueOffset(),
338        cell.getValueLength());
339    try {
340      return State.valueOf(state);
341    } catch (IllegalArgumentException e) {
342      LOG.warn("BAD value {} in hbase:meta info:state column for region {} , " +
343              "Consider using HBCK2 setRegionState ENCODED_REGION_NAME STATE",
344          state, regionInfo.getEncodedName());
345      return null;
346    }
347  }
348
349  private static byte[] getStateColumn(int replicaId) {
350    return replicaId == 0
351        ? HConstants.STATE_QUALIFIER
352        : Bytes.toBytes(HConstants.STATE_QUALIFIER_STR + META_REPLICA_ID_DELIMITER
353          + String.format(RegionInfo.REPLICA_ID_FORMAT, replicaId));
354  }
355}