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     */
018    package org.apache.hadoop.hdfs.server.namenode;
019    
020    import org.apache.hadoop.fs.permission.PermissionStatus;
021    import org.apache.hadoop.hdfs.protocol.DSQuotaExceededException;
022    import org.apache.hadoop.hdfs.protocol.HdfsConstants;
023    import org.apache.hadoop.hdfs.protocol.NSQuotaExceededException;
024    import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
025    
026    import com.google.common.annotations.VisibleForTesting;
027    
028    /**
029     * Directory INode class that has a quota restriction
030     */
031    public 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 Content.Counts computeContentSummary(
111          final Content.Counts counts) {
112        final long original = counts.get(Content.DISKSPACE);
113        super.computeContentSummary(counts);
114        checkDiskspace(counts.get(Content.DISKSPACE) - original);
115        return counts;
116      }
117      
118      private void checkDiskspace(final long computed) {
119        if (-1 != getDsQuota() && diskspace != computed) {
120          NameNode.LOG.error("BUG: Inconsistent diskspace for directory "
121              + getFullPathName() + ". Cached = " + diskspace
122              + " != Computed = " + computed);
123        }
124      }
125    
126      /** Get the number of names in the subtree rooted at this directory
127       * @return the size of the subtree rooted at this directory
128       */
129      long numItemsInTree() {
130        return namespace;
131      }
132      
133      @Override
134      public final void addSpaceConsumed(final long nsDelta, final long dsDelta,
135          boolean verify) throws QuotaExceededException {
136        if (isQuotaSet()) { 
137          // The following steps are important: 
138          // check quotas in this inode and all ancestors before changing counts
139          // so that no change is made if there is any quota violation.
140    
141          // (1) verify quota in this inode
142          if (verify) {
143            verifyQuota(nsDelta, dsDelta);
144          }
145          // (2) verify quota and then add count in ancestors 
146          super.addSpaceConsumed(nsDelta, dsDelta, verify);
147          // (3) add count in this inode
148          addSpaceConsumed2Cache(nsDelta, dsDelta);
149        } else {
150          super.addSpaceConsumed(nsDelta, dsDelta, verify);
151        }
152      }
153      
154      /** Update the size of the tree
155       * 
156       * @param nsDelta the change of the tree size
157       * @param dsDelta change to disk space occupied
158       */
159      protected void addSpaceConsumed2Cache(long nsDelta, long dsDelta) {
160        namespace += nsDelta;
161        diskspace += dsDelta;
162      }
163      
164      /** 
165       * Sets namespace and diskspace take by the directory rooted 
166       * at this INode. This should be used carefully. It does not check 
167       * for quota violations.
168       * 
169       * @param namespace size of the directory to be set
170       * @param diskspace disk space take by all the nodes under this directory
171       */
172      void setSpaceConsumed(long namespace, long diskspace) {
173        this.namespace = namespace;
174        this.diskspace = diskspace;
175      }
176      
177      /** Verify if the namespace quota is violated after applying delta. */
178      void verifyNamespaceQuota(long delta) throws NSQuotaExceededException {
179        if (Quota.isViolated(nsQuota, namespace, delta)) {
180          throw new NSQuotaExceededException(nsQuota, namespace + delta);
181        }
182      }
183    
184      /** Verify if the namespace count disk space satisfies the quota restriction 
185       * @throws QuotaExceededException if the given quota is less than the count
186       */
187      void verifyQuota(long nsDelta, long dsDelta) throws QuotaExceededException {
188        verifyNamespaceQuota(nsDelta);
189    
190        if (Quota.isViolated(dsQuota, diskspace, dsDelta)) {
191          throw new DSQuotaExceededException(dsQuota, diskspace + dsDelta);
192        }
193      }
194    
195      String namespaceString() {
196        return "namespace: " + (nsQuota < 0? "-": namespace + "/" + nsQuota);
197      }
198      String diskspaceString() {
199        return "diskspace: " + (dsQuota < 0? "-": diskspace + "/" + dsQuota);
200      }
201      String quotaString() {
202        return ", Quota[" + namespaceString() + ", " + diskspaceString() + "]";
203      }
204      
205      @VisibleForTesting
206      public long getNamespace() {
207        return this.namespace;
208      }
209      
210      @VisibleForTesting
211      public long getDiskspace() {
212        return this.diskspace;
213      }
214    }