node_control.in 9.46 KB
Newer Older
1
#!/usr/bin/perl -wT
Leigh Stoller's avatar
Leigh Stoller committed
2
#
3
# Copyright (c) 2000-2015 University of Utah and the Flux Group.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
Leigh Stoller's avatar
Leigh Stoller committed
23
#
24
use strict;
25 26 27 28 29 30 31
use English;
use Getopt::Std;

#
# usage: node_control [options] node [node ...]
#        node_control [options] -e pid,eid
#
32 33 34 35
# XXX virt_nodes osname is not handled properly.
#
# This script is invoked from ops and from the web interface. Must check
# all the args.
36
#
37 38 39 40 41 42 43 44 45 46
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);
}
47 48
my $optlist   = "de:l";
my $debug     = 0;
49 50 51 52 53 54 55 56 57 58

#
# 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 =
(
 #
59 60
 # Symbolic name =>
 #      Admin, Multi args, nodes field, virt_nodes field, osselect, checkslot
61
 #
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
 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"],
78 79 80 81 82 83 84 85 86
);
  
#
# Configure variables
#
my $TB		= "@prefix@";
my $TBOPS       = "@TBOPSEMAIL@";
my $TBLOGS      = "@TBLOGSEMAIL@";

87
my $osselect    = "$TB/bin/os_select";
88 89 90
my @nodes       = ();
my %controls    = ();
my $errors	= 0;
91
my $experiment;
92 93 94 95 96 97 98

#
# Load the Testbed support stuff. 
#
use lib "@prefix@/lib";
use libdb;
use libtestbed;
99 100 101
use User;
use Experiment;
use Node;
102

103 104
# Protos
sub fatal($);
105 106 107 108 109 110 111 112 113 114 115 116

# 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.
#
117
my %options = ();
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
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);
}
142
# Experiment mode.
143
if (defined($options{"e"})) {
144 145 146
    $experiment = Experiment->Lookup($options{"e"});
    if (!defined($experiment)) {
	fatal("No such experiment in the Emulab DB!");
147 148 149 150 151 152
    }
}
if (! @ARGV) {
    usage();
}

153 154 155 156 157 158 159 160 161
#
# 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();

162 163 164 165 166 167 168 169 170 171 172 173 174
#
# 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})) {
175
	fatal("Illegal control setting: $1='$2'. Try the -l option!");
176
    }
177
    my ($admin,$multi) = @{ $controlset{$1} };
178

179 180
    if ($admin && ! $isadmin) {
	fatal("You do not have permission to set $1. Try the -l option!");
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
    }

    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.
#
202
if (defined($experiment)) {
203
    # Permission check.
204 205 206
    if (!$isadmin &&
	!$experiment->AccessCheck($this_user, TB_EXPT_MODIFY)) {
	fatal("You do not have permission to control nodes in $experiment!");
207 208
    }

209 210
    if (! (@nodes = $experiment->NodeList())) {
	fatal("There are no nodes in $experiment!");
211 212 213 214 215 216 217
    }
}
else {
    if (! @ARGV) {
	usage();
    }
    
218 219 220 221
    foreach my $nodeid (@ARGV) {
	my $node = Node->Lookup($nodeid);
	if (!defined($node)) {
	    fatal("Bad node name: $node");
222 223 224 225 226 227
	}
	push(@nodes, $node);
    }
}

#
228
# Create update key/value pairs
229
#
230 231 232
my %physnode_updates = ();
my %virtnode_updates = ();
my @osselect_params  = ();
233 234

foreach my $option (keys(%controls)) {
235
    my ($admin, $multi, $physdbkey, $virtdbkey,
236
	$needs_osselect, $osselect_arg, $checkslot) = @{$controlset{$option}};
237
    my $value = $controls{$option};
238
    my $version = "";
239

240 241 242 243 244 245 246 247 248 249 250 251 252
    #
    # OSIDs might include a version number now.
    #
    if ($needs_osselect) {
	my ($osid,$vers) = split(":", $value);
	if (defined($vers)) {
	    if ($vers !~ /^\d+$/) {
		fatal("Illegal value specified for $option: '$value'");
	    }
	    $value = $osid;
	    $version = ":${vers}";
	}
    }
253 254 255 256 257 258 259 260
    #
    # 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)) {
261
	fatal("Illegal value specified for $option: '$value'");
262 263
    }

264
    if ($needs_osselect) {
265 266 267 268 269
	my $str = ($debug ? "-d " : "");
	    
        if ($value eq "") {
	    # Clearing the field.
	    $str .= "-c $osselect_arg";
270
	}
271 272
	else {
	    # Setting the field
273
	    $str .= "$osselect_arg ${value}${version}";
274 275 276 277 278 279 280
	}
	if ($debug) {
	    print "$option=$value ($physdbkey) made osselect str='$str'\n";
	}
	push(@osselect_params, $str);
    }
    else {
281
	$physnode_updates{$physdbkey} = $value;
282 283 284
    }

    if (defined($virtdbkey)) {
285
	$virtnode_updates{$virtdbkey} = $value;
286 287
    }
}
288 289 290
if (! keys(%physnode_updates) && 
    @osselect_params == 0 && 
    ! keys(%virtnode_updates)) {
291 292 293 294
    exit(0);
}

if ($debug) {
295 296 297 298 299
    if (keys(%physnode_updates)) {
	print "Phys update will be '".
	    join(",", map("$_='" .
			  $physnode_updates{$_} . "'",
			  keys(%physnode_updates))) . "'\n";
300
    }
301 302 303
    if (@osselect_params>0) {
	print "osselect calls:\n  ".join("\n  ",@osselect_params)."\n";
    }
304 305 306 307 308
    if (keys(%virtnode_updates)) {
	print "Virt update will be '".
	    join(",", map("$_='" .
			  $virtnode_updates{$_} . "'",
			  keys(%virtnode_updates))) . "'\n";
309 310 311 312 313 314 315 316
    }
}

#
# 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
317
    my $node_id = $node->node_id();
318 319
    if ($debug) { print "Processing $node...\n"; }

320 321
    if (keys(%physnode_updates) || @osselect_params) {
	if (! $node->AccessCheck($this_user, TB_NODEACCESS_MODIFYINFO)) {
322
	    print("*** $0:\n".
323
		  "*** You do not have permission to modify physical ".
324 325 326 327
		  "parameters for $node!\n");
	    $errors++;
	    next;
	}
328 329 330
	if (keys(%physnode_updates) &&
	    $node->Update(\%physnode_updates) != 0) {
	    print STDERR "*** Failed to update $node!\n";
331 332
	    $errors++;
	}
333 334
	if (@osselect_params) {
	    foreach my $str (@osselect_params) {
335
		if ($debug) { print "Running '$osselect $str $node_id'\n"; }
336
		
Leigh Stoller's avatar
Leigh Stoller committed
337
		if (system("$osselect $str $node_id")) {
338
		    print STDERR "*** 'os_select $str $node_id' failed!\n";
339 340
		    $errors++;
		}
341 342 343
	    }
	}
    }
344 345
    next
	if (! keys(%virtnode_updates));
346

347 348 349 350 351 352 353 354
    #
    # 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?
    #
355 356 357 358 359
    my $this_experiment = $node->Reservation();
    next
	if (!defined($this_experiment));

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

361 362
    if (defined($node_vname)) {
	if (! $this_experiment->AccessCheck($this_user, TB_EXPT_MODIFY)) {
363 364 365 366 367 368
	    print("*** $0:\n".
		  "    You do not have permission to modify virtual ".
		  "parameters for $node!\n");
	    $errors++;
	    next;
	}
369 370 371 372 373
	if ($this_experiment->TableUpdate("virt_nodes", \%virtnode_updates,
					  "vname='$node_vname'") != 0) {
	    print STDERR "*** Failed to update $node in $experiment!\n";
	    $errors++;
	}
374 375 376 377
    }
}

exit($errors);
378 379 380 381 382 383 384 385

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