os_select.in 8.15 KB
Newer Older
1
2
3
4
5
6
7
8
9
#!/usr/bin/perl -wT

#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2002 University of Utah and the Flux Group.
# 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
128
129

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