File Coverage

lib/OpenVZ/Vzctl.pm
Criterion Covered Total %
statement 127 130 97.6
branch 36 52 69.2
condition 11 15 73.3
subroutine 28 30 93.3
pod 7 7 100.0
total 209 234 89.3


line stmt bran cond sub pod time code
1             package OpenVZ::Vzctl;
2              
3             # ABSTRACT: Call OpenVZ vzctl command from your program
4              
5             #XXX: Do we need to load and parse the VZ system config file?
6             #XXX: Need to abstract out the common code into a top level OpenVZ module.
7             #XXX: Need to handle version call
8             #XXX: Need to use 'on_fail' option for validate_with for smoother error
9             # handling.
10              
11              
12 20     20   3871345 use 5.006;
  20         84  
  20         882  
13              
14 20     20   119 use strict;
  20         40  
  20         656  
15 20     20   105 use warnings;
  20         39  
  20         567  
16              
17 20     20   17877 use namespace::autoclean;
  20         342792  
  20         133  
18              
19 20     20   1614 use Carp;
  20         43  
  20         1458  
20 20     20   19876 use List::MoreUtils qw( any );
  20         82703  
  20         1761  
21 20     20   16143 use OpenVZ ':all';
  20         77  
  20         131  
22 20     20   6836 use Params::Validate ':all';
  20         45  
  20         4910  
23 20     20   1093 use Regexp::Common qw( URI net );
  20         4071  
  20         212  
24 20     20   50224 use Scalar::Util 'blessed';
  20         42  
  20         1004  
25 20     20   105 use Sub::Exporter;
  20         40  
  20         112  
26              
27 20     20   3738 use parent 'OpenVZ';
  20         43  
  20         241  
28              
29             our $VERSION = '0.01'; # VERSION
30              
31             our $AUTOLOAD;
32              
33             ############################################################################
34             # Base structure describing the subcommands and their arguments.
35              
36              
37             # Every subcommand requires ctid and has the optional flag of C or C. Though these flags are mutually exclusive,
38             # C will accept both at the same time. Results are undefined when using both flag at the same time. However, this code is
39             # setup to accept only one or the other.
40              
41             # Surrounding a paremeter with square brackets ( [parm] ) will make the parm optional in C.
42              
43             { # Quick, hide in here! And don't make a *sound*!
44              
45             my @vzctl_exports;
46              
47             push @vzctl_exports, 'execute'; # imported from OpenVZ
48              
49             my %vzctl = (
50              
51             destroy => [],
52             mount => [],
53             quotainit => [],
54             quotaoff => [],
55             quotaon => [],
56             restart => [],
57             status => [],
58             stop => [],
59             umount => [],
60             exec => [qw( command )],
61             exec2 => [qw( command )],
62             runscript => [qw( script )],
63             start => [qw( [force] [wait] )],
64             enter => [qw( [exec] )],
65             chkpnt => [qw( [create_dumpfile] )],
66             restore => [qw( [restore_dumpfile] )],
67             create => [qw( [config] [hostname] [ipadd] [ostemplate] [private] [root] )],
68              
69             set => [ qw(
70              
71             [applyconfig] [applyconfig_map] [avnumproc] [bootorder] [capability]
72             [cpulimit] [cpumask] [cpus] [cpuunits] [dcachesize] [devices] [devnodes]
73             [dgramrcvbuf] [disabled] [diskinodes] [diskspace] [features] [force]
74             [hostname] [ioprio] [ipadd] [ipdel] [iptables] [kmemsize] [lockedpages]
75             [name] [nameserver] [netif_add] [netif_del] [noatime] [numfile]
76             [numflock] [numiptent] [numothersock] [numproc] [numpty] [numsiginfo]
77             [numtcpsock] [onboot] [oomguarpages] [othersockbuf] [pci_add] [pci_del]
78             [physpages] [privvmpages] [quotatime] [quotaugidlimit] [save]
79             [searchdomain] [setmode] [shmpages] [swappages] [tcprcvbuf] [tcpsndbuf]
80             [userpasswd] [vmguarpages]
81              
82             ),
83             ],
84              
85             );
86              
87             ####################################
88              
89              
90             push @vzctl_exports, 'known_commands';
91              
92 21     21 1 14065 sub known_commands { return keys %vzctl }
93              
94             ####################################
95              
96              
97             push @vzctl_exports, 'known_options';
98              
99             my $commands_rx = join q{|}, keys %vzctl;
100              
101             sub known_options { ## no critic qw( Subroutines::RequireArgUnpacking )
102              
103             #my @spec; $spec[0] = { type => SCALAR, regex => qr/^$commands_rx$/ };
104 18     18 1 114106 my @spec = ( { type => SCALAR, regex => qr/^$commands_rx$/ } );
105              
106 18         711 my @arg = validate_with( params => \@_, spec => \@spec );
107              
108 18         278 my @options = ( 'flag', 'ctid', @{ $vzctl{ $arg[0] } } );
  18         113  
109              
110 18 50       232 return wantarray ? @options : \@options;
111              
112             }
113              
114             ####################################
115              
116              
117             my @capabilities = qw(
118              
119             chown dac_override dac_read_search fowner fsetid ipc_lock ipc_owner kill
120             lease linux_immutable mknod net_admin net_bind_service net_broadcast
121             net_raw setgid setpcap setuid setveid sys_admin sys_boot sys_chroot
122             sys_module sys_nice sys_pacct sys_ptrace sys_rawio sys_resource sys_time
123             sys_tty_config ve_admin
124              
125             );
126              
127             push @vzctl_exports, 'capabilities';
128              
129 1 50   1 1 3658 sub capabilities { return wantarray ? @capabilities : \@capabilities }
130              
131             ####################################
132              
133              
134             my @iptables_modules = qw(
135              
136             ip_conntrack ip_conntrack_ftp ip_conntrack_irc ip_nat_ftp ip_nat_irc
137             iptable_filter iptable_mangle iptable_nat ipt_conntrack ipt_helper
138             ipt_length ipt_limit ipt_LOG ipt_multiport ipt_owner ipt_recent
139             ipt_REDIRECT ipt_REJECT ipt_state ipt_tcpmss ipt_TCPMSS ipt_tos ipt_TOS
140             ipt_ttl xt_mac
141              
142             );
143              
144             push @vzctl_exports, 'iptables_modules';
145              
146 1 50   1 1 5064 sub iptables_modules { return wantarray ? @iptables_modules : \@iptables_modules }
147              
148             ####################################
149              
150              
151             my @features = qw( sysfs nfs sit ipip ppp ipgre bridge nfsd );
152              
153             push @vzctl_exports, 'features';
154              
155 1 50   1 1 142926 sub features { return wantarray ? @features : \@features }
156              
157             ####################################
158              
159             my %validate = do {
160              
161             my $capability_names = join q{|}, @capabilities;
162             my $iptables_names = join q{|}, @iptables_modules;
163             my $features_names = join q{|}, @features;
164              
165             my %hash = (
166              
167             # XXX: Annoying. Need to submit a bug for this.
168             ## no critic qw( Variables::ProhibitPunctuationVars )
169             avnumproc => { type => SCALAR, regex => qr{^\d+[gmkp]?(?::\d+[gmkp]?)?$}i },
170             bootorder => { type => SCALAR, regex => qr{^\d+$} },
171             capability => { type => SCALAR, regex => qr{^(?:$capability_names):(?:on|off)$}i },
172             cpumask => { type => SCALAR, regex => qr{^\d+(?:[,-]\d+)*|all$}i },
173             ctid => { type => SCALAR, callbacks => { 'validate ctid' => \&_validate_ctid } },
174             devices => { type => SCALAR, regex => qr{^(?:(?:[bc]:\d+:\d+)|all:(?:r?w?))|none$}i },
175             features => { type => SCALAR, regex => qr{^(?:$features_names):(?:on|off)$}i },
176             flag => { type => SCALAR, regex => qr{^quiet|verbose$}i },
177             force => { type => UNDEF },
178             ioprio => { type => SCALAR, regex => qr{^[0-7]$} },
179             onboot => { type => SCALAR, regex => qr{^yes|no$}i },
180             setmode => { type => SCALAR, regex => qr{^restart|ignore$}i },
181             userpasswd => { type => SCALAR, regex => qr{^(?:\w+):(?:\w+)$} },
182             ## use critic
183              
184             applyconfig => { type => SCALAR, callbacks => { 'do not want empty strings' => sub { return $_[0] ne '' }, }, },
185              
186             command => {
187             type => SCALAR | ARRAYREF,
188             callbacks => {
189             'do not want empty values' => sub {
190              
191             return ref $_[0] eq ''
192             ? do { $_[0] ne '' }
193             : do { defined $_[0]->[0] && $_[0]->[0] ne '' };
194              
195             },
196             },
197             },
198              
199             ipadd => {
200             type => SCALAR | ARRAYREF,
201             callbacks => {
202             'do these look like valid ip(s)?' => sub {
203              
204             my @ips = ref $_[0] eq 'ARRAY' ? @{ $_[0] } : $_[0];
205             return unless @ips;
206              
207             # I'd rather not do
208 20     20   25707 no warnings 'uninitialized'; ## no critic qw( TestingAndDebugging::ProhibitNoWarnings )
  20         46  
  20         4370  
209              
210             # but
211             # my @bad_ips = grep { defined && ! /^$RE{net}{IPv4}$/ } @ips;
212             # my @bad_ips = grep { defined $_ && ! /^$RE{net}{IPv4}$/ } @ips;
213             # don't work and I'm not sure what else to try.
214             my @bad_ips = grep { ! /^$RE{net}{IPv4}$/ } @ips;
215             return ! @bad_ips; # return 1 if there are no bad ips, undef otherwise.
216              
217             #NOTE: I can't find a way to modify the incoming data, and it may not
218             # be a good idea to do that in any case. Unless, and until, I can
219             # figure out how to do this the right way this will be an atomic
220             # operation. It's either all good, or it's not.
221              
222             },
223             },
224             },
225              
226             ipdel => {
227             type => SCALAR | ARRAYREF,
228             callbacks => {
229             'do these look like valid ip(s)?' => sub {
230              
231             my @ips = ref $_[0] eq 'ARRAY' ? @{ $_[0] } : $_[0];
232             return unless @ips;
233              
234             # see notes for ipadd
235 20     20   116 no warnings 'uninitialized'; ## no critic qw( TestingAndDebugging::ProhibitNoWarnings )
  20         40  
  20         5077  
236             my @bad_ips = grep { ! /^$RE{net}{IPv4}$/ } @ips;
237             return 1 if any { $_ eq 'all' } @bad_ips;
238             return ! @bad_ips;
239              
240             #NOTE: See ipadd note.
241              
242             },
243             },
244             },
245              
246             iptables => {
247             type => SCALAR | ARRAYREF,
248             callbacks => {
249             'see manpage for list of valid iptables names' => sub {
250              
251             my @names;
252              
253             if ( ref $_[0] eq 'ARRAY' ) {
254              
255             @names = @{ $_[0] };
256             return if @names == 0;
257              
258             } else {
259              
260             return if ! defined $_[0] || $_[0] eq '';
261             my $names = shift;
262             @names = split /\s+/, $names;
263              
264             }
265              
266             # see notes for ipadd
267 20     20   117 no warnings 'uninitialized'; ## no critic qw( TestingAndDebugging::ProhibitNoWarnings )
  20         45  
  20         27494  
268             my @bad_names = grep { ! /^(?:$iptables_names):o(?:n|ff)$/ } @names;
269             return ! @bad_names;
270              
271             #NOTE: See ipadd note.
272              
273             },
274             },
275             },
276              
277             create_dumpfile => {
278             type => SCALAR,
279             callbacks => {
280             'does it look like a valid filename?' => sub {
281             return if $_[0] eq '';
282             my $file = sprintf 'file://localhost/%s', +shift;
283             $file =~ /^$RE{URI}{file}$/;
284             },
285             },
286             },
287              
288             restore_dumpfile => { type => SCALAR, callbacks => { 'does file exist?' => sub { -e ( +shift ) }, }, },
289              
290             devnodes => {
291             type => SCALAR,
292             callbacks => {
293             'setting access to devnode' => sub {
294              
295             return if ! defined $_[0] || $_[0] eq '';
296             return 1 if $_[0] eq 'none';
297             ( my $device = $_[0] ) =~ s/^(.*?):r?w?q?$/$1/;
298             $device = "/dev/$device";
299             return -e $device;
300              
301             },
302             },
303             },
304              
305             );
306              
307             my %same = (
308              
309             # SCALAR checks
310             applyconfig => [ qw(
311              
312             applyconfig_map config hostname name netif_add netif_del ostemplate
313             pci_add pci_del private root searchdomain
314              
315             ),
316             ],
317              
318             #XXX: Need to make 'config', 'ostemplate', 'private' and 'root' more
319             # robust. We can pull the data from the global config file to help
320             # validate this info.
321              
322             # SCALAR | ARRAYREF checks
323             command => [qw( exec script )],
324              
325             # UNDEF checks
326             force => [qw( save wait )],
327              
328             # INT checks
329             bootorder => [qw( cpulimit cpus cpuunits quotatime quotaugidlimit )],
330              
331             # yes or no checks
332             onboot => [qw( disabled noatime )],
333              
334             # ip checks
335             ipadd => [qw( nameserver )],
336              
337             # hard|soft limits
338             avnumproc => [ qw(
339              
340             dcachesize dgramrcvbuf diskinodes diskspace kmemsize lockedpages numfile
341             numflock numiptent numothersock numproc numpty numsiginfo numtcpsock
342             oomguarpages othersockbuf physpages privvmpages shmpages swappages
343             tcprcvbuf tcpsndbuf vmguarpages
344              
345             ),
346             ],
347             );
348              
349             for my $key ( keys %same ) {
350              
351             $hash{ $_ } = $hash{ $key } for @{ $same{ $key } };
352              
353             }
354              
355             %hash;
356              
357             };
358              
359             ############################################################################
360             # Public functions
361              
362             #XXX: Some of these should be extracted out into common module (OpenVZ.pm?)
363              
364             my %global;
365             my $spec = subcommand_specs( qw( flag ctid ) );
366             my $subcommands = join q{|}, sort( known_commands() );
367             $spec->{ subcommand } = { regex => qr/^$subcommands$/ }; ## no critic qw( ValuesAndExpressions::ProhibitAccessOfPrivateData )
368              
369             my %hash = ( command => 'vzctl' );
370              
371             push @vzctl_exports, 'vzctl';
372              
373             sub vzctl { ## no critic qw( Subroutines::RequireArgUnpacking )
374              
375 2478 100   2478 1 44684 shift if blessed $_[0];
376              
377 2478         114580 my %arg = validate_with( params => @_, spec => $spec, allow_extra => 1, );
378              
379 2466         76198 my @params;
380              
381             push @params, ( sprintf '--%s', delete $arg{ flag } )
382 2466 100       31727 if exists $arg{ flag };
383              
384 2466         20662 push @params, delete $arg{ subcommand };
385              
386 2466         9188 delete $arg{ ctid };
387 2466         8376 push @params, $global{ ctid };
388              
389 2466         12975 for my $p ( keys %arg ) {
390              
391             # XXX: Need better way to determine if this is a bare option
392             # maybe '!option' to indicate this option should be bare?
393              
394 2376 100       67033 my $arg_name = $p =~ /^command|script$/ ? '' : "--$p";
395 2376         20542 my $ref = ref $arg{ $p };
396              
397 2376 100       23245 if ( $ref eq 'ARRAY' ) {
    50          
398              
399 48         305 push @params, ( $arg_name, $_ ) for @{ $arg{ $p } };
  48         791  
400              
401             } elsif ( $ref eq '' ) {
402              
403 2328         6653 push @params, $arg_name;
404              
405             # coverage: I don't see a way to test for ! defined $arg{$p}
406             # ... so we'll have to accept a 67% coverage for this one.
407              
408 2328 100 66     47494 push @params, $arg{ $p }
409             if defined $arg{ $p } && $arg{ $p } ne '';
410              
411             } else {
412              
413 0         0 croak "Don't know how to handle ref type $ref for $p";
414              
415             }
416             } ## end for my $p ( keys %arg)
417              
418 2466         21063 @params = grep { $_ ne '' } @params;
  11400         45458  
419              
420 2466         10739 $hash{ params } = \@params;
421              
422 2466         39661 return execute( \%hash );
423              
424             } ## end sub vzctl
425              
426             ####################################
427              
428             push @vzctl_exports, 'subcommand_specs';
429              
430             sub subcommand_specs { ## no critic qw( Subroutines::RequireArgUnpacking )
431              
432 74 50   74 1 231811 shift if blessed $_[0];
433              
434 74         2927 my @args = validate_with( params => \@_, spec => [ { type => SCALAR } ], allow_extra => 1, );
435              
436 74         471 my %spec_hash;
437              
438 74 100 66     2506 if ( defined $subcommands && $args[0] =~ /^$subcommands$/ ) {
439              
440             # then build predefined specification hash
441              
442 54         117 my @specs = @{ $vzctl{ +shift @args } };
  54         301  
443              
444             # Every subcommand has these two at a minimum.
445 54         210 unshift @specs, '[flag]', 'ctid';
446              
447 54         151 for my $spec ( @specs ) {
448              
449 318         1732 my $optional = $spec =~ s/^\[(.*)\]$/$1/;
450              
451 318 50       928 croak "Unknown spec $spec"
452             unless exists $validate{ $spec };
453              
454 318 50   0   1797 next if any { /^-$spec$/ } @args;
  0         0  
455              
456 318         1369 $spec_hash{ $spec } = $validate{ $spec };
457              
458 318 100       1050 $spec_hash{ $spec }{ optional } = 1
459             if $optional;
460              
461             }
462             } ## end if ( defined $subcommands...)
463              
464             # build custom specification hash if any args are left
465              
466 74         250 for my $spec ( @args ) {
467              
468 40 50       135 next if $spec =~ /^-/;
469 40 50       114 next if exists $spec_hash{ $spec };
470              
471 40 50       114 croak "Unknown spec $spec"
472             unless exists $validate{ $spec };
473              
474 40         113 $spec_hash{ $spec } = $validate{ $spec };
475              
476             }
477              
478 74         547 return \%spec_hash;
479              
480             } ## end sub subcommand_specs
481              
482             ############################################################################
483             # Internal Functions
484              
485             #XXX: Should be extracted out into common module (OpenVZ.pm?)
486              
487             # Is the provided ctid a valid container identifier?
488              
489             sub _validate_ctid { ## no critic qw( Subroutines::RequireArgUnpacking )
490              
491 7894 50   7894   171302 shift if blessed $_[0];
492              
493             #my ( $ctid, $params ) = @_;
494 7894         41094 my $check_ctid = shift;
495              
496             {
497 20     20   621 no warnings qw( numeric uninitialized ); ## no critic qw( TestingAndDebugging::ProhibitNoWarnings )
  20         189  
  20         11834  
  7894         29192  
498              
499             # coverage: we can't check against ! exists, so we'll have to live
500             # with a 71% coverage on this one.
501              
502             return 1
503             if ( exists $global{ ctid } && $global{ ctid } == $check_ctid )
504 7894 100 100     462714 || ( exists $global{ name } && $global{ name } eq $check_ctid );
      66        
      66        
505             }
506              
507             # XXX: Need to modify this when vzlist is handled so we keep things
508             # uncluttered.
509              
510 1317         25584 my ( $stdout, $stderr, $syserr ) = execute( { command => 'vzlist', params => [ '-Ho', 'ctid,name', $check_ctid ], } );
511              
512             ## no critic qw( ErrorHandling::RequireUseOfExceptions ValuesAndExpressions::ProhibitMagicNumbers )
513 1317 50       53339569 croak 'vzlist did not execute'
514             if $syserr == -1;
515              
516 1317         10197 $syserr >>= 8;
517              
518 1317 100       133121 croak "Invalid or unknown container ($check_ctid): $stderr"
519             if $syserr == 1;
520             ## use critic
521              
522 261         11360 $stdout =~ s/^\s*(.*?)\s*$/$1/;
523 261         3278 my ( $ctid, $name ) = split /\s+/, $stdout;
524              
525 261         2562 $global{ ctid } = $ctid;
526 261         3668 $global{ name } = $name;
527              
528 261         49757 return 1;
529              
530             } ## end sub _validate_ctid
531              
532             # Generate the code for each of the subcommands
533             # https://metacpan.org/module/Sub::Exporter#Export-Configuration
534              
535             sub _generate_subcommand { ## no critic qw( Subroutines::RequireArgUnpacking )
536              
537 36 50   36   5456 shift if blessed $_[0];
538              
539             #XXX: Need to handle case of calling class using something like
540             #
541             # use OpenVZ::vzctl set => { -as => 'setip', arg => 'ipadd' };
542             #
543             # and creating a sub that only accepts the ipadd parameter.
544              
545             #my ( $class, $name, $arg, $collection ) = @_;
546 36         185 my ( undef, $subcommand ) = @_;
547 36         520 my $subcommand_spec = subcommand_specs( $subcommand );
548              
549 36         70 my %sub_spec;
550              
551 36         96 $sub_spec{ spec } = $subcommand_spec;
552              
553             return sub {
554              
555 7206 100   7206   57745732 shift if blessed $_[0];
556              
557 7206         49867 $sub_spec{ params } = \@_;
558              
559 7206         475615 my %arg = validate_with( %sub_spec );
560 2466         239459 $arg{ subcommand } = $subcommand;
561 2466         23197 vzctl( \%arg );
562              
563 36         373 };
564             } ## end sub _generate_subcommand
565              
566             # for oop stuff
567              
568             # XXX: Do we need/want to support methods for the various options (what is returned from subcommand_specs)?
569              
570             sub AUTOLOAD { ## no critic qw( Subroutines::RequireArgUnpacking ClassHierarchies::ProhibitAutoloading )
571              
572 18 50   18   72228 carp "$_[0] is not an object"
573             unless blessed $_[0];
574              
575 18         387 ( my $subcommand = $AUTOLOAD ) =~ s/^.*:://;
576              
577 18 50       190 carp "$subcommand is not a valid method"
578             unless exists $vzctl{ $subcommand };
579              
580             ## no critic qw( TestingAndDebugging::ProhibitNoStrict References::ProhibitDoubleSigils )
581 20     20   131 no strict 'refs';
  20         42  
  20         4182  
582 18         185 *$AUTOLOAD = _generate_subcommand( undef, $subcommand );
583              
584 18         194 goto &$AUTOLOAD;
585             ## use critic
586              
587             } ## end sub AUTOLOAD
588              
589             # AUTOLOAD assumes DESTROY exists
590 0     0     DESTROY { }
591              
592             push @vzctl_exports, ( $_ => \&_generate_subcommand ) for keys %vzctl;
593              
594             ############################################################################
595             # Setup exporter
596              
597             my $config = {
598              
599             exports => \@vzctl_exports,
600             groups => {},
601             collectors => [],
602              
603             };
604              
605             Sub::Exporter::setup_exporter( $config );
606              
607             } # Ok, they're gone. You can come out now. Guys? Hello?
608              
609             1;
610              
611             __END__