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