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 // To prevent the "empty" row in QuotaTableUtil.QUOTA_TABLE_NAME 125 throttleBuilder = null; 126 } else { 127 QuotaProtos.ThrottleRequest otherProto = otherThrottle.proto; 128 validateTimedQuota(otherProto.getTimedQuota()); 129 if (throttleBuilder == null) { 130 throttleBuilder = QuotaProtos.Throttle.newBuilder(); 131 } 132 133 switch (otherProto.getType()) { 134 case REQUEST_NUMBER: 135 throttleBuilder.setReqNum(otherProto.getTimedQuota()); 136 break; 137 case REQUEST_SIZE: 138 throttleBuilder.setReqSize(otherProto.getTimedQuota()); 139 break; 140 case WRITE_NUMBER: 141 throttleBuilder.setWriteNum(otherProto.getTimedQuota()); 142 break; 143 case WRITE_SIZE: 144 throttleBuilder.setWriteSize(otherProto.getTimedQuota()); 145 break; 146 case READ_NUMBER: 147 throttleBuilder.setReadNum(otherProto.getTimedQuota()); 148 break; 149 case READ_SIZE: 150 throttleBuilder.setReadSize(otherProto.getTimedQuota()); 151 break; 152 } 153 } 154 } 155 156 // Propagate the space quota portion 157 QuotaProtos.SpaceQuota.Builder spaceBuilder = (spaceProto == null 158 ? null : spaceProto.toBuilder()); 159 if (other instanceof SpaceLimitSettings) { 160 if (spaceBuilder == null) { 161 spaceBuilder = QuotaProtos.SpaceQuota.newBuilder(); 162 } 163 SpaceLimitSettings settingsToMerge = (SpaceLimitSettings) other; 164 165 QuotaProtos.SpaceLimitRequest spaceRequest = settingsToMerge.getProto(); 166 167 // The message contained the expect SpaceQuota object 168 if (spaceRequest.hasQuota()) { 169 SpaceQuota quotaToMerge = spaceRequest.getQuota(); 170 // Validate that the two settings are for the same target. 171 // SpaceQuotas either apply to a table or a namespace (no user spacequota). 172 if (!Objects.equals(getTableName(), settingsToMerge.getTableName()) 173 && !Objects.equals(getNamespace(), settingsToMerge.getNamespace())) { 174 throw new IllegalArgumentException( 175 "Cannot merge " + settingsToMerge + " into " + this); 176 } 177 178 if (quotaToMerge.getRemove()) { 179 // Update the builder to propagate the removal 180 spaceBuilder.setRemove(true).clearSoftLimit().clearViolationPolicy(); 181 } else { 182 // Add the new settings to the existing settings 183 spaceBuilder.mergeFrom(quotaToMerge); 184 } 185 } 186 } 187 188 Boolean bypassGlobals = this.bypassGlobals; 189 if (other instanceof QuotaGlobalsSettingsBypass) { 190 bypassGlobals = ((QuotaGlobalsSettingsBypass) other).getBypass(); 191 } 192 193 if (throttleBuilder == null && 194 (spaceBuilder == null || (spaceBuilder.hasRemove() && spaceBuilder.getRemove())) 195 && bypassGlobals == null) { 196 return null; 197 } 198 199 return new GlobalQuotaSettingsImpl( 200 getUserName(), getTableName(), getNamespace(), 201 (throttleBuilder == null ? null : throttleBuilder.build()), bypassGlobals, 202 (spaceBuilder == null ? null : spaceBuilder.build())); 203 } 204 205 private void validateTimedQuota(final TimedQuota timedQuota) throws IOException { 206 if (timedQuota.getSoftLimit() < 1) { 207 throw new DoNotRetryIOException(new UnsupportedOperationException( 208 "The throttle limit must be greater then 0, got " + timedQuota.getSoftLimit())); 209 } 210 } 211 212 @Override 213 public String toString() { 214 StringBuilder builder = new StringBuilder(); 215 builder.append("GlobalQuota: "); 216 if (throttleProto != null) { 217 Map<ThrottleType,TimedQuota> throttleQuotas = buildThrottleQuotas(throttleProto); 218 builder.append(" { TYPE => THROTTLE "); 219 for (Entry<ThrottleType,TimedQuota> entry : throttleQuotas.entrySet()) { 220 final ThrottleType type = entry.getKey(); 221 final TimedQuota timedQuota = entry.getValue(); 222 builder.append("{THROTTLE_TYPE => ").append(type.name()).append(", LIMIT => "); 223 if (timedQuota.hasSoftLimit()) { 224 switch (type) { 225 case REQUEST_NUMBER: 226 case WRITE_NUMBER: 227 case READ_NUMBER: 228 builder.append(String.format("%dreq", timedQuota.getSoftLimit())); 229 break; 230 case REQUEST_SIZE: 231 case WRITE_SIZE: 232 case READ_SIZE: 233 builder.append(sizeToString(timedQuota.getSoftLimit())); 234 break; 235 } 236 } else if (timedQuota.hasShare()) { 237 builder.append(String.format("%.2f%%", timedQuota.getShare())); 238 } 239 builder.append('/'); 240 builder.append(timeToString(ProtobufUtil.toTimeUnit(timedQuota.getTimeUnit()))); 241 if (timedQuota.hasScope()) { 242 builder.append(", SCOPE => "); 243 builder.append(timedQuota.getScope().toString()); 244 } 245 } 246 builder.append( "} } "); 247 } else { 248 builder.append(" {} "); 249 } 250 if (bypassGlobals != null) { 251 builder.append(" { GLOBAL_BYPASS => " + bypassGlobals + " } "); 252 } 253 if (spaceProto != null) { 254 builder.append(" { TYPE => SPACE"); 255 if (getTableName() != null) { 256 builder.append(", TABLE => ").append(getTableName()); 257 } 258 if (getNamespace() != null) { 259 builder.append(", NAMESPACE => ").append(getNamespace()); 260 } 261 if (spaceProto.getRemove()) { 262 builder.append(", REMOVE => ").append(spaceProto.getRemove()); 263 } else { 264 builder.append(", LIMIT => ").append(sizeToString(spaceProto.getSoftLimit())); 265 builder.append(", VIOLATION_POLICY => ").append(spaceProto.getViolationPolicy()); 266 } 267 builder.append(" } "); 268 } 269 return builder.toString(); 270 } 271 272 private Map<ThrottleType,TimedQuota> buildThrottleQuotas(Throttle proto) { 273 HashMap<ThrottleType,TimedQuota> quotas = new HashMap<>(); 274 if (proto.hasReadNum()) { 275 quotas.put(ThrottleType.READ_NUMBER, proto.getReadNum()); 276 } 277 if (proto.hasReadSize()) { 278 quotas.put(ThrottleType.READ_SIZE, proto.getReadSize()); 279 } 280 if (proto.hasReqNum()) { 281 quotas.put(ThrottleType.REQUEST_NUMBER, proto.getReqNum()); 282 } 283 if (proto.hasReqSize()) { 284 quotas.put(ThrottleType.REQUEST_SIZE, proto.getReqSize()); 285 } 286 if (proto.hasWriteNum()) { 287 quotas.put(ThrottleType.WRITE_NUMBER, proto.getWriteNum()); 288 } 289 if (proto.hasWriteSize()) { 290 quotas.put(ThrottleType.WRITE_SIZE, proto.getWriteSize()); 291 } 292 return quotas; 293 } 294 295 private void clearThrottleBuilder(QuotaProtos.Throttle.Builder builder) { 296 builder.clearReadNum(); 297 builder.clearReadSize(); 298 builder.clearReqNum(); 299 builder.clearReqSize(); 300 builder.clearWriteNum(); 301 builder.clearWriteSize(); 302 } 303}