From 2641af4dfda6c35aec8732edd9897685d3439f11 Mon Sep 17 00:00:00 2001 From: "Leigh B. Stoller" Date: Thu, 9 Oct 2003 20:03:10 +0000 Subject: [PATCH] Reorg of two aspects of node update. * install-rpm, install-tarfile, spewrpmtar.php3, spewrpmtar.in: Pumped up even more! The db file we store in /var/db now records both the timestamp (of the file, or if remote the install time) and the MD5 of the file that was installed. Locally, we can get this info when accessing the file via NFS (copymode on or off). Remote, we use wget to get the file, and so pass the timestamp along in the URL request, and let spewrpmtar.in determine if the file has changed. If the timestamp it gets is >= to the timestamp of the file, an error code of 304 (Not Modifed) is returned. Otherwise the file is returned. If the timestamps are different (remote, server sends back an actual file), the MD5 of the file is compared against the value stored. If they are equal, update the timestamp in the db file to avoid repeated MD5s (or server downloads) in the future. If the MD5 is different, then reinstall the tarball or rpm, and update the db file with the new timestamp and MD5. Presto, we have auto update capability! Caveat: I pass along the old MD5 in the URL, but it is currently ignored. I do not know if doing the MD5 on the server is a good idea, but obviously it is easy to add later. At the moment it happens on the node, which means wasted bandwidth when the timestamp has changed, but the file has not (probably not something that will happen in typical usage). Caveat: The timestamp used on remote nodes is the time the tarfile is installed (GM time of course). We could arrange to return the timestamp of the local file back to the node, but that would mean complicating the protocol (or using an http header) and I was not in the mood for that. In typical usage, I do not think that people will be changing tarfiles and rpms so rapidly that this will make a difference, but if it does, we can change it. * node_update.in, client side watchdog, and various web pages: Deflated node_update, removing all of the older ssh code. We now assume that all nodes will auto update on a periodic basis, via the watchdog that runs on all client nodes, including plab nodes. Changed the permission check to look for new UPDATE permission (used to be UPDATEACCOUNT). As before, it requires local_root or better. The reason for this is that node_update now implies more than just updating the accounts/mounts. The web pages have been changed to explain that in addition to mounts/accounts, rpms and tarfiles will also be updated. At the moment, this is still tied to a single variable (update_accounts) in the nodes table, but as Kirk requested at the meeting, it will probably be nice to split these out in the future. Added the ability to node_update a single node in an experiment (in addition to all nodes option on the showexp page). This has been added to the shownode webpage menu options. Changed locking code to use the newer wrapper states, and to move the experiment to RUNNING_LOCKED until the update completes. This is to prevent mayhem in the rest of the system (which could be dealt with, but is not worth the trouble; people have to wait until their initiated update is complete, before they can swap out the experiment). Added "short" mode to shownode routine, equiv to the recently added short mode for showexp. I use this on the confirmation page for updating a single node, giving the user a couple of pertinent (feel good) facts before they comfirm. --- db/libdb.pm.in | 5 +- os/GNUmakefile.in | 3 + os/install-rpm | 255 ++++++++++++++++++++++---- os/install-tarfile | 255 ++++++++++++++++++++++---- tbsetup/GNUmakefile.in | 2 - tbsetup/node_update.in | 324 +++++++++++---------------------- tbsetup/spewrpmtar.in | 65 +++++-- tmcd/common/update | 40 +++- tmcd/common/watchdog | 2 +- www/dbdefs.php3.in | 7 +- www/showexp.php3 | 4 +- www/showimageid_list.php3 | 2 +- www/shownode.php3 | 5 + www/showstuff.php3 | 374 +++++++++++++++++++------------------- www/spewrpmtar.php3 | 25 ++- www/swapexp.php3 | 54 ++++-- www/updateaccounts.php3 | 157 +++++++++------- 17 files changed, 1006 insertions(+), 573 deletions(-) diff --git a/db/libdb.pm.in b/db/libdb.pm.in index 9f1bf65db..985aba1a3 100644 --- a/db/libdb.pm.in +++ b/db/libdb.pm.in @@ -42,7 +42,7 @@ use Exporter; USERSTATUS_ACTIVE USERSTATUS_FROZEN USERSTATUS_UNAPPROVED USERSTATUS_UNVERIFIED USERSTATUS_NEWUSER - TB_EXPT_READINFO TB_EXPT_MODIFY TB_EXPT_DESTROY + TB_EXPT_READINFO TB_EXPT_MODIFY TB_EXPT_DESTROY TB_EXPT_UPDATE TB_EXPT_MIN TB_EXPT_MAX TB_PROJECT_READINFO TB_PROJECT_MAKEGROUP @@ -352,8 +352,9 @@ sub TB_USERINFO_MAX() { TB_USERINFO_MODIFYINFO; } sub TB_EXPT_READINFO() { 1; } sub TB_EXPT_MODIFY() { 2; } sub TB_EXPT_DESTROY() { 3; } +sub TB_EXPT_UPDATE() { 4; } sub TB_EXPT_MIN() { TB_EXPT_READINFO; } -sub TB_EXPT_MAX() { TB_EXPT_DESTROY; } +sub TB_EXPT_MAX() { TB_EXPT_UPDATE; } # Projects. sub TB_PROJECT_READINFO() { 1; } diff --git a/os/GNUmakefile.in b/os/GNUmakefile.in index 4d90d6360..f88eb91bc 100644 --- a/os/GNUmakefile.in +++ b/os/GNUmakefile.in @@ -10,6 +10,7 @@ OBJDIR = .. SUBDIR = os LBINDIR = $(DESTDIR)/usr/local/bin +SYSTEM := $(shell uname -s) include $(OBJDIR)/Makeconf @@ -39,8 +40,10 @@ client-install: $(INSTALL) -m 755 -o root -g wheel -d $(LBINDIR) $(INSTALL_PROGRAM) $(SRCDIR)/install-tarfile $(LBINDIR)/install-tarfile $(INSTALL_PROGRAM) $(SRCDIR)/install-rpm $(LBINDIR)/install-rpm +ifneq ($(SYSTEM),Linux) $(INSTALL_PROGRAM) $(SRCDIR)/create-image $(LBINDIR)/create-image $(MAKE) -C imagezip client-install +endif $(MAKE) -C syncd client-install remote-install: diff --git a/os/install-rpm b/os/install-rpm index 490256e0c..b725be78e 100644 --- a/os/install-rpm +++ b/os/install-rpm @@ -1,17 +1,14 @@ #!/usr/bin/perl -wT - # # EMULAB-COPYRIGHT # Copyright (c) 2000-2003 University of Utah and the Flux Group. # All rights reserved. # - use English; use Getopt::Std; -use POSIX 'setsid'; +use POSIX qw(mktime); # Drag in path stuff so we can find emulab stuff. -# XXX Temporary until I have the new tmcc library finished! BEGIN { require "/etc/emulab/paths.pm"; import emulabpaths; } # @@ -54,15 +51,22 @@ my $copymode = 0; my $debug = 0; my $copyfile; my $nodeid; +my $keyhash; +my $filemd5; +my @identlines = (); +my $installed = 0; # # Load the OS independent support library. It will load the OS dependent # library and initialize itself. # use libsetup; +use libtmcc; # Protos -sub GetRPMFile($$$); +sub GetRPMFile($$$$$$$); +sub GetMD5($); +sub WriteIdentFile(); # # Must be running as root to work. @@ -112,14 +116,39 @@ else { } # -# Check to make sure this RPM has not already been installed. Update -# the file now. If the rpm fails, we got big problems. -# +# Check to make sure this rpm has not already been installed. +# If so, we get the old timestamp and md5 so we can compare against +# current ones. +# We need to update the stamp/md5 in place in case it has changed, so +# copy out all the identlines so we can write them back later. We do not +# copyout the current one of course; we make up a new line at the end +# of this script based on the new info. +# if (-e $IDENTFILE) { - if (! system("egrep -q -s '^${rpm}' $IDENTFILE")) { - print STDOUT "RPM $rpm has already been installed!\n"; - exit(1); + if (!open(IDENT, $IDENTFILE)) { + fatal("Could not open $IDENTFILE: $!"); } + while () { + if ($_ =~ /^([-\w\.\/]*) ([\d]*) ([\w]*)$/) { + my $file = $1; + my $stamp= $2; + my $md5 = $3; + + if ($file eq $rpm) { + # + # Save the info and continue; + # + $oldstamp = $stamp; + $oldmd5 = $md5; + next; + } + push(@identlines, "$file $stamp $md5"); + } + else { + warn("*** WARNING: Bad line in $IDENTFILE: $_\n"); + } + } + close(IDENT); } # @@ -133,6 +162,28 @@ if (! $copymode) { if (! -r $rpm) { fatal("$rpm does not exist or is not accessible!"); } + + # + # Compare timestamp. If no change, we are done. + # + (undef,undef,undef,undef,undef,undef, + undef,undef,undef,$filestamp) = stat($rpm); + + if (defined($oldstamp) && $oldstamp >= $filestamp) { + print STDOUT "RPM $rpm has already been installed!\n"; + exit(1); + } + # + # Otherwise compare MD5. + # + $filemd5 = GetMD5($rpm); + if (defined($oldmd5) && $filemd5 eq $oldmd5) { + print STDOUT "RPM $rpm has already been installed!\n"; + # Must write a new ident file to avoid repeated checks. + push(@identlines, "$rpm $filestamp $filemd5"); + WriteIdentFile(); + exit(1); + } } else { $copyfile = `mktemp /var/tmp/rpm.XXXXXX`; @@ -143,18 +194,29 @@ else { else { die("Bad data in copyfile name: $copyfile"); } - GetRPMFile($rpm, $copyfile, $usewget); # - # Dies on any failure! + # Dies on any failure. + # Returns >0 if server copy has not been modifed. + # Returns =0 if okay to install, and gives us new stamp/md5. # + if (GetRPMFile($rpm, $copyfile, $usewget, + $oldstamp, $oldmd5, \$filestamp, \$filemd5)) { + print STDOUT "RPM $rpm has already been installed!\n"; + if (defined($filestamp) && $filestamp != $oldstamp) { + # Must write a new ident file to avoid repeated checks. + push(@identlines, "$rpm $filestamp $oldmd5"); + WriteIdentFile(); + } + unlink($copyfile) + if (-e $copyfile); + exit(1); + } } # -# Add to index first; if fails too bad. -# -if (system("echo \"$rpm\" >> $IDENTFILE")) { - fatal("Could not update $IDENTFILE"); -} +# Okay, add new info to the list for update. +# +push(@identlines, "$rpm $filestamp $filemd5"); # # Run the RPM. @@ -162,12 +224,18 @@ if (system("echo \"$rpm\" >> $IDENTFILE")) { if ($copymode) { $rpm = $copyfile; } -system("rpm -i $rpm"); +system("rpm -U --force --nodeps $rpm"); $exit_status = $? >> 8; if ($copymode) { unlink($copyfile); } +# +# Recreate the index file if the install was okay. +# +if (!$exit_status) { + WriteIdentFile(); +} exit($exit_status); sub fatal { @@ -183,19 +251,33 @@ sub fatal { # # Get an RPM from the server via tmcc and stash. # -sub GetRPMFile($$$) +sub GetRPMFile($$$$$$$) { - my ($rpm, $copyfile, $usewget) = @_; + my ($rpm, $copyfile, $usewget, + $oldstamp, $oldmd5, $filestamp, $filemd5) = @_; my $buf; - my $bytelen; # # If copying via NFS, must watch for read errors and retry. # if (! $usewget) { + # + # Compare timestamp. If no change, we are done. + # + my (undef,undef,undef,undef,undef,undef,undef,$bytelen, + undef,$stamp) = stat($rpm); + + if (defined($oldstamp) && $oldstamp >= $stamp) { + print STDOUT "Timestamp ($stamp) for $rpm unchanged!\n" + if ($debug); + return 1; + } + # Must do this for caller so that if the MD5 has not changed, + # the caller can update the timestamp in the ident file. + $$filestamp = $stamp; + open(TMCC, "< $rpm") or fatal("Could not open rpm on server!"); - $bytelen = (stat($rpm))[7]; # # Open the target file and start dumping the data in. @@ -237,6 +319,17 @@ sub GetRPMFile($$$) } close(JFILE); close(TMCC); + + # + # Compare md5. + # + my $md5 = GetMD5($copyfile); + if (defined($oldmd5) && $oldmd5 eq $md5) { + print STDOUT "MD5 ($md5) for $rpm unchanged!\n" + if ($debug); + return 2; + } + $$filemd5 = $md5; } else { # @@ -274,10 +367,8 @@ sub GetRPMFile($$$) # # Lastly, need our boss node. - # - my ($www) = split(" ", `tmcc bossinfo`); - die("Could not get bossinfo!") - if ($?); + # + my ($www) = tmccbossname(); if ($www =~ /^[-\w]+\.(.*)$/) { $www = "www.${1}"; @@ -285,22 +376,120 @@ sub GetRPMFile($$$) else { fatal("Tainted bossinfo $www!"); } + $www = "https://${www}"; + #$www = "https://${www}/dev/stoller"; + #$www = "http://golden-gw.ballmoss.com:9876/~stoller/testbed"; # # Okay, run wget with the proper arguments. # - my $cmd = "wget -q -O $copyfile ". + my $cmd = "wget -nv -O $copyfile ". ($debug ? "--server-response " : "") . - "'https://${www}/spewrpmtar.php3". - "?nodeid=${nodeid}&file=${rpm}&key=${keyhash}'"; + "'${www}/spewrpmtar.php3". + "?nodeid=${nodeid}&file=${rpm}&key=${keyhash}". + (defined($oldstamp) ? "&stamp=$oldstamp" : "") . + (defined($oldmd5) ? "&md5=$oldmd5" : "") . + "'"; if ($debug) { print STDERR "$cmd\n"; } - system($cmd); - fatal("Could not retrieve $rpm from $www") - if ($?); + # + # We need to read back the response to see if the file was + # unchanged. This is dumb; why doesn't wget exit with reasonable + # error codes? + # + my $nochange = 0; + if (!open(WGET, "$cmd 2>&1 |")) { + fatal("Cannot start wget: $!\n"); + } + while () { + print $_ + if ($debug); + + # Ick! + if ($_ =~ /^.* ERROR 304.*$/i) { + $nochange = 1; + } + } + if (! close(WGET)) { + if ($?) { + fatal("Could not retrieve $rpm from $www") + if (!$nochange); + # Otherwise, not modifed. + print STDOUT "Timestamp for $rpm unchanged!\n" + if ($debug); + return 1; + } + else { + fatal("Error closing wget pipe: $!\n"); + } + } + # Must do this for caller so that if the MD5 has not changed, + # the caller can update the timestamp in the ident file. + # + # Always use GM time for this. The server expects it. + $$filestamp = mktime(gmtime(time())); + + # + # We got a file. Compare the MD5 now. + # + my $md5 = GetMD5($copyfile); + if (defined($oldmd5) && $oldmd5 eq $md5) { + print STDOUT "MD5 ($md5) for $rpm unchanged!\n" + if ($debug); + return 2; + } + $$filemd5 = $md5; } return 0; } + +# +# Get MD5 of file. +# +sub GetMD5($) +{ + my ($file) = @_; + my $md5; + + if ($OSNAME eq "linux") { + $md5 = `md5sum $file`; + + if ($md5 =~ /^([\w]*)\s*.*$/) { + $md5 = $1; + } + else { + fatal("Bad MD5 for $file: $md5."); + } + } + elsif ($OSNAME eq "freebsd") { + $md5 = `md5 -q $file`; + + if ($md5 =~ /^([\w]*)$/) { + $md5 = $1; + } + else { + fatal("Bad MD5 for $file: $md5."); + } + } + else { + fatal("Do not know how to compute MD5s!"); + } + return $md5; +} + +# +# Recreate the ident file. +# +sub WriteIdentFile() +{ + if (!open(IDENT, "> $IDENTFILE")) { + fatal("Could not open $IDENTFILE for writing: $!"); + } + foreach my $id (@identlines) { + print IDENT "$id\n"; + } + close(IDENT); +} diff --git a/os/install-tarfile b/os/install-tarfile index 2f31552fc..d16f7a2f9 100755 --- a/os/install-tarfile +++ b/os/install-tarfile @@ -1,17 +1,14 @@ #!/usr/bin/perl -wT - # # EMULAB-COPYRIGHT # Copyright (c) 2000-2003 University of Utah and the Flux Group. # All rights reserved. # - use English; use Getopt::Std; -use POSIX 'setsid'; +use POSIX qw(mktime); # Drag in path stuff so we can find emulab stuff. -# XXX Temporary until I have the new tmcc library finished! BEGIN { require "/etc/emulab/paths.pm"; import emulabpaths; } # @@ -58,15 +55,23 @@ my $debug = 0; my $copyfile; my $nodeid; my $keyhash; +my $filemd5; +my $filestamp; +my $oldmd5; +my $oldstamp; +my @identlines = (); # # Load the OS independent support library. It will load the OS dependent # library and initialize itself. # use libsetup; +use libtmcc; # Protos -sub GetTarFile($$$); +sub GetTarFile($$$$$$$); +sub GetMD5($); +sub WriteIdentFile(); # # Must be running as root to work. @@ -138,14 +143,39 @@ if (! -d $installdir) { } # -# Check to make sure this tarfile has not already been installed. Update -# the file now. If the tar fails, we got big problems. -# +# Check to make sure this tarfile has not already been installed. +# If so, we get the old timestamp and md5 so we can compare against +# current ones. +# We need to update the stamp/md5 in place in case it has changed, so +# copy out all the identlines so we can write them back later. We do not +# copyout the current one of course; we make up a new line at the end +# of this script based on the new info. +# if (-e $IDENTFILE) { - if (! system("egrep -q -s '^${tarfile}' $IDENTFILE")) { - print STDOUT "Tarfile $tarfile has already been installed!\n"; - exit(1); + if (!open(IDENT, $IDENTFILE)) { + fatal("Could not open $IDENTFILE: $!"); } + while () { + if ($_ =~ /^([-\w\.\/]*) ([\d]*) ([\w]*)$/) { + my $file = $1; + my $stamp= $2; + my $md5 = $3; + + if ($file eq $tarfile) { + # + # Save the info and continue; + # + $oldstamp = $stamp; + $oldmd5 = $md5; + next; + } + push(@identlines, "$file $stamp $md5"); + } + else { + warn("*** WARNING: Bad line in $IDENTFILE: $_\n"); + } + } + close(IDENT); } # @@ -159,6 +189,28 @@ if (! $copymode) { if (! -r $tarfile) { fatal("$tarfile does not exist or is not accessible!"); } + + # + # Compare timestamp. If no change, we are done. + # + (undef,undef,undef,undef,undef,undef, + undef,undef,undef,$filestamp) = stat($tarfile); + + if (defined($oldstamp) && $oldstamp >= $filestamp) { + print STDOUT "Tarfile $tarfile has already been installed!\n"; + exit(1); + } + # + # Otherwise compare MD5. + # + $filemd5 = GetMD5($tarfile); + if (defined($oldmd5) && $filemd5 eq $oldmd5) { + print STDOUT "Tarfile $tarfile has already been installed!\n"; + # Must write a new ident file to avoid repeated checks. + push(@identlines, "$tarfile $filestamp $filemd5"); + WriteIdentFile(); + exit(1); + } } else { $copyfile = `mktemp /var/tmp/tarball.XXXXXX`; @@ -169,18 +221,29 @@ else { else { die("Bad data in copyfile name: $copyfile"); } - GetTarFile($tarfile, $copyfile, $usewget); # - # Dies on any failure! + # Dies on any failure. + # Returns >0 if server copy has not been modifed. + # Returns =0 if okay to install, and gives us new stamp/md5. # + if (GetTarFile($tarfile, $copyfile, $usewget, + $oldstamp, $oldmd5, \$filestamp, \$filemd5)) { + print STDOUT "Tarfile $tarfile has already been installed!\n"; + if (defined($filestamp) && $filestamp != $oldstamp) { + # Must write a new ident file to avoid repeated checks. + push(@identlines, "$tarfile $filestamp $oldmd5"); + WriteIdentFile(); + } + unlink($copyfile) + if (-e $copyfile); + exit(1); + } } # -# Add to index first; if fails too bad. -# -if (system("echo \"$tarfile\" >> $IDENTFILE")) { - fatal("Could not update $IDENTFILE"); -} +# Okay, add new info to the list for update. +# +push(@identlines, "$tarfile $filestamp $filemd5"); # # Figure what decompression flag is required, based on file extension. @@ -211,6 +274,12 @@ if ($copymode) { unlink($copyfile); } +# +# Recreate the index file if the install was okay. +# +if (!$exit_status) { + WriteIdentFile(); +} exit($exit_status); sub fatal { @@ -226,19 +295,33 @@ sub fatal { # # Get a tarfile from the server via tmcc and stash. # -sub GetTarFile($$$) +sub GetTarFile($$$$$$$) { - my ($tarfile, $copyfile, $usewget) = @_; + my ($tarfile, $copyfile, $usewget, + $oldstamp, $oldmd5, $filestamp, $filemd5) = @_; my $buf; - my $bytelen; # # If copying via NFS, must watch for read errors and retry. # if (! $usewget) { + # + # Compare timestamp. If no change, we are done. + # + my (undef,undef,undef,undef,undef,undef,undef,$bytelen, + undef,$stamp) = stat($tarfile); + + if (defined($oldstamp) && $oldstamp >= $stamp) { + print STDOUT "Timestamp ($stamp) for $tarfile unchanged!\n" + if ($debug); + return 1; + } + # Must do this for caller so that if the MD5 has not changed, + # the caller can update the timestamp in the ident file. + $$filestamp = $stamp; + open(TMCC, "< $tarfile") or fatal("Could not open tarfile on server!"); - $bytelen = (stat($tarfile))[7]; # # Open the target file and start dumping the data in. @@ -280,6 +363,17 @@ sub GetTarFile($$$) } close(JFILE); close(TMCC); + + # + # Compare md5. + # + my $md5 = GetMD5($copyfile); + if (defined($oldmd5) && $oldmd5 eq $md5) { + print STDOUT "MD5 ($md5) for $tarfile unchanged!\n" + if ($debug); + return 2; + } + $$filemd5 = $md5; } else { # @@ -317,10 +411,8 @@ sub GetTarFile($$$) # # Lastly, need our boss node. - # - my ($www) = split(" ", `tmcc bossinfo`); - die("Could not get bossinfo!") - if ($?); + # + my ($www) = tmccbossname(); if ($www =~ /^[-\w]+\.(.*)$/) { $www = "www.${1}"; @@ -328,22 +420,121 @@ sub GetTarFile($$$) else { fatal("Tainted bossinfo $www!"); } + $www = "https://${www}"; + #$www = "https://${www}/dev/stoller"; + #$www = "http://golden-gw.ballmoss.com:9876/~stoller/testbed"; # # Okay, run wget with the proper arguments. # - my $cmd = "wget -q -O $copyfile ". + my $cmd = "wget -nv -O $copyfile ". ($debug ? "--server-response " : "") . - "'https://${www}/spewrpmtar.php3". - "?nodeid=${nodeid}&file=${tarfile}&key=${keyhash}'"; + "'${www}/spewrpmtar.php3". + "?nodeid=${nodeid}&file=${tarfile}&key=${keyhash}" . + (defined($oldstamp) ? "&stamp=$oldstamp" : "") . + (defined($oldmd5) ? "&md5=$oldmd5" : "") . + "'"; + if ($debug) { print STDERR "$cmd\n"; } - system($cmd); - fatal("Could not retrieve $tarfile from $www") - if ($?); + # + # We need to read back the response to see if the file was + # unchanged. This is dumb; why doesn't wget exit with reasonable + # error codes? + # + my $nochange = 0; + if (!open(WGET, "$cmd 2>&1 |")) { + fatal("Cannot start wget: $!\n"); + } + while () { + print $_ + if ($debug); + + # Ick! + if ($_ =~ /^.* ERROR 304.*$/i) { + $nochange = 1; + } + } + if (! close(WGET)) { + if ($?) { + fatal("Could not retrieve $tarfile from $www") + if (!$nochange); + # Otherwise, not modifed. + print STDOUT "Timestamp for $tarfile unchanged!\n" + if ($debug); + return 1; + } + else { + fatal("Error closing wget pipe: $!\n"); + } + } + # Must do this for caller so that if the MD5 has not changed, + # the caller can update the timestamp in the ident file. + # + # Always use GM time for this. The server expects it. + $$filestamp = mktime(gmtime(time())); + + # + # We got a file. Compare the MD5 now. + # + my $md5 = GetMD5($copyfile); + if (defined($oldmd5) && $oldmd5 eq $md5) { + print STDOUT "MD5 ($md5) for $tarfile unchanged!\n" + if ($debug); + return 2; + } + $$filemd5 = $md5; } return 0; } + +# +# Get MD5 of file. +# +sub GetMD5($) +{ + my ($file) = @_; + my $md5; + + if ($OSNAME eq "linux") { + $md5 = `md5sum $file`; + + if ($md5 =~ /^([\w]*)\s*.*$/) { + $md5 = $1; + } + else { + fatal("Bad MD5 for $file: $md5."); + } + } + elsif ($OSNAME eq "freebsd") { + $md5 = `md5 -q $file`; + + if ($md5 =~ /^([\w]*)$/) { + $md5 = $1; + } + else { + fatal("Bad MD5 for $file: $md5."); + } + } + else { + fatal("Do not know how to compute MD5s!"); + } + return $md5; +} + +# +# Recreate the ident file. +# +sub WriteIdentFile() +{ + if (!open(IDENT, "> $IDENTFILE")) { + fatal("Could not open $IDENTFILE for writing: $!"); + } + foreach my $id (@identlines) { + print IDENT "$id\n"; + } + close(IDENT); +} diff --git a/tbsetup/GNUmakefile.in b/tbsetup/GNUmakefile.in index 4757307c7..693967826 100644 --- a/tbsetup/GNUmakefile.in +++ b/tbsetup/GNUmakefile.in @@ -140,8 +140,6 @@ post-install: chmod u+s $(INSTALL_BINDIR)/node_reboot chown root $(INSTALL_SBINDIR)/newnode_reboot chmod u+s $(INSTALL_SBINDIR)/newnode_reboot - chown root $(INSTALL_BINDIR)/node_update - chmod u+s $(INSTALL_BINDIR)/node_update chown root $(INSTALL_SBINDIR)/vnode_setup chmod u+s $(INSTALL_SBINDIR)/vnode_setup chown root $(INSTALL_BINDIR)/eventsys_control diff --git a/tbsetup/node_update.in b/tbsetup/node_update.in index 4447ec9fc..e8fedc7d6 100644 --- a/tbsetup/node_update.in +++ b/tbsetup/node_update.in @@ -2,7 +2,7 @@ # # EMULAB-COPYRIGHT -# Copyright (c) 2000-2002 University of Utah and the Flux Group. +# Copyright (c) 2000-2003 University of Utah and the Flux Group. # All rights reserved. # @@ -10,28 +10,33 @@ use English; use Getopt::Std; # -# Update mounts and accounts and anything else after changing the permissions -# for a node. This is intended to be invoked from the web interface after -# adding and/or subtracting pids from the experiment pid access list. +# Mark nodes for update. At the moment all kinds of things will get +# updated (mounts, accounts, tarballs, rpms). At some point these +# should be split up. # # XXX There is an inherent race condition with using this script. What if # nodes are released while it is running? # -# Eventually, this script should be tossed. The widearea nodes poll -# for updates, and thats how local nodes should do it too. -# -# The output is all jumbled together since the updates are issued in parallel. -# Might be a pain when debugging. -# sub usage() { - print STDOUT "Usage: node_update [-b] \n". + print STDOUT "Usage: node_update [-b] pid eid [node ...]\n". "Update user accounts and NFS mounts on nodes in your project.\n". "Use -b to use batch operation (place in background, send email).\n"; exit(-1); } -my $optlist = "be:"; - +my $optlist = "b"; + +# +# Exit codes are important; they tell the web page what has happened so +# it can say something useful to the user. Fatal errors are mostly done +# with die(), but expected errors use this routine. At some point we will +# use the DB to communicate the actual error. +# +# $status < 0 - Fatal error. Something went wrong we did not expect. +# $status = 0 - Proceeding in the background. Notified later. +# $status > 0 - Expected error. User not allowed for some reason. +# + # # Configure variables # @@ -40,10 +45,15 @@ my $TESTMODE = @TESTMODE@; my $TBOPS = "@TBOPSEMAIL@"; my $TBLOGS = "@TBLOGSEMAIL@"; -my $ssh = "$TB/bin/sshtb -n"; my $expsetup = "$TB/sbin/exports_setup"; my $batchmode = 0; -my $maxchildren = 20; +my @nodes = (); +my $user_name; +my $user_email; +my $logname; +my $failed = 0; +my $dbuid; +my $estate; # # Load the Testbed support stuff. @@ -67,14 +77,14 @@ $| = 1; if (! getopts($optlist, \%options)) { usage(); } -if (@ARGV != 2) { - usage(); -} -my $pid = $ARGV[0]; -my $eid = $ARGV[1]; if (defined($options{"b"})) { $batchmode = 1; } +if (@ARGV < 2) { + usage(); +} +my $pid = shift(@ARGV); +my $eid = shift(@ARGV); # # Untaint the arguments. @@ -90,23 +100,23 @@ if ($eid =~ /^([-\@\w]+)$/) { } else { die("*** Bad data in eid: $eid\n"); -} - -my $user_name; -my $user_email; -my $logname; -my %pids = (); -my $failed = 0; -my $dbuid; -my @autonodes = (); +} # -# We don't want to run this script unless its the real version. -# That is, it must be setuid root. +# If more args, they are node names. # -if ($EUID != 0) { - die("*** $0:\n". - " Must be root! Maybe its a development version?\n"); +if (@ARGV) { + # Untaint the nodes. + foreach my $node ( @ARGV ) { + if ($node =~ /^([-\w]+)$/) { + $node = $1; + } + else { + die("*** Bad node name: $node."); + } + + push(@nodes, $node); + } } # @@ -123,22 +133,12 @@ if (! UserDBInfo($dbuid, \$user_name, \$user_email)) { } # -# Verify that this person is allowed to do this. Must be an admin type, -# the experiment creator or the project leader. +# Verify that this person is allowed to do this. # -if ($UID && !TBAdmin()) { - my $expt_leader = ExpLeader($pid, $eid); - my $proj_leader = ProjLeader($pid); - - if (!$expt_leader || !$proj_leader) { - die("*** $0:\n". - " No such Experiment $eid or no such Project $pid\n"); - } - - if ($expt_leader ne $dbuid && $proj_leader ne $dbuid) { - die("*** $0:\n". - " You must be the experiment creator or the project leader\n"); - } +if (!TBAdmin() && + ! TBExptAccessCheck($UID, $pid, $eid, TB_EXPT_UPDATE)) { + die("*** $0:\n". + " You not have permission to update nodes in $pid/$eid!\n"); } # @@ -146,10 +146,11 @@ if ($UID && !TBAdmin()) { # DBQueryFatal("lock tables experiments write"); -if (TBExpLocked($pid, $eid)) { +if (TBExpLocked($pid, $eid, \$estate)) { DBQueryWarn("unlock tables"); - die("*** $0:\n". - " Experiment $pid/$eid is in transition. Please try later!\n"); + print STDERR "Experiment $pid/$eid is in transition! Please try later!\n"; + # For web page. + exit(1); } # @@ -157,14 +158,44 @@ if (TBExpLocked($pid, $eid)) { # state so that we are not trying to update nodes that are still booting # or swapping out, etc. # -if (ExpState($pid, $eid) ne EXPTSTATE_ACTIVE) { +if ($estate ne BATCHSTATE_RUNNING) { DBQueryWarn("unlock tables"); - die("*** $0:\n". - " The experiment $pid/$eid must be fully activated first!\n"); + print STDERR "Experiment $pid/$eid is not active or is in transition!\n"; + # For web page. + exit(1); } -TBLockExp($pid, $eid); +TBLockExp($pid, $eid, BATCHSTATE_RUNNING_LOCKED); DBQueryFatal("unlock tables"); +# +# Get the list of nodes that need to be "updated." We already have the +# exerpiment locked, so if we have a problem, must unlock it. +# +if (! scalar(@nodes)) { + @nodes = ExpNodes($pid, $eid); +} +else { + # + # Must verify that user supplied node names are really in the + # experiment. + # + my @allnodes = ExpNodes($pid, $eid); + + foreach my $n1 (@nodes) { + if (! grep {$_ eq $n1} @allnodes) { + TBUnLockExp($pid, $eid, BATCHSTATE_RUNNING()); + die("*** $0:\n". + " Node $n1 is not allocated to $pid/$eid!\n"); + } + } +} +if (! scalar(@nodes)) { + print STDERR "There are no nodes allocated to experiment $pid/$eid\n"; + TBUnLockExp($pid, $eid, BATCHSTATE_RUNNING()); + # For web page. + exit(1); +} + # # Batchmode (as from the web interface) goes to background and reports # later via email. @@ -188,24 +219,14 @@ if ($batchmode) { } # -# Currently, we just need to update the mount points. The UID change because -# of PERL sillyness. +# Currently, we just need to update the mount points. # -$UID = $EUID; if (system("$expsetup")) { fatal("Exports Setup Failed"); } # Give ops a chance to react. sleep(2); -# -# Get the list of nodes that need to be "updated." -# -my @nodes = ExpNodes($pid, $eid); -if (! @nodes) { - fatal("No Nodes in the experiment"); -} - # # Mark the nodes for auto update. Nodes may not respect this field # (old local images), but its harmless. @@ -218,170 +239,47 @@ foreach my $node ( @nodes ) { } } -# -# We want some overlap, but not too much since we could burn up -# a lot processes on wedged nodes. Issue a small number in parallel, -# and wait once we reach the limit for one to finish, before issuing -# the next one. -# -my $maxpids = 0; -foreach my $node ( @nodes ) { - my ($jailed, $plab); - - # - # widearea and jailed nodes auto update. Skip them. - # - if (TBIsNodeRemote($node)) { - push(@autonodes, $node); - next; - } - # eventually all virtual nodes will be jailed ... - if (TBIsNodeVirtual($node, \$jailed, \$plab)) { - if ($jailed || $plab) { - push(@autonodes, $node); - } - next; - } - - while ($maxpids >= $maxchildren) { - my $thispid = waitpid(-1, 0); - my $thisnode = $pids{$thispid}; - - if ($?) { - $failed++; - print STDERR "Update of node $thisnode failed!\n"; - } - else { - print STDOUT "$thisnode updated.\n"; - } +print STDOUT "Waiting a while for nodes to auto update ...\n"; +for (my $i = 0; $i < 10; $i++) { + sleep(30); - delete($pids{$thispid}); - $maxpids--; - } - my $thispid = UpdateNode($node); - $pids{$thispid} = $node; - $maxpids++; - sleep(1); -} + my $firstnode = pop(@nodes); + my $querystr = "node_id='$firstnode' "; -# -# Wait for any remaining children to exit before continuing. -# -foreach my $thispid ( keys(%pids) ) { - my $node = $pids{$thispid}; + while (@nodes) { + my $node = pop(@nodes); - waitpid($thispid, 0); - if ($?) { - $failed++; - print STDERR "Update of node $node failed!\n"; - } - else { - print STDOUT "$node updated.\n"; + $querystr .= "or node_id='$node' "; } -} - -# -# Wait a couple of minutes for remote nodes to do the update. We know -# this cause tmcd decrements the update_accounts flag whenever a node -# picks up new accounts. -# -if (@autonodes) { - print STDOUT "Waiting a while for nodes to auto update ...\n"; - for (my $i = 0; $i < 6; $i++) { - sleep(30); - - my $firstnode = pop(@autonodes); - my $querystr = "node_id='$firstnode' "; - - while (@autonodes) { - my $node = pop(@autonodes); + + my $query_result = + DBQueryFatal("select node_id,update_accounts from nodes ". + "where ($querystr)"); - $querystr .= "or node_id='$node' "; + while (my ($node,$notdone) = $query_result->fetchrow_array) { + if ($notdone) { + push(@nodes, $node); } - - my $query_result = - DBQueryFatal("select node_id,update_accounts from nodes ". - "where ($querystr)"); - - while (my ($node,$notdone) = $query_result->fetchrow_array) { - if ($notdone) { - push(@autonodes, $node); - } - else { - print STDOUT "$node updated.\n"; - } + else { + print STDOUT "$node updated.\n"; } - last if (! @autonodes); - print STDOUT "Still waiting for nodes to auto update ...\n"; } + last + if (! @nodes); + print STDOUT "Still waiting for nodes to auto update ...\n"; } -foreach my $node ( @autonodes ) { +foreach my $node ( @nodes ) { print STDOUT "Node update failed on $node.\n"; $failed++; } -TBUnLockExp($pid, $eid); +TBUnLockExp($pid, $eid, BATCHSTATE_RUNNING); NotifyUser("Node Update Complete", $failed); if (defined($logname)) { unlink($logname); } exit($failed); -# -# Update a node in a child process. Return the pid to the parent so -# that it can wait on all the children later. -# -sub UpdateNode { - my($node) = @_; - my($syspid, $mypid); - - print STDOUT "Updating $node.\n"; - - $mypid = fork(); - if ($mypid) { - return $mypid; - } - TBdbfork(); - - # - # Run an ssh command in a child process, protected by an alarm to - # ensure that the ssh is not hung up forever if the machine is in - # some funky state. - # - $syspid = fork(); - - # Must change our real UID to root so that ssh will work. - $UID = 0; - - if ($syspid) { - local $SIG{ALRM} = sub { kill("TERM", $syspid); }; - alarm 15; - waitpid($syspid, 0); - alarm 0; - - print STDERR "update of $node returned $?.\n" if $debug; - - # - # If either ssh is not running or it timed out, - # send it a ping of death. - # - if ($? == 256 || $? == 15) { - if ($? == 256) { - print STDERR "$node is not running sshd.\n" if $debug; - } else { - print STDERR "$node is wedged.\n" if $debug; - } - exit(-1); - } - exit(0); - } - else { - exec("$ssh -host $node /etc/testbed/update"); - exit(0); - } - exit(0); -} - sub NotifyUser($$) { my($mesg, $iserr) = @_; @@ -426,7 +324,7 @@ sub NotifyUser($$) sub fatal($) { my($mesg) = @_; - TBUnLockExp($pid, $eid); + TBUnLockExp($pid, $eid, BATCHSTATE_RUNNING()); NotifyUser($mesg, 1); if (defined($logname)) { unlink($logname); diff --git a/tbsetup/spewrpmtar.in b/tbsetup/spewrpmtar.in index ee1030c8b..ade8ae555 100644 --- a/tbsetup/spewrpmtar.in +++ b/tbsetup/spewrpmtar.in @@ -6,6 +6,7 @@ # use English; use Getopt::Std; +use POSIX qw(mktime); # # Spew a tar/rpm file to stdout. @@ -14,13 +15,28 @@ use Getopt::Std; # sub usage() { - print STDERR "Usage: spewtarfile [-v] \n". + print STDERR "Usage: spewtarfile [-v] [t timestamp] \n". "Spew a tar/rpm for an experiment.\n"; exit(-1); } -my $optlist = "v"; +my $optlist = "vt:"; my $debug = 1; my $doverify = 0; +my $timestamp; # GM Time. + +# +# Exit codes are important; they tell the web page what has happened so +# it can say something useful to the user. Fatal errors are mostly done +# with die(), but expected errors use this routine. At some point we will +# use the DB to communicate the actual error. +# +# $status < 0 - Fatal error. Something went wrong we did not expect. +# $status = 0 - Proceeding. +# $status > 0 - Expected error. No such file, not modified, etc. +# 1. File could not be verified. +# 2. File has not changed since timestamp. +# 3. File could not be opened for reading. +# # # Configure variables @@ -54,6 +70,16 @@ if (! getopts($optlist, \%options)) { if (defined($options{"v"})) { $doverify = 1; } +if (defined($options{"t"})) { + $timestamp = $options{"t"}; + + if ($timestamp =~ /^([\d]+)$/) { + $timestamp = $1; + } + else { + die("*** Bad data in timestamp: $timestamp\n"); + } +} if (@ARGV != 2) { usage(); } @@ -90,12 +116,13 @@ my $gid = ExpGroup($pid, $eid); my $creator = ExpLeader($pid, $eid); my $bytelen = 0; -exit(1) - if (!VerifyFile()); +if (my $retval = VerifyFile()) { + exit($retval); +} exit(0) if ($doverify); -exit(2) - if (!SpewFile()); +exit(3) + if (SpewFile() != 0); exit(0); # @@ -141,11 +168,12 @@ sub SpewFile() fatal("Did not get the entire file! $bytelen bytes left."); } close(FD); - + return 0; } # -# Verify that we can return this file, return 0 if not allowed. +# Verify that we can return this file, return error if not allowed. +# Otherwise return 0 for okay. # sub VerifyFile() { @@ -157,7 +185,7 @@ sub VerifyFile() if ($debug) { print STDERR "VerifyFile: Could not verify $file!\n"; } - return 0; + return 1; } # @@ -183,15 +211,15 @@ sub VerifyFile() if ($debug) { print STDERR "$translated is not in /proj or /groups!\n"; } - return 0; + return 1; } # # Stat the file to confirm that its either owned by the experiment # creator, or in the gid of the experiment. # - (undef,undef,undef,undef,$stat_uid,$stat_gid,undef,$bytelen) - = stat($translated); + (undef,undef,undef,undef,$stat_uid,$stat_gid,undef,$bytelen, + undef,$mtime) = stat($translated); my ($unix_uid, $unix_gid, $unix_gname); @@ -206,10 +234,19 @@ sub VerifyFile() if ($debug) { print STDERR "$translated has wrong uid/gid!\n"; } - return 0; + return 1; } + + # + # Check timestamp if supplied. Remember, we get GM timestamps, so + # must convert the local stamp. + # + $mtime = mktime(gmtime($mtime)); + return 2 + if (defined($timestamp) && $timestamp >= $mtime); + $file = $translated; - return 1; + return 0; } # diff --git a/tmcd/common/update b/tmcd/common/update index 70b0257f2..96c50f66f 100755 --- a/tmcd/common/update +++ b/tmcd/common/update @@ -15,15 +15,17 @@ use POSIX qw(strftime); # sub usage() { - print "Usage: update [-i] [-a] [-m] [-h] [-l]\n"; + print "Usage: update [-i] [-a] [-m] [-h] [-l] [-t] [-r]\n"; exit(1); } -my $optlist = "imalh"; +my $optlist = "imalhtr"; my $batchmode = 1; my $doall = 0; my $accounts = 0; my $mounts = 0; my $hostsfile = 0; +my $tarballs = 0; +my $rpms = 0; my $immediate = 0; # Drag in path stuff so we can find emulab stuff. @@ -97,11 +99,17 @@ if (defined($options{"m"})) { if (defined($options{"h"})) { $hostsfile = 1; } +if (defined($options{"t"})) { + $tarballs = 1; +} +if (defined($options{"r"})) { + $rpms = 1; +} if (@ARGV) { usage(); } # Backwards compat; no options means mounts and accounts. -if (!$mounts && !$accounts && !$hostsfile) { +if (!$mounts && !$accounts && !$hostsfile && !$doall) { $mounts = $accounts = 1; } @@ -193,5 +201,31 @@ if ($accounts || $doall) { } } +if ($tarballs || $doall) { + print "Updating tarball configuration ... \n"; + + if (dotarballs()) { + die("*** $0:\n". + " Failed to get new tarball configuration!\n"); + } + if (-x TMTARBALLS()) { + print "Installing new tarballs ...\n"; + system(TMTARBALLS()); + } +} + +if ($rpms || $doall) { + print "Updating RPM configuration ... \n"; + + if (dorpms()) { + die("*** $0:\n". + " Failed to get new RPM configuration!\n"); + } + if (-x TMRPM()) { + print "Installing new RPMs ...\n"; + system(TMRPM()); + } +} + close(LOCK); exit(0); diff --git a/tmcd/common/watchdog b/tmcd/common/watchdog index a8d49fe91..4d9026ffe 100755 --- a/tmcd/common/watchdog +++ b/tmcd/common/watchdog @@ -229,7 +229,7 @@ sub startisalive() if ($failed || $tmccresults[0] =~ /^UPDATE=1$/) { print "Running an update at $date ...\n"; - system("update -i"); + system("update -i -a"); $failed = $?; } last; diff --git a/www/dbdefs.php3.in b/www/dbdefs.php3.in index 0ead0d9b4..1cf68dd6a 100644 --- a/www/dbdefs.php3.in +++ b/www/dbdefs.php3.in @@ -86,9 +86,9 @@ $TB_USERINFO_MAX = $TB_USERINFO_MODIFYINFO; $TB_EXPT_READINFO = 1; $TB_EXPT_MODIFY = 2; # Allocate/dealloc nodes $TB_EXPT_DESTROY = 3; -$TB_EXPT_UPDATEACCOUNTS = 4; +$TB_EXPT_UPDATE = 4; $TB_EXPT_MIN = $TB_EXPT_READINFO; -$TB_EXPT_MAX = $TB_EXPT_UPDATEACCOUNTS; +$TB_EXPT_MAX = $TB_EXPT_UPDATE; # Projects. $TB_PROJECT_READINFO = 1; @@ -136,6 +136,7 @@ define("TBDB_IFACEROLE_CONTROL", "ctrl"); define("TBDB_IFACEROLE_EXPERIMENT", "expt"); define("TBDB_IFACEROLE_JAIL", "jail"); define("TBDB_IFACEROLE_FAKE", "fake"); +define("TBDB_IFACEROLE_GW", "gw"); define("TBDB_IFACEROLE_OTHER", "other"); # @@ -504,7 +505,7 @@ function TBExptAccessCheck($uid, $pid, $eid, $access_type) global $TB_EXPT_READINFO; global $TB_EXPT_MODIFY; global $TB_EXPT_DESTROY; - global $TB_EXPT_UPDATEACCOUNTS; + global $TB_EXPT_UPDATE; global $TB_EXPT_MIN; global $TB_EXPT_MAX; global $TBDB_TRUST_USER; diff --git a/www/showexp.php3 b/www/showexp.php3 index 4755d15cf..a436a3e5c 100644 --- a/www/showexp.php3 +++ b/www/showexp.php3 @@ -141,8 +141,8 @@ WRITESUBMENUBUTTON("Edit Experiment Metadata", # # Admin and project/experiment leader get this option. # -if (TBExptAccessCheck($uid, $exp_pid, $exp_eid, $TB_EXPT_UPDATEACCOUNTS)) { - WRITESUBMENUBUTTON("Update Mounts/Accounts", +if (TBExptAccessCheck($uid, $exp_pid, $exp_eid, $TB_EXPT_UPDATE)) { + WRITESUBMENUBUTTON("Update All Nodes", "updateaccounts.php3?pid=$exp_pid&eid=$exp_eid"); } diff --git a/www/showimageid_list.php3 b/www/showimageid_list.php3 index 4fd2ebbea..c74bef2a0 100644 --- a/www/showimageid_list.php3 +++ b/www/showimageid_list.php3 @@ -76,7 +76,7 @@ echo "Listed below are the Images that you can load on your nodes with the a node is not loaded on that node when the experiment is swapped in, the Testbed system will automatically reload that node's disk with the appropriate image. You might notice that it takes a few minutes longer - to start start your experiment when selecting an OS that is not + to start your experiment when selecting an OS that is not already resident. Please be patient.
More information on how to create your own Images is in the diff --git a/www/shownode.php3 b/www/shownode.php3 index ba4d8eaa5..022468225 100644 --- a/www/shownode.php3 +++ b/www/shownode.php3 @@ -100,6 +100,11 @@ WRITESUBMENUBUTTON("Edit Node Info", "nodecontrol_form.php3?node_id=$node_id"); if (TBNodeAccessCheck($uid, $node_id, $TB_NODEACCESS_REBOOT)) { + if (isset($pid)) { + WRITESUBMENUBUTTON("Update Node", + "updateaccounts.php3?pid=$pid&eid=$eid". + "&nodeid=$node_id"); + } WRITESUBMENUBUTTON("Reboot Node", "boot.php3?node_id=$node_id"); } diff --git a/www/showstuff.php3 b/www/showstuff.php3 index 402eea182..d0ed69fec 100644 --- a/www/showstuff.php3 +++ b/www/showstuff.php3 @@ -1468,7 +1468,7 @@ function SHOWIMAGEID($imageid, $edit, $isadmin = 0) { # # Show node record. # -function SHOWNODE($node_id) { +function SHOWNODE($node_id, $short = 0) { $query_result = DBQueryFatal("select n.*,na.*,r.vname,r.pid,r.eid,i.IP, ". "greatest(last_tty_act,last_net_act,last_cpu_act,". @@ -1564,24 +1564,27 @@ function SHOWNODE($node_id) { } } - if ($vname) { - echo " - Virtual Name: - $vname - \n"; - } + if (!$short) { + if ($vname) { + echo " + Virtual Name: + $vname + \n"; + } - if ($pid) { - echo " - Project: - - $pid - \n"; + if ($pid) { + echo " + Project: + + $pid + \n"; - echo " - Experiment: - $eid - \n"; + echo " + Experiment: + + $eid + \n"; + } } echo " @@ -1589,127 +1592,128 @@ function SHOWNODE($node_id) { $type \n"; - echo " - Def Boot OS: - "; - SPITOSINFOLINK($def_boot_osid); - echo " - \n"; - - if ($eventstate) { - $when = strftime("20%y-%m-%d %H:%M:%S", $state_timestamp); + if (!$short) { echo " - EventState: - $eventstate ($when) + Def Boot OS: + "; + SPITOSINFOLINK($def_boot_osid); + echo " \n"; - } - if ($op_mode) { - $when = strftime("20%y-%m-%d %H:%M:%S", $op_mode_timestamp); - echo " - Operating Mode: - $op_mode ($when) - \n"; - } + if ($eventstate) { + $when = strftime("20%y-%m-%d %H:%M:%S", $state_timestamp); + echo " + EventState: + $eventstate ($when) + \n"; + } - if ($allocstate) { - $when = strftime("20%y-%m-%d %H:%M:%S", $allocstate_timestamp); - echo " - AllocState: - $allocstate ($when) - \n"; - } + if ($op_mode) { + $when = strftime("20%y-%m-%d %H:%M:%S", $op_mode_timestamp); + echo " + Operating Mode: + $op_mode ($when) + \n"; + } - # - # We want the last login for this node, but only if its *after* the - # experiment was created (or swapped in). - # - if ($lastnodeuidlogin = TBNodeUidLastLogin($node_id)) { + if ($allocstate) { + $when = strftime("20%y-%m-%d %H:%M:%S", $allocstate_timestamp); + echo " + AllocState: + $allocstate ($when) + \n"; + } - $foo = $lastnodeuidlogin["date"] . " " . - $lastnodeuidlogin["time"] . " " . - "(" . $lastnodeuidlogin["uid"] . ")"; + # + # We want the last login for this node, but only if its *after* the + # experiment was created (or swapped in). + # + if ($lastnodeuidlogin = TBNodeUidLastLogin($node_id)) { + $foo = $lastnodeuidlogin["date"] . " " . + $lastnodeuidlogin["time"] . " " . + "(" . $lastnodeuidlogin["uid"] . ")"; - echo " - Last Login: - $foo - \n"; - } - - if ($last_act) { - echo " - Last Activity: - $last_act - \n"; + echo " + Last Login: + $foo + \n"; + } - $idletime = TBGetNodeIdleTime($node_id); - echo " - Idle Time: - $idletime hours - \n"; + if ($last_act) { + echo " + Last Activity: + $last_act + \n"; - echo " - Last Act. Report: - $last_report - \n"; + $idletime = TBGetNodeIdleTime($node_id); + echo " + Idle Time: + $idletime hours + \n"; - echo " - Last TTY Act.: - $last_tty_act - \n"; + echo " + Last Act. Report: + $last_report + \n"; - echo " - Last Net. Act.: - $last_net_act - \n"; + echo " + Last TTY Act.: + $last_tty_act + \n"; - echo " - Last CPU Act.: - $last_cpu_act - \n"; + echo " + Last Net. Act.: + $last_net_act + \n"; - echo " - Last Ext. Act.: - $last_ext_act - \n"; + echo " + Last CPU Act.: + $last_cpu_act + \n"; + echo " + Last Ext. Act.: + $last_ext_act + \n"; + } } - - if (!$isvirtnode && !$isremotenode) { - echo " - Def Boot Path: - $def_boot_path - \n"; - echo " - Def Boot Command Line: - $def_boot_cmd_line - \n"; + if (!$short) { + if (!$isvirtnode && !$isremotenode) { + echo " + Def Boot Path: + $def_boot_path + \n"; - echo " - Next Boot OS: - "; + echo " + Def Boot Command Line: + $def_boot_cmd_line + \n"; + + echo " + Next Boot OS: + "; - if ($next_boot_osid) - SPITOSINFOLINK($next_boot_osid); - else - echo " "; + if ($next_boot_osid) + SPITOSINFOLINK($next_boot_osid); + else + echo " "; - echo " - \n"; + echo " + \n"; - echo " - Next Boot Path: - $next_boot_path - \n"; + echo " + Next Boot Path: + $next_boot_path + \n"; - echo " - Next Boot Command Line: - $next_boot_cmd_line - \n"; - } - elseif ($isvirtnode) { - if (!$isplabdslice) { + echo " + Next Boot Command Line: + $next_boot_cmd_line + \n"; + } + elseif ($isvirtnode) { + if (!$isplabdslice) { echo " IP Port Low: $ipport_low @@ -1724,12 +1728,12 @@ function SHOWNODE($node_id) { IP Port High: $ipport_high \n"; + } + echo " + SSHD Port: + $sshdport + \n"; } - - echo " - SSHD Port: - $sshdport - \n"; } echo " @@ -1742,83 +1746,81 @@ function SHOWNODE($node_id) { $tarballs \n"; - if (!$isremotenode) { - echo " - RPMs: - $rpms - \n"; - } - - if (!$isvirtnode && !$isremotenode) { - echo " - Router Type: - $routertype + echo " + RPMs: + $rpms \n"; - } - if ($IP) { - echo " - Control Net IP: - $IP - \n"; - } - elseif ($phys_IP) { - echo " - Physical IP: - $phys_IP - \n"; - } + if (!$short) { + if (!$isvirtnode && !$isremotenode) { + echo " + Router Type: + $routertype + \n"; + } - if ($rsrvrole) { - echo " - Role: - $rsrvrole - \n"; - } + if ($IP) { + echo " + Control Net IP: + $IP + \n"; + } + elseif ($phys_IP) { + echo " + Physical IP: + $phys_IP + \n"; + } - if ($bios) { - echo " - Bios Version: - $bios - \n"; - } + if ($rsrvrole) { + echo " + Role: + $rsrvrole + \n"; + } - if ($isremotenode) { - if ($isvirtnode) { - SHOWWIDEAREANODE($phys_nodeid, 1); + if ($bios) { + echo " + Bios Version: + $bios + \n"; } - else { - SHOWWIDEAREANODE($node_id, 1); + + if ($isremotenode) { + if ($isvirtnode) { + SHOWWIDEAREANODE($phys_nodeid, 1); + } + else { + SHOWWIDEAREANODE($node_id, 1); + } } - } - # - # Show any auxtypes the node has - # - $query_result = - DBQueryFatal("select type, count from node_auxtypes ". - "where node_id='$node_id'"); + # + # Show any auxtypes the node has + # + $query_result = + DBQueryFatal("select type, count from node_auxtypes ". + "where node_id='$node_id'"); - if (mysql_num_rows($query_result) != 0) { - echo " - - Auxiliary Types - - \n"; + if (mysql_num_rows($query_result) != 0) { + echo " + + Auxiliary Types + + \n"; - echo "TypeCount\n"; + echo "TypeCount\n"; - while ($row = mysql_fetch_array($query_result)) { - $type = $row[type]; - $count = $row[count]; - echo " - $type - $count - \n"; + while ($row = mysql_fetch_array($query_result)) { + $type = $row[type]; + $count = $row[count]; + echo " + $type + $count + \n"; + } } - } - echo "\n"; } diff --git a/www/spewrpmtar.php3 b/www/spewrpmtar.php3 index ec57e2ca2..e8af472eb 100644 --- a/www/spewrpmtar.php3 +++ b/www/spewrpmtar.php3 @@ -35,6 +35,13 @@ if (!isset($key) || strcmp($key, "") == 0) { SPITERROR(400, "You must provide an key."); } +if (!isset($stamp) || !strcmp($stamp, "")) { + unset($stamp); +} +# We ignore MD5 for now. +if (!isset($md5) || !strcmp($md5, "")) { + unset($md5); +} # # Make sure a reserved node. @@ -87,18 +94,30 @@ register_shutdown_function("SPEWCLEANUP"); # $nodeid = escapeshellarg($nodeid); $file = escapeshellarg($file); +$arg = (isset($stamp) ? "-t " . escapeshellarg($stamp) : ""); # # Run once with just the verify option to see if the file exists. # Then do it for real, spitting out the data. Sure, the user could # delete the file in the meantime, but thats his problem. # -$retval = SUEXEC($creator, $gid, "spewrpmtar -v $nodeid $file", +$retval = SUEXEC($creator, $gid, "spewrpmtar -v $arg $nodeid $file", SUEXEC_ACTION_IGNORE); +if ($retval < 0) { + SUEXECERROR(SUEXEC_ACTION_CONTINUE); + SPITERROR(500, "Could not verify file!"); +} + +# +# An expected error. +# if ($retval) { - SPITERROR(404, "Could not find $file!"); -} + if ($retval == 2) { + SPITERROR(304, "File has not changed"); + } + SPITERROR(404, "Could not verify file: $retval!"); +} # # Okay, now do it for real. diff --git a/www/swapexp.php3 b/www/swapexp.php3 index 71fac95cb..4f3534035 100644 --- a/www/swapexp.php3 +++ b/www/swapexp.php3 @@ -80,6 +80,7 @@ if (mysql_num_rows($query_result) == 0) { $row = mysql_fetch_array($query_result); $exp_gid = $row[gid]; $isbatch = $row[batchmode]; +$batchstate = $row[batchstate]; $swappable = $row[swappable]; $idleswap_bit = $row[idleswap]; $idleswap_time = $row[idleswap_timeout]; @@ -102,8 +103,14 @@ if (!strcmp($inout, "in")) { elseif (!strcmp($inout, "out")) { if ($isbatch) $action = "swapout"; - else - $action = "swapout"; + else { + if (! strcmp($batchstate, TBDB_BATCHSTATE_ACTIVATING)) { + $action = "cancel"; + } + else { + $action = "swapout"; + } + } } elseif (!strcmp($inout, "pause")) { if (!$isbatch) @@ -266,22 +273,35 @@ else { } } else { - if (strcmp($inout, "in") == 0) - $howlong = "two to ten"; - else - $howlong = "less than two"; + if (strcmp($inout, "out") == 0 && + strcmp($batchstate, TBDB_BATCHSTATE_ACTIVATING) == 0) { + + echo "Your experiment swapin has been marked for cancelation. + It typically takes a few minutes for this to be recognized, + assuming you made your request early enough. You will + be notified via email when the original swapin request has + either aborted or finished.\n"; + } + else { + if (strcmp($inout, "in") == 0) + $howlong = "two to ten"; + else + $howlong = "less than two"; - echo "Your experiment has started its $action. - You will be notified via email when the operation is complete. - This typically takes $howlong minutes, depending on the - number of nodes in the experiment. -

- If you do not receive email notification within a reasonable - amount of time, please contact $TBMAILADDR. -

- While you are waiting, you can watch the log in - - realtime.\n"; + echo "Your experiment has started its $action. + You will be notified via email when the operation is complete. + This typically takes $howlong minutes, depending on the + number of nodes in the experiment.\n"; + } + echo "

+ If you do not receive + email notification within a reasonable amount of time, + please contact $TBMAILADDR.\n"; + + echo "

+ While you are waiting, you can watch the log in + + realtime.\n"; } } diff --git a/www/updateaccounts.php3 b/www/updateaccounts.php3 index 44e855705..84517e9e9 100644 --- a/www/updateaccounts.php3 +++ b/www/updateaccounts.php3 @@ -1,15 +1,11 @@ ". - "You must wait until the experiment is no longer in transition.". - "

". - "When the transition has completed, a notification will be ". - "sent via email to the user that initiated it.", 1); -} +$gid = $row[gid]; # # Verify permissions. # -if (! TBExptAccessCheck($uid, $exp_pid, $exp_eid, $TB_EXPT_UPDATEACCOUNTS)) { - USERERROR("You do not have permission to update accounts!", 1); +if (! TBExptAccessCheck($uid, $pid, $eid, $TB_EXPT_UPDATE)) { + USERERROR("You do not have permission to initiate node updates!", 1); } +if (isset($nodeid) && !TBValidNodeName($nodeid)) { + USERERROR("Node $nodeid is not a valid nodeid!", 1); +} + +echo "Experiment ". + "$pid/". + "$eid\n"; # # We run this twice. The first time we are checking for a confirmation # by putting up a form. The next time through the confirmation will be -# set. Or, the user can hit the cancel button, in which case we should -# probably redirect the browser back up a level. +# set. Or, the user can hit the cancel button (see above). # -if ($canceled) { - echo "


- Account Update canceled! -

\n"; - - PAGEFOOTER(); - return; -} - if (!$confirmed) { - echo "Confirming this operation will cause the password file on each - node in experiment '$exp_eid' to be updated. Typically, you would - initiate this operation if a new project member has been approved and - you want his/her account added immediately (without having to reboot - all your nodes or swap the experiment in/out).\n"; + echo "

+ Confirming this operation will initiate various updates to be + performed, including updating the password and group files, + adding new mounts, and installing (or updating if modified) tarballs + and rpms. + This is sometimes quicker and easier than rebooting nodes.\n"; - echo "

-
- Are you sure you want to update Accounts - on all of the nodes in experiment '$exp_eid?'\n"; + echo "


+ Are you sure you want to perform an update on "; + + if (isset($nodeid)) { + echo "node $nodeid in experiment '$eid?'\n"; + } + else { + echo "all of the nodes in experiment '$eid?'\n"; + } + echo "

\n"; + + if (isset($nodeid)) { + SHOWNODE($nodeid, 1); + } + else { + SHOWEXP($pid, $eid, 1); + } echo "
"; echo "\n"; echo "\n"; + if (isset($nodeid)) { + echo "\n"; + } echo "
\n"; echo "
\n"; echo "
- This operation will consume a small number of CPU cycles on + NOTE that this operation will consume a small number of CPU + cycles on each node. If this would disturb an experiment in progress, you should cancel this operation until later. Please note that accounts are automatically updated whenever a node @@ -115,31 +127,54 @@ if (!$confirmed) { # We need the unix gid for the project for running the scripts below. # Note usage of default group in project. # -TBGroupUnixInfo($exp_pid, $exp_gid, $unix_gid, $unix_name); +TBGroupUnixInfo($pid, $gid, $unix_gid, $unix_name); # # Start up a script to do this. # echo "

"; -echo "

Starting account update. Please wait a moment ... +echo "

Starting node update. Please wait a moment ...

"; flush(); -SUEXEC($uid, $unix_gid, "webnodeupdate -b $pid $eid", 1); +$retval = SUEXEC($uid, $unix_gid, + "webnodeupdate -b $pid $eid" . + (isset($nodeid) ? " $nodeid" : ""), + SUEXEC_ACTION_IGNORE); -echo "

- You will be notified via email when the update has completed on - all of the nodes in your experiment. Since this involves rebuilding - the accounts database on each node, it should be just a few minutes - before you receive notification.\n"; +# +# Fatal Error. Report to the user, even though there is not much he can +# do with the error. Also reports to tbops. +# +if ($retval < 0) { + SUEXECERROR(SUEXEC_ACTION_DIE); + # + # Never returns ... + # + die(""); +} # -# Back links for convenience. +# Exit status 0 means the operation is proceeding in the background. # -echo "

- - Back to experiment information page\n"; +echo "
\n"; +if ($retval) { + echo "

Node update could not proceed

"; + echo "
$suexec_output
"; +} +else { + echo "You will be notified via email when the update has completed on + all of the nodes in your experiment. This might take a just a + few minutes (if updating only accounts) or it might take longer + if new tar/rpm files need to be installed. In the meantime, the + experiment is locked it may not be swapped or modified.\n"; + + echo "

+ If you do not receive + email notification within a reasonable amount of time, + please contact $TBMAILADDR.\n"; +} # # Standard Testbed Footer -- GitLab