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.hdfs.protocol.DSQuotaExceededException;
021    import org.apache.hadoop.hdfs.protocol.HdfsConstants;
022    import org.apache.hadoop.hdfs.protocol.NSQuotaExceededException;
023    import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
024    
025    /**
026     * Quota feature for {@link INodeDirectory}. 
027     */
028    public 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    }