Commit 23a230e8 authored by Leigh Stoller's avatar Leigh 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
# This handles all the OS TB commands:
# create-os
# set-dnard-os
# set-node-os
# Yet to do:
......@@ -17,11 +15,11 @@ my $DBNAME = "@TBDBNAME@";
push(@INC, $IRLIB);
require libir;
if ($#ARGV != 1) {
print STDERR "Syntax: $0 ir_file tbcmdfile\n";
if ($#ARGV != 0) {
print STDERR "Syntax: $0 ir_file\n";
exit 1;
}
($irfile,$tbcmdfile) = @ARGV;
$irfile = $ARGV[0];
use DBI;
......@@ -39,18 +37,14 @@ if ($@) {
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
# %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
# XXX - Should grab this from the DB eventually
$delayos = "FBSD40-STD";
$delayos = "FBSD40-STD";
$delaytype = "pc";
$raw = eval {&ir_get("/topology/nodes")};
if ($@) {
print STDERR "Could not load /topology/nodes.\n";
......@@ -62,7 +56,8 @@ foreach (split("\n",$raw)) {
$type = $info[1];
$nodetype{$node} = $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;
if ($rr == 0) {
push(@ERRORS,"Invalid type $type");
......@@ -77,7 +72,7 @@ foreach (split("\n",$raw)) {
# Load the nodemap and set up an initial os table
# 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")};
if ($@) {
print STDERR "Could not load /virtual/nodes. Run assign first.\n";
......@@ -87,78 +82,64 @@ foreach (split("\n",$raw)) {
($virtual,$physical) = split;
$nodemap{$virtual} = $physical;
if (!defined($nodetype{$virtual})) {
# assume to be a delay node
# assume to be a delay node.
$os{$physical} = $delayos;
$nodetype{$virtual} = $delaytype;
} else {
$os{$physical} = $ostype{$nodetype{$virtual}};
}
}
# Now we parse the tbcmd file
# %images is indexed by label and contains "$path $partition"
while (<TBCMD>) {
chop;
@line = split;
$_ = $line[0];
SWITCH: {
/^tb-create-os$/ && do {
# Should add some checking for partition
if ($#line != 3) {
push(@ERRORS,"Syntax: tb-create-os label path partition");
} else {
($label,$path,$partition) = @line[1..3];
$images{$label} = "$path $partition";
}
last SWITCH;
};
/^tb-set-node-os$/ && do {
if ($#line != 2) {
push(@ERRORS,"Syntax: tb-set-node-os nodespec os");
} else {
($nodespec,$label) = @line[1..2];
# Do initial checking on label
# We also get the valid type for this image and store it
# in valid_type;
if (! defined($images{$label})) {
# Check DB
$sth = $dbh->prepare("SELECT type from disk_images where image_id = \"$label\"");
$rr = $sth->execute;
if ($rr == 0) {
# Bad OS
$sth->finish;
push(@ERRORS,"Unknown OS label $label");
last SWITCH;
} else {
@result = $sth->fetchrow_array;
$sth->finish;
$valid_type = $result[0];
}
} else {
# Custom image - don't know type info
$valid_type = "";
}
foreach (keys(%nodemap)) {
if (eval("/^$nodespec" . '$' . "/")) {
if (($valid_type ne "") &&
($nodetype{$_} ne $valid_type)) {
push(@ERRORS,"$_ is of type $nodetype($_), OS $label is of type $valid_type");
$os{$nodemap{$_}} = "INVALID";
} else {
$os{$nodemap{$_}} = $label;
}
}
}
}
last SWITCH;
};
/^tb-set-node-deltas$/ && do {
# PLACE HOLDER
last SWITCH;
};
}
#
# Read in the osid section. We overwrite the default for any node listed
# in the osid section.
#
if (! &ir_exists("/osid")) {
print STDERR "IR incomplete: No /osid.\n";
exit(1);
}
foreach (split("\n",&ir_get("/osid"))) {
my ($node,$osid) = /^([^ ]+)(.*)$/;
$osid =~ s/^ //;
$os{$nodemap{$node}} = $osid;
}
#
# Now verify that the OSID is valid and appropriate for the node type.
#
# os_path is indexed by physical node id and contains the path.
#
foreach my $virt (keys(%nodemap)) {
my $phys = $nodemap{$virt};
my $osid = $os{$phys};
$sth = $dbh->prepare("SELECT machinetype,path FROM os_info ".
"where osid = \"$osid\"");
$rr = $sth->execute;
if ($rr == 0) {
# Bad OSID
$sth->finish;
push(@ERRORS,"Unknown OSID: $osid");
next;
}
@result = $sth->fetchrow_array;
$sth->finish;
$valid_type = $result[0];
if ($nodetype{$virt} ne $valid_type) {
push(@ERRORS, "Node $virt is type $nodetype{$virt}, ".
"but $osid is for $valid_type.\n");
next;
}
$os{$phys} = $osid;
if (defined($result[1])) {
$os_path{$phys} = $result[1];
}
else {
$os_path{$phys} = "";
}
}
close(TBCMD);
# Check if we're good so far
if ($#ERRORS != -1) {
......@@ -168,6 +149,21 @@ if ($#ERRORS != -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.
if (! &ir_exists("/cmdline")) {
print STDERR "IR incomplete: No /cmdline.\n";
......@@ -228,46 +224,3 @@ foreach (values(%nodemap)) {
$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 @@
# set-node-cmdline
# set-node-rpms
# set-node-startup
# set-node-os
#
# Configure variables
......@@ -94,6 +95,7 @@ open(TBCMD,$tbcmdfile) || do {
# hwtype is indexed by node and contains the type
# linkloss is indexed by src:dst and contains the loss rate
$osids = ();
$cmdlines = ();
$startups = ();
$rpms = ();
......@@ -151,6 +153,16 @@ while (<TBCMD>) {
# lanloss(lan) = loss
$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") {
if ($#line < 2) {
push(@ERRORS,"Syntax: tb-set-node-cmdline node cmdline");
......@@ -265,6 +277,14 @@ open(IRFILE,">> $irfile") || do {
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";
foreach $node (keys(%nodes)) {
if (defined($rpms{$node})) {
......
#!/usr/bin/perl -wT
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.
# Paths to the images are wired in.
# Path to netdisk is wired in.
# Need to reset the partitions when reloading the entire disk.
# Path to netdisk is wired in (should come from os_info table).
# wd0 wired in. Should come from node_types table in DB
#
#
# Load an image onto a disk. We support the loading of an image
# into a particular partition, or onto the entire disk. Partitions
# are numbered from 1-4, with 0 being the special "entire disk" load.
# 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.
# 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
......@@ -36,13 +33,13 @@ my $NETDISK = "/tftpboot/netdisk";
my $PAPERADDR = "boss.emulab.net";
my $PLASTICADDR = "users.emulab.net";
my $nodereboot = "$TB/bin/node_reboot";
my $dbg = 1;
my %waitfor = ();
my $SAVEUID = $UID;
my $dbg = 0;
my @row;
my %imageid_row = ();
my @nodes = ();
my $name = "";
my $mereuser = 0;
my $setuponly = 0;
my $failures = 0;
# un-taint path
......@@ -52,20 +49,31 @@ delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
$| = 1; #Turn off line buffering on output
#
# Set up for querying the database.
#
use Mysql;
my $DB = Mysql->connect("localhost", $DBNAME, "script", "none");
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
%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".
"Writes OS image to a node partition.\n");
#
# Untaint args.
#
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 ) {
if ($node =~ /^([-\@\w]+)$/) {
$node = $1;
......@@ -77,6 +85,12 @@ foreach my $node ( @ARGV ) {
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
# want. Normal users can only change nodes in experiments in their
......@@ -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
#
......@@ -109,39 +150,28 @@ if ($mereuser) {
}
}
#
# We only support 0 or 4 right now.
#
if ($imagepart != 0 && $imagepart != 4) {
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");
}
my $loadpart = $imageid_row{'loadpart'};
my $loadlen = $imageid_row{'loadlength'};
my $imagepath = $imageid_row{'path'};
my $defosid = $imageid_row{'default_osid'};
#
# 0 means load the entire disk.
# 0 means load the entire disk.
#
my $diskpart = "";
if ($imagepart) {
$diskpart = "wd0:s${imagepart}";
if ($loadpart) {
$diskpart = "wd0:s${loadpart}";
}
else {
$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 = "";
if ($mereuser) {
if ($imagepid) {
if (! ($imagepath =~ /^\/proj\//)) {
die("Your image must reside in /proj\n");
}
......@@ -152,49 +182,68 @@ else {
}
#
# Do the best we can
# Loop for each node.
#
foreach my $node (@nodes) {
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 ".
"def_boot_image_id='$imageid',def_boot_path='' ".
"def_boot_osid='$defosid',def_boot_path='' ".
"where node_id='$pc'");
if ($sth == 0) {
die("Database update failed (nodes def_boot). Aborted...\n");
die("Database update failed (nodes def_boot_osid).");
}
#
# Assign partition table entry.
#
if ($imagepart) {
$sth = $DB->query("delete from partitions where ".
"partition='$imagepart' and node_id='$pc'");
$sth = $DB->query("insert into partitions ".
"(node_id,partition,image_id) ".
"values ('$pc','$imagepart',$imageid)");
# Assign partition table entries for each partition in the image.
# This is complicated by the fact that an image that covers only
# part of the slices, should only change the partition table entries
# for the subset of slices that are written to disk. In reality, this
# 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
# a user generated image. Not sure how to handle this yet. For now
# lets just say that a user defined images essentially wipe the disk
# 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) {
die("Database delete failed (partitions). Aborted...\n");
die("Database update failed (partitions table). Aborted ...");
}
}
print STDOUT "Setting up reload for $pc\n";
$sth = $DB->query("update nodes set ".
"next_boot_path='$NETDISK',".
"next_boot_cmd_line='$cmdline' ".
"where node_id='$pc'");
if ($sth == 0) {
die("Database update failed (nodes next_boot). Aborted...\n");
die("Database update failed (nodes next_boot). Aborted ...");
}
#
# Fire off a reboot.
#
if (system("$nodereboot $pc")) {
print STDERR "Node $pc could not be rebooted!\n";
$failures++;
if (! $setuponly) {
if (system("$nodereboot $pc")) {
print STDERR "Node $pc could not be rebooted!\n";
$failures++;
}
}
}
......
This diff is collapsed.
......@@ -29,8 +29,6 @@ my $TBOPS = "@TBOPSEMAIL@";
my $TYPE = "pc";
my $reloader = "$TB/sbin/sched_reload";
my $IMAGE = "/usr/testbed/images/wd0-all.ndz";
my $PART = 0;
my $logfile = "$TB/log/reloadlog";
my $debug = 0;
......@@ -75,7 +73,7 @@ my $DB = Mysql->connect("localhost", $DBNAME, "script", "none");
# Loop, looking for nodes to reload.
#
while (1) {
my($count, $which, @row, $osid, $node);
my($count, $which, @row, $imageid, $node);
#
# Find all of the free node that have not been reloaded (no pid entry
......@@ -110,19 +108,19 @@ while (1) {
$node = $row[0];
#
# Query for the default OSID. I do this each time through the loop
# Query for the default imageid. I do this each time through the loop
# in case it gets changed in the DB.
#
$query_result =
DBquery("select image_id from node_types where type='$TYPE'");
DBquery("select imageid from node_types where type='$TYPE'");
if (! $query_result) {
print "DB Error getting node type. Waiting a bit.\n";
sleep(10);
next;
}
@row = $query_result->fetchrow_array();
$osid = $row[0];
@row = $query_result->fetchrow_array();
$imageid = $row[0];
print "Trying to reload $node ...\n";
......@@ -132,7 +130,7 @@ while (1) {
# then don't schedule a reload for later. Just fail outright.
# We will try again in a bit.
#
if (system("$reloader -f $osid $PART $IMAGE $node")) {
if (system("$reloader -f $imageid $node")) {
#
# Could not get it. Wait and go around again.
#
......
......@@ -8,12 +8,11 @@ use Getopt::Std;
# testbed:reloading. Otherwise, put the right info into the database, and
# nfree will do it when the node gets freed.
#
# usage: sched_reload [-f] <imageid> <imagepart> <imagepath> <node> [node ...]
# usage: sched_reload [-f] <imageid> <node> [node ...]
#
sub usage()
{
print STDOUT "Usage: sched_reload [-f] <imageid> <imagepart> <imagepath> ".
"<node> [node ...]\n".
print STDOUT "Usage: sched_reload [-f] <imageid> <node> [node ...]\n".
"Use the -f to force reload. Fail if node cannot be reserved.\n";
exit(-1);
}
......@@ -31,6 +30,7 @@ my $mereuser = 0;
my $error = 0;
my $debug = 0;
my $force = 0;
my @nodes = ();
my @row;
# un-taint path
......@@ -47,17 +47,34 @@ $| = 1; #Turn off line buffering on output
if (! getopts($optlist, \%options)) {
usage();
}
if (@ARGV != 4) {
if (@ARGV != 2) {
usage();
}
if (defined($options{"f"})) {
$force = $options{"f"};
}
my $imageid = shift;
my $imagepart = shift;
my $imagepath = shift;
my @nodes = @ARGV;
#
# Untaint args.
#
if ($imageid =~ /^([-\@\w.\+]+)$/) {
$imageid = $1;
}
else {
die("Bad data in $imageid.");
}
foreach my $node ( @ARGV ) {
if ($node =~ /^([-\@\w]+)$/) {
$node = $1;
}
else {
die("Bad node name: $node.");
}
push(@nodes, $node);
}
#
# Set up for querying the database.
......@@ -89,53 +106,12 @@ if ($mereuser) {
}
#
# We only support 0 or 4 right now.
#
if ($imagepart != 0 && $imagepart != 4) {
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");
}
#
# Do the best we can
# A loop.
#
my @load_list=();
foreach my $node (@nodes) {
my $pc = $node;
# Untaint the arguments. Sheer idiocy.
#
if ($pc =~ /^([-\@\w.]+)$/) {
$pc = $1;
}
if ($imageid =~ /^([-\@\w.]+)$/) {
$imageid = $1;