1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 package org.apache.hadoop.hdfs.tools;
19 
20 import java.io.IOException;
21 import java.util.EnumSet;
22 import java.util.LinkedList;
23 import java.util.List;
24 
25 import org.apache.commons.lang.WordUtils;
26 import org.apache.hadoop.classification.InterfaceAudience;
27 import org.apache.hadoop.conf.Configuration;
28 import org.apache.hadoop.conf.Configured;
29 import org.apache.hadoop.fs.CacheFlag;
30 import org.apache.hadoop.fs.Path;
31 import org.apache.hadoop.fs.RemoteIterator;
32 import org.apache.hadoop.fs.permission.FsPermission;
33 import org.apache.hadoop.hdfs.DFSUtil;
34 import org.apache.hadoop.hdfs.DistributedFileSystem;
35 import org.apache.hadoop.hdfs.protocol.CacheDirectiveEntry;
36 import org.apache.hadoop.hdfs.protocol.CacheDirectiveInfo;
37 import org.apache.hadoop.hdfs.protocol.CacheDirectiveInfo.Expiration;
38 import org.apache.hadoop.hdfs.protocol.CacheDirectiveStats;
39 import org.apache.hadoop.hdfs.protocol.CachePoolEntry;
40 import org.apache.hadoop.hdfs.protocol.CachePoolInfo;
41 import org.apache.hadoop.hdfs.protocol.CachePoolStats;
42 import org.apache.hadoop.tools.TableListing;
43 import org.apache.hadoop.tools.TableListing.Justification;
44 import org.apache.hadoop.util.StringUtils;
45 import org.apache.hadoop.util.Tool;
46 
47 import com.google.common.base.Joiner;
48 
49 /**
50  * This class implements command-line operations on the HDFS Cache.
51  */
52 @InterfaceAudience.Private
53 public class CacheAdmin extends Configured implements Tool {
54 
CacheAdmin()55   public CacheAdmin() {
56     this(null);
57   }
58 
CacheAdmin(Configuration conf)59   public CacheAdmin(Configuration conf) {
60     super(conf);
61   }
62 
63   @Override
run(String[] args)64   public int run(String[] args) throws IOException {
65     if (args.length == 0) {
66       AdminHelper.printUsage(false, "cacheadmin", COMMANDS);
67       return 1;
68     }
69     AdminHelper.Command command = AdminHelper.determineCommand(args[0],
70         COMMANDS);
71     if (command == null) {
72       System.err.println("Can't understand command '" + args[0] + "'");
73       if (!args[0].startsWith("-")) {
74         System.err.println("Command names must start with dashes.");
75       }
76       AdminHelper.printUsage(false, "cacheadmin", COMMANDS);
77       return 1;
78     }
79     List<String> argsList = new LinkedList<String>();
80     for (int j = 1; j < args.length; j++) {
81       argsList.add(args[j]);
82     }
83     try {
84       return command.run(getConf(), argsList);
85     } catch (IllegalArgumentException e) {
86       System.err.println(AdminHelper.prettifyException(e));
87       return -1;
88     }
89   }
90 
main(String[] argsArray)91   public static void main(String[] argsArray) throws IOException {
92     CacheAdmin cacheAdmin = new CacheAdmin(new Configuration());
93     System.exit(cacheAdmin.run(argsArray));
94   }
95 
parseExpirationString(String ttlString)96   private static CacheDirectiveInfo.Expiration parseExpirationString(String ttlString)
97       throws IOException {
98     CacheDirectiveInfo.Expiration ex = null;
99     if (ttlString != null) {
100       if (ttlString.equalsIgnoreCase("never")) {
101         ex = CacheDirectiveInfo.Expiration.NEVER;
102       } else {
103         long ttl = DFSUtil.parseRelativeTime(ttlString);
104         ex = CacheDirectiveInfo.Expiration.newRelative(ttl);
105       }
106     }
107     return ex;
108   }
109 
110   private static class AddCacheDirectiveInfoCommand
111       implements AdminHelper.Command {
112     @Override
getName()113     public String getName() {
114       return "-addDirective";
115     }
116 
117     @Override
getShortUsage()118     public String getShortUsage() {
119       return "[" + getName() +
120           " -path <path> -pool <pool-name> " +
121           "[-force] " +
122           "[-replication <replication>] [-ttl <time-to-live>]]\n";
123     }
124 
125     @Override
getLongUsage()126     public String getLongUsage() {
127       TableListing listing = AdminHelper.getOptionDescriptionListing();
128       listing.addRow("<path>", "A path to cache. The path can be " +
129           "a directory or a file.");
130       listing.addRow("<pool-name>", "The pool to which the directive will be " +
131           "added. You must have write permission on the cache pool "
132           + "in order to add new directives.");
133       listing.addRow("-force",
134           "Skips checking of cache pool resource limits.");
135       listing.addRow("<replication>", "The cache replication factor to use. " +
136           "Defaults to 1.");
137       listing.addRow("<time-to-live>", "How long the directive is " +
138           "valid. Can be specified in minutes, hours, and days, e.g. " +
139           "30m, 4h, 2d. Valid units are [smhd]." +
140           " \"never\" indicates a directive that never expires." +
141           " If unspecified, the directive never expires.");
142       return getShortUsage() + "\n" +
143         "Add a new cache directive.\n\n" +
144         listing.toString();
145     }
146 
147     @Override
run(Configuration conf, List<String> args)148     public int run(Configuration conf, List<String> args) throws IOException {
149       CacheDirectiveInfo.Builder builder = new CacheDirectiveInfo.Builder();
150 
151       String path = StringUtils.popOptionWithArgument("-path", args);
152       if (path == null) {
153         System.err.println("You must specify a path with -path.");
154         return 1;
155       }
156       builder.setPath(new Path(path));
157 
158       String poolName = StringUtils.popOptionWithArgument("-pool", args);
159       if (poolName == null) {
160         System.err.println("You must specify a pool name with -pool.");
161         return 1;
162       }
163       builder.setPool(poolName);
164       boolean force = StringUtils.popOption("-force", args);
165       String replicationString =
166           StringUtils.popOptionWithArgument("-replication", args);
167       if (replicationString != null) {
168         Short replication = Short.parseShort(replicationString);
169         builder.setReplication(replication);
170       }
171 
172       String ttlString = StringUtils.popOptionWithArgument("-ttl", args);
173       try {
174         Expiration ex = parseExpirationString(ttlString);
175         if (ex != null) {
176           builder.setExpiration(ex);
177         }
178       } catch (IOException e) {
179         System.err.println(
180             "Error while parsing ttl value: " + e.getMessage());
181         return 1;
182       }
183 
184       if (!args.isEmpty()) {
185         System.err.println("Can't understand argument: " + args.get(0));
186         return 1;
187       }
188 
189       DistributedFileSystem dfs = AdminHelper.getDFS(conf);
190       CacheDirectiveInfo directive = builder.build();
191       EnumSet<CacheFlag> flags = EnumSet.noneOf(CacheFlag.class);
192       if (force) {
193         flags.add(CacheFlag.FORCE);
194       }
195       try {
196         long id = dfs.addCacheDirective(directive, flags);
197         System.out.println("Added cache directive " + id);
198       } catch (IOException e) {
199         System.err.println(AdminHelper.prettifyException(e));
200         return 2;
201       }
202 
203       return 0;
204     }
205   }
206 
207   private static class RemoveCacheDirectiveInfoCommand
208       implements AdminHelper.Command {
209     @Override
getName()210     public String getName() {
211       return "-removeDirective";
212     }
213 
214     @Override
getShortUsage()215     public String getShortUsage() {
216       return "[" + getName() + " <id>]\n";
217     }
218 
219     @Override
getLongUsage()220     public String getLongUsage() {
221       TableListing listing = AdminHelper.getOptionDescriptionListing();
222       listing.addRow("<id>", "The id of the cache directive to remove.  " +
223         "You must have write permission on the pool of the " +
224         "directive in order to remove it.  To see a list " +
225         "of cache directive IDs, use the -listDirectives command.");
226       return getShortUsage() + "\n" +
227         "Remove a cache directive.\n\n" +
228         listing.toString();
229     }
230 
231     @Override
run(Configuration conf, List<String> args)232     public int run(Configuration conf, List<String> args) throws IOException {
233       String idString= StringUtils.popFirstNonOption(args);
234       if (idString == null) {
235         System.err.println("You must specify a directive ID to remove.");
236         return 1;
237       }
238       long id;
239       try {
240         id = Long.parseLong(idString);
241       } catch (NumberFormatException e) {
242         System.err.println("Invalid directive ID " + idString + ": expected " +
243             "a numeric value.");
244         return 1;
245       }
246       if (id <= 0) {
247         System.err.println("Invalid directive ID " + id + ": ids must " +
248             "be greater than 0.");
249         return 1;
250       }
251       if (!args.isEmpty()) {
252         System.err.println("Can't understand argument: " + args.get(0));
253         System.err.println("Usage is " + getShortUsage());
254         return 1;
255       }
256       DistributedFileSystem dfs = AdminHelper.getDFS(conf);
257       try {
258         dfs.getClient().removeCacheDirective(id);
259         System.out.println("Removed cached directive " + id);
260       } catch (IOException e) {
261         System.err.println(AdminHelper.prettifyException(e));
262         return 2;
263       }
264       return 0;
265     }
266   }
267 
268   private static class ModifyCacheDirectiveInfoCommand
269       implements AdminHelper.Command {
270     @Override
getName()271     public String getName() {
272       return "-modifyDirective";
273     }
274 
275     @Override
getShortUsage()276     public String getShortUsage() {
277       return "[" + getName() +
278           " -id <id> [-path <path>] [-force] [-replication <replication>] " +
279           "[-pool <pool-name>] [-ttl <time-to-live>]]\n";
280     }
281 
282     @Override
getLongUsage()283     public String getLongUsage() {
284       TableListing listing = AdminHelper.getOptionDescriptionListing();
285       listing.addRow("<id>", "The ID of the directive to modify (required)");
286       listing.addRow("<path>", "A path to cache. The path can be " +
287           "a directory or a file. (optional)");
288       listing.addRow("-force",
289           "Skips checking of cache pool resource limits.");
290       listing.addRow("<replication>", "The cache replication factor to use. " +
291           "(optional)");
292       listing.addRow("<pool-name>", "The pool to which the directive will be " +
293           "added. You must have write permission on the cache pool "
294           + "in order to move a directive into it. (optional)");
295       listing.addRow("<time-to-live>", "How long the directive is " +
296           "valid. Can be specified in minutes, hours, and days, e.g. " +
297           "30m, 4h, 2d. Valid units are [smhd]." +
298           " \"never\" indicates a directive that never expires.");
299       return getShortUsage() + "\n" +
300         "Modify a cache directive.\n\n" +
301         listing.toString();
302     }
303 
304     @Override
run(Configuration conf, List<String> args)305     public int run(Configuration conf, List<String> args) throws IOException {
306       CacheDirectiveInfo.Builder builder =
307         new CacheDirectiveInfo.Builder();
308       boolean modified = false;
309       String idString = StringUtils.popOptionWithArgument("-id", args);
310       if (idString == null) {
311         System.err.println("You must specify a directive ID with -id.");
312         return 1;
313       }
314       builder.setId(Long.parseLong(idString));
315       String path = StringUtils.popOptionWithArgument("-path", args);
316       if (path != null) {
317         builder.setPath(new Path(path));
318         modified = true;
319       }
320       boolean force = StringUtils.popOption("-force", args);
321       String replicationString =
322         StringUtils.popOptionWithArgument("-replication", args);
323       if (replicationString != null) {
324         builder.setReplication(Short.parseShort(replicationString));
325         modified = true;
326       }
327       String poolName =
328         StringUtils.popOptionWithArgument("-pool", args);
329       if (poolName != null) {
330         builder.setPool(poolName);
331         modified = true;
332       }
333       String ttlString = StringUtils.popOptionWithArgument("-ttl", args);
334       try {
335         Expiration ex = parseExpirationString(ttlString);
336         if (ex != null) {
337           builder.setExpiration(ex);
338           modified = true;
339         }
340       } catch (IOException e) {
341         System.err.println(
342             "Error while parsing ttl value: " + e.getMessage());
343         return 1;
344       }
345       if (!args.isEmpty()) {
346         System.err.println("Can't understand argument: " + args.get(0));
347         System.err.println("Usage is " + getShortUsage());
348         return 1;
349       }
350       if (!modified) {
351         System.err.println("No modifications were specified.");
352         return 1;
353       }
354       DistributedFileSystem dfs = AdminHelper.getDFS(conf);
355       EnumSet<CacheFlag> flags = EnumSet.noneOf(CacheFlag.class);
356       if (force) {
357         flags.add(CacheFlag.FORCE);
358       }
359       try {
360         dfs.modifyCacheDirective(builder.build(), flags);
361         System.out.println("Modified cache directive " + idString);
362       } catch (IOException e) {
363         System.err.println(AdminHelper.prettifyException(e));
364         return 2;
365       }
366       return 0;
367     }
368   }
369 
370   private static class RemoveCacheDirectiveInfosCommand
371       implements AdminHelper.Command {
372     @Override
getName()373     public String getName() {
374       return "-removeDirectives";
375     }
376 
377     @Override
getShortUsage()378     public String getShortUsage() {
379       return "[" + getName() + " -path <path>]\n";
380     }
381 
382     @Override
getLongUsage()383     public String getLongUsage() {
384       TableListing listing = AdminHelper.getOptionDescriptionListing();
385       listing.addRow("-path <path>", "The path of the cache directives to remove.  " +
386         "You must have write permission on the pool of the directive in order " +
387         "to remove it.  To see a list of cache directives, use the " +
388         "-listDirectives command.");
389       return getShortUsage() + "\n" +
390         "Remove every cache directive with the specified path.\n\n" +
391         listing.toString();
392     }
393 
394     @Override
run(Configuration conf, List<String> args)395     public int run(Configuration conf, List<String> args) throws IOException {
396       String path = StringUtils.popOptionWithArgument("-path", args);
397       if (path == null) {
398         System.err.println("You must specify a path with -path.");
399         return 1;
400       }
401       if (!args.isEmpty()) {
402         System.err.println("Can't understand argument: " + args.get(0));
403         System.err.println("Usage is " + getShortUsage());
404         return 1;
405       }
406       int exitCode = 0;
407       try {
408         DistributedFileSystem dfs = AdminHelper.getDFS(conf);
409         RemoteIterator<CacheDirectiveEntry> iter =
410             dfs.listCacheDirectives(
411                 new CacheDirectiveInfo.Builder().
412                     setPath(new Path(path)).build());
413         while (iter.hasNext()) {
414           CacheDirectiveEntry entry = iter.next();
415           try {
416             dfs.removeCacheDirective(entry.getInfo().getId());
417             System.out.println("Removed cache directive " +
418                 entry.getInfo().getId());
419           } catch (IOException e) {
420             System.err.println(AdminHelper.prettifyException(e));
421             exitCode = 2;
422           }
423         }
424       } catch (IOException e) {
425         System.err.println(AdminHelper.prettifyException(e));
426         exitCode = 2;
427       }
428       if (exitCode == 0) {
429         System.out.println("Removed every cache directive with path " +
430             path);
431       }
432       return exitCode;
433     }
434   }
435 
436   private static class ListCacheDirectiveInfoCommand
437       implements AdminHelper.Command {
438     @Override
getName()439     public String getName() {
440       return "-listDirectives";
441     }
442 
443     @Override
getShortUsage()444     public String getShortUsage() {
445       return "[" + getName()
446           + " [-stats] [-path <path>] [-pool <pool>] [-id <id>]\n";
447     }
448 
449     @Override
getLongUsage()450     public String getLongUsage() {
451       TableListing listing = AdminHelper.getOptionDescriptionListing();
452       listing.addRow("-stats", "List path-based cache directive statistics.");
453       listing.addRow("<path>", "List only " +
454           "cache directives with this path. " +
455           "Note that if there is a cache directive for <path> " +
456           "in a cache pool that we don't have read access for, it " +
457           "will not be listed.");
458       listing.addRow("<pool>", "List only path cache directives in that pool.");
459       listing.addRow("<id>", "List the cache directive with this id.");
460       return getShortUsage() + "\n" +
461         "List cache directives.\n\n" +
462         listing.toString();
463     }
464 
465     @Override
run(Configuration conf, List<String> args)466     public int run(Configuration conf, List<String> args) throws IOException {
467       CacheDirectiveInfo.Builder builder =
468           new CacheDirectiveInfo.Builder();
469       String pathFilter = StringUtils.popOptionWithArgument("-path", args);
470       if (pathFilter != null) {
471         builder.setPath(new Path(pathFilter));
472       }
473       String poolFilter = StringUtils.popOptionWithArgument("-pool", args);
474       if (poolFilter != null) {
475         builder.setPool(poolFilter);
476       }
477       boolean printStats = StringUtils.popOption("-stats", args);
478       String idFilter = StringUtils.popOptionWithArgument("-id", args);
479       if (idFilter != null) {
480         builder.setId(Long.parseLong(idFilter));
481       }
482       if (!args.isEmpty()) {
483         System.err.println("Can't understand argument: " + args.get(0));
484         return 1;
485       }
486       TableListing.Builder tableBuilder = new TableListing.Builder().
487           addField("ID", Justification.RIGHT).
488           addField("POOL", Justification.LEFT).
489           addField("REPL", Justification.RIGHT).
490           addField("EXPIRY", Justification.LEFT).
491           addField("PATH", Justification.LEFT);
492       if (printStats) {
493         tableBuilder.addField("BYTES_NEEDED", Justification.RIGHT).
494                     addField("BYTES_CACHED", Justification.RIGHT).
495                     addField("FILES_NEEDED", Justification.RIGHT).
496                     addField("FILES_CACHED", Justification.RIGHT);
497       }
498       TableListing tableListing = tableBuilder.build();
499       try {
500         DistributedFileSystem dfs = AdminHelper.getDFS(conf);
501         RemoteIterator<CacheDirectiveEntry> iter =
502             dfs.listCacheDirectives(builder.build());
503         int numEntries = 0;
504         while (iter.hasNext()) {
505           CacheDirectiveEntry entry = iter.next();
506           CacheDirectiveInfo directive = entry.getInfo();
507           CacheDirectiveStats stats = entry.getStats();
508           List<String> row = new LinkedList<String>();
509           row.add("" + directive.getId());
510           row.add(directive.getPool());
511           row.add("" + directive.getReplication());
512           String expiry;
513           // This is effectively never, round for nice printing
514           if (directive.getExpiration().getMillis() >
515               Expiration.MAX_RELATIVE_EXPIRY_MS / 2) {
516             expiry = "never";
517           } else {
518             expiry = directive.getExpiration().toString();
519           }
520           row.add(expiry);
521           row.add(directive.getPath().toUri().getPath());
522           if (printStats) {
523             row.add("" + stats.getBytesNeeded());
524             row.add("" + stats.getBytesCached());
525             row.add("" + stats.getFilesNeeded());
526             row.add("" + stats.getFilesCached());
527           }
528           tableListing.addRow(row.toArray(new String[row.size()]));
529           numEntries++;
530         }
531         System.out.print(String.format("Found %d entr%s%n",
532             numEntries, numEntries == 1 ? "y" : "ies"));
533         if (numEntries > 0) {
534           System.out.print(tableListing);
535         }
536       } catch (IOException e) {
537         System.err.println(AdminHelper.prettifyException(e));
538         return 2;
539       }
540       return 0;
541     }
542   }
543 
544   private static class AddCachePoolCommand implements AdminHelper.Command {
545 
546     private static final String NAME = "-addPool";
547 
548     @Override
getName()549     public String getName() {
550       return NAME;
551     }
552 
553     @Override
getShortUsage()554     public String getShortUsage() {
555       return "[" + NAME + " <name> [-owner <owner>] " +
556           "[-group <group>] [-mode <mode>] [-limit <limit>] " +
557           "[-maxTtl <maxTtl>]\n";
558     }
559 
560     @Override
getLongUsage()561     public String getLongUsage() {
562       TableListing listing = AdminHelper.getOptionDescriptionListing();
563 
564       listing.addRow("<name>", "Name of the new pool.");
565       listing.addRow("<owner>", "Username of the owner of the pool. " +
566           "Defaults to the current user.");
567       listing.addRow("<group>", "Group of the pool. " +
568           "Defaults to the primary group name of the current user.");
569       listing.addRow("<mode>", "UNIX-style permissions for the pool. " +
570           "Permissions are specified in octal, e.g. 0755. " +
571           "By default, this is set to " + String.format("0%03o",
572           FsPermission.getCachePoolDefault().toShort()) + ".");
573       listing.addRow("<limit>", "The maximum number of bytes that can be " +
574           "cached by directives in this pool, in aggregate. By default, " +
575           "no limit is set.");
576       listing.addRow("<maxTtl>", "The maximum allowed time-to-live for " +
577           "directives being added to the pool. This can be specified in " +
578           "seconds, minutes, hours, and days, e.g. 120s, 30m, 4h, 2d. " +
579           "Valid units are [smhd]. By default, no maximum is set. " +
580           "A value of \"never\" specifies that there is no limit.");
581       return getShortUsage() + "\n" +
582           "Add a new cache pool.\n\n" +
583           listing.toString();
584     }
585 
586     @Override
run(Configuration conf, List<String> args)587     public int run(Configuration conf, List<String> args) throws IOException {
588       String name = StringUtils.popFirstNonOption(args);
589       if (name == null) {
590         System.err.println("You must specify a name when creating a " +
591             "cache pool.");
592         return 1;
593       }
594       CachePoolInfo info = new CachePoolInfo(name);
595 
596       String owner = StringUtils.popOptionWithArgument("-owner", args);
597       if (owner != null) {
598         info.setOwnerName(owner);
599       }
600       String group = StringUtils.popOptionWithArgument("-group", args);
601       if (group != null) {
602         info.setGroupName(group);
603       }
604       String modeString = StringUtils.popOptionWithArgument("-mode", args);
605       if (modeString != null) {
606         short mode = Short.parseShort(modeString, 8);
607         info.setMode(new FsPermission(mode));
608       }
609       String limitString = StringUtils.popOptionWithArgument("-limit", args);
610       Long limit = AdminHelper.parseLimitString(limitString);
611       if (limit != null) {
612         info.setLimit(limit);
613       }
614       String maxTtlString = StringUtils.popOptionWithArgument("-maxTtl", args);
615       try {
616         Long maxTtl = AdminHelper.parseTtlString(maxTtlString);
617         if (maxTtl != null) {
618           info.setMaxRelativeExpiryMs(maxTtl);
619         }
620       } catch (IOException e) {
621         System.err.println(
622             "Error while parsing maxTtl value: " + e.getMessage());
623         return 1;
624       }
625 
626       if (!args.isEmpty()) {
627         System.err.print("Can't understand arguments: " +
628           Joiner.on(" ").join(args) + "\n");
629         System.err.println("Usage is " + getShortUsage());
630         return 1;
631       }
632       DistributedFileSystem dfs = AdminHelper.getDFS(conf);
633       try {
634         dfs.addCachePool(info);
635       } catch (IOException e) {
636         System.err.println(AdminHelper.prettifyException(e));
637         return 2;
638       }
639       System.out.println("Successfully added cache pool " + name + ".");
640       return 0;
641     }
642   }
643 
644   private static class ModifyCachePoolCommand implements AdminHelper.Command {
645 
646     @Override
getName()647     public String getName() {
648       return "-modifyPool";
649     }
650 
651     @Override
getShortUsage()652     public String getShortUsage() {
653       return "[" + getName() + " <name> [-owner <owner>] " +
654           "[-group <group>] [-mode <mode>] [-limit <limit>] " +
655           "[-maxTtl <maxTtl>]]\n";
656     }
657 
658     @Override
getLongUsage()659     public String getLongUsage() {
660       TableListing listing = AdminHelper.getOptionDescriptionListing();
661 
662       listing.addRow("<name>", "Name of the pool to modify.");
663       listing.addRow("<owner>", "Username of the owner of the pool");
664       listing.addRow("<group>", "Groupname of the group of the pool.");
665       listing.addRow("<mode>", "Unix-style permissions of the pool in octal.");
666       listing.addRow("<limit>", "Maximum number of bytes that can be cached " +
667           "by this pool.");
668       listing.addRow("<maxTtl>", "The maximum allowed time-to-live for " +
669           "directives being added to the pool.");
670 
671       return getShortUsage() + "\n" +
672           WordUtils.wrap("Modifies the metadata of an existing cache pool. " +
673           "See usage of " + AddCachePoolCommand.NAME + " for more details.",
674           AdminHelper.MAX_LINE_WIDTH) + "\n\n" +
675           listing.toString();
676     }
677 
678     @Override
run(Configuration conf, List<String> args)679     public int run(Configuration conf, List<String> args) throws IOException {
680       String owner = StringUtils.popOptionWithArgument("-owner", args);
681       String group = StringUtils.popOptionWithArgument("-group", args);
682       String modeString = StringUtils.popOptionWithArgument("-mode", args);
683       Integer mode = (modeString == null) ?
684           null : Integer.parseInt(modeString, 8);
685       String limitString = StringUtils.popOptionWithArgument("-limit", args);
686       Long limit = AdminHelper.parseLimitString(limitString);
687       String maxTtlString = StringUtils.popOptionWithArgument("-maxTtl", args);
688       Long maxTtl;
689       try {
690         maxTtl = AdminHelper.parseTtlString(maxTtlString);
691       } catch (IOException e) {
692         System.err.println(
693             "Error while parsing maxTtl value: " + e.getMessage());
694         return 1;
695       }
696       String name = StringUtils.popFirstNonOption(args);
697       if (name == null) {
698         System.err.println("You must specify a name when creating a " +
699             "cache pool.");
700         return 1;
701       }
702       if (!args.isEmpty()) {
703         System.err.print("Can't understand arguments: " +
704           Joiner.on(" ").join(args) + "\n");
705         System.err.println("Usage is " + getShortUsage());
706         return 1;
707       }
708       boolean changed = false;
709       CachePoolInfo info = new CachePoolInfo(name);
710       if (owner != null) {
711         info.setOwnerName(owner);
712         changed = true;
713       }
714       if (group != null) {
715         info.setGroupName(group);
716         changed = true;
717       }
718       if (mode != null) {
719         info.setMode(new FsPermission(mode.shortValue()));
720         changed = true;
721       }
722       if (limit != null) {
723         info.setLimit(limit);
724         changed = true;
725       }
726       if (maxTtl != null) {
727         info.setMaxRelativeExpiryMs(maxTtl);
728         changed = true;
729       }
730       if (!changed) {
731         System.err.println("You must specify at least one attribute to " +
732             "change in the cache pool.");
733         return 1;
734       }
735       DistributedFileSystem dfs = AdminHelper.getDFS(conf);
736       try {
737         dfs.modifyCachePool(info);
738       } catch (IOException e) {
739         System.err.println(AdminHelper.prettifyException(e));
740         return 2;
741       }
742       System.out.print("Successfully modified cache pool " + name);
743       String prefix = " to have ";
744       if (owner != null) {
745         System.out.print(prefix + "owner name " + owner);
746         prefix = " and ";
747       }
748       if (group != null) {
749         System.out.print(prefix + "group name " + group);
750         prefix = " and ";
751       }
752       if (mode != null) {
753         System.out.print(prefix + "mode " + new FsPermission(mode.shortValue()));
754         prefix = " and ";
755       }
756       if (limit != null) {
757         System.out.print(prefix + "limit " + limit);
758         prefix = " and ";
759       }
760       if (maxTtl != null) {
761         System.out.print(prefix + "max time-to-live " + maxTtlString);
762       }
763       System.out.print("\n");
764       return 0;
765     }
766   }
767 
768   private static class RemoveCachePoolCommand implements AdminHelper.Command {
769 
770     @Override
getName()771     public String getName() {
772       return "-removePool";
773     }
774 
775     @Override
getShortUsage()776     public String getShortUsage() {
777       return "[" + getName() + " <name>]\n";
778     }
779 
780     @Override
getLongUsage()781     public String getLongUsage() {
782       return getShortUsage() + "\n" +
783           WordUtils.wrap("Remove a cache pool. This also uncaches paths " +
784               "associated with the pool.\n\n", AdminHelper.MAX_LINE_WIDTH) +
785           "<name>  Name of the cache pool to remove.\n";
786     }
787 
788     @Override
run(Configuration conf, List<String> args)789     public int run(Configuration conf, List<String> args) throws IOException {
790       String name = StringUtils.popFirstNonOption(args);
791       if (name == null) {
792         System.err.println("You must specify a name when deleting a " +
793             "cache pool.");
794         return 1;
795       }
796       if (!args.isEmpty()) {
797         System.err.print("Can't understand arguments: " +
798           Joiner.on(" ").join(args) + "\n");
799         System.err.println("Usage is " + getShortUsage());
800         return 1;
801       }
802       DistributedFileSystem dfs = AdminHelper.getDFS(conf);
803       try {
804         dfs.removeCachePool(name);
805       } catch (IOException e) {
806         System.err.println(AdminHelper.prettifyException(e));
807         return 2;
808       }
809       System.out.println("Successfully removed cache pool " + name + ".");
810       return 0;
811     }
812   }
813 
814   private static class ListCachePoolsCommand implements AdminHelper.Command {
815 
816     @Override
getName()817     public String getName() {
818       return "-listPools";
819     }
820 
821     @Override
getShortUsage()822     public String getShortUsage() {
823       return "[" + getName() + " [-stats] [<name>]]\n";
824     }
825 
826     @Override
getLongUsage()827     public String getLongUsage() {
828       TableListing listing = AdminHelper.getOptionDescriptionListing();
829       listing.addRow("-stats", "Display additional cache pool statistics.");
830       listing.addRow("<name>", "If specified, list only the named cache pool.");
831 
832       return getShortUsage() + "\n" +
833           WordUtils.wrap("Display information about one or more cache pools, " +
834               "e.g. name, owner, group, permissions, etc.",
835               AdminHelper.MAX_LINE_WIDTH) + "\n\n" + listing.toString();
836     }
837 
838     @Override
run(Configuration conf, List<String> args)839     public int run(Configuration conf, List<String> args) throws IOException {
840       String name = StringUtils.popFirstNonOption(args);
841       final boolean printStats = StringUtils.popOption("-stats", args);
842       if (!args.isEmpty()) {
843         System.err.print("Can't understand arguments: " +
844           Joiner.on(" ").join(args) + "\n");
845         System.err.println("Usage is " + getShortUsage());
846         return 1;
847       }
848       DistributedFileSystem dfs = AdminHelper.getDFS(conf);
849       TableListing.Builder builder = new TableListing.Builder().
850           addField("NAME", Justification.LEFT).
851           addField("OWNER", Justification.LEFT).
852           addField("GROUP", Justification.LEFT).
853           addField("MODE", Justification.LEFT).
854           addField("LIMIT", Justification.RIGHT).
855           addField("MAXTTL", Justification.RIGHT);
856       if (printStats) {
857         builder.
858             addField("BYTES_NEEDED", Justification.RIGHT).
859             addField("BYTES_CACHED", Justification.RIGHT).
860             addField("BYTES_OVERLIMIT", Justification.RIGHT).
861             addField("FILES_NEEDED", Justification.RIGHT).
862             addField("FILES_CACHED", Justification.RIGHT);
863       }
864       TableListing listing = builder.build();
865       int numResults = 0;
866       try {
867         RemoteIterator<CachePoolEntry> iter = dfs.listCachePools();
868         while (iter.hasNext()) {
869           CachePoolEntry entry = iter.next();
870           CachePoolInfo info = entry.getInfo();
871           LinkedList<String> row = new LinkedList<String>();
872           if (name == null || info.getPoolName().equals(name)) {
873             row.add(info.getPoolName());
874             row.add(info.getOwnerName());
875             row.add(info.getGroupName());
876             row.add(info.getMode() != null ? info.getMode().toString() : null);
877             Long limit = info.getLimit();
878             String limitString;
879             if (limit != null && limit.equals(CachePoolInfo.LIMIT_UNLIMITED)) {
880               limitString = "unlimited";
881             } else {
882               limitString = "" + limit;
883             }
884             row.add(limitString);
885             Long maxTtl = info.getMaxRelativeExpiryMs();
886             String maxTtlString = null;
887 
888             if (maxTtl != null) {
889               if (maxTtl == CachePoolInfo.RELATIVE_EXPIRY_NEVER) {
890                 maxTtlString  = "never";
891               } else {
892                 maxTtlString = DFSUtil.durationToString(maxTtl);
893               }
894             }
895             row.add(maxTtlString);
896             if (printStats) {
897               CachePoolStats stats = entry.getStats();
898               row.add(Long.toString(stats.getBytesNeeded()));
899               row.add(Long.toString(stats.getBytesCached()));
900               row.add(Long.toString(stats.getBytesOverlimit()));
901               row.add(Long.toString(stats.getFilesNeeded()));
902               row.add(Long.toString(stats.getFilesCached()));
903             }
904             listing.addRow(row.toArray(new String[row.size()]));
905             ++numResults;
906             if (name != null) {
907               break;
908             }
909           }
910         }
911       } catch (IOException e) {
912         System.err.println(AdminHelper.prettifyException(e));
913         return 2;
914       }
915       System.out.print(String.format("Found %d result%s.%n", numResults,
916           (numResults == 1 ? "" : "s")));
917       if (numResults > 0) {
918         System.out.print(listing);
919       }
920       // If list pools succeed, we return 0 (success exit code)
921       return 0;
922     }
923   }
924 
925   private static final AdminHelper.Command[] COMMANDS = {
926     new AddCacheDirectiveInfoCommand(),
927     new ModifyCacheDirectiveInfoCommand(),
928     new ListCacheDirectiveInfoCommand(),
929     new RemoveCacheDirectiveInfoCommand(),
930     new RemoveCacheDirectiveInfosCommand(),
931     new AddCachePoolCommand(),
932     new ModifyCachePoolCommand(),
933     new RemoveCachePoolCommand(),
934     new ListCachePoolsCommand()
935   };
936 }
937