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.hdfs.server.namenode;
019
020import org.apache.hadoop.fs.StorageType;
021import org.apache.hadoop.hdfs.protocol.DSQuotaExceededException;
022import org.apache.hadoop.hdfs.protocol.HdfsConstants;
023import org.apache.hadoop.hdfs.protocol.NSQuotaExceededException;
024import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
025import org.apache.hadoop.hdfs.protocol.QuotaByStorageTypeExceededException;
026import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
027import org.apache.hadoop.hdfs.util.EnumCounters;
028
029/**
030 * Quota feature for {@link INodeDirectory}. 
031 */
032public final class DirectoryWithQuotaFeature implements INode.Feature {
033  public static final long DEFAULT_NAMESPACE_QUOTA = Long.MAX_VALUE;
034  public static final long DEFAULT_STORAGE_SPACE_QUOTA = HdfsConstants.QUOTA_RESET;
035
036  private QuotaCounts quota;
037  private QuotaCounts usage;
038
039  public static class Builder {
040    private QuotaCounts quota;
041    private QuotaCounts usage;
042
043    public Builder() {
044      this.quota = new QuotaCounts.Builder().nameSpace(DEFAULT_NAMESPACE_QUOTA).
045          storageSpace(DEFAULT_STORAGE_SPACE_QUOTA).
046          typeSpaces(DEFAULT_STORAGE_SPACE_QUOTA).build();
047      this.usage = new QuotaCounts.Builder().nameSpace(1).build();
048    }
049
050    public Builder nameSpaceQuota(long nameSpaceQuota) {
051      this.quota.setNameSpace(nameSpaceQuota);
052      return this;
053    }
054
055    public Builder storageSpaceQuota(long spaceQuota) {
056      this.quota.setStorageSpace(spaceQuota);
057      return this;
058    }
059
060    public Builder typeQuotas(EnumCounters<StorageType> typeQuotas) {
061      this.quota.setTypeSpaces(typeQuotas);
062      return this;
063    }
064
065    public Builder typeQuota(StorageType type, long quota) {
066      this.quota.setTypeSpace(type, quota);
067      return this;
068    }
069
070    public DirectoryWithQuotaFeature build() {
071      return new DirectoryWithQuotaFeature(this);
072    }
073  }
074
075  private DirectoryWithQuotaFeature(Builder builder) {
076    this.quota = builder.quota;
077    this.usage = builder.usage;
078  }
079
080  /** @return the quota set or -1 if it is not set. */
081  QuotaCounts getQuota() {
082    return new QuotaCounts.Builder().quotaCount(this.quota).build();
083  }
084
085  /** Set this directory's quota
086   * 
087   * @param nsQuota Namespace quota to be set
088   * @param ssQuota Storagespace quota to be set
089   * @param type Storage type of the storage space quota to be set.
090   *             To set storagespace/namespace quota, type must be null.
091   */
092  void setQuota(long nsQuota, long ssQuota, StorageType type) {
093    if (type != null) {
094      this.quota.setTypeSpace(type, ssQuota);
095    } else {
096      setQuota(nsQuota, ssQuota);
097    }
098  }
099
100  void setQuota(long nsQuota, long ssQuota) {
101    this.quota.setNameSpace(nsQuota);
102    this.quota.setStorageSpace(ssQuota);
103  }
104
105  void setQuota(long quota, StorageType type) {
106    this.quota.setTypeSpace(type, quota);
107  }
108
109  /** Set storage type quota in a batch. (Only used by FSImage load)
110   *
111   * @param tsQuotas type space counts for all storage types supporting quota
112   */
113  void setQuota(EnumCounters<StorageType> tsQuotas) {
114    this.quota.setTypeSpaces(tsQuotas);
115  }
116
117  /**
118   * Add current quota usage to counts and return the updated counts
119   * @param counts counts to be added with current quota usage
120   * @return counts that have been added with the current qutoa usage
121   */
122  QuotaCounts AddCurrentSpaceUsage(QuotaCounts counts) {
123    counts.add(this.usage);
124    return counts;
125  }
126
127  ContentSummaryComputationContext computeContentSummary(final INodeDirectory dir,
128      final ContentSummaryComputationContext summary) {
129    final long original = summary.getCounts().getStoragespace();
130    long oldYieldCount = summary.getYieldCount();
131    dir.computeDirectoryContentSummary(summary, Snapshot.CURRENT_STATE_ID);
132    // Check only when the content has not changed in the middle.
133    if (oldYieldCount == summary.getYieldCount()) {
134      checkStoragespace(dir, summary.getCounts().getStoragespace() - original);
135    }
136    return summary;
137  }
138
139  private void checkStoragespace(final INodeDirectory dir, final long computed) {
140    if (-1 != quota.getStorageSpace() && usage.getStorageSpace() != computed) {
141      NameNode.LOG.error("BUG: Inconsistent storagespace for directory "
142          + dir.getFullPathName() + ". Cached = " + usage.getStorageSpace()
143          + " != Computed = " + computed);
144    }
145  }
146
147  void addSpaceConsumed(final INodeDirectory dir, final QuotaCounts counts,
148      boolean verify) throws QuotaExceededException {
149    if (dir.isQuotaSet()) {
150      // The following steps are important:
151      // check quotas in this inode and all ancestors before changing counts
152      // so that no change is made if there is any quota violation.
153      // (1) verify quota in this inode
154      if (verify) {
155        verifyQuota(counts);
156      }
157      // (2) verify quota and then add count in ancestors
158      dir.addSpaceConsumed2Parent(counts, verify);
159      // (3) add count in this inode
160      addSpaceConsumed2Cache(counts);
161    } else {
162      dir.addSpaceConsumed2Parent(counts, verify);
163    }
164  }
165  
166  /** Update the space/namespace/type usage of the tree
167   * 
168   * @param delta the change of the namespace/space/type usage
169   */
170  public void addSpaceConsumed2Cache(QuotaCounts delta) {
171    usage.add(delta);
172  }
173
174  /** 
175   * Sets namespace and storagespace take by the directory rooted
176   * at this INode. This should be used carefully. It does not check 
177   * for quota violations.
178   * 
179   * @param namespace size of the directory to be set
180   * @param storagespace storage space take by all the nodes under this directory
181   * @param typespaces counters of storage type usage
182   */
183  void setSpaceConsumed(long namespace, long storagespace,
184      EnumCounters<StorageType> typespaces) {
185    usage.setNameSpace(namespace);
186    usage.setStorageSpace(storagespace);
187    usage.setTypeSpaces(typespaces);
188  }
189
190  void setSpaceConsumed(QuotaCounts c) {
191    usage.setNameSpace(c.getNameSpace());
192    usage.setStorageSpace(c.getStorageSpace());
193    usage.setTypeSpaces(c.getTypeSpaces());
194  }
195
196  /** @return the namespace and storagespace and typespace consumed. */
197  public QuotaCounts getSpaceConsumed() {
198    return new QuotaCounts.Builder().quotaCount(usage).build();
199  }
200
201  /** Verify if the namespace quota is violated after applying delta. */
202  private void verifyNamespaceQuota(long delta) throws NSQuotaExceededException {
203    if (Quota.isViolated(quota.getNameSpace(), usage.getNameSpace(), delta)) {
204      throw new NSQuotaExceededException(quota.getNameSpace(),
205          usage.getNameSpace() + delta);
206    }
207  }
208  /** Verify if the storagespace quota is violated after applying delta. */
209  private void verifyStoragespaceQuota(long delta) throws DSQuotaExceededException {
210    if (Quota.isViolated(quota.getStorageSpace(), usage.getStorageSpace(), delta)) {
211      throw new DSQuotaExceededException(quota.getStorageSpace(),
212          usage.getStorageSpace() + delta);
213    }
214  }
215
216  private void verifyQuotaByStorageType(EnumCounters<StorageType> typeDelta)
217      throws QuotaByStorageTypeExceededException {
218    if (!isQuotaByStorageTypeSet()) {
219      return;
220    }
221    for (StorageType t: StorageType.getTypesSupportingQuota()) {
222      if (!isQuotaByStorageTypeSet(t)) {
223        continue;
224      }
225      if (Quota.isViolated(quota.getTypeSpace(t), usage.getTypeSpace(t),
226          typeDelta.get(t))) {
227        throw new QuotaByStorageTypeExceededException(
228          quota.getTypeSpace(t), usage.getTypeSpace(t) + typeDelta.get(t), t);
229      }
230    }
231  }
232
233  /**
234   * @throws QuotaExceededException if namespace, storagespace or storage type
235   * space quota is violated after applying the deltas.
236   */
237  void verifyQuota(QuotaCounts counts) throws QuotaExceededException {
238    verifyNamespaceQuota(counts.getNameSpace());
239    verifyStoragespaceQuota(counts.getStorageSpace());
240    verifyQuotaByStorageType(counts.getTypeSpaces());
241  }
242
243  boolean isQuotaSet() {
244    return quota.anyNsSsCountGreaterOrEqual(0) ||
245        quota.anyTypeSpaceCountGreaterOrEqual(0);
246  }
247
248  boolean isQuotaByStorageTypeSet() {
249    return quota.anyTypeSpaceCountGreaterOrEqual(0);
250  }
251
252  boolean isQuotaByStorageTypeSet(StorageType t) {
253    return quota.getTypeSpace(t) >= 0;
254  }
255
256  private String namespaceString() {
257    return "namespace: " + (quota.getNameSpace() < 0? "-":
258        usage.getNameSpace() + "/" + quota.getNameSpace());
259  }
260  private String storagespaceString() {
261    return "storagespace: " + (quota.getStorageSpace() < 0? "-":
262        usage.getStorageSpace() + "/" + quota.getStorageSpace());
263  }
264
265  private String typeSpaceString() {
266    StringBuilder sb = new StringBuilder();
267    for (StorageType t : StorageType.getTypesSupportingQuota()) {
268      sb.append("StorageType: " + t +
269          (quota.getTypeSpace(t) < 0? "-":
270          usage.getTypeSpace(t) + "/" + usage.getTypeSpace(t)));
271    }
272    return sb.toString();
273  }
274
275  @Override
276  public String toString() {
277    return "Quota[" + namespaceString() + ", " + storagespaceString() +
278        ", " + typeSpaceString() + "]";
279  }
280}