File Coverage

blib/lib/Hadoop/HDFS/Command.pm
Criterion Covered Total %
statement 41 261 15.7
branch 0 68 0.0
condition 0 31 0.0
subroutine 14 30 46.6
pod 1 1 100.0
total 56 391 14.3


line stmt bran cond sub pod time code
1             package Hadoop::HDFS::Command;
2             $Hadoop::HDFS::Command::VERSION = '0.007';
3 1     1   89261 use 5.010;
  1         13  
4 1     1   5 use strict;
  1         2  
  1         37  
5 1     1   6 use warnings;
  1         2  
  1         26  
6 1     1   463 use Capture::Tiny ();
  1         25662  
  1         31  
7 1     1   7 use Carp ();
  1         3  
  1         15  
8 1     1   620 use Data::Dumper;
  1         6227  
  1         84  
9 1     1   571 use DateTime::Format::Strptime;
  1         510989  
  1         7  
10 1     1   112 use DateTime;
  1         2  
  1         19  
11 1     1   848 use Getopt::Long ();
  1         9454  
  1         37  
12 1     1   753 use IPC::Cmd ();
  1         41328  
  1         34  
13 1     1   11 use Ref::Util ();
  1         2  
  1         19  
14 1     1   5 use Time::HiRes qw( time );
  1         2  
  1         9  
15 1     1   759 use Types::Standard qw(Bool Str);
  1         62224  
  1         15  
16              
17 1     1   2878 { use Moo; }
  1         6820  
  1         7  
18              
19             has cmd_hdfs => (
20             is => 'rw',
21             isa => sub {
22             my $val = shift;
23             return if $val && -e $val && -x _;
24             Carp::confess sprintf "The command `%s` either does not exist or not an executable!",
25             $val,
26             ;
27             },
28             default => sub { '/usr/bin/hdfs' },
29             lazy => 1,
30             );
31              
32             has enable_log => (
33             is => 'rw',
34             isa => Bool,
35             default => sub { 0 },
36             lazy => 1,
37             );
38              
39             has trace_logs => (
40             is => 'rw',
41             isa => Bool,
42             default => sub { 0 },
43             lazy => 1,
44             );
45              
46             has runas => (
47             is => 'rw',
48             isa => Str,
49             default => scalar getpwuid $<,
50             lazy => 1,
51             );
52              
53             before ['_capture', '_capture_with_stdin'] => sub {
54             my ($self, $options, @cmd) = @_;
55             unshift @cmd, 'sudo', '-u', $self->runas
56             unless $self->runas eq getpwuid $<;
57             @_ = ($self, $options, @cmd);
58             };
59              
60             sub dfs {
61 0     0 1   my $self = shift;
62 0 0         my $options = Ref::Util::is_hashref $_[0] ? shift( @_ ) : {};
63 0   0       (my $cmd = shift || die "No dfs command specified") =~ s{ \A [-]+ }{}xms;
64 0           my $method = '_dfs_' . $cmd;
65 0 0         Carp::croak "'$cmd' is not implemented!" if ! $self->can( $method );
66 0           $self->$method( $options, @_ );
67             }
68              
69             sub _dfs_ls {
70 0     0     my $self = shift;
71 0           state $strp;
72              
73 0           my $options = shift;
74 0           my @params = @_;
75 0           my @flags = qw( d h R );
76 0           my($arg, $paths) = $self->_parse_options(
77             \@params,
78             \@flags,
79             undef,
80             {
81             require_params => 1,
82             },
83             );
84              
85 0           my $want_epoch = $options->{want_epoch};
86 0           my $cb = delete $options->{callback};
87              
88 0 0         if ( $cb ) {
89 0 0         die "callback needs to be a CODE" if ! Ref::Util::is_coderef $cb;
90 0 0         if ( defined wantarray ) {
91 0           Carp::croak "You need to call this function in void context when callback is specified";
92             }
93             }
94              
95             my @response = $self->_capture(
96             $options,
97             $self->cmd_hdfs,
98             qw( dfs -ls ),
99 0           ( map { '-' . $_ } grep { $arg->{ $_ } } @flags ),
  0            
100 0           @{ $paths },
  0            
101             );
102              
103             # directory is empty
104             #
105 0 0         return if ! @response;
106              
107 0 0 0       if ( $response[0] && $response[0] =~ m{ \A Found \s+ [0-9] }xms ) {
108 0           shift @response; # junk
109             }
110              
111 0           my $space = q{ };
112              
113 0           my @rv;
114 0           for my $line ( @response ) {
115 0           my($mode, $replication, $user, $group, @unknown) = split m{ \s+ }xms, $line, 5;
116 0           my @rest = map { split $space, $_ } @unknown;
  0            
117 0           my $size;
118 0 0         if ( $arg->{h}) {
119 0 0 0       if ( $rest[0] eq '0' || $rest[1] !~ m{ [a-zA-Z_] }xms ) {
120 0           $size = shift @rest;
121             }
122             else {
123 0           $size = join $space, shift @rest, shift @rest;
124             }
125             }
126             else {
127 0           $size = shift @rest;
128             }
129 0           my $date = join ' ', shift @rest, shift @rest;
130 0   0       my $path = shift( @rest ) || die "Unable to parse $line to gather the path";
131 0 0         my $is_dir = $mode =~ m{ \A [d] }xms ? 1 : 0;
132              
133 0 0         my %record = (
134             mode => $mode,
135             replication => $replication,
136             user => $user,
137             group => $group,
138             size => $size,
139             date => $date,
140             path => $path,
141             type => $is_dir ? 'dir' : 'file',
142             );
143              
144 0 0         if ( $want_epoch ) {
145 0   0       $strp ||= DateTime::Format::Strptime->new(
146             pattern => '%Y-%m-%d %H:%M',
147             time_zone => 'CET',
148             on_error => 'croak',
149             );
150             eval {
151 0           $record{epoch} = $strp->parse_datetime( $date )->epoch;
152 0           1;
153 0 0         } or do {
154 0   0       my $eval_error = $@ || 'Zombie error';
155 0           $self->_log( debug => 'Failed to convert %s into an epoch: %s',
156             $date,
157             $eval_error,
158             );
159             };
160             }
161              
162 0 0         if ( @rest ) {
163             # interpret as the rest of the path as spaces in paths are ok
164             # possibly this will need to be revisited in the future.
165             #
166 0           $record{path} = join $space, $record{path}, @rest;
167             }
168              
169 0 0         if ( $cb ) {
170             # control the flow from the callback
171             # So, the return value matters.
172             #
173 0 0         if ( ! $cb->( \%record ) ) {
174 0           $self->_log( info => 'Terminating the ls processing as the user callback did not return a true value.');
175 0           last;
176             }
177 0           next;
178             }
179              
180 0           push @rv, { %record };
181             }
182              
183 0 0         return if $cb;
184 0           return @rv;
185             }
186              
187             sub _dfs_du {
188 0     0     my $self = shift;
189 0           my $options = shift;
190 0           my @params = @_;
191 0           my @flags = qw( h s );
192 0           my($arg, $paths) = $self->_parse_options(
193             \@params,
194             \@flags,
195             undef,
196             {
197             require_params => 1,
198             },
199             );
200              
201             my @rv = $self->_capture(
202             $options,
203             $self->cmd_hdfs,
204             qw( dfs -du ),
205 0           ( map { '-' . $_ } grep { $arg->{ $_ } } @flags ),
  0            
206 0 0         @{ $paths },
  0            
207             ) or die "No output collected from -du command";
208              
209             return map {
210 0           my @val = split m{ \s{2,} }xms, $_;
  0            
211             {
212 0 0         size => shift( @val ),
213             name => pop( @val ),
214             ( @val ? (
215             disk_space_consumed => shift( @val ),
216             ) : () ),
217             }
218             } @rv;
219             }
220              
221             sub _dfs_mv {
222 0     0     my $self = shift;
223 0           my $options = shift;
224 0           my @params = @_;
225 0           my($arg, $paths) = $self->_parse_options(
226             \@params,
227             [],
228             undef,
229             {
230             require_params => 1,
231             },
232             );
233 0   0       my $source = shift @{ $paths } || die "Source path not specified";
234 0   0       my $target = shift @{ $paths } || die "Target path not specified";
235              
236             # will die on error
237 0           $self->_capture(
238             $options,
239             $self->cmd_hdfs,
240             qw( dfs -mv ),
241             $source => $target,
242             );
243              
244 0           return;
245             }
246              
247             sub _dfs_rm {
248 0     0     my $self = shift;
249 0           my $options = shift;
250 0           my @params = @_;
251 0           my @flags = qw( f r skipTrash );
252 0           my($arg, $paths) = $self->_parse_options(
253             \@params,
254             \@flags,
255             undef,
256             {
257             require_params => 1,
258             },
259             );
260              
261             my @response = $self->_capture(
262             $options,
263             $self->cmd_hdfs,
264             qw( dfs -rm ),
265 0           ( map { '-' . $_ } grep { $arg->{ $_ } } @flags ),
  0            
266 0           @{ $paths },
  0            
267             );
268              
269             # just a confirmation message
270 0           return @response;
271             }
272              
273             sub _dfs_put {
274 0     0     my $self = shift;
275 0           my $options = shift;
276 0           my @params = @_;
277 0           my @flags = qw( f p l - );
278 0           my($arg, $paths) = $self->_parse_options(
279             \@params,
280             \@flags,
281             undef,
282             {
283             require_params => 1,
284             },
285             );
286              
287 0 0 0       if ( $paths->[0] && $paths->[0] eq '\-' ) {
288 0           shift @{ $paths };
  0            
289 0   0       $options->{stdin} = pop( @{ $paths } ) || die "stdin content not specified!";
290             }
291              
292 0 0         if ( @{ $paths } < ( $options->{stdin} ? 1 : 2 ) ) {
  0 0          
293 0           die "Missing arguments!";
294             }
295              
296             my @response = $self->_capture_with_stdin(
297             $options,
298             $self->cmd_hdfs,
299             qw( dfs -put ),
300 0 0         ( map { $_ eq '-' ? $_ : '-' . $_ } grep { $arg->{ $_ } } @flags ),
  0            
301             ( $options->{stdin} ? '-' : () ),
302 0 0         @{ $paths },
  0            
303             );
304              
305             # just a confirmation message
306 0           return @response;
307             }
308              
309             sub _dfs_test {
310 0     0     my $self = shift;
311 0           my $options = shift;
312 0           my @params = @_;
313 0           my @flags = qw( d e f s z );
314 0           my($arg, $paths) = $self->_parse_options(
315             \@params,
316             \@flags,
317             undef,
318             {
319             require_params => 1,
320             },
321             );
322 0 0         eval {
323             $self->_capture(
324             $options,
325             $self->cmd_hdfs,
326             qw( dfs -test ),
327 0           ( map { '-' . $_ } grep { $arg->{ $_ } } @flags ),
  0            
328 0           @{ $paths },
  0            
329             );
330 0           return 1;
331             } or return 0;
332             }
333              
334             sub _dfs_mkdir {
335 0     0     my $self = shift;
336 0           my $options = shift;
337 0           my @params = @_;
338 0           my @flags = qw( p );
339 0           my($arg, $paths) = $self->_parse_options(
340             \@params,
341             \@flags,
342             undef,
343             {
344             require_params => 1,
345             },
346             );
347             my @response = $self->_capture(
348             $options,
349             $self->cmd_hdfs,
350             qw( dfs -mkdir ),
351 0           ( map { '-' . $_ } grep { $arg->{ $_ } } @flags ),
  0            
352 0           @{ $paths },
  0            
353             );
354              
355             # just a confirmation message
356             return @response
357 0           }
358              
359             sub _dfs_chmod {
360 0     0     my $self = shift;
361 0           my $options = shift;
362 0           my @params = @_;
363 0           my @flags = qw( p );
364 0           my($arg, $paths) = $self->_parse_options(
365             \@params,
366             \@flags,
367             undef,
368             {
369             require_params => 1,
370             },
371             );
372             my @response = $self->_capture(
373             $options,
374             $self->cmd_hdfs,
375             qw( dfs -chmod ),
376 0           ( map { '-' . $_ } grep { $arg->{ $_ } } @flags ),
  0            
377 0           @{ $paths },
  0            
378             );
379              
380             # just a confirmation message
381             return @response
382 0           }
383              
384             sub _dfs_chown {
385 0     0     my $self = shift;
386 0           my $options = shift;
387 0           my @params = @_;
388 0           my @flags = qw( p );
389 0           my($arg, $paths) = $self->_parse_options(
390             \@params,
391             \@flags,
392             undef,
393             {
394             require_params => 1,
395             },
396             );
397             my @response = $self->_capture(
398             $options,
399             $self->cmd_hdfs,
400             qw( dfs -chown ),
401 0           ( map { '-' . $_ } grep { $arg->{ $_ } } @flags ),
  0            
402 0           @{ $paths },
  0            
403             );
404              
405             # just a confirmation message
406             return @response
407 0           }
408              
409             sub _dfs_get {
410 0     0     my $self = shift;
411 0           my $options = shift;
412 0           my @params = @_;
413 0           my @flags = qw( p ignoreCrc crc );
414 0           my($arg, $paths) = $self->_parse_options(
415             \@params,
416             \@flags,
417             undef,
418             {
419             require_params => 1,
420             },
421             );
422             my @response = $self->_capture(
423             $options,
424             $self->cmd_hdfs,
425             qw( dfs -get ),
426 0           ( map { '-' . $_ } grep { $arg->{ $_ } } @flags ),
  0            
427 0           @{ $paths },
  0            
428             );
429              
430             # just a confirmation message
431             return @response
432 0           }
433              
434             sub _dfs_getfacl {
435 0     0     my $self = shift;
436 0           my $options = shift;
437 0           my @params = @_;
438 0           my @flags = qw( R );
439 0           my($arg, $paths) = $self->_parse_options(
440             \@params,
441             \@flags,
442             undef,
443             {
444             require_params => 1,
445             },
446             );
447             my @response = $self->_capture(
448             $options,
449             $self->cmd_hdfs,
450             qw( dfs -getfacl ),
451 0           ( map { '-' . $_ } grep { $arg->{ $_ } } @flags ),
  0            
452 0           @{ $paths },
  0            
453             );
454              
455              
456 0           my %rv;
457 0           for my $line ( @response ) {
458 0 0         if ( my($match) = $line =~ m{ \A [#] \s+ (.*) \z }xms ) {
459 0           my($k, $v) = split m{ [:] \s+ }xms, $match, 2;
460 0           $rv{ $k } = $v;
461 0           next;
462             }
463 0   0       push @{ $rv{entries} ||= [] }, $line;
  0            
464             }
465              
466 0           return \%rv;
467             }
468              
469             sub _dfs_setfacl {
470 0     0     my $self = shift;
471 0           my $options = shift;
472 0           my @params = @_;
473 0           my @flags = qw( b k R );
474 0           my @args = qw( m=s x=s set=s );
475              
476 0           my($arg, $paths) = $self->_parse_options(
477             \@params,
478             \@flags,
479             \@args,
480             {
481             require_params => 1,
482             },
483             );
484              
485 0           my @acl_flags = map { '-' . $_ } grep { $arg->{ $_ } } @flags;
  0            
  0            
486 0           delete @{ $arg }{ @flags };
  0            
487              
488             my @acl_args = map {
489 0 0         my $key = $_ eq 'set'? '--set' : '-' . $_;
490 0           $key => $arg->{ $_ }
491 0           } keys %{ $arg };
  0            
492              
493             my @response = $self->_capture(
494             $options,
495             $self->cmd_hdfs,
496             qw( dfs -setfacl ),
497             @acl_flags,
498             @acl_args,
499 0           @{ $paths },
  0            
500             );
501              
502             # empty on success
503 0           @response;
504             }
505              
506             sub _parse_options {
507 0     0     my $self = shift;
508             # TODO: collect dfs generic options
509             #
510             # Generic options supported are
511             # -conf <configuration file> specify an application configuration file
512             # -D <property=value> use value for given property
513             # -fs <local|namenode:port> specify a namenode
514             # -jt <local|resourcemanager:port> specify a ResourceManager
515             # -files <comma separated list of files> specify comma separated files to be copied to the map reduce cluster
516             # -libjars <comma separated list of jars> specify comma separated jar files to include in the classpath.
517             # -archives <comma separated list of archives> specify comma separated archives to be unarchived on the compute machines.
518              
519 0           my($params, $flags, $opt, $conf) = @_;
520 0   0       $conf ||= {};
521 0 0         my @params = map { $_ eq '-' ? '\-' : $_ } @{ $params };
  0            
  0            
522              
523             my @getopt_args = (
524             \@params,
525             \my %arg,
526             (
527 0 0         map { Ref::Util::is_arrayref $_ ? @{ $_ } : () }
  0            
  0            
528             $flags,
529             $opt,
530             ),
531             );
532              
533 0 0         if ( $self->trace_logs ) {
534 0           $self->_log( trace => '_parse_options::getopt: %s', \@getopt_args );
535             }
536              
537             Getopt::Long::GetOptionsFromArray(
538             @getopt_args
539 0 0         ) || die qq{Unable to parse parameters: '@{$params}'};
  0            
540              
541 0 0 0       if ( $conf->{require_params} && ! @params ) {
542 0           die "No parameters were specified!";
543             }
544              
545 0 0         if ( $self->trace_logs ) {
546 0           $self->_log( trace => '_parse_options::rv: %s', Dumper [ \%arg, [ @params ] ] );
547             }
548              
549 0           return \%arg, [ @params ];
550             }
551              
552             sub _capture {
553             my $self = shift;
554             my $options = shift;
555             my @cmd = @_;
556              
557             $self->_log( debug => 'Executing command: %s', join(' ', @cmd) );
558              
559             my $start = time;
560              
561             my($stdout, $stderr, $fail) = Capture::Tiny::capture {
562             system( @cmd );
563             };
564              
565             $self->_log( debug => 'Execution took %.3f seconds', time - $start );
566              
567             if ( $fail ) {
568             my $code = $fail >> 8;
569             $stderr ||= '[no error]';
570             my $msg = "External command (@cmd) failed with status=$code: $stderr";
571             if ( $options->{ignore_fail} ) {
572             if ( ! $options->{silent} ) {
573             warn "[Fatal error downgraded to a warning] $msg";
574             }
575             return $self->_split_on_newlines( $stdout || '' );
576             }
577             die $msg;
578             }
579              
580             if ( $stderr ) {
581             warn "Warning from external command: $stderr";
582             }
583              
584             return $self->_split_on_newlines( $stdout );
585             }
586              
587             sub _capture_with_stdin {
588             my $self = shift;
589             # TODO: use a single capture method.
590             my $options = shift;
591             my @cmd = @_;
592              
593             my $stdin = delete $options->{stdin};
594              
595             $self->_log( debug => 'Executing command(IPC): %s', join(' ', @cmd) );
596              
597             my $start = time;
598              
599             my $res = IPC::Cmd::run_forked(
600             \@cmd,
601             {
602             ( $stdin ? (
603             child_stdin => $stdin,
604             ) : () ),
605             #timeout => $timeout,
606             terminate_on_parent_sudden_death => 1,
607             }
608             );
609              
610             $self->_log( debug => 'Execution took %.3f seconds', time - $start );
611              
612             my($stdout, $stderr, $fail);
613              
614             my $success = defined $res->{exit_code}
615             && $res->{exit_code} == 0
616             && ! $res->{timeout};
617              
618             $fail = $success ? 0 : $res->{exit_code};
619             $stderr = $res->{stderr};
620             $stdout = $res->{stdout};
621              
622             if ( $fail ) {
623             my $code = $fail >> 8;
624             $stderr ||= $res->{err_msg} || '[no error]';
625             my $msg = "External command (@cmd) failed with status=$code: $stderr";
626             if ( $options->{ignore_fail} ) {
627             if ( ! $options->{silent} ) {
628             warn "[Fatal error downgraded to a warning] $msg";
629             }
630             return $self->_split_on_newlines( $stdout || '' );
631             }
632             die $msg;
633             }
634              
635             if ( $stderr ) {
636             warn "Warning from external command: $stderr";
637             }
638              
639             return $self->_split_on_newlines( $stdout );
640             }
641              
642             sub _split_on_newlines {
643 0     0     my $self = shift;
644 0           my $rv = shift;
645              
646 0           $rv =~ s{ \A \s+ }{}xms;
647 0           $rv =~ s{ \s+ \z }{}xms;
648              
649 0           return split m{ \n+ }xms, $rv;
650             }
651              
652             sub _log {
653 0     0     my $self = shift;
654 0 0         return if ! $self->enable_log;
655 0           my($level, $tmpl, @param) = @_;
656 0           my $msg = sprintf "[%s] %s\n", uc $level, $tmpl;
657 0           printf STDERR $msg, @param;
658             }
659              
660             1;
661              
662             __END__
663              
664             =pod
665              
666             =encoding UTF-8
667              
668             =head1 NAME
669              
670             Hadoop::HDFS::Command
671              
672             =head1 VERSION
673              
674             version 0.007
675              
676             =head1 SYNOPSIS
677              
678             use Hadoop::HDFS::Command;
679             my $hdfs = Hadoop::HDFS::Command->new;
680             my @rv = $hdfs->$command( @command_args );
681              
682             =head1 DESCRIPTION
683              
684             This is a simple wrapper around the hdfs commandline to make them easier to
685             call from Perl and parse their output.
686              
687             The interface is partially done at the moment (see the implemented wrappers
688             down below).
689              
690             You can always use the WebHDFS to do similar operations instead of failling
691             back to the commandline. However there are several benefits of using the
692             cli; i) you'll end up with a single C<JVM> invocation, so the response
693             might be faster ii) Some functionality / endpoints might be buggy for WebHDFS
694             but might work with the cli (for example escaping certain values is broken
695             in some versions, but works with the cli).
696              
697             =head1 NAME
698              
699             Hadoop::HDFS::Command - Wrappers for various hadoop hdfs cli commands
700              
701             =head1 METHODS
702              
703             =head2 new
704              
705             The constructor. Available attributes are listed below.
706              
707             =head3 cmd_hdfs
708              
709             Default value is C</usr/bin/hdfs>. This option needs to be altered if you have
710             the C<`hdfs`> command in some other place.
711              
712             =head3 enable_log :Bool
713              
714             Can be used to enable the internal logging feature. Disabled by default.
715              
716             =head2 dfs
717              
718             One of the top level commands, including an interface to the sub-commands
719             listed below. The calling convention of the sub commands is as simple as:
720              
721             my @rv = $hdfs->dfs( \%options, $sub_command => @subcommand_args );
722             # options hash is optional
723             my @rv = $hdfs->dfs( $sub_command => @subcommand_args );
724              
725             Available options are listed below:
726              
727             =over 4
728              
729             =item ignore_fail :Bool
730              
731             Global.
732              
733             =item silent :Bool
734              
735             Global.
736              
737             =item want_epoch :Bool
738              
739             Only used for C<ls>. Converts timestamps to epoch.
740              
741             =item callback :CODE
742              
743             Only used for C<ls>. The callback always needs to return true to continue
744             processing, returning false from it will short-circuit the processor.
745              
746             =back
747              
748             =head3 du
749              
750             The C<@subcommand_args> can have these defined: C<-s>, C<-h>.
751              
752             my @rv = $hdfs->dfs( du => @subcommand_args => $hdfs_path );
753             my @rv = $hdfs->dfs( du => qw( -h -s ) => "/tmp" );
754             my @rv = $hdfs->dfs(
755             {
756             ignore_fail => 1,
757             silent => 1,
758             },
759             du => -s => @hdfs_paths,
760             );
761              
762             =head3 getfacl
763              
764             my $rv = $hdfs->dfs( getfacl => $hdfs_path );
765              
766             =head3 ls
767              
768             The C<@subcommand_args> can have these defined: C<-d>, C<-h>, C<R>.
769              
770             my @rv = $hdfs->dfs( ls => @subcommand_args => $hdfs_path );
771              
772             The callback can be used to prevent buffering and process the result set yourself.
773             The callback always needs to return true to continue processing. If you want to
774             skip some entries but continue processing then a true value needs to be returned.
775             A bare return (which is false) will short circuit the iterator and discard any
776             remaining records.
777              
778             my %options = (
779             callback => sub {
780             # This callback will receive a hash meta-data about the file.
781             my $file = shift;
782             if ( $file->{type} eq 'dir' ) {
783             # do something
784             }
785              
786             # skip this one, but continue processing
787             return 1 if $file->{type} ne 'file';
788              
789             # do something
790              
791             return if $something_really_bad_so_end_this_processor;
792              
793             # continue processing
794             return 1;
795             },
796             # The meta-data passed to the callback will have an "epoch"
797             # key set when this is true.
798             want_epoch => 1,
799             );
800             # execute the command recursively on the path
801             $hdfs->dfs( \%options, ls => -R => $hdfs_path );
802              
803             =head3 mv
804              
805             my @rv = $hdfs->dfs( mv => $hdfs_source_path, $hdfs_dest_path );
806              
807             =head3 put
808              
809             The C<@subcommand_args> can have these defined: C<-f>, C<-p>, C<-l>
810              
811             $hdfs->dfs( put => @subcommand_args, $local_path, $hdfs_path );
812             # notice the additional "-"
813             $hdfs->dfs( put => '-f', '-', $hdfs_path, $in_memory_data );
814              
815             =head3 rm
816              
817             The C<@subcommand_args> can have these defined: C<-f>, C<-r>, C<-skipTrash>
818              
819             $hdfs->dfs( rm => @subcommand_args, $hdfs_path );
820              
821             =head3 setfacl
822              
823             The C<@subcommand_args> can have these defined: C<-b>, C<-k>, C<-m acl_spec>,
824             C<-x acl_spec>, C<--set acl_spec>
825              
826             $hdfs->dfs( setfacl => @subcommand_args, $hdfs_path );
827              
828             =head3 test
829              
830             The C<@subcommand_args> can have these defined: C<-d>, C<-e>, C<-f>, C<-s>, C<-z>
831              
832             $hdfs->dfs( test => @subcommand_args, $hdfs_path );
833              
834             =head3 mkdir
835              
836             The C<@subcommand_args> can have these defined: C<-p>
837              
838             $hdfs->dfs( mkdir => @subcommand_args, $path );
839              
840             =head3 chmod
841              
842             The C<@subcommand_args> can have these defined: C<-R>
843              
844             $hdfs->dfs( chmod => @subcommand_args, $mode, $path );
845              
846             =head3 chown
847              
848             The C<@subcommand_args> can have these defined: C<-R>
849              
850             $hdfs->dfs( chown => @subcommand_args, $OWNERCOLONGROUP, $path );
851              
852             =head3 get
853              
854             The C<@subcommand_args> can have these defined: C<-p>, C<-ignoreCrc>, C<-crc>
855              
856             $hdfs->dfs( get => @subcommand_args, $src, $localdst );
857              
858             =head1 SEE ALSO
859              
860             C<`hdfs dfs -help`>.
861              
862             =head1 AUTHOR
863              
864             Burak Gursoy <burak@cpan.org>
865              
866             =head1 COPYRIGHT AND LICENSE
867              
868             This software is copyright (c) 2016 by Burak Gursoy.
869              
870             This is free software; you can redistribute it and/or modify it under
871             the same terms as the Perl 5 programming language system itself.
872              
873             =cut