os_select.in 8.14 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 37 38 39 40 41

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

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

# 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?

56 57 58
# Set up syslog
openlog("osselect","pid","user");

59 60 61 62 63
# 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];
64 65
my $cmdline = join(" ",$0,@ARGV);
debug("ARGV= ".join(" ",@ARGV)."\n");
66 67 68 69 70 71 72 73 74 75 76 77

# 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; }
78
#debug("DEBUG LEVEL $d\n");
79

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

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

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

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

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

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

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

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

foreach $n (@nodes) {
    my $curmode = node_opmode($n);
    if (!$curmode) { next; }
146
    debug("Current opmode for node '$n' is '$curmode'.\n");
147 148 149 150
    # 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); }
151 152 153
    my $boot = TBBootWhat($n);
    if (!$boot) { fatal("***Bootwhat query failed. Contact testbed-ops.\n"); }
    debug("Bootwhat says: $n => $boot\n");
154 155 156 157
    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); 
158
	debug("Desired opmode for node '$n' is '$opmode'.\n");
159
    }
160
    if (!$curmode || ($curmode ne $opmode)) { set_nextmode($n); }
161 162 163 164 165 166 167 168 169 170 171 172 173 174
    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';";
175
    debug("Setting next_op_mode for node '$node' to '$mode'.\n");
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
    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';";
192 193
    #debug("cmd=$cmd\n");
    debug("Setting $field for node '$node' to '$osid'.\n");
194 195 196 197 198 199 200 201 202 203 204 205 206
    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';";
207 208
    #debug("cmd=$cmd\n");
    debug("Setting $field for node '$node' to '$pxebootpath'.\n");
209 210 211 212 213 214 215 216 217
    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) {
218
	warning("Ignoring invalid node '$node' (non-existent)\n");
219 220 221 222 223
	return 0;
    }
    my @r = $q->fetchrow_array();
    my $opmode=$r[0];
    if (defined($opmode) && $opmode) { return $opmode; }
224
    warning("Invalid opmode '$opmode' for node '$node'.\n");
225 226 227 228 229
    return 0;
}

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

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

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

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

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

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

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