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.yetus.audience.InterfaceAudience; 031 032import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 033import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos; 034import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas; 035import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.SpaceQuota; 036import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Throttle; 037import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.TimedQuota; 038 039/** 040 * Implementation of {@link GlobalQuotaSettings} to hide the Protobuf messages we use internally. 041 */ 042@InterfaceAudience.Private 043public class GlobalQuotaSettingsImpl extends GlobalQuotaSettings { 044 045 private final QuotaProtos.Throttle throttleProto; 046 private final Boolean bypassGlobals; 047 private final QuotaProtos.SpaceQuota spaceProto; 048 049 protected GlobalQuotaSettingsImpl(String username, TableName tableName, String namespace, 050 String regionServer, QuotaProtos.Quotas quotas) { 051 this(username, tableName, namespace, regionServer, 052 (quotas != null && quotas.hasThrottle() ? quotas.getThrottle() : null), 053 (quotas != null && quotas.hasBypassGlobals() ? quotas.getBypassGlobals() : null), 054 (quotas != null && quotas.hasSpace() ? quotas.getSpace() : null)); 055 } 056 057 protected GlobalQuotaSettingsImpl(String userName, TableName tableName, String namespace, 058 String regionServer, QuotaProtos.Throttle throttleProto, Boolean bypassGlobals, 059 QuotaProtos.SpaceQuota spaceProto) { 060 super(userName, tableName, namespace, regionServer); 061 this.throttleProto = throttleProto; 062 this.bypassGlobals = bypassGlobals; 063 this.spaceProto = spaceProto; 064 } 065 066 @Override 067 public List<QuotaSettings> getQuotaSettings() { 068 // Very similar to QuotaSettingsFactory 069 List<QuotaSettings> settings = new ArrayList<>(); 070 if (throttleProto != null) { 071 settings.addAll(QuotaSettingsFactory.fromThrottle(getUserName(), getTableName(), 072 getNamespace(), getRegionServer(), throttleProto)); 073 } 074 if (bypassGlobals != null && bypassGlobals.booleanValue()) { 075 settings.add(new QuotaGlobalsSettingsBypass(getUserName(), getTableName(), getNamespace(), 076 getRegionServer(), true)); 077 } 078 if (spaceProto != null) { 079 settings.add(QuotaSettingsFactory.fromSpace(getTableName(), getNamespace(), spaceProto)); 080 } 081 return settings; 082 } 083 084 protected QuotaProtos.Throttle getThrottleProto() { 085 return this.throttleProto; 086 } 087 088 protected Boolean getBypassGlobals() { 089 return this.bypassGlobals; 090 } 091 092 protected QuotaProtos.SpaceQuota getSpaceProto() { 093 return this.spaceProto; 094 } 095 096 /** 097 * Constructs a new {@link Quotas} message from {@code this}. 098 */ 099 protected Quotas toQuotas() { 100 QuotaProtos.Quotas.Builder builder = QuotaProtos.Quotas.newBuilder(); 101 if (getThrottleProto() != null) { 102 builder.setThrottle(getThrottleProto()); 103 } 104 if (getBypassGlobals() != null) { 105 builder.setBypassGlobals(getBypassGlobals()); 106 } 107 if (getSpaceProto() != null) { 108 builder.setSpace(getSpaceProto()); 109 } 110 return builder.build(); 111 } 112 113 private boolean hasThrottle(QuotaProtos.ThrottleType quotaType, 114 QuotaProtos.Throttle.Builder throttleBuilder) { 115 boolean hasThrottle = false; 116 switch (quotaType) { 117 case REQUEST_NUMBER: 118 if (throttleBuilder.hasReqNum()) { 119 hasThrottle = true; 120 } 121 break; 122 case REQUEST_SIZE: 123 if (throttleBuilder.hasReqSize()) { 124 hasThrottle = true; 125 } 126 break; 127 case WRITE_NUMBER: 128 if (throttleBuilder.hasWriteNum()) { 129 hasThrottle = true; 130 } 131 break; 132 case WRITE_SIZE: 133 if (throttleBuilder.hasWriteSize()) { 134 hasThrottle = true; 135 } 136 break; 137 case READ_NUMBER: 138 if (throttleBuilder.hasReadNum()) { 139 hasThrottle = true; 140 } 141 break; 142 case READ_SIZE: 143 if (throttleBuilder.hasReadSize()) { 144 hasThrottle = true; 145 } 146 break; 147 case REQUEST_CAPACITY_UNIT: 148 if (throttleBuilder.hasReqCapacityUnit()) { 149 hasThrottle = true; 150 } 151 break; 152 case READ_CAPACITY_UNIT: 153 if (throttleBuilder.hasReadCapacityUnit()) { 154 hasThrottle = true; 155 } 156 break; 157 case WRITE_CAPACITY_UNIT: 158 if (throttleBuilder.hasWriteCapacityUnit()) { 159 hasThrottle = true; 160 } 161 break; 162 default: 163 } 164 return hasThrottle; 165 } 166 167 @Override 168 protected GlobalQuotaSettingsImpl merge(QuotaSettings other) throws IOException { 169 // Validate the quota subject 170 validateQuotaTarget(other); 171 172 // Propagate the Throttle 173 QuotaProtos.Throttle.Builder throttleBuilder = 174 throttleProto == null ? null : throttleProto.toBuilder(); 175 176 if (other instanceof ThrottleSettings) { 177 ThrottleSettings otherThrottle = (ThrottleSettings) other; 178 if (!otherThrottle.proto.hasType() || !otherThrottle.proto.hasTimedQuota()) { 179 // It means it's a remove request 180 // To prevent the "empty" row in QuotaTableUtil.QUOTA_TABLE_NAME 181 182 QuotaProtos.ThrottleRequest otherProto = otherThrottle.proto; 183 if (throttleBuilder != null && !otherThrottle.proto.hasTimedQuota() && otherThrottle.proto 184 .hasType()) { 185 switch (otherProto.getType()) { 186 case REQUEST_NUMBER: 187 throttleBuilder.clearReqNum(); 188 break; 189 case REQUEST_SIZE: 190 throttleBuilder.clearReqSize(); 191 break; 192 case WRITE_NUMBER: 193 throttleBuilder.clearWriteNum(); 194 break; 195 case WRITE_SIZE: 196 throttleBuilder.clearWriteSize(); 197 break; 198 case READ_NUMBER: 199 throttleBuilder.clearReadNum(); 200 break; 201 case READ_SIZE: 202 throttleBuilder.clearReadSize(); 203 break; 204 case REQUEST_CAPACITY_UNIT: 205 throttleBuilder.clearReqCapacityUnit(); 206 break; 207 case READ_CAPACITY_UNIT: 208 throttleBuilder.clearReadCapacityUnit(); 209 break; 210 case WRITE_CAPACITY_UNIT: 211 throttleBuilder.clearWriteCapacityUnit(); 212 break; 213 default: 214 } 215 boolean hasThrottle = false; 216 for (QuotaProtos.ThrottleType quotaType : QuotaProtos.ThrottleType.values()) { 217 hasThrottle = hasThrottle(quotaType, throttleBuilder); 218 if (hasThrottle) { 219 break; 220 } 221 } 222 if (!hasThrottle) { 223 throttleBuilder = null; 224 } 225 } else { 226 throttleBuilder = null; 227 } 228 229 } else { 230 QuotaProtos.ThrottleRequest otherProto = otherThrottle.proto; 231 validateTimedQuota(otherProto.getTimedQuota()); 232 if (throttleBuilder == null) { 233 throttleBuilder = QuotaProtos.Throttle.newBuilder(); 234 } 235 switch (otherProto.getType()) { 236 case REQUEST_NUMBER: 237 throttleBuilder.setReqNum(otherProto.getTimedQuota()); 238 break; 239 case REQUEST_SIZE: 240 throttleBuilder.setReqSize(otherProto.getTimedQuota()); 241 break; 242 case WRITE_NUMBER: 243 throttleBuilder.setWriteNum(otherProto.getTimedQuota()); 244 break; 245 case WRITE_SIZE: 246 throttleBuilder.setWriteSize(otherProto.getTimedQuota()); 247 break; 248 case READ_NUMBER: 249 throttleBuilder.setReadNum(otherProto.getTimedQuota()); 250 break; 251 case READ_SIZE: 252 throttleBuilder.setReadSize(otherProto.getTimedQuota()); 253 break; 254 case REQUEST_CAPACITY_UNIT: 255 throttleBuilder.setReqCapacityUnit(otherProto.getTimedQuota()); 256 break; 257 case READ_CAPACITY_UNIT: 258 throttleBuilder.setReadCapacityUnit(otherProto.getTimedQuota()); 259 break; 260 case WRITE_CAPACITY_UNIT: 261 throttleBuilder.setWriteCapacityUnit(otherProto.getTimedQuota()); 262 break; 263 default: 264 } 265 } 266 } 267 268 // Propagate the space quota portion 269 QuotaProtos.SpaceQuota.Builder spaceBuilder = 270 (spaceProto == null ? null : spaceProto.toBuilder()); 271 if (other instanceof SpaceLimitSettings) { 272 if (spaceBuilder == null) { 273 spaceBuilder = QuotaProtos.SpaceQuota.newBuilder(); 274 } 275 SpaceLimitSettings settingsToMerge = (SpaceLimitSettings) other; 276 277 QuotaProtos.SpaceLimitRequest spaceRequest = settingsToMerge.getProto(); 278 279 // The message contained the expect SpaceQuota object 280 if (spaceRequest.hasQuota()) { 281 SpaceQuota quotaToMerge = spaceRequest.getQuota(); 282 // Validate that the two settings are for the same target. 283 // SpaceQuotas either apply to a table or a namespace (no user spacequota). 284 if (!Objects.equals(getTableName(), settingsToMerge.getTableName()) && !Objects 285 .equals(getNamespace(), settingsToMerge.getNamespace())) { 286 throw new IllegalArgumentException("Cannot merge " + settingsToMerge + " into " + this); 287 } 288 289 if (quotaToMerge.getRemove()) { 290 // It means it's a remove request 291 // Update the builder to propagate the removal 292 spaceBuilder.setRemove(true).clearSoftLimit().clearViolationPolicy(); 293 } else { 294 // Add the new settings to the existing settings 295 spaceBuilder.mergeFrom(quotaToMerge); 296 } 297 } 298 } 299 300 boolean removeSpaceBuilder = 301 (spaceBuilder == null) || (spaceBuilder.hasRemove() && spaceBuilder.getRemove()); 302 303 Boolean bypassGlobals = this.bypassGlobals; 304 if (other instanceof QuotaGlobalsSettingsBypass) { 305 bypassGlobals = ((QuotaGlobalsSettingsBypass) other).getBypass(); 306 } 307 308 if (throttleBuilder == null && removeSpaceBuilder && bypassGlobals == null) { 309 return null; 310 } 311 312 return new GlobalQuotaSettingsImpl(getUserName(), getTableName(), getNamespace(), 313 getRegionServer(), (throttleBuilder == null ? null : throttleBuilder.build()), 314 bypassGlobals, (removeSpaceBuilder ? null : spaceBuilder.build())); 315 } 316 317 private void validateTimedQuota(final TimedQuota timedQuota) throws IOException { 318 if (timedQuota.getSoftLimit() < 1) { 319 throw new DoNotRetryIOException(new UnsupportedOperationException( 320 "The throttle limit must be greater then 0, got " + timedQuota.getSoftLimit())); 321 } 322 } 323 324 @Override 325 public String toString() { 326 StringBuilder builder = new StringBuilder(); 327 builder.append("GlobalQuota: "); 328 if (throttleProto != null) { 329 Map<ThrottleType,TimedQuota> throttleQuotas = buildThrottleQuotas(throttleProto); 330 builder.append(" { TYPE => THROTTLE "); 331 for (Entry<ThrottleType,TimedQuota> entry : throttleQuotas.entrySet()) { 332 final ThrottleType type = entry.getKey(); 333 final TimedQuota timedQuota = entry.getValue(); 334 builder.append("{THROTTLE_TYPE => ").append(type.name()).append(", LIMIT => "); 335 if (timedQuota.hasSoftLimit()) { 336 switch (type) { 337 case REQUEST_NUMBER: 338 case WRITE_NUMBER: 339 case READ_NUMBER: 340 builder.append(String.format("%dreq", timedQuota.getSoftLimit())); 341 break; 342 case REQUEST_SIZE: 343 case WRITE_SIZE: 344 case READ_SIZE: 345 builder.append(sizeToString(timedQuota.getSoftLimit())); 346 break; 347 case REQUEST_CAPACITY_UNIT: 348 case READ_CAPACITY_UNIT: 349 case WRITE_CAPACITY_UNIT: 350 builder.append(String.format("%dCU", timedQuota.getSoftLimit())); 351 default: 352 } 353 } else if (timedQuota.hasShare()) { 354 builder.append(String.format("%.2f%%", timedQuota.getShare())); 355 } 356 builder.append('/'); 357 builder.append(timeToString(ProtobufUtil.toTimeUnit(timedQuota.getTimeUnit()))); 358 if (timedQuota.hasScope()) { 359 builder.append(", SCOPE => "); 360 builder.append(timedQuota.getScope().toString()); 361 } 362 } 363 builder.append( "} } "); 364 } else { 365 builder.append(" {} "); 366 } 367 if (bypassGlobals != null) { 368 builder.append(" { GLOBAL_BYPASS => " + bypassGlobals + " } "); 369 } 370 if (spaceProto != null) { 371 builder.append(" { TYPE => SPACE"); 372 if (getTableName() != null) { 373 builder.append(", TABLE => ").append(getTableName()); 374 } 375 if (getNamespace() != null) { 376 builder.append(", NAMESPACE => ").append(getNamespace()); 377 } 378 if (spaceProto.getRemove()) { 379 builder.append(", REMOVE => ").append(spaceProto.getRemove()); 380 } else { 381 builder.append(", LIMIT => ").append(sizeToString(spaceProto.getSoftLimit())); 382 builder.append(", VIOLATION_POLICY => ").append(spaceProto.getViolationPolicy()); 383 } 384 builder.append(" } "); 385 } 386 return builder.toString(); 387 } 388 389 private Map<ThrottleType,TimedQuota> buildThrottleQuotas(Throttle proto) { 390 HashMap<ThrottleType,TimedQuota> quotas = new HashMap<>(); 391 if (proto.hasReadNum()) { 392 quotas.put(ThrottleType.READ_NUMBER, proto.getReadNum()); 393 } 394 if (proto.hasReadSize()) { 395 quotas.put(ThrottleType.READ_SIZE, proto.getReadSize()); 396 } 397 if (proto.hasReqNum()) { 398 quotas.put(ThrottleType.REQUEST_NUMBER, proto.getReqNum()); 399 } 400 if (proto.hasReqSize()) { 401 quotas.put(ThrottleType.REQUEST_SIZE, proto.getReqSize()); 402 } 403 if (proto.hasWriteNum()) { 404 quotas.put(ThrottleType.WRITE_NUMBER, proto.getWriteNum()); 405 } 406 if (proto.hasWriteSize()) { 407 quotas.put(ThrottleType.WRITE_SIZE, proto.getWriteSize()); 408 } 409 if (proto.hasReqCapacityUnit()) { 410 quotas.put(ThrottleType.REQUEST_CAPACITY_UNIT, proto.getReqCapacityUnit()); 411 } 412 if (proto.hasReadCapacityUnit()) { 413 quotas.put(ThrottleType.READ_CAPACITY_UNIT, proto.getReqCapacityUnit()); 414 } 415 if (proto.hasWriteCapacityUnit()) { 416 quotas.put(ThrottleType.WRITE_CAPACITY_UNIT, proto.getWriteCapacityUnit()); 417 } 418 return quotas; 419 } 420}