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}