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}