node_control.in 8.44 KB
Newer Older
1
#!/usr/bin/perl -wT
Leigh Stoller's avatar
Leigh Stoller committed
2 3
#
# EMULAB-COPYRIGHT
4
# Copyright (c) 2000-2007 University of Utah and the Flux Group.
Leigh Stoller's avatar
Leigh Stoller committed
5 6
# All rights reserved.
#
7
use strict;
8 9 10 11 12 13 14
use English;
use Getopt::Std;

#
# usage: node_control [options] node [node ...]
#        node_control [options] -e pid,eid
#
15 16 17 18
# XXX virt_nodes osname is not handled properly.
#
# This script is invoked from ops and from the web interface. Must check
# all the args.
19
#
20 21 22 23 24 25 26 27 28 29
sub usage()
{
    print("Usage: node_control name=value [name=value ...] node [node ...]\n".
	  "       node_control -e pid,eid name=value [name=value ...]\n".
	  "       node_control -l\n".
	  "For multiword values, use name='word0 ... wordN'\n".
	  "Use -l to get a list of operational parameters you can change.\n".
	  "Use -e to change parameters of all nodes in an experiment.\n");
    exit(-1);
}
30 31
my $optlist   = "de:l";
my $debug     = 0;
32 33 34 35 36 37 38 39 40 41

#
# Array of allowed names. All of these values are text, so no need to
# worry about distinguishing numeric stuff.
#
# XXX This should be in the library.
#
my %controlset =
(
 #
42 43
 # Symbolic name =>
 #      Admin, Multi args, nodes field, virt_nodes field, osselect, checkslot
44
 #
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
 default_boot_osid      =>
     [0, 0, "def_boot_osid",      undef,       1, "",   "os_info:osid"],
 default_boot_cmdline	=> 
     [0, 0, "def_boot_cmd_line",  "cmd_line",  0, "",   "virt_nodes:cmd_line"],
 startup_command	=>
     [0, 0, "startupcmd",         "startupcmd",0, "",   "virt_nodes:startupcmd"],
 tarfiles		=>
     [0, 1, "tarballs",	          "tarfiles",  0, "",   "virt_nodes:tarfiles"],
 rpms			=>
     [0, 1, "rpms",	   	  "rpms",      0, "",   "virt_nodes:rpms"],
 next_boot_osid         =>
     [1, 0, "next_boot_osid",     undef,       1, "-1", "os_info:osid"],
 next_boot_cmdline	=>
     [1, 0, "next_boot_cmd_line", undef,       0, "",   "virt_nodes:cmd_line"],
 temp_boot_osid         =>
     [1, 0, "temp_boot_osid",     undef,       1, "-t", "os_info:osid"],
61 62 63 64 65 66 67 68 69
);
  
#
# Configure variables
#
my $TB		= "@prefix@";
my $TBOPS       = "@TBOPSEMAIL@";
my $TBLOGS      = "@TBLOGSEMAIL@";

70
my $osselect    = "$TB/bin/os_select";
71 72 73
my @nodes       = ();
my %controls    = ();
my $errors	= 0;
74
my $experiment;
75 76 77 78 79 80 81

#
# Load the Testbed support stuff. 
#
use lib "@prefix@/lib";
use libdb;
use libtestbed;
82 83 84
use User;
use Experiment;
use Node;
85

86 87
# Protos
sub fatal($);
88 89 90 91 92 93 94 95 96 97 98 99

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

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

#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
100
my %options = ();
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
if (! getopts($optlist, \%options)) {
    usage();
}
if (defined($options{"d"})) {
    $debug = 1;
}
if (defined($options{"l"})) {
    foreach my $option (keys(%controlset)) {
	my ($admin, $multi, $dbkey) = @{ $controlset{$option} };
	
	printf("  %-25s ", $option);
	if ($multi || $admin) {
	    print "- ";
	}
	if ($multi) {
	    print "(multiple options allowed) ";
	}
	if ($admin) {
	    print "(administrators only)";
	}
	print "\n";
    }
    exit(0);
}
125
# Experiment mode.
126
if (defined($options{"e"})) {
127 128 129
    $experiment = Experiment->Lookup($options{"e"});
    if (!defined($experiment)) {
	fatal("No such experiment in the Emulab DB!");
130 131 132 133 134 135
    }
}
if (! @ARGV) {
    usage();
}

136 137 138 139 140 141 142 143 144
#
# Verify user and get his DB uid and other info for later.
#
my $this_user = User->ThisUser();
if (! defined($this_user)) {
    fatal("You ($UID) do not exist!");
}
my $isadmin = TBAdmin();

145 146 147 148 149 150 151 152 153 154 155 156 157
#
# Shift off the set strings (name=value). Verify that each one is in the
# proper format.
#
while (@ARGV) {
    my $string = $ARGV[0];

    if (! ($string =~ /([-\w]*)=[\']?([^\']*)[\']?/)) {
	last;
    }
    shift;

    if (! defined($controlset{$1})) {
158
	fatal("Illegal control setting: $1='$2'. Try the -l option!");
159
    }
160
    my ($admin,$multi) = @{ $controlset{$1} };
161

162 163
    if ($admin && ! $isadmin) {
	fatal("You do not have permission to set $1. Try the -l option!");
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
    }

    if ($multi && defined($controls{$1})) {
	$controls{$1} = "$controls{$1}:$2";
    }
    else {
	$controls{$1} = "$2";
    }
}

if ($debug) {
    foreach my $option (keys(%controls)) {
	print "Will set $option to '$controls{$option}'\n";
    }
}

#
# In eidmode, check the access permission for the experiment, and then
# get the nodes. Otherwise, check access for each node given on the
# command line.
#
185
if (defined($experiment)) {
186
    # Permission check.
187 188 189
    if (!$isadmin &&
	!$experiment->AccessCheck($this_user, TB_EXPT_MODIFY)) {
	fatal("You do not have permission to control nodes in $experiment!");
190 191
    }

192 193
    if (! (@nodes = $experiment->NodeList())) {
	fatal("There are no nodes in $experiment!");
194 195 196 197 198 199 200
    }
}
else {
    if (! @ARGV) {
	usage();
    }
    
201 202 203 204
    foreach my $nodeid (@ARGV) {
	my $node = Node->Lookup($nodeid);
	if (!defined($node)) {
	    fatal("Bad node name: $node");
205 206 207 208 209 210
	}
	push(@nodes, $node);
    }
}

#
211
# Create update key/value pairs
212
#
213 214 215
my %physnode_updates = ();
my %virtnode_updates = ();
my @osselect_params  = ();
216 217

foreach my $option (keys(%controls)) {
218
    my ($admin, $multi, $physdbkey, $virtdbkey,
219
	$needs_osselect, $osselect_arg, $checkslot) = @{$controlset{$option}};
220 221
    my $value = $controls{$option};

222 223 224 225 226 227 228 229
    #
    # Do a checkslot on it to make sure its valid for the DB slot.
    #
    my ($table,$slot) = split(":", $checkslot);

    if ($value ne "" &&
	!TBcheck_dbslot($value, $table, $slot,
			TBDB_CHECKDBSLOT_WARN|TBDB_CHECKDBSLOT_ERROR)) {
230
	fatal("Illegal value specified for $option: '$value'");
231 232
    }

233
    if ($needs_osselect) {
234 235 236 237 238
	my $str = ($debug ? "-d " : "");
	    
        if ($value eq "") {
	    # Clearing the field.
	    $str .= "-c $osselect_arg";
239
	}
240 241 242 243 244 245 246 247 248 249
	else {
	    # Setting the field
	    $str .= "$osselect_arg $value";
	}
	if ($debug) {
	    print "$option=$value ($physdbkey) made osselect str='$str'\n";
	}
	push(@osselect_params, $str);
    }
    else {
250
	$physnode_updates{$physdbkey} = $value;
251 252 253
    }

    if (defined($virtdbkey)) {
254
	$virtnode_updates{$virtdbkey} = $value;
255 256
    }
}
257 258 259
if (! keys(%physnode_updates) && 
    @osselect_params == 0 && 
    ! keys(%virtnode_updates)) {
260 261 262 263
    exit(0);
}

if ($debug) {
264 265 266 267 268
    if (keys(%physnode_updates)) {
	print "Phys update will be '".
	    join(",", map("$_='" .
			  $physnode_updates{$_} . "'",
			  keys(%physnode_updates))) . "'\n";
269
    }
270 271 272
    if (@osselect_params>0) {
	print "osselect calls:\n  ".join("\n  ",@osselect_params)."\n";
    }
273 274 275 276 277
    if (keys(%virtnode_updates)) {
	print "Virt update will be '".
	    join(",", map("$_='" .
			  $virtnode_updates{$_} . "'",
			  keys(%virtnode_updates))) . "'\n";
278 279 280 281 282 283 284 285
    }
}

#
# Now do it for every node. Do the permission check here to reduce the
# race condition window. Should probably lock instead, but thats a pain.
#
foreach my $node (@nodes) {
Leigh Stoller's avatar
Leigh Stoller committed
286
    my $node_id = $node->node_id();
287 288
    if ($debug) { print "Processing $node...\n"; }

289 290
    if (keys(%physnode_updates) || @osselect_params) {
	if (! $node->AccessCheck($this_user, TB_NODEACCESS_MODIFYINFO)) {
291
	    print("*** $0:\n".
292
		  "*** You do not have permission to modify physical ".
293 294 295 296
		  "parameters for $node!\n");
	    $errors++;
	    next;
	}
297 298 299
	if (keys(%physnode_updates) &&
	    $node->Update(\%physnode_updates) != 0) {
	    print STDERR "*** Failed to update $node!\n";
300 301
	    $errors++;
	}
302 303
	if (@osselect_params) {
	    foreach my $str (@osselect_params) {
304
		if ($debug) { print "Running '$osselect $str $node_id'\n"; }
305
		
Leigh Stoller's avatar
Leigh Stoller committed
306
		if (system("$osselect $str $node_id")) {
307
		    print STDERR "*** 'os_select $str $node_id' failed!\n";
308 309
		    $errors++;
		}
310 311 312
	    }
	}
    }
313 314
    next
	if (! keys(%virtnode_updates));
315

316 317 318 319 320 321 322 323
    #
    # We need the vname for the node so that we can update the virt_nodes
    # table. This implies that we cannot update the virt_nodes unless the
    # experiment is swapped in (so we can map from phys node name to virt
    # node name). At some point, maybe we want to provide a way to change
    # the params of a swapped out experiment by having the user specify
    # the vname?
    #
324 325 326 327 328
    my $this_experiment = $node->Reservation();
    next
	if (!defined($this_experiment));

    my $node_vname = $node->vname();
329

330 331
    if (defined($node_vname)) {
	if (! $this_experiment->AccessCheck($this_user, TB_EXPT_MODIFY)) {
332 333 334 335 336 337
	    print("*** $0:\n".
		  "    You do not have permission to modify virtual ".
		  "parameters for $node!\n");
	    $errors++;
	    next;
	}
338 339 340 341 342
	if ($this_experiment->TableUpdate("virt_nodes", \%virtnode_updates,
					  "vname='$node_vname'") != 0) {
	    print STDERR "*** Failed to update $node in $experiment!\n";
	    $errors++;
	}
343 344 345 346
    }
}

exit($errors);
347 348 349 350 351 352 353 354

sub fatal($)
{
    my ($msg) = @_;
    
    die("*** $0:\n".
	"    $msg\n");
}