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