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