The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#
# (c) Jan Gehring <jan.gehring@gmail.com>
#
# vim: set ts=2 sw=2 tw=0:
# vim: set expandtab:

=head1 NAME

Rex::Commands::LVM - Get LVM Information

=head1 DESCRIPTION

With this module you can get information of your lvm setup.

Version <= 1.0: All these functions will not be reported.

All these functions are not idempotent.

=head1 SYNOPSIS

 use Rex::Commands::LVM;
 
 my @physical_devices = pvs;
 my @volume_groups = vgs;
 my @logical_volumes = lvs;



=head1 EXPORTED FUNCTIONS

=over 4

=cut

package Rex::Commands::LVM;

use strict;
use warnings;

require Rex::Exporter;
use base qw(Rex::Exporter);
use vars qw(@EXPORT);

@EXPORT = qw(pvs vgs lvs pvcreate vgcreate lvcreate vgextend);

use Rex::Commands::Run;

=item pvs

Get Information of all your physical volumes.

 use Data::Dumper;
 use Rex::Commands::LVM;
 
 task "lvm", sub {
   my @physical_volumes = pvs;
 
   for my $physical_volume (@physical_volumes) {
     say Dumper($physical_volume);
   }
 };

=cut

sub pvs {

  my @lines = run 'pvdisplay --units b --columns --separator "|" --noheadings';
  if ( $? != 0 ) {
    die("Error running pvdisplay");
  }

  my @ret;
  for my $line (@lines) {
    chomp $line;
    $line =~ s/^\s+//g;
    my ( $phy_vol, $vol_group, $format, $attr, $psize, $pfree ) =
      split( /\|/, $line );
    $pfree =~ s/B$//;
    $psize =~ s/B$//;

    push(
      @ret,
      {
        physical_volume => $phy_vol,
        volume_group    => $vol_group,
        format          => $format,
        attributes      => $attr,
        size            => $psize,
        free            => $pfree,
      }
    );
  }

  return @ret;

}

=item vgs

Get Information of all your volume groups.

 use Data::Dumper;
 use Rex::Commands::LVM;
 
 task "lvm", sub {
   my @volume_groups = vgs;
 
   for my $volume_group (@volume_groups) {
     say Dumper($volume_group);
   }
 };

=cut

sub vgs {

  my ($vg) = @_;

  my $cmd =
    'vgdisplay --units b --columns --separator "|" --noheadings -o "pv_name,vg_name,vg_size,vg_free,vg_attr"';
  if ($vg) {
    $cmd .= " $vg";
  }

  my @lines = run $cmd;
  if ( $? != 0 ) {
    die("Error running vgdisplay");
  }

  my @ret;
  for my $line (@lines) {
    chomp $line;
    $line =~ s/^\s+//g;
    my ( $pv_name, $vg_name, $vg_size, $vg_free, $vg_attr ) =
      split( /\|/, $line );
    $vg_free =~ s/B$//;
    $vg_size =~ s/B$//;

    push(
      @ret,
      {
        physical_volume => $pv_name,
        volume_group    => $vg_name,
        size            => $vg_size,
        free            => $vg_free,
        attributes      => $vg_attr,
      }
    );
  }

  return @ret;

}

=item lvs

Get Information of all your logical volumes.

 use Data::Dumper;
 use Rex::Commands::LVM;
 
 task "lvm", sub {
   my @logical_volumes = lvs;
 
   for my $logical_volume (@logical_volumes) {
     say Dumper($logical_volume);
   }
 };

=cut

sub lvs {

  my ($vg) = @_;

  my $cmd =
    'lvdisplay --units b --columns --separator "|" -o "lv_name,vg_name,lv_attr,lv_size" --noheading';
  if ($vg) {
    $cmd .= " " . $vg;
  }

  my @lines = run $cmd;
  if ( $? != 0 ) {
    die("Error running lvdisplay");
  }

  my @ret;
  for my $line (@lines) {
    chomp $line;
    $line =~ s/^\s+//g;

    my ( $lv_name, $vg_name, $lv_attr, $lv_size ) = split( /\|/, $line );
    $lv_size =~ s/B$//;
    push(
      @ret,
      {
        name       => $lv_name,
        path       => "/dev/$vg_name/$lv_name",
        attributes => $lv_attr,
        size       => $lv_size,
      }
    );
  }

  return @ret;
}

sub pvcreate {
  my ($dev) = @_;
  my $s = run "pvcreate $dev";
  if ( $? != 0 ) {
    die("Error creating pv.\n$s\n");
  }

  return 1;
}

sub vgcreate {
  my ( $vgname, @devices ) = @_;

  my $s = run "vgcreate $vgname " . join( " ", @devices );
  if ( $? != 0 ) {
    die("Error creating vg.\n$s\n");
  }

  return 1;
}

sub lvcreate {
  my ( $lvname, %option ) = @_;

  if ( !exists $option{size} || !exists $option{onvg} ) {
    die("Missing parameter size or onvg.");
  }

  unless ( $lvname =~ m/^[a-z0-9\-_]+$/i ) {
    die("Error in lvname. Allowed characters a-z, 0-9 and _- .");
  }

  my $size = $option{size};
  if ( $size =~ m/^[0-9]+$/ ) { $size .= "M"; }
  my $onvg = $option{onvg};

  my $s = run "lvcreate -n $lvname -L $size $onvg";

  my $lv_path = $option{onvg} . "/" . $lvname;

  if ( exists $option{fstype} ) {
    if ( can_run("mkfs.$option{fstype}") ) {
      Rex::Logger::info("Creating filesystem $option{fstype} on /dev/$lv_path");
      run "mkfs.$option{fstype} /dev/$lv_path";
    }
    elsif ( $option{fstype} eq "swap" ) {
      Rex::Logger::info("Creating swap space on /dev/$lv_path");
      run "mkswap -f /dev/$lv_path";
    }
    else {
      die("Can't format partition with $option{fstype}");
    }
  }

  if ( $? != 0 ) {
    die("Error creating lv.\n$s\n");
  }

  return $lv_path;
}

sub vgextend {
  my ( $vgname, @devices ) = @_;

  my $s = run "vgextend $vgname " . join( " ", @devices );

  if ( $? != 0 ) {
    die("Error extending vg.\n$s\n");
  }

  return 1;
}

=back

=cut

1;