create_image.in 27.9 KB
Newer Older
1
#!/usr/bin/perl -wT
Leigh B. Stoller's avatar
Leigh B. Stoller committed
2
#
3
# Copyright (c) 2000-2014 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/>.
# 
# }}}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
23
#
24
use strict;
25
26
use English;
use Getopt::Std;
27
use POSIX qw(setsid :sys_wait_h);
28
use File::Basename;
29
use Cwd qw(realpath);
30

31
#
32
# Image Creation Tuneables.
33
34
#
# $maxwait	max wall clock time to allow, progress or not
35
36
37
#		Empirically we have observed about 1.6MB/sec on a pc850
#		for a Windows image (the slowest to create), so figuring
#		1.5MB/sec for a 6GB max image works out to around 72 minutes.
38
39
40
41
# $idlewait	max time to wait between periods of progress
# $checkwait	time between progress checks (must be int div of $idlewait)
# $reportwait	time between progress reports (must be multiple of $checkwait)
#
42
43
44
45
# $maximagesize	max size in bytes of an image.  This should really be in the
#		DB (per-testbed, per-project, per-user, per-something), and
#		not hardwired here.  In the meantime, we set this big and let
#		disk quotas do the dirty work of limiting size.
46
#
47
my $maxwait      = (72 * 60);
48
49
50
my $idlewait     = ( 8 * 60);
my $reportwait   = ( 2 * 60);
my $checkwait    = 15;
51
52
my $maximagesize = (6 * 1024**3); # 20GB

53
54
55
56
57
58
59
60
#
# Create a disk image.
#
# XXX: Device file should come from DB.
#      Start/count slice computation is not generalized at all.
#
sub usage()
{
61
    print(STDERR
62
	  "Usage: create_image [-wsN] [-p <pid>] <imagename> <node>\n" .
63
	  "switches and arguments:\n".
64
	  "-w          - wait for image to be fully created\n".
65
66
	  "-s          - use ssh instead of frisbee uploader\n".
	  "-N          - use NFS (if available) instead of frisbee uploader\n".
67
68
69
	  "-p <pid>    - project ID of the image; defaults to system project\n".
	  "<imagename> - imagename to use\n".
	  "<node>      - nodeid to create the image from\n");
70
71
    exit(-1);
}
72
my $optlist  = "p:wsNdfe";
73
my $waitmode = 0;
74
75
76
77
my $usessh   = 0;
my $usenfs   = 0;
my $usefup   = 1;
my $noemail  = 0;
78
my $webtask;
79
80
81
82
83
84

#
# Configure variables
#
my $TB		= "@prefix@";
my $TBOPS       = "@TBOPSEMAIL@";
85
my $TBLOGS      = "@TBLOGSEMAIL@";
86
my $BOSSIP	= "@BOSSNODE_IP@";
87
my $CONTROL     = "@USERNODE@";
88
my $NONFS	= @NOSHAREDFS@;
89
90
91
92
93
94
95

#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use libdb;
use libtestbed;
96
use libadminmfs;
97
use Experiment;
98
use Node;
99
100
use User;
use Image;
101
use OSinfo;
102
use Logfile;
103
use WebTask;
104
105
106
107
108
109
110
111
112
113
114
115

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

#
# Untaint the path
# 
$ENV{'PATH'} = "/bin:/sbin:/usr/bin:";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

Leigh B Stoller's avatar
Leigh B Stoller committed
116
117
118
119
120
121
122
123
#
# We don't want to run this script unless its the real version.
#
if ($EUID != 0) {
    die("*** $0:\n".
	"    Must be setuid! Maybe its a development version?\n");
}

124
125
sub cleanup();
sub fatal($);
126
sub check_progress($$);
127
sub run_with_ssh($$);
128

129
130
my $nodereboot	= "$TB/bin/node_reboot";
my $createimage = "/usr/local/bin/create-image";
131
my $reboot_prep = "@CLIENT_BINDIR@/reboot_prepare";
132
my $EC2SNAP     = "$TB/sbin/ec2import.proxy";
133
my $friskiller  = "$TB/sbin/frisbeehelper";
134
my $osselect    = "$TB/bin/os_select";
135
my $checkquota  = "$TB/sbin/checkquota";
136
my $imagehash	= "$TB/bin/imagehash";
137
my $imagevalidate = "$TB/sbin/imagevalidate";
138
my $SHA1	= "/sbin/sha1";
139
my $SCP		= "/usr/bin/scp";
140
141
142
143
my $def_devtype	= "ad";
my $def_devnum	= 0;
my $devtype;
my $devnum;
144
my $device;
145
my $mereuser    = 0;
Leigh B Stoller's avatar
Leigh B Stoller committed
146
my $debug       = 1;
147
my $foreground  = 0;
148
my $imagepid    = TB_OPSPID;
149
150
my $logfile;
my $oldlogfile;
Kirk Webb's avatar
   
Kirk Webb committed
151
my $needcleanup = 0;
152
my $needunlock  = 0;
153
my $isvirtnode  = 0;
154
my $isec2node   = 0;
155
my $onsharednode= 0;
156
my $didbackup   = 0;
157
158
159
my $node_id;
my $node;
my ($experiment,$pid);
160
161
162
163
164

#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
165
my %options = ();
166
167
168
if (! getopts($optlist, \%options)) {
    usage();
}
169
170
171
if (defined($options{"w"})) {
    $waitmode = 1;
}
172
173
174
if (defined($options{"e"})) {
    $noemail = 1;
}
175
if (defined($options{"s"})) {
176
177
178
179
180
181
182
183
184
185
186
    $usessh = 1;
    $usefup = $usenfs = 0;
}
if (defined($options{"N"})) {
    if (!$NONFS) {
	$usenfs = 1;
	$usefup = $usessh = 0;
    } else {
	print STDERR "NFS not available, cannot use -N\n";
	exit(1);
    }
187
188
189
190
}
if (defined($options{"d"})) {
    $debug = 1;
}
191
192
if (defined($options{"f"})) {
    $foreground = 1;
Leigh B Stoller's avatar
Leigh B Stoller committed
193
    $waitmode = 0;
194
}
195
if (@ARGV != 2) {
196
197
198
    usage();
}

199
my $imagename  = $ARGV[0];
200
my $target     = $ARGV[1];
201

202
203
204
205
206
207
208
#
# There is no reason to run as root unless we are taking a snapshot
# of a VM on a shared node. In that case we will flip back when
# we do the ssh over.
#
$EUID = $UID;

209
210
211
#
# Untaint the arguments.
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
212
if ($imagename =~ /^([-\w\.\+]+)$/) {
213
214
215
    $imagename = $1;
}
else {
216
217
    die("*** $0:\n".
	"    Bad data in $imagename.\n");
218
219
220
221
222
}
    
if (defined($options{"p"})) {
    $imagepid = $options{"p"};
	
223
    if ($imagepid =~ /^([-\w\.]+)$/) {
224
225
226
	$imagepid = $1;
    }
    else {
227
228
	die("*** $0:\n".
	    "    Bad data in $imagepid.\n");
229
    }
230
231
}

232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#
# Reset default values from site variables if they exist.
#
my $tmp;
if (TBGetSiteVar("images/create/maxwait", \$tmp)) {
    $maxwait = $tmp * 60;
}
if (TBGetSiteVar("images/create/idlewait", \$tmp)) {
    $idlewait = $tmp * 60;
}
if (TBGetSiteVar("images/create/maxsize", \$tmp)) {
    $maximagesize = $tmp * 1024**3;
}
$idlewait = $maxwait
    if ($maxwait < $idlewait);
$reportwait = $idlewait
    if ($idlewait < $reportwait);
$checkwait = $reportwait
    if ($reportwait < $checkwait);

252
#
253
# Verify user and get his DB uid and other info for later.
254
#
255
256
257
my $this_user = User->ThisUser();
if (! defined($this_user)) {
    tbdie("You ($UID) do not exist!");
258
}
259
260
261
my $user_uid   = $this_user->uid();
my $user_name  = $this_user->name();
my $user_email = $this_user->email();
262

263
264
265
266
#
# Before doing anything else, check for overquota ... lets not waste
# our time. Make sure user sees the error by exiting with 1.
#
267
if (system("$checkquota $user_uid") != 0) {
268
    die("*** $0:\n".
269
270
	"    You are over your disk quota on $CONTROL; ".
	"please login there and cleanup!\n");
271
}
272
273
if ($UID && ! $this_user->IsAdmin()) {
    $mereuser = 1;
274
275
}

276
277
278
279
280
#
# Grab the imageid description from the DB. We do a permission check, but
# mostly to avoid hard to track errors that would result if the user picked
# the wrong one (which is likely to happen no matter what I do).
#
281
282
my $image = Image->Lookup($imagepid, $imagename);
if (!defined($image)) {
283
284
    die("*** $0:\n".
	"    No such image descriptor $imagename in project $imagepid!\n");
285
}
286
my $imageid = $image->imageid();
287

288
if ($mereuser &&
289
    ! $image->AccessCheck($this_user, TB_IMAGEID_ACCESS)) {
290
291
    die("*** $0:\n".
	"    You do not have permission to use imageid $imageid!\n");
292
293
}

294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
#
# Is it a local node or a remote EC2 node (need to generalize). 
#
if ($target =~ /^.*@.*$/) {
    if ($target =~ /^([-\w\@\+\.]+)$/) {
	$target = $1;
    }
    else {
	die("*** $0:\n".
	    "    Bad data in $target\n");
    }

    $isec2node = 1;
    $usefup = $usessh = 0;
    $pid = $image->pid();
}
else {
    if ($target =~ /^([-\w]+)$/) {
	$node_id = $1;
    }
    else {
	die("*** $0:\n".
	    "    Bad data in $target\n");
    }

    # Check node and permission
    $node = Node->Lookup($node_id);
    if (!defined($node)) {
	die("*** $0:\n".
	    "    Invalid node name $node_id!\n");
    }
    $isvirtnode   = $node->isvirtnode();
    $onsharednode = $node->sharing_mode()
	if ($isvirtnode);

    if (! $node->AccessCheck($this_user, TB_NODEACCESS_LOADIMAGE)) {
	die("*** $0:\n".
	    "    You do not have permission to create an image from $node\n");
    }

334
335
336
337
338
    if ($node->IsTainted()) {
	die("*** $0:\n".
	    "    $node is tainted - image creation denied.\n");
    }

339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
    #
    # We need the project id for test below. The target directory for the
    # output file has to be the node project directory, since that is the
    # directory that is going to be NFS mounted by default.
    #
    $experiment = $node->Reservation();
    if (!defined($experiment)) {
	die("*** $0:\n".
	    "    Could not map $node to its experiment object!\n");
    }
    $pid = $experiment->pid();

    #
    # To avoid blowing a cavernous hole ("allow all TCP ports to boss")
    # in the per-experiment firewall, we don't use the frisbee uploader if
    # the node is firewalled.
    # 
    if ($usefup && $experiment->IsFirewalled()) {
	print "*** WARNING: $node_id is firewalled, not using Frisbee uploader\n";
	$usefup = 0;
	if ($NONFS) {
	    $usenfs = 0;
	    $usessh = 1;
	} else {
	    $usenfs = 1;
	    $usessh = 0;
	}
    }
}

369
#
370
371
# Make sure that the directory exists and is writeable for the user.
# We test this by creating the file. Its going to get wiped anyway.
372
#
373
374
my $filename = $image->path();
my $isglobal = $image->global();
375
my $usepath = 0;
376
377

#
378
# Redirect pathname for global images.
379
#
380
if ($isglobal && ($filename =~ /^\/usr\/testbed/)) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
381
    $filename = PROJROOT() . "/$pid/images/" . basename($filename);
382
    print "*** WARNING: Writing global descriptor to $filename instead!\n";
383
384
385
386
387
388
389
390
    #
    # XXX the Emulab config of the master server doesn't know this trick
    # so when it tries to lookup imageid emulab-ops/<whatever> it would
    # still map to /usr/testbed and fail because it cannot update images
    # outside of /{users,grouop,proj}. So we skirt the issue by passing
    # it the full path contructed here rather than the imageid.
    #
    $usepath = 1;
391
}
392

393
394
395
396
397
398
399
#
# Make sure real path is someplace that makes sense; remember that the
# image is created on the nodes, and it NFS mounts directories on ops.
# Writing the image to anyplace else is just going to break things.
#
# Use realpath to resolve any symlinks.
#
400
my $translated = realpath($filename);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
401
if ($translated =~ /^([-\w\.\/\+]+)$/) {
402
403
404
405
406
407
    $filename = $1;
}
else {
    die("*** $0:\n".
	"    Bad data returned by realpath: $translated\n");
}
408
409
410
411
412
# Make sure not a directory.
if (-d $filename) {
    die("*** $0:\n".
	"    $filename is a directory! Must be a plain file.\n");
}
413
414

#
415
# The file must reside in an allowed directory. Since this script
416
417
418
# runs as the caller, regular file permission checks ensure its a file
# the user is allowed to use. 
#
419
if (! TBValidUserDir($filename, 0)) {
420
421
422
423
    die("*** $0:\n".
	"    $filename does not resolve to an allowed directory!\n");
}

424
425
426
427
428
429
430
431
432
#
# Before we do anything destructive, we lock the descriptor.
#
if ($image->Lock()) {
    die("*** $0:\n".
	"    Image is locked, try again later!\n");
}
$needunlock = 1;

433
434
435
436
437
438
# See if a web task is tracking this image creation.
$webtask = WebTask->LookupByObject($image->uuid());
if (defined($webtask)) {
    $webtask->AutoStore(1);
}

439
440
441
442
#
# Be sure to kill off running frisbee. If a node is trying to load that
# image, well tough. 
#
443
444
system("$friskiller -k $imageid");
if ($?) {
445
    fatal("Could not kill running frisbee for $imageid!");
446
}
447

448
if (-e $filename) {
449
450
451
452
453
454
455
456
457
458
459
    #
    # Back it up in case of failure. Note that the frisbee upload server
    # does this, so we do it only for the ssh/nfs case.
    #
    if (!$usefup) {
	system("/bin/mv -f $filename ${filename}.bak");
	if ($?) {
	    fatal("Could not back up $filename");
	}
	$didbackup = 1;
    }
460
461
}

462
#
463
464
465
466
467
468
469
# We want to truncate the file (we backed it up above), which also
# confirms the user can really create a new file.
#
# XXX The problem is that frisbee upload server does this too, which
# is why we have a lot of zero length backup files. So, in uploader
# mode, make sure the user can create the tmp file that the uploader
# uses.
470
#
471
472
473
474
475
$tmp = $filename . ($usefup ? ".tmp" : "");
open(FILE, "> $tmp") or
    fatal("Could not create $tmp: $!");
close(FILE) or
    fatal("Could not truncate $tmp: $!");
476

477
if (! ($isvirtnode || $isec2node)) {
478
479
480
481
482
483
484
485
486
487
488
489
    #
    # Get the disktype for this node
    #
    $node->disktype(\$devtype);
    $node->bootdisk_unit(\$devnum);

    $devtype = $def_devtype
	if (!defined($devtype));
    $devnum = $def_devnum
	if (!defined($devnum));
    $device = "/dev/${devtype}${devnum}";
}
490

491
492
493
494
#
# Record when this image was updated, so that we can figure out which
# revision of the testbed image it was based off.
#
495
496
497
# Makes no sense to do this when writing a global image to a different path.
# We need a better way to make new images live.
#
Leigh B Stoller's avatar
Leigh B Stoller committed
498
$image->MarkUpdate($this_user) == 0 or
499
    fatal("Could not mark the update time in $image");
500
    
501
502
503
504
505
506
#
# Okay, we want to build up a command line that will run the script on
# on the client node. We use the imageid description to determine what
# slice (or perhaps the entire disk) is going to be zipped up. We do not
# allow arbitrary combos of course. 
#
507
508
my $startslice;
my $loadlength;
509
510
my $command    = "$createimage ";

511
512
513
if ($usefup) {
    my $id = $usepath ? $filename : ($image->pid() . "/$imagename");
    $command .= " -S $BOSSIP -F $id";
514
}
515

516
517
518
519
if ($isec2node) {
    $command = "$EC2SNAP ";
}
elsif ($isvirtnode) {
520
521
522
523
524
525
526
527
528
    #
    # Need to know this is a xen-host to tailor options.
    #
    my $pnode  = Node->Lookup($node->phys_nodeid());
    my $osinfo = OSinfo->Lookup($pnode->def_boot_osid());
    if (!defined($osinfo)) {
	fatal("Could not get osinfo for $pnode");
    }
    
529
530
531
    #
    # XXX Need to add XEN package flag to descriptor.
    #
532
533
534
535
536
537
538
    if ($osinfo->FeatureSupported("xen-host")) {
	if ($image->mbr_version() == 99) {
	    $command .= " -p";
	}
	if ($image->loadpart()) {
	    $command .= " -s " . $image->loadpart();
	}
539
    }
540
541
542
543
544
545
546
547
548
549
    $command .= " $node_id";
}
else {
    $startslice = $image->loadpart();
    $loadlength = $image->loadlength();

    if ($startslice || $loadlength == 1) {
	$command .= " -s $startslice";
    }
    $command .= " $device";
550
}
551
552

if ($usefup || $usessh) {
553
554
555
    $command .= " -";
} else {
    $command .= " $filename";
556
557
558
559
560
}

#
# Go to the background since this is going to take a while.
# 
Leigh B Stoller's avatar
Leigh B Stoller committed
561
if (! $foreground) {
562
563
    $logfile = Logfile->Create((defined($experiment) ?
				$experiment->gid_idx() : $image->gid_idx()));
564
565
566
567
    fatal("Could not create a logfile")
	if (!defined($logfile));
    # Mark it open since we are going to start using it right away.
    $logfile->Open();
568

569
570
    # Logfile becomes the current spew.
    $image->SetLogFile($logfile);
571

572
    if (my $childpid = TBBackGround($logfile->filename())) {
573
574
	#
	# Parent exits normally, except if in waitmode. 
575
	#
576
	if (!$waitmode) {
577
	    print("Your image from $target is being created\n".
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
		  "You will be notified via email when the image has been\n".
		  "completed, and you can load the image on another node.\n");
	    exit(0);
	}
	print("Waiting for image creation to complete\n");
	print("You may type ^C at anytime; you will be notified via email;\n".
	      "later; you will not actually interrupt image creation.\n");
	
	# Give child a chance to run.
	select(undef, undef, undef, 0.25);
	
	#
	# Reset signal handlers. User can now kill this process, without
	# stopping the child.
	#
	$SIG{TERM} = 'DEFAULT';
	$SIG{INT}  = 'DEFAULT';
	$SIG{QUIT} = 'DEFAULT';

597
	#
598
599
600
601
602
603
	# Wait until child exits or until user gets bored and types ^C.
	#
	waitpid($childpid, 0);
	
	print("Done. Exited with status: $?\n");
	exit($? >> 8);
604
    }
605
606
}

607
#
608
609
610
# New process group since we get called from the web interface,
# and so the child does not get zapped if the user types ^C
# in waitmode. 
611
#
Leigh B Stoller's avatar
Leigh B Stoller committed
612
if (! $foreground) {
613
614
615
    POSIX::setsid();
}

Kirk Webb's avatar
   
Kirk Webb committed
616
617
618
619
620
#
# From here on out, we should take care to clean up the DB, and
# reboot the source node.
#
$needcleanup = 1;
621

622
# Clear the bootlog; see below.
623
624
$node->ClearBootLog()
    if (defined($node));
625

626
627
628
629
630
631
632
633
634
# check_progress state
my $runticks	 = 0;
my $maxticks	 = int($maxwait / $checkwait);
my $reportticks  = int($reportwait / $checkwait);
my $idleticks    = 0;
my $maxidleticks = int($idlewait / $checkwait);
my $lastsize     = 0;
my $result;

635
#
636
# We can skip a lot of the stuff below for virtnodes and ec2 nodes.
637
#
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
if ($isec2node) {
    my $safe_target = User::escapeshellarg($target);
    
    my $cmd = "$TB/bin/sshtb -host $CONTROL $EC2SNAP -u $user_uid ".
	"$safe_target $pid $user_uid $imageid $filename";
    print STDERR "About to: '$cmd'\n" if (1 || $debug);

    my $SAVEUID	= $UID;
    $EUID = $UID = 0;

    system($cmd);
    fatal("'$cmd' failed")
	if ($?);

    $EUID = $UID = $SAVEUID;
    goto ec2done;
}
elsif ($isvirtnode) {
656
    #
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
    # XEN creates a problem; the physical host cannot actually
    # execute a command inside the guest, but we need to run
    # reboot_prepare and reboot it. FreeBSD creates an additional
    # problem in that shutdown has to run to invoke prepare; reboot
    # does not run it, and a shutdown from outside the VM has the
    # sae effect; prepare does not run. What a pain. 
    #
    my $SAVEUID	= $UID;
    $EUID = $UID = 0;

    my $cmd = "$TB/bin/sshtb -n -o ConnectTimeout=10 ".
	"-host $node_id $reboot_prep";
    print STDERR "About to: '$cmd'\n" if ($debug);
    system($cmd);
    fatal("'$cmd' failed")
	if ($?);

674
675
676
677
    # Mark webtask
    $webtask->status("imaging")
	if (defined($webtask));

678
679
    #
    # Now execute command and wait.
680
681
682
683
684
685
    #
    if ($NONFS) {
	$result = run_with_ssh($command, $filename);
    } else {
	$result = run_with_ssh($command, undef);
    }
686
    $EUID = $UID = $SAVEUID;
687
688
689
    goto done;
}

690
#
Russ Fish's avatar
typo.    
Russ Fish committed
691
# Reboot into admin mode and run the command.
692
693
# Note that without a shared FS, we just boot the node into the admin MFS
# and run the command via SSH, capturing the output.
694
695
696
697
#
my $me           = $0;
my %args         = ();
$args{'name'}    = $me;
698
$args{'prepare'} = 1;
699

700
if ($usessh) {
701
702
703
704
705
706
707
708
709
    #
    # Put the node in admin mode...
    #
    $args{'on'} = 1;
    $args{'clearall'} = 0;
    if (TBAdminMfsSelect(\%args, undef, $node_id)) {
	$result = "setupfailed";
	goto done;
    }
710

711
712
713
714
715
716
717
718
719
720
721
722
    #
    # ...boot it...
    #
    $args{'reboot'} = 1;
    $args{'retry'} = 0;
    $args{'wait'} = 1;
    my @failed = ();
    if (TBAdminMfsBoot(\%args, \@failed, $node_id)) {
	$result = "setupfailed";
	goto done;
    }

723
724
725
726
    # Mark webtask
    $webtask->status("imaging")
	if (defined($webtask));

727
728
729
    #
    # ...execute command and wait!
    #
730
731
    $result = run_with_ssh($command, $filename);
    if ($result eq "setupfailed") {
732
733
734
735
736
737
738
739
	goto done;
    }
} else {
    $args{'command'} = $command;
    $args{'timeout'} = $maxwait + $checkwait;
    $args{'pfunc'}     = \&check_progress;
    $args{'pinterval'} = $checkwait;

740
741
742
743
    # Mark webtask
    $webtask->status("imaging")
	if (defined($webtask));

744
745
746
747
748
749
750
    my $retry = 1;
    while ($retry) {
	$retry = 0;
	if (TBAdminMfsRunCmd(\%args, undef, $node_id)) {
	    $result = "setupfailed"
		if (!defined($result));
	}
751
752
753
    }
}

754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
#
# XXX woeful backward compat hack.
# The old client-side script will not recognize the -S and -F options
# we pass in and will exit(-1).  We detect that here and retry with
#
if ($usefup && $result eq "255") {
    print STDERR "MFS does not support frisbee upload, falling back on ",
                 $NONFS ? "ssh" : "nfs", "...\n";

    $command = "$createimage ";
    if ($startslice || $loadlength == 1) {
	$command .= " -s $startslice";
    }
    $command .= " $device";
    if ($usessh) {
	$command .= " -";
    } else {
	$command .= " $filename";
    }
773
774
775
776
777
778
779
780

    # reset state for check_progress
    $usefup = 0;
    $runticks = 0;
    $idleticks = 0;
    $lastsize = 0;
    $result = undef;

781
782
783
784
785
786
787
    if ($NONFS) {
	$result = run_with_ssh($command, $filename);
    } else {
	$result = run_with_ssh($command, undef);
    }
}

788
789
done:

790
791
792
793
794
795
796
# Grab boot log now. Node will reboot and possibly erase it. We should
# probably come up with a better way to handle this.
my $bootlog;
if ($node->GetBootLog(\$bootlog)) {
    $bootlog = undef;
}

Kirk Webb's avatar
   
Kirk Webb committed
797
798
if (! cleanup()) {
    fatal("Problem encountered while cleaning up!\n");
799
800
801
}

#
Kirk Webb's avatar
   
Kirk Webb committed
802
803
# If we timed out, if the result code was bad, or if the image size
# grew too large.
804
#
805
806
if ($result eq "setupfailed") {
    fatal("FAILED: Node setup failed ... \n");
807
}
808
809
if ($result eq "timeout") {
    fatal("FAILED: Timed out generating image ... \n");
810
}
811
if ($result eq "toobig") {
Kirk Webb's avatar
   
Kirk Webb committed
812
813
    fatal("FAILED: Maximum image size ($maximagesize bytes) exceeded ... \n");
}
814
if ($result != 0) {
Mike Hibler's avatar
Mike Hibler committed
815
    fatal("FAILED: Returned error code $result generating image ... \n");
816
}
817

818
ec2done:
819
820
821
if (defined($webtask)) {
    $webtask->status("finishing");
}
822
823
824
825
826
#
# Everything worked, create the hash signature file.
#
my $sigdir;
($sigdir = $filename) =~ s/^(.*)\/[^\/]+$/$1\/sigs/;
827
mkdir($sigdir, 0770)
828
829
830
831
832
    if (! -d "$sigdir");

my $sigfilename;
($sigfilename = $filename) =~ s/^(.*)(\/[^\/]+$)/$1\/sigs$2.sig/;
my $swmsg = "";
833
834
if (! -x $imagehash ||
    system("$imagehash -c -o $sigfilename $filename") != 0) {
835
836
837
838
839
840
841
    warn("Could not create swapout signature file\n");
    $swmsg = "WARNING: could not create swapout signature file $sigfilename\n".
	     "       You will not be able to save disk state for this image\n";
} else {
    print("Swapout signature file created\n");
}

842
#
843
# Update fields in the DB related to the image.
844
#
845
846
847
848
849
850
851
852
853
854
855
856
# Note that we do not do this for "standard" images since they get uploaded
# into /proj/emulab-ops rather than /usr/testbed. We could automatically move
# the image into place here, but that makes us nervous. We prefer an admin do
# that by hand after testing the new image!
#
my $tbopsmsg = "";
if ($isglobal && $usepath) {
    $tbopsmsg =
	"Did not update DB state for global image $pid/$imagename since\n".
	"image was written to '$filename'\n".
	"instead of $TB/images. Move image into place and run:\n".
	"    $imagevalidate -uq $pid/$imagename\n";
857
}
858
859
860
861
862
863
864
865
866
867
868
869
elsif (system("$imagevalidate -uq $pid/$imagename") != 0) {
    $tbopsmsg =
	"DB state update for image $pid/$imagename failed, try again with:\n".
	"    $imagevalidate -u $pid/$imagename\n";
}
if ($tbopsmsg) {
    SENDMAIL($TBOPS,
	     "Image DB state update failure for $pid/$imagename",
	     $tbopsmsg,
	     $TBOPS,
	     undef,
	     ());
870
871
}

872
print "Image creation succeeded.\n";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
873
print "Image written to $filename.\n";
874
#      "Final size: " . (stat($filename))[7] . " bytes.\n";
Kirk Webb's avatar
   
Kirk Webb committed
875

876
# Append bootlog (which has prepare output)
877
if (defined($bootlog)) {
878
879
    print "\n\n";
    print "------------------ Prepare Output ----------------\n";
880
    print "$bootlog\n";
881
882
}

883
SENDMAIL("$user_name <$user_email>",
884
885
	 "Image Creation on $target Completed: $pid/$imagename",
	 "Image creation on $target has completed. As you requested, the\n".
886
	 "image has been written to $filename.\n".
887
888
	 "You may now os_load this image on other nodes in your experiment.\n".
	 "$swmsg",
889
890
	 "$user_name <$user_email>",
	 "Bcc: $TBLOGS",
891
	 defined($logfile) ? ($logfile->filename()) : ()) if (!$noemail);
892

893
if (defined($logfile)) {
894
    # Close up the log file so the webpage stops.
895
    $logfile->Close();
896
    $image->ClearLogFile();
897
}
898
899
900
901
if (defined($webtask)) {
    $webtask->status("ready");
    $webtask->Exited(0);
}
902
$image->Unlock();
903
904
905
906
exit 0;

sub cleanup ()
{
Kirk Webb's avatar
   
Kirk Webb committed
907
908
    $needcleanup = 0;

909
    if ($isvirtnode || $isec2node) {
910
911
912
913
914
915
	#
	# Nothing to do; the clientside script rebooted the container.
	#
	return 1;
    }

Kirk Webb's avatar
   
Kirk Webb committed
916
    #
917
    # Turn admin mode back off and reboot back to the old OS
Kirk Webb's avatar
   
Kirk Webb committed
918
    #
919
920
921
922
    my %args          = ();
    $args{'name'}     = $me;
    $args{'on'}       = 0;
    $args{'clearall'} = 0;
923
    if (TBAdminMfsSelect(\%args, undef, $node_id)) {
924
	print("*** $me:\n".
925
	      "    Could not turn admin mode off for $node_id!\n");
926
	return 0;
Kirk Webb's avatar
   
Kirk Webb committed
927
928
    }

929
930
931
932
933
    %args           = ();
    $args{'name'}   = $me;
    $args{'on'}     = 0;
    $args{'reboot'} = 1;
    $args{'wait'}   = 0;
934
    if (TBAdminMfsBoot(\%args, undef, $node_id)) {
935
	print("*** $me:\n".
936
	      "    Failed to reboot $node_id on cleanup!\n");
937
	return 0;
Kirk Webb's avatar
   
Kirk Webb committed
938
939
    }

940
    return 1;
941
942
943
944
945
}

sub fatal($)
{
    my($mesg) = $_[0];
946
947

    print "$mesg\n";
Kirk Webb's avatar
   
Kirk Webb committed
948
949
950
951

    if ($needcleanup && !cleanup()) {
        print "Encountered problems cleaning up!\n";
    }
952
953
    
    #
954
    # Send a message to the testbed list. 
955
    #
956
    SENDMAIL("$user_name <$user_email>",
957
	     "Image Creation Failure on $target: $pid/$imagename",
958
959
960
	     $mesg,
	     "$user_name <$user_email>",
	     "Cc: $TBOPS",
961
	     defined($logfile) ? ($logfile->filename()) : ());
962
    
963
964
965
    if (defined($logfile)) {
	# Close up the log file so the webpage stops.
	$logfile->Close();
966
	$image->ClearLogFile();
967
    }
968
969
970
971
972
973
    
    if (defined($webtask)) {
	$webtask->status("failed");
	$webtask->imagesize(0);
	$webtask->Exited(1);
    }
974
975
    $image->Unlock()
	if ($needunlock);
976
977
978
979
    # Restore old image file. 
    if ($didbackup) {
	system("/bin/mv -f ${filename}.bak $filename");
    }
980
981
982
    exit(-1);
}

983
984
985
986
987
988
989
990
991
992
993
#
# Check progress of image creation by periodically checking the image size.
#
# Called every $checkwait seconds.
# Reports progress every $reportwait seconds.
# Gives up after $idlewait seconds without a size change.
#
sub check_progress($$)
{
    my (undef, $statusp) = @_;

994
995
996
997
998
    if ($runticks == 0) {
	print "$node_id: started image capture, ".
	    "waiting up to " . int($maxwait/60) . " minutes\n";
    }

999
1000
    #
    # XXX frisbee uploader uploads into a temporary file and then moves
For faster browsing, not all history is shown. View entire blame