os_load.in 13.5 KB
Newer Older
1
#!/usr/bin/perl -wT
Leigh Stoller's avatar
Leigh Stoller committed
2
#
3
# Copyright (c) 2000-2016 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 Stoller's avatar
Leigh Stoller committed
23
#
24
use English;
25
use Getopt::Std;
26

27
#
28 29
# Load an image onto a disk. The image must be in the DB images table,
# which defines how/where to load, and what partitions are affected.
30
# The nodes and partitions tables are updated appropriately.
31
#
32 33
sub usage()
{
34
    print("Usage: os_load [-s] [[-p <pid>] -i <imageid>] <node> [node ...]\n".
35
	  "       os_load [-s] [[-p <pid>] -i <imageid>] [-V] -e pid,eid\n".
36
	  "       os_load -l\n".
37 38
	  "Use -i to specify a comma seperated list of image IDs.\n".
          "       Use the node default otherwise.\n".
39
	  "Use -p to specify the project ID in which to find the imageid.\n".
40 41
	  "       If the image is not found in <pid> also try ".TB_OPSPID().".\n".
	  "Use -m to specify the internal name(s) if an image ID.\n".
42
	  "Use -c to reload the current default.\n".
43
	  "Use -s to start reload, but do not wait for it to complete.\n".
44
	  "Use -w to wait for the nodes to finish booting.\n".
45
	  "Use -r to supress rebooting nodes - you need to to it yourself\n".
46
	  "Use -e to reload all the nodes in an experiment.\n" .
47 48
	  "Use -l to get a list of images you are permitted to load.\n".
	  "Use -z <style> to zero all unallocated blocks on the disk\n".
49
	  "       style==0: do not zero (same as not using -z)\n".
50
	  "       style==1: let frisbee do the zeroing\n".
51
	  "       style==2: zero disk before running frisbee\n".
52
          "Use -P to prepare the disk as if a whole disk image was loaded\n".
53
          "Use -V to load JUST the physical nodes in an experiment\n".
54
          "Use -R to push a reconfig to the node after the reload\n".
55 56
          "Use -D to set a specific debug level\n".
          "Use -o <opt1=foo,opt2=bar,...> to set custom options\n");
57 58
    exit(-1);
}
59
my $optlist   = "swldD:i:e:p:m:rz:PcRFo:V";
60 61 62 63
my $waitmode  = 1;
my $listonly  = 0;
my $debug     = 0;
my $noreboot  = 0;
64
my $zerofree  = 0;
65
my $prepare   = 0;
66
my $usecurrent= 0;
67 68
my $reconfig  = 0;
my $force     = 0;
69
my $physonly  = 0;
70
my %reload_args = ();
71
my @nodes     = ();
Leigh Stoller's avatar
Leigh Stoller committed
72
my $imagepid;
73 74
my $pid;
my $eid;
75
my @images = ();
76
my $imagenames;
77 78 79

# Configure variables
my $TB		= "@prefix@";
Leigh Stoller's avatar
Leigh Stoller committed
80

81
# Load the Testbed support stuff.
82 83
use lib "@prefix@/lib";
use libdb;
84
use libosload;
85
use libtestbed;
86 87
use Experiment;
use Node;
88
use OSImage;
89 90
use EmulabFeatures;
use User;
91

92 93 94
# Be careful not to exit on transient error
$libdb::DBQUERY_MAXTRIES = 30;

95 96 97 98
# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

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

102
#
103 104
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
105 106 107 108 109 110 111
#
my %options = ();
if (! getopts($optlist, \%options)) {
    usage();
}
$debug = 1
    if (defined($options{"d"}));
112 113 114
if (defined($options{"D"}) && $options{"D"} =~ /^(\d+)$/) {
    $debug = $1;
}
115 116

# List only mode. No need to do anymore argument processing.
117
sub dolisting();
118 119 120 121 122 123 124
if (defined($options{"l"})) {
    dolisting();
    exit(0);
}

$waitmode = 0
    if (defined($options{"s"}));
125 126
$waitmode = 2
    if (defined($options{"w"}));
127 128
$noreboot = 1
    if (defined($options{"r"}));
129 130
$zerofree = $options{"z"}
    if (defined($options{"z"}));
131 132
$prepare = 1
    if (defined($options{"P"}));
133 134
$physonly = 1
    if (defined($options{"V"}));
135 136
$usecurrent = 1
    if (defined($options{"c"}));
137 138 139 140
$reconfig = 1
    if (defined($options{"R"}));
$force   = 1
    if (defined($options{"F"}));
141 142 143 144 145 146 147 148 149 150 151 152
if (defined($options{"o"})) {
    my @kva = split(/,/,$options{"o"});
    foreach my $kv (@kva) {
        if ($kv =~ /^([\w\-]+)=([^,]+)$/) {
	    $reload_args{$1} = $2;
        }
        else {
	    print STDERR "Improper -o custom option '$kv'!\n";
	    usage();
        }
    }
}
Mike Hibler's avatar
Mike Hibler committed
153 154 155 156
if ($zerofree && $zerofree !~ /^[0-2]$/) {
    print STDERR "Improper -z option '$zerofree'\n";
    usage();
}
157

158 159 160 161
#
# Figure out which nodes. Choice of nodes on command line, or all nodes in an
# experiment. To get all free nodes, must use sched_reload. 
# 
162
my $experiment;
163
my $group;
164 165 166
if (defined($options{"e"})) {
    usage()
	if (@ARGV);
167

168 169 170 171 172
    my $pideid = $options{"e"};

    if ($pideid =~ /([-\w]*),([-\w]*)/) {
	$pid = $1;
	$eid = $2;
173

174
        $experiment = Experiment->Lookup($pid, $eid);
175 176 177 178
	if (!defined($experiment)) {
	    die("*** $0:\n".
		"    No such experiment $pid/$eid!\n");
	}
179
	$group = $experiment->GetGroup();
180
	if (! (@nodes = $experiment->NodeList(0, !$physonly))) {
181 182 183 184 185 186 187 188 189 190 191 192 193
	    die("*** $0:\n".
		"    There are no nodes in $pid/$eid!\n");
	}
    }
    else {
	die("*** $0:\n".
	    "    Invalid argument to -e option: $pideid\n");
	usage();
    }
}
else {
    usage()
	if (! @ARGV);
194 195 196 197 198 199

    foreach my $nodeid (@ARGV) {
	my $node = Node->Lookup($nodeid);
	if (!defined($node)) {
	    die("*** $0:\n".
		"    No such node $nodeid!\n");
200
	}
201 202 203
	if (!$node->IsReserved()) {
	    die("*** $0:\n".
		"    Node $nodeid is not reserved; reserve it first!\n");
204 205 206 207 208 209
        }
        else {
	    $experiment = $node->Reservation();
	    $group = $experiment->GetGroup();
        }

210 211 212 213
	push(@nodes, $node);
    }
}

214 215 216 217 218 219 220
#
# Image name (user visible name, not the internal ID). User is allowed
# to specify the pid the belongs to, but this is mostly broken cause
# images are stored in proj trees and created via NFS, so not really
# possibly to share images between projects; must be a member of the
# project. Could be fixed.
# 
Leigh Stoller's avatar
Leigh Stoller committed
221
if (defined($options{"i"})) {
222 223
    usage()
	if (defined($options{"m"}));
224 225 226

    if (defined($options{"p"})) {
	$imagepid = $options{"p"};
227 228 229 230 231 232 233 234
	
	if ($imagepid =~ /^([-\w\.\+]+)$/) {
	    $imagepid = $1;
	}
	else {
	    die("*** Bad data in imagepid: $imagepid.\n");
	}
    }
235 236
    
    $imagenames = $options{"i"};
237

238
    foreach my $imagename (split /,/, $imagenames) {
239 240
        # Default to current default if no version specified.
        my $version = undef;
241

242 243
	if ($imagename =~ /^([-\w\.\+]+)$/) {
	    $imagename = $1;
244
	}
245 246 247 248
	elsif ($imagename =~ /^([-\w\.\+]+):(\d+)$/) {
	    $imagename = $1;
	    $version   = $2;
	}
249 250 251 252 253 254 255 256 257 258
	else {
	    die("*** Bad data in imagename: $imagename.\n");
	}
	
	#
	# If -p option given, use that.
	# If in experiment mode, then use the pid of the experiment, unless
	# a -p option was given.
	# Otherwise look in the system project.
	#
259
	my $image;
260
	if (defined($imagepid)) {
261
	    $image = OSImage->Lookup($imagepid, $imagename, $version);
262
	}
263
	if (!defined($image) && defined($pid)) {
264
	    $image = OSImage->Lookup($pid, $imagename, $version);
265
	}
266
	if (!defined($image)) {
267
	    $image = OSImage->Lookup(TB_OPSPID(), $imagename, $version);
268
	}
269
	if (!defined($image)) {
270
	    die("*** $0:\n".
271
		"	 No such image $imagename!\n");
272
	}
273
	push @images, $image;
274 275
    }
}
276 277 278 279

#
# Or an internal imageID. Sometimes easier.
# 
280
if (defined($options{"m"})) {
281 282 283
    usage()
	if (defined($options{"i"}));

284
    foreach my $imageid (split /,/, $options{"m"}) {
285
        my $version;
286 287 288 289

	if ($imageid =~ /^([-\w\.\+]+)$/) {
	    $imageid = $1;
	}
290 291 292 293
	elsif ($imageid =~ /^([-\w\.\+]+):(\d+)$/) {
	    $imageid = $1;
	    $version = $2;
	}
294 295 296
	else {
	    die("*** Bad data in imageid: $imageid\n");
	}
297
	my $image = OSImage->Lookup($imageid, $version);
298 299 300 301 302
	if (!defined($image)) {
	    die("*** $0:\n".
		"	 No such image $imageid!\n");
	}
	push @images, $image;
303
    }
304
}
305

306 307 308
#
# Weed out non-imageable nodes (e.g., virtnodes, emotes, etc.)
#
Mike Hibler's avatar
Mike Hibler committed
309
my $first = 1;
310 311
my @temp = ();
foreach my $node ( @nodes ) {
312 313 314 315 316 317 318 319 320 321
    my $nodeid = $node->node_id();
    if ($node->isvirtnode()) {
	#
	# Some virtnodes can be reloaded, IF their virthost is running an OS
	# that supports subimages, and if the new child OS can run on the
	# current virthost OS.
	#
	my $parent = $node->GetPhysHost();
	my $failed = 0;
	if (defined($parent)) {
322 323 324
            my $posimage = OSImage->Lookup($parent->def_boot_osid(),
					   $parent->def_boot_osid_vers());
            if (defined($posimage) && $posimage->FeatureSupported("suboses")) {
325
                foreach my $image (@images) {
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
		    #
		    # If an image alias, which image do we use? We have
		    # to map it based on the architecture of the parent
		    # node.
		    #
		    if ($image->isImageAlias()) {
			my $real = $image->MapToImage($parent->type());
			if (!defined($real)) {
			    print "*** reload($nodeid): $image cannot be ".
				"mapped\n";
			    $failed = 1;
			    goto failed;
			}
			$image = $real;
		    }
		    my $osimage = OSImage->Lookup($image->default_osid(),
						  $image->default_vers());
		    if (!$osimage->IsSubOS()) {
			print "*** reload($nodeid): $osimage is not a subOS and thus cannot run on a virtnode, skipping!\n";
345 346
			$failed = 1;
		    }
347 348
		    elsif (!$osimage->RunsOnParent($posimage)) {
			print "*** reload($nodeid): subOS $osimage cannot run on parent node $parent running $posimage, skipping!\n";
349 350
			$failed = 1;
		    }
351 352
		    elsif (($osimage->IsTainted(TB_TAINTSTATE_USERONLY) ||
			    $osimage->IsTainted(TB_TAINTSTATE_BLACKBOX))
353
			   && !$parent->IsTainted(TB_TAINTSTATE_BLACKBOX)) {
354
			print "*** reload($nodeid): subOS $osimage is tainted, but parent node $parent is not, skipping!\n";
355 356
			$failed = 1;
		    }
357 358 359
                }
            }
            else {
360
                print "*** reload ($nodeid): parent os $posimage on $parent does not support suboses, skipping!\n";
361 362 363 364 365 366 367
                $failed = 1;
            }
        }
        else {
	    print "*** reload ($nodeid): could not find parent phys host!\n";
	    $failed = 1;
        }
368
 failed:
369 370 371 372 373 374
        if ($failed) {
	    $first = 0;
	    next;
        }
    }
    elsif (!$node->imageable()) {
Mike Hibler's avatar
Mike Hibler committed
375 376 377 378 379 380 381
	#
	# Common mistake: forget the -i before the imagename, e.g.,
	# "os_load FBSD54-STD pcNN", which results in pcNN getting loaded
	# with the default image.  So if the first arg fails as a node, but
	# is an image ID, assume they have made this mistake and stop.
	#
	my $_pid = defined($imagepid) ? $imagepid : TB_OPSPID();
382
	if ($first && !defined($imagenames) && TBImageID($_pid, $nodeid)) {
383
	    print "*** reload: forgot the -i before image name $nodeid?\n";
Mike Hibler's avatar
Mike Hibler committed
384 385
	    exit(1);
	}
386
	print "*** reload ($nodeid): cannot image node, skipped.\n";
Mike Hibler's avatar
Mike Hibler committed
387
	$first = 0;
388 389 390
	next;
    }
    push(@temp, $node);
Mike Hibler's avatar
Mike Hibler committed
391
    $first = 0;
392 393 394
}
@nodes = @temp;
if (! @nodes) {
395
    print "*** reload: No nodes to load. Exiting.\n";
396 397 398
    exit(0);
}

399 400 401 402 403 404 405 406 407 408 409 410
#
# Okay, call into the library using a hash of arguments. Pass a reference
# to the args array, and to a return parameter for the list of failed nodes.
#
# NB: Permission checking is done in the library. Maybe that is wrong?
#
my %osloadargs  = ();
my %failednodes = ();

$osloadargs{'debug'}    = $debug;
$osloadargs{'waitmode'} = $waitmode;
$osloadargs{'noreboot'} = $noreboot;
411
$osloadargs{'zerofree'} = $zerofree;
412
$osloadargs{'prepare'}  = $prepare;
413
$osloadargs{'nodelist'} = [ map { $_->node_id() } @nodes ];
414
# No imageid means to load the default image.
415 416
$osloadargs{'images'}   = [ @images ]
    if (@images);
417
$osloadargs{'swapinfo'} = 0;
418
$osloadargs{'usecurrent'} = $usecurrent;
419

420 421 422 423 424 425 426
#
# Allow command-line osloadargs overrides
#
foreach my $key (keys(%reload_args)) {
    $osloadargs{$key} = $reload_args{$key};
}

427
my $user = User->ThisUser();
428
if (EmulabFeatures->FeatureEnabled("NewOsload",$user,$group,$experiment)) {
429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
    require libosload_new;
    my $loadobj = libosload_new->New();
    $loadobj->debug($debug);
    #
    # XXX basically, tell devices that try not to reload (like switches)
    # that they really need to do it -- if the user or reload daemon actually
    # invokes this script, we *have* to reload!
    #
    $osloadargs{'force'} = $force;
    #
    # XXX basically, tell devices that might be reconfig'd via push from us
    # (like switches) that a reconfig should follow the reload!
    #
    $osloadargs{'reconfig'} = $reconfig;

444 445 446 447 448
    # add a few more things for feature checks down the line:
    $osloadargs{user} = $user;
    $osloadargs{group} = $group;
    $osloadargs{experiment} = $experiment;

449 450 451
    exit($loadobj->osload(\%osloadargs, \%failednodes));
}

452
exit(osload(\%osloadargs, \%failednodes));
453

454
# Print a listing of imageids.
455
sub dolisting() {
456
    my($query_result);
457

458 459 460
    if ($UID && !TBAdmin($UID)) {
	my ($me) = getpwuid($UID);
	$query_result =
461 462 463 464 465 466
	    DBQueryFatal("select distinct i.pid,i.imagename from images as i ".
			 "left join image_versions as v on ".
			 "     v.imageid=i.imageid and v.version=i.version ".
			 "left join group_membership as g on ".
			 "     g.pid_idx=i.pid_idx ".
			 "where (g.uid='$me' or v.global) ".
Leigh Stoller's avatar
Leigh Stoller committed
467
			 "order by i.pid,i.imageid");
468
    } else {
469
	$query_result =
470 471
	    DBQueryFatal("SELECT distinct pid,imagename FROM images ".
			 "order by imageid");
472 473
    }

474 475 476
    if ($query_result->numrows) {
	printf "%-12s %-20s %s\n", "Pid", "Imagename", "Description";
	printf "------------ -------------------- -------------------------\n";
477

478
	while (my ($pid,$imagename) = $query_result->fetchrow_array()) {
479
	    my $image = OSImage->Lookup($pid,$imagename);
480 481 482 483 484 485 486
	    next
		if (!defined($image));

	    my $id   = $image->imageid();
	    my $pid  = $image->pid();
	    my $name = $image->imagename();
	    my $desc = $image->description();
487 488 489

	    printf "%-12s %-20s %s\n", $pid, $name, $desc;
	}
490 491
    }
}