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 */ 018 019package org.apache.hadoop.hbase.quotas; 020 021import java.io.IOException; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025 026import org.apache.hadoop.conf.Configuration; 027import org.apache.hadoop.hbase.Cell; 028import org.apache.hadoop.hbase.HColumnDescriptor; 029import org.apache.hadoop.hbase.HConstants; 030import org.apache.hadoop.hbase.HTableDescriptor; 031import org.apache.hadoop.hbase.KeyValueUtil; 032import org.apache.hadoop.hbase.TableName; 033import org.apache.hadoop.hbase.TableNotDisabledException; 034import org.apache.hadoop.hbase.TableNotEnabledException; 035import org.apache.hadoop.hbase.TableNotFoundException; 036import org.apache.yetus.audience.InterfaceAudience; 037import org.apache.yetus.audience.InterfaceStability; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040import org.apache.hadoop.hbase.client.Connection; 041import org.apache.hadoop.hbase.client.Delete; 042import org.apache.hadoop.hbase.client.Get; 043import org.apache.hadoop.hbase.client.Mutation; 044import org.apache.hadoop.hbase.client.Put; 045import org.apache.hadoop.hbase.client.Result; 046import org.apache.hadoop.hbase.client.Table; 047import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas; 048import org.apache.hadoop.hbase.regionserver.BloomType; 049import org.apache.hadoop.hbase.util.Bytes; 050import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 051 052/** 053 * Helper class to interact with the quota table 054 */ 055@InterfaceAudience.Private 056@InterfaceStability.Evolving 057public class QuotaUtil extends QuotaTableUtil { 058 private static final Logger LOG = LoggerFactory.getLogger(QuotaUtil.class); 059 060 public static final String QUOTA_CONF_KEY = "hbase.quota.enabled"; 061 private static final boolean QUOTA_ENABLED_DEFAULT = false; 062 063 /** Table descriptor for Quota internal table */ 064 public static final HTableDescriptor QUOTA_TABLE_DESC = 065 new HTableDescriptor(QUOTA_TABLE_NAME); 066 static { 067 QUOTA_TABLE_DESC.addFamily( 068 new HColumnDescriptor(QUOTA_FAMILY_INFO) 069 .setScope(HConstants.REPLICATION_SCOPE_LOCAL) 070 .setBloomFilterType(BloomType.ROW) 071 .setMaxVersions(1) 072 ); 073 QUOTA_TABLE_DESC.addFamily( 074 new HColumnDescriptor(QUOTA_FAMILY_USAGE) 075 .setScope(HConstants.REPLICATION_SCOPE_LOCAL) 076 .setBloomFilterType(BloomType.ROW) 077 .setMaxVersions(1) 078 ); 079 } 080 081 /** Returns true if the support for quota is enabled */ 082 public static boolean isQuotaEnabled(final Configuration conf) { 083 return conf.getBoolean(QUOTA_CONF_KEY, QUOTA_ENABLED_DEFAULT); 084 } 085 086 /* ========================================================================= 087 * Quota "settings" helpers 088 */ 089 public static void addTableQuota(final Connection connection, final TableName table, 090 final Quotas data) throws IOException { 091 addQuotas(connection, getTableRowKey(table), data); 092 } 093 094 public static void deleteTableQuota(final Connection connection, final TableName table) 095 throws IOException { 096 deleteQuotas(connection, getTableRowKey(table)); 097 } 098 099 public static void addNamespaceQuota(final Connection connection, final String namespace, 100 final Quotas data) throws IOException { 101 addQuotas(connection, getNamespaceRowKey(namespace), data); 102 } 103 104 public static void deleteNamespaceQuota(final Connection connection, final String namespace) 105 throws IOException { 106 deleteQuotas(connection, getNamespaceRowKey(namespace)); 107 } 108 109 public static void addUserQuota(final Connection connection, final String user, 110 final Quotas data) throws IOException { 111 addQuotas(connection, getUserRowKey(user), data); 112 } 113 114 public static void addUserQuota(final Connection connection, final String user, 115 final TableName table, final Quotas data) throws IOException { 116 addQuotas(connection, getUserRowKey(user), getSettingsQualifierForUserTable(table), data); 117 } 118 119 public static void addUserQuota(final Connection connection, final String user, 120 final String namespace, final Quotas data) throws IOException { 121 addQuotas(connection, getUserRowKey(user), 122 getSettingsQualifierForUserNamespace(namespace), data); 123 } 124 125 public static void deleteUserQuota(final Connection connection, final String user) 126 throws IOException { 127 deleteQuotas(connection, getUserRowKey(user)); 128 } 129 130 public static void deleteUserQuota(final Connection connection, final String user, 131 final TableName table) throws IOException { 132 deleteQuotas(connection, getUserRowKey(user), 133 getSettingsQualifierForUserTable(table)); 134 } 135 136 public static void deleteUserQuota(final Connection connection, final String user, 137 final String namespace) throws IOException { 138 deleteQuotas(connection, getUserRowKey(user), 139 getSettingsQualifierForUserNamespace(namespace)); 140 } 141 142 private static void addQuotas(final Connection connection, final byte[] rowKey, 143 final Quotas data) throws IOException { 144 addQuotas(connection, rowKey, QUOTA_QUALIFIER_SETTINGS, data); 145 } 146 147 private static void addQuotas(final Connection connection, final byte[] rowKey, 148 final byte[] qualifier, final Quotas data) throws IOException { 149 Put put = new Put(rowKey); 150 put.addColumn(QUOTA_FAMILY_INFO, qualifier, quotasToData(data)); 151 doPut(connection, put); 152 } 153 154 private static void deleteQuotas(final Connection connection, final byte[] rowKey) 155 throws IOException { 156 deleteQuotas(connection, rowKey, null); 157 } 158 159 private static void deleteQuotas(final Connection connection, final byte[] rowKey, 160 final byte[] qualifier) throws IOException { 161 Delete delete = new Delete(rowKey); 162 if (qualifier != null) { 163 delete.addColumns(QUOTA_FAMILY_INFO, qualifier); 164 } 165 doDelete(connection, delete); 166 } 167 168 public static Map<String, UserQuotaState> fetchUserQuotas(final Connection connection, 169 final List<Get> gets) throws IOException { 170 long nowTs = EnvironmentEdgeManager.currentTime(); 171 Result[] results = doGet(connection, gets); 172 173 Map<String, UserQuotaState> userQuotas = new HashMap<>(results.length); 174 for (int i = 0; i < results.length; ++i) { 175 byte[] key = gets.get(i).getRow(); 176 assert isUserRowKey(key); 177 String user = getUserFromRowKey(key); 178 179 final UserQuotaState quotaInfo = new UserQuotaState(nowTs); 180 userQuotas.put(user, quotaInfo); 181 182 if (results[i].isEmpty()) continue; 183 assert Bytes.equals(key, results[i].getRow()); 184 185 try { 186 parseUserResult(user, results[i], new UserQuotasVisitor() { 187 @Override 188 public void visitUserQuotas(String userName, String namespace, Quotas quotas) { 189 quotaInfo.setQuotas(namespace, quotas); 190 } 191 192 @Override 193 public void visitUserQuotas(String userName, TableName table, Quotas quotas) { 194 quotaInfo.setQuotas(table, quotas); 195 } 196 197 @Override 198 public void visitUserQuotas(String userName, Quotas quotas) { 199 quotaInfo.setQuotas(quotas); 200 } 201 }); 202 } catch (IOException e) { 203 LOG.error("Unable to parse user '" + user + "' quotas", e); 204 userQuotas.remove(user); 205 } 206 } 207 return userQuotas; 208 } 209 210 public static Map<TableName, QuotaState> fetchTableQuotas(final Connection connection, 211 final List<Get> gets) throws IOException { 212 return fetchGlobalQuotas("table", connection, gets, new KeyFromRow<TableName>() { 213 @Override 214 public TableName getKeyFromRow(final byte[] row) { 215 assert isTableRowKey(row); 216 return getTableFromRowKey(row); 217 } 218 }); 219 } 220 221 public static Map<String, QuotaState> fetchNamespaceQuotas(final Connection connection, 222 final List<Get> gets) throws IOException { 223 return fetchGlobalQuotas("namespace", connection, gets, new KeyFromRow<String>() { 224 @Override 225 public String getKeyFromRow(final byte[] row) { 226 assert isNamespaceRowKey(row); 227 return getNamespaceFromRowKey(row); 228 } 229 }); 230 } 231 232 public static <K> Map<K, QuotaState> fetchGlobalQuotas(final String type, 233 final Connection connection, final List<Get> gets, final KeyFromRow<K> kfr) 234 throws IOException { 235 long nowTs = EnvironmentEdgeManager.currentTime(); 236 Result[] results = doGet(connection, gets); 237 238 Map<K, QuotaState> globalQuotas = new HashMap<>(results.length); 239 for (int i = 0; i < results.length; ++i) { 240 byte[] row = gets.get(i).getRow(); 241 K key = kfr.getKeyFromRow(row); 242 243 QuotaState quotaInfo = new QuotaState(nowTs); 244 globalQuotas.put(key, quotaInfo); 245 246 if (results[i].isEmpty()) continue; 247 assert Bytes.equals(row, results[i].getRow()); 248 249 byte[] data = results[i].getValue(QUOTA_FAMILY_INFO, QUOTA_QUALIFIER_SETTINGS); 250 if (data == null) continue; 251 252 try { 253 Quotas quotas = quotasFromData(data); 254 quotaInfo.setQuotas(quotas); 255 } catch (IOException e) { 256 LOG.error("Unable to parse " + type + " '" + key + "' quotas", e); 257 globalQuotas.remove(key); 258 } 259 } 260 return globalQuotas; 261 } 262 263 private static interface KeyFromRow<T> { 264 T getKeyFromRow(final byte[] row); 265 } 266 267 /* ========================================================================= 268 * HTable helpers 269 */ 270 private static void doPut(final Connection connection, final Put put) 271 throws IOException { 272 try (Table table = connection.getTable(QuotaUtil.QUOTA_TABLE_NAME)) { 273 table.put(put); 274 } 275 } 276 277 private static void doDelete(final Connection connection, final Delete delete) 278 throws IOException { 279 try (Table table = connection.getTable(QuotaUtil.QUOTA_TABLE_NAME)) { 280 table.delete(delete); 281 } 282 } 283 284 /* ========================================================================= 285 * Data Size Helpers 286 */ 287 public static long calculateMutationSize(final Mutation mutation) { 288 long size = 0; 289 for (Map.Entry<byte [], List<Cell>> entry : mutation.getFamilyCellMap().entrySet()) { 290 for (Cell cell : entry.getValue()) { 291 size += KeyValueUtil.length(cell); 292 } 293 } 294 return size; 295 } 296 297 public static long calculateResultSize(final Result result) { 298 long size = 0; 299 for (Cell cell : result.rawCells()) { 300 size += KeyValueUtil.length(cell); 301 } 302 return size; 303 } 304 305 public static long calculateResultSize(final List<Result> results) { 306 long size = 0; 307 for (Result result: results) { 308 for (Cell cell : result.rawCells()) { 309 size += KeyValueUtil.length(cell); 310 } 311 } 312 return size; 313 } 314 315 /** 316 * Method to enable a table, if not already enabled. This method suppresses 317 * {@link TableNotDisabledException} and {@link TableNotFoundException}, if thrown while enabling 318 * the table. 319 * @param conn connection to re-use 320 * @param tableName name of the table to be enabled 321 */ 322 public static void enableTableIfNotEnabled(Connection conn, TableName tableName) 323 throws IOException { 324 try { 325 conn.getAdmin().enableTable(tableName); 326 } catch (TableNotDisabledException | TableNotFoundException e) { 327 // ignore 328 } 329 } 330 331 /** 332 * Method to disable a table, if not already disabled. This method suppresses 333 * {@link TableNotEnabledException}, if thrown while disabling the table. 334 * @param conn connection to re-use 335 * @param tableName table name which has moved into space quota violation 336 */ 337 public static void disableTableIfNotDisabled(Connection conn, TableName tableName) 338 throws IOException { 339 try { 340 conn.getAdmin().disableTable(tableName); 341 } catch (TableNotEnabledException | TableNotFoundException e) { 342 // ignore 343 } 344 } 345}