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.permission.PermissionStatus;
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;
025
026import com.google.common.annotations.VisibleForTesting;
027
028/**
029 * Directory INode class that has a quota restriction
030 */
031public class INodeDirectoryWithQuota extends INodeDirectory {
032  /** Name space quota */
033  private long nsQuota = Long.MAX_VALUE;
034  /** Name space count */
035  private long namespace = 1L;
036  /** Disk space quota */
037  private long dsQuota = HdfsConstants.QUOTA_RESET;
038  /** Disk space count */
039  private long diskspace = 0L;
040  
041  /** Convert an existing directory inode to one with the given quota
042   * 
043   * @param nsQuota Namespace quota to be assigned to this inode
044   * @param dsQuota Diskspace quota to be assigned to this indoe
045   * @param other The other inode from which all other properties are copied
046   */
047  public INodeDirectoryWithQuota(INodeDirectory other, boolean adopt,
048      long nsQuota, long dsQuota) {
049    super(other, adopt);
050    final Quota.Counts counts = other.computeQuotaUsage();
051    this.namespace = counts.get(Quota.NAMESPACE);
052    this.diskspace = counts.get(Quota.DISKSPACE);
053    this.nsQuota = nsQuota;
054    this.dsQuota = dsQuota;
055  }
056  
057  /** constructor with no quota verification */
058  INodeDirectoryWithQuota(long id, byte[] name, PermissionStatus permissions,
059      long modificationTime, long nsQuota, long dsQuota) {
060    super(id, name, permissions, modificationTime);
061    this.nsQuota = nsQuota;
062    this.dsQuota = dsQuota;
063  }
064  
065  /** constructor with no quota verification */
066  INodeDirectoryWithQuota(long id, byte[] name, PermissionStatus permissions) {
067    super(id, name, permissions, 0L);
068  }
069  
070  /** Get this directory's namespace quota
071   * @return this directory's namespace quota
072   */
073  @Override
074  public long getNsQuota() {
075    return nsQuota;
076  }
077  
078  /** Get this directory's diskspace quota
079   * @return this directory's diskspace quota
080   */
081  @Override
082  public long getDsQuota() {
083    return dsQuota;
084  }
085  
086  /** Set this directory's quota
087   * 
088   * @param nsQuota Namespace quota to be set
089   * @param dsQuota diskspace quota to be set
090   */
091  public void setQuota(long nsQuota, long dsQuota) {
092    this.nsQuota = nsQuota;
093    this.dsQuota = dsQuota;
094  }
095  
096  @Override
097  public Quota.Counts computeQuotaUsage(Quota.Counts counts, boolean useCache,
098      int lastSnapshotId) {
099    if (useCache && isQuotaSet()) {
100      // use cache value
101      counts.add(Quota.NAMESPACE, namespace);
102      counts.add(Quota.DISKSPACE, diskspace);
103    } else {
104      super.computeQuotaUsage(counts, false, lastSnapshotId);
105    }
106    return counts;
107  }
108
109  @Override
110  public ContentSummaryComputationContext computeContentSummary(
111      final ContentSummaryComputationContext summary) {
112    final long original = summary.getCounts().get(Content.DISKSPACE);
113    long oldYieldCount = summary.getYieldCount();
114    super.computeContentSummary(summary);
115    // Check only when the content has not changed in the middle.
116    if (oldYieldCount == summary.getYieldCount()) {
117      checkDiskspace(summary.getCounts().get(Content.DISKSPACE) - original);
118    }
119    return summary;
120  }
121  
122  private void checkDiskspace(final long computed) {
123    if (-1 != getDsQuota() && diskspace != computed) {
124      NameNode.LOG.error("BUG: Inconsistent diskspace for directory "
125          + getFullPathName() + ". Cached = " + diskspace
126          + " != Computed = " + computed);
127    }
128  }
129
130  /** Get the number of names in the subtree rooted at this directory
131   * @return the size of the subtree rooted at this directory
132   */
133  long numItemsInTree() {
134    return namespace;
135  }
136  
137  @Override
138  public final void addSpaceConsumed(final long nsDelta, final long dsDelta,
139      boolean verify) throws QuotaExceededException {
140    if (isQuotaSet()) { 
141      // The following steps are important: 
142      // check quotas in this inode and all ancestors before changing counts
143      // so that no change is made if there is any quota violation.
144
145      // (1) verify quota in this inode
146      if (verify) {
147        verifyQuota(nsDelta, dsDelta);
148      }
149      // (2) verify quota and then add count in ancestors 
150      super.addSpaceConsumed(nsDelta, dsDelta, verify);
151      // (3) add count in this inode
152      addSpaceConsumed2Cache(nsDelta, dsDelta);
153    } else {
154      super.addSpaceConsumed(nsDelta, dsDelta, verify);
155    }
156  }
157  
158  /** Update the size of the tree
159   * 
160   * @param nsDelta the change of the tree size
161   * @param dsDelta change to disk space occupied
162   */
163  protected void addSpaceConsumed2Cache(long nsDelta, long dsDelta) {
164    namespace += nsDelta;
165    diskspace += dsDelta;
166  }
167  
168  /** 
169   * Sets namespace and diskspace take by the directory rooted 
170   * at this INode. This should be used carefully. It does not check 
171   * for quota violations.
172   * 
173   * @param namespace size of the directory to be set
174   * @param diskspace disk space take by all the nodes under this directory
175   */
176  void setSpaceConsumed(long namespace, long diskspace) {
177    this.namespace = namespace;
178    this.diskspace = diskspace;
179  }
180  
181  /** Verify if the namespace quota is violated after applying delta. */
182  void verifyNamespaceQuota(long delta) throws NSQuotaExceededException {
183    if (Quota.isViolated(nsQuota, namespace, delta)) {
184      throw new NSQuotaExceededException(nsQuota, namespace + delta);
185    }
186  }
187
188  /** Verify if the namespace count disk space satisfies the quota restriction 
189   * @throws QuotaExceededException if the given quota is less than the count
190   */
191  void verifyQuota(long nsDelta, long dsDelta) throws QuotaExceededException {
192    verifyNamespaceQuota(nsDelta);
193
194    if (Quota.isViolated(dsQuota, diskspace, dsDelta)) {
195      throw new DSQuotaExceededException(dsQuota, diskspace + dsDelta);
196    }
197  }
198
199  String namespaceString() {
200    return "namespace: " + (nsQuota < 0? "-": namespace + "/" + nsQuota);
201  }
202  String diskspaceString() {
203    return "diskspace: " + (dsQuota < 0? "-": diskspace + "/" + dsQuota);
204  }
205  String quotaString() {
206    return ", Quota[" + namespaceString() + ", " + diskspaceString() + "]";
207  }
208  
209  @VisibleForTesting
210  public long getNamespace() {
211    return this.namespace;
212  }
213  
214  @VisibleForTesting
215  public long getDiskspace() {
216    return this.diskspace;
217  }
218}