tbadb.in 22 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
#!/usr/bin/perl -w

#
# Copyright (c) 2016 University of Utah and the Flux Group.
# 
# {{{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/>.
# 
# }}}
#

use strict;
use English;
use POSIX ":sys_wait_h";
use Getopt::Std;
use Data::Dumper;
31
use IO::Socket::INET;
32
use File::Temp qw(tempfile);
33

34
use lib "@prefix@/lib";
35 36
use libdb;
use libtestbed;
37
use libjsonrpc;
38
use tbadb_rpc;
39
use User;
40
use Node;
41
use OSImage;
42
use EmulabConstants;
43 44

# Func prototypes
45
sub cmd_setup($@);
46
sub cmd_loadimage($@);
47 48
sub cmd_reserveport($@);
sub cmd_forward($;@);
49
sub cmd_unforward($@);
50
sub cmd_reboot($;@);
Kirk Webb's avatar
Kirk Webb committed
51
sub cmd_nodewait($;@);
52 53
sub GetRPCPipeHandles($);
sub ConnectRPCHost($);
54 55

# Global variables
56 57 58 59 60
my $TB = "@prefix@";
my $MINHLEN   = 2;
my $MAXHLEN   = 32;
my $MINCMDLEN = 2;
my $MAXCMDLEN = 32;
61
my %RPCPIPES = ();
62
my $TBADB_PROXYCMD = "/usr/testbed/sbin/tbadb_proxy";
63
my $TBADB_HELLO_TMO      = 10;
64 65 66 67
my $TBADB_LOCKIMAGE_TMO  = 300;
my $TBADB_UNLOCKIMAGE_TMO = 10;
my $TBADB_CHECKIMAGE_TMO = 60;
my $TBADB_LOADIMAGE_TMO  = 300;
68 69
my $TBADB_RESERVEPORT_TMO = 10;
my $TBADB_FORWARD_TMO    = 20;
70
my $TBADB_REBOOT_TMO     = 60;
Kirk Webb's avatar
Kirk Webb committed
71
my $TBADB_NODEWAIT_TMO   = 60;
72
my $CHILD_WAIT_TMO       = 10;
73 74 75
my $SCP = "/usr/bin/scp";

my %DISPATCH = (
76
    'setup'     => \&cmd_setup,
77
    'loadimage' => \&cmd_loadimage,
78
    'resvport'  => \&cmd_reserveport,
79
    'forward'   => \&cmd_forward,
80
    'unforward' => \&cmd_unforward,
81
    'reboot'    => \&cmd_reboot,
Kirk Webb's avatar
Kirk Webb committed
82
    'nodewait'  => \&cmd_nodewait,
83 84 85
);

sub showhelp() {
86
    print "Usage: $0 -n <node_id> <cmd> <cmd_args>\n\n";
87 88
    print "<cmd>:       TBADB command to run (see list below).\n".
	  "<cmd_args>:  set of arguments specific to <cmd>\n";
89
    print "Command list: ". join(", ", keys %DISPATCH) ."\n";
90
    print "Run again listing just a command to get that command's help.\n";
91 92 93 94 95 96 97 98 99 100 101 102
}

#
# Untaint the path
# 
$ENV{'PATH'} = "/bin:/sbin:/usr/bin:";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

#
# We don't want to run this script unless it's the real version.
#
if ($EUID != 0) {
103
    die("$0: Must be setuid! Maybe it's a development version?\n");
104 105 106 107 108 109
}

#
# Verify user and get user's DB uid and other info for later.
#
my $this_user = User->ThisUser();
110 111 112 113 114 115
my $isroot = 0;
# Special case for root, for when invoked by the reload_daemon...
if ($UID == 0) {
    $isroot = 1;
}
elsif (!defined($this_user)) {
116 117 118
    die("You ($UID) do not exist!\n");
}

119
# Parse command line switches.
120
my %opts = ();
121
if (!getopts("dhn:",\%opts) || $opts{'h'} || @ARGV < 1) {
122 123 124 125 126 127 128
    showhelp();
    exit 1;
}

my $debug = $opts{'d'} ? 1 : 0;
$libjsonrpc::debug = 1 if $debug;

129 130 131 132
# Untaint node_id argument, if provided.
my $node_id = $opts{'n'} ? $opts{'n'} : "";
if ($node_id) {
    die "$0: malformed node_id argument!\n"
133
	if ($node_id !~ /^([-\w]{$MINHLEN,$MAXHLEN})$/);
134 135 136 137 138 139 140
    $node_id = $1;    
}

# Gather other command line args.
my ($CMD, @ARGS) = @ARGV;

# Untaint command
141 142
die "$0: malformed command!\n"
    if ($CMD !~ /^([-\w]{$MINCMDLEN,$MAXCMDLEN})$/);
143 144 145
$CMD = $1;

die "$0: unknown command: $CMD\n"
146 147
    if (!exists($DISPATCH{$CMD}));

148 149
# Execute!
exit $DISPATCH{$CMD}->($node_id, @ARGS);
150 151 152 153 154 155 156 157 158


#
# Given a valid image identifier (name, osid), project (to scope
# image) and node_id, load an image onto a remote device.  Check with
# the remote side to ensure the image is there, and tranfer it first
# if necessary.  The remote end keeps an LRU cache of images.
#
sub cmd_loadimage($@) {
159 160
    my ($node_id, $imagepid, $imagename, @extra) = @_;
    my $nowait = 0;
161
    my $doforward = 0;
162

163
    # Process and untaint arguments.
164
    die "tbadb::cmd_loadimage: missing one or more arguments (need: <project> <image_name>)!\n"
165 166 167 168 169 170 171
	if (!$node_id || !$imagepid || !$imagename);
    die "tbadb::cmd_loadimage: malformed project id!"
	if ($imagepid !~ /^([-\w]{$MINHLEN,$MAXHLEN})$/);
    $imagepid = $1;
    die "tbadb::cmd_loadimage: malformed image id/name!"
	if ($imagename !~ /^([-\w]{$MINHLEN,$MAXHLEN})$/);
    $imagename = $1;
172 173 174 175 176 177
    foreach my $arg (@extra) {
	ARGS: for ($arg) {
	    /^nowait$/i && do {
		$nowait = 1;
		last ARGS;
	    };
178 179 180 181
	    /^doforward$/i && do {
		$doforward = 1;
		last ARGS;
	    };
182 183 184 185
	    # Default
	    die "tbadb::cmd_loadimage: unknown argument: $arg!\n";
	}
    }
186 187

    # Lookup image and extract some info.
188
    my $image = OSImage->Lookup($imagepid, $imagename);
189 190 191
    die "tbadb::cmd_loadimage: No such image descriptor $imagename in project $imagepid!\n"
	if (!defined($image));
    my $imageid = $image->imageid();
192
    my $imagefile = $image->FullImageFile();
193
    $imagename  = $image->imagename(); # strip any version
194 195 196
    my $size  = $image->size();
    my $mtime;
    $image->GetUpdate(\$mtime);
197 198 199

    # Check user's access to the image.
    die "tbadb::cmd_loadimage: You do not have permission to use imageid $imageid!\n"
200
	if (!$isroot && !$this_user->IsAdmin() &&
201
	    !$image->AccessCheck($this_user, TB_IMAGEID_READINFO));
202 203
    die "tbadb::cmd_loadimage: Cannot access image file: $imagefile\n"
	if (!-r $imagefile);
204 205 206 207 208 209

    # Make sure user has access to requested node too.
    my $node = Node->Lookup($node_id);
    die "tbadb::cmd_loadimage: Invalid node name $node_id!\n"
	if (!defined($node));
    die("tbadb::cmd_loadimage: You do not have permission to load an image onto $node\n")
210 211
	if (!$isroot && !$this_user->IsAdmin() && 
	    !$node->AccessCheck($this_user, TB_NODEACCESS_LOADIMAGE));
212 213 214 215 216 217 218 219 220 221 222

    # If told not to wait, fork into the background.
    my $logname;
    if ($nowait) {
	(undef, $logname) = tempfile("tbadb-XXXXXX", TMPDIR => 1);
	die "tbadb::cmd_loadimage: Could not create temp logging file: $!\n"
	    if !$logname;
	return 0
	    if (TBBackGround($logname));
    }

223 224 225 226
    # Grab the RPC pipe.
    my ($rpcin, $rpcout) = GetRPCPipeHandles($node);
    die "tbadb::cmd_reboot: Failed to get valid SSH pipe filehandles!\n"
	if (!$rpcin || !$rpcout);
227

228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
    # Grab a lock for this image on remote side first.
    die "tbadb::cmd_loadimage: Failed to send 'lockimage' RPC!\n"
	if (!SendRPCData($rpcout, 
			 EncodeCall("lockimage",
				    {
					IMG_PROJ => $imagepid,
					IMG_NAME => $imagename,
				    })));
    my $pdu;
    die "tbadb::cmd_loadimage: Failed to receive valid response for 'checkimage'\n"
	if (RecvRPCData($rpcin, \$pdu, $TBADB_LOCKIMAGE_TMO) != 1);
    my $data = DecodeRPCData($pdu);
    die "tbadb::cmd_loadimage: Could not decode RPC response from 'lockimage'"
	if (!$data);
    if (exists($data->{ERROR})) {
	warn "tbadb::cmd_loadimage: Received error from 'lockimage':\n";
	warn "". Dumper($data);
	exit 1;
    }

248 249
    # Have remote side check for this image in its cache.
    die "tbadb::cmd_loadimage: Failed to send 'checkimage' RPC!\n"
250
	if (!SendRPCData($rpcout, 
251 252
			 EncodeCall("checkimage",
				    {
253
					IMG_PROJ => $imagepid,
254 255 256 257 258
					IMG_NAME => $imagename,
					IMG_TIME => $mtime,
					IMG_SIZE => $size,
				    })));
    die "tbadb::cmd_loadimage: Failed to receive valid response for 'checkimage'\n"
259
	if (RecvRPCData($rpcin, \$pdu, $TBADB_CHECKIMAGE_TMO) != 1);
260
    $data = DecodeRPCData($pdu);
261 262 263 264 265 266 267 268 269 270
    die "tbadb::cmd_loadimage: Could not decode RPC response from 'checkimage'"
	if (!$data);
    if (exists($data->{ERROR})) {
	warn "tbadb::cmd_loadimage: Received error from 'checkimage':\n";
	warn "". Dumper($data);
	exit 1;
    }

    # Transfer the image to the remote host if necessary (SCP).
    if ($data->{RESULT}->{NEED_IMG} == 1) {
271 272 273 274
	my $rhost;
	$node->TipServer(\$rhost);
	die "tbadb::cmd_loadimage: Could not lookup control server for $node!\n"
	    if (!$rhost);
275 276 277
	die "tbadb::cmd_loadimage: Malformed remote image path!\n"
	    if ($data->{RESULT}->{REMOTE_PATH} !~ /^([-\/\w]+)$/);
	my $rpath = $1;
278
	print "tbadb::cmd_loadimage: Transferring $imagename to $rhost\n";
279 280
	my $SAVEUID = $UID; 
	$EUID = $UID = 0; # Flip to root to run!
281
	die "tbadb::cmd_loadimage: Failed to transfer image to $rhost: $imagefile\n"
282
	    if (system($SCP, '-q', '-B', '-p', 
283
		       "$imagefile", "$rhost:$rpath/$imagename") != 0);
284
	$EUID = $UID = $SAVEUID; # Flip back.
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
	print "tbadb::cmd_loadimage: Transfer complete\n";
    }

    # Release our lock on the image.
    die "tbadb::cmd_loadimage: Failed to send 'unlockimage' RPC!\n"
	if (!SendRPCData($rpcout, 
			 EncodeCall("unlockimage",
				    {
					IMG_PROJ => $imagepid,
					IMG_NAME => $imagename,
				    })));
    die "tbadb::cmd_loadimage: Failed to receive valid response for 'unlockimage'\n"
	if (RecvRPCData($rpcin, \$pdu, $TBADB_UNLOCKIMAGE_TMO) != 1);
    $data = DecodeRPCData($pdu);
    die "tbadb::cmd_loadimage: Could not decode RPC response from 'unlockimage'"
	if (!$data);
    if (exists($data->{ERROR})) {
	warn "tbadb::cmd_loadimage: Received error from 'unlockimage':\n";
	warn "". Dumper($data);
	exit 1;
305 306 307 308 309
    }

    # Now that the image is (ostensibly) in place on the remote side,
    # ask the remote host to load it onto the device.
    die "tbadb::cmd_loadimage: Failed to send 'loadimage' RPC!\n"
310
	if (!SendRPCData($rpcout, 
311 312
			 EncodeCall("loadimage",
				    {
313 314
					IMG_PROJ => $imagepid,
					IMG_NAME => $imagename,
315 316 317
					NODE_ID  => $node_id,
				    })));
    die "tbadb::cmd_loadimage: Failed to receive response for 'loadimage'\n"
318
	if (RecvRPCData($rpcin, \$pdu, $TBADB_LOADIMAGE_TMO) != 1);
319 320 321 322 323 324 325 326 327
    $data = DecodeRPCData($pdu);
    die "tbadb::cmd_loadimage: Could not decode RPC response from 'loadimage'\n"
	if (!$data);
    if (exists($data->{ERROR}) || !exists($data->{RESULT}->{SUCCESS})) {
	warn "tbadb::cmd_loadimage: Received error from 'loadimage':\n";
	warn "". Dumper($data);
	exit 1;
    }

328
    # Tell stated that we've finished reloading the node if the node
329 330
    # is in the RELOADUE opmode.  This will push it along in the
    # reloading processes.
331 332 333 334
    my $opmode;
    if (TBGetNodeOpMode($node_id,\$opmode) && 
	$opmode eq TBDB_NODEOPMODE_RELOADUE) {
	TBSetNodeEventState($node_id,TBDB_NODESTATE_RELOADDONE);
335
	$node->ResetNextOpMode();
336 337 338
	TBSetNodeEventState($node_id,TBDB_NODESTATE_SHUTDOWN);
    }

339
    # Done with loading!
340
    print "tbadb::cmd_loadimage: Successfully loaded $imagename onto $node_id\n";
341 342 343 344 345 346 347

    # Activate forwarding if requested.  Will die() on error.
    if ($doforward) {
	cmd_forward($node_id);
    }

    # Remove log (if running in background) since there were no problems.
348 349 350
    if ($nowait && $logname) {
	unlink($logname);
    }
351

352
    return 0;
353 354
}

355
#
356
# Reserve a port for adb forwarding.  Must provide a valid device node_id
357
#
358
sub cmd_reserveport($@) {
359
    my ($node_id, $thost) = @_;
360 361

    # Check and untaint arguments
362
    die "tbadb::cmd_reserveport: missing arguments! (Need: <target_host>)\n"
363
	if (!$node_id || !$thost);
364
    die "tbadb::cmd_reserveport: malformed target host!"
365
	if ($thost !~ /^([-\.\w]{$MINHLEN,$MAXHLEN})$/);
366
    $thost = $1;
367

368 369
    # Make sure user has access to requested node
    my $node = Node->Lookup($node_id);
370
    die "tbadb::cmd_reserveport: Invalid node name $node_id!\n"
371
	if (!defined($node));
372
    die("tbadb::cmd_reserveport: You do not have permission to modify $node\n")
373 374
	if (!$isroot && !$this_user->IsAdmin() &&
	    !$node->AccessCheck($this_user, TB_NODEACCESS_REBOOT));
375 376 377
    
    # Grab the RPC pipe.
    my ($rpcin, $rpcout) = GetRPCPipeHandles($node);
378
    die "tbadb::cmd_reserveport: Failed to get valid SSH pipe filehandles!\n"
379 380
	if (!$rpcin || !$rpcout);
    
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
    # Call adb port reservation RPC.
    die "tbadb::cmd_reserveport: Failed to send 'reserveport' RPC!\n"
	if (!SendRPCData($rpcout,
			 EncodeCall("reserveport", {
			     NODE_ID => $node_id,
			     TARGET_HOST => $thost})));

    # Grab RPC result.
    my $pdu;
    die "tbadb::cmd_reserveport: Failed to receive valid response for 'reserveport'\n"
	if (RecvRPCData($rpcin, \$pdu, $TBADB_RESERVEPORT_TMO) != 1);
    my $data = DecodeRPCData($pdu);
    die "tbadb::cmd_reserveport: Could not decode RPC response from 'reserveport'"
	if (!$data);

    # Check for error.
    if (exists($data->{ERROR})) {
	warn "tbadb::cmd_reserveport: Received error from 'reserveport':\n";
	warn "". Dumper($data);
	exit 1;
    }

    # Grab the returned port number and store it.
    my $portnum = $data->{RESULT}->{PORT};
    if (!$portnum) {
	warn "tbadb::cmd_reserveport: Did not receive a port number from 'reserveport'!\n";
	exit 1;
    }
409 410 411 412 413 414

    # We store the returned port number in the virt_node_attributes table if
    # the node is currently allocated to an experiment.
    my $experiment = $node->Reservation();
    $experiment->SetVirtNodeAttribute($node->vname(), "adb_port", $portnum)
	if $experiment;
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446

    # Done!
    my $chost;
    $node->TipServer(\$chost);
    print "tbadb::cmd_reserveport: successfully reserved adb port for $node_id: $chost:$portnum\n";
    return 0;
}

#
# Activate ADB forwarding for a device.  A prior 'reserveport' RPC must
# have been done first!
#
sub cmd_forward($;@) {
    my ($node_id) = @_;

    # Check and untaint arguments
    die "tbadb::cmd_forward: missing node_id argument!\n"
	if (!$node_id);

    # Make sure user has access to requested node
    my $node = Node->Lookup($node_id);
    die "tbadb::cmd_forward: Invalid node name $node_id!\n"
	if (!defined($node));
    die "tbadb::cmd_forward: You do not have permission to access $node\n"
	if (!$isroot && !$this_user->IsAdmin() &&
	    !$node->AccessCheck($this_user, TB_NODEACCESS_REBOOT));

    # Grab the RPC pipe.
    my ($rpcin, $rpcout) = GetRPCPipeHandles($node);
    die "tbadb::cmd_reboot: Failed to get valid SSH pipe filehandles!\n"
	if (!$rpcin || !$rpcout);

447 448 449
    # Request adb port forwarding on device's control host.
    die "tbadb::cmd_forward: Failed to send 'forward' RPC!\n"
	if (!SendRPCData($rpcout, 
450
			 EncodeCall("forward", { NODE_ID => $node_id })));
451 452 453 454 455 456 457 458 459 460

    # Grab remote result.
    my $pdu;
    die "tbadb::cmd_forward: Failed to receive valid response for 'forward'\n"
	if (RecvRPCData($rpcin, \$pdu, $TBADB_FORWARD_TMO) != 1);
    my $data = DecodeRPCData($pdu);
    die "tbadb::cmd_forward: Could not decode RPC response from 'forward'"
	if (!$data);

    # Check returned result.
461
    if (exists($data->{ERROR})) {
462 463 464 465
	warn "tbadb::cmd_forward: Received error from 'forward':\n";
	warn "". Dumper($data);
	exit 1;
    }
466

467
    # Grab the returned port number and store it.
468 469 470 471 472
    my $portnum = $data->{RESULT}->{PORT};
    if (!$portnum) {
	warn "tbadb::cmd_forward: Did not receive a port number from 'forward'!\n";
	exit 1;
    }
473

474
    # Done!
475
    my $chost;
Kirk Webb's avatar
Kirk Webb committed
476
    $node->TipServer(\$chost);
477
    print "tbadb::cmd_forward: successfully setup adb port: $chost:$portnum\n";
478 479
    return 0;
}
480

481 482 483 484 485 486 487 488 489 490 491 492 493 494
#
# Clear a forwarding setup.  Must provide a valid device node_id
#
sub cmd_unforward($@) {
    my ($node_id) = @_;

    # Check and untaint arguments
    die "tbadb::cmd_unforward: missing node_id argument!"
	if (!$node_id);

    # Make sure user has access to requested node
    my $node = Node->Lookup($node_id);
    die "tbadb::cmd_unforward: Invalid node name $node_id!\n"
	if (!defined($node));
495 496 497 498 499
    # Fine to unforward if we are cleaning up inside of nfree.
    my $experiment = $node->Reservation();
    my $isholding = (defined($experiment)
		     && $experiment->pid() eq NFREELOCKED_PID
		     && $experiment->eid() eq NFREELOCKED_EID);
500
    die("tbadb::cmd_unforward: You do not have permission to modify $node\n")
501
	if (!$isroot && !$isholding &&
502
	    !$node->AccessCheck($this_user, TB_NODEACCESS_REBOOT));
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
    
    # Grab the RPC pipe.
    my ($rpcin, $rpcout) = GetRPCPipeHandles($node);
    die "tbadb::cmd_unforward: Failed to get valid SSH pipe filehandles!\n"
	if (!$rpcin || !$rpcout);
    
    # Clear adb port forwarding for the device via control host.
    die "tbadb::cmd_unforward: Failed to send 'unforward' RPC!\n"
	if (!SendRPCData($rpcout, 
			 EncodeCall("unforward", { NODE_ID => $node_id })));

    # Grab remote result.
    my $pdu;
    die "tbadb::cmd_unforward: Failed to receive valid response for 'unforward'\n"
	if (RecvRPCData($rpcin, \$pdu, $TBADB_FORWARD_TMO) != 1);
    my $data = DecodeRPCData($pdu);
    die "tbadb::cmd_unforward: Could not decode RPC response from 'unforward'"
	if (!$data);

    # Check returned result.
    if (exists($data->{ERROR}) || !exists($data->{RESULT}->{SUCCESS})) {
	warn "tbadb::cmd_unforward: Received error from 'unforward':\n";
	warn "". Dumper($data);
	exit 1;
    }
528 529 530 531 532 533 534 535 536 537

    # Clear the virt_node_attributes entry, if there is one.
    if ($experiment) {
	my $pid = $experiment->pid();
	my $eid = $experiment->eid();
	my $vname = $node->vname();
	DBQueryWarn("delete from virt_node_attributes".
		    " where pid='$pid' and eid='$eid'".
		    "       and vname='$vname' and attrkey='adb_port'");
    }
538 539
    
    # Done!
540
    print "tbadb::cmd_unforward: successfully removed adb fowarding for $node_id\n";
541 542 543
    return 0;
}

544 545 546
#
# Given a valid node_id, reboot a device.
#
547
sub cmd_reboot($;@) {
548
    my ($node_id, $suplcmd) = @_;
549

550 551 552
    # Check and untaint arguments;
    die "tbadb::cmd_reboot: node_id argument missing!\n"
	if (!$node_id);
553

554 555 556 557 558 559 560 561 562 563 564 565 566 567
    if ($suplcmd) {
        SW1: for ($suplcmd) {
	    /^wait$/ && do {
		$suplcmd = "WAIT";
		last SW1;
	    };
	    /^fastboot$/ && do {
		$suplcmd = "FASTBOOT";
		last SW1;
	    };
	    die "tbadb::cmd_reboot: Unknown supplementary command: $suplcmd\n";
	}
    }

568 569 570 571 572
    # Make sure user has access to requested node
    my $node = Node->Lookup($node_id);
    die "tbadb::cmd_reboot: Invalid node name $node_id!\n"
	if (!defined($node));
    die("tbadb::cmd_reboot: You do not have permission to reboot $node\n")
573 574
	if (!$isroot && !$this_user->IsAdmin() &&
	    !$node->AccessCheck($this_user, TB_NODEACCESS_REBOOT));
575

576 577
    # Grab the RPC pipe.
    my ($rpcin, $rpcout) = GetRPCPipeHandles($node);
578
    die "tbadb::cmd_reboot: Failed to get valid SSH pipe filehandles!\n"
579
	if (!$rpcin || !$rpcout);
580

581
    # Request device reboot via remote host.
582 583 584 585
    my $arghash = { NODE_ID => $node_id };
    if ($suplcmd) {
	$arghash->{$suplcmd} = 1;
    }
586
    die "tbadb::cmd_reboot: Failed to send 'reboot' RPC!\n"
587
	if (!SendRPCData($rpcout, 
588
			 EncodeCall("reboot", $arghash)));
589

590
    # Wait for reboot and grab returned result.
591 592
    my $pdu;
    die "tbadb::cmd_reboot: Failed to receive valid response for 'reboot'\n"
593
	if (RecvRPCData($rpcin, \$pdu, $TBADB_REBOOT_TMO) != 1);
594
    my $data = DecodeRPCData($pdu);
Kirk Webb's avatar
Kirk Webb committed
595
    die "tbadb::cmd_reboot: Could not decode RPC response from 'reboot'\n"
596 597
	if (!$data);

598
    # Check returned result.
599
    if (exists($data->{ERROR}) || !exists($data->{RESULT}->{SUCCESS})) {
600
	warn "tbadb::cmd_reboot: Received error from 'reboot':\n";
601
	warn "". Dumper($data);
602
	exit 1;
603
    }
604 605

    # Done!
606
    print "tbadb: Successfully rebooted $node_id\n";
607
    return 0;
608 609
}

Kirk Webb's avatar
Kirk Webb committed
610 611 612 613 614 615 616 617 618 619 620 621 622 623 624
#
# Given a valid node_id, wait for it to become available via adb.
#
sub cmd_nodewait($;@) {
    my ($node_id) = @_;

    # Check and untaint arguments;
    die "tbadb::cmd_nodewait: node_id argument missing!\n"
	if (!$node_id);

    # Make sure user has access to requested node
    my $node = Node->Lookup($node_id);
    die "tbadb::cmd_nodewait: Invalid node name $node_id!\n"
	if (!defined($node));
    die("tbadb::cmd_nodewait: You do not have permission to access $node\n")
625 626
	if (!$isroot && !$this_user->IsAdmin() &&
	    !$node->AccessCheck($this_user, TB_NODEACCESS_READINFO));
Kirk Webb's avatar
Kirk Webb committed
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660

    # Grab the RPC pipe.
    my ($rpcin, $rpcout) = GetRPCPipeHandles($node);
    die "tbadb::cmd_nodewait: Failed to get valid SSH pipe filehandles!\n"
	if (!$rpcin || !$rpcout);

    # Call nodewait RPC on device's console host.
    die "tbadb::cmd_nodewiat: 'nodewait' RPC failed for $node!\n"
	if (!SendRPCData($rpcout, 
			 EncodeCall("nodewait", { NODE_ID => $node_id })));

    # Wait for node.
    my $pdu;
    my $wres = RecvRPCData($rpcin, \$pdu, $TBADB_NODEWAIT_TMO);
    die "tbadb::cmd_nodewait: invalid response from 'nodewait'\n"
	if ($wres == 0);
    die "tbadb::cmd_nodewait: 'nodewait' timed out for $node_id\n"
	if ($wres == -1);
    my $data = DecodeRPCData($pdu);
    die "tbadb::cmd_nodewait: Could not decode RPC response from 'nodewait'\n"
	if (!$data);

    # Check returned result.
    if (exists($data->{ERROR}) || !exists($data->{RESULT}->{SUCCESS})) {
	warn "tbadb::cmd_nodewait: Received error from 'nodewait':\n";
	warn "". Dumper($data);
	exit 1;
    }

    # Done!
    print "tbadb::cmd_nodewait: $node_id is ready.\n";
    return 0;
}

661 662 663 664 665 666
# Helper that returns the RPC in/out pipe pair.  Establishes the remote
# connection if necessary.  Argument is a node object.
sub GetRPCPipeHandles($) {
    my ($node) = @_;
    my ($rpcin, $rpcout);

667 668
    # Look up the node's control (console) server and connect to it if
    # we haven't done so yet.  Otherwise grab and return the open pipe.
669 670 671 672
    my $conserver;
    $node->TipServer(\$conserver);
    die "tbadb::GetRPCPipeHandles: Could not lookup control server for $node!\n"
	if (!$conserver);
673 674
    if (!exists($RPCPIPES{$conserver})) {
	$RPCPIPES{$conserver} = ConnectRPCHost($conserver);
675
    }
676 677 678
    my $rpcpipe = $RPCPIPES{$conserver};
    die "tbadb::GetRPCPipeHandles: RPC pipe for $conserver closed unexpectedly!"
	if (!$rpcpipe->connected());
679

680
    return ($rpcpipe, $rpcpipe);
681 682
}

683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700
# Helper that connects to a remote TBADB RPC proxy service.
sub ConnectRPCHost($) {
    my ($host) = @_;

    # Connect and read in expected "hello" message.
    my $socket = 
	IO::Socket::INET->new(
	    PeerAddr => $host,
	    PeerPort => TBADB_PORT,
	    Proto    => 'tcp'
	);
    die "tbadb::ConnectRPCHost: Could not connect to tbadb proxy on host $host: $!\n"
	if (!$socket);
    $socket->autoflush(1);
    my $pdu;
    my $res = RecvRPCData($socket, \$pdu, $TBADB_HELLO_TMO);
    if ($res == -1) {
	die "tbadb::ConnectRPCHost: Timeout while opening RPC Pipe!\n";
701
    }
702 703
    elsif ($res == 0) {
	die "tbadb::ConnectRPCHost: Error encountered while opening RPC Pipe!\n";
704
    }
705 706 707 708 709 710 711 712
    # Look for the hello.
    my $hello = DecodeRPCData($pdu);
    die "tbadb::ConnectRPCHost: Unexpected data received when opening RPC Pipe!\n"
	if (!$hello);
    die "tbadb::ConnectRPCHost: Did not receive valid 'hello' from remote end!\n"
	if (!exists($hello->{RESULT}->{HELLO}));

    return $socket;
713
}