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.quotas; 019 020import java.io.IOException; 021import java.util.Objects; 022import java.util.concurrent.TimeUnit; 023 024import org.apache.hadoop.hbase.TableName; 025import org.apache.hadoop.hbase.quotas.QuotaSettingsFactory.QuotaGlobalsSettingsBypass; 026import org.apache.yetus.audience.InterfaceAudience; 027 028import org.apache.hbase.thirdparty.com.google.protobuf.TextFormat; 029import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 030import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetQuotaRequest; 031 032@InterfaceAudience.Public 033public abstract class QuotaSettings { 034 private final String userName; 035 private final String namespace; 036 private final TableName tableName; 037 private final String regionServer; 038 039 protected QuotaSettings(final String userName, final TableName tableName, final String namespace, 040 final String regionServer) { 041 this.userName = userName; 042 this.namespace = namespace; 043 this.tableName = tableName; 044 this.regionServer = regionServer; 045 } 046 047 public abstract QuotaType getQuotaType(); 048 049 public String getUserName() { 050 return userName; 051 } 052 053 public TableName getTableName() { 054 return tableName; 055 } 056 057 public String getNamespace() { 058 return namespace; 059 } 060 061 public String getRegionServer() { 062 return regionServer; 063 } 064 065 /** 066 * Converts the protocol buffer request into a QuotaSetting POJO. Arbitrarily 067 * enforces that the request only contain one "limit", despite the message 068 * allowing multiple. The public API does not allow such use of the message. 069 * 070 * @param request The protocol buffer request. 071 * @return A {@link QuotaSettings} POJO. 072 */ 073 @InterfaceAudience.Private 074 public static QuotaSettings buildFromProto(SetQuotaRequest request) { 075 String username = null; 076 if (request.hasUserName()) { 077 username = request.getUserName(); 078 } 079 TableName tableName = null; 080 if (request.hasTableName()) { 081 tableName = ProtobufUtil.toTableName(request.getTableName()); 082 } 083 String namespace = null; 084 if (request.hasNamespace()) { 085 namespace = request.getNamespace(); 086 } 087 String regionServer = null; 088 if (request.hasRegionServer()) { 089 regionServer = request.getRegionServer(); 090 } 091 if (request.hasBypassGlobals()) { 092 // Make sure we don't have either of the two below limits also included 093 if (request.hasSpaceLimit() || request.hasThrottle()) { 094 throw new IllegalStateException( 095 "SetQuotaRequest has multiple limits: " + TextFormat.shortDebugString(request)); 096 } 097 return new QuotaGlobalsSettingsBypass( 098 username, tableName, namespace, regionServer, request.getBypassGlobals()); 099 } else if (request.hasSpaceLimit()) { 100 // Make sure we don't have the below limit as well 101 if (request.hasThrottle()) { 102 throw new IllegalStateException( 103 "SetQuotaRequests has multiple limits: " + TextFormat.shortDebugString(request)); 104 } 105 // Sanity check on the pb received. 106 if (!request.getSpaceLimit().hasQuota()) { 107 throw new IllegalArgumentException( 108 "SpaceLimitRequest is missing the expected SpaceQuota."); 109 } 110 return QuotaSettingsFactory.fromSpace( 111 tableName, namespace, request.getSpaceLimit().getQuota()); 112 } else if (request.hasThrottle()) { 113 return new ThrottleSettings(username, tableName, namespace, regionServer, 114 request.getThrottle()); 115 } else { 116 throw new IllegalStateException("Unhandled SetRequestRequest state"); 117 } 118 } 119 120 /** 121 * Convert a QuotaSettings to a protocol buffer SetQuotaRequest. 122 * This is used internally by the Admin client to serialize the quota settings 123 * and send them to the master. 124 */ 125 @InterfaceAudience.Private 126 public static SetQuotaRequest buildSetQuotaRequestProto(final QuotaSettings settings) { 127 SetQuotaRequest.Builder builder = SetQuotaRequest.newBuilder(); 128 if (settings.getUserName() != null) { 129 builder.setUserName(settings.getUserName()); 130 } 131 if (settings.getTableName() != null) { 132 builder.setTableName(ProtobufUtil.toProtoTableName(settings.getTableName())); 133 } 134 if (settings.getNamespace() != null) { 135 builder.setNamespace(settings.getNamespace()); 136 } 137 if (settings.getRegionServer() != null) { 138 builder.setRegionServer(settings.getRegionServer()); 139 } 140 settings.setupSetQuotaRequest(builder); 141 return builder.build(); 142 } 143 144 /** 145 * Called by toSetQuotaRequestProto() 146 * the subclass should implement this method to set the specific SetQuotaRequest 147 * properties. 148 */ 149 @InterfaceAudience.Private 150 protected abstract void setupSetQuotaRequest(SetQuotaRequest.Builder builder); 151 152 protected String ownerToString() { 153 StringBuilder builder = new StringBuilder(); 154 if (userName != null) { 155 builder.append("USER => '"); 156 builder.append(userName); 157 builder.append("', "); 158 } 159 if (tableName != null) { 160 builder.append("TABLE => '"); 161 builder.append(tableName.toString()); 162 builder.append("', "); 163 } 164 if (namespace != null) { 165 builder.append("NAMESPACE => '"); 166 builder.append(namespace); 167 builder.append("', "); 168 } 169 if (regionServer != null) { 170 builder.append("REGIONSERVER => ").append(regionServer).append(", "); 171 } 172 return builder.toString(); 173 } 174 175 protected static String sizeToString(final long size) { 176 if (size >= (1L << 50)) { 177 return String.format("%.2fP", (double)size / (1L << 50)); 178 } 179 if (size >= (1L << 40)) { 180 return String.format("%.2fT", (double)size / (1L << 40)); 181 } 182 if (size >= (1L << 30)) { 183 return String.format("%.2fG", (double)size / (1L << 30)); 184 } 185 if (size >= (1L << 20)) { 186 return String.format("%.2fM", (double)size / (1L << 20)); 187 } 188 if (size >= (1L << 10)) { 189 return String.format("%.2fK", (double)size / (1L << 10)); 190 } 191 return String.format("%.2fB", (double)size); 192 } 193 194 protected static String timeToString(final TimeUnit timeUnit) { 195 switch (timeUnit) { 196 case NANOSECONDS: return "nsec"; 197 case MICROSECONDS: return "usec"; 198 case MILLISECONDS: return "msec"; 199 case SECONDS: return "sec"; 200 case MINUTES: return "min"; 201 case HOURS: return "hour"; 202 case DAYS: return "day"; 203 } 204 throw new RuntimeException("Invalid TimeUnit " + timeUnit); 205 } 206 207 /** 208 * Merges the provided settings with {@code this} and returns a new settings 209 * object to the caller if the merged settings differ from the original. 210 * 211 * @param newSettings The new settings to merge in. 212 * @return The merged {@link QuotaSettings} object or null if the quota should be deleted. 213 */ 214 abstract QuotaSettings merge(QuotaSettings newSettings) throws IOException; 215 216 /** 217 * Validates that settings being merged into {@code this} is targeting the same "subject", e.g. 218 * user, table, namespace. 219 * 220 * @param mergee The quota settings to be merged into {@code this}. 221 * @throws IllegalArgumentException if the subjects are not equal. 222 */ 223 void validateQuotaTarget(QuotaSettings mergee) { 224 if (!Objects.equals(getUserName(), mergee.getUserName())) { 225 throw new IllegalArgumentException("Mismatched user names on settings to merge"); 226 } 227 if (!Objects.equals(getTableName(), mergee.getTableName())) { 228 throw new IllegalArgumentException("Mismatched table names on settings to merge"); 229 } 230 if (!Objects.equals(getNamespace(), mergee.getNamespace())) { 231 throw new IllegalArgumentException("Mismatched namespace on settings to merge"); 232 } 233 if (!Objects.equals(getRegionServer(), mergee.getRegionServer())) { 234 throw new IllegalArgumentException("Mismatched region server on settings to merge"); 235 } 236 } 237}