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.util.Objects;
021import org.apache.hadoop.hbase.TableName;
022import org.apache.yetus.audience.InterfaceAudience;
023import org.apache.yetus.audience.InterfaceStability;
024
025import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
026import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetQuotaRequest;
027import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos;
028import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.SpaceLimitRequest;
029import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.SpaceQuota;
030
031/**
032 * A {@link QuotaSettings} implementation for configuring filesystem-use quotas.
033 */
034@InterfaceAudience.Private
035@InterfaceStability.Evolving
036class SpaceLimitSettings extends QuotaSettings {
037
038  private final SpaceLimitRequest proto;
039
040  SpaceLimitSettings(TableName tableName, long sizeLimit, SpaceViolationPolicy violationPolicy) {
041    super(null, Objects.requireNonNull(tableName), null, null);
042    validateSizeLimit(sizeLimit);
043    proto = buildProtoAddQuota(sizeLimit, Objects.requireNonNull(violationPolicy));
044  }
045
046  /**
047   * Constructs a {@code SpaceLimitSettings} to remove a space quota on the given {@code tableName}.
048   */
049  SpaceLimitSettings(TableName tableName) {
050    super(null, Objects.requireNonNull(tableName), null, null);
051    proto = buildProtoRemoveQuota();
052  }
053
054  SpaceLimitSettings(String namespace, long sizeLimit, SpaceViolationPolicy violationPolicy) {
055    super(null, null, Objects.requireNonNull(namespace), null);
056    validateSizeLimit(sizeLimit);
057    proto = buildProtoAddQuota(sizeLimit, Objects.requireNonNull(violationPolicy));
058  }
059
060  /**
061   * Constructs a {@code SpaceLimitSettings} to remove a space quota on the given {@code namespace}.
062   */
063  SpaceLimitSettings(String namespace) {
064    super(null, null, Objects.requireNonNull(namespace), null);
065    proto = buildProtoRemoveQuota();
066  }
067
068  SpaceLimitSettings(TableName tableName, String namespace, SpaceLimitRequest req) {
069    super(null, tableName, namespace, null);
070    proto = req;
071  }
072
073  /**
074   * Build a {@link SpaceLimitRequest} protobuf object from the given {@link SpaceQuota}.
075   * @param protoQuota The preconstructed SpaceQuota protobuf
076   * @return A protobuf request to change a space limit quota
077   */
078  private SpaceLimitRequest buildProtoFromQuota(SpaceQuota protoQuota) {
079    return SpaceLimitRequest.newBuilder().setQuota(protoQuota).build();
080  }
081
082  /**
083   * Builds a {@link SpaceQuota} protobuf object given the arguments.
084   * @param sizeLimit       The size limit of the quota.
085   * @param violationPolicy The action to take when the quota is exceeded.
086   * @return The protobuf SpaceQuota representation.
087   */
088  private SpaceLimitRequest buildProtoAddQuota(long sizeLimit,
089    SpaceViolationPolicy violationPolicy) {
090    return buildProtoFromQuota(SpaceQuota.newBuilder().setSoftLimit(sizeLimit)
091      .setViolationPolicy(ProtobufUtil.toProtoViolationPolicy(violationPolicy)).build());
092  }
093
094  /**
095   * Builds a {@link SpaceQuota} protobuf object to remove a quota.
096   * @return The protobuf SpaceQuota representation.
097   */
098  private SpaceLimitRequest buildProtoRemoveQuota() {
099    return SpaceLimitRequest.newBuilder().setQuota(SpaceQuota.newBuilder().setRemove(true).build())
100      .build();
101  }
102
103  /**
104   * Returns a copy of the internal state of <code>this</code>
105   */
106  SpaceLimitRequest getProto() {
107    return proto.toBuilder().build();
108  }
109
110  @Override
111  public QuotaType getQuotaType() {
112    return QuotaType.SPACE;
113  }
114
115  @Override
116  protected void setupSetQuotaRequest(SetQuotaRequest.Builder builder) {
117    // TableName/Namespace are serialized in QuotaSettings
118    builder.setSpaceLimit(proto);
119  }
120
121  /**
122   * Constructs a {@link SpaceLimitSettings} from the provided protobuf message and tablename.
123   * @param tableName The target tablename for the limit.
124   * @param proto     The protobuf representation.
125   * @return A QuotaSettings.
126   */
127  static SpaceLimitSettings fromSpaceQuota(final TableName tableName,
128    final QuotaProtos.SpaceQuota proto) {
129    validateProtoArguments(proto);
130    return new SpaceLimitSettings(tableName, proto.getSoftLimit(),
131      ProtobufUtil.toViolationPolicy(proto.getViolationPolicy()));
132  }
133
134  /**
135   * Constructs a {@link SpaceLimitSettings} from the provided protobuf message and namespace.
136   * @param namespace The target namespace for the limit.
137   * @param proto     The protobuf representation.
138   * @return A QuotaSettings.
139   */
140  static SpaceLimitSettings fromSpaceQuota(final String namespace,
141    final QuotaProtos.SpaceQuota proto) {
142    validateProtoArguments(proto);
143    return new SpaceLimitSettings(namespace, proto.getSoftLimit(),
144      ProtobufUtil.toViolationPolicy(proto.getViolationPolicy()));
145  }
146
147  /**
148   * Validates that the provided protobuf SpaceQuota has the necessary information to construct a
149   * {@link SpaceLimitSettings}.
150   * @param proto The protobuf message to validate.
151   */
152  static void validateProtoArguments(final QuotaProtos.SpaceQuota proto) {
153    if (!Objects.requireNonNull(proto).hasSoftLimit()) {
154      throw new IllegalArgumentException("Cannot handle SpaceQuota without a soft limit");
155    }
156    if (!proto.hasViolationPolicy()) {
157      throw new IllegalArgumentException("Cannot handle SpaceQuota without a violation policy");
158    }
159  }
160
161  @Override
162  public int hashCode() {
163    return Objects.hash(getTableName(), getNamespace(), proto);
164  }
165
166  @Override
167  public boolean equals(Object o) {
168    if (o == this) {
169      return true;
170    }
171    if (!(o instanceof SpaceLimitSettings)) {
172      return false;
173    }
174    // o is non-null and an instance of SpaceLimitSettings
175    SpaceLimitSettings other = (SpaceLimitSettings) o;
176    return Objects.equals(getTableName(), other.getTableName())
177      && Objects.equals(getNamespace(), other.getNamespace()) && Objects.equals(proto, other.proto);
178  }
179
180  @Override
181  public String toString() {
182    StringBuilder sb = new StringBuilder();
183    sb.append("TYPE => SPACE");
184    if (getTableName() != null) {
185      sb.append(", TABLE => ").append(getTableName());
186    }
187    if (getNamespace() != null) {
188      sb.append(", NAMESPACE => ").append(getNamespace());
189    }
190    if (proto.getQuota().getRemove()) {
191      sb.append(", REMOVE => ").append(proto.getQuota().getRemove());
192    } else {
193      sb.append(", LIMIT => ").append(sizeToString(proto.getQuota().getSoftLimit()));
194      sb.append(", VIOLATION_POLICY => ").append(proto.getQuota().getViolationPolicy());
195    }
196    return sb.toString();
197  }
198
199  @Override
200  protected QuotaSettings merge(QuotaSettings newSettings) {
201    if (newSettings instanceof SpaceLimitSettings) {
202      SpaceLimitSettings settingsToMerge = (SpaceLimitSettings) newSettings;
203
204      // The message contained the expect SpaceQuota object
205      if (settingsToMerge.proto.hasQuota()) {
206        SpaceQuota quotaToMerge = settingsToMerge.proto.getQuota();
207        if (quotaToMerge.getRemove()) {
208          return settingsToMerge;
209        } else {
210          // Validate that the two settings are for the same target.
211          // SpaceQuotas either apply to a table or a namespace (no user spacequota).
212          if (
213            !Objects.equals(getTableName(), settingsToMerge.getTableName())
214              && !Objects.equals(getNamespace(), settingsToMerge.getNamespace())
215          ) {
216            throw new IllegalArgumentException("Cannot merge " + newSettings + " into " + this);
217          }
218          // Create a builder from the old settings
219          SpaceQuota.Builder mergedBuilder = this.proto.getQuota().toBuilder();
220          // Build a new SpaceQuotas object from merging in the new settings
221          return new SpaceLimitSettings(getTableName(), getNamespace(),
222            buildProtoFromQuota(mergedBuilder.mergeFrom(quotaToMerge).build()));
223        }
224      }
225      // else, we don't know what to do, so return the original object
226    }
227    return this;
228  }
229
230  // Helper function to validate sizeLimit
231  private void validateSizeLimit(long sizeLimit) {
232    if (sizeLimit < 0L) {
233      throw new IllegalArgumentException("Size limit must be a non-negative value.");
234    }
235  }
236}