#!/usr/bin/perl -w
#
# Copyright (c) 2009-2012 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
# This file is part of the Emulab network testbed software.
#
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this file. If not, see .
#
# }}}
#
use strict;
use Getopt::Std;
use English;
use Errno;
use Data::Dumper;
# Drag in path stuff so we can find emulab stuff.
BEGIN { require "/etc/emulab/paths.pm"; import emulabpaths; }
sub usage()
{
print "Usage: restorevm.pl [-d] [-t targetdir] vnodeid path\n" .
" -d Debug mode.\n".
" -t Write new xm.conf and copy kernel/ramdisk to targetdir\n".
" -i Info mode only\n";
exit(-1);
}
my $optlist = "dixt:";
my $debug = 1;
my $infomode = 0;
my $targetdir;
my $IMAGEUNZIP = "imageunzip";
my $IMAGEDUMP = "imagedump";
#
# Turn off line buffering on output
#
$| = 1;
use libvnode_xen;
# From the library
my $VGNAME = $libvnode_xen::VGNAME;
# Locals
my %xminfo = ();
# Protos
sub Fatal($);
#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"d"})) {
$debug = 1;
}
if (defined($options{"t"})) {
$targetdir = $options{"t"};
}
if (defined($options{"i"})) {
$infomode = 1;
}
usage()
if (@ARGV != 2);
my $vnodeid = $ARGV[0];
my $path = $ARGV[1];
my $XMINFO = "$path/xm.conf";
# Must supply an absolute path.
if (! ($path =~ /^\//)) {
Fatal("Must supply an absolute path");
}
if (! -e $IMAGEUNZIP) {
$IMAGEUNZIP = "/usr/local/bin/imageunzip";
$IMAGEDUMP = "/usr/local/bin/imagedump";
}
#
# We need this file to figure out the disk info.
#
if (! -e "$XMINFO") {
Fatal("$XMINFO does not exist");
}
open(XM, $XMINFO)
or Fatal("Could not open $XMINFO: $!");
while () {
if ($_ =~ /^([-\w]*)\s*=\s*(.*)$/) {
my $key = $1;
my $val = $2;
if ($val =~ /^'(.*)'$/) {
$val = $1;
}
$xminfo{$key} = "$val";
}
elsif ($_ =~ /^([-\w]*)\s*[\+\=]+\s*(.*)$/) {
my $key = $1;
my $val = $2;
if ($val =~ /^'(.*)'$/) {
$val = $1;
}
$xminfo{$key} .= $val;
}
}
close(XM);
#
# Localize the path to the kernel (ramdisk). Copy out if there is a target dir.
#
if (defined($targetdir)) {
if (!$infomode) {
system("/bin/cp -pf $path/" . $xminfo{"kernel"} .
" $targetdir/" . $xminfo{"kernel"});
if (exists($xminfo{"ramdisk"})) {
system("/bin/cp -pf $path/" . $xminfo{"ramdisk"} .
" $targetdir/" . $xminfo{"ramdisk"});
}
}
$xminfo{"kernel"} = $targetdir . "/" . $xminfo{"kernel"};
if (exists($xminfo{"ramdisk"})) {
$xminfo{"ramdisk"} = $targetdir . "/" . $xminfo{"ramdisk"};
}
}
else {
$xminfo{"kernel"} = $path . "/" . $xminfo{"kernel"};
if (exists($xminfo{"ramdisk"})) {
$xminfo{"ramdisk"} = $path . "/" . $xminfo{"ramdisk"};
}
}
#
# Fix up the network interfaces.
#
my $ifacelist = eval $xminfo{'vif'};
my @newifaces = ();
foreach my $vif (@$ifacelist) {
my ($mac, $ip, $bridge) = split(',', $vif);
$bridge = $ip
if (!defined($bridge));
my (undef, $iface) = split('=', $bridge);
$iface =~ s/eth/xenbr/;
push(@newifaces, "$mac, bridge=$iface");
}
# XXX Ick!
if ($vnodeid eq "boss" && !defined($options{"x"})) {
for (my $i = 1; $i <= 4; $i++) {
my $iface = "xenbr$i";
my $mac = "00:00:99:98:97:0$i";
push(@newifaces, "mac=$mac, bridge=$iface");
}
}
$xminfo{'vif'} = "[" . join(",", map {"'$_'" } @newifaces) . "]";
#
# Parse the disk info.
#
if (!exists($xminfo{'disk'})) {
Fatal("No disk info in config file!");
}
my $disklist = eval $xminfo{'disk'};
my %diskinfo = ();
my %disksize = ();
foreach my $disk (@$disklist) {
if ($disk =~ /^phy:([^,]*)/) {
$diskinfo{$1} = $disk;
}
else {
Fatal("Cannot parse disk: $disk");
}
}
my %newdiskinfo = ();
my $newlvms = {};
#
# And the size info.
#
foreach my $spec (split(',', $xminfo{'disksizes'})) {
my ($dev,$size) = split(':', $spec);
$disksize{$dev} = $size;
}
print Dumper(\%disksize);
foreach my $physinfo (keys(%diskinfo)) {
my $spec = $diskinfo{$physinfo};
my $dev;
my $filename;
if ($spec =~ /,(sd\w+),/ || $spec =~ /,(xvd\w+),/) {
$dev = $1;
}
else {
Fatal("Could not parse $spec");
}
#
# Figure out the size of the LVM.
#
my $lvmsize = $disksize{$dev};
Fatal("Could not get lvsize for $dev")
if (!defined($lvmsize));
#
# Form a new lvmname and create the LVM using the size.
# Swap has to be treated special for now.
#
my $lvmname = ($spec =~ /swap/ ? "${vnodeid}.swap" : "${vnodeid}.${dev}");
my $device = "/dev/$VGNAME/$lvmname";
if (! -e $device) {
if (!$infomode) {
system("lvcreate -n $lvmname -L $lvmsize $VGNAME") == 0
or Fatal("Could not create lvm for $lvmname");
}
}
# Rewrite the diskinfo path for new xm.conf
delete($diskinfo{$physinfo});
$newdiskinfo{$device} = "phy:$device,$dev,w";
# For cleanup on error.
$newlvms->{$lvmname} = $lvmname;
#
# For swap, just need to mark it as a linux swap partition.
#
if ($spec =~ /swap/) {
#
# Mark it as a linux swap partition.
#
if (!$infomode &&
system("echo ',,S' | sfdisk $device -N0")) {
Fatal("Could not mark $device as linux swap");
}
next;
}
$filename = "$path/$dev";
Fatal("$filename does not exist")
if (! -e $filename);
print "Working on $filename.\n";
print "Size is $lvmsize. Writing to $device\n";
#
# The root FS is a single partition image, while the aux disks
# have a real MBR in them.
#
my $opts = "-W 64";
if ($infomode) {
system("$IMAGEDUMP $filename");
}
else {
system("$IMAGEUNZIP -o $opts $filename $device");
if ($?) {
Fatal("Failed to unzip $filename to $device!");
}
}
}
#
# Write out the config file.
#
delete($xminfo{"disksizes"});
$xminfo{"name"} = $vnodeid;
$xminfo{"memory"} = "2048";
$xminfo{"disk"} = "[" . join(",", map {"'$_'" } values(%newdiskinfo)) . "]";
if ($infomode) {
print Dumper(\%xminfo);
}
else {
#
# Before we write it out, need to munge the vif spec since there is
# no need for the script. Use just the default.
#
my $xmconf;
if (defined($targetdir)) {
$xmconf = "$targetdir/xm.conf";
}
else {
$xmconf = "/var/tmp/${vnodeid}.conf";
}
print "Writing new xen config to $xmconf\n";
open(XM, ">$xmconf")
or fatal("Could not open $xmconf: $!");
foreach my $key (keys(%xminfo)) {
my $val = $xminfo{$key};
if ($val =~ /^\[/) {
print XM "$key = $val\n";
}
else {
print XM "$key = '$val'\n";
}
}
close(XM);
}
exit(0);
sub Fatal($)
{
my ($msg) = @_;
#
# Destroy any new lvms we created.
#
if (defined($newlvms)) {
foreach my $lvname (keys(%{ $newlvms })) {
system("lvremove -f $VGNAME/$lvname");
}
}
die("*** $0:\n".
" $msg\n");
}