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