os_select.in 8.17 KB
Newer Older
1 2 3 4
#!/usr/bin/perl -wT

#
# EMULAB-COPYRIGHT
5
# Copyright (c) 2000-2003 University of Utah and the Flux Group.
6 7 8 9
# All rights reserved.
#

# os_select sets the os that should boot next on a node, and sets
10
# next_op_mode accordingly.
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

sub usage() {
    print <<"EOF";
Usage: os_select [-h] [-d] [-v] [-1] [-p | -m] <osid> <node> [<node> ...]
 -h    Display this help message
 -d    Debug mode
 -v    Verbose mode
 -p    Path mode: osid is really a path to a kernel
 -1    Set up one-time boot to OS instead of changing default OS
 -m    MFS mode: osid={"PXEFBSD","PXEFRISBEE","PXEBOOT","host:/tftpboot/file"}
 osid  OS identifier for the selected OS (see web interface for listing)
 node  Node identifiers (ie pcXX)
EOF
    exit(-1);
}

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

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

# Configure variables
my $TB          = "@prefix@";
35
my $TBOPS       = "@TBSTATEDEMAIL@";
36
my $TBLOG	= "@TBLOGFACIL@";
37 38 39 40 41 42

# Testbed Support libraries
use lib "@prefix@/lib";
use libdb;
use libtestbed;
use English;
43 44
use Getopt::Std;
use Sys::Syslog;
45 46

# Constants
47
my $MBKERNEL = TB_OSID_MBKERNEL;
48
my %osidmap = # Map some magic OSIDs to op_modes
49
    ( $MBKERNEL => "MINIMAL");
50 51 52 53 54 55 56

# Global vars
my $d=0; # debug/verbose
my $oneshot=0; # is this a one-shot OS boot?
my $pathmode=0; # is osid really a path instead of an osid?
my $mfs=0; # is this for a pxe_boot_path change?

57
# Set up syslog
58
openlog("osselect","pid",$TBLOG);
59

60 61 62 63 64
# Find default pxe boot path
my $cmd = "select path from os_info where osid='".TB_OSID_PXEBOOT."'";
my $q = DBQueryFatal($cmd);
my @r = $q->fetchrow_array();
my $pxebootpath = $r[0];
65 66
my $cmdline = join(" ",$0,@ARGV);
debug("ARGV= ".join(" ",@ARGV)."\n");
67 68 69 70 71 72 73 74 75 76 77 78

# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
my  $optlist = "hdvp1m";
%options = ();
if (! getopts($optlist, \%options)) { usage(); }
if (defined($options{"h"})) { usage(); }
if (defined($options{"d"})) { $d++; }
if (defined($options{"v"})) { $d++; }
if (defined($options{"p"})) { $pathmode=1; }
if (defined($options{"1"})) { $oneshot=1; }
if (defined($options{"m"})) { $mfs=1; }
79
#debug("DEBUG LEVEL $d\n");
80

81
if (@ARGV < 2) { warning("An osid and a node list are required.\n"); usage(); }
82 83 84 85 86

my $osid = shift;
my @nodes = @ARGV;

# Untaint args.
87
if ($osid =~ /^([-\@\w\/\+\.]+)$/) { $osid = $1; }
88
elsif (!$mfs) { fatal("Bad data in osid: '$osid'\n"); }
89 90

if ($mfs) {
91
    if ($osid =~ /^[-0-9a-z\.]+:\/tftpboot\/[-0-9a-z\.\/]+$/) {
92
	debug("MFS osid '$osid' is a PXE path\n");
93 94 95 96 97
	$pxebootpath=$osid;
	my $cmd = "select osid from os_info where path='$osid';";
	my $q = DBQueryFatal($cmd);
	if ($q->numrows() > 0) {
	    my @r = $q->fetchrow_array();
98
	    debug("Path '$osid' => OSID '$r[0]'\n");
99
	    $osid=$r[0];
100
	    $pathmode=0;
101 102 103 104 105
	} else { $pathmode=1; }
    } else {
	my $cmd = "select path from os_info where osid='$osid';";
	my $q = DBQueryFatal($cmd);
	if ($q->numrows() < 1) {
106
	    fatal("Invalid osid '$osid' does not exist.\n");
107 108 109 110 111 112
	}
	my @r = $q->fetchrow_array();
	my $path=$r[0];
	if (defined($path) && $path ne "") {
	    $pxebootpath=$path;
	} else {
113
	    fatal("Invalid osid for use with -m: '$osid'\n");
114 115 116 117 118 119
	}
    }
}

my @temp;
foreach $n (@nodes) {
120
    if ($n =~ /^([-\w]+)$/) { push(@temp,$1); }
121
    else { warning("Ignoring bad data in node_id: '$n'\n"); }
122 123 124
}
@nodes=@temp;

125
if (@nodes < 1) { fatal("No valid nodes supplied.\n"); }
126 127 128

# Figure out who called us. Only root, people with admin status
# in the DB, or members of the right project can do this.
129 130
my ($me) = getpwuid($UID)
  or fatal("$UID not in passwd file\n");
131 132
if ($UID && !TBAdmin($UID)) {
    if (! TBNodeAccessCheck($UID,TB_NODEACCESS_MODIFYINFO,@nodes)) {
133
        fatal("os_select: You do not have permission to modify ".
134
	    "one or more of the nodes. ($me/$UID)\n");
135
    }
136 137
    debug("$me/$UID: Access granted to all nodes requested.\n");
} else { debug("$me/$UID: Running as an admin.\n"); }
138 139 140

my $pernodeopmode=0;
my $opmode = os_opmode($osid);
141
debug("Found opmode '$opmode' for osid '$osid'.\n");
142 143 144 145 146
if ($opmode eq TBDB_NODEOPMODE_BOOTWHAT ) { $pernodeopmode=1; }

foreach $n (@nodes) {
    my $curmode = node_opmode($n);
    if (!$curmode) { next; }
147
    debug("Current opmode for node '$n' is '$curmode'.\n");
148 149 150 151
    # Always set the pxe_boot_path (to the osid in mfs mode, else PXEBOOT)
    set_pxe_path($n);
    # Only do the boot osid if we're not in mfs mode
    if (!$mfs) { set_boot_osid($n); }
152 153 154
    my $boot = TBBootWhat($n);
    if (!$boot) { fatal("***Bootwhat query failed. Contact testbed-ops.\n"); }
    debug("Bootwhat says: $n => $boot\n");
155 156 157 158
    if ($pernodeopmode) { 
	# in per-node mode, opmode is the mode for the os that we're going 
	# to boot, whatever that may be on each node
	$opmode= os_opmode($boot); 
159
	debug("Desired opmode for node '$n' is '$opmode'.\n");
160
    }
161
    if (!$curmode || ($curmode ne $opmode)) { set_nextmode($n); }
162 163 164 165 166 167 168 169 170 171 172 173 174 175
    else { set_nextmode($n,""); } # Make sure it is clear
}

exit();

#
# Subroutines
#

sub set_nextmode() {
    my $node = shift || "";
    my $mode = shift;
    if (!defined($mode)) { $mode = $opmode; }
    my $cmd = "update nodes set next_op_mode='$mode' where node_id='$node';";
176
    debug("Setting next_op_mode for node '$node' to '$mode'.\n");
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
    my $q = DBQueryFatal($cmd);
    return 0;
}

sub set_boot_osid() {
    my $node = shift || "";
    my $field = ($oneshot?"next":"def")."_boot_".($pathmode?"path":"osid");
    my $cmd = "update nodes set $field='$osid'";
    # Clear out the paths and the next_boot_osid except for possibly
    # one that we're going to set. Never clear def_boot_osid, since it
    # won't ever get in the way of what we really want to boot, and we
    # want something to fall back on.
    foreach $f ("next_boot_path", "def_boot_path", "next_boot_osid") {
	if ($f ne $field) { $cmd .= ", $f=''"; }
    }
    $cmd .= " where node_id='$node';";
193 194
    #debug("cmd=$cmd\n");
    debug("Setting $field for node '$node' to '$osid'.\n");
195 196 197 198 199 200 201 202 203 204 205 206 207
    my $q = DBQueryFatal($cmd);
    return 0;
}

sub set_pxe_path() {
    my $node = shift || "";
    my $field = ($oneshot?"next_":"")."pxe_boot_path";
    my $cmd = "update nodes set $field='$pxebootpath'";
    # Clear out the next_pxe_boot_path if we're not going to set it
    foreach $f ("next_pxe_boot_path") {
	if ($f ne $field) { $cmd .= ", $f=''"; }
    }
    $cmd .= " where node_id='$node';";
208 209
    #debug("cmd=$cmd\n");
    debug("Setting $field for node '$node' to '$pxebootpath'.\n");
210 211 212 213 214 215 216 217 218
    my $q = DBQueryFatal($cmd);
    return 0;
}

sub node_opmode() {
    my $node = shift || "";
    my $cmd = "select op_mode from nodes where node_id='$node';";
    my $q = DBQueryFatal($cmd);
    if ($q->numrows() < 1) {
219
	warning("Ignoring invalid node '$node' (non-existent)\n");
220 221 222 223 224
	return 0;
    }
    my @r = $q->fetchrow_array();
    my $opmode=$r[0];
    if (defined($opmode) && $opmode) { return $opmode; }
225
    warning("Invalid opmode '$opmode' for node '$node'.\n");
226 227 228 229 230
    return 0;
}

sub os_opmode() {
    my $osid = shift || "";
231
    if ($pathmode) { return $osidmap{$MBKERNEL}; }
232 233 234 235
    if (defined($osidmap{$osid})) { return $osidmap{$osid}; }
    my $cmd = "select op_mode from os_info where osid='$osid';";
    my $q = DBQueryFatal($cmd);
    if ($q->numrows() < 1) {
236
	fatal("Invalid osid '$osid' is non-existent.\n");
237 238 239
    }
    my @r = $q->fetchrow_array();
    my $opmode=$r[0];
240
    debug("OpMode for '$osid' is '$opmode'\n");
241
    if (defined($opmode) && $opmode ne "") { return $opmode; }
242 243 244 245 246 247 248 249
    fatal("No opmode found for osid '$osid'.\n");
}

sub debug ( $;$ ) {
  my $msg = shift;
  my $notice = shift || 0;
  my $prio="info";
  if ($notice) { $prio = "notice"; }
250
  syslog($prio,$msg) || notify("syslog failed: $! $?\n");
251 252 253 254 255
  if ($d) { print $msg; }
}

sub notify ( $ ) {
  my $msg = shift;
256 257
  my $user= getpwuid($UID);
  $msg .= "\ndate=".`date`."\ncmdline=\n$cmdline\n\npid=$$\n\nuser=$user\n";
258
  if (!$d) {
259 260 261 262 263
    SENDMAIL($TBOPS,"os_select error",$msg,$TBOPS);
  } else {
    debug("notify: Not sending mail in debug mode\n");
  }
  debug($msg,1);
264 265
}

266 267 268 269 270 271 272 273 274 275 276 277 278
sub info ( $;$ ) {
  my $msg = shift;
  debug($msg);
}

sub fatal ( $ ) {
  my $msg = shift;
  notify("FATAL: ".$msg);
  die($msg);
}

sub warning ( $ ) {
  my $msg = shift;
279
  info("WARNING: ".$msg);
280 281 282 283 284 285 286
  warn($msg);
}

# This is called when we exit with exit() or die()
END {
  closelog();
}