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.ArrayList; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Objects; 025import java.util.Map.Entry; 026 027import org.apache.hadoop.hbase.DoNotRetryIOException; 028import org.apache.hadoop.hbase.TableName; 029import org.apache.hadoop.hbase.quotas.QuotaSettingsFactory.QuotaGlobalsSettingsBypass; 030import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 031import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos; 032import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas; 033import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.SpaceQuota; 034import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Throttle; 035import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.TimedQuota; 036import org.apache.yetus.audience.InterfaceAudience; 037 038/** 039 * Implementation of {@link GlobalQuotaSettings} to hide the Protobuf messages we use internally. 040 */ 041@InterfaceAudience.Private 042public class GlobalQuotaSettingsImpl extends GlobalQuotaSettings { 043 044 private final QuotaProtos.Throttle throttleProto; 045 private final Boolean bypassGlobals; 046 private final QuotaProtos.SpaceQuota spaceProto; 047 048 protected GlobalQuotaSettingsImpl( 049 String username, TableName tableName, String namespace, QuotaProtos.Quotas quotas) { 050 this(username, tableName, namespace, 051 (quotas != null && quotas.hasThrottle() ? quotas.getThrottle() : null), 052 (quotas != null && quotas.hasBypassGlobals() ? quotas.getBypassGlobals() : null), 053 (quotas != null && quotas.hasSpace() ? quotas.getSpace() : null)); 054 } 055 056 protected GlobalQuotaSettingsImpl( 057 String userName, TableName tableName, String namespace, QuotaProtos.Throttle throttleProto, 058 Boolean bypassGlobals, QuotaProtos.SpaceQuota spaceProto) { 059 super(userName, tableName, namespace); 060 this.throttleProto = throttleProto; 061 this.bypassGlobals = bypassGlobals; 062 this.spaceProto = spaceProto; 063 } 064 065 @Override 066 public List<QuotaSettings> getQuotaSettings() { 067 // Very similar to QuotaSettingsFactory 068 List<QuotaSettings> settings = new ArrayList<>(); 069 if (throttleProto != null) { 070 settings.addAll(QuotaSettingsFactory.fromThrottle( 071 getUserName(), getTableName(), getNamespace(), throttleProto)); 072 } 073 if (bypassGlobals != null && bypassGlobals.booleanValue()) { 074 settings.add(new QuotaGlobalsSettingsBypass( 075 getUserName(), getTableName(), getNamespace(), true)); 076 } 077 if (spaceProto != null) { 078 settings.add(QuotaSettingsFactory.fromSpace(getTableName(), getNamespace(), spaceProto)); 079 } 080 return settings; 081 } 082 083 protected QuotaProtos.Throttle getThrottleProto() { 084 return this.throttleProto; 085 } 086 087 protected Boolean getBypassGlobals() { 088 return this.bypassGlobals; 089 } 090 091 protected QuotaProtos.SpaceQuota getSpaceProto() { 092 return this.spaceProto; 093 } 094 095 /** 096 * Constructs a new {@link Quotas} message from {@code this}. 097 */ 098 protected Quotas toQuotas() { 099 QuotaProtos.Quotas.Builder builder = QuotaProtos.Quotas.newBuilder(); 100 if (getThrottleProto() != null) { 101 builder.setThrottle(getThrottleProto()); 102 } 103 if (getBypassGlobals() != null) { 104 builder.setBypassGlobals(getBypassGlobals()); 105 } 106 if (getSpaceProto() != null) { 107 builder.setSpace(getSpaceProto()); 108 } 109 return builder.build(); 110 } 111 112 @Override 113 protected GlobalQuotaSettingsImpl merge(QuotaSettings other) throws IOException { 114 // Validate the quota subject 115 validateQuotaTarget(other); 116 117 // Propagate the Throttle 118 QuotaProtos.Throttle.Builder throttleBuilder = 119 throttleProto == null ? null : throttleProto.toBuilder(); 120 121 if (other instanceof ThrottleSettings) { 122 ThrottleSettings otherThrottle = (ThrottleSettings) other; 123 if (!otherThrottle.proto.hasType() || !otherThrottle.proto.hasTimedQuota()) { 124 // It means it's a remove request 125 // To prevent the "empty" row in QuotaTableUtil.QUOTA_TABLE_NAME 126 throttleBuilder = null; 127 } else { 128 QuotaProtos.ThrottleRequest otherProto = otherThrottle.proto; 129 validateTimedQuota(otherProto.getTimedQuota()); 130 if (throttleBuilder == null) { 131 throttleBuilder = QuotaProtos.Throttle.newBuilder(); 132 } 133 134 switch (otherProto.getType()) { 135 case REQUEST_NUMBER: 136 throttleBuilder.setReqNum(otherProto.getTimedQuota()); 137 break; 138 case REQUEST_SIZE: 139 throttleBuilder.setReqSize(otherProto.getTimedQuota()); 140 break; 141 case WRITE_NUMBER: 142 throttleBuilder.setWriteNum(otherProto.getTimedQuota()); 143 break; 144 case WRITE_SIZE: 145 throttleBuilder.setWriteSize(otherProto.getTimedQuota()); 146 break; 147 case READ_NUMBER: 148 throttleBuilder.setReadNum(otherProto.getTimedQuota()); 149 break; 150 case READ_SIZE: 151 throttleBuilder.setReadSize(otherProto.getTimedQuota()); 152 break; 153 } 154 } 155 } 156 157 // Propagate the space quota portion 158 QuotaProtos.SpaceQuota.Builder spaceBuilder = (spaceProto == null 159 ? null : spaceProto.toBuilder()); 160 if (other instanceof SpaceLimitSettings) { 161 if (spaceBuilder == null) { 162 spaceBuilder = QuotaProtos.SpaceQuota.newBuilder(); 163 } 164 SpaceLimitSettings settingsToMerge = (SpaceLimitSettings) other; 165 166 QuotaProtos.SpaceLimitRequest spaceRequest = settingsToMerge.getProto(); 167 168 // The message contained the expect SpaceQuota object 169 if (spaceRequest.hasQuota()) { 170 SpaceQuota quotaToMerge = spaceRequest.getQuota(); 171 // Validate that the two settings are for the same target. 172 // SpaceQuotas either apply to a table or a namespace (no user spacequota). 173 if (!Objects.equals(getTableName(), settingsToMerge.getTableName()) 174 && !Objects.equals(getNamespace(), settingsToMerge.getNamespace())) { 175 throw new IllegalArgumentException( 176 "Cannot merge " + settingsToMerge + " into " + this); 177 } 178 179 if (quotaToMerge.getRemove()) { 180 // It means it's a remove request 181 // Update the builder to propagate the removal 182 spaceBuilder.setRemove(true).clearSoftLimit().clearViolationPolicy(); 183 } else { 184 // Add the new settings to the existing settings 185 spaceBuilder.mergeFrom(quotaToMerge); 186 } 187 } 188 } 189 190 boolean removeSpaceBuilder = 191 (spaceBuilder == null) || (spaceBuilder.hasRemove() && spaceBuilder.getRemove()); 192 193 Boolean bypassGlobals = this.bypassGlobals; 194 if (other instanceof QuotaGlobalsSettingsBypass) { 195 bypassGlobals = ((QuotaGlobalsSettingsBypass) other).getBypass(); 196 } 197 198 if (throttleBuilder == null && removeSpaceBuilder && bypassGlobals == null) { 199 return null; 200 } 201 202 return new GlobalQuotaSettingsImpl( 203 getUserName(), getTableName(), getNamespace(), 204 (throttleBuilder == null ? null : throttleBuilder.build()), bypassGlobals, 205 (removeSpaceBuilder ? null : spaceBuilder.build())); 206 } 207 208 private void validateTimedQuota(final TimedQuota timedQuota) throws IOException { 209 if (timedQuota.getSoftLimit() < 1) { 210 throw new DoNotRetryIOException(new UnsupportedOperationException( 211 "The throttle limit must be greater then 0, got " + timedQuota.getSoftLimit())); 212 } 213 } 214 215 @Override 216 public String toString() { 217 StringBuilder builder = new StringBuilder(); 218 builder.append("GlobalQuota: "); 219 if (throttleProto != null) { 220 Map<ThrottleType,TimedQuota> throttleQuotas = buildThrottleQuotas(throttleProto); 221 builder.append(" { TYPE => THROTTLE "); 222 for (Entry<ThrottleType,TimedQuota> entry : throttleQuotas.entrySet()) { 223 final ThrottleType type = entry.getKey(); 224 final TimedQuota timedQuota = entry.getValue(); 225 builder.append("{THROTTLE_TYPE => ").append(type.name()).append(", LIMIT => "); 226 if (timedQuota.hasSoftLimit()) { 227 switch (type) { 228 case REQUEST_NUMBER: 229 case WRITE_NUMBER: 230 case READ_NUMBER: 231 builder.append(String.format("%dreq", timedQuota.getSoftLimit())); 232 break; 233 case REQUEST_SIZE: 234 case WRITE_SIZE: 235 case READ_SIZE: 236 builder.append(sizeToString(timedQuota.getSoftLimit())); 237 break; 238 } 239 } else if (timedQuota.hasShare()) { 240 builder.append(String.format("%.2f%%", timedQuota.getShare())); 241 } 242 builder.append('/'); 243 builder.append(timeToString(ProtobufUtil.toTimeUnit(timedQuota.getTimeUnit()))); 244 if (timedQuota.hasScope()) { 245 builder.append(", SCOPE => "); 246 builder.append(timedQuota.getScope().toString()); 247 } 248 } 249 builder.append( "} } "); 250 } else { 251 builder.append(" {} "); 252 } 253 if (bypassGlobals != null) { 254 builder.append(" { GLOBAL_BYPASS => " + bypassGlobals + " } "); 255 } 256 if (spaceProto != null) { 257 builder.append(" { TYPE => SPACE"); 258 if (getTableName() != null) { 259 builder.append(", TABLE => ").append(getTableName()); 260 } 261 if (getNamespace() != null) { 262 builder.append(", NAMESPACE => ").append(getNamespace()); 263 } 264 if (spaceProto.getRemove()) { 265 builder.append(", REMOVE => ").append(spaceProto.getRemove()); 266 } else { 267 builder.append(", LIMIT => ").append(sizeToString(spaceProto.getSoftLimit())); 268 builder.append(", VIOLATION_POLICY => ").append(spaceProto.getViolationPolicy()); 269 } 270 builder.append(" } "); 271 } 272 return builder.toString(); 273 } 274 275 private Map<ThrottleType,TimedQuota> buildThrottleQuotas(Throttle proto) { 276 HashMap<ThrottleType,TimedQuota> quotas = new HashMap<>(); 277 if (proto.hasReadNum()) { 278 quotas.put(ThrottleType.READ_NUMBER, proto.getReadNum()); 279 } 280 if (proto.hasReadSize()) { 281 quotas.put(ThrottleType.READ_SIZE, proto.getReadSize()); 282 } 283 if (proto.hasReqNum()) { 284 quotas.put(ThrottleType.REQUEST_NUMBER, proto.getReqNum()); 285 } 286 if (proto.hasReqSize()) { 287 quotas.put(ThrottleType.REQUEST_SIZE, proto.getReqSize()); 288 } 289 if (proto.hasWriteNum()) { 290 quotas.put(ThrottleType.WRITE_NUMBER, proto.getWriteNum()); 291 } 292 if (proto.hasWriteSize()) { 293 quotas.put(ThrottleType.WRITE_SIZE, proto.getWriteSize()); 294 } 295 return quotas; 296 } 297}