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}