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 static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_PATH_BASED_CACHE_BLOCK_MAP_ALLOCATION_PERCENT;
021 import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_PATH_BASED_CACHE_BLOCK_MAP_ALLOCATION_PERCENT_DEFAULT;
022 import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_LIST_CACHE_DIRECTIVES_NUM_RESPONSES;
023 import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_LIST_CACHE_DIRECTIVES_NUM_RESPONSES_DEFAULT;
024 import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_LIST_CACHE_POOLS_NUM_RESPONSES;
025 import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_LIST_CACHE_POOLS_NUM_RESPONSES_DEFAULT;
026 import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_PATH_BASED_CACHE_REFRESH_INTERVAL_MS;
027 import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_PATH_BASED_CACHE_REFRESH_INTERVAL_MS_DEFAULT;
028
029 import java.io.DataInput;
030 import java.io.IOException;
031 import java.util.ArrayList;
032 import java.util.Collection;
033 import java.util.Collections;
034 import java.util.Date;
035 import java.util.EnumSet;
036 import java.util.Iterator;
037 import java.util.LinkedList;
038 import java.util.List;
039 import java.util.Map.Entry;
040 import java.util.SortedMap;
041 import java.util.TreeMap;
042 import java.util.concurrent.locks.ReentrantLock;
043
044 import org.apache.commons.io.IOUtils;
045 import org.apache.commons.logging.Log;
046 import org.apache.commons.logging.LogFactory;
047 import org.apache.hadoop.classification.InterfaceAudience;
048 import org.apache.hadoop.conf.Configuration;
049 import org.apache.hadoop.fs.BatchedRemoteIterator.BatchedListEntries;
050 import org.apache.hadoop.fs.CacheFlag;
051 import org.apache.hadoop.fs.InvalidRequestException;
052 import org.apache.hadoop.fs.Path;
053 import org.apache.hadoop.fs.UnresolvedLinkException;
054 import org.apache.hadoop.fs.permission.FsAction;
055 import org.apache.hadoop.fs.permission.FsPermission;
056 import org.apache.hadoop.hdfs.DFSUtil;
057 import org.apache.hadoop.hdfs.protocol.CacheDirective;
058 import org.apache.hadoop.hdfs.protocol.CacheDirectiveEntry;
059 import org.apache.hadoop.hdfs.protocol.CacheDirectiveInfo;
060 import org.apache.hadoop.hdfs.protocol.CacheDirectiveInfo.Expiration;
061 import org.apache.hadoop.hdfs.protocol.CacheDirectiveStats;
062 import org.apache.hadoop.hdfs.protocol.CachePoolEntry;
063 import org.apache.hadoop.hdfs.protocol.CachePoolInfo;
064 import org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos.CacheDirectiveInfoProto;
065 import org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos.CachePoolInfoProto;
066 import org.apache.hadoop.hdfs.protocol.DatanodeID;
067 import org.apache.hadoop.hdfs.protocol.LocatedBlock;
068 import org.apache.hadoop.hdfs.protocolPB.PBHelper;
069 import org.apache.hadoop.hdfs.server.blockmanagement.BlockManager;
070 import org.apache.hadoop.hdfs.server.blockmanagement.CacheReplicationMonitor;
071 import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor;
072 import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor.CachedBlocksList;
073 import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor.CachedBlocksList.Type;
074 import org.apache.hadoop.hdfs.server.namenode.FsImageProto.CacheManagerSection;
075 import org.apache.hadoop.hdfs.server.namenode.metrics.NameNodeMetrics;
076 import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
077 import org.apache.hadoop.hdfs.server.namenode.startupprogress.Phase;
078 import org.apache.hadoop.hdfs.server.namenode.startupprogress.StartupProgress;
079 import org.apache.hadoop.hdfs.server.namenode.startupprogress.StartupProgress.Counter;
080 import org.apache.hadoop.hdfs.server.namenode.startupprogress.Step;
081 import org.apache.hadoop.hdfs.server.namenode.startupprogress.StepType;
082 import org.apache.hadoop.hdfs.util.ReadOnlyList;
083 import org.apache.hadoop.security.AccessControlException;
084 import org.apache.hadoop.util.GSet;
085 import org.apache.hadoop.util.LightWeightGSet;
086 import org.apache.hadoop.util.Time;
087
088 import com.google.common.annotations.VisibleForTesting;
089 import com.google.common.collect.Lists;
090
091 /**
092 * The Cache Manager handles caching on DataNodes.
093 *
094 * This class is instantiated by the FSNamesystem.
095 * It maintains the mapping of cached blocks to datanodes via processing
096 * datanode cache reports. Based on these reports and addition and removal of
097 * caching directives, we will schedule caching and uncaching work.
098 */
099 @InterfaceAudience.LimitedPrivate({"HDFS"})
100 public final class CacheManager {
101 public static final Log LOG = LogFactory.getLog(CacheManager.class);
102
103 private static final float MIN_CACHED_BLOCKS_PERCENT = 0.001f;
104
105 // TODO: add pending / underCached / schedule cached blocks stats.
106
107 /**
108 * The FSNamesystem that contains this CacheManager.
109 */
110 private final FSNamesystem namesystem;
111
112 /**
113 * The BlockManager associated with the FSN that owns this CacheManager.
114 */
115 private final BlockManager blockManager;
116
117 /**
118 * Cache directives, sorted by ID.
119 *
120 * listCacheDirectives relies on the ordering of elements in this map
121 * to track what has already been listed by the client.
122 */
123 private final TreeMap<Long, CacheDirective> directivesById =
124 new TreeMap<Long, CacheDirective>();
125
126 /**
127 * The directive ID to use for a new directive. IDs always increase, and are
128 * never reused.
129 */
130 private long nextDirectiveId;
131
132 /**
133 * Cache directives, sorted by path
134 */
135 private final TreeMap<String, List<CacheDirective>> directivesByPath =
136 new TreeMap<String, List<CacheDirective>>();
137
138 /**
139 * Cache pools, sorted by name.
140 */
141 private final TreeMap<String, CachePool> cachePools =
142 new TreeMap<String, CachePool>();
143
144 /**
145 * Maximum number of cache pools to list in one operation.
146 */
147 private final int maxListCachePoolsResponses;
148
149 /**
150 * Maximum number of cache pool directives to list in one operation.
151 */
152 private final int maxListCacheDirectivesNumResponses;
153
154 /**
155 * Interval between scans in milliseconds.
156 */
157 private final long scanIntervalMs;
158
159 /**
160 * All cached blocks.
161 */
162 private final GSet<CachedBlock, CachedBlock> cachedBlocks;
163
164 /**
165 * Lock which protects the CacheReplicationMonitor.
166 */
167 private final ReentrantLock crmLock = new ReentrantLock();
168
169 private final SerializerCompat serializerCompat = new SerializerCompat();
170
171 /**
172 * The CacheReplicationMonitor.
173 */
174 private CacheReplicationMonitor monitor;
175
176 public static final class PersistState {
177 public final CacheManagerSection section;
178 public final List<CachePoolInfoProto> pools;
179 public final List<CacheDirectiveInfoProto> directives;
180
181 public PersistState(CacheManagerSection section,
182 List<CachePoolInfoProto> pools, List<CacheDirectiveInfoProto> directives) {
183 this.section = section;
184 this.pools = pools;
185 this.directives = directives;
186 }
187 }
188
189 CacheManager(FSNamesystem namesystem, Configuration conf,
190 BlockManager blockManager) {
191 this.namesystem = namesystem;
192 this.blockManager = blockManager;
193 this.nextDirectiveId = 1;
194 this.maxListCachePoolsResponses = conf.getInt(
195 DFS_NAMENODE_LIST_CACHE_POOLS_NUM_RESPONSES,
196 DFS_NAMENODE_LIST_CACHE_POOLS_NUM_RESPONSES_DEFAULT);
197 this.maxListCacheDirectivesNumResponses = conf.getInt(
198 DFS_NAMENODE_LIST_CACHE_DIRECTIVES_NUM_RESPONSES,
199 DFS_NAMENODE_LIST_CACHE_DIRECTIVES_NUM_RESPONSES_DEFAULT);
200 scanIntervalMs = conf.getLong(
201 DFS_NAMENODE_PATH_BASED_CACHE_REFRESH_INTERVAL_MS,
202 DFS_NAMENODE_PATH_BASED_CACHE_REFRESH_INTERVAL_MS_DEFAULT);
203 float cachedBlocksPercent = conf.getFloat(
204 DFS_NAMENODE_PATH_BASED_CACHE_BLOCK_MAP_ALLOCATION_PERCENT,
205 DFS_NAMENODE_PATH_BASED_CACHE_BLOCK_MAP_ALLOCATION_PERCENT_DEFAULT);
206 if (cachedBlocksPercent < MIN_CACHED_BLOCKS_PERCENT) {
207 LOG.info("Using minimum value " + MIN_CACHED_BLOCKS_PERCENT +
208 " for " + DFS_NAMENODE_PATH_BASED_CACHE_BLOCK_MAP_ALLOCATION_PERCENT);
209 cachedBlocksPercent = MIN_CACHED_BLOCKS_PERCENT;
210 }
211 this.cachedBlocks = new LightWeightGSet<CachedBlock, CachedBlock>(
212 LightWeightGSet.computeCapacity(cachedBlocksPercent,
213 "cachedBlocks"));
214
215 }
216
217 /**
218 * Resets all tracked directives and pools. Called during 2NN checkpointing to
219 * reset FSNamesystem state. See {FSNamesystem{@link #clear()}.
220 */
221 void clear() {
222 directivesById.clear();
223 directivesByPath.clear();
224 cachePools.clear();
225 nextDirectiveId = 1;
226 }
227
228 public void startMonitorThread() {
229 crmLock.lock();
230 try {
231 if (this.monitor == null) {
232 this.monitor = new CacheReplicationMonitor(namesystem, this,
233 scanIntervalMs, crmLock);
234 this.monitor.start();
235 }
236 } finally {
237 crmLock.unlock();
238 }
239 }
240
241 public void stopMonitorThread() {
242 crmLock.lock();
243 try {
244 if (this.monitor != null) {
245 CacheReplicationMonitor prevMonitor = this.monitor;
246 this.monitor = null;
247 IOUtils.closeQuietly(prevMonitor);
248 }
249 } finally {
250 crmLock.unlock();
251 }
252 }
253
254 public void clearDirectiveStats() {
255 assert namesystem.hasWriteLock();
256 for (CacheDirective directive : directivesById.values()) {
257 directive.resetStatistics();
258 }
259 }
260
261 /**
262 * @return Unmodifiable view of the collection of CachePools.
263 */
264 public Collection<CachePool> getCachePools() {
265 assert namesystem.hasReadLock();
266 return Collections.unmodifiableCollection(cachePools.values());
267 }
268
269 /**
270 * @return Unmodifiable view of the collection of CacheDirectives.
271 */
272 public Collection<CacheDirective> getCacheDirectives() {
273 assert namesystem.hasReadLock();
274 return Collections.unmodifiableCollection(directivesById.values());
275 }
276
277 @VisibleForTesting
278 public GSet<CachedBlock, CachedBlock> getCachedBlocks() {
279 assert namesystem.hasReadLock();
280 return cachedBlocks;
281 }
282
283 private long getNextDirectiveId() throws IOException {
284 assert namesystem.hasWriteLock();
285 if (nextDirectiveId >= Long.MAX_VALUE - 1) {
286 throw new IOException("No more available IDs.");
287 }
288 return nextDirectiveId++;
289 }
290
291 // Helper getter / validation methods
292
293 private static void checkWritePermission(FSPermissionChecker pc,
294 CachePool pool) throws AccessControlException {
295 if ((pc != null)) {
296 pc.checkPermission(pool, FsAction.WRITE);
297 }
298 }
299
300 private static String validatePoolName(CacheDirectiveInfo directive)
301 throws InvalidRequestException {
302 String pool = directive.getPool();
303 if (pool == null) {
304 throw new InvalidRequestException("No pool specified.");
305 }
306 if (pool.isEmpty()) {
307 throw new InvalidRequestException("Invalid empty pool name.");
308 }
309 return pool;
310 }
311
312 private static String validatePath(CacheDirectiveInfo directive)
313 throws InvalidRequestException {
314 if (directive.getPath() == null) {
315 throw new InvalidRequestException("No path specified.");
316 }
317 String path = directive.getPath().toUri().getPath();
318 if (!DFSUtil.isValidName(path)) {
319 throw new InvalidRequestException("Invalid path '" + path + "'.");
320 }
321 return path;
322 }
323
324 private static short validateReplication(CacheDirectiveInfo directive,
325 short defaultValue) throws InvalidRequestException {
326 short repl = (directive.getReplication() != null)
327 ? directive.getReplication() : defaultValue;
328 if (repl <= 0) {
329 throw new InvalidRequestException("Invalid replication factor " + repl
330 + " <= 0");
331 }
332 return repl;
333 }
334
335 /**
336 * Calculates the absolute expiry time of the directive from the
337 * {@link CacheDirectiveInfo.Expiration}. This converts a relative Expiration
338 * into an absolute time based on the local clock.
339 *
340 * @param info to validate.
341 * @param maxRelativeExpiryTime of the info's pool.
342 * @return the expiration time, or the pool's max absolute expiration if the
343 * info's expiration was not set.
344 * @throws InvalidRequestException if the info's Expiration is invalid.
345 */
346 private static long validateExpiryTime(CacheDirectiveInfo info,
347 long maxRelativeExpiryTime) throws InvalidRequestException {
348 if (LOG.isTraceEnabled()) {
349 LOG.trace("Validating directive " + info
350 + " pool maxRelativeExpiryTime " + maxRelativeExpiryTime);
351 }
352 final long now = new Date().getTime();
353 final long maxAbsoluteExpiryTime = now + maxRelativeExpiryTime;
354 if (info == null || info.getExpiration() == null) {
355 return maxAbsoluteExpiryTime;
356 }
357 Expiration expiry = info.getExpiration();
358 if (expiry.getMillis() < 0l) {
359 throw new InvalidRequestException("Cannot set a negative expiration: "
360 + expiry.getMillis());
361 }
362 long relExpiryTime, absExpiryTime;
363 if (expiry.isRelative()) {
364 relExpiryTime = expiry.getMillis();
365 absExpiryTime = now + relExpiryTime;
366 } else {
367 absExpiryTime = expiry.getMillis();
368 relExpiryTime = absExpiryTime - now;
369 }
370 // Need to cap the expiry so we don't overflow a long when doing math
371 if (relExpiryTime > Expiration.MAX_RELATIVE_EXPIRY_MS) {
372 throw new InvalidRequestException("Expiration "
373 + expiry.toString() + " is too far in the future!");
374 }
375 // Fail if the requested expiry is greater than the max
376 if (relExpiryTime > maxRelativeExpiryTime) {
377 throw new InvalidRequestException("Expiration " + expiry.toString()
378 + " exceeds the max relative expiration time of "
379 + maxRelativeExpiryTime + " ms.");
380 }
381 return absExpiryTime;
382 }
383
384 /**
385 * Throws an exception if the CachePool does not have enough capacity to
386 * cache the given path at the replication factor.
387 *
388 * @param pool CachePool where the path is being cached
389 * @param path Path that is being cached
390 * @param replication Replication factor of the path
391 * @throws InvalidRequestException if the pool does not have enough capacity
392 */
393 private void checkLimit(CachePool pool, String path,
394 short replication) throws InvalidRequestException {
395 CacheDirectiveStats stats = computeNeeded(path, replication);
396 if (pool.getLimit() == CachePoolInfo.LIMIT_UNLIMITED) {
397 return;
398 }
399 if (pool.getBytesNeeded() + (stats.getBytesNeeded() * replication) > pool
400 .getLimit()) {
401 throw new InvalidRequestException("Caching path " + path + " of size "
402 + stats.getBytesNeeded() / replication + " bytes at replication "
403 + replication + " would exceed pool " + pool.getPoolName()
404 + "'s remaining capacity of "
405 + (pool.getLimit() - pool.getBytesNeeded()) + " bytes.");
406 }
407 }
408
409 /**
410 * Computes the needed number of bytes and files for a path.
411 * @return CacheDirectiveStats describing the needed stats for this path
412 */
413 private CacheDirectiveStats computeNeeded(String path, short replication) {
414 FSDirectory fsDir = namesystem.getFSDirectory();
415 INode node;
416 long requestedBytes = 0;
417 long requestedFiles = 0;
418 CacheDirectiveStats.Builder builder = new CacheDirectiveStats.Builder();
419 try {
420 node = fsDir.getINode(path);
421 } catch (UnresolvedLinkException e) {
422 // We don't cache through symlinks
423 return builder.build();
424 }
425 if (node == null) {
426 return builder.build();
427 }
428 if (node.isFile()) {
429 requestedFiles = 1;
430 INodeFile file = node.asFile();
431 requestedBytes = file.computeFileSize();
432 } else if (node.isDirectory()) {
433 INodeDirectory dir = node.asDirectory();
434 ReadOnlyList<INode> children = dir
435 .getChildrenList(Snapshot.CURRENT_STATE_ID);
436 requestedFiles = children.size();
437 for (INode child : children) {
438 if (child.isFile()) {
439 requestedBytes += child.asFile().computeFileSize();
440 }
441 }
442 }
443 return new CacheDirectiveStats.Builder()
444 .setBytesNeeded(requestedBytes)
445 .setFilesCached(requestedFiles)
446 .build();
447 }
448
449 /**
450 * Get a CacheDirective by ID, validating the ID and that the directive
451 * exists.
452 */
453 private CacheDirective getById(long id) throws InvalidRequestException {
454 // Check for invalid IDs.
455 if (id <= 0) {
456 throw new InvalidRequestException("Invalid negative ID.");
457 }
458 // Find the directive.
459 CacheDirective directive = directivesById.get(id);
460 if (directive == null) {
461 throw new InvalidRequestException("No directive with ID " + id
462 + " found.");
463 }
464 return directive;
465 }
466
467 /**
468 * Get a CachePool by name, validating that it exists.
469 */
470 private CachePool getCachePool(String poolName)
471 throws InvalidRequestException {
472 CachePool pool = cachePools.get(poolName);
473 if (pool == null) {
474 throw new InvalidRequestException("Unknown pool " + poolName);
475 }
476 return pool;
477 }
478
479 // RPC handlers
480
481 private void addInternal(CacheDirective directive, CachePool pool) {
482 boolean addedDirective = pool.getDirectiveList().add(directive);
483 assert addedDirective;
484 directivesById.put(directive.getId(), directive);
485 String path = directive.getPath();
486 List<CacheDirective> directives = directivesByPath.get(path);
487 if (directives == null) {
488 directives = new ArrayList<CacheDirective>(1);
489 directivesByPath.put(path, directives);
490 }
491 directives.add(directive);
492 // Fix up pool stats
493 CacheDirectiveStats stats =
494 computeNeeded(directive.getPath(), directive.getReplication());
495 directive.addBytesNeeded(stats.getBytesNeeded());
496 directive.addFilesNeeded(directive.getFilesNeeded());
497
498 setNeedsRescan();
499 }
500
501 /**
502 * Adds a directive, skipping most error checking. This should only be called
503 * internally in special scenarios like edit log replay.
504 */
505 CacheDirectiveInfo addDirectiveFromEditLog(CacheDirectiveInfo directive)
506 throws InvalidRequestException {
507 long id = directive.getId();
508 CacheDirective entry = new CacheDirective(directive);
509 CachePool pool = cachePools.get(directive.getPool());
510 addInternal(entry, pool);
511 if (nextDirectiveId <= id) {
512 nextDirectiveId = id + 1;
513 }
514 return entry.toInfo();
515 }
516
517 public CacheDirectiveInfo addDirective(
518 CacheDirectiveInfo info, FSPermissionChecker pc, EnumSet<CacheFlag> flags)
519 throws IOException {
520 assert namesystem.hasWriteLock();
521 CacheDirective directive;
522 try {
523 CachePool pool = getCachePool(validatePoolName(info));
524 checkWritePermission(pc, pool);
525 String path = validatePath(info);
526 short replication = validateReplication(info, (short)1);
527 long expiryTime = validateExpiryTime(info, pool.getMaxRelativeExpiryMs());
528 // Do quota validation if required
529 if (!flags.contains(CacheFlag.FORCE)) {
530 checkLimit(pool, path, replication);
531 }
532 // All validation passed
533 // Add a new entry with the next available ID.
534 long id = getNextDirectiveId();
535 directive = new CacheDirective(id, path, replication, expiryTime);
536 addInternal(directive, pool);
537 } catch (IOException e) {
538 LOG.warn("addDirective of " + info + " failed: ", e);
539 throw e;
540 }
541 LOG.info("addDirective of " + info + " successful.");
542 return directive.toInfo();
543 }
544
545 /**
546 * Factory method that makes a new CacheDirectiveInfo by applying fields in a
547 * CacheDirectiveInfo to an existing CacheDirective.
548 *
549 * @param info with some or all fields set.
550 * @param defaults directive providing default values for unset fields in
551 * info.
552 *
553 * @return new CacheDirectiveInfo of the info applied to the defaults.
554 */
555 private static CacheDirectiveInfo createFromInfoAndDefaults(
556 CacheDirectiveInfo info, CacheDirective defaults) {
557 // Initialize the builder with the default values
558 CacheDirectiveInfo.Builder builder =
559 new CacheDirectiveInfo.Builder(defaults.toInfo());
560 // Replace default with new value if present
561 if (info.getPath() != null) {
562 builder.setPath(info.getPath());
563 }
564 if (info.getReplication() != null) {
565 builder.setReplication(info.getReplication());
566 }
567 if (info.getPool() != null) {
568 builder.setPool(info.getPool());
569 }
570 if (info.getExpiration() != null) {
571 builder.setExpiration(info.getExpiration());
572 }
573 return builder.build();
574 }
575
576 /**
577 * Modifies a directive, skipping most error checking. This is for careful
578 * internal use only. modifyDirective can be non-deterministic since its error
579 * checking depends on current system time, which poses a problem for edit log
580 * replay.
581 */
582 void modifyDirectiveFromEditLog(CacheDirectiveInfo info)
583 throws InvalidRequestException {
584 // Check for invalid IDs.
585 Long id = info.getId();
586 if (id == null) {
587 throw new InvalidRequestException("Must supply an ID.");
588 }
589 CacheDirective prevEntry = getById(id);
590 CacheDirectiveInfo newInfo = createFromInfoAndDefaults(info, prevEntry);
591 removeInternal(prevEntry);
592 addInternal(new CacheDirective(newInfo), getCachePool(newInfo.getPool()));
593 }
594
595 public void modifyDirective(CacheDirectiveInfo info,
596 FSPermissionChecker pc, EnumSet<CacheFlag> flags) throws IOException {
597 assert namesystem.hasWriteLock();
598 String idString =
599 (info.getId() == null) ?
600 "(null)" : info.getId().toString();
601 try {
602 // Check for invalid IDs.
603 Long id = info.getId();
604 if (id == null) {
605 throw new InvalidRequestException("Must supply an ID.");
606 }
607 CacheDirective prevEntry = getById(id);
608 checkWritePermission(pc, prevEntry.getPool());
609
610 // Fill in defaults
611 CacheDirectiveInfo infoWithDefaults =
612 createFromInfoAndDefaults(info, prevEntry);
613 CacheDirectiveInfo.Builder builder =
614 new CacheDirectiveInfo.Builder(infoWithDefaults);
615
616 // Do validation
617 validatePath(infoWithDefaults);
618 validateReplication(infoWithDefaults, (short)-1);
619 // Need to test the pool being set here to avoid rejecting a modify for a
620 // directive that's already been forced into a pool
621 CachePool srcPool = prevEntry.getPool();
622 CachePool destPool = getCachePool(validatePoolName(infoWithDefaults));
623 if (!srcPool.getPoolName().equals(destPool.getPoolName())) {
624 checkWritePermission(pc, destPool);
625 if (!flags.contains(CacheFlag.FORCE)) {
626 checkLimit(destPool, infoWithDefaults.getPath().toUri().getPath(),
627 infoWithDefaults.getReplication());
628 }
629 }
630 // Verify the expiration against the destination pool
631 validateExpiryTime(infoWithDefaults, destPool.getMaxRelativeExpiryMs());
632
633 // Indicate changes to the CRM
634 setNeedsRescan();
635
636 // Validation passed
637 removeInternal(prevEntry);
638 addInternal(new CacheDirective(builder.build()), destPool);
639 } catch (IOException e) {
640 LOG.warn("modifyDirective of " + idString + " failed: ", e);
641 throw e;
642 }
643 LOG.info("modifyDirective of " + idString + " successfully applied " +
644 info+ ".");
645 }
646
647 private void removeInternal(CacheDirective directive)
648 throws InvalidRequestException {
649 assert namesystem.hasWriteLock();
650 // Remove the corresponding entry in directivesByPath.
651 String path = directive.getPath();
652 List<CacheDirective> directives = directivesByPath.get(path);
653 if (directives == null || !directives.remove(directive)) {
654 throw new InvalidRequestException("Failed to locate entry " +
655 directive.getId() + " by path " + directive.getPath());
656 }
657 if (directives.size() == 0) {
658 directivesByPath.remove(path);
659 }
660 // Fix up the stats from removing the pool
661 final CachePool pool = directive.getPool();
662 directive.addBytesNeeded(-directive.getBytesNeeded());
663 directive.addFilesNeeded(-directive.getFilesNeeded());
664
665 directivesById.remove(directive.getId());
666 pool.getDirectiveList().remove(directive);
667 assert directive.getPool() == null;
668
669 setNeedsRescan();
670 }
671
672 public void removeDirective(long id, FSPermissionChecker pc)
673 throws IOException {
674 assert namesystem.hasWriteLock();
675 try {
676 CacheDirective directive = getById(id);
677 checkWritePermission(pc, directive.getPool());
678 removeInternal(directive);
679 } catch (IOException e) {
680 LOG.warn("removeDirective of " + id + " failed: ", e);
681 throw e;
682 }
683 LOG.info("removeDirective of " + id + " successful.");
684 }
685
686 public BatchedListEntries<CacheDirectiveEntry>
687 listCacheDirectives(long prevId,
688 CacheDirectiveInfo filter,
689 FSPermissionChecker pc) throws IOException {
690 assert namesystem.hasReadLock();
691 final int NUM_PRE_ALLOCATED_ENTRIES = 16;
692 String filterPath = null;
693 if (filter.getId() != null) {
694 throw new IOException("Filtering by ID is unsupported.");
695 }
696 if (filter.getPath() != null) {
697 filterPath = validatePath(filter);
698 }
699 if (filter.getReplication() != null) {
700 throw new IOException("Filtering by replication is unsupported.");
701 }
702 ArrayList<CacheDirectiveEntry> replies =
703 new ArrayList<CacheDirectiveEntry>(NUM_PRE_ALLOCATED_ENTRIES);
704 int numReplies = 0;
705 SortedMap<Long, CacheDirective> tailMap =
706 directivesById.tailMap(prevId + 1);
707 for (Entry<Long, CacheDirective> cur : tailMap.entrySet()) {
708 if (numReplies >= maxListCacheDirectivesNumResponses) {
709 return new BatchedListEntries<CacheDirectiveEntry>(replies, true);
710 }
711 CacheDirective curDirective = cur.getValue();
712 CacheDirectiveInfo info = cur.getValue().toInfo();
713 if (filter.getPool() != null &&
714 !info.getPool().equals(filter.getPool())) {
715 continue;
716 }
717 if (filterPath != null &&
718 !info.getPath().toUri().getPath().equals(filterPath)) {
719 continue;
720 }
721 boolean hasPermission = true;
722 if (pc != null) {
723 try {
724 pc.checkPermission(curDirective.getPool(), FsAction.READ);
725 } catch (AccessControlException e) {
726 hasPermission = false;
727 }
728 }
729 if (hasPermission) {
730 replies.add(new CacheDirectiveEntry(info, cur.getValue().toStats()));
731 numReplies++;
732 }
733 }
734 return new BatchedListEntries<CacheDirectiveEntry>(replies, false);
735 }
736
737 /**
738 * Create a cache pool.
739 *
740 * Only the superuser should be able to call this function.
741 *
742 * @param info The info for the cache pool to create.
743 * @return Information about the cache pool we created.
744 */
745 public CachePoolInfo addCachePool(CachePoolInfo info)
746 throws IOException {
747 assert namesystem.hasWriteLock();
748 CachePool pool;
749 try {
750 CachePoolInfo.validate(info);
751 String poolName = info.getPoolName();
752 pool = cachePools.get(poolName);
753 if (pool != null) {
754 throw new InvalidRequestException("Cache pool " + poolName
755 + " already exists.");
756 }
757 pool = CachePool.createFromInfoAndDefaults(info);
758 cachePools.put(pool.getPoolName(), pool);
759 } catch (IOException e) {
760 LOG.info("addCachePool of " + info + " failed: ", e);
761 throw e;
762 }
763 LOG.info("addCachePool of " + info + " successful.");
764 return pool.getInfo(true);
765 }
766
767 /**
768 * Modify a cache pool.
769 *
770 * Only the superuser should be able to call this function.
771 *
772 * @param info
773 * The info for the cache pool to modify.
774 */
775 public void modifyCachePool(CachePoolInfo info)
776 throws IOException {
777 assert namesystem.hasWriteLock();
778 StringBuilder bld = new StringBuilder();
779 try {
780 CachePoolInfo.validate(info);
781 String poolName = info.getPoolName();
782 CachePool pool = cachePools.get(poolName);
783 if (pool == null) {
784 throw new InvalidRequestException("Cache pool " + poolName
785 + " does not exist.");
786 }
787 String prefix = "";
788 if (info.getOwnerName() != null) {
789 pool.setOwnerName(info.getOwnerName());
790 bld.append(prefix).
791 append("set owner to ").append(info.getOwnerName());
792 prefix = "; ";
793 }
794 if (info.getGroupName() != null) {
795 pool.setGroupName(info.getGroupName());
796 bld.append(prefix).
797 append("set group to ").append(info.getGroupName());
798 prefix = "; ";
799 }
800 if (info.getMode() != null) {
801 pool.setMode(info.getMode());
802 bld.append(prefix).append("set mode to " + info.getMode());
803 prefix = "; ";
804 }
805 if (info.getLimit() != null) {
806 pool.setLimit(info.getLimit());
807 bld.append(prefix).append("set limit to " + info.getLimit());
808 prefix = "; ";
809 // New limit changes stats, need to set needs refresh
810 setNeedsRescan();
811 }
812 if (info.getMaxRelativeExpiryMs() != null) {
813 final Long maxRelativeExpiry = info.getMaxRelativeExpiryMs();
814 pool.setMaxRelativeExpiryMs(maxRelativeExpiry);
815 bld.append(prefix).append("set maxRelativeExpiry to "
816 + maxRelativeExpiry);
817 prefix = "; ";
818 }
819 if (prefix.isEmpty()) {
820 bld.append("no changes.");
821 }
822 } catch (IOException e) {
823 LOG.info("modifyCachePool of " + info + " failed: ", e);
824 throw e;
825 }
826 LOG.info("modifyCachePool of " + info.getPoolName() + " successful; "
827 + bld.toString());
828 }
829
830 /**
831 * Remove a cache pool.
832 *
833 * Only the superuser should be able to call this function.
834 *
835 * @param poolName
836 * The name for the cache pool to remove.
837 */
838 public void removeCachePool(String poolName)
839 throws IOException {
840 assert namesystem.hasWriteLock();
841 try {
842 CachePoolInfo.validateName(poolName);
843 CachePool pool = cachePools.remove(poolName);
844 if (pool == null) {
845 throw new InvalidRequestException(
846 "Cannot remove non-existent cache pool " + poolName);
847 }
848 // Remove all directives in this pool.
849 Iterator<CacheDirective> iter = pool.getDirectiveList().iterator();
850 while (iter.hasNext()) {
851 CacheDirective directive = iter.next();
852 directivesByPath.remove(directive.getPath());
853 directivesById.remove(directive.getId());
854 iter.remove();
855 }
856 setNeedsRescan();
857 } catch (IOException e) {
858 LOG.info("removeCachePool of " + poolName + " failed: ", e);
859 throw e;
860 }
861 LOG.info("removeCachePool of " + poolName + " successful.");
862 }
863
864 public BatchedListEntries<CachePoolEntry>
865 listCachePools(FSPermissionChecker pc, String prevKey) {
866 assert namesystem.hasReadLock();
867 final int NUM_PRE_ALLOCATED_ENTRIES = 16;
868 ArrayList<CachePoolEntry> results =
869 new ArrayList<CachePoolEntry>(NUM_PRE_ALLOCATED_ENTRIES);
870 SortedMap<String, CachePool> tailMap = cachePools.tailMap(prevKey, false);
871 int numListed = 0;
872 for (Entry<String, CachePool> cur : tailMap.entrySet()) {
873 if (numListed++ >= maxListCachePoolsResponses) {
874 return new BatchedListEntries<CachePoolEntry>(results, true);
875 }
876 results.add(cur.getValue().getEntry(pc));
877 }
878 return new BatchedListEntries<CachePoolEntry>(results, false);
879 }
880
881 public void setCachedLocations(LocatedBlock block) {
882 CachedBlock cachedBlock =
883 new CachedBlock(block.getBlock().getBlockId(),
884 (short)0, false);
885 cachedBlock = cachedBlocks.get(cachedBlock);
886 if (cachedBlock == null) {
887 return;
888 }
889 List<DatanodeDescriptor> datanodes = cachedBlock.getDatanodes(Type.CACHED);
890 for (DatanodeDescriptor datanode : datanodes) {
891 block.addCachedLoc(datanode);
892 }
893 }
894
895 public final void processCacheReport(final DatanodeID datanodeID,
896 final List<Long> blockIds) throws IOException {
897 namesystem.writeLock();
898 final long startTime = Time.monotonicNow();
899 final long endTime;
900 try {
901 final DatanodeDescriptor datanode =
902 blockManager.getDatanodeManager().getDatanode(datanodeID);
903 if (datanode == null || !datanode.isAlive) {
904 throw new IOException(
905 "processCacheReport from dead or unregistered datanode: " +
906 datanode);
907 }
908 processCacheReportImpl(datanode, blockIds);
909 } finally {
910 endTime = Time.monotonicNow();
911 namesystem.writeUnlock();
912 }
913
914 // Log the block report processing stats from Namenode perspective
915 final NameNodeMetrics metrics = NameNode.getNameNodeMetrics();
916 if (metrics != null) {
917 metrics.addCacheBlockReport((int) (endTime - startTime));
918 }
919 if (LOG.isDebugEnabled()) {
920 LOG.debug("Processed cache report from "
921 + datanodeID + ", blocks: " + blockIds.size()
922 + ", processing time: " + (endTime - startTime) + " msecs");
923 }
924 }
925
926 private void processCacheReportImpl(final DatanodeDescriptor datanode,
927 final List<Long> blockIds) {
928 CachedBlocksList cached = datanode.getCached();
929 cached.clear();
930 CachedBlocksList cachedList = datanode.getCached();
931 CachedBlocksList pendingCachedList = datanode.getPendingCached();
932 for (Iterator<Long> iter = blockIds.iterator(); iter.hasNext(); ) {
933 long blockId = iter.next();
934 CachedBlock cachedBlock =
935 new CachedBlock(blockId, (short)0, false);
936 CachedBlock prevCachedBlock = cachedBlocks.get(cachedBlock);
937 // Add the block ID from the cache report to the cachedBlocks map
938 // if it's not already there.
939 if (prevCachedBlock != null) {
940 cachedBlock = prevCachedBlock;
941 } else {
942 cachedBlocks.put(cachedBlock);
943 }
944 // Add the block to the datanode's implicit cached block list
945 // if it's not already there. Similarly, remove it from the pending
946 // cached block list if it exists there.
947 if (!cachedBlock.isPresent(cachedList)) {
948 cachedList.add(cachedBlock);
949 }
950 if (cachedBlock.isPresent(pendingCachedList)) {
951 pendingCachedList.remove(cachedBlock);
952 }
953 }
954 }
955
956 public PersistState saveState() throws IOException {
957 ArrayList<CachePoolInfoProto> pools = Lists
958 .newArrayListWithCapacity(cachePools.size());
959 ArrayList<CacheDirectiveInfoProto> directives = Lists
960 .newArrayListWithCapacity(directivesById.size());
961
962 for (CachePool pool : cachePools.values()) {
963 CachePoolInfo p = pool.getInfo(true);
964 CachePoolInfoProto.Builder b = CachePoolInfoProto.newBuilder()
965 .setPoolName(p.getPoolName());
966
967 if (p.getOwnerName() != null)
968 b.setOwnerName(p.getOwnerName());
969
970 if (p.getGroupName() != null)
971 b.setGroupName(p.getGroupName());
972
973 if (p.getMode() != null)
974 b.setMode(p.getMode().toShort());
975
976 if (p.getLimit() != null)
977 b.setLimit(p.getLimit());
978
979 pools.add(b.build());
980 }
981
982 for (CacheDirective directive : directivesById.values()) {
983 CacheDirectiveInfo info = directive.toInfo();
984 CacheDirectiveInfoProto.Builder b = CacheDirectiveInfoProto.newBuilder()
985 .setId(info.getId());
986
987 if (info.getPath() != null) {
988 b.setPath(info.getPath().toUri().getPath());
989 }
990
991 if (info.getReplication() != null) {
992 b.setReplication(info.getReplication());
993 }
994
995 if (info.getPool() != null) {
996 b.setPool(info.getPool());
997 }
998
999 Expiration expiry = info.getExpiration();
1000 if (expiry != null) {
1001 assert (!expiry.isRelative());
1002 b.setExpiration(PBHelper.convert(expiry));
1003 }
1004
1005 directives.add(b.build());
1006 }
1007 CacheManagerSection s = CacheManagerSection.newBuilder()
1008 .setNextDirectiveId(nextDirectiveId).setNumPools(pools.size())
1009 .setNumDirectives(directives.size()).build();
1010
1011 return new PersistState(s, pools, directives);
1012 }
1013
1014 /**
1015 * Reloads CacheManager state from the passed DataInput. Used during namenode
1016 * startup to restore CacheManager state from an FSImage.
1017 * @param in DataInput from which to restore state
1018 * @throws IOException
1019 */
1020 public void loadStateCompat(DataInput in) throws IOException {
1021 serializerCompat.load(in);
1022 }
1023
1024 public void loadState(PersistState s) throws IOException {
1025 nextDirectiveId = s.section.getNextDirectiveId();
1026 for (CachePoolInfoProto p : s.pools) {
1027 CachePoolInfo info = new CachePoolInfo(p.getPoolName());
1028 if (p.hasOwnerName())
1029 info.setOwnerName(p.getOwnerName());
1030
1031 if (p.hasGroupName())
1032 info.setGroupName(p.getGroupName());
1033
1034 if (p.hasMode())
1035 info.setMode(new FsPermission((short) p.getMode()));
1036
1037 if (p.hasLimit())
1038 info.setLimit(p.getLimit());
1039
1040 addCachePool(info);
1041 }
1042
1043 for (CacheDirectiveInfoProto p : s.directives) {
1044 // Get pool reference by looking it up in the map
1045 final String poolName = p.getPool();
1046 CacheDirective directive = new CacheDirective(p.getId(), new Path(
1047 p.getPath()).toUri().getPath(), (short) p.getReplication(), p
1048 .getExpiration().getMillis());
1049 addCacheDirective(poolName, directive);
1050 }
1051 }
1052
1053 private void addCacheDirective(final String poolName,
1054 final CacheDirective directive) throws IOException {
1055 CachePool pool = cachePools.get(poolName);
1056 if (pool == null) {
1057 throw new IOException("Directive refers to pool " + poolName
1058 + ", which does not exist.");
1059 }
1060 boolean addedDirective = pool.getDirectiveList().add(directive);
1061 assert addedDirective;
1062 if (directivesById.put(directive.getId(), directive) != null) {
1063 throw new IOException("A directive with ID " + directive.getId()
1064 + " already exists");
1065 }
1066 List<CacheDirective> directives = directivesByPath.get(directive.getPath());
1067 if (directives == null) {
1068 directives = new LinkedList<CacheDirective>();
1069 directivesByPath.put(directive.getPath(), directives);
1070 }
1071 directives.add(directive);
1072 }
1073
1074 private final class SerializerCompat {
1075 private void load(DataInput in) throws IOException {
1076 nextDirectiveId = in.readLong();
1077 // pools need to be loaded first since directives point to their parent pool
1078 loadPools(in);
1079 loadDirectives(in);
1080 }
1081
1082 /**
1083 * Load cache pools from fsimage
1084 */
1085 private void loadPools(DataInput in)
1086 throws IOException {
1087 StartupProgress prog = NameNode.getStartupProgress();
1088 Step step = new Step(StepType.CACHE_POOLS);
1089 prog.beginStep(Phase.LOADING_FSIMAGE, step);
1090 int numberOfPools = in.readInt();
1091 prog.setTotal(Phase.LOADING_FSIMAGE, step, numberOfPools);
1092 Counter counter = prog.getCounter(Phase.LOADING_FSIMAGE, step);
1093 for (int i = 0; i < numberOfPools; i++) {
1094 addCachePool(FSImageSerialization.readCachePoolInfo(in));
1095 counter.increment();
1096 }
1097 prog.endStep(Phase.LOADING_FSIMAGE, step);
1098 }
1099
1100 /**
1101 * Load cache directives from the fsimage
1102 */
1103 private void loadDirectives(DataInput in) throws IOException {
1104 StartupProgress prog = NameNode.getStartupProgress();
1105 Step step = new Step(StepType.CACHE_ENTRIES);
1106 prog.beginStep(Phase.LOADING_FSIMAGE, step);
1107 int numDirectives = in.readInt();
1108 prog.setTotal(Phase.LOADING_FSIMAGE, step, numDirectives);
1109 Counter counter = prog.getCounter(Phase.LOADING_FSIMAGE, step);
1110 for (int i = 0; i < numDirectives; i++) {
1111 CacheDirectiveInfo info = FSImageSerialization.readCacheDirectiveInfo(in);
1112 // Get pool reference by looking it up in the map
1113 final String poolName = info.getPool();
1114 CacheDirective directive =
1115 new CacheDirective(info.getId(), info.getPath().toUri().getPath(),
1116 info.getReplication(), info.getExpiration().getAbsoluteMillis());
1117 addCacheDirective(poolName, directive);
1118 counter.increment();
1119 }
1120 prog.endStep(Phase.LOADING_FSIMAGE, step);
1121 }
1122 }
1123
1124 public void waitForRescanIfNeeded() {
1125 crmLock.lock();
1126 try {
1127 if (monitor != null) {
1128 monitor.waitForRescanIfNeeded();
1129 }
1130 } finally {
1131 crmLock.unlock();
1132 }
1133 }
1134
1135 private void setNeedsRescan() {
1136 crmLock.lock();
1137 try {
1138 if (monitor != null) {
1139 monitor.setNeedsRescan();
1140 }
1141 } finally {
1142 crmLock.unlock();
1143 }
1144 }
1145
1146 @VisibleForTesting
1147 public Thread getCacheReplicationMonitor() {
1148 crmLock.lock();
1149 try {
1150 return monitor;
1151 } finally {
1152 crmLock.unlock();
1153 }
1154 }
1155 }