restorevm.pl 7.39 KB
Newer Older
1 2 3
#!/usr/bin/perl -w
#
# Copyright (c) 2009-2012 University of Utah and the Flux Group.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# 
# {{{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 <http://www.gnu.org/licenses/>.
# 
# }}}
23 24 25 26 27 28 29
#
use strict;
use Getopt::Std;
use English;
use Errno;
use Data::Dumper;

30 31 32
# Drag in path stuff so we can find emulab stuff.
BEGIN { require "/etc/emulab/paths.pm"; import emulabpaths; }

33 34
sub usage()
{
35
    print "Usage: restorevm.pl [-d] [-t targetdir] vnodeid path\n" . 
36
	  "  -d   Debug mode.\n".
37
	  "  -t   Write new xm.conf and copy kernel/ramdisk to targetdir\n".
38 39 40
	  "  -i   Info mode only\n";
    exit(-1);
}
41
my $optlist     = "dixt:";
42 43
my $debug       = 1;
my $infomode    = 0;
44
my $targetdir;
45 46
my $IMAGEUNZIP  = "imageunzip";
my $IMAGEDUMP   = "imagedump";
47 48 49 50 51 52

#
# Turn off line buffering on output
#
$| = 1;

53 54 55 56 57
use libvnode_xen;

# From the library
my $VGNAME	= $libvnode_xen::VGNAME;

58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
# 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;
}
75 76 77
if (defined($options{"t"})) {
    $targetdir = $options{"t"};
}
78 79 80 81 82 83 84 85 86 87
if (defined($options{"i"})) {
    $infomode = 1;
}
usage()
    if (@ARGV != 2);

my $vnodeid = $ARGV[0];
my $path    = $ARGV[1];
my $XMINFO  = "$path/xm.conf";

88 89 90 91 92
# Must supply an absolute path.
if (! ($path =~ /^\//)) {
    Fatal("Must supply an absolute path");
}

Leigh B Stoller's avatar
Leigh B Stoller committed
93 94 95 96 97
if (! -e $IMAGEUNZIP) {
    $IMAGEUNZIP  = "/usr/local/bin/imageunzip";
    $IMAGEDUMP   = "/usr/local/bin/imagedump";
}

98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
#
# 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 (<XM>) {
    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);

126
#
127
# Localize the path to the kernel (ramdisk). Copy out if there is a target dir.
128
#
129 130 131 132
if (defined($targetdir)) {
    if (!$infomode) {
	system("/bin/cp -pf $path/" . $xminfo{"kernel"} .
	       "            $targetdir/" . $xminfo{"kernel"});
133 134 135 136 137

	if (exists($xminfo{"ramdisk"})) {
	    system("/bin/cp -pf $path/" . $xminfo{"ramdisk"} .
		   "            $targetdir/" . $xminfo{"ramdisk"});
	}
138 139
    }
    $xminfo{"kernel"} = $targetdir . "/" . $xminfo{"kernel"};
140 141 142
    if (exists($xminfo{"ramdisk"})) {
	$xminfo{"ramdisk"} = $targetdir . "/" . $xminfo{"ramdisk"};
    }
143 144 145
}
else {
    $xminfo{"kernel"} = $path . "/" . $xminfo{"kernel"};
146 147 148
    if (exists($xminfo{"ramdisk"})) {
	$xminfo{"ramdisk"} = $path . "/" . $xminfo{"ramdisk"};
    }
149
}
150 151 152 153 154 155 156

#
# Fix up the network interfaces.
#
my $ifacelist = eval $xminfo{'vif'};
my @newifaces = ();
foreach my $vif (@$ifacelist) {
157 158 159
    my ($mac, $ip, $bridge) = split(',', $vif);
    $bridge = $ip
	if (!defined($bridge));
160 161 162 163 164 165
    my (undef, $iface) = split('=', $bridge);

    $iface =~ s/eth/xenbr/;
    push(@newifaces, "$mac, bridge=$iface");
}
# XXX Ick!
Leigh B Stoller's avatar
Leigh B Stoller committed
166 167
if ($vnodeid eq "boss" && !defined($options{"x"})) {
    for (my $i = 1; $i <= 4; $i++) {
168 169 170 171 172 173 174
	my $iface = "xenbr$i";
	my $mac   = "00:00:99:98:97:0$i";
	push(@newifaces, "mac=$mac, bridge=$iface");
    }
}
$xminfo{'vif'} = "[" . join(",", map {"'$_'" } @newifaces) . "]";

175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
#
# 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");
    }
}
192 193
my %newdiskinfo = ();
my $newlvms     = {};
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208

#
# 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;
209
    if ($spec =~ /,(sd\w+),/ || $spec =~ /,(xvd\w+),/) {
210 211 212
	$dev = $1;
    }
    else {
Leigh B Stoller's avatar
Leigh B Stoller committed
213
	Fatal("Could not parse $spec");
214 215 216 217 218 219 220 221 222 223
    }
    #
    # 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.
224
    # Swap has to be treated special for now. 
225
    #
226
    my $lvmname = ($spec =~ /swap/ ? "${vnodeid}.swap" : "${vnodeid}.${dev}");
227 228 229 230
    my $device  = "/dev/$VGNAME/$lvmname";

    if (! -e $device) {
	if (!$infomode) {
Leigh B Stoller's avatar
Leigh B Stoller committed
231 232
	    system("lvcreate -n $lvmname -L $lvmsize $VGNAME") == 0
		or Fatal("Could not create lvm for $lvmname");
233 234 235 236
	}
    }
    # Rewrite the diskinfo path for new xm.conf
    delete($diskinfo{$physinfo});
237 238 239
    $newdiskinfo{$device} = "phy:$device,$dev,w";
    # For cleanup on error.
    $newlvms->{$lvmname} = $lvmname;
240 241 242 243 244 245 246 247

    #
    # For swap, just need to mark it as a linux swap partition.
    #
    if ($spec =~ /swap/) {
	#
	# Mark it as a linux swap partition. 
	#
Leigh B Stoller's avatar
Leigh B Stoller committed
248 249 250
	if (!$infomode &&
	    system("echo ',,S' | sfdisk $device -N0")) {
	    Fatal("Could not mark $device as linux swap");
251 252 253 254 255 256 257 258 259 260 261 262 263 264
	}
	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. 
    #
265
    my $opts = "-W 64";
266
    if ($infomode) {
Leigh B Stoller's avatar
Leigh B Stoller committed
267
	system("$IMAGEDUMP $filename");
268 269
    }
    else {
Leigh B Stoller's avatar
Leigh B Stoller committed
270
	system("$IMAGEUNZIP -o $opts $filename $device");
271 272 273
	if ($?) {
	    Fatal("Failed to unzip $filename to $device!");
	}
274 275 276 277 278 279 280 281
    }
}

#
# Write out the config file.
#
delete($xminfo{"disksizes"});
$xminfo{"name"}   = $vnodeid;
282
$xminfo{"memory"} = "2048";
283
$xminfo{"disk"}   = "[" . join(",", map {"'$_'" } values(%newdiskinfo)) . "]";
284 285 286 287 288 289 290 291 292

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.
    #
293 294 295 296 297 298 299 300
    my $xmconf;
    if (defined($targetdir)) {
	$xmconf = "$targetdir/xm.conf";
    }
    else {
	$xmconf = "/var/tmp/${vnodeid}.conf";
    }
    print "Writing new xen config to $xmconf\n";
301
    
302 303
    open(XM, ">$xmconf")
	or fatal("Could not open $xmconf: $!");
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
    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) = @_;

322 323 324 325 326 327 328 329
    #
    # Destroy any new lvms we created.
    #
    if (defined($newlvms)) {
	foreach my $lvname (keys(%{ $newlvms })) {
	    system("lvremove -f $VGNAME/$lvname");
	}
    }
330 331 332 333
    die("*** $0:\n".
	"    $msg\n");
}