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