File Coverage

blib/lib/Linux/LVM2.pm
Criterion Covered Total %
statement 11 13 84.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 16 18 88.8


line stmt bran cond sub pod time code
1             package Linux::LVM2;
2             {
3             $Linux::LVM2::VERSION = '0.14';
4             }
5             BEGIN {
6 1     1   26109 $Linux::LVM2::AUTHORITY = 'cpan:TEX';
7             }
8             # ABSTRACT: a Linux LVM2 wrapper.
9              
10 1     1   33 use 5.010_000;
  1         4  
  1         39  
11 1     1   1052 use mro 'c3';
  1         822  
  1         5  
12 1     1   45 use feature ':5.10';
  1         2  
  1         126  
13              
14 1     1   682 use Moose;
  0            
  0            
15             use namespace::autoclean;
16              
17             # use IO::Handle;
18             # use autodie;
19             # use MooseX::Params::Validate;
20              
21             use Carp;
22             use Try::Tiny;
23              
24             use Linux::LVM2::VG;
25             use Linux::LVM2::PV;
26             use Linux::LVM2::LV;
27             use Linux::LVM2::Utils;
28             use Sys::FS;
29             use Sys::Run;
30              
31             has 'vgs' => (
32             'is' => 'ro',
33             'isa' => 'HashRef[Linux::LVM2::VG]',
34             'lazy' => 1,
35             'builder' => '_find_vgs',
36             );
37              
38             has 'verbose' => (
39             'is' => 'rw',
40             'isa' => 'Bool',
41             'default' => 0,
42             );
43              
44             has 'logger' => (
45             'is' => 'ro',
46             'isa' => 'Log::Tree',
47             'required' => 1,
48             );
49              
50             has 'sys' => (
51             'is' => 'rw',
52             'isa' => 'Sys::Run',
53             'lazy' => 1,
54             'builder' => '_init_sys',
55             );
56              
57             has 'fs' => (
58             'is' => 'rw',
59             'isa' => 'Sys::FS',
60             'lazy' => 1,
61             'builder' => '_init_fs',
62             );
63              
64             sub _init_sys {
65             my $self = shift;
66              
67             my $Sys = Sys::Run::->new( { 'logger' => $self->logger(), } );
68              
69             return $Sys;
70             }
71              
72             sub _init_fs {
73             my $self = shift;
74              
75             my $FS = Sys::FS::->new(
76             {
77             'logger' => $self->logger(),
78             'sys' => $self->sys(),
79             }
80             );
81              
82             return $FS;
83             }
84              
85             sub _find_vgs {
86             my $self = shift;
87             my $vg_ref = shift || {};
88              
89             my %sbin = ();
90             $sbin{'vgdisplay'} = '/sbin/vgdisplay';
91             $sbin{'lvdisplay'} = '/sbin/lvdisplay';
92             $sbin{'lvs'} = '/sbin/lvs';
93             $sbin{'pvdisplay'} = '/sbin/pvdisplay';
94              
95             foreach my $key ( keys %sbin ) {
96             if ( !-x $sbin{$key} ) {
97             croak( 'Binary not executeable: ' . $sbin{$key} );
98             }
99             }
100              
101             # read in the command output in a batch,
102             # keep the disabled warnings and output redirect as contained
103             # and brief as possible.
104             my ( @vgdisplay, @lvdisplay, @lvs, @pvdisplay );
105             {
106              
107             # redirect stderr, to get rid of those useless lvm warnings
108             ## no critic (ProhibitTwoArgOpen ProhibitBarewordFileHandles ProhibitNoWarnings RequireCheckedOpen RequireBriefOpen)
109             no warnings 'once';
110             open( OLDSTDERR, '>&STDERR' ) unless $self->verbose();
111             use warnings 'once';
112             open( STDERR, '/dev/null' ) unless $self->verbose();
113             ## use critic
114              
115             ## no critic (RequireCheckedClose ProhibitPunctuationVars)
116             local $ENV{LANG} = q{C};
117             open( my $VGS, '-|', $sbin{'vgdisplay'} . ' -c' )
118             or confess('Could not execute '.$sbin{'vgdisplay'}.'! Is LVM2 installed?: '.$!."\n");
119             @vgdisplay = <$VGS>;
120             close($VGS);
121             open( my $PVS, '-|', $sbin{'pvdisplay'} . ' -c' )
122             or confess('Could not execute '.$sbin{'pvdisplay'}.'! Is LVM2 installed?: '.$!."\n");
123             @pvdisplay = <$PVS>;
124             close($PVS);
125             open( my $LVD, '-|', $sbin{'lvdisplay'} . ' -c' )
126             or confess('Could not execute '.$sbin{'lvdisplay'}.'! Is LVM2 installed?: '.$!."\n");
127             @lvdisplay = <$LVD>;
128             close($LVD);
129             open( my $LVS, '-|', $sbin{'lvs'} . ' --separator=: --units=b' )
130             or confess('Could not execute '.$sbin{'lvs'}.'! Is LVM2 installed?: '.$!."\n");
131             @lvs = <$LVS>;
132             close($LVS);
133             ## use critic
134              
135             ## no critic (ProhibitTwoArgOpen)
136             open( STDERR, '>&OLDSTDERR' ) unless $self->verbose();
137             ## use critic
138             }
139              
140             # Process all VGs
141             foreach my $line (@vgdisplay) {
142             next unless $line;
143             chomp($line);
144             $line =~ s/^\s+//;
145             $line =~ s/\s+$//;
146             next unless $line;
147             my %h;
148             @h{qw(name access status vgid maxlvs curlvs openlvs maxlvsize maxpvs curpvs numpvs vgsize pesize totalpe allocpe freepe uuid)} = split( /:/, $line );
149             $h{'parent'} = $self;
150              
151             # if the object exists, just update it
152             if ( $vg_ref->{ $h{'name'} } && $vg_ref->{ $h{'name'} }->isa('Linux::LVM2::VG') ) {
153              
154             # some attrs are read-only, just update those which are rw
155             foreach my $attr ( keys %h ) {
156             try {
157             $vg_ref->{ $h{'name'} }->$attr( $h{$attr} );
158             };
159             }
160             }
161             else {
162             $vg_ref->{ $h{'name'} } = Linux::LVM2::VG::->new( \%h );
163             }
164             }
165              
166             # Process all PVs
167             foreach my $line (@pvdisplay) {
168             next unless $line;
169             chomp($line);
170             $line =~ s/^\s+//;
171             $line =~ s/\s+$//;
172             next unless $line;
173             my %h;
174             @h{qw(name vg size pesize totalpe freepe allocpe uuid)} = split( /:/, $line );
175             if ( $h{'vg'} && $vg_ref->{ $h{'vg'} } && $vg_ref->{ $h{'vg'} }->isa('Linux::LVM2::VG') ) {
176              
177             if ( $vg_ref->{ $h{'vg'} }->pvs()->{ $h{'name'} } && $vg_ref->{ $h{'vg'} }->pvs()->{ $h{'name'} }->isa('Linux::LVM2::PV') ) {
178             foreach my $attr ( keys %h ) {
179             try {
180             $vg_ref->{ $h{'vg'} }->pvs()->{ $h{'name'} }->$attr( $h{$attr} );
181             };
182             }
183             }
184             else {
185             $h{'vg'} = $vg_ref->{ $h{'vg'} };
186             my $PV = Linux::LVM2::PV::->new( \%h );
187              
188             # no need to do anything with $PV, its constructor will attach itself
189             # to the vg passed it will be reachable via the associated VG
190             }
191             }
192             else {
193             next;
194             }
195              
196             }
197              
198             # Process each LV (from lvdisplay)
199             foreach my $line (@lvdisplay) {
200             next unless $line;
201             chomp($line);
202             $line =~ s/^\s+//;
203             $line =~ s/\s+$//;
204             next unless $line;
205             my %h;
206             @h{qw(name vg access status intlvnum opencount lvsize leassoc lealloc allocpol rasect majornum minornum)} = split( /:/, $line );
207             $h{'name'} =~ s#^/dev/##;
208             $h{'name'} =~ s#^$h{'vg'}/##;
209              
210             if ( $h{'vg'} && $vg_ref->{ $h{'vg'} } ) {
211             if ( $vg_ref->{ $h{'vg'} }->lvs()->{ $h{'name'} } && $vg_ref->{ $h{'vg'} }->lvs()->{ $h{'name'} }->isa('Linux::LVM2::LV') ) {
212             foreach my $attr ( keys %h ) {
213              
214             # some attrs are read-only, just update those which are rw
215             try {
216             $vg_ref->{ $h{'vg'} }->lvs()->{ $h{'name'} }->$attr( $h{$attr} );
217             };
218             }
219             }
220             else {
221             $h{'vg'} = $vg_ref->{ $h{'vg'} };
222             my $VG = Linux::LVM2::LV::->new( \%h );
223              
224             # no need to do anything with $VG, its constructor will attach itself
225             # to the vg passed it will be reachable via the associated VG
226             }
227             }
228             else {
229             next;
230             }
231             }
232              
233             # Try to get mount points for mounted devices
234             my $mounts = $self->fs()->mounts( { 'DevAsKey' => 1, } );
235              
236             # Process each LV (from LVs, provides additional information about Snapshot and Copy Progress)
237             foreach my $line (@lvs) {
238             next unless $line;
239             next if $line =~ m/\s*LV:VG:Attr/;
240             chomp($line);
241             $line =~ s/^\s+//;
242             $line =~ s/\s+$//;
243             next unless $line;
244             my %h;
245             @h{qw(name vg attr lsize origin snap_pc move log copy_pc convert)} = split( /:/, $line );
246              
247             if ( $h{'vg'} && $h{'name'} && $vg_ref->{ $h{'vg'} } && $vg_ref->{ $h{'vg'} }->lvs()->{ $h{'name'} } ) {
248             my $lv = $vg_ref->{ $h{'vg'} }->lvs()->{ $h{'name'} };
249              
250             # convert percent
251             foreach my $key (qw(snap_pc copy_pc)) {
252             $h{$key} ||= 0;
253             $h{$key} = int( $h{$key} * 100 );
254             }
255              
256             # set parameters
257             foreach my $attr (qw(snap_pc move log copy_pc convert)) {
258             try {
259             $h{$attr} ||= q{};
260             $lv->$attr( $h{$attr} );
261             };
262             }
263              
264             # set origin of the new LV
265             if ( $h{'origin'} && $vg_ref->{ $h{'vg'} }->lvs()->{ $h{'origin'} } && $vg_ref->{ $h{'vg'} }->lvs()->{ $h{'origin'} }->isa('Linux::LVM2::LV') ) {
266             $lv->origin( $vg_ref->{ $h{'vg'} }->lvs()->{ $h{'origin'} } );
267             }
268             elsif ( $h{'origin'} ) {
269             confess("Did not find origin ($h{'origin'}) of LV $h{'name'}. This is impossible!");
270             }
271              
272             # set mount point
273             if ( $mounts->{ $lv->mapper_path() } ) {
274             $lv->mount_point( $mounts->{ $lv->mapper_path() }{'mount_point'} );
275             }
276             elsif ( $mounts->{ $lv->full_path() } ) {
277             $lv->mount_point( $mounts->{ $lv->full_path() }{'mount_point'} );
278             }
279             else {
280             warn 'No mount point found for LV ' . $lv->full_path() . "\n" if $self->verbose();
281             }
282             }
283             }
284              
285             return $vg_ref;
286             }
287              
288             sub is_lv {
289             my $self = shift;
290             my $vg_name = shift;
291             my $lv_name = shift;
292              
293             foreach my $vg ( keys %{ $self->vgs() } ) {
294             next if $vg_name && $vg ne $vg_name;
295             if ( $self->vgs()->{$vg}->lvs()->{$lv_name} ) {
296             return 1;
297             }
298             }
299             return;
300             }
301              
302             sub is_vg {
303             my $self = shift;
304             my $vg_name = shift;
305              
306             if ( $self->vgs()->{$vg_name} ) {
307             return 1;
308             }
309             else {
310             return;
311             }
312             }
313              
314             sub lv_from_path {
315             my $self = shift;
316             my $path = shift;
317              
318             # find out which device $path is located
319             my ( $device, $fs_type, $fs_options, $mount_point ) = $self->fs()->get_mounted_device($path);
320              
321             my $LV = $self->lv_from_dev($device);
322             if ($LV) {
323             $LV->fs_type($fs_type);
324             $LV->fs_options($fs_options);
325             $LV->mount_point($mount_point);
326             return $LV;
327             }
328             else {
329             carp "Did not find lv from given path $path\n" if $self->verbose();
330             }
331             return;
332             }
333              
334             sub lv_from_dev {
335             my $self = shift;
336             my $dev = shift;
337              
338             if ( $dev =~ m#/mapper/# ) {
339             warn "Trying to translate dev-mapper name to lvm name $dev\n" if $self->verbose();
340             $dev = Linux::LVM2::Utils::translate_lvm_name($dev);
341             warn "Translated to $dev\n" if $self->verbose();
342             }
343             $dev =~ s#^/dev/##;
344             $dev =~ s#/$##;
345              
346             my ( $vg, $lv ) = split /\//, $dev;
347              
348             if ( $vg && $lv && $self->is_lv( $vg, $lv ) ) {
349             return $self->vgs()->{$vg}->lvs()->{$lv};
350             }
351              
352             # no lv found
353             return;
354             }
355              
356             sub update {
357             my $self = shift;
358             $self->_find_vgs( $self->vgs() );
359             return 1;
360             }
361              
362             no Moose;
363             __PACKAGE__->meta->make_immutable;
364              
365             1;
366              
367             __END__
368              
369             =pod
370              
371             =encoding utf-8
372              
373             =head1 NAME
374              
375             Linux::LVM2 - a Linux LVM2 wrapper.
376              
377             =head1 SYNOPSIS
378              
379             use Linux::LVM2;
380             my $LVM = Linux::LVM2::->new();
381              
382             =head1 DESCRIPTION
383              
384             This class wraps the Linux LVM2 subsystem into handy perl classes.
385              
386             =head1 ATTRIBUTES
387              
388             =head2 vgs
389              
390             Contains all VGs present at the last update.
391              
392             =head2 verbose
393              
394             When true, be more verbose.
395              
396             =head2 logger
397              
398             An instance of Log::Tree.
399              
400             =head2 sys
401              
402             An instance of Sys::Run.
403              
404             =head2 fs
405              
406             An instance of Sys::FS.
407              
408             =head1 METHODS
409              
410             =head2 _find_vgs
411              
412             Detect all available VGs w/ containing PVs and contained LVs.
413              
414             =head2 is_lv
415              
416             Returns true if the given vg/lv is a known LV.
417              
418             =head2 is_vg
419              
420             Returns true if the given vg is a known VG.
421              
422             =head2 lv_from_dev
423              
424             Translate the given /dev/mapper/... path to a LV object.
425              
426             =head2 lv_from_path
427              
428             Translate the given fs path to a LV object.
429              
430             =head2 update
431              
432             Update the internal LVM data-structures.
433              
434             =head1 NAME
435              
436             Linux::LVM2 - Linux LVM2 wrapper.
437              
438             =head1 AUTHOR
439              
440             Dominik Schulz <dominik.schulz@gauner.org>
441              
442             =head1 COPYRIGHT AND LICENSE
443              
444             This software is copyright (c) 2012 by Dominik Schulz.
445              
446             This is free software; you can redistribute it and/or modify it under
447             the same terms as the Perl 5 programming language system itself.
448              
449             =cut