Commit 33251a18 authored by Mac Newbold's avatar Mac Newbold

The first working version of the StateWait library. The API changed a bit:

# $rv = initStateWait(\@states, @nodes);
#
# Call this first. Make sure to call it _before_ performing any
# action that may trigger one or more of the states you're
# expecting to see. Each node must see the list of states in the
# proper order, ie if @states=(stateA, stateB), it will not
# complete until the first time stateB is seen after stateA has
# been seen. Returns 0 on success.
#
# $rv = waitForState(\@finished, \@failed[, $timeout);
#
# Do the actual waiting. Blocks until each node has either reached the
# desired state(s) or failed, or timeout is reached (if given and
# non-zero). Returns lists of nodes.
#
# $rv = endStateWait();
#
# Stop listening for states. Call this soon after waitForState.
# This must be called before calling initStateWait again.

Also, commit a command line tool that uses the lib. The waitForState
script can be used by other programs to do the state waiting for you, or
you can use the lib directly for more control, using this script as an
example of how to do it.
parent 8a7a353f
...@@ -19,10 +19,11 @@ ...@@ -19,10 +19,11 @@
# complete until the first time stateB is seen after stateA has # complete until the first time stateB is seen after stateA has
# been seen. Returns 0 on success. # been seen. Returns 0 on success.
# #
# $rv = waitForState(\@failed); # $rv = waitForState(\@finished, \@failed[, $timeout);
# #
# Do the actual waiting. Blocks until each node has either reached # Do the actual waiting. Blocks until each node has either reached the
# the desired state(s) or failed. Returns number of failures. # desired state(s) or failed, or timeout is reached (if given and
# non-zero). Returns lists of nodes.
# #
# $rv = endStateWait(); # $rv = endStateWait();
# #
...@@ -37,51 +38,161 @@ use Exporter; ...@@ -37,51 +38,161 @@ use Exporter;
@EXPORT = qw ( initStateWait waitForState endStateWait ); @EXPORT = qw ( initStateWait waitForState endStateWait );
# Any 'use' or 'require' stuff must come here, after 'package' # Any 'use' or 'require' stuff must come here, after 'package'
# Note that whatever script is using us has already done a "use lib" to
# be able to use us. So we don't need to do one, or be configged.
use event; use event;
use libtestbed;
use libdb;
my $server = "boss"; # Set these to nothing here, so they can be set before calling init
my $port = ""; # by the caller
my $URL = "elvin://$server"; # Don't my them, or it won't work!
$server = "";
$port = "";
$URL = "";
$debug = 0;
my $handle;
my $tuple;
my @done = ();
my @failures = ();
my @nodes = ();
my %remain = ();
# #
# Exported Sub-Routines / Functions # Exported Sub-Routines / Functions
# #
sub initStateWait( $@ ) { sub initStateWait( $@ ) {
my ($states, @nodes) = @_; my $states = shift;
@nodes = @_;
# states is an arrayref # states is an arrayref
print "initStateWait: states=(".join(",",@$states). if ($debug) {
") nodes=(".join(",",@nodes).")\n"; print "initStateWait: states=(".join(",",@$states).
") nodes=(".join(",",@nodes).")\n";
}
if ($server eq "") { $server = TB_BOSSNODE; }
if ($URL eq "") { $URL = "elvin://$server"; }
if ($port ne "") { $URL .= ":$port"; }
# Do the subscription for the right stuff, including all the
# states for all the nodes, and the failure event for all nodes
$handle = event_register($URL,0);
if (!$handle) { die "Unable to register with event system\n"; }
$tuple = address_tuple_alloc();
if (!$tuple) { die "Could not allocate an address tuple\n"; }
%$tuple = ( objtype => TBDB_TBEVENT_NODESTATE #,
eventtype => join(",",@$states),
objname => join(",",@nodes) );
if ($debug > 1) {
print "tuple = ('".join("', '",keys(%$tuple))."') => ('".
join("', '",values(%$tuple))."')\n";
}
if (!event_subscribe($handle,\&doEvent,$tuple)) {
die "Could not subscribe to events\n";
}
foreach $n (@nodes) {
my @l = @$states;
$remain{$n} = \@l;
}
if ($debug) {
foreach $n (@nodes) { print "$n==>(".join(",",@{$remain{$n}}).")\n"; }
}
return 0; return 0;
} }
sub waitForState( $ ) { sub doEvent( $$$ ) {
my ($failed) = @_; my ($handle,$event) = @_;
# failed is an arrayref
@$failed=(); my $time = time();
print "waitForState: failed=(".join(",",@$failed).")\n"; my $site = event_notification_get_site($handle, $event);
my $expt = event_notification_get_expt($handle, $event);
my $group = event_notification_get_group($handle, $event);
my $host = event_notification_get_host($handle, $event);
my $objtype = event_notification_get_objtype($handle, $event);
my $objname = event_notification_get_objname($handle, $event);
my $eventtype = event_notification_get_eventtype($handle, $event);
if ($debug > 1) {
print "Event: $time $site $expt $group $host $objtype $objname " .
"$eventtype\n";
}
my $n = $objname;
if (@{$remain{$n}}[0] eq $eventtype) {
# this is the next state we were waiting for
if ($debug) { print "Got $eventtype for $n\n" };
shift(@{$remain{$n}});
if ($debug) { print "$n==>(".join(",",@{$remain{$n}}).")\n"; }
if (scalar(@{$remain{$n}}) == 0) {
if ($debug) { print "Done with $n!\n"; }
push(@done,$n);
delete($remain{$n});
}
}
#exit(0);
}
sub waitForState( $$;$ ) {
my ($finished,$failed,$timeout) = @_;
if (!defined($timeout)) { $timeout=0; }
my $now = ($timeout > 0 ? time() : 0);
my $deadline = ($timeout > 0 ? $now + $timeout : 1);
my $total = scalar(@done) + scalar(@failures);
while ($total < scalar(@nodes) && $now < $deadline) {
if ($timeout > 0) {
if ($debug) {
print "waitForState: done=(".join(",",@done)."), ".
"failures=(".join(",",@failures).")\n";
print "Polling for ".($deadline-$now)." seconds\n";
}
event_poll_blocking($handle,($deadline-$now)*1000);
$now = time();
} else {
event_poll_blocking($handle,0);
}
$total = scalar(@done) + scalar(@failures);
}
# finished and failed are arrayrefs
@$finished=@done;
@$failed=@failures;
if ($debug) {
print "waitForState: finished=(".join(",",@$finished)."), ".
"failed=(".join(",",@$failed).")\n";
}
return 0; return 0;
} }
sub endStateWait() { sub endStateWait() {
print "endStateWait\n"; if (event_unregister($handle) == 0) {
die "Unable to unregister with event system\n";
}
if ($debug) { print "endStateWait\n"; }
return 0; return 0;
} }
if (1) { # Don't enable this unless you fix it first...
if (0) {
# do some testing... # do some testing...
my @states=("SHUTDOWN","ISUP"); my @states=("SHUTDOWN","ISUP");
my @finished=();
my @failed=("foo"); my @failed=("foo");
print "Failed was (".join(",",@failed).")\n"; print "Failed was (".join(",",@failed).")\n";
initStateWait(["ISUP"],pc52); initStateWait(["ISUP"],pc52);
waitForState(\@failed); waitForState(\@finished,\@failed);
endStateWait(); endStateWait();
print "Failed was (".join(",",@failed).")\n"; print "Failed was (".join(",",@failed).")\n";
initStateWait(\@states,pc31,pc32); initStateWait(\@states,pc31,pc32);
waitForState(\@failed); waitForState(\@finished,\@failed);
endStateWait(); endStateWait();
print "Failed was (".join(",",@failed).")\n"; print "Failed was (".join(",",@failed).")\n";
......
...@@ -9,3 +9,95 @@ ...@@ -9,3 +9,95 @@
# waitForState - a command line tool that uses the StateWait library # waitForState - a command line tool that uses the StateWait library
# #
use lib '@prefix@/lib';
use StateWait;
use Getopt::Std;
my $debug = 0;
my @states=('ISUP');
my $u = 10;
my $t = 300;
my %opt = ();
getopts("hds:u:t:",\%opt);
if ($opt{h}) { usage(); }
if ($opt{d}) { $debug++; }
if ($opt{u}) { $u = $opt{u}; }
if ($opt{t}) { $t = $opt{t}; }
if ($opt{s}) { @states = split(",",$opt{s}); }
if (!@ARGV) { usage(); }
my @nodes = @ARGV;
sub usage {
print <<EOF;
Usage:
$0 [-h] [-d]
[-u <update freq.>] [-t <timeout>]
[-s state1[,s2]] <node> [<node> ...]
Waits for all of the nodes listed to get to the right state. For a
list of states, each node must go through those states in the right
order (non-consecutively). If no state is supplied, ISUP is
used. Update frequency is how many seconds apart status updates should
be (default $u). Timeout is how many seconds we should wait before
giving up and returning (default $t). The -h option shows this
message, and -d enables extra debugging output.
EOF
exit(1);
}
if ($debug) { $StateWait::debug = $debug; }
debug("Calling initStateWait([".join(",",@states)."],".join(",",@nodes).")\n");
initStateWait(\@states,@nodes);
my $total = scalar(@nodes);
my $done = 0;
my $start = time();
my $now = $start;
my @finished = ();
my @failed = ();
debug("Start waiting at $start, stop at ".($start+$t)." - ".
$total." nodes\n");
debug("COND: now-start=".($now-$start).",t=$t,u=$u,done=$done,total=$total\n");
while ( ($now - $start) < $t && $done < $total ) {
my $wait = min($u,$start + $t - $now);
debug("Calling waitForState(\@finished,\@failed,$wait) at $now\n");
waitForState(\@finished, \@failed,$wait);
$now = time();
$done = scalar(@finished) + scalar(@failed);
debug("Returned at $now: finished=".join(",",@finished).
" failed=".join(",",@failed)."\n".
"Still waiting for ".($total-$done)." nodes\n");
debug("COND: now-start=".($now-$start).",t=$t, done=$done, total=$total\n");
}
debug("All done at $now: finished=".join(",",@finished).
" failed=".join(",",@failed)."\n");
debug("Calling endStateWait()\n");
endStateWait();
my $worked = scalar(@finished);
my $failed = scalar(@finished);
my $remain = $total - $worked - $failed;
if ($worked != $total) {
print "*** waitForState: Only $worked nodes of $total succeeded!\n";
if ($failed) { print "\tThere were $failed failures.\n"; }
if ($remain) { print "\tThere were $remain nodes that timed out.\n"; }
}
exit(0);
sub max ( $$ ) {
return ($_[0] > $_[1] ? $_[0] : $_[1]);
}
sub min ( $$ ) {
return ($_[0] < $_[1] ? $_[0] : $_[1]);
}
sub debug {
if ($debug) { print @_; }
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment