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.hdfs.protocol.DSQuotaExceededException;
021import org.apache.hadoop.hdfs.protocol.HdfsConstants;
022import org.apache.hadoop.hdfs.protocol.NSQuotaExceededException;
023import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
024
025/**
026 * Quota feature for {@link INodeDirectory}. 
027 */
028public final class DirectoryWithQuotaFeature implements INode.Feature {
029  public static final long DEFAULT_NAMESPACE_QUOTA = Long.MAX_VALUE;
030  public static final long DEFAULT_DISKSPACE_QUOTA = HdfsConstants.QUOTA_RESET;
031
032  /** Name space quota */
033  private long nsQuota = DEFAULT_NAMESPACE_QUOTA;
034  /** Name space count */
035  private long namespace = 1L;
036  /** Disk space quota */
037  private long dsQuota = DEFAULT_DISKSPACE_QUOTA;
038  /** Disk space count */
039  private long diskspace = 0L;
040  
041  DirectoryWithQuotaFeature(long nsQuota, long dsQuota) {
042    this.nsQuota = nsQuota;
043    this.dsQuota = dsQuota;
044  }
045
046  /** @return the quota set or -1 if it is not set. */
047  Quota.Counts getQuota() {
048    return Quota.Counts.newInstance(nsQuota, dsQuota);
049  }
050  
051  /** Set this directory's quota
052   * 
053   * @param nsQuota Namespace quota to be set
054   * @param dsQuota Diskspace quota to be set
055   */
056  void setQuota(long nsQuota, long dsQuota) {
057    this.nsQuota = nsQuota;
058    this.dsQuota = dsQuota;
059  }
060  
061  Quota.Counts addNamespaceDiskspace(Quota.Counts counts) {
062    counts.add(Quota.NAMESPACE, namespace);
063    counts.add(Quota.DISKSPACE, diskspace);
064    return counts;
065  }
066
067  ContentSummaryComputationContext computeContentSummary(final INodeDirectory dir,
068      final ContentSummaryComputationContext summary) {
069    final long original = summary.getCounts().get(Content.DISKSPACE);
070    long oldYieldCount = summary.getYieldCount();
071    dir.computeDirectoryContentSummary(summary);
072    // Check only when the content has not changed in the middle.
073    if (oldYieldCount == summary.getYieldCount()) {
074      checkDiskspace(dir, summary.getCounts().get(Content.DISKSPACE) - original);
075    }
076    return summary;
077  }
078  
079  private void checkDiskspace(final INodeDirectory dir, final long computed) {
080    if (-1 != getQuota().get(Quota.DISKSPACE) && diskspace != computed) {
081      NameNode.LOG.error("BUG: Inconsistent diskspace for directory "
082          + dir.getFullPathName() + ". Cached = " + diskspace
083          + " != Computed = " + computed);
084    }
085  }
086
087  void addSpaceConsumed(final INodeDirectory dir, final long nsDelta,
088      final long dsDelta, boolean verify) throws QuotaExceededException {
089    if (dir.isQuotaSet()) { 
090      // The following steps are important: 
091      // check quotas in this inode and all ancestors before changing counts
092      // so that no change is made if there is any quota violation.
093
094      // (1) verify quota in this inode
095      if (verify) {
096        verifyQuota(nsDelta, dsDelta);
097      }
098      // (2) verify quota and then add count in ancestors 
099      dir.addSpaceConsumed2Parent(nsDelta, dsDelta, verify);
100      // (3) add count in this inode
101      addSpaceConsumed2Cache(nsDelta, dsDelta);
102    } else {
103      dir.addSpaceConsumed2Parent(nsDelta, dsDelta, verify);
104    }
105  }
106  
107  /** Update the size of the tree
108   * 
109   * @param nsDelta the change of the tree size
110   * @param dsDelta change to disk space occupied
111   */
112  public void addSpaceConsumed2Cache(long nsDelta, long dsDelta) {
113    namespace += nsDelta;
114    diskspace += dsDelta;
115  }
116  
117  /** 
118   * Sets namespace and diskspace take by the directory rooted 
119   * at this INode. This should be used carefully. It does not check 
120   * for quota violations.
121   * 
122   * @param namespace size of the directory to be set
123   * @param diskspace disk space take by all the nodes under this directory
124   */
125  void setSpaceConsumed(long namespace, long diskspace) {
126    this.namespace = namespace;
127    this.diskspace = diskspace;
128  }
129  
130  /** @return the namespace and diskspace consumed. */
131  public Quota.Counts getSpaceConsumed() {
132    return Quota.Counts.newInstance(namespace, diskspace);
133  }
134
135  /** Verify if the namespace quota is violated after applying delta. */
136  private void verifyNamespaceQuota(long delta) throws NSQuotaExceededException {
137    if (Quota.isViolated(nsQuota, namespace, delta)) {
138      throw new NSQuotaExceededException(nsQuota, namespace + delta);
139    }
140  }
141  /** Verify if the diskspace quota is violated after applying delta. */
142  private void verifyDiskspaceQuota(long delta) throws DSQuotaExceededException {
143    if (Quota.isViolated(dsQuota, diskspace, delta)) {
144      throw new DSQuotaExceededException(dsQuota, diskspace + delta);
145    }
146  }
147
148  /**
149   * @throws QuotaExceededException if namespace or diskspace quotas is
150   *         violated after applying the deltas.
151   */
152  void verifyQuota(long nsDelta, long dsDelta) throws QuotaExceededException {
153    verifyNamespaceQuota(nsDelta);
154    verifyDiskspaceQuota(dsDelta);
155  }
156  
157  boolean isQuotaSet() {
158    return nsQuota >= 0 || dsQuota >= 0;
159  }
160
161  private String namespaceString() {
162    return "namespace: " + (nsQuota < 0? "-": namespace + "/" + nsQuota);
163  }
164  private String diskspaceString() {
165    return "diskspace: " + (dsQuota < 0? "-": diskspace + "/" + dsQuota);
166  }
167  
168  @Override
169  public String toString() {
170    return "Quota[" + namespaceString() + ", " + diskspaceString() + "]";
171  }
172}