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.janitor;
019
020import java.io.IOException;
021import org.apache.hadoop.hbase.CatalogFamilyFormat;
022import org.apache.hadoop.hbase.ClientMetaTableAccessor;
023import org.apache.hadoop.hbase.HConstants;
024import org.apache.hadoop.hbase.HRegionLocation;
025import org.apache.hadoop.hbase.RegionLocations;
026import org.apache.hadoop.hbase.ServerName;
027import org.apache.hadoop.hbase.TableName;
028import org.apache.hadoop.hbase.client.RegionInfo;
029import org.apache.hadoop.hbase.client.RegionInfoBuilder;
030import org.apache.hadoop.hbase.client.Result;
031import org.apache.hadoop.hbase.client.TableState;
032import org.apache.hadoop.hbase.master.MasterServices;
033import org.apache.hadoop.hbase.master.RegionState;
034import org.apache.hadoop.hbase.master.ServerManager;
035import org.apache.hadoop.hbase.util.Bytes;
036import org.apache.hadoop.hbase.util.Pair;
037import org.apache.yetus.audience.InterfaceAudience;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041/**
042 * Visitor we use in here in CatalogJanitor to go against hbase:meta table. Generates a Report made
043 * of a collection of split parents and counts of rows in the hbase:meta table. Also runs hbase:meta
044 * consistency checks to generate more report. Report is NOT ready until after this visitor has been
045 * {@link #close()}'d.
046 */
047@InterfaceAudience.Private
048class ReportMakingVisitor implements ClientMetaTableAccessor.CloseableVisitor {
049
050  private static final Logger LOG = LoggerFactory.getLogger(ReportMakingVisitor.class);
051
052  private final MasterServices services;
053  private volatile boolean closed;
054
055  /**
056   * Report is not done until after the close has been called.
057   */
058  private CatalogJanitorReport report = new CatalogJanitorReport();
059
060  /**
061   * RegionInfo from previous row.
062   */
063  private RegionInfo previous = null;
064
065  /**
066   * Keep account of the highest end key seen as we move through hbase:meta. Usually, the current
067   * RegionInfo has the highest end key but if an overlap, this may no longer hold. An overlap may
068   * be a region with startkey 'd' and endkey 'g'. The next region in meta may be 'e' to 'f' and
069   * then 'f' to 'g'. Looking at previous and current meta row, we won't know about the 'd' to 'g'
070   * overlap unless we keep a running 'highest-endpoint-seen'.
071   */
072  private RegionInfo highestEndKeyRegionInfo = null;
073
074  ReportMakingVisitor(MasterServices services) {
075    this.services = services;
076  }
077
078  /**
079   * Do not call until after {@link #close()}. Will throw a {@link RuntimeException} if you do.
080   */
081  CatalogJanitorReport getReport() {
082    if (!this.closed) {
083      throw new RuntimeException("Report not ready until after close()");
084    }
085    return this.report;
086  }
087
088  @Override
089  public boolean visit(Result r) {
090    if (r == null || r.isEmpty()) {
091      return true;
092    }
093    this.report.count++;
094    RegionInfo regionInfo = null;
095    try {
096      regionInfo = metaTableConsistencyCheck(r);
097    } catch (Throwable t) {
098      LOG.warn("Failed consistency check on {}", Bytes.toStringBinary(r.getRow()), t);
099    }
100    if (regionInfo != null) {
101      LOG.trace(regionInfo.toString());
102      if (regionInfo.isSplitParent()) { // splitParent means split and offline.
103        this.report.splitParents.put(regionInfo, r);
104      }
105      if (CatalogFamilyFormat.hasMergeRegions(r.rawCells())) {
106        this.report.mergedRegions.put(regionInfo, r);
107      }
108    }
109    // Returning true means "keep scanning"
110    return true;
111  }
112
113  /**
114   * Check row.
115   * @param metaTableRow Row from hbase:meta table.
116   * @return Returns default regioninfo found in row parse as a convenience to save on having to do
117   *         a double-parse of Result.
118   */
119  private RegionInfo metaTableConsistencyCheck(Result metaTableRow) {
120    RegionInfo ri;
121    // Locations comes back null if the RegionInfo field is empty.
122    // If locations is null, ensure the regioninfo is for sure empty before progressing.
123    // If really empty, report as missing regioninfo! Otherwise, can run server check
124    // and get RegionInfo from locations.
125    RegionLocations locations = CatalogFamilyFormat.getRegionLocations(metaTableRow);
126    if (locations == null) {
127      ri = CatalogFamilyFormat.getRegionInfo(metaTableRow, HConstants.REGIONINFO_QUALIFIER);
128    } else {
129      ri = locations.getDefaultRegionLocation().getRegion();
130      checkServer(locations);
131    }
132
133    if (ri == null) {
134      this.report.emptyRegionInfo.add(metaTableRow.getRow());
135      return ri;
136    }
137
138    if (!Bytes.equals(metaTableRow.getRow(), ri.getRegionName())) {
139      LOG.warn(
140        "INCONSISTENCY: Row name is not equal to serialized info:regioninfo content; "
141          + "row={} {}; See if RegionInfo is referenced in another {} row? Delete?",
142        Bytes.toStringBinary(metaTableRow.getRow()), ri.getRegionNameAsString(),
143        TableName.META_TABLE_NAME);
144      return null;
145    }
146    // Skip split parent region
147    if (ri.isSplitParent()) {
148      return ri;
149    }
150    // If table is disabled, skip integrity check.
151    if (!isTableDisabled(ri)) {
152      if (isTableTransition(ri)) {
153        // HBCK1 used to have a special category for missing start or end keys.
154        // We'll just lump them in as 'holes'.
155
156        // This is a table transition. If this region is not first region, report a hole.
157        if (!ri.isFirst()) {
158          addHole(RegionInfoBuilder.UNDEFINED, ri);
159        }
160        // This is a table transition. If last region was not last region of previous table,
161        // report a hole
162        if (this.previous != null && !this.previous.isLast()) {
163          addHole(this.previous, RegionInfoBuilder.UNDEFINED);
164        }
165      } else {
166        if (!this.previous.isNext(ri)) {
167          if (this.previous.isOverlap(ri)) {
168            addOverlap(this.previous, ri);
169          } else if (ri.isOverlap(this.highestEndKeyRegionInfo)) {
170            // We may have seen a region a few rows back that overlaps this one.
171            addOverlap(this.highestEndKeyRegionInfo, ri);
172          } else if (!this.highestEndKeyRegionInfo.isNext(ri)) {
173            // Need to check the case if this.highestEndKeyRegionInfo.isNext(ri). If no,
174            // report a hole, otherwise, it is ok. For an example,
175            // previous: [aa, bb), ri: [cc, dd), highestEndKeyRegionInfo: [a, cc)
176            // In this case, it should not report a hole, as highestEndKeyRegionInfo covers
177            // the hole between previous and ri.
178            addHole(this.previous, ri);
179          }
180        } else if (ri.isOverlap(this.highestEndKeyRegionInfo)) {
181          // We may have seen a region a few rows back that overlaps this one
182          // even though it properly 'follows' the region just before.
183          addOverlap(this.highestEndKeyRegionInfo, ri);
184        }
185      }
186      this.previous = ri;
187      this.highestEndKeyRegionInfo =
188        MetaFixer.getRegionInfoWithLargestEndKey(this.highestEndKeyRegionInfo, ri);
189    }
190    return ri;
191  }
192
193  private void addOverlap(RegionInfo a, RegionInfo b) {
194    this.report.overlaps.add(new Pair<>(a, b));
195  }
196
197  private void addHole(RegionInfo a, RegionInfo b) {
198    this.report.holes.add(new Pair<>(a, b));
199  }
200
201  /** Returns True if table is disabled or disabling; defaults false! */
202  boolean isTableDisabled(RegionInfo ri) {
203    if (ri == null) {
204      return false;
205    }
206    if (this.services == null) {
207      return false;
208    }
209    if (this.services.getTableStateManager() == null) {
210      return false;
211    }
212    TableState state = null;
213    try {
214      state = this.services.getTableStateManager().getTableState(ri.getTable());
215    } catch (IOException e) {
216      LOG.warn("Failed getting table state", e);
217    }
218    return state != null && state.isDisabledOrDisabling();
219  }
220
221  /**
222   * Run through referenced servers and save off unknown and the dead.
223   */
224  private void checkServer(RegionLocations locations) {
225    if (this.services == null) {
226      // Can't do this test if no services.
227      return;
228    }
229    if (locations == null) {
230      return;
231    }
232    if (locations.getRegionLocations() == null) {
233      return;
234    }
235    // Check referenced servers are known/online. Here we are looking
236    // at both the default replica -- the main replica -- and then replica
237    // locations too.
238    for (HRegionLocation location : locations.getRegionLocations()) {
239      if (location == null) {
240        continue;
241      }
242      ServerName sn = location.getServerName();
243      if (sn == null) {
244        continue;
245      }
246      if (location.getRegion() == null) {
247        LOG.warn("Empty RegionInfo in {}", location);
248        // This should never happen but if it does, will mess up below.
249        continue;
250      }
251      RegionInfo ri = location.getRegion();
252      // Skip split parent region
253      if (ri.isSplitParent()) {
254        continue;
255      }
256      // skip the offline regions which belong to disabled table.
257      if (isTableDisabled(ri)) {
258        continue;
259      }
260      RegionState rs = this.services.getAssignmentManager().getRegionStates().getRegionState(ri);
261      if (rs == null || rs.isClosedOrAbnormallyClosed()) {
262        // If closed against an 'Unknown Server', that is should be fine.
263        continue;
264      }
265      ServerManager.ServerLiveState state =
266        this.services.getServerManager().isServerKnownAndOnline(sn);
267      switch (state) {
268        case UNKNOWN:
269          this.report.unknownServers.add(new Pair<>(ri, sn));
270          break;
271
272        default:
273          break;
274      }
275    }
276  }
277
278  /** Returns True iff first row in hbase:meta or if we've broached a new table in hbase:meta */
279  private boolean isTableTransition(RegionInfo ri) {
280    return this.previous == null || !this.previous.getTable().equals(ri.getTable());
281  }
282
283  @Override
284  public void close() throws IOException {
285    // This is a table transition... after the last region. Check previous.
286    // Should be last region. If not, its a hole on end of laster table.
287    if (this.previous != null && !this.previous.isLast()) {
288      addHole(this.previous, RegionInfoBuilder.UNDEFINED);
289    }
290    this.closed = true;
291  }
292}