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.ArrayList; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Map.Entry; 026import java.util.Objects; 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 case ATOMIC_READ_SIZE: 163 if (throttleBuilder.hasAtomicReadSize()) { 164 hasThrottle = true; 165 } 166 break; 167 case ATOMIC_REQUEST_NUMBER: 168 if (throttleBuilder.hasAtomicReqNum()) { 169 hasThrottle = true; 170 } 171 break; 172 case ATOMIC_WRITE_SIZE: 173 if (throttleBuilder.hasAtomicWriteSize()) { 174 hasThrottle = true; 175 } 176 break; 177 case REQUEST_HANDLER_USAGE_MS: 178 if (throttleBuilder.hasReqHandlerUsageMs()) { 179 hasThrottle = true; 180 } 181 break; 182 default: 183 } 184 return hasThrottle; 185 } 186 187 @Override 188 protected GlobalQuotaSettingsImpl merge(QuotaSettings other) throws IOException { 189 // Validate the quota subject 190 validateQuotaTarget(other); 191 192 // Propagate the Throttle 193 QuotaProtos.Throttle.Builder throttleBuilder = 194 throttleProto == null ? null : throttleProto.toBuilder(); 195 196 if (other instanceof ThrottleSettings) { 197 ThrottleSettings otherThrottle = (ThrottleSettings) other; 198 if (!otherThrottle.proto.hasType() || !otherThrottle.proto.hasTimedQuota()) { 199 // It means it's a remove request 200 // To prevent the "empty" row in QuotaTableUtil.QUOTA_TABLE_NAME 201 202 QuotaProtos.ThrottleRequest otherProto = otherThrottle.proto; 203 if ( 204 throttleBuilder != null && !otherThrottle.proto.hasTimedQuota() 205 && otherThrottle.proto.hasType() 206 ) { 207 switch (otherProto.getType()) { 208 case REQUEST_NUMBER: 209 throttleBuilder.clearReqNum(); 210 break; 211 case REQUEST_SIZE: 212 throttleBuilder.clearReqSize(); 213 break; 214 case WRITE_NUMBER: 215 throttleBuilder.clearWriteNum(); 216 break; 217 case WRITE_SIZE: 218 throttleBuilder.clearWriteSize(); 219 break; 220 case READ_NUMBER: 221 throttleBuilder.clearReadNum(); 222 break; 223 case READ_SIZE: 224 throttleBuilder.clearReadSize(); 225 break; 226 case REQUEST_CAPACITY_UNIT: 227 throttleBuilder.clearReqCapacityUnit(); 228 break; 229 case READ_CAPACITY_UNIT: 230 throttleBuilder.clearReadCapacityUnit(); 231 break; 232 case WRITE_CAPACITY_UNIT: 233 throttleBuilder.clearWriteCapacityUnit(); 234 break; 235 case ATOMIC_READ_SIZE: 236 throttleBuilder.clearAtomicReadSize(); 237 break; 238 case ATOMIC_REQUEST_NUMBER: 239 throttleBuilder.clearAtomicReqNum(); 240 break; 241 case ATOMIC_WRITE_SIZE: 242 throttleBuilder.clearAtomicWriteSize(); 243 break; 244 case REQUEST_HANDLER_USAGE_MS: 245 throttleBuilder.clearReqHandlerUsageMs(); 246 break; 247 default: 248 } 249 boolean hasThrottle = false; 250 for (QuotaProtos.ThrottleType quotaType : QuotaProtos.ThrottleType.values()) { 251 hasThrottle = hasThrottle(quotaType, throttleBuilder); 252 if (hasThrottle) { 253 break; 254 } 255 } 256 if (!hasThrottle) { 257 throttleBuilder = null; 258 } 259 } else { 260 throttleBuilder = null; 261 } 262 263 } else { 264 QuotaProtos.ThrottleRequest otherProto = otherThrottle.proto; 265 validateTimedQuota(otherProto.getTimedQuota()); 266 if (throttleBuilder == null) { 267 throttleBuilder = QuotaProtos.Throttle.newBuilder(); 268 } 269 switch (otherProto.getType()) { 270 case REQUEST_NUMBER: 271 throttleBuilder.setReqNum(otherProto.getTimedQuota()); 272 break; 273 case REQUEST_SIZE: 274 throttleBuilder.setReqSize(otherProto.getTimedQuota()); 275 break; 276 case WRITE_NUMBER: 277 throttleBuilder.setWriteNum(otherProto.getTimedQuota()); 278 break; 279 case WRITE_SIZE: 280 throttleBuilder.setWriteSize(otherProto.getTimedQuota()); 281 break; 282 case READ_NUMBER: 283 throttleBuilder.setReadNum(otherProto.getTimedQuota()); 284 break; 285 case READ_SIZE: 286 throttleBuilder.setReadSize(otherProto.getTimedQuota()); 287 break; 288 case REQUEST_CAPACITY_UNIT: 289 throttleBuilder.setReqCapacityUnit(otherProto.getTimedQuota()); 290 break; 291 case READ_CAPACITY_UNIT: 292 throttleBuilder.setReadCapacityUnit(otherProto.getTimedQuota()); 293 break; 294 case WRITE_CAPACITY_UNIT: 295 throttleBuilder.setWriteCapacityUnit(otherProto.getTimedQuota()); 296 break; 297 case ATOMIC_READ_SIZE: 298 throttleBuilder.setAtomicReadSize(otherProto.getTimedQuota()); 299 break; 300 case ATOMIC_REQUEST_NUMBER: 301 throttleBuilder.setAtomicReqNum(otherProto.getTimedQuota()); 302 break; 303 case ATOMIC_WRITE_SIZE: 304 throttleBuilder.setAtomicWriteSize(otherProto.getTimedQuota()); 305 break; 306 case REQUEST_HANDLER_USAGE_MS: 307 throttleBuilder.setReqHandlerUsageMs(otherProto.getTimedQuota()); 308 default: 309 } 310 } 311 } 312 313 // Propagate the space quota portion 314 QuotaProtos.SpaceQuota.Builder spaceBuilder = 315 (spaceProto == null ? null : spaceProto.toBuilder()); 316 if (other instanceof SpaceLimitSettings) { 317 if (spaceBuilder == null) { 318 spaceBuilder = QuotaProtos.SpaceQuota.newBuilder(); 319 } 320 SpaceLimitSettings settingsToMerge = (SpaceLimitSettings) other; 321 322 QuotaProtos.SpaceLimitRequest spaceRequest = settingsToMerge.getProto(); 323 324 // The message contained the expect SpaceQuota object 325 if (spaceRequest.hasQuota()) { 326 SpaceQuota quotaToMerge = spaceRequest.getQuota(); 327 // Validate that the two settings are for the same target. 328 // SpaceQuotas either apply to a table or a namespace (no user spacequota). 329 if ( 330 !Objects.equals(getTableName(), settingsToMerge.getTableName()) 331 && !Objects.equals(getNamespace(), settingsToMerge.getNamespace()) 332 ) { 333 throw new IllegalArgumentException("Cannot merge " + settingsToMerge + " into " + this); 334 } 335 336 if (quotaToMerge.getRemove()) { 337 // It means it's a remove request 338 // Update the builder to propagate the removal 339 spaceBuilder.setRemove(true).clearSoftLimit().clearViolationPolicy(); 340 } else { 341 // Add the new settings to the existing settings 342 spaceBuilder.mergeFrom(quotaToMerge); 343 } 344 } 345 } 346 347 boolean removeSpaceBuilder = 348 (spaceBuilder == null) || (spaceBuilder.hasRemove() && spaceBuilder.getRemove()); 349 350 Boolean bypassGlobals = this.bypassGlobals; 351 if (other instanceof QuotaGlobalsSettingsBypass) { 352 bypassGlobals = ((QuotaGlobalsSettingsBypass) other).getBypass(); 353 } 354 355 if (throttleBuilder == null && removeSpaceBuilder && bypassGlobals == null) { 356 return null; 357 } 358 359 return new GlobalQuotaSettingsImpl(getUserName(), getTableName(), getNamespace(), 360 getRegionServer(), (throttleBuilder == null ? null : throttleBuilder.build()), bypassGlobals, 361 (removeSpaceBuilder ? null : spaceBuilder.build())); 362 } 363 364 private void validateTimedQuota(final TimedQuota timedQuota) throws IOException { 365 if (timedQuota.getSoftLimit() < 1) { 366 throw new DoNotRetryIOException(new UnsupportedOperationException( 367 "The throttle limit must be greater then 0, got " + timedQuota.getSoftLimit())); 368 } 369 } 370 371 @Override 372 public String toString() { 373 StringBuilder builder = new StringBuilder(); 374 builder.append("GlobalQuota: "); 375 if (throttleProto != null) { 376 Map<ThrottleType, TimedQuota> throttleQuotas = buildThrottleQuotas(throttleProto); 377 builder.append(" { TYPE => THROTTLE "); 378 for (Entry<ThrottleType, TimedQuota> entry : throttleQuotas.entrySet()) { 379 final ThrottleType type = entry.getKey(); 380 final TimedQuota timedQuota = entry.getValue(); 381 builder.append("{THROTTLE_TYPE => ").append(type.name()).append(", LIMIT => "); 382 if (timedQuota.hasSoftLimit()) { 383 switch (type) { 384 case REQUEST_NUMBER: 385 case WRITE_NUMBER: 386 case READ_NUMBER: 387 case ATOMIC_REQUEST_NUMBER: 388 builder.append(String.format("%dreq", timedQuota.getSoftLimit())); 389 break; 390 case REQUEST_SIZE: 391 case WRITE_SIZE: 392 case READ_SIZE: 393 case ATOMIC_READ_SIZE: 394 case ATOMIC_WRITE_SIZE: 395 builder.append(sizeToString(timedQuota.getSoftLimit())); 396 break; 397 case REQUEST_CAPACITY_UNIT: 398 case READ_CAPACITY_UNIT: 399 case WRITE_CAPACITY_UNIT: 400 builder.append(String.format("%dCU", timedQuota.getSoftLimit())); 401 break; 402 case REQUEST_HANDLER_USAGE_MS: 403 builder.append(String.format("%dms", timedQuota.getSoftLimit())); 404 break; 405 default: 406 // no-op 407 } 408 } else if (timedQuota.hasShare()) { 409 builder.append(String.format("%.2f%%", timedQuota.getShare())); 410 } 411 builder.append('/'); 412 builder.append(timeToString(ProtobufUtil.toTimeUnit(timedQuota.getTimeUnit()))); 413 if (timedQuota.hasScope()) { 414 builder.append(", SCOPE => "); 415 builder.append(timedQuota.getScope().toString()); 416 } 417 } 418 builder.append("} } "); 419 } else { 420 builder.append(" {} "); 421 } 422 if (bypassGlobals != null) { 423 builder.append(" { GLOBAL_BYPASS => " + bypassGlobals + " } "); 424 } 425 if (spaceProto != null) { 426 builder.append(" { TYPE => SPACE"); 427 if (getTableName() != null) { 428 builder.append(", TABLE => ").append(getTableName()); 429 } 430 if (getNamespace() != null) { 431 builder.append(", NAMESPACE => ").append(getNamespace()); 432 } 433 if (spaceProto.getRemove()) { 434 builder.append(", REMOVE => ").append(spaceProto.getRemove()); 435 } else { 436 builder.append(", LIMIT => ").append(sizeToString(spaceProto.getSoftLimit())); 437 builder.append(", VIOLATION_POLICY => ").append(spaceProto.getViolationPolicy()); 438 } 439 builder.append(" } "); 440 } 441 return builder.toString(); 442 } 443 444 private Map<ThrottleType, TimedQuota> buildThrottleQuotas(Throttle proto) { 445 HashMap<ThrottleType, TimedQuota> quotas = new HashMap<>(); 446 if (proto.hasReadNum()) { 447 quotas.put(ThrottleType.READ_NUMBER, proto.getReadNum()); 448 } 449 if (proto.hasReadSize()) { 450 quotas.put(ThrottleType.READ_SIZE, proto.getReadSize()); 451 } 452 if (proto.hasReqNum()) { 453 quotas.put(ThrottleType.REQUEST_NUMBER, proto.getReqNum()); 454 } 455 if (proto.hasReqSize()) { 456 quotas.put(ThrottleType.REQUEST_SIZE, proto.getReqSize()); 457 } 458 if (proto.hasWriteNum()) { 459 quotas.put(ThrottleType.WRITE_NUMBER, proto.getWriteNum()); 460 } 461 if (proto.hasWriteSize()) { 462 quotas.put(ThrottleType.WRITE_SIZE, proto.getWriteSize()); 463 } 464 if (proto.hasReqCapacityUnit()) { 465 quotas.put(ThrottleType.REQUEST_CAPACITY_UNIT, proto.getReqCapacityUnit()); 466 } 467 if (proto.hasReadCapacityUnit()) { 468 quotas.put(ThrottleType.READ_CAPACITY_UNIT, proto.getReqCapacityUnit()); 469 } 470 if (proto.hasWriteCapacityUnit()) { 471 quotas.put(ThrottleType.WRITE_CAPACITY_UNIT, proto.getWriteCapacityUnit()); 472 } 473 return quotas; 474 } 475}