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