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}