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.Objects;
022import java.util.concurrent.TimeUnit;
023
024import org.apache.hadoop.hbase.TableName;
025import org.apache.yetus.audience.InterfaceAudience;
026
027import org.apache.hbase.thirdparty.com.google.protobuf.TextFormat;
028import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
029import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetQuotaRequest;
030import org.apache.hadoop.hbase.quotas.QuotaSettingsFactory.QuotaGlobalsSettingsBypass;
031
032@InterfaceAudience.Public
033public abstract class QuotaSettings {
034  private final String userName;
035  private final String namespace;
036  private final TableName tableName;
037
038  protected QuotaSettings(final String userName, final TableName tableName,
039      final String namespace) {
040    this.userName = userName;
041    this.namespace = namespace;
042    this.tableName = tableName;
043  }
044
045  public abstract QuotaType getQuotaType();
046
047  public String getUserName() {
048    return userName;
049  }
050
051  public TableName getTableName() {
052    return tableName;
053  }
054
055  public String getNamespace() {
056    return namespace;
057  }
058
059  /**
060   * Converts the protocol buffer request into a QuotaSetting POJO. Arbitrarily
061   * enforces that the request only contain one "limit", despite the message
062   * allowing multiple. The public API does not allow such use of the message.
063   *
064   * @param request The protocol buffer request.
065   * @return A {@link QuotaSettings} POJO.
066   */
067  @InterfaceAudience.Private
068  public static QuotaSettings buildFromProto(SetQuotaRequest request) {
069    String username = null;
070    if (request.hasUserName()) {
071      username = request.getUserName();
072    }
073    TableName tableName = null;
074    if (request.hasTableName()) {
075      tableName = ProtobufUtil.toTableName(request.getTableName());
076    }
077    String namespace = null;
078    if (request.hasNamespace()) {
079      namespace = request.getNamespace();
080    }
081    if (request.hasBypassGlobals()) {
082      // Make sure we don't have either of the two below limits also included
083      if (request.hasSpaceLimit() || request.hasThrottle()) {
084        throw new IllegalStateException(
085            "SetQuotaRequest has multiple limits: " + TextFormat.shortDebugString(request));
086      }
087      return new QuotaGlobalsSettingsBypass(
088          username, tableName, namespace, request.getBypassGlobals());
089    } else if (request.hasSpaceLimit()) {
090      // Make sure we don't have the below limit as well
091      if (request.hasThrottle()) {
092        throw new IllegalStateException(
093            "SetQuotaRequests has multiple limits: " + TextFormat.shortDebugString(request));
094      }
095      // Sanity check on the pb received.
096      if (!request.getSpaceLimit().hasQuota()) {
097        throw new IllegalArgumentException(
098            "SpaceLimitRequest is missing the expected SpaceQuota.");
099      }
100      return QuotaSettingsFactory.fromSpace(
101          tableName, namespace, request.getSpaceLimit().getQuota());
102    } else if (request.hasThrottle()) {
103      return new ThrottleSettings(username, tableName, namespace, request.getThrottle());
104    } else {
105      throw new IllegalStateException("Unhandled SetRequestRequest state");
106    }
107  }
108
109  /**
110   * Convert a QuotaSettings to a protocol buffer SetQuotaRequest.
111   * This is used internally by the Admin client to serialize the quota settings
112   * and send them to the master.
113   */
114  @InterfaceAudience.Private
115  public static SetQuotaRequest buildSetQuotaRequestProto(final QuotaSettings settings) {
116    SetQuotaRequest.Builder builder = SetQuotaRequest.newBuilder();
117    if (settings.getUserName() != null) {
118      builder.setUserName(settings.getUserName());
119    }
120    if (settings.getTableName() != null) {
121      builder.setTableName(ProtobufUtil.toProtoTableName(settings.getTableName()));
122    }
123    if (settings.getNamespace() != null) {
124      builder.setNamespace(settings.getNamespace());
125    }
126    settings.setupSetQuotaRequest(builder);
127    return builder.build();
128  }
129
130  /**
131   * Called by toSetQuotaRequestProto()
132   * the subclass should implement this method to set the specific SetQuotaRequest
133   * properties.
134   */
135  @InterfaceAudience.Private
136  protected abstract void setupSetQuotaRequest(SetQuotaRequest.Builder builder);
137
138  protected String ownerToString() {
139    StringBuilder builder = new StringBuilder();
140    if (userName != null) {
141      builder.append("USER => '");
142      builder.append(userName);
143      builder.append("', ");
144    }
145    if (tableName != null) {
146      builder.append("TABLE => '");
147      builder.append(tableName.toString());
148      builder.append("', ");
149    }
150    if (namespace != null) {
151      builder.append("NAMESPACE => '");
152      builder.append(namespace);
153      builder.append("', ");
154    }
155    return builder.toString();
156  }
157
158  protected static String sizeToString(final long size) {
159    if (size >= (1L << 50)) return String.format("%dP", size / (1L << 50));
160    if (size >= (1L << 40)) return String.format("%dT", size / (1L << 40));
161    if (size >= (1L << 30)) return String.format("%dG", size / (1L << 30));
162    if (size >= (1L << 20)) return String.format("%dM", size / (1L << 20));
163    if (size >= (1L << 10)) return String.format("%dK", size / (1L << 10));
164    return String.format("%dB", size);
165  }
166
167  protected static String timeToString(final TimeUnit timeUnit) {
168    switch (timeUnit) {
169      case NANOSECONDS:  return "nsec";
170      case MICROSECONDS: return "usec";
171      case MILLISECONDS: return "msec";
172      case SECONDS:      return "sec";
173      case MINUTES:      return "min";
174      case HOURS:        return "hour";
175      case DAYS:         return "day";
176    }
177    throw new RuntimeException("Invalid TimeUnit " + timeUnit);
178  }
179
180  /**
181   * Merges the provided settings with {@code this} and returns a new settings
182   * object to the caller if the merged settings differ from the original.
183   *
184   * @param newSettings The new settings to merge in.
185   * @return The merged {@link QuotaSettings} object or null if the quota should be deleted.
186   */
187  abstract QuotaSettings merge(QuotaSettings newSettings) throws IOException;
188
189  /**
190   * Validates that settings being merged into {@code this} is targeting the same "subject", e.g.
191   * user, table, namespace.
192   *
193   * @param mergee The quota settings to be merged into {@code this}.
194   * @throws IllegalArgumentException if the subjects are not equal.
195   */
196  void validateQuotaTarget(QuotaSettings mergee) {
197    if (!Objects.equals(getUserName(), mergee.getUserName())) {
198      throw new IllegalArgumentException("Mismatched user names on settings to merge");
199    }
200    if (!Objects.equals(getTableName(), mergee.getTableName())) {
201      throw new IllegalArgumentException("Mismatched table names on settings to merge");
202    }
203    if (!Objects.equals(getNamespace(), mergee.getNamespace())) {
204      throw new IllegalArgumentException("Mismatched namespace on settings to merge");
205    }
206  }
207}