001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to you under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.hadoop.hbase.quotas; 018 019import java.io.IOException; 020import java.util.HashMap; 021import java.util.Map; 022import java.util.Map.Entry; 023import java.util.concurrent.TimeUnit; 024 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.hbase.ScheduledChore; 027import org.apache.hadoop.hbase.TableName; 028import org.apache.yetus.audience.InterfaceAudience; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031import org.apache.hadoop.hbase.client.Connection; 032import org.apache.hadoop.hbase.client.Result; 033import org.apache.hadoop.hbase.client.ResultScanner; 034import org.apache.hadoop.hbase.client.Table; 035import org.apache.hadoop.hbase.util.Bytes; 036 037/** 038 * A {@link ScheduledChore} which periodically updates the {@link RegionServerSpaceQuotaManager} 039 * with information from the hbase:quota. 040 */ 041@InterfaceAudience.Private 042public class SpaceQuotaRefresherChore extends ScheduledChore { 043 private static final Logger LOG = LoggerFactory.getLogger(SpaceQuotaRefresherChore.class); 044 045 static final String POLICY_REFRESHER_CHORE_PERIOD_KEY = 046 "hbase.regionserver.quotas.policy.refresher.chore.period"; 047 static final int POLICY_REFRESHER_CHORE_PERIOD_DEFAULT = 1000 * 60 * 1; // 1 minute in millis 048 049 static final String POLICY_REFRESHER_CHORE_DELAY_KEY = 050 "hbase.regionserver.quotas.policy.refresher.chore.delay"; 051 static final long POLICY_REFRESHER_CHORE_DELAY_DEFAULT = 1000L * 15L; // 15 seconds in millis 052 053 static final String POLICY_REFRESHER_CHORE_TIMEUNIT_KEY = 054 "hbase.regionserver.quotas.policy.refresher.chore.timeunit"; 055 static final String POLICY_REFRESHER_CHORE_TIMEUNIT_DEFAULT = TimeUnit.MILLISECONDS.name(); 056 057 static final String POLICY_REFRESHER_CHORE_REPORT_PERCENT_KEY = 058 "hbase.regionserver.quotas.policy.refresher.report.percent"; 059 static final double POLICY_REFRESHER_CHORE_REPORT_PERCENT_DEFAULT= 0.95; 060 061 private final RegionServerSpaceQuotaManager manager; 062 private final Connection conn; 063 064 public SpaceQuotaRefresherChore(RegionServerSpaceQuotaManager manager, Connection conn) { 065 super(SpaceQuotaRefresherChore.class.getSimpleName(), 066 manager.getRegionServerServices(), 067 getPeriod(manager.getRegionServerServices().getConfiguration()), 068 getInitialDelay(manager.getRegionServerServices().getConfiguration()), 069 getTimeUnit(manager.getRegionServerServices().getConfiguration())); 070 this.manager = manager; 071 this.conn = conn; 072 } 073 074 @Override 075 protected void chore() { 076 try { 077 if (LOG.isTraceEnabled()) { 078 LOG.trace("Reading current quota snapshots from hbase:quota."); 079 } 080 // Get the snapshots that the quota manager is currently aware of 081 final Map<TableName, SpaceQuotaSnapshot> currentSnapshots = 082 getManager().copyQuotaSnapshots(); 083 // Read the new snapshots from the quota table 084 final Map<TableName, SpaceQuotaSnapshot> newSnapshots = fetchSnapshotsFromQuotaTable(); 085 if (LOG.isTraceEnabled()) { 086 LOG.trace(currentSnapshots.size() + " table quota snapshots are collected, " 087 + "read " + newSnapshots.size() + " from the quota table."); 088 } 089 // Iterate over each new quota snapshot 090 for (Entry<TableName, SpaceQuotaSnapshot> entry : newSnapshots.entrySet()) { 091 final TableName tableName = entry.getKey(); 092 final SpaceQuotaSnapshot newSnapshot = entry.getValue(); 093 // May be null! 094 final SpaceQuotaSnapshot currentSnapshot = currentSnapshots.get(tableName); 095 if (LOG.isTraceEnabled()) { 096 LOG.trace(tableName + ": current=" + currentSnapshot + ", new=" + newSnapshot); 097 } 098 if (!newSnapshot.equals(currentSnapshot)) { 099 // We have a new snapshot. We might need to enforce it or disable the enforcement 100 if (!isInViolation(currentSnapshot) && newSnapshot.getQuotaStatus().isInViolation()) { 101 if (LOG.isTraceEnabled()) { 102 LOG.trace("Enabling " + newSnapshot + " on " + tableName); 103 } 104 getManager().enforceViolationPolicy(tableName, newSnapshot); 105 } 106 if (isInViolation(currentSnapshot) && !newSnapshot.getQuotaStatus().isInViolation()) { 107 if (LOG.isTraceEnabled()) { 108 LOG.trace("Removing quota violation policy on " + tableName); 109 } 110 getManager().disableViolationPolicyEnforcement(tableName); 111 } 112 } 113 } 114 115 // Disable violation policy for all such tables which have been removed in new snapshot 116 for (TableName tableName : currentSnapshots.keySet()) { 117 // check whether table was removed in new snapshot 118 if (!newSnapshots.containsKey(tableName)) { 119 if (LOG.isTraceEnabled()) { 120 LOG.trace("Removing quota violation policy on " + tableName); 121 } 122 getManager().disableViolationPolicyEnforcement(tableName); 123 } 124 } 125 126 // We're intentionally ignoring anything extra with the currentSnapshots. If we were missing 127 // information from the RegionServers to create an accurate SpaceQuotaSnapshot in the Master, 128 // the Master will generate a new SpaceQuotaSnapshot which represents this state. This lets 129 // us avoid having to do anything special with currentSnapshots here. 130 131 // Update the snapshots in the manager 132 getManager().updateQuotaSnapshot(newSnapshots); 133 } catch (IOException e) { 134 LOG.warn( 135 "Caught exception while refreshing enforced quota violation policies, will retry.", e); 136 } 137 } 138 139 /** 140 * Checks if the given <code>snapshot</code> is in violation, allowing the snapshot to be null. 141 * If the snapshot is null, this is interpreted as no snapshot which implies not in violation. 142 * 143 * @param snapshot The snapshot to operate on. 144 * @return true if the snapshot is in violation, false otherwise. 145 */ 146 boolean isInViolation(SpaceQuotaSnapshot snapshot) { 147 if (snapshot == null) { 148 return false; 149 } 150 return snapshot.getQuotaStatus().isInViolation(); 151 } 152 153 /** 154 * Reads all quota snapshots from the quota table. 155 * 156 * @return The current "view" of space use by each table. 157 */ 158 public Map<TableName, SpaceQuotaSnapshot> fetchSnapshotsFromQuotaTable() throws IOException { 159 try (Table quotaTable = getConnection().getTable(QuotaUtil.QUOTA_TABLE_NAME); 160 ResultScanner scanner = quotaTable.getScanner(QuotaTableUtil.makeQuotaSnapshotScan())) { 161 Map<TableName,SpaceQuotaSnapshot> snapshots = new HashMap<>(); 162 for (Result result : scanner) { 163 try { 164 extractQuotaSnapshot(result, snapshots); 165 } catch (IllegalArgumentException e) { 166 final String msg = "Failed to parse result for row " + Bytes.toString(result.getRow()); 167 LOG.error(msg, e); 168 throw new IOException(msg, e); 169 } 170 } 171 return snapshots; 172 } 173 } 174 175 /** 176 * Wrapper around {@link QuotaTableUtil#extractQuotaSnapshot(Result, Map)} for testing. 177 */ 178 void extractQuotaSnapshot(Result result, Map<TableName,SpaceQuotaSnapshot> snapshots) { 179 QuotaTableUtil.extractQuotaSnapshot(result, snapshots); 180 } 181 182 Connection getConnection() { 183 return conn; 184 } 185 186 RegionServerSpaceQuotaManager getManager() { 187 return manager; 188 } 189 190 /** 191 * Extracts the period for the chore from the configuration. 192 * 193 * @param conf The configuration object. 194 * @return The configured chore period or the default value. 195 */ 196 static int getPeriod(Configuration conf) { 197 return conf.getInt(POLICY_REFRESHER_CHORE_PERIOD_KEY, 198 POLICY_REFRESHER_CHORE_PERIOD_DEFAULT); 199 } 200 201 /** 202 * Extracts the initial delay for the chore from the configuration. 203 * 204 * @param conf The configuration object. 205 * @return The configured chore initial delay or the default value. 206 */ 207 static long getInitialDelay(Configuration conf) { 208 return conf.getLong(POLICY_REFRESHER_CHORE_DELAY_KEY, 209 POLICY_REFRESHER_CHORE_DELAY_DEFAULT); 210 } 211 212 /** 213 * Extracts the time unit for the chore period and initial delay from the configuration. The 214 * configuration value for {@link #POLICY_REFRESHER_CHORE_TIMEUNIT_KEY} must correspond to 215 * a {@link TimeUnit} value. 216 * 217 * @param conf The configuration object. 218 * @return The configured time unit for the chore period and initial delay or the default value. 219 */ 220 static TimeUnit getTimeUnit(Configuration conf) { 221 return TimeUnit.valueOf(conf.get(POLICY_REFRESHER_CHORE_TIMEUNIT_KEY, 222 POLICY_REFRESHER_CHORE_TIMEUNIT_DEFAULT)); 223 } 224 225 /** 226 * Extracts the percent of Regions for a table to have been reported to enable quota violation 227 * state change. 228 * 229 * @param conf The configuration object. 230 * @return The percent of regions reported to use. 231 */ 232 static Double getRegionReportPercent(Configuration conf) { 233 return conf.getDouble(POLICY_REFRESHER_CHORE_REPORT_PERCENT_KEY, 234 POLICY_REFRESHER_CHORE_REPORT_PERCENT_DEFAULT); 235 } 236}