Commit 23a230e8 authored by Leigh B. Stoller's avatar Leigh B. Stoller

A slew of changes for new images/os_info tables. disk_images is gone,

replaced by the "images" table. New os_info table is added. New web
pages to add and delete OSIDs to/from the os_info table, for use in
the NS file. tb-create-os is gone. handle_os no longer operates on the
tbcmds file, and no longer writes anything into the ir file. Moved the
setting up of os state (nodes table) from os_setup to handle_os, where
it should be. os_load and sched_reload now take a single argument, the
name of the imageid from the images table.
parent b60fc1c5
#!/usr/bin/perl -w #!/usr/bin/perl -w
# This handles all the OS TB commands: # This handles all the OS TB commands:
# create-os
# set-dnard-os
# set-node-os # set-node-os
# Yet to do: # Yet to do:
...@@ -17,11 +15,11 @@ my $DBNAME = "@TBDBNAME@"; ...@@ -17,11 +15,11 @@ my $DBNAME = "@TBDBNAME@";
push(@INC, $IRLIB); push(@INC, $IRLIB);
require libir; require libir;
if ($#ARGV != 1) { if ($#ARGV != 0) {
print STDERR "Syntax: $0 ir_file tbcmdfile\n"; print STDERR "Syntax: $0 ir_file\n";
exit 1; exit 1;
} }
($irfile,$tbcmdfile) = @ARGV; $irfile = $ARGV[0];
use DBI; use DBI;
...@@ -39,18 +37,14 @@ if ($@) { ...@@ -39,18 +37,14 @@ if ($@) {
exit(1); exit(1);
} }
# open the tbcmd file
open(TBCMD,$tbcmdfile) || do {
print STDERR "Could not open $tbcmdfile.\n";
exit(1);
};
# Load the node types and grab the default OS from the database # Load the node types and grab the default OS from the database
# %nodetype is indexed by virtual node id and contains the type # %nodetype is indexed by virtual node id and contains the type
# %defaultos is indexed by type and contains the default OS # %ostype is indexed by type and contains the default OS
# $delayos is the delay os, currently assumed to be os of PC's # $delayos is the delay os, currently assumed to be os of PC's
# XXX - Should grab this from the DB eventually # XXX - Should grab this from the DB eventually
$delayos = "FBSD40-STD"; $delayos = "FBSD40-STD";
$delaytype = "pc";
$raw = eval {&ir_get("/topology/nodes")}; $raw = eval {&ir_get("/topology/nodes")};
if ($@) { if ($@) {
print STDERR "Could not load /topology/nodes.\n"; print STDERR "Could not load /topology/nodes.\n";
...@@ -62,7 +56,8 @@ foreach (split("\n",$raw)) { ...@@ -62,7 +56,8 @@ foreach (split("\n",$raw)) {
$type = $info[1]; $type = $info[1];
$nodetype{$node} = $type; $nodetype{$node} = $type;
if (! defined($ostype{$type})) { if (! defined($ostype{$type})) {
$sth = $dbh->prepare("SELECT image_id from node_types where type = \"$type\""); $sth = $dbh->prepare("SELECT osid from os_info ".
"where machinetype=\"$type\"");
$rr = $sth->execute; $rr = $sth->execute;
if ($rr == 0) { if ($rr == 0) {
push(@ERRORS,"Invalid type $type"); push(@ERRORS,"Invalid type $type");
...@@ -77,7 +72,7 @@ foreach (split("\n",$raw)) { ...@@ -77,7 +72,7 @@ foreach (split("\n",$raw)) {
# Load the nodemap and set up an initial os table # Load the nodemap and set up an initial os table
# nodemap is indexed by virtual node id and contains the physical node id # nodemap is indexed by virtual node id and contains the physical node id
# os is indexed by physical node id and contains the image id # os is indexed by physical node id and contains the osid
$raw = eval {&ir_get("/virtual/nodes")}; $raw = eval {&ir_get("/virtual/nodes")};
if ($@) { if ($@) {
print STDERR "Could not load /virtual/nodes. Run assign first.\n"; print STDERR "Could not load /virtual/nodes. Run assign first.\n";
...@@ -87,78 +82,64 @@ foreach (split("\n",$raw)) { ...@@ -87,78 +82,64 @@ foreach (split("\n",$raw)) {
($virtual,$physical) = split; ($virtual,$physical) = split;
$nodemap{$virtual} = $physical; $nodemap{$virtual} = $physical;
if (!defined($nodetype{$virtual})) { if (!defined($nodetype{$virtual})) {
# assume to be a delay node # assume to be a delay node.
$os{$physical} = $delayos; $os{$physical} = $delayos;
$nodetype{$virtual} = $delaytype;
} else { } else {
$os{$physical} = $ostype{$nodetype{$virtual}}; $os{$physical} = $ostype{$nodetype{$virtual}};
} }
} }
# Now we parse the tbcmd file
# %images is indexed by label and contains "$path $partition" #
while (<TBCMD>) { # Read in the osid section. We overwrite the default for any node listed
chop; # in the osid section.
@line = split; #
$_ = $line[0]; if (! &ir_exists("/osid")) {
SWITCH: { print STDERR "IR incomplete: No /osid.\n";
/^tb-create-os$/ && do { exit(1);
# Should add some checking for partition }
if ($#line != 3) { foreach (split("\n",&ir_get("/osid"))) {
push(@ERRORS,"Syntax: tb-create-os label path partition"); my ($node,$osid) = /^([^ ]+)(.*)$/;
} else { $osid =~ s/^ //;
($label,$path,$partition) = @line[1..3]; $os{$nodemap{$node}} = $osid;
$images{$label} = "$path $partition"; }
}
last SWITCH; #
}; # Now verify that the OSID is valid and appropriate for the node type.
/^tb-set-node-os$/ && do { #
if ($#line != 2) { # os_path is indexed by physical node id and contains the path.
push(@ERRORS,"Syntax: tb-set-node-os nodespec os"); #
} else { foreach my $virt (keys(%nodemap)) {
($nodespec,$label) = @line[1..2]; my $phys = $nodemap{$virt};
# Do initial checking on label my $osid = $os{$phys};
# We also get the valid type for this image and store it
# in valid_type; $sth = $dbh->prepare("SELECT machinetype,path FROM os_info ".
if (! defined($images{$label})) { "where osid = \"$osid\"");
# Check DB $rr = $sth->execute;
$sth = $dbh->prepare("SELECT type from disk_images where image_id = \"$label\""); if ($rr == 0) {
$rr = $sth->execute; # Bad OSID
if ($rr == 0) { $sth->finish;
# Bad OS push(@ERRORS,"Unknown OSID: $osid");
$sth->finish; next;
push(@ERRORS,"Unknown OS label $label"); }
last SWITCH; @result = $sth->fetchrow_array;
} else { $sth->finish;
@result = $sth->fetchrow_array; $valid_type = $result[0];
$sth->finish;
$valid_type = $result[0]; if ($nodetype{$virt} ne $valid_type) {
} push(@ERRORS, "Node $virt is type $nodetype{$virt}, ".
} else { "but $osid is for $valid_type.\n");
# Custom image - don't know type info next;
$valid_type = ""; }
} $os{$phys} = $osid;
foreach (keys(%nodemap)) {
if (eval("/^$nodespec" . '$' . "/")) { if (defined($result[1])) {
if (($valid_type ne "") && $os_path{$phys} = $result[1];
($nodetype{$_} ne $valid_type)) { }
push(@ERRORS,"$_ is of type $nodetype($_), OS $label is of type $valid_type"); else {
$os{$nodemap{$_}} = "INVALID"; $os_path{$phys} = "";
} else { }
$os{$nodemap{$_}} = $label;
}
}
}
}
last SWITCH;
};
/^tb-set-node-deltas$/ && do {
# PLACE HOLDER
last SWITCH;
};
}
} }
close(TBCMD);
# Check if we're good so far # Check if we're good so far
if ($#ERRORS != -1) { if ($#ERRORS != -1) {
...@@ -168,6 +149,21 @@ if ($#ERRORS != -1) { ...@@ -168,6 +149,21 @@ if ($#ERRORS != -1) {
exit(1); exit(1);
} }
#
# Now update the nodes table in DB.
#
foreach my $virt (keys(%nodemap)) {
my $phys = $nodemap{$virt};
my $osid = $os{$phys};
my $path = $os_path{$phys};
$sth = $dbh->prepare("UPDATE nodes set def_boot_osid = \"$osid\", ".
"def_boot_path = \"$path\" ".
"where node_id = \"$phys\"");
$sth->execute;
$sth->finish;
}
## Handle cmdline/startup/etc. ## Handle cmdline/startup/etc.
if (! &ir_exists("/cmdline")) { if (! &ir_exists("/cmdline")) {
print STDERR "IR incomplete: No /cmdline.\n"; print STDERR "IR incomplete: No /cmdline.\n";
...@@ -228,46 +224,3 @@ foreach (values(%nodemap)) { ...@@ -228,46 +224,3 @@ foreach (values(%nodemap)) {
$sth->finish; $sth->finish;
} }
# Let's do some output
# We allow running this on an IR file that has already been handle_os'ed
# it simply overwrites in that case.
if (! &ir_exists("/os")) {
# Fresh file, just append
open(IRFILE,">> $irfile") || do {
print STDERR "Could not open $irfile for appending.\n";
exit(1);
};
print IRFILE "START os\n";
print IRFILE "START images\n";
foreach (sort(keys(%images))) {
print IRFILE "$_ $images{$_}\n";
}
print IRFILE "END images\n";
print IRFILE "START nodes\n";
foreach (sort(keys(%os))) {
print IRFILE "$_ $os{$_}\n";
}
print IRFILE "END nodes\n";
print IRFILE "END os\n";
close(IRFILE);
} else {
# Here we use libir to overwrite the sections.
$images_raw = "";
foreach (sort(keys(%images))) {
$images_raw .= "$_ $images{$_}\n";
}
&ir_set("/os/images",$images_raw);
$nodes_raw = "";
foreach (sort(keys(%os))) {
$nodes_raw .= "$_ $os{$_}\n";
}
&ir_set("/os/nodes",$nodes_raw);
eval {&ir_write($irfile)};
if ($@) {
print STDERR "Could not write $irfile ($@)\n";
exit(1);
}
}
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
# set-node-cmdline # set-node-cmdline
# set-node-rpms # set-node-rpms
# set-node-startup # set-node-startup
# set-node-os
# #
# Configure variables # Configure variables
...@@ -94,6 +95,7 @@ open(TBCMD,$tbcmdfile) || do { ...@@ -94,6 +95,7 @@ open(TBCMD,$tbcmdfile) || do {
# hwtype is indexed by node and contains the type # hwtype is indexed by node and contains the type
# linkloss is indexed by src:dst and contains the loss rate # linkloss is indexed by src:dst and contains the loss rate
$osids = ();
$cmdlines = (); $cmdlines = ();
$startups = (); $startups = ();
$rpms = (); $rpms = ();
...@@ -151,6 +153,16 @@ while (<TBCMD>) { ...@@ -151,6 +153,16 @@ while (<TBCMD>) {
# lanloss(lan) = loss # lanloss(lan) = loss
$lanloss{$line[1]} = $line[2]; $lanloss{$line[1]} = $line[2];
} }
} elsif ($line[0] eq "tb-set-node-os") {
if ($#line < 2) {
push(@ERRORS,"Syntax: tb-set-node-os node osid");
next;
}
if (! defined($nodes{$line[1]})) {
push(@ERRORS,"$line[1] is not a valid node.");
next;
}
$osids{$line[1]} = $line[2];
} elsif ($line[0] eq "tb-set-node-cmdline") { } elsif ($line[0] eq "tb-set-node-cmdline") {
if ($#line < 2) { if ($#line < 2) {
push(@ERRORS,"Syntax: tb-set-node-cmdline node cmdline"); push(@ERRORS,"Syntax: tb-set-node-cmdline node cmdline");
...@@ -265,6 +277,14 @@ open(IRFILE,">> $irfile") || do { ...@@ -265,6 +277,14 @@ open(IRFILE,">> $irfile") || do {
exit(1); exit(1);
}; };
print IRFILE "START osid\n";
foreach $node (keys(%nodes)) {
if (defined($osids{$node})) {
print IRFILE "$node $osids{$node}\n";
}
}
print IRFILE "END osid\n";
print IRFILE "START rpms\n"; print IRFILE "START rpms\n";
foreach $node (keys(%nodes)) { foreach $node (keys(%nodes)) {
if (defined($rpms{$node})) { if (defined($rpms{$node})) {
......
#!/usr/bin/perl -wT #!/usr/bin/perl -wT
use English; use English;
use Getopt::Std;
#
# So, I now realize that disk_images is rather badly named, or maybe just
# has the wrong information in it. It does not describe disk images, but
# instead describes OSs. What we really need is a disk_images table that
# describes an "image". What partitions have what OSs in them, which we
# can use to load up the partitions table for each node from something that
# describes the image that just got dropped onto the disk. Well, such is
# life.
#
# #
# XXX Paper and plastic IP addresses wired into the kernel choice. # XXX Paper and plastic IP addresses wired into the kernel choice.
# Paths to the images are wired in. # Path to netdisk is wired in (should come from os_info table).
# Path to netdisk is wired in. # wd0 wired in. Should come from node_types table in DB
# Need to reset the partitions when reloading the entire disk.
# #
# #
# Load an image onto a disk. We support the loading of an image # Load an image onto a disk. The image must be in the DB images table,
# into a particular partition, or onto the entire disk. Partitions # which defines how/where to load, and what partitions are affected.
# are numbered from 1-4, with 0 being the special "entire disk" load. # The nodes and partitions tables are updated appropriately.
# #
# usage: os_load <imageid> <imagepart> <imagepath> <node> [node ...] # usage: os_load [-s] <imageid> <node> [node ...]
# #
sub usage()
{
print STDOUT "Usage: os_load [-s] <imageid> <node> [node ...]\n".
"Use the -s to setup reload only, but do not issue a reboot\n";
exit(-1);
}
my $optlist = "s";
# #
# Configure variables # Configure variables
...@@ -36,13 +33,13 @@ my $NETDISK = "/tftpboot/netdisk"; ...@@ -36,13 +33,13 @@ my $NETDISK = "/tftpboot/netdisk";
my $PAPERADDR = "boss.emulab.net"; my $PAPERADDR = "boss.emulab.net";
my $PLASTICADDR = "users.emulab.net"; my $PLASTICADDR = "users.emulab.net";
my $nodereboot = "$TB/bin/node_reboot"; my $nodereboot = "$TB/bin/node_reboot";
my $dbg = 1; my $dbg = 0;
my %waitfor = ();
my $SAVEUID = $UID;
my @row; my @row;
my %imageid_row = ();
my @nodes = (); my @nodes = ();
my $name = ""; my $name = "";
my $mereuser = 0; my $mereuser = 0;
my $setuponly = 0;
my $failures = 0; my $failures = 0;
# un-taint path # un-taint path
...@@ -52,20 +49,31 @@ delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; ...@@ -52,20 +49,31 @@ delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
$| = 1; #Turn off line buffering on output $| = 1; #Turn off line buffering on output
# #
# Set up for querying the database. # Parse command arguments. Once we return from getopts, all that should be
# # left are the required arguments.
use Mysql; #
my $DB = Mysql->connect("localhost", $DBNAME, "script", "none"); %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (@ARGV != 2) {
usage();
}
if (defined($options{"s"})) {
$setuponly = 1;
}
my $imageid = shift;
if ( $#ARGV < 3) { #
die("Usage: os_load <imageid> <imagepart> <imagepath> <node> [node ...]\n". # Untaint args.
"Writes OS image to a node partition.\n"); #
if ($imageid =~ /^([-\@\w.\+]+)$/) {
$imageid = $1;
}
else {
die("Bad data in $imageid.");
} }
my $imageid = shift;
my $imagepart = shift;
my $imagepath = shift;
# Untaint the nodes.
foreach my $node ( @ARGV ) { foreach my $node ( @ARGV ) {
if ($node =~ /^([-\@\w]+)$/) { if ($node =~ /^([-\@\w]+)$/) {
$node = $1; $node = $1;
...@@ -77,6 +85,12 @@ foreach my $node ( @ARGV ) { ...@@ -77,6 +85,12 @@ foreach my $node ( @ARGV ) {
push(@nodes, $node); push(@nodes, $node);
} }
#
# Set up for querying the database.
#
use Mysql;
my $DB = Mysql->connect("localhost", $DBNAME, "script", "none");
# #
# Figure out who called us. Root and admin types can do whatever they # Figure out who called us. Root and admin types can do whatever they
# want. Normal users can only change nodes in experiments in their # want. Normal users can only change nodes in experiments in their
...@@ -93,6 +107,33 @@ if ($UID != 0) { ...@@ -93,6 +107,33 @@ if ($UID != 0) {
} }
} }
#
# Grab the imageid description from the DB. The permission check for this
# is that mere user can load an image from any project he/she is a member of,
# or any image that has a null pid field, since those are defined to be
# open to anyone.
#
$db_result = $DB->query("select * from images where imageid='$imageid'");
if ($db_result->numrows < 1) {
die("No such imageid $imageid!");
}
%imageid_row = $db_result->fetchhash();
my $imagepid = 0;
if (defined($imageid_row{'pid'})) {
$imagepid = $imageid_row{'pid'};
}
if ($mereuser && $imagepid) {
$db_result = $DB->query("select * from proj_memb ".
"where uid='$name' and pid='$imagepid'");
if ($db_result->numrows < 1) {
die("You do not have permission to load imageid $imageid!");
}
}
# #
# Check to make sure that mere user is allowed to muck with nodes # Check to make sure that mere user is allowed to muck with nodes
# #
...@@ -109,39 +150,28 @@ if ($mereuser) { ...@@ -109,39 +150,28 @@ if ($mereuser) {
} }
} }
# my $loadpart = $imageid_row{'loadpart'};
# We only support 0 or 4 right now. my $loadlen = $imageid_row{'loadlength'};
# my $imagepath = $imageid_row{'path'};
if ($imagepart != 0 && $imagepart != 4) { my $defosid = $imageid_row{'default_osid'};
die("Only the entire disk or partition 4 can be loaded.\n");
}
#
# The image has to be accessible, and it must reside in the right places.
#
if (! -e $imagepath) {
die("Cannot access $imagepath.\n");
}
if (! -f $imagepath) {
die("$imagepath is not a plain file.\n");
}
# #
# 0 means load the entire disk. # 0 means load the entire disk.
# #
my $diskpart = ""; my $diskpart = "";
if ($imagepart) { if ($loadpart) {
$diskpart = "wd0:s${imagepart}"; $diskpart = "wd0:s${loadpart}";
} }
else { else {
$diskpart = "wd0"; $diskpart = "wd0";
} }
# #
# Admin types do whatever they like # For now, all testbed default images from from paper and all pid specific
# images come from plastic:/proj.
# #
my $cmdline = ""; my $cmdline = "";
if ($mereuser) { if ($imagepid) {
if (! ($imagepath =~ /^\/proj\//)) { if (! ($imagepath =~ /^\/proj\//)) {
die("Your image must reside in /proj\n"); die("Your image must reside in /proj\n");
} }
...@@ -152,49 +182,68 @@ else { ...@@ -152,49 +182,68 @@ else {
} }
# #
# Do the best we can # Loop for each node.
# #
foreach my $node (@nodes) { foreach my $node (@nodes) {
my $pc = $node; my $pc = $node;
print STDOUT "Changing default OS for $pc to $imageid\n"; print STDOUT "Changing default OS for $pc to $defosid\n";
$sth = $DB->query("update nodes set ". $sth = $DB->query("update nodes set ".
"def_boot_image_id='$imageid',def_boot_path='' ". "def_boot_osid='$defosid',def_boot_path='' ".
"where node_id='$pc'"); "where node_id='$pc'");
if ($sth == 0) { if ($sth == 0) {
die("Database update failed (nodes def_boot). Aborted...\n"); die("Database update failed (nodes def_boot_osid).");
} }
# #
# Assign partition table entry. # Assign partition table entries for each partition in the image.
# # This is complicated by the fact that an image that covers only
if ($imagepart) { # part of the slices, should only change the partition table entries
$sth = $DB->query("delete from partitions where ". # for the subset of slices that are written to disk. In reality, this
"partition='$imagepart' and node_id='$pc'"); # is silly since there is no telling what the disk is going to end
# up looking like after a partial image is written, especially if its
$sth = $DB->query("insert into partitions ". # a user generated image. Not sure how to handle this yet. For now
"(node_id,partition,image_id) ". # lets just say that a user defined images essentially wipe the disk
"values ('$pc','$imagepart',$imageid)"); # except for the stuff they write.
#
for ($i = 1; $i <= 4; $i++) {
my $partname = "part${i}_osid";
if (defined($imageid_row{$partname})) {
my $osid = $imageid_row{$partname};
$sth = $DB->query("replace into partitions ".
"(partition, osid, node_id) ".
"values('$i', '$osid', '$pc')");
}
else {
$sth = $DB->query("delete from partitions ".
"where node_id='$pc' and partition='$i'");
}
if ($sth == 0) { if ($sth == 0) {
die("Database delete failed (partitions). Aborted...\n"); die("Database update failed (partitions table). Aborted ...");
} }
} }
print STDOUT "Setting up reload for $pc\n"; print STDOUT "Setting up reload for $pc\n";
$sth = $DB->query("update nodes set ". $sth = $DB->query("update nodes set ".
"next_boot_path='$NETDISK',". "next_boot_path='$NETDISK',".
"next_boot_cmd_line='$cmdline' ". "next_boot_cmd_line='$cmdline' ".
"where node_id='$pc'"); "where node_id='$pc'");
if ($sth == 0) { if ($sth == 0) {
die("Database update failed (nodes next_boot). Aborted...\n"); die("Database update failed (nodes next_boot). Aborted ...");
} }