#! /usr/bin/perl -w
# Parse XEN configuration file to find LVM disk partitions used by the 
# VM, and how big they are, and turn those into commands to make a single
# logical volume that can be partitioned to hold those partitions.
# (Needed by KVM, which wants to boot from a single disk with a partition
# table, unlike the approach we've used with XEN of one LV per partition,
# and no partition table.)
#
# Written by Ewen McNeill, Naos Ltd <ewen@naos.co.nz>, 2010-11-14
#---------------------------------------------------------------------------

use strict;
use Data::Dumper;

my $VG_NAME  = "/dev/r1";         # Volume Group on which to create LVs
my $MAP_NAME = "/dev/mapper/r1";  # Prefix where LVs appear

# Get LV size in bytes (assuming GB = 1024*1024*1024; MB = 1024*1024)
sub get_lv_size {
  my ($lv_path) = @_;
  my @lv_info   = `/usr/bin/sudo /sbin/lvdisplay ${lv_path}`;
  my ($lv_size) = grep { /LV Size/ } @lv_info;
  if (defined($lv_size) && $lv_size =~ /LV Size\D*([0-9\.]+) ([MG])B/) {
    my $multipler = 1024 * 1024 * ($2 eq 'G' ? 1024 : 1);
    return $1 * $multipler;
  }

  return undef;    # Not found
}

sub ceil {
  ((int($_[0]) == $_[0]) ? $_[0] : int(1 + $_[0]));
}

my %disk;
my $vm_name;

# Identify disk partitions
while (<>) {
  if (/^name = "(.*)"/) {
     $vm_name = $1;
     next;
  }
  next unless (/^disk =\s+\[(.+)\]/);

  my $disk_list = $1;
     $disk_list =~ s/^.*?'//;
     $disk_list =~ s/'[^']*$//;

  my @disk_list = split(/',\s+'/, $disk_list);

  foreach my $disk_info (@disk_list) {
    if ($disk_info =~ /phy:(.+),(sd.+),w/) {
      $disk{$2} = { lvm_lv => "/dev/$1" };
    }
  }
}

# Size each disk partition
foreach my $partition (sort keys %disk) {
  $disk{$partition}->{size} = get_lv_size($disk{$partition}->{lvm_lv});
  $disk{$partition}->{MB}   = ceil($disk{$partition}->{size} / 1000000);
}

# Break into individual disks with their own partition tables
# and figure out how bit they will be
# 
my %newdisk;

foreach my $partition (sort keys %disk) {
  my $basedisk = substr($partition,0,3);   # sda, sdb, etc
  $newdisk{$basedisk}->{partitions}->{$partition} = $disk{$partition};
  $newdisk{$basedisk}->{totalMB} +=                 $disk{$partition}->{MB};
}

foreach my $disk (sort keys %newdisk) {
  $newdisk{$disk}->{totalMB} += 3;        # Round up for safety
}

#print Dumper(\$vm_name, \%newdisk);

# Output commands to make LVs and partition them, using lvcreate and 
# parted.  parted in particular wants sizes in MB (1,000,000 bytes) 
# even though sectors are in 512 byte units (no I have no idea what
# prompted that stupidity).
#
foreach my $disk (sort keys %newdisk) {
  my $disk_name = "${vm_name}_${disk}";
     $disk_name =~ s/-/_/g;   # Avoid hypens (-) in disk names
  my $disk_size = $newdisk{$disk}->{totalMB};
  my ${lv_name} = "${MAP_NAME}-${disk_name}";
  print <<EOF;
sudo lvcreate -n "${disk_name}" -L ${disk_size}M ${VG_NAME}
sudo parted "${lv_name}" mklabel msdos
EOF

  # Now output the individual partitions
  my %partitions = %{$newdisk{$disk}->{partitions}};
  my $start_mb = 1;

  foreach my $partition (sort keys %partitions) {
    my $end_mb = $start_mb + $partitions{$partition}->{MB};
    
    # Devine type, based on where it is stored
    my $type = "ext2";

    if ($partitions{$partition}->{lvm_lv} =~ m|/dev/r1|) {
       $type = "linux-swap";
    }

    print <<EOF;
sudo parted "${lv_name}" mkpart primary "${type}" ${start_mb} ${end_mb}
EOF
    $start_mb = $end_mb;
  }

  # Make the first partition bootable (in theory it should always be
  # like this in the VMs we have), and make the partitions available 
  # so that we can copy into them
  print <<EOF;
sudo parted "${lv_name}" set 1 boot on
sudo kpartx -a "${lv_name}"
EOF
}
