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