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.hadoop.hbase.shaded.protobuf.ProtobufUtil;
031import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos;
032import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas;
033import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.SpaceQuota;
034import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Throttle;
035import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.TimedQuota;
036import org.apache.yetus.audience.InterfaceAudience;
037
038/**
039 * Implementation of {@link GlobalQuotaSettings} to hide the Protobuf messages we use internally.
040 */
041@InterfaceAudience.Private
042public class GlobalQuotaSettingsImpl extends GlobalQuotaSettings {
043
044  private final QuotaProtos.Throttle throttleProto;
045  private final Boolean bypassGlobals;
046  private final QuotaProtos.SpaceQuota spaceProto;
047
048  protected GlobalQuotaSettingsImpl(
049      String username, TableName tableName, String namespace, QuotaProtos.Quotas quotas) {
050    this(username, tableName, namespace,
051        (quotas != null && quotas.hasThrottle() ? quotas.getThrottle() : null),
052        (quotas != null && quotas.hasBypassGlobals() ? quotas.getBypassGlobals() : null),
053        (quotas != null && quotas.hasSpace() ? quotas.getSpace() : null));
054  }
055
056  protected GlobalQuotaSettingsImpl(
057      String userName, TableName tableName, String namespace, QuotaProtos.Throttle throttleProto,
058      Boolean bypassGlobals, QuotaProtos.SpaceQuota spaceProto) {
059    super(userName, tableName, namespace);
060    this.throttleProto = throttleProto;
061    this.bypassGlobals = bypassGlobals;
062    this.spaceProto = spaceProto;
063  }
064
065  @Override
066  public List<QuotaSettings> getQuotaSettings() {
067    // Very similar to QuotaSettingsFactory
068    List<QuotaSettings> settings = new ArrayList<>();
069    if (throttleProto != null) {
070      settings.addAll(QuotaSettingsFactory.fromThrottle(
071          getUserName(), getTableName(), getNamespace(), throttleProto));
072    }
073    if (bypassGlobals != null && bypassGlobals.booleanValue()) {
074      settings.add(new QuotaGlobalsSettingsBypass(
075          getUserName(), getTableName(), getNamespace(), true));
076    }
077    if (spaceProto != null) {
078      settings.add(QuotaSettingsFactory.fromSpace(getTableName(), getNamespace(), spaceProto));
079    }
080    return settings;
081  }
082
083  protected QuotaProtos.Throttle getThrottleProto() {
084    return this.throttleProto;
085  }
086
087  protected Boolean getBypassGlobals() {
088    return this.bypassGlobals;
089  }
090
091  protected QuotaProtos.SpaceQuota getSpaceProto() {
092    return this.spaceProto;
093  }
094
095  /**
096   * Constructs a new {@link Quotas} message from {@code this}.
097   */
098  protected Quotas toQuotas() {
099    QuotaProtos.Quotas.Builder builder = QuotaProtos.Quotas.newBuilder();
100    if (getThrottleProto() != null) {
101      builder.setThrottle(getThrottleProto());
102    }
103    if (getBypassGlobals() != null) {
104      builder.setBypassGlobals(getBypassGlobals());
105    }
106    if (getSpaceProto() != null) {
107      builder.setSpace(getSpaceProto());
108    }
109    return builder.build();
110  }
111
112  @Override
113  protected GlobalQuotaSettingsImpl merge(QuotaSettings other) throws IOException {
114    // Validate the quota subject
115    validateQuotaTarget(other);
116
117    // Propagate the Throttle
118    QuotaProtos.Throttle.Builder throttleBuilder =
119        throttleProto == null ? null : throttleProto.toBuilder();
120
121    if (other instanceof ThrottleSettings) {
122      ThrottleSettings otherThrottle = (ThrottleSettings) other;
123      if (!otherThrottle.proto.hasType() || !otherThrottle.proto.hasTimedQuota()) {
124        // To prevent the "empty" row in QuotaTableUtil.QUOTA_TABLE_NAME
125        throttleBuilder = null;
126      } else {
127        QuotaProtos.ThrottleRequest otherProto = otherThrottle.proto;
128        validateTimedQuota(otherProto.getTimedQuota());
129        if (throttleBuilder == null) {
130          throttleBuilder = QuotaProtos.Throttle.newBuilder();
131        }
132
133        switch (otherProto.getType()) {
134          case REQUEST_NUMBER:
135            throttleBuilder.setReqNum(otherProto.getTimedQuota());
136            break;
137          case REQUEST_SIZE:
138            throttleBuilder.setReqSize(otherProto.getTimedQuota());
139            break;
140          case WRITE_NUMBER:
141            throttleBuilder.setWriteNum(otherProto.getTimedQuota());
142            break;
143          case WRITE_SIZE:
144            throttleBuilder.setWriteSize(otherProto.getTimedQuota());
145            break;
146          case READ_NUMBER:
147            throttleBuilder.setReadNum(otherProto.getTimedQuota());
148            break;
149          case READ_SIZE:
150            throttleBuilder.setReadSize(otherProto.getTimedQuota());
151            break;
152        }
153      }
154    }
155
156    // Propagate the space quota portion
157    QuotaProtos.SpaceQuota.Builder spaceBuilder = (spaceProto == null
158        ? null : spaceProto.toBuilder());
159    if (other instanceof SpaceLimitSettings) {
160      if (spaceBuilder == null) {
161        spaceBuilder = QuotaProtos.SpaceQuota.newBuilder();
162      }
163      SpaceLimitSettings settingsToMerge = (SpaceLimitSettings) other;
164
165      QuotaProtos.SpaceLimitRequest spaceRequest = settingsToMerge.getProto();
166
167      // The message contained the expect SpaceQuota object
168      if (spaceRequest.hasQuota()) {
169        SpaceQuota quotaToMerge = spaceRequest.getQuota();
170        // Validate that the two settings are for the same target.
171        // SpaceQuotas either apply to a table or a namespace (no user spacequota).
172        if (!Objects.equals(getTableName(), settingsToMerge.getTableName())
173            && !Objects.equals(getNamespace(), settingsToMerge.getNamespace())) {
174          throw new IllegalArgumentException(
175              "Cannot merge " + settingsToMerge + " into " + this);
176        }
177
178        if (quotaToMerge.getRemove()) {
179          // Update the builder to propagate the removal
180          spaceBuilder.setRemove(true).clearSoftLimit().clearViolationPolicy();
181        } else {
182          // Add the new settings to the existing settings
183          spaceBuilder.mergeFrom(quotaToMerge);
184        }
185      }
186    }
187
188    Boolean bypassGlobals = this.bypassGlobals;
189    if (other instanceof QuotaGlobalsSettingsBypass) {
190      bypassGlobals = ((QuotaGlobalsSettingsBypass) other).getBypass();
191    }
192
193    if (throttleBuilder == null &&
194        (spaceBuilder == null || (spaceBuilder.hasRemove() && spaceBuilder.getRemove()))
195        && bypassGlobals == null) {
196      return null;
197    }
198
199    return new GlobalQuotaSettingsImpl(
200        getUserName(), getTableName(), getNamespace(),
201        (throttleBuilder == null ? null : throttleBuilder.build()), bypassGlobals,
202        (spaceBuilder == null ? null : spaceBuilder.build()));
203  }
204
205  private void validateTimedQuota(final TimedQuota timedQuota) throws IOException {
206    if (timedQuota.getSoftLimit() < 1) {
207      throw new DoNotRetryIOException(new UnsupportedOperationException(
208          "The throttle limit must be greater then 0, got " + timedQuota.getSoftLimit()));
209    }
210  }
211
212  @Override
213  public String toString() {
214    StringBuilder builder = new StringBuilder();
215    builder.append("GlobalQuota: ");
216    if (throttleProto != null) {
217      Map<ThrottleType,TimedQuota> throttleQuotas = buildThrottleQuotas(throttleProto);
218      builder.append(" { TYPE => THROTTLE ");
219      for (Entry<ThrottleType,TimedQuota> entry : throttleQuotas.entrySet()) {
220        final ThrottleType type = entry.getKey();
221        final TimedQuota timedQuota = entry.getValue();
222        builder.append("{THROTTLE_TYPE => ").append(type.name()).append(", LIMIT => ");
223        if (timedQuota.hasSoftLimit()) {
224          switch (type) {
225            case REQUEST_NUMBER:
226            case WRITE_NUMBER:
227            case READ_NUMBER:
228              builder.append(String.format("%dreq", timedQuota.getSoftLimit()));
229              break;
230            case REQUEST_SIZE:
231            case WRITE_SIZE:
232            case READ_SIZE:
233              builder.append(sizeToString(timedQuota.getSoftLimit()));
234              break;
235          }
236        } else if (timedQuota.hasShare()) {
237          builder.append(String.format("%.2f%%", timedQuota.getShare()));
238        }
239        builder.append('/');
240        builder.append(timeToString(ProtobufUtil.toTimeUnit(timedQuota.getTimeUnit())));
241        if (timedQuota.hasScope()) {
242          builder.append(", SCOPE => ");
243          builder.append(timedQuota.getScope().toString());
244        }
245      }
246      builder.append( "} } ");
247    } else {
248      builder.append(" {} ");
249    }
250    if (bypassGlobals != null) {
251      builder.append(" { GLOBAL_BYPASS => " + bypassGlobals + " } ");
252    }
253    if (spaceProto != null) {
254      builder.append(" { TYPE => SPACE");
255      if (getTableName() != null) {
256        builder.append(", TABLE => ").append(getTableName());
257      }
258      if (getNamespace() != null) {
259        builder.append(", NAMESPACE => ").append(getNamespace());
260      }
261      if (spaceProto.getRemove()) {
262        builder.append(", REMOVE => ").append(spaceProto.getRemove());
263      } else {
264        builder.append(", LIMIT => ").append(sizeToString(spaceProto.getSoftLimit()));
265        builder.append(", VIOLATION_POLICY => ").append(spaceProto.getViolationPolicy());
266      }
267      builder.append(" } ");
268    }
269    return builder.toString();
270  }
271
272  private Map<ThrottleType,TimedQuota> buildThrottleQuotas(Throttle proto) {
273    HashMap<ThrottleType,TimedQuota> quotas = new HashMap<>();
274    if (proto.hasReadNum()) {
275      quotas.put(ThrottleType.READ_NUMBER, proto.getReadNum());
276    }
277    if (proto.hasReadSize()) {
278      quotas.put(ThrottleType.READ_SIZE, proto.getReadSize());
279    }
280    if (proto.hasReqNum()) {
281      quotas.put(ThrottleType.REQUEST_NUMBER, proto.getReqNum());
282    }
283    if (proto.hasReqSize()) {
284      quotas.put(ThrottleType.REQUEST_SIZE, proto.getReqSize());
285    }
286    if (proto.hasWriteNum()) {
287      quotas.put(ThrottleType.WRITE_NUMBER, proto.getWriteNum());
288    }
289    if (proto.hasWriteSize()) {
290      quotas.put(ThrottleType.WRITE_SIZE, proto.getWriteSize());
291    }
292    return quotas;
293  }
294
295  private void clearThrottleBuilder(QuotaProtos.Throttle.Builder builder) {
296    builder.clearReadNum();
297    builder.clearReadSize();
298    builder.clearReqNum();
299    builder.clearReqSize();
300    builder.clearWriteNum();
301    builder.clearWriteSize();
302  }
303}