File Coverage

blib/lib/Linux/Virt/Plugin/Vserver.pm
Criterion Covered Total %
statement 28 224 12.5
branch 0 84 0.0
condition 0 30 0.0
subroutine 10 20 50.0
pod n/a
total 38 358 10.6


line stmt bran cond sub pod time code
1             package Linux::Virt::Plugin::Vserver;
2             {
3             $Linux::Virt::Plugin::Vserver::VERSION = '0.15';
4             }
5             BEGIN {
6 1     1   3013 $Linux::Virt::Plugin::Vserver::AUTHORITY = 'cpan:TEX';
7             }
8             # ABSTRACT: Linux-Vserver plugin for Linux::Virt
9              
10 1     1   36 use 5.010_000;
  1         4  
  1         48  
11 1     1   8 use mro 'c3';
  1         3  
  1         12  
12 1     1   40 use feature ':5.10';
  1         4  
  1         169  
13              
14 1     1   8 use Moose;
  1         2  
  1         11  
15 1     1   20329 use namespace::autoclean;
  1         4  
  1         12  
16              
17             # use IO::Handle;
18             # use autodie;
19             # use MooseX::Params::Validate;
20              
21 1     1   80 use Carp;
  1         2  
  1         93  
22 1     1   4569 use English '-no_match_vars';
  1         3073  
  1         6  
23 1     1   617 use File::Blarf;
  1         2  
  1         3942  
24              
25             extends 'Linux::Virt::Plugin';
26              
27 0     0     sub _init_priority { return 10; }
28              
29             # Check if this is a Vserver/VE
30             # undef - no vserver
31             # 1 - Linux-Vserver
32             sub is_vm {
33 0     0     my $self = shift;
34              
35             # Linux-Vserver
36 0 0         if ( open( my $FH, '<', "/proc/self/status" ) ) {
37 0           local $/ = undef;
38 0           my $proc = $FH;
39 0           close($FH);
40 0 0         if ( $proc =~ m/(s_context|VxID):\s+(\d+)/ ) {
41 0           my $cid = $2;
42 0 0         if ($cid) {
43              
44             # Ok, this is a Linux-Vserver
45 0           return 1;
46             }
47             } ## end if ( $proc =~ m/(s_context|VxID):\s+(\d+)/)
48             } ## end if ( open( my $FH, '<'...))
49              
50             # No Vserver
51 0           return;
52             } ## end sub is_vm
53              
54             # Check if this is a Linux-Vserver Host (i.e. running an vserver-enabled kernel)
55             # undef - no vserver
56             # 1 - Linux Vserver
57             sub is_host {
58 0     0     my $self = shift;
59              
60             # Linux-Vserver
61 0 0         if ( open( my $FH, '<', "/proc/self/status" ) ) {
62 0           local $/ = undef;
63 0           my $proc = <$FH>;
64 0           close($FH);
65 0 0         if ( $proc =~ m/(s_context|VxID):\s+(\d+)/ ) {
66 0           my $cid = $2;
67 0 0 0       if ( $cid && $cid == 0 ) {
68 0           return 1;
69             }
70             } ## end if ( $proc =~ m/(s_context|VxID):\s+(\d+)/)
71             } ## end if ( open( my $FH, '<'...))
72              
73             # No Vserver-enabled host
74 0           return;
75             } ## end sub is_host
76              
77             sub _get_arch_by_ctx {
78 0     0     my $self = shift;
79 0           my $ctx = shift;
80              
81 0           my $ctx_file = "/proc/virtual/$ctx/nsproxy";
82 0 0 0       if ( -f $ctx_file && open( my $FH, "<", $ctx_file ) ) {
83 0           while ( my $line = <$FH> ) {
84 0 0         if ( $line =~ m/^Machine:\s*(\S+)\s*$/ ) {
85 0           my $arch = $1;
86 0           close($FH);
87 0           return $arch;
88             }
89             } ## end while ( my $line = <$FH> )
90 0           close($FH);
91             } ## end if ( -f $ctx_file && open...)
92 0           return;
93             } ## end sub _get_arch_by_ctx
94              
95             # We can't rely on the names reported by
96             # vserver-stat, they are often truncated,
97             # so we'll find the name by the (possible)
98             # dynamic context id in /etc/vserver/<vs>/run
99             sub _get_vs_name_by_ctx {
100 0     0     my $self = shift;
101 0           my $ctx = shift;
102              
103 0           my $name = undef;
104              
105 0           my $basedir = "/etc/vservers";
106 0 0         if ( opendir( my $DH, $basedir ) ) {
107 0           while ( my $vs_name = readdir($DH) ) {
108 0           my $entry = "$basedir/$vs_name";
109              
110             # skip non-dirs
111 0 0         next if ( !-d $entry );
112              
113             # skip self and parent
114 0 0         next if $entry =~ m/\.\.?/;
115 0           my $ctx_file = "$entry/run";
116 0 0         if ( -f $ctx_file ) {
117 0           my $ctx_test = &File::Blarf::slurp( $ctx_file, { Chomp => 1, } );
118 0           $ctx_test =~ s/^\s+//;
119 0           $ctx_test =~ s/\s+$//;
120 0 0         if ( $ctx eq $ctx_test ) {
121 0           closedir($DH);
122 0           return $vs_name;
123             }
124             } ## end if ( -f $ctx_file )
125             } ## end while ( my $vs_name = readdir...)
126 0           closedir($DH);
127             } ## end if ( opendir( my $DH, ...))
128 0           return;
129             } ## end sub _get_vs_name_by_ctx
130              
131             sub vms {
132 0     0     my $self = shift;
133 0           my $vserver_ref = shift;
134 0   0       my $opts = shift || {};
135 0           local $ENV{LANG} = "C";
136 0           my $VSS;
137 0 0         if ( !open( $VSS, '-|', "/usr/sbin/vserver-stat" ) ) {
138 0           my $msg = "Could not execute /usr/sbin/vserver-stat! Is util-vserver installed?: $!";
139 0           $self->logger()->log( message => $msg, level => 'error', );
140 0           return;
141             }
142 0           while ( my $line = <$VSS> ) {
143 0 0         next if $line =~ m/^CTX\s+PROC\s+VSZ/;
144 0           $line =~ s/^\s+//;
145 0           $line =~ s/\s+$//;
146 0           my ( $ctx, $proc, $vsz, $rss, $usertime, $systime, $uptime, $name ) = split( /\s+/, $line );
147 0   0       $name = $self->_get_vs_name_by_ctx($ctx) || $name;
148 0           $vserver_ref->{$name}{'ctx'} = $ctx;
149 0           $vserver_ref->{$name}{'proc'} = $proc;
150 0           $vserver_ref->{$name}{'vsz'} = $vsz;
151 0           $vserver_ref->{$name}{'rss'} = $rss;
152 0           $vserver_ref->{$name}{'usertime'} = $usertime;
153 0           $vserver_ref->{$name}{'systime'} = $systime;
154 0           $vserver_ref->{$name}{'uptime'} = $uptime;
155 0           $vserver_ref->{$name}{'name'} = $name;
156              
157             } ## end while ( my $line = <$VSS>)
158 0           close($VSS);
159              
160             # post-process all vservers, gather remaining information
161             # - get init status (apps/init/mark -> default?)
162             # - get utsnodename
163             # - get caps
164             # - get limits
165             # - get ips
166 0           foreach my $name ( keys %{$vserver_ref} ) {
  0            
167              
168             # this is a linux-vserver
169 0           $vserver_ref->{$name}{'virt'}{'type'} = 'vserver';
170              
171             # read arch from nsproxy
172 0           $vserver_ref->{$name}{'virt'}{'arch'} = $self->_get_arch_by_ctx( $vserver_ref->{$name}{'ctx'} );
173              
174             # get vdir from /etc/vservers/<vs>/vdir symlink
175 0           my $vdir = "/etc/vservers/$name/vdir";
176 0 0         if ( -l $vdir ) {
177 0           $vserver_ref->{$name}{'vdir'} = readlink($vdir);
178             }
179              
180             # all spaces may be reported in GB or MB, so convert if needed
181             # convert GB to MB
182 0           foreach my $prop (qw(vsz rss)) {
183              
184             # remove trailing MB indicator
185 0           $vserver_ref->{$name}{$prop} =~ s/m$//i;
186 0 0         if ( $vserver_ref->{$name}{$prop} =~ m/g$/i ) {
187 0           $vserver_ref->{$name}{$prop} =~ s/g$//i;
188 0           $vserver_ref->{$name}{$prop} *= 1024;
189             }
190             } ## end foreach my $prop (qw(vsz rss))
191              
192             # all time are reported as XdYhZ or XhYmZ or XmYsZ
193             # convert all to seconds
194 0           foreach my $prop (qw(usertime systime uptime)) {
195 0           my ( $day, $hour, $minute, $second, $ms );
196 0 0         if ( $vserver_ref->{$name}{$prop} =~ m/^(\d+)d0?(\d+)h0?(\d+)$/i ) {
    0          
    0          
197 0           ( $day, $hour, $minute, $second, $ms ) = ( $1, $2, $3, 0, 0 );
198             }
199             elsif ( $vserver_ref->{$name}{$prop} =~ m/^(\d+)h0?(\d+)m0?(\d+)$/i ) {
200 0           ( $day, $hour, $minute, $second, $ms ) = ( 0, $1, $2, $3, 0 );
201             }
202             elsif ( $vserver_ref->{$name}{$prop} =~ m/^(\d+)m0?(\d+)s0?(\d+)$/i ) {
203 0           ( $day, $hour, $minute, $second, $ms ) = ( 0, 0, $1, $2, $3 );
204             }
205             else {
206 0           ( $day, $hour, $minute, $second, $ms ) = ( 0, 0, 0, 0, 0 );
207             }
208 0           $vserver_ref->{$name}{$prop} = $second + $minute * 60 + $hour * 60 * 60 + $day * 60 * 60 * 24;
209             } ## end foreach my $prop (qw(usertime systime uptime))
210              
211             # - get init status (apps/init/mark -> default?)
212 0           $vserver_ref->{$name}{'init'} = 0;
213 0           my $init_file = "/etc/vservers/$name/apps/init/mark";
214 0 0         if ( -e $init_file ) {
215 0 0         if ( open( my $FH, "<", $init_file ) ) {
216 0           my $mark = <$FH>;
217 0           close($FH);
218 0           chomp($mark);
219 0 0         if ( $mark eq 'default' ) {
220 0           $vserver_ref->{$name}{'init'} = 1;
221             }
222             } ## end if ( open( my $FH, "<"...))
223             } ## end if ( -e $init_file )
224              
225             # - get utsnodename
226 0           $vserver_ref->{$name}{'utsnodename'} = $name;
227 0           my $uts_nodename_file = "/etc/vservers/$name/uts/nodename";
228 0 0         if ( -e $uts_nodename_file ) {
229 0 0         if ( open( my $FH, "<", $uts_nodename_file ) ) {
230 0           my $uts_nodename = <$FH>;
231 0           close($FH);
232 0           chomp($uts_nodename);
233 0           $vserver_ref->{$name}{'utsnodename'} = $uts_nodename;
234             } ## end if ( open( my $FH, "<"...))
235             } ## end if ( -e $uts_nodename_file)
236              
237             # - get caps
238             # see http://www.linux-vserver.org/Capabilities_and_Flags
239             # and http://www.linux-vserver.org/util-vserver:Capabilities_and_Flags
240 0           foreach my $cap_name (qw(ccapabilities flags nflags bcapabilities ncapabilities)) {
241 0           $vserver_ref->{$name}{$cap_name} = undef;
242 0           my $caps_file = "/etc/vservers/$name/$cap_name";
243 0 0         if ( -e $caps_file ) {
244 0 0         if ( open( my $FH, "<", $caps_file ) ) {
245 0           while ( my $cap = <$FH> ) {
246 0           chomp($cap);
247 0           $vserver_ref->{$name}{$cap_name}{$cap} = 1;
248             }
249 0           close($FH);
250             } ## end if ( open( my $FH, "<"...))
251             } ## end if ( -e $caps_file )
252             } ## end foreach my $cap_name (qw(ccapabilities flags nflags bcapabilities ncapabilities))
253              
254             # - get limits
255 0           my $limits_file = "/proc/virtual/" . $vserver_ref->{$name}{'ctx'} . "/limit";
256 0 0         if ( -e $limits_file ) {
257 0 0         if ( open( my $FH, "<", $limits_file ) ) {
258 0           while ( my $line = <$FH> ) {
259 0           chomp($line);
260              
261             # table header
262 0 0         next if $line =~ m/^Limit\s+/i;
263 0           my ( $res, $current, $min, $max, $soft, $hard, $hits ) = split( /[\s\/]+/, $line );
264              
265             # remove trailing ':'
266 0           $res =~ s/:$//;
267              
268             # remove trailing '/'
269 0           $min =~ s#/$##;
270 0           $soft =~ s#/$##;
271              
272             # skip resources w/o limit
273 0 0 0       next if ( $min == -1 && $max == -1 );
274              
275 0           $vserver_ref->{$name}{'limits'}{$res}{'current'} = $current;
276 0           $vserver_ref->{$name}{'limits'}{$res}{'min'} = $min;
277 0           $vserver_ref->{$name}{'limits'}{$res}{'max'} = $max;
278 0           $vserver_ref->{$name}{'limits'}{$res}{'soft'} = $soft;
279 0           $vserver_ref->{$name}{'limits'}{$res}{'hard'} = $hard;
280 0           $vserver_ref->{$name}{'limits'}{$res}{'hits'} = $hits;
281             } ## end while ( my $line = <$FH> )
282 0           close($FH);
283             } ## end if ( open( my $FH, "<"...))
284             } ## end if ( -e $limits_file )
285              
286             # - get ips
287             # any interfaces defined at all?
288 0           my $if_dir = '/etc/vservers/'.$name.'/interfaces';
289 0 0         if ( -d $if_dir ) {
290 0 0         if ( opendir( my $DH, $if_dir ) ) {
291 0           while ( my $dir_entry = readdir($DH) ) {
292 0 0         next if $dir_entry =~ m/\.\.?/;
293 0           my $dir = "$if_dir/$dir_entry";
294 0           my $ip_file = "$dir/ip";
295 0           my $dev_file = "$dir/dev";
296 0           my $prefix_file = "$dir/prefix";
297 0           my ( $ip, $dev, $prefix );
298 0 0         if ( -f $ip_file ) {
299 0           $ip = File::Blarf::slurp( $ip_file, { Chomp => 1, } );
300             }
301 0 0         if ( -f $dev_file ) {
302 0           $dev = File::Blarf::slurp( $dev_file, { Chomp => 1, } );
303             }
304 0 0         if ( -f $prefix_file ) {
305 0           $prefix = File::Blarf::slurp( $prefix_file, { Chomp => 1, } );
306             }
307 0 0 0       if ( $ip && $dev && $prefix ) {
      0        
308 0           my $key = $self->_get_ip_hash_key( $ip, $prefix, $dev );
309 0           $vserver_ref->{$name}{'ips'}{$key}{'ip'} = $ip;
310 0           $vserver_ref->{$name}{'ips'}{$key}{'prefix'} = $prefix;
311 0           $vserver_ref->{$name}{'ips'}{$key}{'dev'} = $dev;
312             } ## end if ( $ip && $dev && $prefix)
313             } ## end while ( my $dir_entry = readdir...)
314 0           closedir($DH);
315             } ## end if ( opendir( my $DH, ...))
316             } ## end if ( -d $if_dir )
317             } ## end foreach my $name ( keys %{$vserver_ref...})
318              
319 0           return 1;
320             } ## end sub vms
321              
322             sub _get_ip_hash_key {
323 0     0     my $self = shift;
324 0   0       my $ip = shift // '';
325 0   0       my $prefix = shift // '';
326 0   0       my $dev = shift // '';
327              
328 0           return $ip . '/' . $prefix . 'dev' . $dev;
329             } ## end sub _get_ip_hash_key
330              
331             sub is_running {
332 0     0     my $self = shift;
333 0           my $vsname = shift;
334              
335             # remove domain part
336 0           $vsname =~ s/\.[a-z0-9]$//i;
337              
338 0           my $vs_ref = {};
339              
340 0           $self->vms($vs_ref);
341              
342 0 0         if ( $vs_ref->{$vsname} ) {
343 0           return 1;
344             }
345             else {
346 0           return;
347             }
348             } ## end sub is_running
349              
350             sub start {
351 0     0     my $self = shift;
352 0           my $vsname = shift;
353 0   0       my $opts = shift || {};
354              
355 0           my $cmd = "/usr/sbin/vserver $vsname start >/dev/null 2>&1";
356 0           $self->sys()->run_cmd( $cmd, $opts );
357              
358 0 0         if ( !$self->is_running($vsname) ) {
359 0           sleep(120);
360             }
361              
362 0           return $self->is_running($vsname);
363             } ## end sub start
364              
365             sub stop {
366 0     0     my $self = shift;
367 0           my $vsname = shift;
368 0   0       my $opts = shift || {};
369              
370 0           my $cmd = "/usr/sbin/vserver $vsname stop >/dev/null 2>&1";
371              
372 0           my $max_tries = 10;
373              
374 0           foreach my $try ( 1 .. $max_tries ) {
375 0           $self->sys()->run_cmd( $cmd, $opts );
376 0 0         last if ( !$self->is_running($vsname) );
377 0           sleep 30;
378             }
379              
380             # vserver $vsname stop
381 0 0         if ( $self->is_running($vsname) ) {
382 0           my $msg = "Could not stop Vserver! Aborting.";
383 0           return;
384             }
385             else {
386 0           return 1;
387             }
388             } ## end sub stop
389              
390 1     1   14 no Moose;
  1         2  
  1         15  
391             __PACKAGE__->meta->make_immutable;
392              
393             1;
394              
395             __END__
396              
397             =pod
398              
399             =encoding utf-8
400              
401             =head1 NAME
402              
403             Linux::Virt::Plugin::Vserver - Linux-Vserver plugin for Linux::Virt
404              
405             =head1 METHODS
406              
407             =head2 is_host
408              
409             Returns a true value if this is run on a vserver host.
410              
411             =head2 is_running
412              
413             Returns a true value if the given vserver is currently running on the
414             local host.
415              
416             =head2 is_vm
417              
418             Returns a true value if this is run inside a vserver.
419              
420             =head2 start
421              
422             Start the given vserver.
423              
424             =head2 stop
425              
426             Stop the given vserver.
427              
428             =head2 vms
429              
430             List all running VMs.
431              
432             =head1 NAME
433              
434             Linux::Virt::Plugin::Vserver - Linux Vserver Plugin for Linux::Virt
435              
436             =head1 AUTHOR
437              
438             Dominik Schulz <dominik.schulz@gauner.org>
439              
440             =head1 COPYRIGHT AND LICENSE
441              
442             This software is copyright (c) 2012 by Dominik Schulz.
443              
444             This is free software; you can redistribute it and/or modify it under
445             the same terms as the Perl 5 programming language system itself.
446              
447             =cut