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}