| /** |
| * 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.fs.StorageType; |
| 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; |
| import org.apache.hadoop.hdfs.protocol.QuotaByStorageTypeExceededException; |
| import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot; |
| import org.apache.hadoop.hdfs.util.EnumCounters; |
| import org.apache.hadoop.security.AccessControlException; |
| |
| /** |
| * 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_STORAGE_SPACE_QUOTA = HdfsConstants.QUOTA_RESET; |
| |
| private QuotaCounts quota; |
| private QuotaCounts usage; |
| |
| public static class Builder { |
| private QuotaCounts quota; |
| private QuotaCounts usage; |
| |
| public Builder() { |
| this.quota = new QuotaCounts.Builder().nameSpace(DEFAULT_NAMESPACE_QUOTA). |
| storageSpace(DEFAULT_STORAGE_SPACE_QUOTA). |
| typeSpaces(DEFAULT_STORAGE_SPACE_QUOTA).build(); |
| this.usage = new QuotaCounts.Builder().nameSpace(1).build(); |
| } |
| |
| public Builder nameSpaceQuota(long nameSpaceQuota) { |
| this.quota.setNameSpace(nameSpaceQuota); |
| return this; |
| } |
| |
| public Builder storageSpaceQuota(long spaceQuota) { |
| this.quota.setStorageSpace(spaceQuota); |
| return this; |
| } |
| |
| public Builder typeQuotas(EnumCounters<StorageType> typeQuotas) { |
| this.quota.setTypeSpaces(typeQuotas); |
| return this; |
| } |
| |
| public Builder typeQuota(StorageType type, long quota) { |
| this.quota.setTypeSpace(type, quota); |
| return this; |
| } |
| |
| public DirectoryWithQuotaFeature build() { |
| return new DirectoryWithQuotaFeature(this); |
| } |
| } |
| |
| private DirectoryWithQuotaFeature(Builder builder) { |
| this.quota = builder.quota; |
| this.usage = builder.usage; |
| } |
| |
| /** @return the quota set or -1 if it is not set. */ |
| QuotaCounts getQuota() { |
| return new QuotaCounts.Builder().quotaCount(this.quota).build(); |
| } |
| |
| /** Set this directory's quota |
| * |
| * @param nsQuota Namespace quota to be set |
| * @param ssQuota Storagespace quota to be set |
| * @param type Storage type of the storage space quota to be set. |
| * To set storagespace/namespace quota, type must be null. |
| */ |
| void setQuota(long nsQuota, long ssQuota, StorageType type) { |
| if (type != null) { |
| this.quota.setTypeSpace(type, ssQuota); |
| } else { |
| setQuota(nsQuota, ssQuota); |
| } |
| } |
| |
| void setQuota(long nsQuota, long ssQuota) { |
| this.quota.setNameSpace(nsQuota); |
| this.quota.setStorageSpace(ssQuota); |
| } |
| |
| void setQuota(long quota, StorageType type) { |
| this.quota.setTypeSpace(type, quota); |
| } |
| |
| /** Set storage type quota in a batch. (Only used by FSImage load) |
| * |
| * @param tsQuotas type space counts for all storage types supporting quota |
| */ |
| void setQuota(EnumCounters<StorageType> tsQuotas) { |
| this.quota.setTypeSpaces(tsQuotas); |
| } |
| |
| /** |
| * Add current quota usage to counts and return the updated counts |
| * @param counts counts to be added with current quota usage |
| * @return counts that have been added with the current qutoa usage |
| */ |
| QuotaCounts AddCurrentSpaceUsage(QuotaCounts counts) { |
| counts.add(this.usage); |
| return counts; |
| } |
| |
| ContentSummaryComputationContext computeContentSummary(final INodeDirectory dir, |
| final ContentSummaryComputationContext summary) |
| throws AccessControlException { |
| final long original = summary.getCounts().getStoragespace(); |
| long oldYieldCount = summary.getYieldCount(); |
| dir.computeDirectoryContentSummary(summary, Snapshot.CURRENT_STATE_ID); |
| // Check only when the content has not changed in the middle. |
| if (oldYieldCount == summary.getYieldCount()) { |
| checkStoragespace(dir, summary.getCounts().getStoragespace() - original); |
| } |
| return summary; |
| } |
| |
| private void checkStoragespace(final INodeDirectory dir, final long computed) { |
| if (-1 != quota.getStorageSpace() && usage.getStorageSpace() != computed) { |
| NameNode.LOG.error("BUG: Inconsistent storagespace for directory " |
| + dir.getFullPathName() + ". Cached = " + usage.getStorageSpace() |
| + " != Computed = " + computed); |
| } |
| } |
| |
| void addSpaceConsumed(final INodeDirectory dir, final QuotaCounts counts, |
| 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(counts); |
| } |
| // (2) verify quota and then add count in ancestors |
| dir.addSpaceConsumed2Parent(counts, verify); |
| // (3) add count in this inode |
| addSpaceConsumed2Cache(counts); |
| } else { |
| dir.addSpaceConsumed2Parent(counts, verify); |
| } |
| } |
| |
| /** Update the space/namespace/type usage of the tree |
| * |
| * @param delta the change of the namespace/space/type usage |
| */ |
| public void addSpaceConsumed2Cache(QuotaCounts delta) { |
| usage.add(delta); |
| } |
| |
| /** |
| * Sets namespace and storagespace 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 storagespace storage space take by all the nodes under this directory |
| * @param typespaces counters of storage type usage |
| */ |
| void setSpaceConsumed(long namespace, long storagespace, |
| EnumCounters<StorageType> typespaces) { |
| usage.setNameSpace(namespace); |
| usage.setStorageSpace(storagespace); |
| usage.setTypeSpaces(typespaces); |
| } |
| |
| void setSpaceConsumed(QuotaCounts c) { |
| usage.setNameSpace(c.getNameSpace()); |
| usage.setStorageSpace(c.getStorageSpace()); |
| usage.setTypeSpaces(c.getTypeSpaces()); |
| } |
| |
| /** @return the namespace and storagespace and typespace consumed. */ |
| public QuotaCounts getSpaceConsumed() { |
| return new QuotaCounts.Builder().quotaCount(usage).build(); |
| } |
| |
| /** Verify if the namespace quota is violated after applying delta. */ |
| private void verifyNamespaceQuota(long delta) throws NSQuotaExceededException { |
| if (Quota.isViolated(quota.getNameSpace(), usage.getNameSpace(), delta)) { |
| throw new NSQuotaExceededException(quota.getNameSpace(), |
| usage.getNameSpace() + delta); |
| } |
| } |
| /** Verify if the storagespace quota is violated after applying delta. */ |
| private void verifyStoragespaceQuota(long delta) throws DSQuotaExceededException { |
| if (Quota.isViolated(quota.getStorageSpace(), usage.getStorageSpace(), delta)) { |
| throw new DSQuotaExceededException(quota.getStorageSpace(), |
| usage.getStorageSpace() + delta); |
| } |
| } |
| |
| private void verifyQuotaByStorageType(EnumCounters<StorageType> typeDelta) |
| throws QuotaByStorageTypeExceededException { |
| if (!isQuotaByStorageTypeSet()) { |
| return; |
| } |
| for (StorageType t: StorageType.getTypesSupportingQuota()) { |
| if (!isQuotaByStorageTypeSet(t)) { |
| continue; |
| } |
| if (Quota.isViolated(quota.getTypeSpace(t), usage.getTypeSpace(t), |
| typeDelta.get(t))) { |
| throw new QuotaByStorageTypeExceededException( |
| quota.getTypeSpace(t), usage.getTypeSpace(t) + typeDelta.get(t), t); |
| } |
| } |
| } |
| |
| /** |
| * @throws QuotaExceededException if namespace, storagespace or storage type |
| * space quota is violated after applying the deltas. |
| */ |
| void verifyQuota(QuotaCounts counts) throws QuotaExceededException { |
| verifyNamespaceQuota(counts.getNameSpace()); |
| verifyStoragespaceQuota(counts.getStorageSpace()); |
| verifyQuotaByStorageType(counts.getTypeSpaces()); |
| } |
| |
| boolean isQuotaSet() { |
| return quota.anyNsSsCountGreaterOrEqual(0) || |
| quota.anyTypeSpaceCountGreaterOrEqual(0); |
| } |
| |
| boolean isQuotaByStorageTypeSet() { |
| return quota.anyTypeSpaceCountGreaterOrEqual(0); |
| } |
| |
| boolean isQuotaByStorageTypeSet(StorageType t) { |
| return quota.getTypeSpace(t) >= 0; |
| } |
| |
| private String namespaceString() { |
| return "namespace: " + (quota.getNameSpace() < 0? "-": |
| usage.getNameSpace() + "/" + quota.getNameSpace()); |
| } |
| private String storagespaceString() { |
| return "storagespace: " + (quota.getStorageSpace() < 0? "-": |
| usage.getStorageSpace() + "/" + quota.getStorageSpace()); |
| } |
| |
| private String typeSpaceString() { |
| StringBuilder sb = new StringBuilder(); |
| for (StorageType t : StorageType.getTypesSupportingQuota()) { |
| sb.append("StorageType: " + t + |
| (quota.getTypeSpace(t) < 0? "-": |
| usage.getTypeSpace(t) + "/" + usage.getTypeSpace(t))); |
| } |
| return sb.toString(); |
| } |
| |
| @Override |
| public String toString() { |
| return "Quota[" + namespaceString() + ", " + storagespaceString() + |
| ", " + typeSpaceString() + "]"; |
| } |
| } |