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.StorageType; 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; 025import org.apache.hadoop.hdfs.protocol.QuotaByStorageTypeExceededException; 026import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot; 027import org.apache.hadoop.hdfs.util.EnumCounters; 028 029/** 030 * Quota feature for {@link INodeDirectory}. 031 */ 032public final class DirectoryWithQuotaFeature implements INode.Feature { 033 public static final long DEFAULT_NAMESPACE_QUOTA = Long.MAX_VALUE; 034 public static final long DEFAULT_STORAGE_SPACE_QUOTA = HdfsConstants.QUOTA_RESET; 035 036 private QuotaCounts quota; 037 private QuotaCounts usage; 038 039 public static class Builder { 040 private QuotaCounts quota; 041 private QuotaCounts usage; 042 043 public Builder() { 044 this.quota = new QuotaCounts.Builder().nameSpace(DEFAULT_NAMESPACE_QUOTA). 045 storageSpace(DEFAULT_STORAGE_SPACE_QUOTA). 046 typeSpaces(DEFAULT_STORAGE_SPACE_QUOTA).build(); 047 this.usage = new QuotaCounts.Builder().nameSpace(1).build(); 048 } 049 050 public Builder nameSpaceQuota(long nameSpaceQuota) { 051 this.quota.setNameSpace(nameSpaceQuota); 052 return this; 053 } 054 055 public Builder storageSpaceQuota(long spaceQuota) { 056 this.quota.setStorageSpace(spaceQuota); 057 return this; 058 } 059 060 public Builder typeQuotas(EnumCounters<StorageType> typeQuotas) { 061 this.quota.setTypeSpaces(typeQuotas); 062 return this; 063 } 064 065 public Builder typeQuota(StorageType type, long quota) { 066 this.quota.setTypeSpace(type, quota); 067 return this; 068 } 069 070 public DirectoryWithQuotaFeature build() { 071 return new DirectoryWithQuotaFeature(this); 072 } 073 } 074 075 private DirectoryWithQuotaFeature(Builder builder) { 076 this.quota = builder.quota; 077 this.usage = builder.usage; 078 } 079 080 /** @return the quota set or -1 if it is not set. */ 081 QuotaCounts getQuota() { 082 return new QuotaCounts.Builder().quotaCount(this.quota).build(); 083 } 084 085 /** Set this directory's quota 086 * 087 * @param nsQuota Namespace quota to be set 088 * @param ssQuota Storagespace quota to be set 089 * @param type Storage type of the storage space quota to be set. 090 * To set storagespace/namespace quota, type must be null. 091 */ 092 void setQuota(long nsQuota, long ssQuota, StorageType type) { 093 if (type != null) { 094 this.quota.setTypeSpace(type, ssQuota); 095 } else { 096 setQuota(nsQuota, ssQuota); 097 } 098 } 099 100 void setQuota(long nsQuota, long ssQuota) { 101 this.quota.setNameSpace(nsQuota); 102 this.quota.setStorageSpace(ssQuota); 103 } 104 105 void setQuota(long quota, StorageType type) { 106 this.quota.setTypeSpace(type, quota); 107 } 108 109 /** Set storage type quota in a batch. (Only used by FSImage load) 110 * 111 * @param tsQuotas type space counts for all storage types supporting quota 112 */ 113 void setQuota(EnumCounters<StorageType> tsQuotas) { 114 this.quota.setTypeSpaces(tsQuotas); 115 } 116 117 /** 118 * Add current quota usage to counts and return the updated counts 119 * @param counts counts to be added with current quota usage 120 * @return counts that have been added with the current qutoa usage 121 */ 122 QuotaCounts AddCurrentSpaceUsage(QuotaCounts counts) { 123 counts.add(this.usage); 124 return counts; 125 } 126 127 ContentSummaryComputationContext computeContentSummary(final INodeDirectory dir, 128 final ContentSummaryComputationContext summary) { 129 final long original = summary.getCounts().getStoragespace(); 130 long oldYieldCount = summary.getYieldCount(); 131 dir.computeDirectoryContentSummary(summary, Snapshot.CURRENT_STATE_ID); 132 // Check only when the content has not changed in the middle. 133 if (oldYieldCount == summary.getYieldCount()) { 134 checkStoragespace(dir, summary.getCounts().getStoragespace() - original); 135 } 136 return summary; 137 } 138 139 private void checkStoragespace(final INodeDirectory dir, final long computed) { 140 if (-1 != quota.getStorageSpace() && usage.getStorageSpace() != computed) { 141 NameNode.LOG.error("BUG: Inconsistent storagespace for directory " 142 + dir.getFullPathName() + ". Cached = " + usage.getStorageSpace() 143 + " != Computed = " + computed); 144 } 145 } 146 147 void addSpaceConsumed(final INodeDirectory dir, final QuotaCounts counts, 148 boolean verify) throws QuotaExceededException { 149 if (dir.isQuotaSet()) { 150 // The following steps are important: 151 // check quotas in this inode and all ancestors before changing counts 152 // so that no change is made if there is any quota violation. 153 // (1) verify quota in this inode 154 if (verify) { 155 verifyQuota(counts); 156 } 157 // (2) verify quota and then add count in ancestors 158 dir.addSpaceConsumed2Parent(counts, verify); 159 // (3) add count in this inode 160 addSpaceConsumed2Cache(counts); 161 } else { 162 dir.addSpaceConsumed2Parent(counts, verify); 163 } 164 } 165 166 /** Update the space/namespace/type usage of the tree 167 * 168 * @param delta the change of the namespace/space/type usage 169 */ 170 public void addSpaceConsumed2Cache(QuotaCounts delta) { 171 usage.add(delta); 172 } 173 174 /** 175 * Sets namespace and storagespace take by the directory rooted 176 * at this INode. This should be used carefully. It does not check 177 * for quota violations. 178 * 179 * @param namespace size of the directory to be set 180 * @param storagespace storage space take by all the nodes under this directory 181 * @param typespaces counters of storage type usage 182 */ 183 void setSpaceConsumed(long namespace, long storagespace, 184 EnumCounters<StorageType> typespaces) { 185 usage.setNameSpace(namespace); 186 usage.setStorageSpace(storagespace); 187 usage.setTypeSpaces(typespaces); 188 } 189 190 void setSpaceConsumed(QuotaCounts c) { 191 usage.setNameSpace(c.getNameSpace()); 192 usage.setStorageSpace(c.getStorageSpace()); 193 usage.setTypeSpaces(c.getTypeSpaces()); 194 } 195 196 /** @return the namespace and storagespace and typespace consumed. */ 197 public QuotaCounts getSpaceConsumed() { 198 return new QuotaCounts.Builder().quotaCount(usage).build(); 199 } 200 201 /** Verify if the namespace quota is violated after applying delta. */ 202 private void verifyNamespaceQuota(long delta) throws NSQuotaExceededException { 203 if (Quota.isViolated(quota.getNameSpace(), usage.getNameSpace(), delta)) { 204 throw new NSQuotaExceededException(quota.getNameSpace(), 205 usage.getNameSpace() + delta); 206 } 207 } 208 /** Verify if the storagespace quota is violated after applying delta. */ 209 private void verifyStoragespaceQuota(long delta) throws DSQuotaExceededException { 210 if (Quota.isViolated(quota.getStorageSpace(), usage.getStorageSpace(), delta)) { 211 throw new DSQuotaExceededException(quota.getStorageSpace(), 212 usage.getStorageSpace() + delta); 213 } 214 } 215 216 private void verifyQuotaByStorageType(EnumCounters<StorageType> typeDelta) 217 throws QuotaByStorageTypeExceededException { 218 if (!isQuotaByStorageTypeSet()) { 219 return; 220 } 221 for (StorageType t: StorageType.getTypesSupportingQuota()) { 222 if (!isQuotaByStorageTypeSet(t)) { 223 continue; 224 } 225 if (Quota.isViolated(quota.getTypeSpace(t), usage.getTypeSpace(t), 226 typeDelta.get(t))) { 227 throw new QuotaByStorageTypeExceededException( 228 quota.getTypeSpace(t), usage.getTypeSpace(t) + typeDelta.get(t), t); 229 } 230 } 231 } 232 233 /** 234 * @throws QuotaExceededException if namespace, storagespace or storage type 235 * space quota is violated after applying the deltas. 236 */ 237 void verifyQuota(QuotaCounts counts) throws QuotaExceededException { 238 verifyNamespaceQuota(counts.getNameSpace()); 239 verifyStoragespaceQuota(counts.getStorageSpace()); 240 verifyQuotaByStorageType(counts.getTypeSpaces()); 241 } 242 243 boolean isQuotaSet() { 244 return quota.anyNsSsCountGreaterOrEqual(0) || 245 quota.anyTypeSpaceCountGreaterOrEqual(0); 246 } 247 248 boolean isQuotaByStorageTypeSet() { 249 return quota.anyTypeSpaceCountGreaterOrEqual(0); 250 } 251 252 boolean isQuotaByStorageTypeSet(StorageType t) { 253 return quota.getTypeSpace(t) >= 0; 254 } 255 256 private String namespaceString() { 257 return "namespace: " + (quota.getNameSpace() < 0? "-": 258 usage.getNameSpace() + "/" + quota.getNameSpace()); 259 } 260 private String storagespaceString() { 261 return "storagespace: " + (quota.getStorageSpace() < 0? "-": 262 usage.getStorageSpace() + "/" + quota.getStorageSpace()); 263 } 264 265 private String typeSpaceString() { 266 StringBuilder sb = new StringBuilder(); 267 for (StorageType t : StorageType.getTypesSupportingQuota()) { 268 sb.append("StorageType: " + t + 269 (quota.getTypeSpace(t) < 0? "-": 270 usage.getTypeSpace(t) + "/" + usage.getTypeSpace(t))); 271 } 272 return sb.toString(); 273 } 274 275 @Override 276 public String toString() { 277 return "Quota[" + namespaceString() + ", " + storagespaceString() + 278 ", " + typeSpaceString() + "]"; 279 } 280}