/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hdfs.server.namenode; import org.apache.hadoop.hdfs.protocol.DSQuotaExceededException; import org.apache.hadoop.hdfs.protocol.HdfsConstants; import org.apache.hadoop.hdfs.protocol.NSQuotaExceededException; import org.apache.hadoop.hdfs.protocol.QuotaExceededException; /** * Quota feature for {@link INodeDirectory}. */ public final class DirectoryWithQuotaFeature implements INode.Feature { public static final long DEFAULT_NAMESPACE_QUOTA = Long.MAX_VALUE; public static final long DEFAULT_DISKSPACE_QUOTA = HdfsConstants.QUOTA_RESET; /** Name space quota */ private long nsQuota = DEFAULT_NAMESPACE_QUOTA; /** Name space count */ private long namespace = 1L; /** Disk space quota */ private long dsQuota = DEFAULT_DISKSPACE_QUOTA; /** Disk space count */ private long diskspace = 0L; DirectoryWithQuotaFeature(long nsQuota, long dsQuota) { this.nsQuota = nsQuota; this.dsQuota = dsQuota; } /** @return the quota set or -1 if it is not set. */ Quota.Counts getQuota() { return Quota.Counts.newInstance(nsQuota, dsQuota); } /** Set this directory's quota * * @param nsQuota Namespace quota to be set * @param dsQuota Diskspace quota to be set */ void setQuota(long nsQuota, long dsQuota) { this.nsQuota = nsQuota; this.dsQuota = dsQuota; } Quota.Counts addNamespaceDiskspace(Quota.Counts counts) { counts.add(Quota.NAMESPACE, namespace); counts.add(Quota.DISKSPACE, diskspace); return counts; } ContentSummaryComputationContext computeContentSummary(final INodeDirectory dir, final ContentSummaryComputationContext summary) { final long original = summary.getCounts().get(Content.DISKSPACE); long oldYieldCount = summary.getYieldCount(); dir.computeDirectoryContentSummary(summary); // Check only when the content has not changed in the middle. if (oldYieldCount == summary.getYieldCount()) { checkDiskspace(dir, summary.getCounts().get(Content.DISKSPACE) - original); } return summary; } private void checkDiskspace(final INodeDirectory dir, final long computed) { if (-1 != getQuota().get(Quota.DISKSPACE) && diskspace != computed) { NameNode.LOG.error("BUG: Inconsistent diskspace for directory " + dir.getFullPathName() + ". Cached = " + diskspace + " != Computed = " + computed); } } void addSpaceConsumed(final INodeDirectory dir, final long nsDelta, final long dsDelta, boolean verify) throws QuotaExceededException { if (dir.isQuotaSet()) { // The following steps are important: // check quotas in this inode and all ancestors before changing counts // so that no change is made if there is any quota violation. // (1) verify quota in this inode if (verify) { verifyQuota(nsDelta, dsDelta); } // (2) verify quota and then add count in ancestors dir.addSpaceConsumed2Parent(nsDelta, dsDelta, verify); // (3) add count in this inode addSpaceConsumed2Cache(nsDelta, dsDelta); } else { dir.addSpaceConsumed2Parent(nsDelta, dsDelta, verify); } } /** Update the size of the tree * * @param nsDelta the change of the tree size * @param dsDelta change to disk space occupied */ public void addSpaceConsumed2Cache(long nsDelta, long dsDelta) { namespace += nsDelta; diskspace += dsDelta; } /** * Sets namespace and diskspace take by the directory rooted * at this INode. This should be used carefully. It does not check * for quota violations. * * @param namespace size of the directory to be set * @param diskspace disk space take by all the nodes under this directory */ void setSpaceConsumed(long namespace, long diskspace) { this.namespace = namespace; this.diskspace = diskspace; } /** @return the namespace and diskspace consumed. */ public Quota.Counts getSpaceConsumed() { return Quota.Counts.newInstance(namespace, diskspace); } /** Verify if the namespace quota is violated after applying delta. */ private void verifyNamespaceQuota(long delta) throws NSQuotaExceededException { if (Quota.isViolated(nsQuota, namespace, delta)) { throw new NSQuotaExceededException(nsQuota, namespace + delta); } } /** Verify if the diskspace quota is violated after applying delta. */ private void verifyDiskspaceQuota(long delta) throws DSQuotaExceededException { if (Quota.isViolated(dsQuota, diskspace, delta)) { throw new DSQuotaExceededException(dsQuota, diskspace + delta); } } /** * @throws QuotaExceededException if namespace or diskspace quotas is * violated after applying the deltas. */ void verifyQuota(long nsDelta, long dsDelta) throws QuotaExceededException { verifyNamespaceQuota(nsDelta); verifyDiskspaceQuota(dsDelta); } boolean isQuotaSet() { return nsQuota >= 0 || dsQuota >= 0; } private String namespaceString() { return "namespace: " + (nsQuota < 0? "-": namespace + "/" + nsQuota); } private String diskspaceString() { return "diskspace: " + (dsQuota < 0? "-": diskspace + "/" + dsQuota); } @Override public String toString() { return "Quota[" + namespaceString() + ", " + diskspaceString() + "]"; } }